1
2
3
4
5 package asm
6
7 import (
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
25
26
27
28 func 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)
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
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")
62 Diff:
63 for _, line := range lines {
64 lineno++
65
66
67 if strings.HasPrefix(line, "#include ") {
68 continue
69 }
70
71
72 if strings.HasPrefix(line, "GLOBL ") {
73 continue
74 }
75
76
77
78
79 parts := strings.Split(line, "//")
80 printed := strings.TrimSpace(parts[0])
81 if printed == "" || strings.HasSuffix(printed, ":") {
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
92 case 2:
93
94 note := strings.TrimSpace(parts[1])
95 if isHexes(note) {
96 hexes = note
97 } else {
98 printed = note
99 }
100 case 3:
101
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
114
115
116
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
142
143
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
183 output = output[1:]
184 continue
185 }
186 t.Errorf("unexpected output: %q", output[0])
187 output = output[1:]
188 }
189
190
191
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
256 func 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
272
273
274
275 var fileLineRE = regexp.MustCompile(`(?:^|\()(testdata[/\\][\da-z]+\.s:\d+)(?:$|\)|:)`)
276
277
278 var (
279 errRE = regexp.MustCompile(`// ERROR ?(.*)`)
280 errQuotesRE = regexp.MustCompile(`"([^"]*)"`)
281 )
282
283 func 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
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
376 func Test386EndToEnd(t *testing.T) {
377 testEndToEnd(t, "386", "386")
378 }
379
380 func 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
392 func TestGoBuildErrors(t *testing.T) {
393 testErrors(t, "amd64", "buildtagerror")
394 }
395
396 func TestGenericErrors(t *testing.T) {
397 testErrors(t, "amd64", "duperror")
398 }
399
400 func TestARMErrors(t *testing.T) {
401 testErrors(t, "arm", "armerror")
402 }
403
404 func TestARM64EndToEnd(t *testing.T) {
405 testEndToEnd(t, "arm64", "arm64")
406 }
407
408 func TestARM64Encoder(t *testing.T) {
409 testEndToEnd(t, "arm64", "arm64enc")
410 }
411
412 func TestARM64Errors(t *testing.T) {
413 testErrors(t, "arm64", "arm64error")
414 }
415
416 func TestAMD64EndToEnd(t *testing.T) {
417 testEndToEnd(t, "amd64", "amd64")
418 }
419
420 func Test386Encoder(t *testing.T) {
421 testEndToEnd(t, "386", "386enc")
422 }
423
424 func 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
451 func TestAMD64Errors(t *testing.T) {
452 testErrors(t, "amd64", "amd64error")
453 }
454
455 func TestAMD64DynLinkErrors(t *testing.T) {
456 testErrors(t, "amd64", "amd64dynlinkerror", "dynlink")
457 }
458
459 func TestMIPSEndToEnd(t *testing.T) {
460 testEndToEnd(t, "mips", "mips")
461 testEndToEnd(t, "mips64", "mips64")
462 }
463
464 func 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
471 func 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
477 testEndToEnd(t, "ppc64", "ppc64")
478 testEndToEnd(t, "ppc64", "ppc64_p10")
479 }
480 }
481
482 func TestRISCVEndToEnd(t *testing.T) {
483 testEndToEnd(t, "riscv64", "riscv64")
484 }
485
486 func TestRISCVErrors(t *testing.T) {
487 testErrors(t, "riscv64", "riscv64error")
488 }
489
490 func TestS390XEndToEnd(t *testing.T) {
491 testEndToEnd(t, "s390x", "s390x")
492 }
493
View as plain text