Newer
Older
// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gc
import (
"bytes"
"fmt"
"internal/testenv"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"regexp"
"runtime"
"strings"
"testing"
)
// This file contains code generation tests.
//
// Each test is defined in a variable of type asmTest. Tests are
// architecture-specific, and they are grouped in arrays of tests, one
// for each architecture.
//
// Each asmTest consists of a function to compile, an array of
// positive regexps that must match the generated assembly and
// an array of negative regexps that must not match generated assembly.
// For example, the following amd64 test
//
// {
// func f0(x int) int {
// return x * 64
// }
// `,
// pos: []string{"\tSHLQ\t[$]6,"},
// neg: []string{"MULQ"}
// }
//
// verifies that the code the compiler generates for a multiplication
// by 64 contains a 'SHLQ' instruction and does not contain a MULQ.
//
// Since all the tests for a given architecture are dumped in the same
// file, the function names must be unique. As a workaround for this
// restriction, the test harness supports the use of a '$' placeholder
// for function names. The func f0 above can be also written as
//
// {
// func $(x int) int {
// return x * 64
// }
// `,
// pos: []string{"\tSHLQ\t[$]6,"},
// neg: []string{"MULQ"}
// }
//
// Each '$'-function will be given a unique name of form f<N>_<arch>,
// where <N> is the test index in the test array, and <arch> is the
// test's architecture.
//
// It is allowed to mix named and unnamed functions in the same test
// array; the named functions will retain their original names.
// TestAssembly checks to make sure the assembly generated for
// functions contains certain expected instructions.
func TestAssembly(t *testing.T) {
testenv.MustHaveGoBuild(t)
if runtime.GOOS == "windows" {
// TODO: remove if we can get "go tool compile -S" to work on windows.
t.Skipf("skipping test: recursive windows compile not working")
}
dir, err := ioutil.TempDir("", "TestAssembly")
if err != nil {
t.Fatalf("could not create directory: %v", err)
}
defer os.RemoveAll(dir)
nameRegexp := regexp.MustCompile("func \\w+")
t.Run("platform", func(t *testing.T) {
for _, ats := range allAsmTests {
ats := ats
t.Run(ats.os+"/"+ats.arch, func(tt *testing.T) {
tt.Parallel()
asm := ats.compileToAsm(tt, dir)
for i, at := range ats.tests {
var funcName string
if strings.Contains(at.fn, "func $") {
funcName = fmt.Sprintf("f%d_%s", i, ats.arch)
} else {
funcName = nameRegexp.FindString(at.fn)[len("func "):]
fa := funcAsm(tt, asm, funcName)
if fa != "" {
at.verifyAsm(tt, fa)
}
var nextTextRegexp = regexp.MustCompile(`\n\S`)
// funcAsm returns the assembly listing for the given function name.
func funcAsm(t *testing.T, asm string, funcName string) string {
if i := strings.Index(asm, fmt.Sprintf("TEXT\t\"\".%s(SB)", funcName)); i >= 0 {
} else {
t.Errorf("could not find assembly for function %v", funcName)
return ""
// Find the next line that doesn't begin with whitespace.
loc := nextTextRegexp.FindStringIndex(asm)
if loc != nil {
asm = asm[:loc[0]]
}
return asm
}
type asmTest struct {
// function to compile
fn string
// regular expressions that must match the generated assembly
pos []string
// regular expressions that must not match the generated assembly
neg []string
}
func (at asmTest) verifyAsm(t *testing.T, fa string) {
if b, err := regexp.MatchString(r, fa); !b || err != nil {
t.Errorf("expected:%s\ngo:%s\nasm:%s\n", r, at.fn, fa)
if b, err := regexp.MatchString(r, fa); b || err != nil {
t.Errorf("not expected:%s\ngo:%s\nasm:%s\n", r, at.fn, fa)
}
}
type asmTests struct {
arch string
os string
imports []string
tests []*asmTest
}
func (ats *asmTests) generateCode() []byte {
var buf bytes.Buffer
fmt.Fprintln(&buf, "package main")
for _, s := range ats.imports {
fmt.Fprintf(&buf, "import %q\n", s)
}
for i, t := range ats.tests {
function := strings.Replace(t.fn, "func $", fmt.Sprintf("func f%d_%s", i, ats.arch), 1)
fmt.Fprintln(&buf, function)
}
return buf.Bytes()
}
// compile compiles the package pkg for architecture arch and
// returns the generated assembly. dir is a scratch directory.
func (ats *asmTests) compileToAsm(t *testing.T, dir string) string {
// create test directory
testDir := filepath.Join(dir, fmt.Sprintf("%s_%s", ats.arch, ats.os))
err := os.Mkdir(testDir, 0700)
if err != nil {
t.Fatalf("could not create directory: %v", err)
}
// Create source.
src := filepath.Join(testDir, "test.go")
err = ioutil.WriteFile(src, ats.generateCode(), 0600)
if err != nil {
t.Fatalf("error writing code: %v", err)
// First, install any dependencies we need. This builds the required export data
// for any packages that are imported.
for _, i := range ats.imports {
out := filepath.Join(testDir, i+".a")
if s := ats.runGo(t, "build", "-o", out, "-gcflags=-dolinkobj=false", i); s != "" {
t.Fatalf("Stdout = %s\nWant empty", s)
}
}
// Now, compile the individual file for which we want to see the generated assembly.
asm := ats.runGo(t, "tool", "compile", "-I", testDir, "-S", "-o", filepath.Join(testDir, "out.o"), src)
return asm
}
// runGo runs go command with the given args and returns stdout string.
// go is run with GOARCH and GOOS set as ats.arch and ats.os respectively
func (ats *asmTests) runGo(t *testing.T, args ...string) string {
var stdout, stderr bytes.Buffer
cmd := exec.Command(testenv.GoToolPath(t), args...)
cmd.Env = append(os.Environ(), "GOARCH="+ats.arch, "GOOS="+ats.os)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
if err := cmd.Run(); err != nil {
t.Fatalf("error running cmd: %v\nstdout:\n%sstderr:\n%s\n", err, stdout.String(), stderr.String())
if s := stderr.String(); s != "" {
t.Fatalf("Stderr = %s\nWant empty", s)
return stdout.String()
}
var allAsmTests = []*asmTests{
{
arch: "amd64",
os: "linux",
imports: []string{"unsafe", "runtime"},
tests: linuxAMD64Tests,
},
{
arch: "386",
os: "linux",
tests: linux386Tests,
arch: "s390x",
os: "linux",
tests: linuxS390XTests,
arch: "arm",
os: "linux",
imports: []string{"runtime"},
tests: linuxARMTests,
arch: "arm64",
os: "linux",
tests: linuxARM64Tests,
arch: "mips",
os: "linux",
tests: linuxMIPSTests,
arch: "mips64",
os: "linux",
tests: linuxMIPS64Tests,
arch: "ppc64le",
os: "linux",
tests: linuxPPC64LETests,
{
arch: "amd64",
os: "plan9",
tests: plan9AMD64Tests,
},
{
fn: `
func $(x int) int {
pos: []string{"\tSHLQ\t\\$5,", "\tLEAQ\t\\(.*\\)\\(.*\\*2\\),"},
// Structure zeroing. See issue #18370.
type T1 struct {
a, b, c int
}
func $(t *T1) {
pos: []string{"\tXORPS\tX., X", "\tMOVUPS\tX., \\(.*\\)", "\tMOVQ\t\\$0, 16\\(.*\\)"},
// SSA-able composite literal initialization. Issue 18872.
{
type T18872 struct {
a, b, c, d int
}
func f18872(p *T18872) {
*p = T18872{1, 2, 3, 4}
}
`,
pos: []string{"\tMOVQ\t[$]1", "\tMOVQ\t[$]2", "\tMOVQ\t[$]3", "\tMOVQ\t[$]4"},
},
// Also test struct containing pointers (this was special because of write barriers).
type T2 struct {
a, b, c *int
}
func f19(t *T2) {
*t = T2{}
}
`,
pos: []string{"\tXORPS\tX., X", "\tMOVUPS\tX., \\(.*\\)", "\tMOVQ\t\\$0, 16\\(.*\\)", "\tCALL\truntime\\.gcWriteBarrier\\(SB\\)"},
func f20(x uint64) uint64 {
return x<<7 | x>>57
}
`,
pos: []string{"\tROLQ\t[$]7,"},
func f21(x uint64) uint64 {
return x<<7 + x>>57
}
`,
pos: []string{"\tROLQ\t[$]7,"},
func f22(x uint64) uint64 {
return x<<7 ^ x>>57
}
`,
pos: []string{"\tROLQ\t[$]7,"},
func f23(x uint32) uint32 {
return x<<7 + x>>25
}
`,
pos: []string{"\tROLL\t[$]7,"},
func f24(x uint32) uint32 {
return x<<7 | x>>25
}
`,
pos: []string{"\tROLL\t[$]7,"},
func f25(x uint32) uint32 {
return x<<7 ^ x>>25
}
`,
pos: []string{"\tROLL\t[$]7,"},
func f26(x uint16) uint16 {
return x<<7 + x>>9
}
`,
pos: []string{"\tROLW\t[$]7,"},
func f27(x uint16) uint16 {
return x<<7 | x>>9
}
`,
pos: []string{"\tROLW\t[$]7,"},
func f28(x uint16) uint16 {
return x<<7 ^ x>>9
}
`,
pos: []string{"\tROLW\t[$]7,"},
func f29(x uint8) uint8 {
return x<<7 + x>>1
}
`,
pos: []string{"\tROLB\t[$]7,"},
func f30(x uint8) uint8 {
return x<<7 | x>>1
}
`,
pos: []string{"\tROLB\t[$]7,"},
func f31(x uint8) uint8 {
return x<<7 ^ x>>1
}
`,
pos: []string{"\tROLB\t[$]7,"},
// Rotate after inlining (see issue 18254).
{
func f32(x uint32) uint32 {
return g(x, 7)
}
func g(x uint32, k uint) uint32 {
return x<<k | x>>(32-k)
}
pos: []string{"\tROLL\t[$]7,"},
func f33(m map[int]int) int {
return m[5]
}
`,
pos: []string{"\tMOVQ\t[$]5,"},
// Direct use of constants in fast map access calls. Issue 19015.
{
func f34(m map[int]int) bool {
_, ok := m[5]
return ok
}
`,
pos: []string{"\tMOVQ\t[$]5,"},
func f35(m map[string]int) int {
return m["abc"]
}
`,
func f36(m map[string]int) bool {
_, ok := m["abc"]
return ok
}
`,
// Bit test ops on amd64, issue 18943.
{
func f37(a, b uint64) int {
if a&(1<<(b&63)) != 0 {
return 1
}
return -1
}
`,
func f38(a, b uint64) bool {
return a&(1<<(b&63)) != 0
}
`,
func f39(a uint64) int {
if a&(1<<60) != 0 {
return 1
}
return -1
}
`,
pos: []string{"\tBTQ\t\\$60"},
func f40(a uint64) bool {
return a&(1<<60) != 0
}
`,
pos: []string{"\tBTQ\t\\$60"},
// see issue 19595.
// We want to merge load+op in f58, but not in f59.
{
func f58(p, q *int) {
x := *p
*q += x
}`,
pos: []string{"\tADDQ\t\\("},
func f59(p, q *int) {
x := *p
for i := 0; i < 10; i++ {
*q += x
}
}`,
pos: []string{"\tADDQ\t[A-Z]"},
// Floating-point strength reduction
{
func f60(f float64) float64 {
return f * 2.0
}`,
func f62(f float64) float64 {
return f / 16.0
}`,
func f63(f float64) float64 {
return f / 0.125
}`,
func f64(f float64) float64 {
return f / 0.5
}`,
// Check that compare to constant string uses 2/4/8 byte compares
{
func f65(a string) bool {
return a == "xx"
}`,
pos: []string{"\tCMPW\t\\(.*\\), [$]"},
func f66(a string) bool {
return a == "xxxx"
}`,
pos: []string{"\tCMPL\t\\(.*\\), [$]"},
func f67(a string) bool {
return a == "xxxxxxxx"
}`,
pos: []string{"\tCMPQ\t[A-Z]"},
// Non-constant rotate
{
fn: `func rot64l(x uint64, y int) uint64 {
z := uint(y & 63)
return x << z | x >> (64-z)
}`,
fn: `func rot64r(x uint64, y int) uint64 {
z := uint(y & 63)
return x >> z | x << (64-z)
}`,
fn: `func rot32l(x uint32, y int) uint32 {
z := uint(y & 31)
return x << z | x >> (32-z)
}`,
fn: `func rot32r(x uint32, y int) uint32 {
z := uint(y & 31)
return x >> z | x << (32-z)
}`,
fn: `func rot16l(x uint16, y int) uint16 {
z := uint(y & 15)
return x << z | x >> (16-z)
}`,
fn: `func rot16r(x uint16, y int) uint16 {
z := uint(y & 15)
return x >> z | x << (16-z)
}`,
fn: `func rot8l(x uint8, y int) uint8 {
z := uint(y & 7)
return x << z | x >> (8-z)
}`,
fn: `func rot8r(x uint8, y int) uint8 {
z := uint(y & 7)
return x >> z | x << (8-z)
}`,
// Check that array compare uses 2/4/8 byte compares
{
func f68(a,b [2]byte) bool {
return a == b
}`,
pos: []string{"\tCMPW\t\"\"[.+_a-z0-9]+\\(SP\\), [A-Z]"},
func f69(a,b [3]uint16) bool {
return a == b
}`,
pos: []string{
"\tCMPL\t\"\"[.+_a-z0-9]+\\(SP\\), [A-Z]",
"\tCMPW\t\"\"[.+_a-z0-9]+\\(SP\\), [A-Z]",
},
{
fn: `
func $(a,b [3]int16) bool {
return a == b
}`,
pos: []string{
"\tCMPL\t\"\"[.+_a-z0-9]+\\(SP\\), [A-Z]",
"\tCMPW\t\"\"[.+_a-z0-9]+\\(SP\\), [A-Z]",
},
},
{
fn: `
func $(a,b [12]int8) bool {
return a == b
}`,
pos: []string{
"\tCMPQ\t\"\"[.+_a-z0-9]+\\(SP\\), [A-Z]",
"\tCMPL\t\"\"[.+_a-z0-9]+\\(SP\\), [A-Z]",
},
func f70(a,b [15]byte) bool {
return a == b
}`,
pos: []string{"\tCMPQ\t\"\"[.+_a-z0-9]+\\(SP\\), [A-Z]"},
func f71(a,b unsafe.Pointer) bool { // This was a TODO in mapaccess1_faststr
return *((*[4]byte)(a)) != *((*[4]byte)(b))
}`,
pos: []string{"\tCMPL\t\\(.*\\), [A-Z]"},
{
// make sure assembly output has matching offset and base register.
func f72(a, b int) int {
runtime.GC() // use some frame
return b
}
`,
pos: []string{"b\\+24\\(SP\\)"},
},
{
// check load combining
func f73(a, b byte) (byte,byte) {
return f73(f73(a,b))
}
`,
},
{
func f74(a, b uint16) (uint16,uint16) {
return f74(f74(a,b))
}
`,
},
{
func f75(a, b uint32) (uint32,uint32) {
return f75(f75(a,b))
}
`,
},
// Make sure we don't put pointers in SSE registers across safe points.
{
func $(p, q *[2]*int) {
a, b := p[0], p[1]
runtime.GC()
q[0], q[1] = a, b
}
`,
{
// check that stack store is optimized away
func $() int {
var x int
return *(&x)
}
`,
pos: []string{"TEXT\t.*, [$]0-8"},
// int <-> fp moves
{
fn: `
func $(x uint32) bool {
return x > 4
}
`,
pos: []string{"\tSETHI\t.*\\(SP\\)"},
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
// Check that len() and cap() div by a constant power of two
// are compiled into SHRQ.
{
fn: `
func $(a []int) int {
return len(a) / 1024
}
`,
pos: []string{"\tSHRQ\t\\$10,"},
},
{
fn: `
func $(s string) int {
return len(s) / (4097 >> 1)
}
`,
pos: []string{"\tSHRQ\t\\$11,"},
},
{
fn: `
func $(a []int) int {
return cap(a) / ((1 << 11) + 2048)
}
`,
pos: []string{"\tSHRQ\t\\$12,"},
},
// Check that len() and cap() mod by a constant power of two
// are compiled into ANDQ.
{
fn: `
func $(a []int) int {
return len(a) % 1024
}
`,
pos: []string{"\tANDQ\t\\$1023,"},
},
{
fn: `
func $(s string) int {
return len(s) % (4097 >> 1)
}
`,
pos: []string{"\tANDQ\t\\$2047,"},
},
{
fn: `
func $(a []int) int {
return cap(a) % ((1 << 11) + 2048)
}
`,
pos: []string{"\tANDQ\t\\$4095,"},
},
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
{
// Test that small memmove was replaced with direct movs
fn: `
func $() {
x := [...]byte{1, 2, 3, 4, 5, 6, 7}
copy(x[1:], x[:])
}
`,
neg: []string{"memmove"},
},
{
// Same as above but with different size
fn: `
func $() {
x := [...]byte{1, 2, 3, 4}
copy(x[1:], x[:])
}
`,
neg: []string{"memmove"},
},
{
// Same as above but with different size
fn: `
func $() {
x := [...]byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
copy(x[1:], x[:])
}
`,
neg: []string{"memmove"},
},
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
{
fn: `
func $(p int, q *int) bool {
return p < *q
}
`,
pos: []string{"CMPQ\t\\(.*\\), [A-Z]"},
},
{
fn: `
func $(p *int, q int) bool {
return *p < q
}
`,
pos: []string{"CMPQ\t\\(.*\\), [A-Z]"},
},
{
fn: `
func $(p *int) bool {
return *p < 7
}
`,
pos: []string{"CMPQ\t\\(.*\\), [$]7"},
},
{
fn: `
func $(p *int) bool {
return 7 < *p
}
`,
pos: []string{"CMPQ\t\\(.*\\), [$]7"},
},
{
fn: `
func $(p **int) {
*p = nil
}
`,
pos: []string{"CMPL\truntime.writeBarrier\\(SB\\), [$]0"},
},
{
// check that stack store is optimized away
func $() int {
var x int
return *(&x)
}
`,
pos: []string{"TEXT\t.*, [$]0-4"},
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
// Check that len() and cap() div by a constant power of two
// are compiled into SHRL.
{
fn: `
func $(a []int) int {
return len(a) / 1024
}
`,
pos: []string{"\tSHRL\t\\$10,"},
},
{
fn: `
func $(s string) int {
return len(s) / (4097 >> 1)
}
`,
pos: []string{"\tSHRL\t\\$11,"},
},
{
fn: `
func $(a []int) int {
return cap(a) / ((1 << 11) + 2048)
}
`,
pos: []string{"\tSHRL\t\\$12,"},
},
// Check that len() and cap() mod by a constant power of two
// are compiled into ANDL.
{
fn: `
func $(a []int) int {
return len(a) % 1024
}
`,
pos: []string{"\tANDL\t\\$1023,"},
},
{
fn: `
func $(s string) int {
return len(s) % (4097 >> 1)
}
`,
pos: []string{"\tANDL\t\\$2047,"},
},
{
fn: `
func $(a []int) int {
return cap(a) % ((1 << 11) + 2048)
}
`,
pos: []string{"\tANDL\t\\$4095,"},
},
{
// Test that small memmove was replaced with direct movs
fn: `
func $() {
x := [...]byte{1, 2, 3, 4, 5, 6, 7}
copy(x[1:], x[:])
}
`,
neg: []string{"memmove"},
},
{
// Same as above but with different size
fn: `
func $() {
x := [...]byte{1, 2, 3, 4}
copy(x[1:], x[:])
}
`,
neg: []string{"memmove"},
},
}
var linuxS390XTests = []*asmTest{
{
func f8(x uint64) uint64 {
return x<<7 + x>>57
}
`,
pos: []string{"\tRLLG\t[$]7,"},
func f9(x uint64) uint64 {
return x<<7 | x>>57
}
`,
pos: []string{"\tRLLG\t[$]7,"},
func f10(x uint64) uint64 {
return x<<7 ^ x>>57
}
`,
pos: []string{"\tRLLG\t[$]7,"},
func f11(x uint32) uint32 {
return x<<7 + x>>25
}
`,
pos: []string{"\tRLL\t[$]7,"},
func f12(x uint32) uint32 {
return x<<7 | x>>25
}
`,
pos: []string{"\tRLL\t[$]7,"},
func f13(x uint32) uint32 {
return x<<7 ^ x>>25