github.com/dannin/go@v0.0.0-20161031215817-d35dfd405eaa/src/cmd/compile/internal/gc/asm_test.go (about) 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 testenv.MustHaveGoBuild(t) 25 if runtime.GOOS == "windows" { 26 // TODO: remove if we can get "go tool compile -S" to work on windows. 27 t.Skipf("skipping test: recursive windows compile not working") 28 } 29 dir, err := ioutil.TempDir("", "TestAssembly") 30 if err != nil { 31 t.Fatalf("could not create directory: %v", err) 32 } 33 defer os.RemoveAll(dir) 34 35 for _, test := range asmTests { 36 asm := compileToAsm(t, dir, test.arch, test.os, fmt.Sprintf(template, test.function)) 37 // Get rid of code for "".init. Also gets rid of type algorithms & other junk. 38 if i := strings.Index(asm, "\n\"\".init "); i >= 0 { 39 asm = asm[:i+1] 40 } 41 for _, r := range test.regexps { 42 if b, err := regexp.MatchString(r, asm); !b || err != nil { 43 t.Errorf("expected:%s\ngo:%s\nasm:%s\n", r, test.function, asm) 44 } 45 } 46 } 47 } 48 49 // compile compiles the package pkg for architecture arch and 50 // returns the generated assembly. dir is a scratch directory. 51 func compileToAsm(t *testing.T, dir, goarch, goos, pkg string) string { 52 // Create source. 53 src := filepath.Join(dir, "test.go") 54 f, err := os.Create(src) 55 if err != nil { 56 panic(err) 57 } 58 f.Write([]byte(pkg)) 59 f.Close() 60 61 // First, install any dependencies we need. This builds the required export data 62 // for any packages that are imported. 63 // TODO: extract dependencies automatically? 64 var stdout, stderr bytes.Buffer 65 cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", filepath.Join(dir, "encoding/binary.a"), "encoding/binary") 66 cmd.Env = mergeEnvLists([]string{"GOARCH=" + goarch, "GOOS=" + goos}, os.Environ()) 67 cmd.Stdout = &stdout 68 cmd.Stderr = &stderr 69 if err := cmd.Run(); err != nil { 70 panic(err) 71 } 72 if s := stdout.String(); s != "" { 73 panic(fmt.Errorf("Stdout = %s\nWant empty", s)) 74 } 75 if s := stderr.String(); s != "" { 76 panic(fmt.Errorf("Stderr = %s\nWant empty", s)) 77 } 78 79 // Now, compile the individual file for which we want to see the generated assembly. 80 cmd = exec.Command(testenv.GoToolPath(t), "tool", "compile", "-I", dir, "-S", "-o", filepath.Join(dir, "out.o"), src) 81 cmd.Env = mergeEnvLists([]string{"GOARCH=" + goarch, "GOOS=" + goos}, os.Environ()) 82 cmd.Stdout = &stdout 83 cmd.Stderr = &stderr 84 if err := cmd.Run(); err != nil { 85 panic(err) 86 } 87 if s := stderr.String(); s != "" { 88 panic(fmt.Errorf("Stderr = %s\nWant empty", s)) 89 } 90 return stdout.String() 91 } 92 93 // template to convert a function to a full file 94 const template = ` 95 package main 96 %s 97 ` 98 99 type asmTest struct { 100 // architecture to compile to 101 arch string 102 // os to compile to 103 os string 104 // function to compile 105 function string 106 // regexps that must match the generated assembly 107 regexps []string 108 } 109 110 var asmTests = [...]asmTest{ 111 {"amd64", "linux", ` 112 func f(x int) int { 113 return x * 64 114 } 115 `, 116 []string{"\tSHLQ\t\\$6,"}, 117 }, 118 {"amd64", "linux", ` 119 func f(x int) int { 120 return x * 96 121 }`, 122 []string{"\tSHLQ\t\\$5,", "\tLEAQ\t\\(.*\\)\\(.*\\*2\\),"}, 123 }, 124 // Load-combining tests. 125 {"amd64", "linux", ` 126 import "encoding/binary" 127 func f(b []byte) uint64 { 128 return binary.LittleEndian.Uint64(b) 129 } 130 `, 131 []string{"\tMOVQ\t\\(.*\\),"}, 132 }, 133 {"amd64", "linux", ` 134 import "encoding/binary" 135 func f(b []byte, i int) uint64 { 136 return binary.LittleEndian.Uint64(b[i:]) 137 } 138 `, 139 []string{"\tMOVQ\t\\(.*\\)\\(.*\\*1\\),"}, 140 }, 141 {"amd64", "linux", ` 142 import "encoding/binary" 143 func f(b []byte) uint32 { 144 return binary.LittleEndian.Uint32(b) 145 } 146 `, 147 []string{"\tMOVL\t\\(.*\\),"}, 148 }, 149 {"amd64", "linux", ` 150 import "encoding/binary" 151 func f(b []byte, i int) uint32 { 152 return binary.LittleEndian.Uint32(b[i:]) 153 } 154 `, 155 []string{"\tMOVL\t\\(.*\\)\\(.*\\*1\\),"}, 156 }, 157 {"386", "linux", ` 158 import "encoding/binary" 159 func f(b []byte) uint32 { 160 return binary.LittleEndian.Uint32(b) 161 } 162 `, 163 []string{"\tMOVL\t\\(.*\\),"}, 164 }, 165 {"386", "linux", ` 166 import "encoding/binary" 167 func f(b []byte, i int) uint32 { 168 return binary.LittleEndian.Uint32(b[i:]) 169 } 170 `, 171 []string{"\tMOVL\t\\(.*\\)\\(.*\\*1\\),"}, 172 }, 173 } 174 175 // mergeEnvLists merges the two environment lists such that 176 // variables with the same name in "in" replace those in "out". 177 // This always returns a newly allocated slice. 178 func mergeEnvLists(in, out []string) []string { 179 out = append([]string(nil), out...) 180 NextVar: 181 for _, inkv := range in { 182 k := strings.SplitAfterN(inkv, "=", 2)[0] 183 for i, outkv := range out { 184 if strings.HasPrefix(outkv, k) { 185 out[i] = inkv 186 continue NextVar 187 } 188 } 189 out = append(out, inkv) 190 } 191 return out 192 } 193 194 // TestLineNumber checks to make sure the generated assembly has line numbers 195 // see issue #16214 196 func TestLineNumber(t *testing.T) { 197 testenv.MustHaveGoBuild(t) 198 dir, err := ioutil.TempDir("", "TestLineNumber") 199 if err != nil { 200 t.Fatalf("could not create directory: %v", err) 201 } 202 defer os.RemoveAll(dir) 203 204 src := filepath.Join(dir, "x.go") 205 err = ioutil.WriteFile(src, []byte(issue16214src), 0644) 206 if err != nil { 207 t.Fatalf("could not write file: %v", err) 208 } 209 210 cmd := exec.Command(testenv.GoToolPath(t), "tool", "compile", "-S", "-o", filepath.Join(dir, "out.o"), src) 211 out, err := cmd.CombinedOutput() 212 if err != nil { 213 t.Fatalf("fail to run go tool compile: %v", err) 214 } 215 216 if strings.Contains(string(out), "unknown line number") { 217 t.Errorf("line number missing in assembly:\n%s", out) 218 } 219 } 220 221 var issue16214src = ` 222 package main 223 224 func Mod32(x uint32) uint32 { 225 return x % 3 // frontend rewrites it as HMUL with 2863311531, the LITERAL node has Lineno 0 226 } 227 `