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

     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  	"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  
    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) // 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")
    62  Diff:
    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  
   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  // 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.
   275  var fileLineRE = regexp.MustCompile(`(?:^|\()(testdata[/\\][\da-z]+\.s:\d+)(?:$|\)|:)`)
   276  
   277  // Same as in test/run.go
   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  	// 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  
   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  		// Some pseudo-ops may assemble differently depending on GOPPC64
   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