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