Home | History | Annotate | Download | only in gc
      1 // Copyright 2016 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 package gc
      6 
      7 import (
      8 	"bytes"
      9 	"fmt"
     10 	"internal/testenv"
     11 	"io/ioutil"
     12 	"os"
     13 	"os/exec"
     14 	"path/filepath"
     15 	"regexp"
     16 	"runtime"
     17 	"strings"
     18 	"testing"
     19 )
     20 
     21 // TestAssembly checks to make sure the assembly generated for
     22 // functions contains certain expected instructions.
     23 func TestAssembly(t *testing.T) {
     24 	if testing.Short() {
     25 		t.Skip("slow test; skipping")
     26 	}
     27 	testenv.MustHaveGoBuild(t)
     28 	if runtime.GOOS == "windows" {
     29 		// TODO: remove if we can get "go tool compile -S" to work on windows.
     30 		t.Skipf("skipping test: recursive windows compile not working")
     31 	}
     32 	dir, err := ioutil.TempDir("", "TestAssembly")
     33 	if err != nil {
     34 		t.Fatalf("could not create directory: %v", err)
     35 	}
     36 	defer os.RemoveAll(dir)
     37 
     38 	for _, test := range asmTests {
     39 		asm := compileToAsm(t, dir, test.arch, test.os, fmt.Sprintf(template, test.function))
     40 		// Get rid of code for "".init. Also gets rid of type algorithms & other junk.
     41 		if i := strings.Index(asm, "\n\"\".init "); i >= 0 {
     42 			asm = asm[:i+1]
     43 		}
     44 		for _, r := range test.regexps {
     45 			if b, err := regexp.MatchString(r, asm); !b || err != nil {
     46 				t.Errorf("expected:%s\ngo:%s\nasm:%s\n", r, test.function, asm)
     47 			}
     48 		}
     49 	}
     50 }
     51 
     52 // compile compiles the package pkg for architecture arch and
     53 // returns the generated assembly.  dir is a scratch directory.
     54 func compileToAsm(t *testing.T, dir, goarch, goos, pkg string) string {
     55 	// Create source.
     56 	src := filepath.Join(dir, "test.go")
     57 	f, err := os.Create(src)
     58 	if err != nil {
     59 		panic(err)
     60 	}
     61 	f.Write([]byte(pkg))
     62 	f.Close()
     63 
     64 	// First, install any dependencies we need.  This builds the required export data
     65 	// for any packages that are imported.
     66 	// TODO: extract dependencies automatically?
     67 	var stdout, stderr bytes.Buffer
     68 	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", filepath.Join(dir, "encoding/binary.a"), "encoding/binary")
     69 	cmd.Env = mergeEnvLists([]string{"GOARCH=" + goarch, "GOOS=" + goos}, os.Environ())
     70 	cmd.Stdout = &stdout
     71 	cmd.Stderr = &stderr
     72 	if err := cmd.Run(); err != nil {
     73 		panic(err)
     74 	}
     75 	if s := stdout.String(); s != "" {
     76 		panic(fmt.Errorf("Stdout = %s\nWant empty", s))
     77 	}
     78 	if s := stderr.String(); s != "" {
     79 		panic(fmt.Errorf("Stderr = %s\nWant empty", s))
     80 	}
     81 
     82 	// Now, compile the individual file for which we want to see the generated assembly.
     83 	cmd = exec.Command(testenv.GoToolPath(t), "tool", "compile", "-I", dir, "-S", "-o", filepath.Join(dir, "out.o"), src)
     84 	cmd.Env = mergeEnvLists([]string{"GOARCH=" + goarch, "GOOS=" + goos}, os.Environ())
     85 	cmd.Stdout = &stdout
     86 	cmd.Stderr = &stderr
     87 	if err := cmd.Run(); err != nil {
     88 		panic(err)
     89 	}
     90 	if s := stderr.String(); s != "" {
     91 		panic(fmt.Errorf("Stderr = %s\nWant empty", s))
     92 	}
     93 	return stdout.String()
     94 }
     95 
     96 // template to convert a function to a full file
     97 const template = `
     98 package main
     99 %s
    100 `
    101 
    102 type asmTest struct {
    103 	// architecture to compile to
    104 	arch string
    105 	// os to compile to
    106 	os string
    107 	// function to compile
    108 	function string
    109 	// regexps that must match the generated assembly
    110 	regexps []string
    111 }
    112 
    113 var asmTests = [...]asmTest{
    114 	{"amd64", "linux", `
    115 func f(x int) int {
    116 	return x * 64
    117 }
    118 `,
    119 		[]string{"\tSHLQ\t\\$6,"},
    120 	},
    121 	{"amd64", "linux", `
    122 func f(x int) int {
    123 	return x * 96
    124 }`,
    125 		[]string{"\tSHLQ\t\\$5,", "\tLEAQ\t\\(.*\\)\\(.*\\*2\\),"},
    126 	},
    127 	// Load-combining tests.
    128 	{"amd64", "linux", `
    129 import "encoding/binary"
    130 func f(b []byte) uint64 {
    131 	return binary.LittleEndian.Uint64(b)
    132 }
    133 `,
    134 		[]string{"\tMOVQ\t\\(.*\\),"},
    135 	},
    136 	{"amd64", "linux", `
    137 import "encoding/binary"
    138 func f(b []byte, i int) uint64 {
    139 	return binary.LittleEndian.Uint64(b[i:])
    140 }
    141 `,
    142 		[]string{"\tMOVQ\t\\(.*\\)\\(.*\\*1\\),"},
    143 	},
    144 	{"amd64", "linux", `
    145 import "encoding/binary"
    146 func f(b []byte) uint32 {
    147 	return binary.LittleEndian.Uint32(b)
    148 }
    149 `,
    150 		[]string{"\tMOVL\t\\(.*\\),"},
    151 	},
    152 	{"amd64", "linux", `
    153 import "encoding/binary"
    154 func f(b []byte, i int) uint32 {
    155 	return binary.LittleEndian.Uint32(b[i:])
    156 }
    157 `,
    158 		[]string{"\tMOVL\t\\(.*\\)\\(.*\\*1\\),"},
    159 	},
    160 	{"amd64", "linux", `
    161 import "encoding/binary"
    162 func f(b []byte) uint64 {
    163 	return binary.BigEndian.Uint64(b)
    164 }
    165 `,
    166 		[]string{"\tBSWAPQ\t"},
    167 	},
    168 	{"amd64", "linux", `
    169 import "encoding/binary"
    170 func f(b []byte, i int) uint64 {
    171 	return binary.BigEndian.Uint64(b[i:])
    172 }
    173 `,
    174 		[]string{"\tBSWAPQ\t"},
    175 	},
    176 	{"amd64", "linux", `
    177 import "encoding/binary"
    178 func f(b []byte, v uint64) {
    179 	binary.BigEndian.PutUint64(b, v)
    180 }
    181 `,
    182 		[]string{"\tBSWAPQ\t"},
    183 	},
    184 	{"amd64", "linux", `
    185 import "encoding/binary"
    186 func f(b []byte) uint32 {
    187 	return binary.BigEndian.Uint32(b)
    188 }
    189 `,
    190 		[]string{"\tBSWAPL\t"},
    191 	},
    192 	{"amd64", "linux", `
    193 import "encoding/binary"
    194 func f(b []byte, i int) uint32 {
    195 	return binary.BigEndian.Uint32(b[i:])
    196 }
    197 `,
    198 		[]string{"\tBSWAPL\t"},
    199 	},
    200 	{"amd64", "linux", `
    201 import "encoding/binary"
    202 func f(b []byte, v uint32) {
    203 	binary.BigEndian.PutUint32(b, v)
    204 }
    205 `,
    206 		[]string{"\tBSWAPL\t"},
    207 	},
    208 	{"386", "linux", `
    209 import "encoding/binary"
    210 func f(b []byte) uint32 {
    211 	return binary.LittleEndian.Uint32(b)
    212 }
    213 `,
    214 		[]string{"\tMOVL\t\\(.*\\),"},
    215 	},
    216 	{"386", "linux", `
    217 import "encoding/binary"
    218 func f(b []byte, i int) uint32 {
    219 	return binary.LittleEndian.Uint32(b[i:])
    220 }
    221 `,
    222 		[]string{"\tMOVL\t\\(.*\\)\\(.*\\*1\\),"},
    223 	},
    224 
    225 	// Structure zeroing.  See issue #18370.
    226 	{"amd64", "linux", `
    227 type T struct {
    228 	a, b, c int
    229 }
    230 func f(t *T) {
    231 	*t = T{}
    232 }
    233 `,
    234 		[]string{"\tMOVQ\t\\$0, \\(.*\\)", "\tMOVQ\t\\$0, 8\\(.*\\)", "\tMOVQ\t\\$0, 16\\(.*\\)"},
    235 	},
    236 	// TODO: add a test for *t = T{3,4,5} when we fix that.
    237 }
    238 
    239 // mergeEnvLists merges the two environment lists such that
    240 // variables with the same name in "in" replace those in "out".
    241 // This always returns a newly allocated slice.
    242 func mergeEnvLists(in, out []string) []string {
    243 	out = append([]string(nil), out...)
    244 NextVar:
    245 	for _, inkv := range in {
    246 		k := strings.SplitAfterN(inkv, "=", 2)[0]
    247 		for i, outkv := range out {
    248 			if strings.HasPrefix(outkv, k) {
    249 				out[i] = inkv
    250 				continue NextVar
    251 			}
    252 		}
    253 		out = append(out, inkv)
    254 	}
    255 	return out
    256 }
    257 
    258 // TestLineNumber checks to make sure the generated assembly has line numbers
    259 // see issue #16214
    260 func TestLineNumber(t *testing.T) {
    261 	testenv.MustHaveGoBuild(t)
    262 	dir, err := ioutil.TempDir("", "TestLineNumber")
    263 	if err != nil {
    264 		t.Fatalf("could not create directory: %v", err)
    265 	}
    266 	defer os.RemoveAll(dir)
    267 
    268 	src := filepath.Join(dir, "x.go")
    269 	err = ioutil.WriteFile(src, []byte(issue16214src), 0644)
    270 	if err != nil {
    271 		t.Fatalf("could not write file: %v", err)
    272 	}
    273 
    274 	cmd := exec.Command(testenv.GoToolPath(t), "tool", "compile", "-S", "-o", filepath.Join(dir, "out.o"), src)
    275 	out, err := cmd.CombinedOutput()
    276 	if err != nil {
    277 		t.Fatalf("fail to run go tool compile: %v", err)
    278 	}
    279 
    280 	if strings.Contains(string(out), "unknown line number") {
    281 		t.Errorf("line number missing in assembly:\n%s", out)
    282 	}
    283 }
    284 
    285 var issue16214src = `
    286 package main
    287 
    288 func Mod32(x uint32) uint32 {
    289 	return x % 3 // frontend rewrites it as HMUL with 2863311531, the LITERAL node has Lineno 0
    290 }
    291 `
    292