Home | History | Annotate | Download | only in 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 	"io/ioutil"
     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 	"cmd/internal/objabi"
     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)
     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 	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 := ioutil.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 		// The general form of a test input line is:
     72 		//	// comment
     73 		//	INST args [// printed form] [// hex encoding]
     74 		parts := strings.Split(line, "//")
     75 		printed := strings.TrimSpace(parts[0])
     76 		if printed == "" || strings.HasSuffix(printed, ":") { // empty or label
     77 			continue
     78 		}
     79 		seq++
     80 
     81 		var hexes string
     82 		switch len(parts) {
     83 		default:
     84 			t.Errorf("%s:%d: unable to understand comments: %s", input, lineno, line)
     85 		case 1:
     86 			// no comment
     87 		case 2:
     88 			// might be printed form or hex
     89 			note := strings.TrimSpace(parts[1])
     90 			if isHexes(note) {
     91 				hexes = note
     92 			} else {
     93 				printed = note
     94 			}
     95 		case 3:
     96 			// printed form, then hex
     97 			printed = strings.TrimSpace(parts[1])
     98 			hexes = strings.TrimSpace(parts[2])
     99 			if !isHexes(hexes) {
    100 				t.Errorf("%s:%d: malformed hex instruction encoding: %s", input, lineno, line)
    101 			}
    102 		}
    103 
    104 		if hexes != "" {
    105 			hexByLine[fmt.Sprintf("%s:%d", input, lineno)] = hexes
    106 		}
    107 
    108 		// Canonicalize spacing in printed form.
    109 		// First field is opcode, then tab, then arguments separated by spaces.
    110 		// Canonicalize spaces after commas first.
    111 		// Comma to separate argument gets a space; comma within does not.
    112 		var buf []byte
    113 		nest := 0
    114 		for i := 0; i < len(printed); i++ {
    115 			c := printed[i]
    116 			switch c {
    117 			case '{', '[':
    118 				nest++
    119 			case '}', ']':
    120 				nest--
    121 			case ',':
    122 				buf = append(buf, ',')
    123 				if nest == 0 {
    124 					buf = append(buf, ' ')
    125 				}
    126 				for i+1 < len(printed) && (printed[i+1] == ' ' || printed[i+1] == '\t') {
    127 					i++
    128 				}
    129 				continue
    130 			}
    131 			buf = append(buf, c)
    132 		}
    133 
    134 		f := strings.Fields(string(buf))
    135 
    136 		// Turn relative (PC) into absolute (PC) automatically,
    137 		// so that most branch instructions don't need comments
    138 		// giving the absolute form.
    139 		if len(f) > 0 && strings.HasSuffix(printed, "(PC)") {
    140 			last := f[len(f)-1]
    141 			n, err := strconv.Atoi(last[:len(last)-len("(PC)")])
    142 			if err == nil {
    143 				f[len(f)-1] = fmt.Sprintf("%d(PC)", seq+n)
    144 			}
    145 		}
    146 
    147 		if len(f) == 1 {
    148 			printed = f[0]
    149 		} else {
    150 			printed = f[0] + "\t" + strings.Join(f[1:], " ")
    151 		}
    152 
    153 		want := fmt.Sprintf("%05d (%s:%d)\t%s", seq, input, lineno, printed)
    154 		for len(output) > 0 && (output[0] < want || output[0] != want && len(output[0]) >= 5 && output[0][:5] == want[:5]) {
    155 			if len(output[0]) >= 5 && output[0][:5] == want[:5] {
    156 				t.Errorf("mismatched output:\nhave %s\nwant %s", output[0], want)
    157 				output = output[1:]
    158 				continue Diff
    159 			}
    160 			t.Errorf("unexpected output: %q", output[0])
    161 			output = output[1:]
    162 		}
    163 		if len(output) > 0 && output[0] == want {
    164 			output = output[1:]
    165 		} else {
    166 			t.Errorf("missing output: %q", want)
    167 		}
    168 	}
    169 	for len(output) > 0 {
    170 		if output[0] == "" {
    171 			// spurious blank caused by Split on "\n"
    172 			output = output[1:]
    173 			continue
    174 		}
    175 		t.Errorf("unexpected output: %q", output[0])
    176 		output = output[1:]
    177 	}
    178 
    179 	// Checked printing.
    180 	// Now check machine code layout.
    181 
    182 	top := pList.Firstpc
    183 	var text *obj.LSym
    184 	ok = true
    185 	ctxt.DiagFunc = func(format string, args ...interface{}) {
    186 		t.Errorf(format, args...)
    187 		ok = false
    188 	}
    189 	obj.Flushplist(ctxt, pList, nil, "")
    190 
    191 	for p := top; p != nil; p = p.Link {
    192 		if p.As == obj.ATEXT {
    193 			text = p.From.Sym
    194 		}
    195 		hexes := hexByLine[p.Line()]
    196 		if hexes == "" {
    197 			continue
    198 		}
    199 		delete(hexByLine, p.Line())
    200 		if text == nil {
    201 			t.Errorf("%s: instruction outside TEXT", p)
    202 		}
    203 		size := int64(len(text.P)) - p.Pc
    204 		if p.Link != nil {
    205 			size = p.Link.Pc - p.Pc
    206 		} else if p.Isize != 0 {
    207 			size = int64(p.Isize)
    208 		}
    209 		var code []byte
    210 		if p.Pc < int64(len(text.P)) {
    211 			code = text.P[p.Pc:]
    212 			if size < int64(len(code)) {
    213 				code = code[:size]
    214 			}
    215 		}
    216 		codeHex := fmt.Sprintf("%x", code)
    217 		if codeHex == "" {
    218 			codeHex = "empty"
    219 		}
    220 		ok := false
    221 		for _, hex := range strings.Split(hexes, " or ") {
    222 			if codeHex == hex {
    223 				ok = true
    224 				break
    225 			}
    226 		}
    227 		if !ok {
    228 			t.Errorf("%s: have encoding %s, want %s", p, codeHex, hexes)
    229 		}
    230 	}
    231 
    232 	if len(hexByLine) > 0 {
    233 		var missing []string
    234 		for key := range hexByLine {
    235 			missing = append(missing, key)
    236 		}
    237 		sort.Strings(missing)
    238 		for _, line := range missing {
    239 			t.Errorf("%s: did not find instruction encoding", line)
    240 		}
    241 	}
    242 
    243 }
    244 
    245 func isHexes(s string) bool {
    246 	if s == "" {
    247 		return false
    248 	}
    249 	if s == "empty" {
    250 		return true
    251 	}
    252 	for _, f := range strings.Split(s, " or ") {
    253 		if f == "" || len(f)%2 != 0 || strings.TrimLeft(f, "0123456789abcdef") != "" {
    254 			return false
    255 		}
    256 	}
    257 	return true
    258 }
    259 
    260 // It would be nice if the error messages began with
    261 // the standard file:line: prefix,
    262 // but that's not where we are today.
    263 // It might be at the beginning but it might be in the middle of the printed instruction.
    264 var fileLineRE = regexp.MustCompile(`(?:^|\()(testdata[/\\][0-9a-z]+\.s:[0-9]+)(?:$|\))`)
    265 
    266 // Same as in test/run.go
    267 var (
    268 	errRE       = regexp.MustCompile(`// ERROR ?(.*)`)
    269 	errQuotesRE = regexp.MustCompile(`"([^"]*)"`)
    270 )
    271 
    272 func testErrors(t *testing.T, goarch, file string) {
    273 	input := filepath.Join("testdata", file+".s")
    274 	architecture, ctxt := setArch(goarch)
    275 	lexer := lex.NewLexer(input)
    276 	parser := NewParser(ctxt, architecture, lexer)
    277 	pList := new(obj.Plist)
    278 	var ok bool
    279 	testOut = new(bytes.Buffer) // The assembler writes test output to this buffer.
    280 	ctxt.Bso = bufio.NewWriter(os.Stdout)
    281 	defer ctxt.Bso.Flush()
    282 	failed := false
    283 	var errBuf bytes.Buffer
    284 	ctxt.DiagFunc = func(format string, args ...interface{}) {
    285 		failed = true
    286 		s := fmt.Sprintf(format, args...)
    287 		if !strings.HasSuffix(s, "\n") {
    288 			s += "\n"
    289 		}
    290 		errBuf.WriteString(s)
    291 	}
    292 	pList.Firstpc, ok = parser.Parse()
    293 	obj.Flushplist(ctxt, pList, nil, "")
    294 	if ok && !failed {
    295 		t.Errorf("asm: %s had no errors", goarch)
    296 	}
    297 
    298 	errors := map[string]string{}
    299 	for _, line := range strings.Split(errBuf.String(), "\n") {
    300 		if line == "" || strings.HasPrefix(line, "\t") {
    301 			continue
    302 		}
    303 		m := fileLineRE.FindStringSubmatch(line)
    304 		if m == nil {
    305 			t.Errorf("unexpected error: %v", line)
    306 			continue
    307 		}
    308 		fileline := m[1]
    309 		if errors[fileline] != "" && errors[fileline] != line {
    310 			t.Errorf("multiple errors on %s:\n\t%s\n\t%s", fileline, errors[fileline], line)
    311 			continue
    312 		}
    313 		errors[fileline] = line
    314 	}
    315 
    316 	// Reconstruct expected errors by independently "parsing" the input.
    317 	data, err := ioutil.ReadFile(input)
    318 	if err != nil {
    319 		t.Error(err)
    320 		return
    321 	}
    322 	lineno := 0
    323 	lines := strings.Split(string(data), "\n")
    324 	for _, line := range lines {
    325 		lineno++
    326 
    327 		fileline := fmt.Sprintf("%s:%d", input, lineno)
    328 		if m := errRE.FindStringSubmatch(line); m != nil {
    329 			all := m[1]
    330 			mm := errQuotesRE.FindAllStringSubmatch(all, -1)
    331 			if len(mm) != 1 {
    332 				t.Errorf("%s: invalid errorcheck line:\n%s", fileline, line)
    333 			} else if err := errors[fileline]; err == "" {
    334 				t.Errorf("%s: missing error, want %s", fileline, all)
    335 			} else if !strings.Contains(err, mm[0][1]) {
    336 				t.Errorf("%s: wrong error for %s:\n%s", fileline, all, err)
    337 			}
    338 		} else {
    339 			if errors[fileline] != "" {
    340 				t.Errorf("unexpected error on %s: %v", fileline, errors[fileline])
    341 			}
    342 		}
    343 		delete(errors, fileline)
    344 	}
    345 	var extra []string
    346 	for key := range errors {
    347 		extra = append(extra, key)
    348 	}
    349 	sort.Strings(extra)
    350 	for _, fileline := range extra {
    351 		t.Errorf("unexpected error on %s: %v", fileline, errors[fileline])
    352 	}
    353 }
    354 
    355 func Test386EndToEnd(t *testing.T) {
    356 	defer func(old string) { objabi.GO386 = old }(objabi.GO386)
    357 	for _, go386 := range []string{"387", "sse2"} {
    358 		t.Logf("GO386=%v", go386)
    359 		objabi.GO386 = go386
    360 		testEndToEnd(t, "386", "386")
    361 	}
    362 }
    363 
    364 func TestARMEndToEnd(t *testing.T) {
    365 	defer func(old int) { objabi.GOARM = old }(objabi.GOARM)
    366 	for _, goarm := range []int{5, 6, 7} {
    367 		t.Logf("GOARM=%d", goarm)
    368 		objabi.GOARM = goarm
    369 		testEndToEnd(t, "arm", "arm")
    370 		if goarm == 6 {
    371 			testEndToEnd(t, "arm", "armv6")
    372 		}
    373 	}
    374 }
    375 
    376 func TestARMErrors(t *testing.T) {
    377 	testErrors(t, "arm", "armerror")
    378 }
    379 
    380 func TestARM64EndToEnd(t *testing.T) {
    381 	testEndToEnd(t, "arm64", "arm64")
    382 }
    383 
    384 func TestARM64Encoder(t *testing.T) {
    385 	testEndToEnd(t, "arm64", "arm64enc")
    386 }
    387 
    388 func TestARM64Errors(t *testing.T) {
    389 	testErrors(t, "arm64", "arm64error")
    390 }
    391 
    392 func TestAMD64EndToEnd(t *testing.T) {
    393 	testEndToEnd(t, "amd64", "amd64")
    394 }
    395 
    396 func TestAMD64Encoder(t *testing.T) {
    397 	testEndToEnd(t, "amd64", "amd64enc")
    398 	testEndToEnd(t, "amd64", "amd64enc_extra")
    399 }
    400 
    401 func TestAMD64Errors(t *testing.T) {
    402 	testErrors(t, "amd64", "amd64error")
    403 }
    404 
    405 func TestMIPSEndToEnd(t *testing.T) {
    406 	testEndToEnd(t, "mips", "mips")
    407 	testEndToEnd(t, "mips64", "mips64")
    408 }
    409 
    410 func TestPPC64EndToEnd(t *testing.T) {
    411 	testEndToEnd(t, "ppc64", "ppc64")
    412 }
    413 
    414 func TestS390XEndToEnd(t *testing.T) {
    415 	testEndToEnd(t, "s390x", "s390x")
    416 }
    417