| // 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 |
| // |
| // { |
| // fn: ` |
| // 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 |
| // |
| // { |
| // fn: ` |
| // 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 { |
| asm = asm[i:] |
| } 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) { |
| for _, r := range at.pos { |
| if b, err := regexp.MatchString(r, fa); !b || err != nil { |
| t.Errorf("expected:%s\ngo:%s\nasm:%s\n", r, at.fn, fa) |
| } |
| } |
| for _, r := range at.neg { |
| 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{"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, |
| }, |
| } |
| |
| var linuxAMD64Tests = []*asmTest{ |
| { |
| fn: ` |
| func $(x int) int { |
| return x * 96 |
| } |
| `, |
| pos: []string{"\tSHLQ\t\\$5,", "\tLEAQ\t\\(.*\\)\\(.*\\*2\\),"}, |
| }, |
| // Bit test ops on amd64, issue 18943. |
| { |
| fn: ` |
| func f37(a, b uint64) int { |
| if a&(1<<(b&63)) != 0 { |
| return 1 |
| } |
| return -1 |
| } |
| `, |
| pos: []string{"\tBTQ\t"}, |
| }, |
| { |
| fn: ` |
| func f38(a, b uint64) bool { |
| return a&(1<<(b&63)) != 0 |
| } |
| `, |
| pos: []string{"\tBTQ\t"}, |
| }, |
| { |
| fn: ` |
| func f39(a uint64) int { |
| if a&(1<<60) != 0 { |
| return 1 |
| } |
| return -1 |
| } |
| `, |
| pos: []string{"\tBTQ\t\\$60"}, |
| }, |
| { |
| fn: ` |
| 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. |
| { |
| fn: ` |
| func f58(p, q *int) { |
| x := *p |
| *q += x |
| }`, |
| pos: []string{"\tADDQ\t\\("}, |
| }, |
| { |
| fn: ` |
| func f59(p, q *int) { |
| x := *p |
| for i := 0; i < 10; i++ { |
| *q += x |
| } |
| }`, |
| pos: []string{"\tADDQ\t[A-Z]"}, |
| }, |
| { |
| // make sure assembly output has matching offset and base register. |
| fn: ` |
| func f72(a, b int) int { |
| runtime.GC() // use some frame |
| return b |
| } |
| `, |
| pos: []string{"b\\+24\\(SP\\)"}, |
| }, |
| { |
| // check load combining |
| fn: ` |
| func f73(a, b byte) (byte,byte) { |
| return f73(f73(a,b)) |
| } |
| `, |
| pos: []string{"\tMOVW\t"}, |
| }, |
| { |
| fn: ` |
| func f74(a, b uint16) (uint16,uint16) { |
| return f74(f74(a,b)) |
| } |
| `, |
| pos: []string{"\tMOVL\t"}, |
| }, |
| { |
| fn: ` |
| func f75(a, b uint32) (uint32,uint32) { |
| return f75(f75(a,b)) |
| } |
| `, |
| pos: []string{"\tMOVQ\t"}, |
| }, |
| // Make sure we don't put pointers in SSE registers across safe points. |
| { |
| fn: ` |
| func $(p, q *[2]*int) { |
| a, b := p[0], p[1] |
| runtime.GC() |
| q[0], q[1] = a, b |
| } |
| `, |
| neg: []string{"MOVUPS"}, |
| }, |
| { |
| // check that stack store is optimized away |
| fn: ` |
| 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\\)"}, |
| }, |
| { |
| 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"}, |
| }, |
| } |
| |
| var linux386Tests = []*asmTest{ |
| { |
| // check that stack store is optimized away |
| fn: ` |
| func $() int { |
| var x int |
| return *(&x) |
| } |
| `, |
| pos: []string{"TEXT\t.*, [$]0-4"}, |
| }, |
| } |
| |
| var linuxS390XTests = []*asmTest{ |
| { |
| // check that stack store is optimized away |
| fn: ` |
| func $() int { |
| var x int |
| return *(&x) |
| } |
| `, |
| pos: []string{"TEXT\t.*, [$]0-8"}, |
| }, |
| } |
| |
| var linuxARMTests = []*asmTest{ |
| { |
| // make sure assembly output has matching offset and base register. |
| fn: ` |
| func f13(a, b int) int { |
| runtime.GC() // use some frame |
| return b |
| } |
| `, |
| pos: []string{"b\\+4\\(FP\\)"}, |
| }, |
| { |
| // check that stack store is optimized away |
| fn: ` |
| func $() int { |
| var x int |
| return *(&x) |
| } |
| `, |
| pos: []string{"TEXT\t.*, [$]-4-4"}, |
| }, |
| } |
| |
| var linuxARM64Tests = []*asmTest{ |
| { |
| fn: ` |
| func $(x, y uint32) uint32 { |
| return x &^ y |
| } |
| `, |
| pos: []string{"\tBIC\t"}, |
| neg: []string{"\tAND\t"}, |
| }, |
| { |
| fn: ` |
| func $(x, y uint32) uint32 { |
| return x ^ ^y |
| } |
| `, |
| pos: []string{"\tEON\t"}, |
| neg: []string{"\tXOR\t"}, |
| }, |
| { |
| fn: ` |
| func $(x, y uint32) uint32 { |
| return x | ^y |
| } |
| `, |
| pos: []string{"\tORN\t"}, |
| neg: []string{"\tORR\t"}, |
| }, |
| { |
| fn: ` |
| func f34(a uint64) uint64 { |
| return a & ((1<<63)-1) |
| } |
| `, |
| pos: []string{"\tAND\t"}, |
| }, |
| { |
| fn: ` |
| func f35(a uint64) uint64 { |
| return a & (1<<63) |
| } |
| `, |
| pos: []string{"\tAND\t"}, |
| }, |
| { |
| // make sure offsets are folded into load and store. |
| fn: ` |
| func f36(_, a [20]byte) (b [20]byte) { |
| b = a |
| return |
| } |
| `, |
| pos: []string{"\tMOVD\t\"\"\\.a\\+[0-9]+\\(FP\\), R[0-9]+", "\tMOVD\tR[0-9]+, \"\"\\.b\\+[0-9]+\\(FP\\)"}, |
| }, |
| { |
| // check that stack store is optimized away |
| fn: ` |
| func $() int { |
| var x int |
| return *(&x) |
| } |
| `, |
| pos: []string{"TEXT\t.*, [$]-8-8"}, |
| }, |
| { |
| // check that we don't emit comparisons for constant shift |
| fn: ` |
| //go:nosplit |
| func $(x int) int { |
| return x << 17 |
| } |
| `, |
| pos: []string{"LSL\t\\$17"}, |
| neg: []string{"CMP"}, |
| }, |
| { |
| fn: ` |
| func $(a int32, ptr *int) { |
| if a >= 0 { |
| *ptr = 0 |
| } |
| } |
| `, |
| pos: []string{"TBNZ"}, |
| }, |
| { |
| fn: ` |
| func $(a int64, ptr *int) { |
| if a >= 0 { |
| *ptr = 0 |
| } |
| } |
| `, |
| pos: []string{"TBNZ"}, |
| }, |
| { |
| fn: ` |
| func $(a int32, ptr *int) { |
| if a < 0 { |
| *ptr = 0 |
| } |
| } |
| `, |
| pos: []string{"TBZ"}, |
| }, |
| { |
| fn: ` |
| func $(a int64, ptr *int) { |
| if a < 0 { |
| *ptr = 0 |
| } |
| } |
| `, |
| pos: []string{"TBZ"}, |
| }, |
| // Load-combining tests. |
| { |
| fn: ` |
| func $(s []byte) uint16 { |
| return uint16(s[0]) | uint16(s[1]) << 8 |
| } |
| `, |
| pos: []string{"\tMOVHU\t\\(R[0-9]+\\)"}, |
| neg: []string{"ORR\tR[0-9]+<<8\t"}, |
| }, |
| { |
| // make sure that CSEL is emitted for conditional moves |
| fn: ` |
| func f37(c int) int { |
| x := c + 4 |
| if c < 0 { |
| x = 182 |
| } |
| return x |
| } |
| `, |
| pos: []string{"\tCSEL\t"}, |
| }, |
| // Check that zero stores are combine into larger stores |
| { |
| fn: ` |
| func $(b []byte) { |
| _ = b[1] // early bounds check to guarantee safety of writes below |
| b[0] = 0 |
| b[1] = 0 |
| } |
| `, |
| pos: []string{"MOVH\tZR"}, |
| neg: []string{"MOVB"}, |
| }, |
| { |
| fn: ` |
| func $(b []byte) { |
| _ = b[1] // early bounds check to guarantee safety of writes below |
| b[1] = 0 |
| b[0] = 0 |
| } |
| `, |
| pos: []string{"MOVH\tZR"}, |
| neg: []string{"MOVB"}, |
| }, |
| { |
| fn: ` |
| func $(b []byte) { |
| _ = b[3] // early bounds check to guarantee safety of writes below |
| b[0] = 0 |
| b[1] = 0 |
| b[2] = 0 |
| b[3] = 0 |
| } |
| `, |
| pos: []string{"MOVW\tZR"}, |
| neg: []string{"MOVB", "MOVH"}, |
| }, |
| { |
| fn: ` |
| func $(b []byte) { |
| _ = b[3] // early bounds check to guarantee safety of writes below |
| b[2] = 0 |
| b[3] = 0 |
| b[1] = 0 |
| b[0] = 0 |
| } |
| `, |
| pos: []string{"MOVW\tZR"}, |
| neg: []string{"MOVB", "MOVH"}, |
| }, |
| { |
| fn: ` |
| func $(h []uint16) { |
| _ = h[1] // early bounds check to guarantee safety of writes below |
| h[0] = 0 |
| h[1] = 0 |
| } |
| `, |
| pos: []string{"MOVW\tZR"}, |
| neg: []string{"MOVB", "MOVH"}, |
| }, |
| { |
| fn: ` |
| func $(h []uint16) { |
| _ = h[1] // early bounds check to guarantee safety of writes below |
| h[1] = 0 |
| h[0] = 0 |
| } |
| `, |
| pos: []string{"MOVW\tZR"}, |
| neg: []string{"MOVB", "MOVH"}, |
| }, |
| { |
| fn: ` |
| func $(b []byte) { |
| _ = b[7] // early bounds check to guarantee safety of writes below |
| b[0] = 0 |
| b[1] = 0 |
| b[2] = 0 |
| b[3] = 0 |
| b[4] = 0 |
| b[5] = 0 |
| b[6] = 0 |
| b[7] = 0 |
| } |
| `, |
| pos: []string{"MOVD\tZR"}, |
| neg: []string{"MOVB", "MOVH", "MOVW"}, |
| }, |
| { |
| fn: ` |
| func $(h []uint16) { |
| _ = h[3] // early bounds check to guarantee safety of writes below |
| h[0] = 0 |
| h[1] = 0 |
| h[2] = 0 |
| h[3] = 0 |
| } |
| `, |
| pos: []string{"MOVD\tZR"}, |
| neg: []string{"MOVB", "MOVH", "MOVW"}, |
| }, |
| { |
| fn: ` |
| func $(h []uint16) { |
| _ = h[3] // early bounds check to guarantee safety of writes below |
| h[2] = 0 |
| h[3] = 0 |
| h[1] = 0 |
| h[0] = 0 |
| } |
| `, |
| pos: []string{"MOVD\tZR"}, |
| neg: []string{"MOVB", "MOVH", "MOVW"}, |
| }, |
| { |
| fn: ` |
| func $(w []uint32) { |
| _ = w[1] // early bounds check to guarantee safety of writes below |
| w[0] = 0 |
| w[1] = 0 |
| } |
| `, |
| pos: []string{"MOVD\tZR"}, |
| neg: []string{"MOVB", "MOVH", "MOVW"}, |
| }, |
| { |
| fn: ` |
| func $(w []uint32) { |
| _ = w[1] // early bounds check to guarantee safety of writes below |
| w[1] = 0 |
| w[0] = 0 |
| } |
| `, |
| pos: []string{"MOVD\tZR"}, |
| neg: []string{"MOVB", "MOVH", "MOVW"}, |
| }, |
| { |
| fn: ` |
| func $(b []byte) { |
| _ = b[15] // early bounds check to guarantee safety of writes below |
| b[0] = 0 |
| b[1] = 0 |
| b[2] = 0 |
| b[3] = 0 |
| b[4] = 0 |
| b[5] = 0 |
| b[6] = 0 |
| b[7] = 0 |
| b[8] = 0 |
| b[9] = 0 |
| b[10] = 0 |
| b[11] = 0 |
| b[12] = 0 |
| b[13] = 0 |
| b[15] = 0 |
| b[14] = 0 |
| } |
| `, |
| pos: []string{"STP"}, |
| neg: []string{"MOVB", "MOVH", "MOVW"}, |
| }, |
| { |
| fn: ` |
| func $(h []uint16) { |
| _ = h[7] // early bounds check to guarantee safety of writes below |
| h[0] = 0 |
| h[1] = 0 |
| h[2] = 0 |
| h[3] = 0 |
| h[4] = 0 |
| h[5] = 0 |
| h[6] = 0 |
| h[7] = 0 |
| } |
| `, |
| pos: []string{"STP"}, |
| neg: []string{"MOVB", "MOVH"}, |
| }, |
| { |
| fn: ` |
| func $(w []uint32) { |
| _ = w[3] // early bounds check to guarantee safety of writes below |
| w[0] = 0 |
| w[1] = 0 |
| w[2] = 0 |
| w[3] = 0 |
| } |
| `, |
| pos: []string{"STP"}, |
| neg: []string{"MOVB", "MOVH"}, |
| }, |
| { |
| fn: ` |
| func $(w []uint32) { |
| _ = w[3] // early bounds check to guarantee safety of writes below |
| w[1] = 0 |
| w[0] = 0 |
| w[3] = 0 |
| w[2] = 0 |
| } |
| `, |
| pos: []string{"STP"}, |
| neg: []string{"MOVB", "MOVH"}, |
| }, |
| { |
| fn: ` |
| func $(d []uint64) { |
| _ = d[1] // early bounds check to guarantee safety of writes below |
| d[0] = 0 |
| d[1] = 0 |
| } |
| `, |
| pos: []string{"STP"}, |
| neg: []string{"MOVB", "MOVH"}, |
| }, |
| { |
| fn: ` |
| func $(d []uint64) { |
| _ = d[1] // early bounds check to guarantee safety of writes below |
| d[1] = 0 |
| d[0] = 0 |
| } |
| `, |
| pos: []string{"STP"}, |
| neg: []string{"MOVB", "MOVH"}, |
| }, |
| { |
| fn: ` |
| func $(a *[39]byte) { |
| *a = [39]byte{} |
| } |
| `, |
| pos: []string{"MOVD"}, |
| neg: []string{"MOVB", "MOVH", "MOVW"}, |
| }, |
| { |
| fn: ` |
| func $(a *[30]byte) { |
| *a = [30]byte{} |
| } |
| `, |
| pos: []string{"STP"}, |
| neg: []string{"MOVB", "MOVH", "MOVW"}, |
| }, |
| } |
| |
| var linuxMIPSTests = []*asmTest{ |
| { |
| // check that stack store is optimized away |
| fn: ` |
| func $() int { |
| var x int |
| return *(&x) |
| } |
| `, |
| pos: []string{"TEXT\t.*, [$]-4-4"}, |
| }, |
| } |
| |
| var linuxMIPS64Tests = []*asmTest{ |
| { |
| // check that we don't emit comparisons for constant shift |
| fn: ` |
| func $(x int) int { |
| return x << 17 |
| } |
| `, |
| pos: []string{"SLLV\t\\$17"}, |
| neg: []string{"SGT"}, |
| }, |
| } |
| |
| var linuxPPC64LETests = []*asmTest{ |
| { |
| // check that stack store is optimized away |
| fn: ` |
| func $() int { |
| var x int |
| return *(&x) |
| } |
| `, |
| pos: []string{"TEXT\t.*, [$]0-8"}, |
| }, |
| } |
| |
| var plan9AMD64Tests = []*asmTest{ |
| // We should make sure that the compiler doesn't generate floating point |
| // instructions for non-float operations on Plan 9, because floating point |
| // operations are not allowed in the note handler. |
| // Array zeroing. |
| { |
| fn: ` |
| func $() [16]byte { |
| var a [16]byte |
| return a |
| } |
| `, |
| pos: []string{"\tMOVQ\t\\$0, \"\""}, |
| }, |
| // Array copy. |
| { |
| fn: ` |
| func $(a [16]byte) (b [16]byte) { |
| b = a |
| return |
| } |
| `, |
| pos: []string{"\tMOVQ\t\"\"\\.a\\+[0-9]+\\(SP\\), (AX|CX)", "\tMOVQ\t(AX|CX), \"\"\\.b\\+[0-9]+\\(SP\\)"}, |
| }, |
| } |
| |
| // TestLineNumber checks to make sure the generated assembly has line numbers |
| // see issue #16214 |
| func TestLineNumber(t *testing.T) { |
| testenv.MustHaveGoBuild(t) |
| dir, err := ioutil.TempDir("", "TestLineNumber") |
| if err != nil { |
| t.Fatalf("could not create directory: %v", err) |
| } |
| defer os.RemoveAll(dir) |
| |
| src := filepath.Join(dir, "x.go") |
| err = ioutil.WriteFile(src, []byte(issue16214src), 0644) |
| if err != nil { |
| t.Fatalf("could not write file: %v", err) |
| } |
| |
| cmd := exec.Command(testenv.GoToolPath(t), "tool", "compile", "-S", "-o", filepath.Join(dir, "out.o"), src) |
| out, err := cmd.CombinedOutput() |
| if err != nil { |
| t.Fatalf("fail to run go tool compile: %v", err) |
| } |
| |
| if strings.Contains(string(out), "unknown line number") { |
| t.Errorf("line number missing in assembly:\n%s", out) |
| } |
| } |
| |
| var issue16214src = ` |
| package main |
| |
| func Mod32(x uint32) uint32 { |
| return x % 3 // frontend rewrites it as HMUL with 2863311531, the LITERAL node has unknown Pos |
| } |
| ` |