blob: 77fa4eb738eddd23e7be09b3022a1eb67d6d7393 [file] [log] [blame]
// 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{"encoding/binary", "math", "math/bits", "unsafe", "runtime"},
tests: linuxAMD64Tests,
},
{
arch: "386",
os: "linux",
imports: []string{"encoding/binary"},
tests: linux386Tests,
},
{
arch: "s390x",
os: "linux",
imports: []string{"encoding/binary", "math", "math/bits"},
tests: linuxS390XTests,
},
{
arch: "arm",
os: "linux",
imports: []string{"math/bits", "runtime"},
tests: linuxARMTests,
},
{
arch: "arm64",
os: "linux",
imports: []string{"encoding/binary", "math", "math/bits"},
tests: linuxARM64Tests,
},
{
arch: "mips",
os: "linux",
imports: []string{"math/bits"},
tests: linuxMIPSTests,
},
{
arch: "mips64",
os: "linux",
tests: linuxMIPS64Tests,
},
{
arch: "ppc64le",
os: "linux",
imports: []string{"encoding/binary", "math", "math/bits"},
tests: linuxPPC64LETests,
},
{
arch: "amd64",
os: "plan9",
tests: plan9AMD64Tests,
},
}
var linuxAMD64Tests = []*asmTest{
// multiplication by powers of two
{
fn: `
func $(n int) int {
return n * 64
}
`,
pos: []string{"\tSHLQ\t\\$6,"},
neg: []string{"IMULQ"},
},
{
fn: `
func $(n int) int {
return -128*n
}
`,
pos: []string{"SHLQ"},
neg: []string{"IMULQ"},
},
{
fn: `
func $(x int) int {
return x * 96
}
`,
pos: []string{"\tSHLQ\t\\$5,", "\tLEAQ\t\\(.*\\)\\(.*\\*2\\),"},
},
// Load-combining tests.
{
fn: `
func f2(b []byte) uint64 {
return binary.LittleEndian.Uint64(b)
}
`,
pos: []string{"\tMOVQ\t\\(.*\\),"},
},
{
fn: `
func f3(b []byte, i int) uint64 {
return binary.LittleEndian.Uint64(b[i:])
}
`,
pos: []string{"\tMOVQ\t\\(.*\\)\\(.*\\*1\\),"},
},
{
fn: `
func f4(b []byte) uint32 {
return binary.LittleEndian.Uint32(b)
}
`,
pos: []string{"\tMOVL\t\\(.*\\),"},
},
{
fn: `
func f5(b []byte, i int) uint32 {
return binary.LittleEndian.Uint32(b[i:])
}
`,
pos: []string{"\tMOVL\t\\(.*\\)\\(.*\\*1\\),"},
},
{
fn: `
func f6(b []byte) uint64 {
return binary.BigEndian.Uint64(b)
}
`,
pos: []string{"\tBSWAPQ\t"},
},
{
fn: `
func f7(b []byte, i int) uint64 {
return binary.BigEndian.Uint64(b[i:])
}
`,
pos: []string{"\tBSWAPQ\t"},
},
{
fn: `
func f8(b []byte, v uint64) {
binary.BigEndian.PutUint64(b, v)
}
`,
pos: []string{"\tBSWAPQ\t"},
},
{
fn: `
func f9(b []byte, i int, v uint64) {
binary.BigEndian.PutUint64(b[i:], v)
}
`,
pos: []string{"\tBSWAPQ\t"},
},
{
fn: `
func f10(b []byte) uint32 {
return binary.BigEndian.Uint32(b)
}
`,
pos: []string{"\tBSWAPL\t"},
},
{
fn: `
func f11(b []byte, i int) uint32 {
return binary.BigEndian.Uint32(b[i:])
}
`,
pos: []string{"\tBSWAPL\t"},
},
{
fn: `
func f12(b []byte, v uint32) {
binary.BigEndian.PutUint32(b, v)
}
`,
pos: []string{"\tBSWAPL\t"},
},
{
fn: `
func f13(b []byte, i int, v uint32) {
binary.BigEndian.PutUint32(b[i:], v)
}
`,
pos: []string{"\tBSWAPL\t"},
},
{
fn: `
func f14(b []byte) uint16 {
return binary.BigEndian.Uint16(b)
}
`,
pos: []string{"\tROLW\t\\$8,"},
},
{
fn: `
func f15(b []byte, i int) uint16 {
return binary.BigEndian.Uint16(b[i:])
}
`,
pos: []string{"\tROLW\t\\$8,"},
},
{
fn: `
func f16(b []byte, v uint16) {
binary.BigEndian.PutUint16(b, v)
}
`,
pos: []string{"\tROLW\t\\$8,"},
},
{
fn: `
func f17(b []byte, i int, v uint16) {
binary.BigEndian.PutUint16(b[i:], v)
}
`,
pos: []string{"\tROLW\t\\$8,"},
},
// Structure zeroing. See issue #18370.
{
fn: `
type T1 struct {
a, b, c int
}
func $(t *T1) {
*t = T1{}
}
`,
pos: []string{"\tXORPS\tX., X", "\tMOVUPS\tX., \\(.*\\)", "\tMOVQ\t\\$0, 16\\(.*\\)"},
},
// SSA-able composite literal initialization. Issue 18872.
{
fn: `
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).
{
fn: `
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\\)"},
},
// Rotate tests
{
fn: `
func f20(x uint64) uint64 {
return x<<7 | x>>57
}
`,
pos: []string{"\tROLQ\t[$]7,"},
},
{
fn: `
func f21(x uint64) uint64 {
return x<<7 + x>>57
}
`,
pos: []string{"\tROLQ\t[$]7,"},
},
{
fn: `
func f22(x uint64) uint64 {
return x<<7 ^ x>>57
}
`,
pos: []string{"\tROLQ\t[$]7,"},
},
{
fn: `
func f23(x uint32) uint32 {
return x<<7 + x>>25
}
`,
pos: []string{"\tROLL\t[$]7,"},
},
{
fn: `
func f24(x uint32) uint32 {
return x<<7 | x>>25
}
`,
pos: []string{"\tROLL\t[$]7,"},
},
{
fn: `
func f25(x uint32) uint32 {
return x<<7 ^ x>>25
}
`,
pos: []string{"\tROLL\t[$]7,"},
},
{
fn: `
func f26(x uint16) uint16 {
return x<<7 + x>>9
}
`,
pos: []string{"\tROLW\t[$]7,"},
},
{
fn: `
func f27(x uint16) uint16 {
return x<<7 | x>>9
}
`,
pos: []string{"\tROLW\t[$]7,"},
},
{
fn: `
func f28(x uint16) uint16 {
return x<<7 ^ x>>9
}
`,
pos: []string{"\tROLW\t[$]7,"},
},
{
fn: `
func f29(x uint8) uint8 {
return x<<7 + x>>1
}
`,
pos: []string{"\tROLB\t[$]7,"},
},
{
fn: `
func f30(x uint8) uint8 {
return x<<7 | x>>1
}
`,
pos: []string{"\tROLB\t[$]7,"},
},
{
fn: `
func f31(x uint8) uint8 {
return x<<7 ^ x>>1
}
`,
pos: []string{"\tROLB\t[$]7,"},
},
// Rotate after inlining (see issue 18254).
{
fn: `
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,"},
},
{
fn: `
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.
{
fn: `
func f34(m map[int]int) bool {
_, ok := m[5]
return ok
}
`,
pos: []string{"\tMOVQ\t[$]5,"},
},
{
fn: `
func f35(m map[string]int) int {
return m["abc"]
}
`,
pos: []string{"\"abc\""},
},
{
fn: `
func f36(m map[string]int) bool {
_, ok := m["abc"]
return ok
}
`,
pos: []string{"\"abc\""},
},
// 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"},
},
// Intrinsic tests for math/bits
{
fn: `
func f41(a uint64) int {
return bits.TrailingZeros64(a)
}
`,
pos: []string{"\tBSFQ\t", "\tMOVL\t\\$64,", "\tCMOVQEQ\t"},
},
{
fn: `
func f42(a uint32) int {
return bits.TrailingZeros32(a)
}
`,
pos: []string{"\tBSFQ\t", "\tORQ\t[^$]", "\tMOVQ\t\\$4294967296,"},
},
{
fn: `
func f43(a uint16) int {
return bits.TrailingZeros16(a)
}
`,
pos: []string{"\tBSFQ\t", "\tORQ\t\\$65536,"},
},
{
fn: `
func f44(a uint8) int {
return bits.TrailingZeros8(a)
}
`,
pos: []string{"\tBSFQ\t", "\tORQ\t\\$256,"},
},
{
fn: `
func f45(a uint64) uint64 {
return bits.ReverseBytes64(a)
}
`,
pos: []string{"\tBSWAPQ\t"},
},
{
fn: `
func f46(a uint32) uint32 {
return bits.ReverseBytes32(a)
}
`,
pos: []string{"\tBSWAPL\t"},
},
{
fn: `
func f47(a uint16) uint16 {
return bits.ReverseBytes16(a)
}
`,
pos: []string{"\tROLW\t\\$8,"},
},
{
fn: `
func f48(a uint64) int {
return bits.Len64(a)
}
`,
pos: []string{"\tBSRQ\t"},
},
{
fn: `
func f49(a uint32) int {
return bits.Len32(a)
}
`,
pos: []string{"\tBSRQ\t"},
},
{
fn: `
func f50(a uint16) int {
return bits.Len16(a)
}
`,
pos: []string{"\tBSRQ\t"},
},
/* see ssa.go
{
fn:`
func f51(a uint8) int {
return bits.Len8(a)
}
`,
pos:[]string{"\tBSRQ\t"},
},
*/
{
fn: `
func f52(a uint) int {
return bits.Len(a)
}
`,
pos: []string{"\tBSRQ\t"},
},
{
fn: `
func f53(a uint64) int {
return bits.LeadingZeros64(a)
}
`,
pos: []string{"\tBSRQ\t"},
},
{
fn: `
func f54(a uint32) int {
return bits.LeadingZeros32(a)
}
`,
pos: []string{"\tBSRQ\t"},
},
{
fn: `
func f55(a uint16) int {
return bits.LeadingZeros16(a)
}
`,
pos: []string{"\tBSRQ\t"},
},
/* see ssa.go
{
fn:`
func f56(a uint8) int {
return bits.LeadingZeros8(a)
}
`,
pos:[]string{"\tBSRQ\t"},
},
*/
{
fn: `
func f57(a uint) int {
return bits.LeadingZeros(a)
}
`,
pos: []string{"\tBSRQ\t"},
},
{
fn: `
func pop1(x uint64) int {
return bits.OnesCount64(x)
}`,
pos: []string{"\tPOPCNTQ\t", "support_popcnt"},
},
{
fn: `
func pop2(x uint32) int {
return bits.OnesCount32(x)
}`,
pos: []string{"\tPOPCNTL\t", "support_popcnt"},
},
{
fn: `
func pop3(x uint16) int {
return bits.OnesCount16(x)
}`,
pos: []string{"\tPOPCNTL\t", "support_popcnt"},
},
{
fn: `
func pop4(x uint) int {
return bits.OnesCount(x)
}`,
pos: []string{"\tPOPCNTQ\t", "support_popcnt"},
},
// multiplication merging tests
{
fn: `
func mul1(n int) int {
return 15*n + 31*n
}`,
pos: []string{"\tIMULQ\t[$]46"}, // 46*n
},
{
fn: `
func mul2(n int) int {
return 5*n + 7*(n+1) + 11*(n+2)
}`,
pos: []string{"\tIMULQ\t[$]23", "\tADDQ\t[$]29"}, // 23*n + 29
},
{
fn: `
func mul3(a, n int) int {
return a*n + 19*n
}`,
pos: []string{"\tADDQ\t[$]19", "\tIMULQ"}, // (a+19)*n
},
{
fn: `
func mul4(n int) int {
return 23*n - 9*n
}`,
pos: []string{"\tIMULQ\t[$]14"}, // 14*n
},
{
fn: `
func mul5(a, n int) int {
return a*n - 19*n
}`,
pos: []string{"\tADDQ\t[$]-19", "\tIMULQ"}, // (a-19)*n
},
// 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]"},
},
// Floating-point strength reduction
{
fn: `
func f60(f float64) float64 {
return f * 2.0
}`,
pos: []string{"\tADDSD\t"},
},
{
fn: `
func f62(f float64) float64 {
return f / 16.0
}`,
pos: []string{"\tMULSD\t"},
},
{
fn: `
func f63(f float64) float64 {
return f / 0.125
}`,
pos: []string{"\tMULSD\t"},
},
{
fn: `
func f64(f float64) float64 {
return f / 0.5
}`,
pos: []string{"\tADDSD\t"},
},
// Check that compare to constant string uses 2/4/8 byte compares
{
fn: `
func f65(a string) bool {
return a == "xx"
}`,
pos: []string{"\tCMPW\t[A-Z]"},
},
{
fn: `
func f66(a string) bool {
return a == "xxxx"
}`,
pos: []string{"\tCMPL\t[A-Z]"},
},
{
fn: `
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)
}`,
pos: []string{"\tROLQ\t"},
},
{
fn: `func rot64r(x uint64, y int) uint64 {
z := uint(y & 63)
return x >> z | x << (64-z)
}`,
pos: []string{"\tRORQ\t"},
},
{
fn: `func rot32l(x uint32, y int) uint32 {
z := uint(y & 31)
return x << z | x >> (32-z)
}`,
pos: []string{"\tROLL\t"},
},
{
fn: `func rot32r(x uint32, y int) uint32 {
z := uint(y & 31)
return x >> z | x << (32-z)
}`,
pos: []string{"\tRORL\t"},
},
{
fn: `func rot16l(x uint16, y int) uint16 {
z := uint(y & 15)
return x << z | x >> (16-z)
}`,
pos: []string{"\tROLW\t"},
},
{
fn: `func rot16r(x uint16, y int) uint16 {
z := uint(y & 15)
return x >> z | x << (16-z)
}`,
pos: []string{"\tRORW\t"},
},
{
fn: `func rot8l(x uint8, y int) uint8 {
z := uint(y & 7)
return x << z | x >> (8-z)
}`,
pos: []string{"\tROLB\t"},
},
{
fn: `func rot8r(x uint8, y int) uint8 {
z := uint(y & 7)
return x >> z | x << (8-z)
}`,
pos: []string{"\tRORB\t"},
},
// Check that array compare uses 2/4/8 byte compares
{
fn: `
func f68(a,b [2]byte) bool {
return a == b
}`,
pos: []string{"\tCMPW\t[A-Z]"},
},
{
fn: `
func f69(a,b [3]uint16) bool {
return a == b
}`,
pos: []string{"\tCMPL\t[A-Z]"},
},
{
fn: `
func $(a,b [3]int16) bool {
return a == b
}`,
pos: []string{"\tCMPL\t[A-Z]"},
},
{
fn: `
func $(a,b [12]int8) bool {
return a == b
}`,
pos: []string{"\tCMPQ\t[A-Z]", "\tCMPL\t[A-Z]"},
},
{
fn: `
func f70(a,b [15]byte) bool {
return a == b
}`,
pos: []string{"\tCMPQ\t[A-Z]"},
},
{
fn: `
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.
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"},
},
// math.Abs using integer registers
{
fn: `
func $(x float64) float64 {
return math.Abs(x)
}
`,
pos: []string{"\tSHLQ\t[$]1,", "\tSHRQ\t[$]1,"},
},
// math.Copysign using integer registers
{
fn: `
func $(x, y float64) float64 {
return math.Copysign(x, y)
}
`,
pos: []string{"\tSHLQ\t[$]1,", "\tSHRQ\t[$]1,", "\tSHRQ\t[$]63,", "\tSHLQ\t[$]63,", "\tORQ\t"},
},
// int <-> fp moves
{
fn: `
func $(x float64) uint64 {
return math.Float64bits(x+1) + 1
}
`,
pos: []string{"\tMOVQ\tX.*, [^X].*"},
},
{
fn: `
func $(x float32) uint32 {
return math.Float32bits(x+1) + 1
}
`,
pos: []string{"\tMOVL\tX.*, [^X].*"},
},
{
fn: `
func $(x uint64) float64 {
return math.Float64frombits(x+1) + 1
}
`,
pos: []string{"\tMOVQ\t[^X].*, X.*"},
},
{
fn: `
func $(x uint32) float32 {
return math.Float32frombits(x+1) + 1
}
`,
pos: []string{"\tMOVL\t[^X].*, X.*"},
},
{
fn: `
func $(x uint32) bool {
return x > 4
}
`,
pos: []string{"\tSETHI\t\\("},
},
// 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,"},
},
{
// 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"},
},
// Nil checks before calling interface methods
{
fn: `
type I interface {
foo000()
foo001()
foo002()
foo003()
foo004()
foo005()
foo006()
foo007()
foo008()
foo009()
foo010()
foo011()
foo012()
foo013()
foo014()
foo015()
foo016()
foo017()
foo018()
foo019()
foo020()
foo021()
foo022()
foo023()
foo024()
foo025()
foo026()
foo027()
foo028()
foo029()
foo030()
foo031()
foo032()
foo033()
foo034()
foo035()
foo036()
foo037()
foo038()
foo039()
foo040()
foo041()
foo042()
foo043()
foo044()
foo045()
foo046()
foo047()
foo048()
foo049()
foo050()
foo051()
foo052()
foo053()
foo054()
foo055()
foo056()
foo057()
foo058()
foo059()
foo060()
foo061()
foo062()
foo063()
foo064()
foo065()
foo066()
foo067()
foo068()
foo069()
foo070()
foo071()
foo072()
foo073()
foo074()
foo075()
foo076()
foo077()
foo078()
foo079()
foo080()
foo081()
foo082()
foo083()
foo084()
foo085()
foo086()
foo087()
foo088()
foo089()
foo090()
foo091()
foo092()
foo093()
foo094()
foo095()
foo096()
foo097()
foo098()
foo099()
foo100()
foo101()
foo102()
foo103()
foo104()
foo105()
foo106()
foo107()
foo108()
foo109()
foo110()
foo111()
foo112()
foo113()
foo114()
foo115()
foo116()
foo117()
foo118()
foo119()
foo120()
foo121()
foo122()
foo123()
foo124()
foo125()
foo126()
foo127()
foo128()
foo129()
foo130()
foo131()
foo132()
foo133()
foo134()
foo135()
foo136()
foo137()
foo138()
foo139()
foo140()
foo141()
foo142()
foo143()
foo144()
foo145()
foo146()
foo147()
foo148()
foo149()
foo150()
foo151()
foo152()
foo153()
foo154()
foo155()
foo156()
foo157()
foo158()
foo159()
foo160()
foo161()
foo162()
foo163()
foo164()
foo165()
foo166()
foo167()
foo168()
foo169()
foo170()
foo171()
foo172()
foo173()
foo174()
foo175()
foo176()
foo177()
foo178()
foo179()
foo180()
foo181()
foo182()
foo183()
foo184()
foo185()
foo186()
foo187()
foo188()
foo189()
foo190()
foo191()
foo192()
foo193()
foo194()
foo195()
foo196()
foo197()
foo198()
foo199()
foo200()
foo201()
foo202()
foo203()
foo204()
foo205()
foo206()
foo207()
foo208()
foo209()
foo210()
foo211()
foo212()
foo213()
foo214()
foo215()
foo216()
foo217()
foo218()
foo219()
foo220()
foo221()
foo222()
foo223()
foo224()
foo225()
foo226()
foo227()
foo228()
foo229()
foo230()
foo231()
foo232()
foo233()
foo234()
foo235()
foo236()
foo237()
foo238()
foo239()
foo240()
foo241()
foo242()
foo243()
foo244()
foo245()
foo246()
foo247()
foo248()
foo249()
foo250()
foo251()
foo252()
foo253()
foo254()
foo255()
foo256()
foo257()
foo258()
foo259()
foo260()
foo261()
foo262()
foo263()
foo264()
foo265()
foo266()
foo267()
foo268()
foo269()
foo270()
foo271()
foo272()
foo273()
foo274()
foo275()
foo276()
foo277()
foo278()
foo279()
foo280()
foo281()
foo282()
foo283()
foo284()
foo285()
foo286()
foo287()
foo288()
foo289()
foo290()
foo291()
foo292()
foo293()
foo294()
foo295()
foo296()
foo297()
foo298()
foo299()
foo300()
foo301()
foo302()
foo303()
foo304()
foo305()
foo306()
foo307()
foo308()
foo309()
foo310()
foo311()
foo312()
foo313()
foo314()
foo315()
foo316()
foo317()
foo318()
foo319()
foo320()
foo321()
foo322()
foo323()
foo324()
foo325()
foo326()
foo327()
foo328()
foo329()
foo330()
foo331()
foo332()
foo333()
foo334()
foo335()
foo336()
foo337()
foo338()
foo339()
foo340()
foo341()
foo342()
foo343()
foo344()
foo345()
foo346()
foo347()
foo348()
foo349()
foo350()
foo351()
foo352()
foo353()
foo354()
foo355()
foo356()
foo357()
foo358()
foo359()
foo360()
foo361()
foo362()
foo363()
foo364()
foo365()
foo366()
foo367()
foo368()
foo369()
foo370()
foo371()
foo372()
foo373()
foo374()
foo375()
foo376()
foo377()
foo378()
foo379()
foo380()
foo381()
foo382()
foo383()
foo384()
foo385()
foo386()
foo387()
foo388()
foo389()
foo390()
foo391()
foo392()
foo393()
foo394()
foo395()
foo396()
foo397()
foo398()
foo399()
foo400()
foo401()
foo402()
foo403()
foo404()
foo405()
foo406()
foo407()
foo408()
foo409()
foo410()
foo411()
foo412()
foo413()
foo414()
foo415()
foo416()
foo417()
foo418()
foo419()
foo420()
foo421()
foo422()
foo423()
foo424()
foo425()
foo426()
foo427()
foo428()
foo429()
foo430()
foo431()
foo432()
foo433()
foo434()
foo435()
foo436()
foo437()
foo438()
foo439()
foo440()
foo441()
foo442()
foo443()
foo444()
foo445()
foo446()
foo447()
foo448()
foo449()
foo450()
foo451()
foo452()
foo453()
foo454()
foo455()
foo456()
foo457()
foo458()
foo459()
foo460()
foo461()
foo462()
foo463()
foo464()
foo465()
foo466()
foo467()
foo468()
foo469()
foo470()
foo471()
foo472()
foo473()
foo474()
foo475()
foo476()
foo477()
foo478()
foo479()
foo480()
foo481()
foo482()
foo483()
foo484()
foo485()
foo486()
foo487()
foo488()
foo489()
foo490()
foo491()
foo492()
foo493()
foo494()
foo495()
foo496()
foo497()
foo498()
foo499()
foo500()
foo501()
foo502()
foo503()
foo504()
foo505()
foo506()
foo507()
foo508()
foo509()
foo510()
foo511()
}
func $(i I) {
i.foo511()
}
`,
pos: []string{"TESTB"},
},
{
fn: `
func $(i I) {
i.foo001()
}
`,
neg: []string{"TESTB"},
},
}
var linux386Tests = []*asmTest{
{
fn: `
func f0(b []byte) uint32 {
return binary.LittleEndian.Uint32(b)
}
`,
pos: []string{"\tMOVL\t\\(.*\\),"},
},
{
fn: `
func f1(b []byte, i int) uint32 {
return binary.LittleEndian.Uint32(b[i:])
}
`,
pos: []string{"\tMOVL\t\\(.*\\)\\(.*\\*1\\),"},
},
// multiplication by powers of two
{
fn: `
func $(n int) int {
return 32*n
}
`,
pos: []string{"SHLL"},
neg: []string{"IMULL"},
},
{
fn: `
func $(n int) int {
return -64*n
}
`,
pos: []string{"SHLL"},
neg: []string{"IMULL"},
},
// multiplication merging tests
{
fn: `
func $(n int) int {
return 9*n + 14*n
}`,
pos: []string{"\tIMULL\t[$]23"}, // 23*n
},
{
fn: `
func $(a, n int) int {
return 19*a + a*n
}`,
pos: []string{"\tADDL\t[$]19", "\tIMULL"}, // (n+19)*a
},
{
// check that stack store is optimized away
fn: `
func $() int {
var x int
return *(&x)
}
`,
pos: []string{"TEXT\t.*, [$]0-4"},
},
{
fn: `
func mul3(n int) int {
return 23*n - 9*n
}`,
pos: []string{"\tIMULL\t[$]14"}, // 14*n
},
{
fn: `
func mul4(a, n int) int {
return n*a - a*19
}`,
pos: []string{"\tADDL\t[$]-19", "\tIMULL"}, // (n-19)*a
},
// 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{
{
fn: `
func f0(b []byte) uint32 {
return binary.LittleEndian.Uint32(b)
}
`,
pos: []string{"\tMOVWBR\t\\(.*\\),"},
},
{
fn: `
func f1(b []byte, i int) uint32 {
return binary.LittleEndian.Uint32(b[i:])
}
`,
pos: []string{"\tMOVWBR\t\\(.*\\)\\(.*\\*1\\),"},
},
{
fn: `
func f2(b []byte) uint64 {
return binary.LittleEndian.Uint64(b)
}
`,
pos: []string{"\tMOVDBR\t\\(.*\\),"},
},
{
fn: `
func f3(b []byte, i int) uint64 {
return binary.LittleEndian.Uint64(b[i:])
}
`,
pos: []string{"\tMOVDBR\t\\(.*\\)\\(.*\\*1\\),"},
},
{
fn: `
func f4(b []byte) uint32 {
return binary.BigEndian.Uint32(b)
}
`,
pos: []string{"\tMOVWZ\t\\(.*\\),"},
},
{
fn: `
func f5(b []byte, i int) uint32 {
return binary.BigEndian.Uint32(b[i:])
}
`,
pos: []string{"\tMOVWZ\t\\(.*\\)\\(.*\\*1\\),"},
},
{
fn: `
func f6(b []byte) uint64 {
return binary.BigEndian.Uint64(b)
}
`,
pos: []string{"\tMOVD\t\\(.*\\),"},
},
{
fn: `
func f7(b []byte, i int) uint64 {
return binary.BigEndian.Uint64(b[i:])
}
`,
pos: []string{"\tMOVD\t\\(.*\\)\\(.*\\*1\\),"},
},
{
fn: `
func f8(x uint64) uint64 {
return x<<7 + x>>57
}
`,
pos: []string{"\tRLLG\t[$]7,"},
},
{
fn: `
func f9(x uint64) uint64 {
return x<<7 | x>>57
}
`,
pos: []string{"\tRLLG\t[$]7,"},
},
{
fn: `
func f10(x uint64) uint64 {
return x<<7 ^ x>>57
}
`,
pos: []string{"\tRLLG\t[$]7,"},
},
{
fn: `
func f11(x uint32) uint32 {
return x<<7 + x>>25
}
`,
pos: []string{"\tRLL\t[$]7,"},
},
{
fn: `
func f12(x uint32) uint32 {
return x<<7 | x>>25
}
`,
pos: []string{"\tRLL\t[$]7,"},
},
{
fn: `
func f13(x uint32) uint32 {
return x<<7 ^ x>>25
}
`,
pos: []string{"\tRLL\t[$]7,"},
},
// Fused multiply-add/sub instructions.
{
fn: `
func f14(x, y, z float64) float64 {
return x * y + z
}
`,
pos: []string{"\tFMADD\t"},
},
{
fn: `
func f15(x, y, z float64) float64 {
return x * y - z
}
`,
pos: []string{"\tFMSUB\t"},
},
{
fn: `
func f16(x, y, z float32) float32 {
return x * y + z
}
`,
pos: []string{"\tFMADDS\t"},
},
{
fn: `
func f17(x, y, z float32) float32 {
return x * y - z
}
`,
pos: []string{"\tFMSUBS\t"},
},
// Intrinsic tests for math/bits
{
fn: `
func f18(a uint64) int {
return bits.TrailingZeros64(a)
}
`,
pos: []string{"\tFLOGR\t"},
},
{
fn: `
func f19(a uint32) int {
return bits.TrailingZeros32(a)
}
`,
pos: []string{"\tFLOGR\t", "\tMOVWZ\t"},
},
{
fn: `
func f20(a uint16) int {
return bits.TrailingZeros16(a)
}
`,
pos: []string{"\tFLOGR\t", "\tOR\t\\$65536,"},
},
{
fn: `
func f21(a uint8) int {
return bits.TrailingZeros8(a)
}
`,
pos: []string{"\tFLOGR\t", "\tOR\t\\$256,"},
},
// Intrinsic tests for math/bits
{
fn: `
func f22(a uint64) uint64 {
return bits.ReverseBytes64(a)
}
`,
pos: []string{"\tMOVDBR\t"},
},
{
fn: `
func f23(a uint32) uint32 {
return bits.ReverseBytes32(a)
}
`,
pos: []string{"\tMOVWBR\t"},
},
{
fn: `
func f24(a uint64) int {
return bits.Len64(a)
}
`,
pos: []string{"\tFLOGR\t"},
},
{
fn: `
func f25(a uint32) int {
return bits.Len32(a)
}
`,
pos: []string{"\tFLOGR\t"},
},
{
fn: `
func f26(a uint16) int {
return bits.Len16(a)
}
`,
pos: []string{"\tFLOGR\t"},
},
{
fn: `
func f27(a uint8) int {
return bits.Len8(a)
}
`,
pos: []string{"\tFLOGR\t"},
},
{
fn: `
func f28(a uint) int {
return bits.Len(a)
}
`,
pos: []string{"\tFLOGR\t"},
},
{
fn: `
func f29(a uint64) int {
return bits.LeadingZeros64(a)
}
`,
pos: []string{"\tFLOGR\t"},
},
{
fn: `
func f30(a uint32) int {
return bits.LeadingZeros32(a)
}
`,
pos: []string{"\tFLOGR\t"},
},
{
fn: `
func f31(a uint16) int {
return bits.LeadingZeros16(a)
}
`,
pos: []string{"\tFLOGR\t"},
},
{
fn: `
func f32(a uint8) int {
return bits.LeadingZeros8(a)
}
`,
pos: []string{"\tFLOGR\t"},
},
{
fn: `
func f33(a uint) int {
return bits.LeadingZeros(a)
}
`,
pos: []string{"\tFLOGR\t"},
},
// Intrinsic tests for math.
{
fn: `
func ceil(x float64) float64 {
return math.Ceil(x)
}
`,
pos: []string{"\tFIDBR\t[$]6"},
},
{
fn: `
func floor(x float64) float64 {
return math.Floor(x)
}
`,
pos: []string{"\tFIDBR\t[$]7"},
},
{
fn: `
func round(x float64) float64 {
return math.Round(x)
}
`,
pos: []string{"\tFIDBR\t[$]1"},
},
{
fn: `
func trunc(x float64) float64 {
return math.Trunc(x)
}
`,
pos: []string{"\tFIDBR\t[$]5"},
},
{
fn: `
func roundToEven(x float64) float64 {
return math.RoundToEven(x)
}
`,
pos: []string{"\tFIDBR\t[$]4"},
},
{
// check that stack store is optimized away
fn: `
func $() int {
var x int
return *(&x)
}
`,
pos: []string{"TEXT\t.*, [$]0-8"},
},
// Constant propagation through raw bits conversions.
{
// uint32 constant converted to float32 constant
fn: `
func $(x float32) float32 {
if x > math.Float32frombits(0x3f800000) {
return -x
}
return x
}
`,
pos: []string{"\tFMOVS\t[$]f32.3f800000\\(SB\\)"},
},
{
// float32 constant converted to uint32 constant
fn: `
func $(x uint32) uint32 {
if x > math.Float32bits(1) {
return -x
}
return x
}
`,
neg: []string{"\tFMOVS\t"},
},
// Constant propagation through float comparisons.
{
fn: `
func $() bool {
return 0.5 == float64(uint32(1)) ||
1.5 > float64(uint64(1<<63)) ||
math.NaN() == math.NaN()
}
`,
pos: []string{"\tMOV(B|BZ|D)\t[$]0,"},
neg: []string{"\tFCMPU\t", "\tMOV(B|BZ|D)\t[$]1,"},
},
{
fn: `
func $() bool {
return float32(0.5) <= float32(int64(1)) &&
float32(1.5) >= float32(int32(-1<<31)) &&
float32(math.NaN()) != float32(math.NaN())
}
`,
pos: []string{"\tMOV(B|BZ|D)\t[$]1,"},
neg: []string{"\tCEBR\t", "\tMOV(B|BZ|D)\t[$]0,"},
},
// math tests
{
fn: `
func $(x float64) float64 {
return math.Abs(x)
}
`,
pos: []string{"\tLPDFR\t"},
neg: []string{"\tMOVD\t"}, // no integer loads/stores
},
{
fn: `
func $(x float32) float32 {
return float32(math.Abs(float64(x)))
}
`,
pos: []string{"\tLPDFR\t"},
neg: []string{"\tLDEBR\t", "\tLEDBR\t"}, // no float64 conversion
},
{
fn: `
func $(x float64) float64 {
return math.Float64frombits(math.Float64bits(x)|1<<63)
}
`,
pos: []string{"\tLNDFR\t"},
neg: []string{"\tMOVD\t"}, // no integer loads/stores
},
{
fn: `
func $(x float64) float64 {
return -math.Abs(x)
}
`,
pos: []string{"\tLNDFR\t"},
neg: []string{"\tMOVD\t"}, // no integer loads/stores
},
{
fn: `
func $(x, y float64) float64 {
return math.Copysign(x, y)
}
`,
pos: []string{"\tCPSDR\t"},
neg: []string{"\tMOVD\t"}, // no integer loads/stores
},
{
fn: `
func $(x float64) float64 {
return math.Copysign(x, -1)
}
`,
pos: []string{"\tLNDFR\t"},
neg: []string{"\tMOVD\t"}, // no integer loads/stores
},
{
fn: `
func $(x float64) float64 {
return math.Copysign(-1, x)
}
`,
pos: []string{"\tCPSDR\t"},
neg: []string{"\tMOVD\t"}, // no integer loads/stores
},
}
var linuxARMTests = []*asmTest{
// multiplication by powers of two
{
fn: `
func $(n int) int {
return 16*n
}
`,
pos: []string{"\tSLL\t[$]4"},
neg: []string{"\tMUL\t"},
},
{
fn: `
func $(n int) int {
return -32*n
}
`,
pos: []string{"\tSLL\t[$]5"},
neg: []string{"\tMUL\t"},
},
{
fn: `
func f0(x uint32) uint32 {
return x<<7 + x>>25
}
`,
pos: []string{"\tMOVW\tR[0-9]+@>25,"},
},
{
fn: `
func f1(x uint32) uint32 {
return x<<7 | x>>25
}
`,
pos: []string{"\tMOVW\tR[0-9]+@>25,"},
},
{
fn: `
func f2(x uint32) uint32 {
return x<<7 ^ x>>25
}
`,
pos: []string{"\tMOVW\tR[0-9]+@>25,"},
},
{
fn: `
func f3(a uint64) int {
return bits.Len64(a)
}
`,
pos: []string{"\tCLZ\t"},
},
{
fn: `
func f4(a uint32) int {
return bits.Len32(a)
}
`,
pos: []string{"\tCLZ\t"},
},
{
fn: `
func f5(a uint16) int {
return bits.Len16(a)
}
`,
pos: []string{"\tCLZ\t"},
},
{
fn: `
func f6(a uint8) int {
return bits.Len8(a)
}
`,
pos: []string{"\tCLZ\t"},
},
{
fn: `
func f7(a uint) int {
return bits.Len(a)
}
`,
pos: []string{"\tCLZ\t"},
},
{
fn: `
func f8(a uint64) int {
return bits.LeadingZeros64(a)
}
`,
pos: []string{"\tCLZ\t"},
},
{
fn: `
func f9(a uint32) int {
return bits.LeadingZeros32(a)
}
`,
pos: []string{"\tCLZ\t"},
},
{
fn: `
func f10(a uint16) int {
return bits.LeadingZeros16(a)
}
`,
pos: []string{"\tCLZ\t"},
},
{
fn: `
func f11(a uint8) int {
return bits.LeadingZeros8(a)
}
`,
pos: []string{"\tCLZ\t"},
},
{
fn: `
func f12(a uint) int {
return bits.LeadingZeros(a)
}
`,
pos: []string{"\tCLZ\t"},
},
{
// 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{
// multiplication by powers of two
{
fn: `
func $(n int) int {
return 64*n
}
`,
pos: []string{"\tLSL\t[$]6"},
neg: []string{"\tMUL\t"},
},
{
fn: `
func $(n int) int {
return -128*n
}
`,
pos: []string{"\tLSL\t[$]7"},
neg: []string{"\tMUL\t"},
},
{
fn: `
func f0(x uint64) uint64 {
return x<<7 + x>>57
}
`,
pos: []string{"\tROR\t[$]57,"},
},
{
fn: `
func f1(x uint64) uint64 {
return x<<7 | x>>57
}
`,
pos: []string{"\tROR\t[$]57,"},
},
{
fn: `
func f2(x uint64) uint64 {
return x<<7 ^ x>>57
}
`,
pos: []string{"\tROR\t[$]57,"},
},
{
fn: `
func f3(x uint32) uint32 {
return x<<7 + x>>25
}
`,
pos: []string{"\tRORW\t[$]25,"},
},
{
fn: `
func f4(x uint32) uint32 {
return x<<7 | x>>25
}
`,
pos: []string{"\tRORW\t[$]25,"},
},
{
fn: `
func f5(x uint32) uint32 {
return x<<7 ^ x>>25
}
`,
pos: []string{"\tRORW\t[$]25,"},
},
{
fn: `
func f22(a uint64) uint64 {
return bits.ReverseBytes64(a)
}
`,
pos: []string{"\tREV\t"},
},
{
fn: `
func f23(a uint32) uint32 {
return bits.ReverseBytes32(a)
}
`,
pos: []string{"\tREVW\t"},
},
{
fn: `
func f24(a uint64) int {
return bits.Len64(a)
}
`,
pos: []string{"\tCLZ\t"},
},
{
fn: `
func f25(a uint32) int {
return bits.Len32(a)
}
`,
pos: []string{"\tCLZ\t"},
},
{
fn: `
func f26(a uint16) int {
return bits.Len16(a)
}
`,
pos: []string{"\tCLZ\t"},
},
{
fn: `
func f27(a uint8) int {
return bits.Len8(a)
}
`,
pos: []string{"\tCLZ\t"},
},
{
fn: `
func f28(a uint) int {
return bits.Len(a)
}
`,
pos: []string{"\tCLZ\t"},
},
{
fn: `
func f29(a uint64) int {
return bits.LeadingZeros64(a)
}
`,
pos: []string{"\tCLZ\t"},
},
{
fn: `
func f30(a uint32) int {
return bits.LeadingZeros32(a)
}
`,
pos: []string{"\tCLZ\t"},
},
{
fn: `
func f31(a uint16) int {
return bits.LeadingZeros16(a)
}
`,
pos: []string{"\tCLZ\t"},
},
{
fn: `
func f32(a uint8) int {
return bits.LeadingZeros8(a)
}
`,
pos: []string{"\tCLZ\t"},
},
{
fn: `
func f33(a uint) int {
return bits.LeadingZeros(a)
}
`,
pos: []string{"\tCLZ\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"},
},
{
fn: `
func $(x uint64) int {
return bits.OnesCount64(x)
}
`,
pos: []string{"\tVCNT\t", "\tVUADDLV\t"},
},
{
fn: `
func $(x uint32) int {
return bits.OnesCount32(x)
}
`,
pos: []string{"\tVCNT\t", "\tVUADDLV\t"},
},
{
fn: `
func $(x uint16) int {
return bits.OnesCount16(x)
}
`,
pos: []string{"\tVCNT\t", "\tVUADDLV\t"},
},
// Load-combining tests.
{
fn: `
func $(b []byte) uint64 {
return binary.LittleEndian.Uint64(b)
}
`,
pos: []string{"\tMOVD\t\\(R[0-9]+\\)"},
},
{
fn: `
func $(b []byte, i int) uint64 {
return binary.LittleEndian.Uint64(b[i:])
}
`,
pos: []string{"\tMOVD\t\\(R[0-9]+\\)"},
},
{
fn: `
func $(b []byte) uint32 {
return binary.LittleEndian.Uint32(b)
}
`,
pos: []string{"\tMOVWU\t\\(R[0-9]+\\)"},
},
{
fn: `
func $(b []byte, i int) uint32 {
return binary.LittleEndian.Uint32(b[i:])
}
`,
pos: []string{"\tMOVWU\t\\(R[0-9]+\\)"},
},
{
fn: `
func $(b []byte) uint64 {
return binary.BigEndian.Uint64(b)
}
`,
pos: []string{"\tREV\t"},
},
{
fn: `
func $(b []byte, i int) uint64 {
return binary.BigEndian.Uint64(b[i:])
}
`,
pos: []string{"\tREV\t"},
},
{
fn: `
func $(b []byte) uint32 {
return binary.BigEndian.Uint32(b)
}
`,
pos: []string{"\tREVW\t"},
},
{
fn: `
func $(b []byte, i int) uint32 {
return binary.BigEndian.Uint32(b[i:])
}
`,
pos: []string{"\tREVW\t"},
},
{
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"},
},
// Intrinsic tests for math.
{
fn: `
func sqrt(x float64) float64 {
return math.Sqrt(x)
}
`,
pos: []string{"FSQRTD"},
},
{
fn: `
func ceil(x float64) float64 {
return math.Ceil(x)
}
`,
pos: []string{"FRINTPD"},
},
{
fn: `
func floor(x float64) float64 {
return math.Floor(x)
}
`,
pos: []string{"FRINTMD"},
},
{
fn: `
func round(x float64) float64 {
return math.Round(x)
}
`,
pos: []string{"FRINTAD"},
},
{
fn: `
func trunc(x float64) float64 {
return math.Trunc(x)
}
`,
pos: []string{"FRINTZD"},
},
{
// 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"},
},
}
var linuxMIPSTests = []*asmTest{
{
fn: `
func f0(a uint64) int {
return bits.Len64(a)
}
`,
pos: []string{"\tCLZ\t"},
},
{
fn: `
func f1(a uint32) int {
return bits.Len32(a)
}
`,
pos: []string{"\tCLZ\t"},
},
{
fn: `
func f2(a uint16) int {
return bits.Len16(a)
}
`,
pos: []string{"\tCLZ\t"},
},
{
fn: `
func f3(a uint8) int {
return bits.Len8(a)
}
`,
pos: []string{"\tCLZ\t"},
},
{
fn: `
func f4(a uint) int {
return bits.Len(a)
}
`,
pos: []string{"\tCLZ\t"},
},
{
fn: `
func f5(a uint64) int {
return bits.LeadingZeros64(a)
}
`,
pos: []string{"\tCLZ\t"},
},
{
fn: `
func f6(a uint32) int {
return bits.LeadingZeros32(a)
}
`,
pos: []string{"\tCLZ\t"},
},
{
fn: `
func f7(a uint16) int {
return bits.LeadingZeros16(a)
}
`,
pos: []string{"\tCLZ\t"},
},
{
fn: `
func f8(a uint8) int {
return bits.LeadingZeros8(a)
}
`,
pos: []string{"\tCLZ\t"},
},
{
fn: `
func f9(a uint) int {
return bits.LeadingZeros(a)
}
`,
pos: []string{"\tCLZ\t"},
},
{
// 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{
// Fused multiply-add/sub instructions.
{
fn: `
func f0(x, y, z float64) float64 {
return x * y + z
}
`,
pos: []string{"\tFMADD\t"},
},
{
fn: `
func f1(x, y, z float64) float64 {
return x * y - z
}
`,
pos: []string{"\tFMSUB\t"},
},
{
fn: `
func f2(x, y, z float32) float32 {
return x * y + z
}
`,
pos: []string{"\tFMADDS\t"},
},
{
fn: `
func f3(x, y, z float32) float32 {
return x * y - z
}
`,
pos: []string{"\tFMSUBS\t"},
},
{
fn: `
func f4(x uint32) uint32 {
return x<<7 | x>>25
}
`,
pos: []string{"\tROTLW\t"},
},
{
fn: `
func f5(x uint32) uint32 {
return x<<7 + x>>25
}
`,
pos: []string{"\tROTLW\t"},
},
{
fn: `
func f6(x uint32) uint32 {
return x<<7 ^ x>>25
}
`,
pos: []string{"\tROTLW\t"},
},
{
fn: `
func f7(x uint64) uint64 {
return x<<7 | x>>57
}
`,
pos: []string{"\tROTL\t"},
},
{
fn: `
func f8(x uint64) uint64 {
return x<<7 + x>>57
}
`,
pos: []string{"\tROTL\t"},
},
{
fn: `
func f9(x uint64) uint64 {
return x<<7 ^ x>>57
}
`,
pos: []string{"\tROTL\t"},
},
{
fn: `
func f10(a uint32) uint32 {
return bits.RotateLeft32(a, 9)
}
`,
pos: []string{"\tROTLW\t"},
},
{
fn: `
func f11(a uint64) uint64 {
return bits.RotateLeft64(a, 37)
}
`,
pos: []string{"\tROTL\t"},
},
{
fn: `
func f12(a, b float64) float64 {
return math.Copysign(a, b)
}
`,
pos: []string{"\tFCPSGN\t"},
},
{
fn: `
func f13(a float64) float64 {
return math.Abs(a)
}
`,
pos: []string{"\tFABS\t"},
},
{
fn: `
func f14(b []byte) uint16 {
return binary.LittleEndian.Uint16(b)
}
`,
pos: []string{"\tMOVHZ\t"},
},
{
fn: `
func f15(b []byte) uint32 {
return binary.LittleEndian.Uint32(b)
}
`,
pos: []string{"\tMOVWZ\t"},
},
{
fn: `
func f16(b []byte) uint64 {
return binary.LittleEndian.Uint64(b)
}
`,
pos: []string{"\tMOVD\t"},
neg: []string{"MOVBZ", "MOVHZ", "MOVWZ"},
},
{
fn: `
func f17(b []byte, v uint16) {
binary.LittleEndian.PutUint16(b, v)
}
`,
pos: []string{"\tMOVH\t"},
},
{
fn: `
func f18(b []byte, v uint32) {
binary.LittleEndian.PutUint32(b, v)
}
`,
pos: []string{"\tMOVW\t"},
},
{
fn: `
func f19(b []byte, v uint64) {
binary.LittleEndian.PutUint64(b, v)
}
`,
pos: []string{"\tMOVD\t"},
neg: []string{"MOVB", "MOVH", "MOVW"},
},
{
// check that stack store is optimized away
fn: `
func $() int {
var x int
return *(&x)
}
`,
pos: []string{"TEXT\t.*, [$]0-8"},
},
// Constant propagation through raw bits conversions.
{
// uint32 constant converted to float32 constant
fn: `
func $(x float32) float32 {
if x > math.Float32frombits(0x3f800000) {
return -x
}
return x
}
`,
pos: []string{"\tFMOVS\t[$]f32.3f800000\\(SB\\)"},
},
{
// float32 constant converted to uint32 constant
fn: `
func $(x uint32) uint32 {
if x > math.Float32bits(1) {
return -x
}
return x
}
`,
neg: []string{"\tFMOVS\t"},
},
}
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
}
`