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