github.com/rakyll/go@v0.0.0-20170216000551-64c02460d703/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("%s/%s: expected:%s\ngo:%s\nasm:%s\n", test.os, test.arch, 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, i int, v uint64) { 187 binary.BigEndian.PutUint64(b[i:], v) 188 } 189 `, 190 []string{"\tBSWAPQ\t"}, 191 }, 192 {"amd64", "linux", ` 193 import "encoding/binary" 194 func f(b []byte) uint32 { 195 return binary.BigEndian.Uint32(b) 196 } 197 `, 198 []string{"\tBSWAPL\t"}, 199 }, 200 {"amd64", "linux", ` 201 import "encoding/binary" 202 func f(b []byte, i int) uint32 { 203 return binary.BigEndian.Uint32(b[i:]) 204 } 205 `, 206 []string{"\tBSWAPL\t"}, 207 }, 208 {"amd64", "linux", ` 209 import "encoding/binary" 210 func f(b []byte, v uint32) { 211 binary.BigEndian.PutUint32(b, v) 212 } 213 `, 214 []string{"\tBSWAPL\t"}, 215 }, 216 {"amd64", "linux", ` 217 import "encoding/binary" 218 func f(b []byte, i int, v uint32) { 219 binary.BigEndian.PutUint32(b[i:], v) 220 } 221 `, 222 []string{"\tBSWAPL\t"}, 223 }, 224 {"amd64", "linux", ` 225 import "encoding/binary" 226 func f(b []byte) uint16 { 227 return binary.BigEndian.Uint16(b) 228 } 229 `, 230 []string{"\tROLW\t\\$8,"}, 231 }, 232 {"amd64", "linux", ` 233 import "encoding/binary" 234 func f(b []byte, i int) uint16 { 235 return binary.BigEndian.Uint16(b[i:]) 236 } 237 `, 238 []string{"\tROLW\t\\$8,"}, 239 }, 240 {"amd64", "linux", ` 241 import "encoding/binary" 242 func f(b []byte, v uint16) { 243 binary.BigEndian.PutUint16(b, v) 244 } 245 `, 246 []string{"\tROLW\t\\$8,"}, 247 }, 248 {"amd64", "linux", ` 249 import "encoding/binary" 250 func f(b []byte, i int, v uint16) { 251 binary.BigEndian.PutUint16(b[i:], v) 252 } 253 `, 254 []string{"\tROLW\t\\$8,"}, 255 }, 256 {"386", "linux", ` 257 import "encoding/binary" 258 func f(b []byte) uint32 { 259 return binary.LittleEndian.Uint32(b) 260 } 261 `, 262 []string{"\tMOVL\t\\(.*\\),"}, 263 }, 264 {"386", "linux", ` 265 import "encoding/binary" 266 func f(b []byte, i int) uint32 { 267 return binary.LittleEndian.Uint32(b[i:]) 268 } 269 `, 270 []string{"\tMOVL\t\\(.*\\)\\(.*\\*1\\),"}, 271 }, 272 {"s390x", "linux", ` 273 import "encoding/binary" 274 func f(b []byte) uint32 { 275 return binary.LittleEndian.Uint32(b) 276 } 277 `, 278 []string{"\tMOVWBR\t\\(.*\\),"}, 279 }, 280 {"s390x", "linux", ` 281 import "encoding/binary" 282 func f(b []byte, i int) uint32 { 283 return binary.LittleEndian.Uint32(b[i:]) 284 } 285 `, 286 []string{"\tMOVWBR\t\\(.*\\)\\(.*\\*1\\),"}, 287 }, 288 {"s390x", "linux", ` 289 import "encoding/binary" 290 func f(b []byte) uint64 { 291 return binary.LittleEndian.Uint64(b) 292 } 293 `, 294 []string{"\tMOVDBR\t\\(.*\\),"}, 295 }, 296 {"s390x", "linux", ` 297 import "encoding/binary" 298 func f(b []byte, i int) uint64 { 299 return binary.LittleEndian.Uint64(b[i:]) 300 } 301 `, 302 []string{"\tMOVDBR\t\\(.*\\)\\(.*\\*1\\),"}, 303 }, 304 {"s390x", "linux", ` 305 import "encoding/binary" 306 func f(b []byte) uint32 { 307 return binary.BigEndian.Uint32(b) 308 } 309 `, 310 []string{"\tMOVWZ\t\\(.*\\),"}, 311 }, 312 {"s390x", "linux", ` 313 import "encoding/binary" 314 func f(b []byte, i int) uint32 { 315 return binary.BigEndian.Uint32(b[i:]) 316 } 317 `, 318 []string{"\tMOVWZ\t\\(.*\\)\\(.*\\*1\\),"}, 319 }, 320 {"s390x", "linux", ` 321 import "encoding/binary" 322 func f(b []byte) uint64 { 323 return binary.BigEndian.Uint64(b) 324 } 325 `, 326 []string{"\tMOVD\t\\(.*\\),"}, 327 }, 328 {"s390x", "linux", ` 329 import "encoding/binary" 330 func f(b []byte, i int) uint64 { 331 return binary.BigEndian.Uint64(b[i:]) 332 } 333 `, 334 []string{"\tMOVD\t\\(.*\\)\\(.*\\*1\\),"}, 335 }, 336 337 // Structure zeroing. See issue #18370. 338 {"amd64", "linux", ` 339 type T struct { 340 a, b, c int 341 } 342 func f(t *T) { 343 *t = T{} 344 } 345 `, 346 []string{"\tMOVQ\t\\$0, \\(.*\\)", "\tMOVQ\t\\$0, 8\\(.*\\)", "\tMOVQ\t\\$0, 16\\(.*\\)"}, 347 }, 348 // TODO: add a test for *t = T{3,4,5} when we fix that. 349 // Also test struct containing pointers (this was special because of write barriers). 350 {"amd64", "linux", ` 351 type T struct { 352 a, b, c *int 353 } 354 func f(t *T) { 355 *t = T{} 356 } 357 `, 358 []string{"\tMOVQ\t\\$0, \\(.*\\)", "\tMOVQ\t\\$0, 8\\(.*\\)", "\tMOVQ\t\\$0, 16\\(.*\\)", "\tCALL\truntime\\.writebarrierptr\\(SB\\)"}, 359 }, 360 361 // Rotate tests 362 {"amd64", "linux", ` 363 func f(x uint64) uint64 { 364 return x<<7 | x>>57 365 } 366 `, 367 []string{"\tROLQ\t[$]7,"}, 368 }, 369 {"amd64", "linux", ` 370 func f(x uint64) uint64 { 371 return x<<7 + x>>57 372 } 373 `, 374 []string{"\tROLQ\t[$]7,"}, 375 }, 376 {"amd64", "linux", ` 377 func f(x uint64) uint64 { 378 return x<<7 ^ x>>57 379 } 380 `, 381 []string{"\tROLQ\t[$]7,"}, 382 }, 383 {"amd64", "linux", ` 384 func f(x uint32) uint32 { 385 return x<<7 + x>>25 386 } 387 `, 388 []string{"\tROLL\t[$]7,"}, 389 }, 390 {"amd64", "linux", ` 391 func f(x uint32) uint32 { 392 return x<<7 | x>>25 393 } 394 `, 395 []string{"\tROLL\t[$]7,"}, 396 }, 397 {"amd64", "linux", ` 398 func f(x uint32) uint32 { 399 return x<<7 ^ x>>25 400 } 401 `, 402 []string{"\tROLL\t[$]7,"}, 403 }, 404 {"amd64", "linux", ` 405 func f(x uint16) uint16 { 406 return x<<7 + x>>9 407 } 408 `, 409 []string{"\tROLW\t[$]7,"}, 410 }, 411 {"amd64", "linux", ` 412 func f(x uint16) uint16 { 413 return x<<7 | x>>9 414 } 415 `, 416 []string{"\tROLW\t[$]7,"}, 417 }, 418 {"amd64", "linux", ` 419 func f(x uint16) uint16 { 420 return x<<7 ^ x>>9 421 } 422 `, 423 []string{"\tROLW\t[$]7,"}, 424 }, 425 {"amd64", "linux", ` 426 func f(x uint8) uint8 { 427 return x<<7 + x>>1 428 } 429 `, 430 []string{"\tROLB\t[$]7,"}, 431 }, 432 {"amd64", "linux", ` 433 func f(x uint8) uint8 { 434 return x<<7 | x>>1 435 } 436 `, 437 []string{"\tROLB\t[$]7,"}, 438 }, 439 {"amd64", "linux", ` 440 func f(x uint8) uint8 { 441 return x<<7 ^ x>>1 442 } 443 `, 444 []string{"\tROLB\t[$]7,"}, 445 }, 446 447 {"arm", "linux", ` 448 func f(x uint32) uint32 { 449 return x<<7 + x>>25 450 } 451 `, 452 []string{"\tMOVW\tR[0-9]+@>25,"}, 453 }, 454 {"arm", "linux", ` 455 func f(x uint32) uint32 { 456 return x<<7 | x>>25 457 } 458 `, 459 []string{"\tMOVW\tR[0-9]+@>25,"}, 460 }, 461 {"arm", "linux", ` 462 func f(x uint32) uint32 { 463 return x<<7 ^ x>>25 464 } 465 `, 466 []string{"\tMOVW\tR[0-9]+@>25,"}, 467 }, 468 469 {"arm64", "linux", ` 470 func f(x uint64) uint64 { 471 return x<<7 + x>>57 472 } 473 `, 474 []string{"\tROR\t[$]57,"}, 475 }, 476 {"arm64", "linux", ` 477 func f(x uint64) uint64 { 478 return x<<7 | x>>57 479 } 480 `, 481 []string{"\tROR\t[$]57,"}, 482 }, 483 {"arm64", "linux", ` 484 func f(x uint64) uint64 { 485 return x<<7 ^ x>>57 486 } 487 `, 488 []string{"\tROR\t[$]57,"}, 489 }, 490 {"arm64", "linux", ` 491 func f(x uint32) uint32 { 492 return x<<7 + x>>25 493 } 494 `, 495 []string{"\tRORW\t[$]25,"}, 496 }, 497 {"arm64", "linux", ` 498 func f(x uint32) uint32 { 499 return x<<7 | x>>25 500 } 501 `, 502 []string{"\tRORW\t[$]25,"}, 503 }, 504 {"arm64", "linux", ` 505 func f(x uint32) uint32 { 506 return x<<7 ^ x>>25 507 } 508 `, 509 []string{"\tRORW\t[$]25,"}, 510 }, 511 512 {"s390x", "linux", ` 513 func f(x uint64) uint64 { 514 return x<<7 + x>>57 515 } 516 `, 517 []string{"\tRLLG\t[$]7,"}, 518 }, 519 {"s390x", "linux", ` 520 func f(x uint64) uint64 { 521 return x<<7 | x>>57 522 } 523 `, 524 []string{"\tRLLG\t[$]7,"}, 525 }, 526 {"s390x", "linux", ` 527 func f(x uint64) uint64 { 528 return x<<7 ^ x>>57 529 } 530 `, 531 []string{"\tRLLG\t[$]7,"}, 532 }, 533 {"s390x", "linux", ` 534 func f(x uint32) uint32 { 535 return x<<7 + x>>25 536 } 537 `, 538 []string{"\tRLL\t[$]7,"}, 539 }, 540 {"s390x", "linux", ` 541 func f(x uint32) uint32 { 542 return x<<7 | x>>25 543 } 544 `, 545 []string{"\tRLL\t[$]7,"}, 546 }, 547 {"s390x", "linux", ` 548 func f(x uint32) uint32 { 549 return x<<7 ^ x>>25 550 } 551 `, 552 []string{"\tRLL\t[$]7,"}, 553 }, 554 555 // Rotate after inlining (see issue 18254). 556 {"amd64", "linux", ` 557 func f(x uint32, k uint) uint32 { 558 return x<<k | x>>(32-k) 559 } 560 func g(x uint32) uint32 { 561 return f(x, 7) 562 } 563 `, 564 []string{"\tROLL\t[$]7,"}, 565 }, 566 567 // Direct use of constants in fast map access calls. Issue 19015. 568 {"amd64", "linux", ` 569 func f(m map[int]int) int { 570 return m[5] 571 } 572 `, 573 []string{"\tMOVQ\t[$]5,"}, 574 }, 575 {"amd64", "linux", ` 576 func f(m map[int]int) bool { 577 _, ok := m[5] 578 return ok 579 } 580 `, 581 []string{"\tMOVQ\t[$]5,"}, 582 }, 583 {"amd64", "linux", ` 584 func f(m map[string]int) int { 585 return m["abc"] 586 } 587 `, 588 []string{"\"abc\""}, 589 }, 590 {"amd64", "linux", ` 591 func f(m map[string]int) bool { 592 _, ok := m["abc"] 593 return ok 594 } 595 `, 596 []string{"\"abc\""}, 597 }, 598 } 599 600 // mergeEnvLists merges the two environment lists such that 601 // variables with the same name in "in" replace those in "out". 602 // This always returns a newly allocated slice. 603 func mergeEnvLists(in, out []string) []string { 604 out = append([]string(nil), out...) 605 NextVar: 606 for _, inkv := range in { 607 k := strings.SplitAfterN(inkv, "=", 2)[0] 608 for i, outkv := range out { 609 if strings.HasPrefix(outkv, k) { 610 out[i] = inkv 611 continue NextVar 612 } 613 } 614 out = append(out, inkv) 615 } 616 return out 617 } 618 619 // TestLineNumber checks to make sure the generated assembly has line numbers 620 // see issue #16214 621 func TestLineNumber(t *testing.T) { 622 testenv.MustHaveGoBuild(t) 623 dir, err := ioutil.TempDir("", "TestLineNumber") 624 if err != nil { 625 t.Fatalf("could not create directory: %v", err) 626 } 627 defer os.RemoveAll(dir) 628 629 src := filepath.Join(dir, "x.go") 630 err = ioutil.WriteFile(src, []byte(issue16214src), 0644) 631 if err != nil { 632 t.Fatalf("could not write file: %v", err) 633 } 634 635 cmd := exec.Command(testenv.GoToolPath(t), "tool", "compile", "-S", "-o", filepath.Join(dir, "out.o"), src) 636 out, err := cmd.CombinedOutput() 637 if err != nil { 638 t.Fatalf("fail to run go tool compile: %v", err) 639 } 640 641 if strings.Contains(string(out), "unknown line number") { 642 t.Errorf("line number missing in assembly:\n%s", out) 643 } 644 } 645 646 var issue16214src = ` 647 package main 648 649 func Mod32(x uint32) uint32 { 650 return x % 3 // frontend rewrites it as HMUL with 2863311531, the LITERAL node has unknown Pos 651 } 652 `