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