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