github.com/riscv/riscv-go@v0.0.0-20200123204226-124ebd6fcc8e/src/cmd/compile/internal/gc/asm_test.go (about)

     1  // Copyright 2016 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 gc
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"internal/testenv"
    11  	"io/ioutil"
    12  	"os"
    13  	"os/exec"
    14  	"path/filepath"
    15  	"regexp"
    16  	"runtime"
    17  	"strings"
    18  	"testing"
    19  )
    20  
    21  // TestAssembly checks to make sure the assembly generated for
    22  // functions contains certain expected instructions.
    23  func TestAssembly(t *testing.T) {
    24  	if testing.Short() {
    25  		t.Skip("slow test; skipping")
    26  	}
    27  	testenv.MustHaveGoBuild(t)
    28  	if runtime.GOOS == "windows" {
    29  		// TODO: remove if we can get "go tool compile -S" to work on windows.
    30  		t.Skipf("skipping test: recursive windows compile not working")
    31  	}
    32  	dir, err := ioutil.TempDir("", "TestAssembly")
    33  	if err != nil {
    34  		t.Fatalf("could not create directory: %v", err)
    35  	}
    36  	defer os.RemoveAll(dir)
    37  
    38  	for _, test := range asmTests {
    39  		asm := compileToAsm(t, dir, test.arch, test.os, fmt.Sprintf(template, test.function))
    40  		// Get rid of code for "".init. Also gets rid of type algorithms & other junk.
    41  		if i := strings.Index(asm, "\n\"\".init "); i >= 0 {
    42  			asm = asm[:i+1]
    43  		}
    44  		for _, r := range test.regexps {
    45  			if b, err := regexp.MatchString(r, asm); !b || err != nil {
    46  				t.Errorf("expected:%s\ngo:%s\nasm:%s\n", r, test.function, asm)
    47  			}
    48  		}
    49  	}
    50  }
    51  
    52  // compile compiles the package pkg for architecture arch and
    53  // returns the generated assembly.  dir is a scratch directory.
    54  func compileToAsm(t *testing.T, dir, goarch, goos, pkg string) string {
    55  	// Create source.
    56  	src := filepath.Join(dir, "test.go")
    57  	f, err := os.Create(src)
    58  	if err != nil {
    59  		panic(err)
    60  	}
    61  	f.Write([]byte(pkg))
    62  	f.Close()
    63  
    64  	// First, install any dependencies we need.  This builds the required export data
    65  	// for any packages that are imported.
    66  	// TODO: extract dependencies automatically?
    67  	var stdout, stderr bytes.Buffer
    68  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", filepath.Join(dir, "encoding/binary.a"), "encoding/binary")
    69  	cmd.Env = mergeEnvLists([]string{"GOARCH=" + goarch, "GOOS=" + goos}, os.Environ())
    70  	cmd.Stdout = &stdout
    71  	cmd.Stderr = &stderr
    72  	if err := cmd.Run(); err != nil {
    73  		panic(err)
    74  	}
    75  	if s := stdout.String(); s != "" {
    76  		panic(fmt.Errorf("Stdout = %s\nWant empty", s))
    77  	}
    78  	if s := stderr.String(); s != "" {
    79  		panic(fmt.Errorf("Stderr = %s\nWant empty", s))
    80  	}
    81  
    82  	// Now, compile the individual file for which we want to see the generated assembly.
    83  	cmd = exec.Command(testenv.GoToolPath(t), "tool", "compile", "-I", dir, "-S", "-o", filepath.Join(dir, "out.o"), src)
    84  	cmd.Env = mergeEnvLists([]string{"GOARCH=" + goarch, "GOOS=" + goos}, os.Environ())
    85  	cmd.Stdout = &stdout
    86  	cmd.Stderr = &stderr
    87  	if err := cmd.Run(); err != nil {
    88  		panic(err)
    89  	}
    90  	if s := stderr.String(); s != "" {
    91  		panic(fmt.Errorf("Stderr = %s\nWant empty", s))
    92  	}
    93  	return stdout.String()
    94  }
    95  
    96  // template to convert a function to a full file
    97  const template = `
    98  package main
    99  %s
   100  `
   101  
   102  type asmTest struct {
   103  	// architecture to compile to
   104  	arch string
   105  	// os to compile to
   106  	os string
   107  	// function to compile
   108  	function string
   109  	// regexps that must match the generated assembly
   110  	regexps []string
   111  }
   112  
   113  var asmTests = [...]asmTest{
   114  	{"amd64", "linux", `
   115  func f(x int) int {
   116  	return x * 64
   117  }
   118  `,
   119  		[]string{"\tSHLQ\t\\$6,"},
   120  	},
   121  	{"amd64", "linux", `
   122  func f(x int) int {
   123  	return x * 96
   124  }`,
   125  		[]string{"\tSHLQ\t\\$5,", "\tLEAQ\t\\(.*\\)\\(.*\\*2\\),"},
   126  	},
   127  	// Load-combining tests.
   128  	{"amd64", "linux", `
   129  import "encoding/binary"
   130  func f(b []byte) uint64 {
   131  	return binary.LittleEndian.Uint64(b)
   132  }
   133  `,
   134  		[]string{"\tMOVQ\t\\(.*\\),"},
   135  	},
   136  	{"amd64", "linux", `
   137  import "encoding/binary"
   138  func f(b []byte, i int) uint64 {
   139  	return binary.LittleEndian.Uint64(b[i:])
   140  }
   141  `,
   142  		[]string{"\tMOVQ\t\\(.*\\)\\(.*\\*1\\),"},
   143  	},
   144  	{"amd64", "linux", `
   145  import "encoding/binary"
   146  func f(b []byte) uint32 {
   147  	return binary.LittleEndian.Uint32(b)
   148  }
   149  `,
   150  		[]string{"\tMOVL\t\\(.*\\),"},
   151  	},
   152  	{"amd64", "linux", `
   153  import "encoding/binary"
   154  func f(b []byte, i int) uint32 {
   155  	return binary.LittleEndian.Uint32(b[i:])
   156  }
   157  `,
   158  		[]string{"\tMOVL\t\\(.*\\)\\(.*\\*1\\),"},
   159  	},
   160  	{"amd64", "linux", `
   161  import "encoding/binary"
   162  func f(b []byte) uint64 {
   163  	return binary.BigEndian.Uint64(b)
   164  }
   165  `,
   166  		[]string{"\tBSWAPQ\t"},
   167  	},
   168  	{"amd64", "linux", `
   169  import "encoding/binary"
   170  func f(b []byte, i int) uint64 {
   171  	return binary.BigEndian.Uint64(b[i:])
   172  }
   173  `,
   174  		[]string{"\tBSWAPQ\t"},
   175  	},
   176  	{"amd64", "linux", `
   177  import "encoding/binary"
   178  func f(b []byte, v uint64) {
   179  	binary.BigEndian.PutUint64(b, v)
   180  }
   181  `,
   182  		[]string{"\tBSWAPQ\t"},
   183  	},
   184  	{"amd64", "linux", `
   185  import "encoding/binary"
   186  func f(b []byte) uint32 {
   187  	return binary.BigEndian.Uint32(b)
   188  }
   189  `,
   190  		[]string{"\tBSWAPL\t"},
   191  	},
   192  	{"amd64", "linux", `
   193  import "encoding/binary"
   194  func f(b []byte, i int) uint32 {
   195  	return binary.BigEndian.Uint32(b[i:])
   196  }
   197  `,
   198  		[]string{"\tBSWAPL\t"},
   199  	},
   200  	{"amd64", "linux", `
   201  import "encoding/binary"
   202  func f(b []byte, v uint32) {
   203  	binary.BigEndian.PutUint32(b, v)
   204  }
   205  `,
   206  		[]string{"\tBSWAPL\t"},
   207  	},
   208  	{"386", "linux", `
   209  import "encoding/binary"
   210  func f(b []byte) uint32 {
   211  	return binary.LittleEndian.Uint32(b)
   212  }
   213  `,
   214  		[]string{"\tMOVL\t\\(.*\\),"},
   215  	},
   216  	{"386", "linux", `
   217  import "encoding/binary"
   218  func f(b []byte, i int) uint32 {
   219  	return binary.LittleEndian.Uint32(b[i:])
   220  }
   221  `,
   222  		[]string{"\tMOVL\t\\(.*\\)\\(.*\\*1\\),"},
   223  	},
   224  
   225  	// Structure zeroing.  See issue #18370.
   226  	{"amd64", "linux", `
   227  type T struct {
   228  	a, b, c int
   229  }
   230  func f(t *T) {
   231  	*t = T{}
   232  }
   233  `,
   234  		[]string{"\tMOVQ\t\\$0, \\(.*\\)", "\tMOVQ\t\\$0, 8\\(.*\\)", "\tMOVQ\t\\$0, 16\\(.*\\)"},
   235  	},
   236  	// TODO: add a test for *t = T{3,4,5} when we fix that.
   237  
   238  	// Rotate tests
   239  	{"amd64", "linux", `
   240  	func f(x uint64) uint64 {
   241  		return x<<7 | x>>57
   242  	}
   243  `,
   244  		[]string{"\tROLQ\t[$]7,"},
   245  	},
   246  	{"amd64", "linux", `
   247  	func f(x uint64) uint64 {
   248  		return x<<7 + x>>57
   249  	}
   250  `,
   251  		[]string{"\tROLQ\t[$]7,"},
   252  	},
   253  	{"amd64", "linux", `
   254  	func f(x uint64) uint64 {
   255  		return x<<7 ^ x>>57
   256  	}
   257  `,
   258  		[]string{"\tROLQ\t[$]7,"},
   259  	},
   260  	{"amd64", "linux", `
   261  	func f(x uint32) uint32 {
   262  		return x<<7 + x>>25
   263  	}
   264  `,
   265  		[]string{"\tROLL\t[$]7,"},
   266  	},
   267  	{"amd64", "linux", `
   268  	func f(x uint32) uint32 {
   269  		return x<<7 | x>>25
   270  	}
   271  `,
   272  		[]string{"\tROLL\t[$]7,"},
   273  	},
   274  	{"amd64", "linux", `
   275  	func f(x uint32) uint32 {
   276  		return x<<7 ^ x>>25
   277  	}
   278  `,
   279  		[]string{"\tROLL\t[$]7,"},
   280  	},
   281  	{"amd64", "linux", `
   282  	func f(x uint16) uint16 {
   283  		return x<<7 + x>>9
   284  	}
   285  `,
   286  		[]string{"\tROLW\t[$]7,"},
   287  	},
   288  	{"amd64", "linux", `
   289  	func f(x uint16) uint16 {
   290  		return x<<7 | x>>9
   291  	}
   292  `,
   293  		[]string{"\tROLW\t[$]7,"},
   294  	},
   295  	{"amd64", "linux", `
   296  	func f(x uint16) uint16 {
   297  		return x<<7 ^ x>>9
   298  	}
   299  `,
   300  		[]string{"\tROLW\t[$]7,"},
   301  	},
   302  	{"amd64", "linux", `
   303  	func f(x uint8) uint8 {
   304  		return x<<7 + x>>1
   305  	}
   306  `,
   307  		[]string{"\tROLB\t[$]7,"},
   308  	},
   309  	{"amd64", "linux", `
   310  	func f(x uint8) uint8 {
   311  		return x<<7 | x>>1
   312  	}
   313  `,
   314  		[]string{"\tROLB\t[$]7,"},
   315  	},
   316  	{"amd64", "linux", `
   317  	func f(x uint8) uint8 {
   318  		return x<<7 ^ x>>1
   319  	}
   320  `,
   321  		[]string{"\tROLB\t[$]7,"},
   322  	},
   323  
   324  	{"arm", "linux", `
   325  	func f(x uint32) uint32 {
   326  		return x<<7 + x>>25
   327  	}
   328  `,
   329  		[]string{"\tMOVW\tR[0-9]+@>25,"},
   330  	},
   331  	{"arm", "linux", `
   332  	func f(x uint32) uint32 {
   333  		return x<<7 | x>>25
   334  	}
   335  `,
   336  		[]string{"\tMOVW\tR[0-9]+@>25,"},
   337  	},
   338  	{"arm", "linux", `
   339  	func f(x uint32) uint32 {
   340  		return x<<7 ^ x>>25
   341  	}
   342  `,
   343  		[]string{"\tMOVW\tR[0-9]+@>25,"},
   344  	},
   345  
   346  	{"arm64", "linux", `
   347  	func f(x uint64) uint64 {
   348  		return x<<7 + x>>57
   349  	}
   350  `,
   351  		[]string{"\tROR\t[$]57,"},
   352  	},
   353  	{"arm64", "linux", `
   354  	func f(x uint64) uint64 {
   355  		return x<<7 | x>>57
   356  	}
   357  `,
   358  		[]string{"\tROR\t[$]57,"},
   359  	},
   360  	{"arm64", "linux", `
   361  	func f(x uint64) uint64 {
   362  		return x<<7 ^ x>>57
   363  	}
   364  `,
   365  		[]string{"\tROR\t[$]57,"},
   366  	},
   367  	{"arm64", "linux", `
   368  	func f(x uint32) uint32 {
   369  		return x<<7 + x>>25
   370  	}
   371  `,
   372  		[]string{"\tRORW\t[$]25,"},
   373  	},
   374  	{"arm64", "linux", `
   375  	func f(x uint32) uint32 {
   376  		return x<<7 | x>>25
   377  	}
   378  `,
   379  		[]string{"\tRORW\t[$]25,"},
   380  	},
   381  	{"arm64", "linux", `
   382  	func f(x uint32) uint32 {
   383  		return x<<7 ^ x>>25
   384  	}
   385  `,
   386  		[]string{"\tRORW\t[$]25,"},
   387  	},
   388  
   389  	{"s390x", "linux", `
   390  	func f(x uint64) uint64 {
   391  		return x<<7 + x>>57
   392  	}
   393  `,
   394  		[]string{"\tRLLG\t[$]7,"},
   395  	},
   396  	{"s390x", "linux", `
   397  	func f(x uint64) uint64 {
   398  		return x<<7 | x>>57
   399  	}
   400  `,
   401  		[]string{"\tRLLG\t[$]7,"},
   402  	},
   403  	{"s390x", "linux", `
   404  	func f(x uint64) uint64 {
   405  		return x<<7 ^ x>>57
   406  	}
   407  `,
   408  		[]string{"\tRLLG\t[$]7,"},
   409  	},
   410  	{"s390x", "linux", `
   411  	func f(x uint32) uint32 {
   412  		return x<<7 + x>>25
   413  	}
   414  `,
   415  		[]string{"\tRLL\t[$]7,"},
   416  	},
   417  	{"s390x", "linux", `
   418  	func f(x uint32) uint32 {
   419  		return x<<7 | x>>25
   420  	}
   421  `,
   422  		[]string{"\tRLL\t[$]7,"},
   423  	},
   424  	{"s390x", "linux", `
   425  	func f(x uint32) uint32 {
   426  		return x<<7 ^ x>>25
   427  	}
   428  `,
   429  		[]string{"\tRLL\t[$]7,"},
   430  	},
   431  
   432  	// Rotate after inlining (see issue 18254).
   433  	{"amd64", "linux", `
   434  	func f(x uint32, k uint) uint32 {
   435  		return x<<k | x>>(32-k)
   436  	}
   437  	func g(x uint32) uint32 {
   438  		return f(x, 7)
   439  	}
   440  `,
   441  		[]string{"\tROLL\t[$]7,"},
   442  	},
   443  }
   444  
   445  // mergeEnvLists merges the two environment lists such that
   446  // variables with the same name in "in" replace those in "out".
   447  // This always returns a newly allocated slice.
   448  func mergeEnvLists(in, out []string) []string {
   449  	out = append([]string(nil), out...)
   450  NextVar:
   451  	for _, inkv := range in {
   452  		k := strings.SplitAfterN(inkv, "=", 2)[0]
   453  		for i, outkv := range out {
   454  			if strings.HasPrefix(outkv, k) {
   455  				out[i] = inkv
   456  				continue NextVar
   457  			}
   458  		}
   459  		out = append(out, inkv)
   460  	}
   461  	return out
   462  }
   463  
   464  // TestLineNumber checks to make sure the generated assembly has line numbers
   465  // see issue #16214
   466  func TestLineNumber(t *testing.T) {
   467  	testenv.MustHaveGoBuild(t)
   468  	dir, err := ioutil.TempDir("", "TestLineNumber")
   469  	if err != nil {
   470  		t.Fatalf("could not create directory: %v", err)
   471  	}
   472  	defer os.RemoveAll(dir)
   473  
   474  	src := filepath.Join(dir, "x.go")
   475  	err = ioutil.WriteFile(src, []byte(issue16214src), 0644)
   476  	if err != nil {
   477  		t.Fatalf("could not write file: %v", err)
   478  	}
   479  
   480  	cmd := exec.Command(testenv.GoToolPath(t), "tool", "compile", "-S", "-o", filepath.Join(dir, "out.o"), src)
   481  	out, err := cmd.CombinedOutput()
   482  	if err != nil {
   483  		t.Fatalf("fail to run go tool compile: %v", err)
   484  	}
   485  
   486  	if strings.Contains(string(out), "unknown line number") {
   487  		t.Errorf("line number missing in assembly:\n%s", out)
   488  	}
   489  }
   490  
   491  var issue16214src = `
   492  package main
   493  
   494  func Mod32(x uint32) uint32 {
   495  	return x % 3 // frontend rewrites it as HMUL with 2863311531, the LITERAL node has unknown Pos
   496  }
   497  `