...

Source file src/golang.org/x/arch/ppc64/ppc64asm/ext_test.go

Documentation: golang.org/x/arch/ppc64/ppc64asm

     1  // Copyright 2014 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  // Support for testing against external disassembler program.
     6  // Copied and simplified from rsc.io/arm/armasm/ext_test.go.
     7  
     8  package ppc64asm
     9  
    10  import (
    11  	"bufio"
    12  	"bytes"
    13  	"encoding/binary"
    14  	"encoding/hex"
    15  	"flag"
    16  	"fmt"
    17  	"io"
    18  	"io/ioutil"
    19  	"log"
    20  	"math/rand"
    21  	"os"
    22  	"os/exec"
    23  	"regexp"
    24  	"runtime"
    25  	"strings"
    26  	"testing"
    27  	"time"
    28  )
    29  
    30  var (
    31  	printTests = flag.Bool("printtests", false, "print test cases that exercise new code paths")
    32  	dumpTest   = flag.Bool("dump", false, "dump all encodings")
    33  	mismatch   = flag.Bool("mismatch", false, "log allowed mismatches")
    34  	longTest   = flag.Bool("long", false, "long test")
    35  	keep       = flag.Bool("keep", false, "keep object files around")
    36  	debug      = false
    37  )
    38  
    39  // An ExtInst represents a single decoded instruction parsed
    40  // from an external disassembler's output.
    41  type ExtInst struct {
    42  	addr uint32
    43  	enc  [8]byte
    44  	nenc int
    45  	text string
    46  }
    47  
    48  func (r ExtInst) String() string {
    49  	return fmt.Sprintf("%#x: % x: %s", r.addr, r.enc, r.text)
    50  }
    51  
    52  // An ExtDis is a connection between an external disassembler and a test.
    53  type ExtDis struct {
    54  	Dec      chan ExtInst
    55  	File     *os.File
    56  	Size     int
    57  	KeepFile bool
    58  	Cmd      *exec.Cmd
    59  }
    60  
    61  // Run runs the given command - the external disassembler - and returns
    62  // a buffered reader of its standard output.
    63  func (ext *ExtDis) Run(cmd ...string) (*bufio.Reader, error) {
    64  	if *keep {
    65  		log.Printf("%s\n", strings.Join(cmd, " "))
    66  	}
    67  	ext.Cmd = exec.Command(cmd[0], cmd[1:]...)
    68  	out, err := ext.Cmd.StdoutPipe()
    69  	if err != nil {
    70  		return nil, fmt.Errorf("stdoutpipe: %v", err)
    71  	}
    72  	if err := ext.Cmd.Start(); err != nil {
    73  		return nil, fmt.Errorf("exec: %v", err)
    74  	}
    75  
    76  	b := bufio.NewReaderSize(out, 1<<20)
    77  	return b, nil
    78  }
    79  
    80  // Wait waits for the command started with Run to exit.
    81  func (ext *ExtDis) Wait() error {
    82  	return ext.Cmd.Wait()
    83  }
    84  
    85  // testExtDis tests a set of byte sequences against an external disassembler.
    86  // The disassembler is expected to produce the given syntax and be run
    87  // in the given architecture mode (16, 32, or 64-bit).
    88  // The extdis function must start the external disassembler
    89  // and then parse its output, sending the parsed instructions on ext.Dec.
    90  // The generate function calls its argument f once for each byte sequence
    91  // to be tested. The generate function itself will be called twice, and it must
    92  // make the same sequence of calls to f each time.
    93  // When a disassembly does not match the internal decoding,
    94  // allowedMismatch determines whether this mismatch should be
    95  // allowed, or else considered an error.
    96  func testExtDis(
    97  	t *testing.T,
    98  	syntax string,
    99  	extdis func(ext *ExtDis) error,
   100  	generate func(f func([]byte)),
   101  	allowedMismatch func(text string, size int, inst *Inst, dec ExtInst) bool,
   102  ) {
   103  	start := time.Now()
   104  	ext := &ExtDis{
   105  		Dec: make(chan ExtInst),
   106  	}
   107  	errc := make(chan error)
   108  
   109  	// First pass: write instructions to input file for external disassembler.
   110  	file, f, size, err := writeInst(generate)
   111  	if err != nil {
   112  		t.Fatal(err)
   113  	}
   114  	ext.Size = size
   115  	ext.File = f
   116  	defer func() {
   117  		f.Close()
   118  		if !*keep {
   119  			os.Remove(file)
   120  		}
   121  	}()
   122  
   123  	// Second pass: compare disassembly against our decodings.
   124  	var (
   125  		totalTests  = 0
   126  		totalSkips  = 0
   127  		totalErrors = 0
   128  
   129  		errors = make([]string, 0, 100) // sampled errors, at most cap
   130  	)
   131  	go func() {
   132  		errc <- extdis(ext)
   133  	}()
   134  	generate(func(enc []byte) {
   135  		dec, ok := <-ext.Dec
   136  		if !ok {
   137  			t.Errorf("decoding stream ended early")
   138  			return
   139  		}
   140  		inst, text := disasm(syntax, pad(enc))
   141  		totalTests++
   142  		if *dumpTest {
   143  			fmt.Printf("%x -> %s [%d]\n", enc[:len(enc)], dec.text, dec.nenc)
   144  		}
   145  		if text != dec.text || inst.Len != dec.nenc {
   146  			suffix := ""
   147  			if allowedMismatch(text, size, &inst, dec) {
   148  				totalSkips++
   149  				if !*mismatch {
   150  					return
   151  				}
   152  				suffix += " (allowed mismatch)"
   153  			}
   154  			totalErrors++
   155  			if len(errors) >= cap(errors) {
   156  				j := rand.Intn(totalErrors)
   157  				if j >= cap(errors) {
   158  					return
   159  				}
   160  				errors = append(errors[:j], errors[j+1:]...)
   161  			}
   162  			errors = append(errors, fmt.Sprintf("decode(%x) = %q, %d, want %q, %d%s", enc, text, inst.Len, dec.text, dec.nenc, suffix))
   163  		}
   164  	})
   165  
   166  	if *mismatch {
   167  		totalErrors -= totalSkips
   168  	}
   169  
   170  	for _, b := range errors {
   171  		t.Log(b)
   172  	}
   173  
   174  	if totalErrors > 0 {
   175  		t.Fail()
   176  	}
   177  	t.Logf("%d test cases, %d expected mismatches, %d failures; %.0f cases/second", totalTests, totalSkips, totalErrors, float64(totalTests)/time.Since(start).Seconds())
   178  
   179  	if err := <-errc; err != nil {
   180  		t.Fatalf("external disassembler: %v", err)
   181  	}
   182  
   183  }
   184  
   185  const start = 0x8000 // start address of text
   186  
   187  // writeInst writes the generated byte sequences to a new file
   188  // starting at offset start. That file is intended to be the input to
   189  // the external disassembler.
   190  func writeInst(generate func(func([]byte))) (file string, f *os.File, size int, err error) {
   191  	f, err = ioutil.TempFile("", "ppc64asm")
   192  	if err != nil {
   193  		return
   194  	}
   195  
   196  	file = f.Name()
   197  
   198  	f.Seek(start, io.SeekStart)
   199  	w := bufio.NewWriter(f)
   200  	defer w.Flush()
   201  	size = 0
   202  	generate(func(x []byte) {
   203  		if len(x) != 4 && len(x) != 8 {
   204  			panic(fmt.Sprintf("Unexpected instruction %v\n", x))
   205  		}
   206  		izeros := zeros
   207  		if len(x) == 4 {
   208  			// Only pad to 4 bytes for a 4 byte instruction word.
   209  			izeros = izeros[4:]
   210  		}
   211  		if debug {
   212  			fmt.Printf("%#x: %x%x\n", start+size, x, izeros[len(x):])
   213  		}
   214  		w.Write(x)
   215  		w.Write(izeros[len(x):])
   216  		size += len(izeros)
   217  	})
   218  	return file, f, size, nil
   219  }
   220  
   221  var zeros = []byte{0, 0, 0, 0, 0, 0, 0, 0}
   222  
   223  // pad pads the code sequence with pops.
   224  func pad(enc []byte) []byte {
   225  	if len(enc) < 4 {
   226  		enc = append(enc[:len(enc):len(enc)], zeros[:4-len(enc)]...)
   227  	}
   228  	return enc
   229  }
   230  
   231  // disasm returns the decoded instruction and text
   232  // for the given source bytes, using the given syntax and mode.
   233  func disasm(syntax string, src []byte) (inst Inst, text string) {
   234  	// If printTests is set, we record the coverage value
   235  	// before and after, and we write out the inputs for which
   236  	// coverage went up, in the format expected in testdata/decode.text.
   237  	// This produces a fairly small set of test cases that exercise nearly
   238  	// all the code.
   239  	var cover float64
   240  	if *printTests {
   241  		cover -= coverage()
   242  	}
   243  
   244  	inst, err := Decode(src, binary.BigEndian)
   245  	if err != nil {
   246  		text = "error: " + err.Error()
   247  	} else {
   248  		text = inst.String()
   249  		switch syntax {
   250  		//case "arm":
   251  		//	text = ARMSyntax(inst)
   252  		case "gnu":
   253  			text = GNUSyntax(inst, 0)
   254  		//case "plan9":
   255  		//	text = GoSyntax(inst, 0, nil)
   256  		default:
   257  			text = "error: unknown syntax " + syntax
   258  		}
   259  	}
   260  
   261  	if *printTests {
   262  		cover += coverage()
   263  		if cover > 0 {
   264  			max := len(src)
   265  			if max > 4 && inst.Len <= 4 {
   266  				max = 4
   267  			}
   268  			fmt.Printf("%x|%x\t%s\t%s\n", src[:inst.Len], src[inst.Len:max], syntax, text)
   269  		}
   270  	}
   271  
   272  	return
   273  }
   274  
   275  // coverage returns a floating point number denoting the
   276  // test coverage until now. The number increases when new code paths are exercised,
   277  // both in the Go program and in the decoder byte code.
   278  func coverage() float64 {
   279  	var f float64
   280  	f += testing.Coverage()
   281  	f += decodeCoverage()
   282  	return f
   283  }
   284  
   285  func decodeCoverage() float64 {
   286  	n := 0
   287  	for _, t := range decoderCover {
   288  		if t {
   289  			n++
   290  		}
   291  	}
   292  	return float64(1+n) / float64(1+len(decoderCover))
   293  }
   294  
   295  // Helpers for writing disassembler output parsers.
   296  
   297  // hasPrefix reports whether any of the space-separated words in the text s
   298  // begins with any of the given prefixes.
   299  func hasPrefix(s string, prefixes ...string) bool {
   300  	for _, prefix := range prefixes {
   301  		for s := s; s != ""; {
   302  			if strings.HasPrefix(s, prefix) {
   303  				return true
   304  			}
   305  			i := strings.Index(s, " ")
   306  			if i < 0 {
   307  				break
   308  			}
   309  			s = s[i+1:]
   310  		}
   311  	}
   312  	return false
   313  }
   314  
   315  // contains reports whether the text s contains any of the given substrings.
   316  func contains(s string, substrings ...string) bool {
   317  	for _, sub := range substrings {
   318  		if strings.Contains(s, sub) {
   319  			return true
   320  		}
   321  	}
   322  	return false
   323  }
   324  
   325  // isHex reports whether b is a hexadecimal character (0-9A-Fa-f).
   326  func isHex(b byte) bool { return b == '0' || unhex[b] > 0 }
   327  
   328  // parseHex parses the hexadecimal byte dump in hex,
   329  // appending the parsed bytes to raw and returning the updated slice.
   330  // The returned bool signals whether any invalid hex was found.
   331  // Spaces and tabs between bytes are okay but any other non-hex is not.
   332  func parseHex(hex []byte, raw []byte) ([]byte, bool) {
   333  	hex = trimSpace(hex)
   334  	for j := 0; j < len(hex); {
   335  		for hex[j] == ' ' || hex[j] == '\t' {
   336  			j++
   337  		}
   338  		if j >= len(hex) {
   339  			break
   340  		}
   341  		if j+2 > len(hex) || !isHex(hex[j]) || !isHex(hex[j+1]) {
   342  			return nil, false
   343  		}
   344  		raw = append(raw, unhex[hex[j]]<<4|unhex[hex[j+1]])
   345  		j += 2
   346  	}
   347  	return raw, true
   348  }
   349  
   350  var unhex = [256]byte{
   351  	'0': 0,
   352  	'1': 1,
   353  	'2': 2,
   354  	'3': 3,
   355  	'4': 4,
   356  	'5': 5,
   357  	'6': 6,
   358  	'7': 7,
   359  	'8': 8,
   360  	'9': 9,
   361  	'A': 10,
   362  	'B': 11,
   363  	'C': 12,
   364  	'D': 13,
   365  	'E': 14,
   366  	'F': 15,
   367  	'a': 10,
   368  	'b': 11,
   369  	'c': 12,
   370  	'd': 13,
   371  	'e': 14,
   372  	'f': 15,
   373  }
   374  
   375  // index is like bytes.Index(s, []byte(t)) but avoids the allocation.
   376  func index(s []byte, t string) int {
   377  	i := 0
   378  	for {
   379  		j := bytes.IndexByte(s[i:], t[0])
   380  		if j < 0 {
   381  			return -1
   382  		}
   383  		i = i + j
   384  		if i+len(t) > len(s) {
   385  			return -1
   386  		}
   387  		for k := 1; k < len(t); k++ {
   388  			if s[i+k] != t[k] {
   389  				goto nomatch
   390  			}
   391  		}
   392  		return i
   393  	nomatch:
   394  		i++
   395  	}
   396  }
   397  
   398  // fixSpace rewrites runs of spaces, tabs, and newline characters into single spaces in s.
   399  // If s must be rewritten, it is rewritten in place.
   400  func fixSpace(s []byte) []byte {
   401  	s = trimSpace(s)
   402  	for i := 0; i < len(s); i++ {
   403  		if s[i] == '\t' || s[i] == '\n' || i > 0 && s[i] == ' ' && s[i-1] == ' ' {
   404  			goto Fix
   405  		}
   406  	}
   407  	return s
   408  
   409  Fix:
   410  	b := s
   411  	w := 0
   412  	for i := 0; i < len(s); i++ {
   413  		c := s[i]
   414  		if c == '\t' || c == '\n' {
   415  			c = ' '
   416  		}
   417  		if c == ' ' && w > 0 && b[w-1] == ' ' {
   418  			continue
   419  		}
   420  		b[w] = c
   421  		w++
   422  	}
   423  	if w > 0 && b[w-1] == ' ' {
   424  		w--
   425  	}
   426  	return b[:w]
   427  }
   428  
   429  // trimSpace trims leading and trailing space from s, returning a subslice of s.
   430  func trimSpace(s []byte) []byte {
   431  	j := len(s)
   432  	for j > 0 && (s[j-1] == ' ' || s[j-1] == '\t' || s[j-1] == '\n') {
   433  		j--
   434  	}
   435  	i := 0
   436  	for i < j && (s[i] == ' ' || s[i] == '\t') {
   437  		i++
   438  	}
   439  	return s[i:j]
   440  }
   441  
   442  // pcrel matches instructions using relative addressing mode.
   443  var (
   444  	pcrel = regexp.MustCompile(`^((?:.* )?(?:b|bc)[^ac ]* (?:(?:[0-9]{1,2},)|(?:[0-7]\*)|\+|lt|gt|eq|so|cr[0-7]|,)*)0x([0-9a-f]+)$`)
   445  )
   446  
   447  // Generators.
   448  //
   449  // The test cases are described as functions that invoke a callback repeatedly,
   450  // with a new input sequence each time. These helpers make writing those
   451  // a little easier.
   452  
   453  // randomCases generates random instructions.
   454  func randomCases(t *testing.T) func(func([]byte)) {
   455  	return func(try func([]byte)) {
   456  		// All the strides are relatively prime to 2 and therefore to 2²⁸,
   457  		// so we will not repeat any instructions until we have tried all 2²⁸.
   458  		// Using a stride other than 1 is meant to visit the instructions in a
   459  		// pseudorandom order, which gives better variety in the set of
   460  		// test cases chosen by -printtests.
   461  		stride := uint32(10007)
   462  		n := 1 << 28 / 7
   463  		if testing.Short() {
   464  			stride = 100003
   465  			n = 1 << 28 / 1001
   466  		} else if *longTest {
   467  			stride = 2000033
   468  			n = 1 << 29
   469  		}
   470  		x := uint32(0)
   471  		for i := 0; i < n; i++ {
   472  			enc := (x%15)<<28 | x&(1<<28-1)
   473  			try([]byte{byte(enc), byte(enc >> 8), byte(enc >> 16), byte(enc >> 24)})
   474  			x += stride
   475  		}
   476  	}
   477  }
   478  
   479  // hexCases generates the cases written in hexadecimal in the encoded string.
   480  // Spaces in 'encoded' separate entire test cases, not individual bytes.
   481  func hexCases(t *testing.T, encoded string) func(func([]byte)) {
   482  	return func(try func([]byte)) {
   483  		for _, x := range strings.Fields(encoded) {
   484  			src, err := hex.DecodeString(x)
   485  			if err != nil {
   486  				t.Errorf("parsing %q: %v", x, err)
   487  			}
   488  			try(src)
   489  		}
   490  	}
   491  }
   492  
   493  // testdataCases generates the test cases recorded in testdata/decode.txt.
   494  // It only uses the inputs; it ignores the answers recorded in that file.
   495  func testdataCases(t *testing.T) func(func([]byte)) {
   496  	var codes [][]byte
   497  	data, err := ioutil.ReadFile("testdata/decode.txt")
   498  	if err != nil {
   499  		t.Fatal(err)
   500  	}
   501  	for _, line := range strings.Split(string(data), "\n") {
   502  		line = strings.TrimSpace(line)
   503  		if line == "" || strings.HasPrefix(line, "#") {
   504  			continue
   505  		}
   506  		f := strings.Fields(line)[0]
   507  		i := strings.Index(f, "|")
   508  		if i < 0 {
   509  			t.Errorf("parsing %q: missing | separator", f)
   510  			continue
   511  		}
   512  		if i%2 != 0 {
   513  			t.Errorf("parsing %q: misaligned | separator", f)
   514  		}
   515  		code, err := hex.DecodeString(f[:i] + f[i+1:])
   516  		if err != nil {
   517  			t.Errorf("parsing %q: %v", f, err)
   518  			continue
   519  		}
   520  		codes = append(codes, code)
   521  	}
   522  
   523  	return func(try func([]byte)) {
   524  		for _, code := range codes {
   525  			try(code)
   526  		}
   527  	}
   528  }
   529  
   530  func caller(skip int) string {
   531  	pc, _, _, _ := runtime.Caller(skip)
   532  	f := runtime.FuncForPC(pc)
   533  	name := "?"
   534  	if f != nil {
   535  		name = f.Name()
   536  		if i := strings.LastIndex(name, "."); i >= 0 {
   537  			name = name[i+1:]
   538  		}
   539  	}
   540  	return name
   541  }
   542  

View as plain text