1// Copyright 2015 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 5package asm 6 7import ( 8 "bufio" 9 "bytes" 10 "fmt" 11 "internal/buildcfg" 12 "os" 13 "path/filepath" 14 "regexp" 15 "sort" 16 "strconv" 17 "strings" 18 "testing" 19 20 "cmd/asm/internal/lex" 21 "cmd/internal/obj" 22) 23 24// An end-to-end test for the assembler: Do we print what we parse? 25// Output is generated by, in effect, turning on -S and comparing the 26// result against a golden file. 27 28func testEndToEnd(t *testing.T, goarch, file string) { 29 input := filepath.Join("testdata", file+".s") 30 architecture, ctxt := setArch(goarch) 31 architecture.Init(ctxt) 32 lexer := lex.NewLexer(input) 33 parser := NewParser(ctxt, architecture, lexer) 34 pList := new(obj.Plist) 35 var ok bool 36 testOut = new(strings.Builder) // The assembler writes test output to this buffer. 37 ctxt.Bso = bufio.NewWriter(os.Stdout) 38 ctxt.IsAsm = true 39 defer ctxt.Bso.Flush() 40 failed := false 41 ctxt.DiagFunc = func(format string, args ...interface{}) { 42 failed = true 43 t.Errorf(format, args...) 44 } 45 pList.Firstpc, ok = parser.Parse() 46 if !ok || failed { 47 t.Errorf("asm: %s assembly failed", goarch) 48 return 49 } 50 output := strings.Split(testOut.String(), "\n") 51 52 // Reconstruct expected output by independently "parsing" the input. 53 data, err := os.ReadFile(input) 54 if err != nil { 55 t.Error(err) 56 return 57 } 58 lineno := 0 59 seq := 0 60 hexByLine := map[string]string{} 61 lines := strings.SplitAfter(string(data), "\n") 62Diff: 63 for _, line := range lines { 64 lineno++ 65 66 // Ignore include of textflag.h. 67 if strings.HasPrefix(line, "#include ") { 68 continue 69 } 70 71 // Ignore GLOBL. 72 if strings.HasPrefix(line, "GLOBL ") { 73 continue 74 } 75 76 // The general form of a test input line is: 77 // // comment 78 // INST args [// printed form] [// hex encoding] 79 parts := strings.Split(line, "//") 80 printed := strings.TrimSpace(parts[0]) 81 if printed == "" || strings.HasSuffix(printed, ":") { // empty or label 82 continue 83 } 84 seq++ 85 86 var hexes string 87 switch len(parts) { 88 default: 89 t.Errorf("%s:%d: unable to understand comments: %s", input, lineno, line) 90 case 1: 91 // no comment 92 case 2: 93 // might be printed form or hex 94 note := strings.TrimSpace(parts[1]) 95 if isHexes(note) { 96 hexes = note 97 } else { 98 printed = note 99 } 100 case 3: 101 // printed form, then hex 102 printed = strings.TrimSpace(parts[1]) 103 hexes = strings.TrimSpace(parts[2]) 104 if !isHexes(hexes) { 105 t.Errorf("%s:%d: malformed hex instruction encoding: %s", input, lineno, line) 106 } 107 } 108 109 if hexes != "" { 110 hexByLine[fmt.Sprintf("%s:%d", input, lineno)] = hexes 111 } 112 113 // Canonicalize spacing in printed form. 114 // First field is opcode, then tab, then arguments separated by spaces. 115 // Canonicalize spaces after commas first. 116 // Comma to separate argument gets a space; comma within does not. 117 var buf []byte 118 nest := 0 119 for i := 0; i < len(printed); i++ { 120 c := printed[i] 121 switch c { 122 case '{', '[': 123 nest++ 124 case '}', ']': 125 nest-- 126 case ',': 127 buf = append(buf, ',') 128 if nest == 0 { 129 buf = append(buf, ' ') 130 } 131 for i+1 < len(printed) && (printed[i+1] == ' ' || printed[i+1] == '\t') { 132 i++ 133 } 134 continue 135 } 136 buf = append(buf, c) 137 } 138 139 f := strings.Fields(string(buf)) 140 141 // Turn relative (PC) into absolute (PC) automatically, 142 // so that most branch instructions don't need comments 143 // giving the absolute form. 144 if len(f) > 0 && strings.Contains(printed, "(PC)") { 145 index := len(f) - 1 146 suf := "(PC)" 147 for !strings.HasSuffix(f[index], suf) { 148 index-- 149 suf = "(PC)," 150 } 151 str := f[index] 152 n, err := strconv.Atoi(str[:len(str)-len(suf)]) 153 if err == nil { 154 f[index] = fmt.Sprintf("%d%s", seq+n, suf) 155 } 156 } 157 158 if len(f) == 1 { 159 printed = f[0] 160 } else { 161 printed = f[0] + "\t" + strings.Join(f[1:], " ") 162 } 163 164 want := fmt.Sprintf("%05d (%s:%d)\t%s", seq, input, lineno, printed) 165 for len(output) > 0 && (output[0] < want || output[0] != want && len(output[0]) >= 5 && output[0][:5] == want[:5]) { 166 if len(output[0]) >= 5 && output[0][:5] == want[:5] { 167 t.Errorf("mismatched output:\nhave %s\nwant %s", output[0], want) 168 output = output[1:] 169 continue Diff 170 } 171 t.Errorf("unexpected output: %q", output[0]) 172 output = output[1:] 173 } 174 if len(output) > 0 && output[0] == want { 175 output = output[1:] 176 } else { 177 t.Errorf("missing output: %q", want) 178 } 179 } 180 for len(output) > 0 { 181 if output[0] == "" { 182 // spurious blank caused by Split on "\n" 183 output = output[1:] 184 continue 185 } 186 t.Errorf("unexpected output: %q", output[0]) 187 output = output[1:] 188 } 189 190 // Checked printing. 191 // Now check machine code layout. 192 193 top := pList.Firstpc 194 var text *obj.LSym 195 ok = true 196 ctxt.DiagFunc = func(format string, args ...interface{}) { 197 t.Errorf(format, args...) 198 ok = false 199 } 200 obj.Flushplist(ctxt, pList, nil) 201 202 for p := top; p != nil; p = p.Link { 203 if p.As == obj.ATEXT { 204 text = p.From.Sym 205 } 206 hexes := hexByLine[p.Line()] 207 if hexes == "" { 208 continue 209 } 210 delete(hexByLine, p.Line()) 211 if text == nil { 212 t.Errorf("%s: instruction outside TEXT", p) 213 } 214 size := int64(len(text.P)) - p.Pc 215 if p.Link != nil { 216 size = p.Link.Pc - p.Pc 217 } else if p.Isize != 0 { 218 size = int64(p.Isize) 219 } 220 var code []byte 221 if p.Pc < int64(len(text.P)) { 222 code = text.P[p.Pc:] 223 if size < int64(len(code)) { 224 code = code[:size] 225 } 226 } 227 codeHex := fmt.Sprintf("%x", code) 228 if codeHex == "" { 229 codeHex = "empty" 230 } 231 ok := false 232 for _, hex := range strings.Split(hexes, " or ") { 233 if codeHex == hex { 234 ok = true 235 break 236 } 237 } 238 if !ok { 239 t.Errorf("%s: have encoding %s, want %s", p, codeHex, hexes) 240 } 241 } 242 243 if len(hexByLine) > 0 { 244 var missing []string 245 for key := range hexByLine { 246 missing = append(missing, key) 247 } 248 sort.Strings(missing) 249 for _, line := range missing { 250 t.Errorf("%s: did not find instruction encoding", line) 251 } 252 } 253 254} 255 256func isHexes(s string) bool { 257 if s == "" { 258 return false 259 } 260 if s == "empty" { 261 return true 262 } 263 for _, f := range strings.Split(s, " or ") { 264 if f == "" || len(f)%2 != 0 || strings.TrimLeft(f, "0123456789abcdef") != "" { 265 return false 266 } 267 } 268 return true 269} 270 271// It would be nice if the error messages always began with 272// the standard file:line: prefix, 273// but that's not where we are today. 274// It might be at the beginning but it might be in the middle of the printed instruction. 275var fileLineRE = regexp.MustCompile(`(?:^|\()(testdata[/\\][\da-z]+\.s:\d+)(?:$|\)|:)`) 276 277// Same as in test/run.go 278var ( 279 errRE = regexp.MustCompile(`// ERROR ?(.*)`) 280 errQuotesRE = regexp.MustCompile(`"([^"]*)"`) 281) 282 283func testErrors(t *testing.T, goarch, file string, flags ...string) { 284 input := filepath.Join("testdata", file+".s") 285 architecture, ctxt := setArch(goarch) 286 architecture.Init(ctxt) 287 lexer := lex.NewLexer(input) 288 parser := NewParser(ctxt, architecture, lexer) 289 pList := new(obj.Plist) 290 var ok bool 291 ctxt.Bso = bufio.NewWriter(os.Stdout) 292 ctxt.IsAsm = true 293 defer ctxt.Bso.Flush() 294 failed := false 295 var errBuf bytes.Buffer 296 parser.errorWriter = &errBuf 297 ctxt.DiagFunc = func(format string, args ...interface{}) { 298 failed = true 299 s := fmt.Sprintf(format, args...) 300 if !strings.HasSuffix(s, "\n") { 301 s += "\n" 302 } 303 errBuf.WriteString(s) 304 } 305 for _, flag := range flags { 306 switch flag { 307 case "dynlink": 308 ctxt.Flag_dynlink = true 309 default: 310 t.Errorf("unknown flag %s", flag) 311 } 312 } 313 pList.Firstpc, ok = parser.Parse() 314 obj.Flushplist(ctxt, pList, nil) 315 if ok && !failed { 316 t.Errorf("asm: %s had no errors", file) 317 } 318 319 errors := map[string]string{} 320 for _, line := range strings.Split(errBuf.String(), "\n") { 321 if line == "" || strings.HasPrefix(line, "\t") { 322 continue 323 } 324 m := fileLineRE.FindStringSubmatch(line) 325 if m == nil { 326 t.Errorf("unexpected error: %v", line) 327 continue 328 } 329 fileline := m[1] 330 if errors[fileline] != "" && errors[fileline] != line { 331 t.Errorf("multiple errors on %s:\n\t%s\n\t%s", fileline, errors[fileline], line) 332 continue 333 } 334 errors[fileline] = line 335 } 336 337 // Reconstruct expected errors by independently "parsing" the input. 338 data, err := os.ReadFile(input) 339 if err != nil { 340 t.Error(err) 341 return 342 } 343 lineno := 0 344 lines := strings.Split(string(data), "\n") 345 for _, line := range lines { 346 lineno++ 347 348 fileline := fmt.Sprintf("%s:%d", input, lineno) 349 if m := errRE.FindStringSubmatch(line); m != nil { 350 all := m[1] 351 mm := errQuotesRE.FindAllStringSubmatch(all, -1) 352 if len(mm) != 1 { 353 t.Errorf("%s: invalid errorcheck line:\n%s", fileline, line) 354 } else if err := errors[fileline]; err == "" { 355 t.Errorf("%s: missing error, want %s", fileline, all) 356 } else if !strings.Contains(err, mm[0][1]) { 357 t.Errorf("%s: wrong error for %s:\n%s", fileline, all, err) 358 } 359 } else { 360 if errors[fileline] != "" { 361 t.Errorf("unexpected error on %s: %v", fileline, errors[fileline]) 362 } 363 } 364 delete(errors, fileline) 365 } 366 var extra []string 367 for key := range errors { 368 extra = append(extra, key) 369 } 370 sort.Strings(extra) 371 for _, fileline := range extra { 372 t.Errorf("unexpected error on %s: %v", fileline, errors[fileline]) 373 } 374} 375 376func Test386EndToEnd(t *testing.T) { 377 testEndToEnd(t, "386", "386") 378} 379 380func TestARMEndToEnd(t *testing.T) { 381 defer func(old int) { buildcfg.GOARM.Version = old }(buildcfg.GOARM.Version) 382 for _, goarm := range []int{5, 6, 7} { 383 t.Logf("GOARM=%d", goarm) 384 buildcfg.GOARM.Version = goarm 385 testEndToEnd(t, "arm", "arm") 386 if goarm == 6 { 387 testEndToEnd(t, "arm", "armv6") 388 } 389 } 390} 391 392func TestGoBuildErrors(t *testing.T) { 393 testErrors(t, "amd64", "buildtagerror") 394} 395 396func TestGenericErrors(t *testing.T) { 397 testErrors(t, "amd64", "duperror") 398} 399 400func TestARMErrors(t *testing.T) { 401 testErrors(t, "arm", "armerror") 402} 403 404func TestARM64EndToEnd(t *testing.T) { 405 testEndToEnd(t, "arm64", "arm64") 406} 407 408func TestARM64Encoder(t *testing.T) { 409 testEndToEnd(t, "arm64", "arm64enc") 410} 411 412func TestARM64Errors(t *testing.T) { 413 testErrors(t, "arm64", "arm64error") 414} 415 416func TestAMD64EndToEnd(t *testing.T) { 417 testEndToEnd(t, "amd64", "amd64") 418} 419 420func Test386Encoder(t *testing.T) { 421 testEndToEnd(t, "386", "386enc") 422} 423 424func TestAMD64Encoder(t *testing.T) { 425 filenames := [...]string{ 426 "amd64enc", 427 "amd64enc_extra", 428 "avx512enc/aes_avx512f", 429 "avx512enc/gfni_avx512f", 430 "avx512enc/vpclmulqdq_avx512f", 431 "avx512enc/avx512bw", 432 "avx512enc/avx512cd", 433 "avx512enc/avx512dq", 434 "avx512enc/avx512er", 435 "avx512enc/avx512f", 436 "avx512enc/avx512pf", 437 "avx512enc/avx512_4fmaps", 438 "avx512enc/avx512_4vnniw", 439 "avx512enc/avx512_bitalg", 440 "avx512enc/avx512_ifma", 441 "avx512enc/avx512_vbmi", 442 "avx512enc/avx512_vbmi2", 443 "avx512enc/avx512_vnni", 444 "avx512enc/avx512_vpopcntdq", 445 } 446 for _, name := range filenames { 447 testEndToEnd(t, "amd64", name) 448 } 449} 450 451func TestAMD64Errors(t *testing.T) { 452 testErrors(t, "amd64", "amd64error") 453} 454 455func TestAMD64DynLinkErrors(t *testing.T) { 456 testErrors(t, "amd64", "amd64dynlinkerror", "dynlink") 457} 458 459func TestMIPSEndToEnd(t *testing.T) { 460 testEndToEnd(t, "mips", "mips") 461 testEndToEnd(t, "mips64", "mips64") 462} 463 464func TestLOONG64Encoder(t *testing.T) { 465 testEndToEnd(t, "loong64", "loong64enc1") 466 testEndToEnd(t, "loong64", "loong64enc2") 467 testEndToEnd(t, "loong64", "loong64enc3") 468 testEndToEnd(t, "loong64", "loong64") 469} 470 471func TestPPC64EndToEnd(t *testing.T) { 472 defer func(old int) { buildcfg.GOPPC64 = old }(buildcfg.GOPPC64) 473 for _, goppc64 := range []int{8, 9, 10} { 474 t.Logf("GOPPC64=power%d", goppc64) 475 buildcfg.GOPPC64 = goppc64 476 // Some pseudo-ops may assemble differently depending on GOPPC64 477 testEndToEnd(t, "ppc64", "ppc64") 478 testEndToEnd(t, "ppc64", "ppc64_p10") 479 } 480} 481 482func TestRISCVEndToEnd(t *testing.T) { 483 testEndToEnd(t, "riscv64", "riscv64") 484} 485 486func TestRISCVErrors(t *testing.T) { 487 testErrors(t, "riscv64", "riscv64error") 488} 489 490func TestS390XEndToEnd(t *testing.T) { 491 testEndToEnd(t, "s390x", "s390x") 492} 493