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 5 package asm 6 7 import ( 8 "bufio" 9 "bytes" 10 "fmt" 11 "io/ioutil" 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 "cmd/internal/objabi" 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 29 func 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) 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 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 := ioutil.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") 62 Diff: 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 // The general form of a test input line is: 72 // // comment 73 // INST args [// printed form] [// hex encoding] 74 parts := strings.Split(line, "//") 75 printed := strings.TrimSpace(parts[0]) 76 if printed == "" || strings.HasSuffix(printed, ":") { // empty or label 77 continue 78 } 79 seq++ 80 81 var hexes string 82 switch len(parts) { 83 default: 84 t.Errorf("%s:%d: unable to understand comments: %s", input, lineno, line) 85 case 1: 86 // no comment 87 case 2: 88 // might be printed form or hex 89 note := strings.TrimSpace(parts[1]) 90 if isHexes(note) { 91 hexes = note 92 } else { 93 printed = note 94 } 95 case 3: 96 // printed form, then hex 97 printed = strings.TrimSpace(parts[1]) 98 hexes = strings.TrimSpace(parts[2]) 99 if !isHexes(hexes) { 100 t.Errorf("%s:%d: malformed hex instruction encoding: %s", input, lineno, line) 101 } 102 } 103 104 if hexes != "" { 105 hexByLine[fmt.Sprintf("%s:%d", input, lineno)] = hexes 106 } 107 108 // Canonicalize spacing in printed form. 109 // First field is opcode, then tab, then arguments separated by spaces. 110 // Canonicalize spaces after commas first. 111 // Comma to separate argument gets a space; comma within does not. 112 var buf []byte 113 nest := 0 114 for i := 0; i < len(printed); i++ { 115 c := printed[i] 116 switch c { 117 case '{', '[': 118 nest++ 119 case '}', ']': 120 nest-- 121 case ',': 122 buf = append(buf, ',') 123 if nest == 0 { 124 buf = append(buf, ' ') 125 } 126 for i+1 < len(printed) && (printed[i+1] == ' ' || printed[i+1] == '\t') { 127 i++ 128 } 129 continue 130 } 131 buf = append(buf, c) 132 } 133 134 f := strings.Fields(string(buf)) 135 136 // Turn relative (PC) into absolute (PC) automatically, 137 // so that most branch instructions don't need comments 138 // giving the absolute form. 139 if len(f) > 0 && strings.HasSuffix(printed, "(PC)") { 140 last := f[len(f)-1] 141 n, err := strconv.Atoi(last[:len(last)-len("(PC)")]) 142 if err == nil { 143 f[len(f)-1] = fmt.Sprintf("%d(PC)", seq+n) 144 } 145 } 146 147 if len(f) == 1 { 148 printed = f[0] 149 } else { 150 printed = f[0] + "\t" + strings.Join(f[1:], " ") 151 } 152 153 want := fmt.Sprintf("%05d (%s:%d)\t%s", seq, input, lineno, printed) 154 for len(output) > 0 && (output[0] < want || output[0] != want && len(output[0]) >= 5 && output[0][:5] == want[:5]) { 155 if len(output[0]) >= 5 && output[0][:5] == want[:5] { 156 t.Errorf("mismatched output:\nhave %s\nwant %s", output[0], want) 157 output = output[1:] 158 continue Diff 159 } 160 t.Errorf("unexpected output: %q", output[0]) 161 output = output[1:] 162 } 163 if len(output) > 0 && output[0] == want { 164 output = output[1:] 165 } else { 166 t.Errorf("missing output: %q", want) 167 } 168 } 169 for len(output) > 0 { 170 if output[0] == "" { 171 // spurious blank caused by Split on "\n" 172 output = output[1:] 173 continue 174 } 175 t.Errorf("unexpected output: %q", output[0]) 176 output = output[1:] 177 } 178 179 // Checked printing. 180 // Now check machine code layout. 181 182 top := pList.Firstpc 183 var text *obj.LSym 184 ok = true 185 ctxt.DiagFunc = func(format string, args ...interface{}) { 186 t.Errorf(format, args...) 187 ok = false 188 } 189 obj.Flushplist(ctxt, pList, nil, "") 190 191 for p := top; p != nil; p = p.Link { 192 if p.As == obj.ATEXT { 193 text = p.From.Sym 194 } 195 hexes := hexByLine[p.Line()] 196 if hexes == "" { 197 continue 198 } 199 delete(hexByLine, p.Line()) 200 if text == nil { 201 t.Errorf("%s: instruction outside TEXT", p) 202 } 203 size := int64(len(text.P)) - p.Pc 204 if p.Link != nil { 205 size = p.Link.Pc - p.Pc 206 } else if p.Isize != 0 { 207 size = int64(p.Isize) 208 } 209 var code []byte 210 if p.Pc < int64(len(text.P)) { 211 code = text.P[p.Pc:] 212 if size < int64(len(code)) { 213 code = code[:size] 214 } 215 } 216 codeHex := fmt.Sprintf("%x", code) 217 if codeHex == "" { 218 codeHex = "empty" 219 } 220 ok := false 221 for _, hex := range strings.Split(hexes, " or ") { 222 if codeHex == hex { 223 ok = true 224 break 225 } 226 } 227 if !ok { 228 t.Errorf("%s: have encoding %s, want %s", p, codeHex, hexes) 229 } 230 } 231 232 if len(hexByLine) > 0 { 233 var missing []string 234 for key := range hexByLine { 235 missing = append(missing, key) 236 } 237 sort.Strings(missing) 238 for _, line := range missing { 239 t.Errorf("%s: did not find instruction encoding", line) 240 } 241 } 242 243 } 244 245 func isHexes(s string) bool { 246 if s == "" { 247 return false 248 } 249 if s == "empty" { 250 return true 251 } 252 for _, f := range strings.Split(s, " or ") { 253 if f == "" || len(f)%2 != 0 || strings.TrimLeft(f, "0123456789abcdef") != "" { 254 return false 255 } 256 } 257 return true 258 } 259 260 // It would be nice if the error messages began with 261 // the standard file:line: prefix, 262 // but that's not where we are today. 263 // It might be at the beginning but it might be in the middle of the printed instruction. 264 var fileLineRE = regexp.MustCompile(`(?:^|\()(testdata[/\\][0-9a-z]+\.s:[0-9]+)(?:$|\))`) 265 266 // Same as in test/run.go 267 var ( 268 errRE = regexp.MustCompile(`// ERROR ?(.*)`) 269 errQuotesRE = regexp.MustCompile(`"([^"]*)"`) 270 ) 271 272 func testErrors(t *testing.T, goarch, file string) { 273 input := filepath.Join("testdata", file+".s") 274 architecture, ctxt := setArch(goarch) 275 lexer := lex.NewLexer(input) 276 parser := NewParser(ctxt, architecture, lexer) 277 pList := new(obj.Plist) 278 var ok bool 279 testOut = new(bytes.Buffer) // The assembler writes test output to this buffer. 280 ctxt.Bso = bufio.NewWriter(os.Stdout) 281 defer ctxt.Bso.Flush() 282 failed := false 283 var errBuf bytes.Buffer 284 ctxt.DiagFunc = func(format string, args ...interface{}) { 285 failed = true 286 s := fmt.Sprintf(format, args...) 287 if !strings.HasSuffix(s, "\n") { 288 s += "\n" 289 } 290 errBuf.WriteString(s) 291 } 292 pList.Firstpc, ok = parser.Parse() 293 obj.Flushplist(ctxt, pList, nil, "") 294 if ok && !failed { 295 t.Errorf("asm: %s had no errors", goarch) 296 } 297 298 errors := map[string]string{} 299 for _, line := range strings.Split(errBuf.String(), "\n") { 300 if line == "" || strings.HasPrefix(line, "\t") { 301 continue 302 } 303 m := fileLineRE.FindStringSubmatch(line) 304 if m == nil { 305 t.Errorf("unexpected error: %v", line) 306 continue 307 } 308 fileline := m[1] 309 if errors[fileline] != "" && errors[fileline] != line { 310 t.Errorf("multiple errors on %s:\n\t%s\n\t%s", fileline, errors[fileline], line) 311 continue 312 } 313 errors[fileline] = line 314 } 315 316 // Reconstruct expected errors by independently "parsing" the input. 317 data, err := ioutil.ReadFile(input) 318 if err != nil { 319 t.Error(err) 320 return 321 } 322 lineno := 0 323 lines := strings.Split(string(data), "\n") 324 for _, line := range lines { 325 lineno++ 326 327 fileline := fmt.Sprintf("%s:%d", input, lineno) 328 if m := errRE.FindStringSubmatch(line); m != nil { 329 all := m[1] 330 mm := errQuotesRE.FindAllStringSubmatch(all, -1) 331 if len(mm) != 1 { 332 t.Errorf("%s: invalid errorcheck line:\n%s", fileline, line) 333 } else if err := errors[fileline]; err == "" { 334 t.Errorf("%s: missing error, want %s", fileline, all) 335 } else if !strings.Contains(err, mm[0][1]) { 336 t.Errorf("%s: wrong error for %s:\n%s", fileline, all, err) 337 } 338 } else { 339 if errors[fileline] != "" { 340 t.Errorf("unexpected error on %s: %v", fileline, errors[fileline]) 341 } 342 } 343 delete(errors, fileline) 344 } 345 var extra []string 346 for key := range errors { 347 extra = append(extra, key) 348 } 349 sort.Strings(extra) 350 for _, fileline := range extra { 351 t.Errorf("unexpected error on %s: %v", fileline, errors[fileline]) 352 } 353 } 354 355 func Test386EndToEnd(t *testing.T) { 356 defer func(old string) { objabi.GO386 = old }(objabi.GO386) 357 for _, go386 := range []string{"387", "sse2"} { 358 t.Logf("GO386=%v", go386) 359 objabi.GO386 = go386 360 testEndToEnd(t, "386", "386") 361 } 362 } 363 364 func TestARMEndToEnd(t *testing.T) { 365 defer func(old int) { objabi.GOARM = old }(objabi.GOARM) 366 for _, goarm := range []int{5, 6, 7} { 367 t.Logf("GOARM=%d", goarm) 368 objabi.GOARM = goarm 369 testEndToEnd(t, "arm", "arm") 370 if goarm == 6 { 371 testEndToEnd(t, "arm", "armv6") 372 } 373 } 374 } 375 376 func TestARMErrors(t *testing.T) { 377 testErrors(t, "arm", "armerror") 378 } 379 380 func TestARM64EndToEnd(t *testing.T) { 381 testEndToEnd(t, "arm64", "arm64") 382 } 383 384 func TestARM64Encoder(t *testing.T) { 385 testEndToEnd(t, "arm64", "arm64enc") 386 } 387 388 func TestARM64Errors(t *testing.T) { 389 testErrors(t, "arm64", "arm64error") 390 } 391 392 func TestAMD64EndToEnd(t *testing.T) { 393 testEndToEnd(t, "amd64", "amd64") 394 } 395 396 func TestAMD64Encoder(t *testing.T) { 397 testEndToEnd(t, "amd64", "amd64enc") 398 testEndToEnd(t, "amd64", "amd64enc_extra") 399 } 400 401 func TestAMD64Errors(t *testing.T) { 402 testErrors(t, "amd64", "amd64error") 403 } 404 405 func TestMIPSEndToEnd(t *testing.T) { 406 testEndToEnd(t, "mips", "mips") 407 testEndToEnd(t, "mips64", "mips64") 408 } 409 410 func TestPPC64EndToEnd(t *testing.T) { 411 testEndToEnd(t, "ppc64", "ppc64") 412 } 413 414 func TestS390XEndToEnd(t *testing.T) { 415 testEndToEnd(t, "s390x", "s390x") 416 } 417