Black Lives Matter. Support the Equal Justice Initiative.

Source file src/cmd/asm/internal/asm/endtoend_test.go

Documentation: cmd/asm/internal/asm

     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  	"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  
    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, 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")
    63  Diff:
    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  
   246  func 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.
   265  var fileLineRE = regexp.MustCompile(`(?:^|\()(testdata[/\\][0-9a-z]+\.s:[0-9]+)(?:$|\)|:)`)
   266  
   267  // Same as in test/run.go
   268  var (
   269  	errRE       = regexp.MustCompile(`// ERROR ?(.*)`)
   270  	errQuotesRE = regexp.MustCompile(`"([^"]*)"`)
   271  )
   272  
   273  func 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  
   366  func Test386EndToEnd(t *testing.T) {
   367  	testEndToEnd(t, "386", "386")
   368  }
   369  
   370  func 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  
   382  func TestGoBuildErrors(t *testing.T) {
   383  	testErrors(t, "amd64", "buildtagerror")
   384  }
   385  
   386  func TestARMErrors(t *testing.T) {
   387  	testErrors(t, "arm", "armerror")
   388  }
   389  
   390  func TestARM64EndToEnd(t *testing.T) {
   391  	testEndToEnd(t, "arm64", "arm64")
   392  }
   393  
   394  func TestARM64Encoder(t *testing.T) {
   395  	testEndToEnd(t, "arm64", "arm64enc")
   396  }
   397  
   398  func TestARM64Errors(t *testing.T) {
   399  	testErrors(t, "arm64", "arm64error")
   400  }
   401  
   402  func TestAMD64EndToEnd(t *testing.T) {
   403  	testEndToEnd(t, "amd64", "amd64")
   404  }
   405  
   406  func Test386Encoder(t *testing.T) {
   407  	testEndToEnd(t, "386", "386enc")
   408  }
   409  
   410  func 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  
   437  func TestAMD64Errors(t *testing.T) {
   438  	testErrors(t, "amd64", "amd64error")
   439  }
   440  
   441  func TestAMD64DynLinkErrors(t *testing.T) {
   442  	testErrors(t, "amd64", "amd64dynlinkerror", "dynlink")
   443  }
   444  
   445  func TestMIPSEndToEnd(t *testing.T) {
   446  	testEndToEnd(t, "mips", "mips")
   447  	testEndToEnd(t, "mips64", "mips64")
   448  }
   449  
   450  func TestPPC64EndToEnd(t *testing.T) {
   451  	testEndToEnd(t, "ppc64", "ppc64")
   452  }
   453  
   454  func TestRISCVEndToEnd(t *testing.T) {
   455  	testEndToEnd(t, "riscv64", "riscv64")
   456  }
   457  
   458  func TestRISCVErrors(t *testing.T) {
   459  	testErrors(t, "riscv64", "riscv64error")
   460  }
   461  
   462  func TestS390XEndToEnd(t *testing.T) {
   463  	testEndToEnd(t, "s390x", "s390x")
   464  }
   465  

View as plain text