github.com/riscv/riscv-go@v0.0.0-20200123204226-124ebd6fcc8e/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 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 // Rotate tests 239 {"amd64", "linux", ` 240 func f(x uint64) uint64 { 241 return x<<7 | x>>57 242 } 243 `, 244 []string{"\tROLQ\t[$]7,"}, 245 }, 246 {"amd64", "linux", ` 247 func f(x uint64) uint64 { 248 return x<<7 + x>>57 249 } 250 `, 251 []string{"\tROLQ\t[$]7,"}, 252 }, 253 {"amd64", "linux", ` 254 func f(x uint64) uint64 { 255 return x<<7 ^ x>>57 256 } 257 `, 258 []string{"\tROLQ\t[$]7,"}, 259 }, 260 {"amd64", "linux", ` 261 func f(x uint32) uint32 { 262 return x<<7 + x>>25 263 } 264 `, 265 []string{"\tROLL\t[$]7,"}, 266 }, 267 {"amd64", "linux", ` 268 func f(x uint32) uint32 { 269 return x<<7 | x>>25 270 } 271 `, 272 []string{"\tROLL\t[$]7,"}, 273 }, 274 {"amd64", "linux", ` 275 func f(x uint32) uint32 { 276 return x<<7 ^ x>>25 277 } 278 `, 279 []string{"\tROLL\t[$]7,"}, 280 }, 281 {"amd64", "linux", ` 282 func f(x uint16) uint16 { 283 return x<<7 + x>>9 284 } 285 `, 286 []string{"\tROLW\t[$]7,"}, 287 }, 288 {"amd64", "linux", ` 289 func f(x uint16) uint16 { 290 return x<<7 | x>>9 291 } 292 `, 293 []string{"\tROLW\t[$]7,"}, 294 }, 295 {"amd64", "linux", ` 296 func f(x uint16) uint16 { 297 return x<<7 ^ x>>9 298 } 299 `, 300 []string{"\tROLW\t[$]7,"}, 301 }, 302 {"amd64", "linux", ` 303 func f(x uint8) uint8 { 304 return x<<7 + x>>1 305 } 306 `, 307 []string{"\tROLB\t[$]7,"}, 308 }, 309 {"amd64", "linux", ` 310 func f(x uint8) uint8 { 311 return x<<7 | x>>1 312 } 313 `, 314 []string{"\tROLB\t[$]7,"}, 315 }, 316 {"amd64", "linux", ` 317 func f(x uint8) uint8 { 318 return x<<7 ^ x>>1 319 } 320 `, 321 []string{"\tROLB\t[$]7,"}, 322 }, 323 324 {"arm", "linux", ` 325 func f(x uint32) uint32 { 326 return x<<7 + x>>25 327 } 328 `, 329 []string{"\tMOVW\tR[0-9]+@>25,"}, 330 }, 331 {"arm", "linux", ` 332 func f(x uint32) uint32 { 333 return x<<7 | x>>25 334 } 335 `, 336 []string{"\tMOVW\tR[0-9]+@>25,"}, 337 }, 338 {"arm", "linux", ` 339 func f(x uint32) uint32 { 340 return x<<7 ^ x>>25 341 } 342 `, 343 []string{"\tMOVW\tR[0-9]+@>25,"}, 344 }, 345 346 {"arm64", "linux", ` 347 func f(x uint64) uint64 { 348 return x<<7 + x>>57 349 } 350 `, 351 []string{"\tROR\t[$]57,"}, 352 }, 353 {"arm64", "linux", ` 354 func f(x uint64) uint64 { 355 return x<<7 | x>>57 356 } 357 `, 358 []string{"\tROR\t[$]57,"}, 359 }, 360 {"arm64", "linux", ` 361 func f(x uint64) uint64 { 362 return x<<7 ^ x>>57 363 } 364 `, 365 []string{"\tROR\t[$]57,"}, 366 }, 367 {"arm64", "linux", ` 368 func f(x uint32) uint32 { 369 return x<<7 + x>>25 370 } 371 `, 372 []string{"\tRORW\t[$]25,"}, 373 }, 374 {"arm64", "linux", ` 375 func f(x uint32) uint32 { 376 return x<<7 | x>>25 377 } 378 `, 379 []string{"\tRORW\t[$]25,"}, 380 }, 381 {"arm64", "linux", ` 382 func f(x uint32) uint32 { 383 return x<<7 ^ x>>25 384 } 385 `, 386 []string{"\tRORW\t[$]25,"}, 387 }, 388 389 {"s390x", "linux", ` 390 func f(x uint64) uint64 { 391 return x<<7 + x>>57 392 } 393 `, 394 []string{"\tRLLG\t[$]7,"}, 395 }, 396 {"s390x", "linux", ` 397 func f(x uint64) uint64 { 398 return x<<7 | x>>57 399 } 400 `, 401 []string{"\tRLLG\t[$]7,"}, 402 }, 403 {"s390x", "linux", ` 404 func f(x uint64) uint64 { 405 return x<<7 ^ x>>57 406 } 407 `, 408 []string{"\tRLLG\t[$]7,"}, 409 }, 410 {"s390x", "linux", ` 411 func f(x uint32) uint32 { 412 return x<<7 + x>>25 413 } 414 `, 415 []string{"\tRLL\t[$]7,"}, 416 }, 417 {"s390x", "linux", ` 418 func f(x uint32) uint32 { 419 return x<<7 | x>>25 420 } 421 `, 422 []string{"\tRLL\t[$]7,"}, 423 }, 424 {"s390x", "linux", ` 425 func f(x uint32) uint32 { 426 return x<<7 ^ x>>25 427 } 428 `, 429 []string{"\tRLL\t[$]7,"}, 430 }, 431 432 // Rotate after inlining (see issue 18254). 433 {"amd64", "linux", ` 434 func f(x uint32, k uint) uint32 { 435 return x<<k | x>>(32-k) 436 } 437 func g(x uint32) uint32 { 438 return f(x, 7) 439 } 440 `, 441 []string{"\tROLL\t[$]7,"}, 442 }, 443 } 444 445 // mergeEnvLists merges the two environment lists such that 446 // variables with the same name in "in" replace those in "out". 447 // This always returns a newly allocated slice. 448 func mergeEnvLists(in, out []string) []string { 449 out = append([]string(nil), out...) 450 NextVar: 451 for _, inkv := range in { 452 k := strings.SplitAfterN(inkv, "=", 2)[0] 453 for i, outkv := range out { 454 if strings.HasPrefix(outkv, k) { 455 out[i] = inkv 456 continue NextVar 457 } 458 } 459 out = append(out, inkv) 460 } 461 return out 462 } 463 464 // TestLineNumber checks to make sure the generated assembly has line numbers 465 // see issue #16214 466 func TestLineNumber(t *testing.T) { 467 testenv.MustHaveGoBuild(t) 468 dir, err := ioutil.TempDir("", "TestLineNumber") 469 if err != nil { 470 t.Fatalf("could not create directory: %v", err) 471 } 472 defer os.RemoveAll(dir) 473 474 src := filepath.Join(dir, "x.go") 475 err = ioutil.WriteFile(src, []byte(issue16214src), 0644) 476 if err != nil { 477 t.Fatalf("could not write file: %v", err) 478 } 479 480 cmd := exec.Command(testenv.GoToolPath(t), "tool", "compile", "-S", "-o", filepath.Join(dir, "out.o"), src) 481 out, err := cmd.CombinedOutput() 482 if err != nil { 483 t.Fatalf("fail to run go tool compile: %v", err) 484 } 485 486 if strings.Contains(string(out), "unknown line number") { 487 t.Errorf("line number missing in assembly:\n%s", out) 488 } 489 } 490 491 var issue16214src = ` 492 package main 493 494 func Mod32(x uint32) uint32 { 495 return x % 3 // frontend rewrites it as HMUL with 2863311531, the LITERAL node has unknown Pos 496 } 497 `