diff options
Diffstat (limited to 'src/cmd/asm/internal/asm/endtoend_test.go')
-rw-r--r-- | src/cmd/asm/internal/asm/endtoend_test.go | 471 |
1 files changed, 471 insertions, 0 deletions
diff --git a/src/cmd/asm/internal/asm/endtoend_test.go b/src/cmd/asm/internal/asm/endtoend_test.go new file mode 100644 index 0000000..33a4465 --- /dev/null +++ b/src/cmd/asm/internal/asm/endtoend_test.go @@ -0,0 +1,471 @@ +// Copyright 2015 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package asm + +import ( + "bufio" + "bytes" + "fmt" + "internal/buildcfg" + "io/ioutil" + "os" + "path/filepath" + "regexp" + "sort" + "strconv" + "strings" + "testing" + + "cmd/asm/internal/lex" + "cmd/internal/obj" +) + +// An end-to-end test for the assembler: Do we print what we parse? +// Output is generated by, in effect, turning on -S and comparing the +// result against a golden file. + +func testEndToEnd(t *testing.T, goarch, file string) { + input := filepath.Join("testdata", file+".s") + architecture, ctxt := setArch(goarch) + architecture.Init(ctxt) + lexer := lex.NewLexer(input) + parser := NewParser(ctxt, architecture, lexer, false) + pList := new(obj.Plist) + var ok bool + testOut = new(bytes.Buffer) // The assembler writes test output to this buffer. + ctxt.Bso = bufio.NewWriter(os.Stdout) + ctxt.IsAsm = true + defer ctxt.Bso.Flush() + failed := false + ctxt.DiagFunc = func(format string, args ...interface{}) { + failed = true + t.Errorf(format, args...) + } + pList.Firstpc, ok = parser.Parse() + if !ok || failed { + t.Errorf("asm: %s assembly failed", goarch) + return + } + output := strings.Split(testOut.String(), "\n") + + // Reconstruct expected output by independently "parsing" the input. + data, err := ioutil.ReadFile(input) + if err != nil { + t.Error(err) + return + } + lineno := 0 + seq := 0 + hexByLine := map[string]string{} + lines := strings.SplitAfter(string(data), "\n") +Diff: + for _, line := range lines { + lineno++ + + // Ignore include of textflag.h. + if strings.HasPrefix(line, "#include ") { + continue + } + + // The general form of a test input line is: + // // comment + // INST args [// printed form] [// hex encoding] + parts := strings.Split(line, "//") + printed := strings.TrimSpace(parts[0]) + if printed == "" || strings.HasSuffix(printed, ":") { // empty or label + continue + } + seq++ + + var hexes string + switch len(parts) { + default: + t.Errorf("%s:%d: unable to understand comments: %s", input, lineno, line) + case 1: + // no comment + case 2: + // might be printed form or hex + note := strings.TrimSpace(parts[1]) + if isHexes(note) { + hexes = note + } else { + printed = note + } + case 3: + // printed form, then hex + printed = strings.TrimSpace(parts[1]) + hexes = strings.TrimSpace(parts[2]) + if !isHexes(hexes) { + t.Errorf("%s:%d: malformed hex instruction encoding: %s", input, lineno, line) + } + } + + if hexes != "" { + hexByLine[fmt.Sprintf("%s:%d", input, lineno)] = hexes + } + + // Canonicalize spacing in printed form. + // First field is opcode, then tab, then arguments separated by spaces. + // Canonicalize spaces after commas first. + // Comma to separate argument gets a space; comma within does not. + var buf []byte + nest := 0 + for i := 0; i < len(printed); i++ { + c := printed[i] + switch c { + case '{', '[': + nest++ + case '}', ']': + nest-- + case ',': + buf = append(buf, ',') + if nest == 0 { + buf = append(buf, ' ') + } + for i+1 < len(printed) && (printed[i+1] == ' ' || printed[i+1] == '\t') { + i++ + } + continue + } + buf = append(buf, c) + } + + f := strings.Fields(string(buf)) + + // Turn relative (PC) into absolute (PC) automatically, + // so that most branch instructions don't need comments + // giving the absolute form. + if len(f) > 0 && strings.HasSuffix(printed, "(PC)") { + last := f[len(f)-1] + n, err := strconv.Atoi(last[:len(last)-len("(PC)")]) + if err == nil { + f[len(f)-1] = fmt.Sprintf("%d(PC)", seq+n) + } + } + + if len(f) == 1 { + printed = f[0] + } else { + printed = f[0] + "\t" + strings.Join(f[1:], " ") + } + + want := fmt.Sprintf("%05d (%s:%d)\t%s", seq, input, lineno, printed) + for len(output) > 0 && (output[0] < want || output[0] != want && len(output[0]) >= 5 && output[0][:5] == want[:5]) { + if len(output[0]) >= 5 && output[0][:5] == want[:5] { + t.Errorf("mismatched output:\nhave %s\nwant %s", output[0], want) + output = output[1:] + continue Diff + } + t.Errorf("unexpected output: %q", output[0]) + output = output[1:] + } + if len(output) > 0 && output[0] == want { + output = output[1:] + } else { + t.Errorf("missing output: %q", want) + } + } + for len(output) > 0 { + if output[0] == "" { + // spurious blank caused by Split on "\n" + output = output[1:] + continue + } + t.Errorf("unexpected output: %q", output[0]) + output = output[1:] + } + + // Checked printing. + // Now check machine code layout. + + top := pList.Firstpc + var text *obj.LSym + ok = true + ctxt.DiagFunc = func(format string, args ...interface{}) { + t.Errorf(format, args...) + ok = false + } + obj.Flushplist(ctxt, pList, nil, "") + + for p := top; p != nil; p = p.Link { + if p.As == obj.ATEXT { + text = p.From.Sym + } + hexes := hexByLine[p.Line()] + if hexes == "" { + continue + } + delete(hexByLine, p.Line()) + if text == nil { + t.Errorf("%s: instruction outside TEXT", p) + } + size := int64(len(text.P)) - p.Pc + if p.Link != nil { + size = p.Link.Pc - p.Pc + } else if p.Isize != 0 { + size = int64(p.Isize) + } + var code []byte + if p.Pc < int64(len(text.P)) { + code = text.P[p.Pc:] + if size < int64(len(code)) { + code = code[:size] + } + } + codeHex := fmt.Sprintf("%x", code) + if codeHex == "" { + codeHex = "empty" + } + ok := false + for _, hex := range strings.Split(hexes, " or ") { + if codeHex == hex { + ok = true + break + } + } + if !ok { + t.Errorf("%s: have encoding %s, want %s", p, codeHex, hexes) + } + } + + if len(hexByLine) > 0 { + var missing []string + for key := range hexByLine { + missing = append(missing, key) + } + sort.Strings(missing) + for _, line := range missing { + t.Errorf("%s: did not find instruction encoding", line) + } + } + +} + +func isHexes(s string) bool { + if s == "" { + return false + } + if s == "empty" { + return true + } + for _, f := range strings.Split(s, " or ") { + if f == "" || len(f)%2 != 0 || strings.TrimLeft(f, "0123456789abcdef") != "" { + return false + } + } + return true +} + +// It would be nice if the error messages always began with +// the standard file:line: prefix, +// but that's not where we are today. +// It might be at the beginning but it might be in the middle of the printed instruction. +var fileLineRE = regexp.MustCompile(`(?:^|\()(testdata[/\\][0-9a-z]+\.s:[0-9]+)(?:$|\)|:)`) + +// Same as in test/run.go +var ( + errRE = regexp.MustCompile(`// ERROR ?(.*)`) + errQuotesRE = regexp.MustCompile(`"([^"]*)"`) +) + +func testErrors(t *testing.T, goarch, file string, flags ...string) { + input := filepath.Join("testdata", file+".s") + architecture, ctxt := setArch(goarch) + lexer := lex.NewLexer(input) + parser := NewParser(ctxt, architecture, lexer, false) + pList := new(obj.Plist) + var ok bool + testOut = new(bytes.Buffer) // The assembler writes test output to this buffer. + ctxt.Bso = bufio.NewWriter(os.Stdout) + ctxt.IsAsm = true + defer ctxt.Bso.Flush() + failed := false + var errBuf bytes.Buffer + parser.errorWriter = &errBuf + ctxt.DiagFunc = func(format string, args ...interface{}) { + failed = true + s := fmt.Sprintf(format, args...) + if !strings.HasSuffix(s, "\n") { + s += "\n" + } + errBuf.WriteString(s) + } + for _, flag := range flags { + switch flag { + case "dynlink": + ctxt.Flag_dynlink = true + default: + t.Errorf("unknown flag %s", flag) + } + } + pList.Firstpc, ok = parser.Parse() + obj.Flushplist(ctxt, pList, nil, "") + if ok && !failed { + t.Errorf("asm: %s had no errors", file) + } + + errors := map[string]string{} + for _, line := range strings.Split(errBuf.String(), "\n") { + if line == "" || strings.HasPrefix(line, "\t") { + continue + } + m := fileLineRE.FindStringSubmatch(line) + if m == nil { + t.Errorf("unexpected error: %v", line) + continue + } + fileline := m[1] + if errors[fileline] != "" && errors[fileline] != line { + t.Errorf("multiple errors on %s:\n\t%s\n\t%s", fileline, errors[fileline], line) + continue + } + errors[fileline] = line + } + + // Reconstruct expected errors by independently "parsing" the input. + data, err := ioutil.ReadFile(input) + if err != nil { + t.Error(err) + return + } + lineno := 0 + lines := strings.Split(string(data), "\n") + for _, line := range lines { + lineno++ + + fileline := fmt.Sprintf("%s:%d", input, lineno) + if m := errRE.FindStringSubmatch(line); m != nil { + all := m[1] + mm := errQuotesRE.FindAllStringSubmatch(all, -1) + if len(mm) != 1 { + t.Errorf("%s: invalid errorcheck line:\n%s", fileline, line) + } else if err := errors[fileline]; err == "" { + t.Errorf("%s: missing error, want %s", fileline, all) + } else if !strings.Contains(err, mm[0][1]) { + t.Errorf("%s: wrong error for %s:\n%s", fileline, all, err) + } + } else { + if errors[fileline] != "" { + t.Errorf("unexpected error on %s: %v", fileline, errors[fileline]) + } + } + delete(errors, fileline) + } + var extra []string + for key := range errors { + extra = append(extra, key) + } + sort.Strings(extra) + for _, fileline := range extra { + t.Errorf("unexpected error on %s: %v", fileline, errors[fileline]) + } +} + +func Test386EndToEnd(t *testing.T) { + testEndToEnd(t, "386", "386") +} + +func TestARMEndToEnd(t *testing.T) { + defer func(old int) { buildcfg.GOARM = old }(buildcfg.GOARM) + for _, goarm := range []int{5, 6, 7} { + t.Logf("GOARM=%d", goarm) + buildcfg.GOARM = goarm + testEndToEnd(t, "arm", "arm") + if goarm == 6 { + testEndToEnd(t, "arm", "armv6") + } + } +} + +func TestGoBuildErrors(t *testing.T) { + testErrors(t, "amd64", "buildtagerror") +} + +func TestARMErrors(t *testing.T) { + testErrors(t, "arm", "armerror") +} + +func TestARM64EndToEnd(t *testing.T) { + testEndToEnd(t, "arm64", "arm64") +} + +func TestARM64Encoder(t *testing.T) { + testEndToEnd(t, "arm64", "arm64enc") +} + +func TestARM64Errors(t *testing.T) { + testErrors(t, "arm64", "arm64error") +} + +func TestAMD64EndToEnd(t *testing.T) { + testEndToEnd(t, "amd64", "amd64") +} + +func Test386Encoder(t *testing.T) { + testEndToEnd(t, "386", "386enc") +} + +func TestAMD64Encoder(t *testing.T) { + filenames := [...]string{ + "amd64enc", + "amd64enc_extra", + "avx512enc/aes_avx512f", + "avx512enc/gfni_avx512f", + "avx512enc/vpclmulqdq_avx512f", + "avx512enc/avx512bw", + "avx512enc/avx512cd", + "avx512enc/avx512dq", + "avx512enc/avx512er", + "avx512enc/avx512f", + "avx512enc/avx512pf", + "avx512enc/avx512_4fmaps", + "avx512enc/avx512_4vnniw", + "avx512enc/avx512_bitalg", + "avx512enc/avx512_ifma", + "avx512enc/avx512_vbmi", + "avx512enc/avx512_vbmi2", + "avx512enc/avx512_vnni", + "avx512enc/avx512_vpopcntdq", + } + for _, name := range filenames { + testEndToEnd(t, "amd64", name) + } +} + +func TestAMD64Errors(t *testing.T) { + testErrors(t, "amd64", "amd64error") +} + +func TestAMD64DynLinkErrors(t *testing.T) { + testErrors(t, "amd64", "amd64dynlinkerror", "dynlink") +} + +func TestMIPSEndToEnd(t *testing.T) { + testEndToEnd(t, "mips", "mips") + testEndToEnd(t, "mips64", "mips64") +} + +func TestLOONG64Encoder(t *testing.T) { + testEndToEnd(t, "loong64", "loong64enc1") + testEndToEnd(t, "loong64", "loong64enc2") + testEndToEnd(t, "loong64", "loong64enc3") + testEndToEnd(t, "loong64", "loong64") +} + +func TestPPC64EndToEnd(t *testing.T) { + testEndToEnd(t, "ppc64", "ppc64") +} + +func TestRISCVEndToEnd(t *testing.T) { + testEndToEnd(t, "riscv64", "riscv64") +} + +func TestRISCVErrors(t *testing.T) { + testErrors(t, "riscv64", "riscv64error") +} + +func TestS390XEndToEnd(t *testing.T) { + testEndToEnd(t, "s390x", "s390x") +} |