github.com/rakyll/go@v0.0.0-20170216000551-64c02460d703/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("%s/%s: expected:%s\ngo:%s\nasm:%s\n", test.os, test.arch, 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, i int, v uint64) {
   187  	binary.BigEndian.PutUint64(b[i:], v)
   188  }
   189  `,
   190  		[]string{"\tBSWAPQ\t"},
   191  	},
   192  	{"amd64", "linux", `
   193  import "encoding/binary"
   194  func f(b []byte) uint32 {
   195  	return binary.BigEndian.Uint32(b)
   196  }
   197  `,
   198  		[]string{"\tBSWAPL\t"},
   199  	},
   200  	{"amd64", "linux", `
   201  import "encoding/binary"
   202  func f(b []byte, i int) uint32 {
   203  	return binary.BigEndian.Uint32(b[i:])
   204  }
   205  `,
   206  		[]string{"\tBSWAPL\t"},
   207  	},
   208  	{"amd64", "linux", `
   209  import "encoding/binary"
   210  func f(b []byte, v uint32) {
   211  	binary.BigEndian.PutUint32(b, v)
   212  }
   213  `,
   214  		[]string{"\tBSWAPL\t"},
   215  	},
   216  	{"amd64", "linux", `
   217  import "encoding/binary"
   218  func f(b []byte, i int, v uint32) {
   219  	binary.BigEndian.PutUint32(b[i:], v)
   220  }
   221  `,
   222  		[]string{"\tBSWAPL\t"},
   223  	},
   224  	{"amd64", "linux", `
   225  import "encoding/binary"
   226  func f(b []byte) uint16 {
   227  	return binary.BigEndian.Uint16(b)
   228  }
   229  `,
   230  		[]string{"\tROLW\t\\$8,"},
   231  	},
   232  	{"amd64", "linux", `
   233  import "encoding/binary"
   234  func f(b []byte, i int) uint16 {
   235  	return binary.BigEndian.Uint16(b[i:])
   236  }
   237  `,
   238  		[]string{"\tROLW\t\\$8,"},
   239  	},
   240  	{"amd64", "linux", `
   241  import "encoding/binary"
   242  func f(b []byte, v uint16) {
   243  	binary.BigEndian.PutUint16(b, v)
   244  }
   245  `,
   246  		[]string{"\tROLW\t\\$8,"},
   247  	},
   248  	{"amd64", "linux", `
   249  import "encoding/binary"
   250  func f(b []byte, i int, v uint16) {
   251  	binary.BigEndian.PutUint16(b[i:], v)
   252  }
   253  `,
   254  		[]string{"\tROLW\t\\$8,"},
   255  	},
   256  	{"386", "linux", `
   257  import "encoding/binary"
   258  func f(b []byte) uint32 {
   259  	return binary.LittleEndian.Uint32(b)
   260  }
   261  `,
   262  		[]string{"\tMOVL\t\\(.*\\),"},
   263  	},
   264  	{"386", "linux", `
   265  import "encoding/binary"
   266  func f(b []byte, i int) uint32 {
   267  	return binary.LittleEndian.Uint32(b[i:])
   268  }
   269  `,
   270  		[]string{"\tMOVL\t\\(.*\\)\\(.*\\*1\\),"},
   271  	},
   272  	{"s390x", "linux", `
   273  import "encoding/binary"
   274  func f(b []byte) uint32 {
   275  	return binary.LittleEndian.Uint32(b)
   276  }
   277  `,
   278  		[]string{"\tMOVWBR\t\\(.*\\),"},
   279  	},
   280  	{"s390x", "linux", `
   281  import "encoding/binary"
   282  func f(b []byte, i int) uint32 {
   283  	return binary.LittleEndian.Uint32(b[i:])
   284  }
   285  `,
   286  		[]string{"\tMOVWBR\t\\(.*\\)\\(.*\\*1\\),"},
   287  	},
   288  	{"s390x", "linux", `
   289  import "encoding/binary"
   290  func f(b []byte) uint64 {
   291  	return binary.LittleEndian.Uint64(b)
   292  }
   293  `,
   294  		[]string{"\tMOVDBR\t\\(.*\\),"},
   295  	},
   296  	{"s390x", "linux", `
   297  import "encoding/binary"
   298  func f(b []byte, i int) uint64 {
   299  	return binary.LittleEndian.Uint64(b[i:])
   300  }
   301  `,
   302  		[]string{"\tMOVDBR\t\\(.*\\)\\(.*\\*1\\),"},
   303  	},
   304  	{"s390x", "linux", `
   305  import "encoding/binary"
   306  func f(b []byte) uint32 {
   307  	return binary.BigEndian.Uint32(b)
   308  }
   309  `,
   310  		[]string{"\tMOVWZ\t\\(.*\\),"},
   311  	},
   312  	{"s390x", "linux", `
   313  import "encoding/binary"
   314  func f(b []byte, i int) uint32 {
   315  	return binary.BigEndian.Uint32(b[i:])
   316  }
   317  `,
   318  		[]string{"\tMOVWZ\t\\(.*\\)\\(.*\\*1\\),"},
   319  	},
   320  	{"s390x", "linux", `
   321  import "encoding/binary"
   322  func f(b []byte) uint64 {
   323  	return binary.BigEndian.Uint64(b)
   324  }
   325  `,
   326  		[]string{"\tMOVD\t\\(.*\\),"},
   327  	},
   328  	{"s390x", "linux", `
   329  import "encoding/binary"
   330  func f(b []byte, i int) uint64 {
   331  	return binary.BigEndian.Uint64(b[i:])
   332  }
   333  `,
   334  		[]string{"\tMOVD\t\\(.*\\)\\(.*\\*1\\),"},
   335  	},
   336  
   337  	// Structure zeroing.  See issue #18370.
   338  	{"amd64", "linux", `
   339  type T struct {
   340  	a, b, c int
   341  }
   342  func f(t *T) {
   343  	*t = T{}
   344  }
   345  `,
   346  		[]string{"\tMOVQ\t\\$0, \\(.*\\)", "\tMOVQ\t\\$0, 8\\(.*\\)", "\tMOVQ\t\\$0, 16\\(.*\\)"},
   347  	},
   348  	// TODO: add a test for *t = T{3,4,5} when we fix that.
   349  	// Also test struct containing pointers (this was special because of write barriers).
   350  	{"amd64", "linux", `
   351  type T struct {
   352  	a, b, c *int
   353  }
   354  func f(t *T) {
   355  	*t = T{}
   356  }
   357  `,
   358  		[]string{"\tMOVQ\t\\$0, \\(.*\\)", "\tMOVQ\t\\$0, 8\\(.*\\)", "\tMOVQ\t\\$0, 16\\(.*\\)", "\tCALL\truntime\\.writebarrierptr\\(SB\\)"},
   359  	},
   360  
   361  	// Rotate tests
   362  	{"amd64", "linux", `
   363  	func f(x uint64) uint64 {
   364  		return x<<7 | x>>57
   365  	}
   366  `,
   367  		[]string{"\tROLQ\t[$]7,"},
   368  	},
   369  	{"amd64", "linux", `
   370  	func f(x uint64) uint64 {
   371  		return x<<7 + x>>57
   372  	}
   373  `,
   374  		[]string{"\tROLQ\t[$]7,"},
   375  	},
   376  	{"amd64", "linux", `
   377  	func f(x uint64) uint64 {
   378  		return x<<7 ^ x>>57
   379  	}
   380  `,
   381  		[]string{"\tROLQ\t[$]7,"},
   382  	},
   383  	{"amd64", "linux", `
   384  	func f(x uint32) uint32 {
   385  		return x<<7 + x>>25
   386  	}
   387  `,
   388  		[]string{"\tROLL\t[$]7,"},
   389  	},
   390  	{"amd64", "linux", `
   391  	func f(x uint32) uint32 {
   392  		return x<<7 | x>>25
   393  	}
   394  `,
   395  		[]string{"\tROLL\t[$]7,"},
   396  	},
   397  	{"amd64", "linux", `
   398  	func f(x uint32) uint32 {
   399  		return x<<7 ^ x>>25
   400  	}
   401  `,
   402  		[]string{"\tROLL\t[$]7,"},
   403  	},
   404  	{"amd64", "linux", `
   405  	func f(x uint16) uint16 {
   406  		return x<<7 + x>>9
   407  	}
   408  `,
   409  		[]string{"\tROLW\t[$]7,"},
   410  	},
   411  	{"amd64", "linux", `
   412  	func f(x uint16) uint16 {
   413  		return x<<7 | x>>9
   414  	}
   415  `,
   416  		[]string{"\tROLW\t[$]7,"},
   417  	},
   418  	{"amd64", "linux", `
   419  	func f(x uint16) uint16 {
   420  		return x<<7 ^ x>>9
   421  	}
   422  `,
   423  		[]string{"\tROLW\t[$]7,"},
   424  	},
   425  	{"amd64", "linux", `
   426  	func f(x uint8) uint8 {
   427  		return x<<7 + x>>1
   428  	}
   429  `,
   430  		[]string{"\tROLB\t[$]7,"},
   431  	},
   432  	{"amd64", "linux", `
   433  	func f(x uint8) uint8 {
   434  		return x<<7 | x>>1
   435  	}
   436  `,
   437  		[]string{"\tROLB\t[$]7,"},
   438  	},
   439  	{"amd64", "linux", `
   440  	func f(x uint8) uint8 {
   441  		return x<<7 ^ x>>1
   442  	}
   443  `,
   444  		[]string{"\tROLB\t[$]7,"},
   445  	},
   446  
   447  	{"arm", "linux", `
   448  	func f(x uint32) uint32 {
   449  		return x<<7 + x>>25
   450  	}
   451  `,
   452  		[]string{"\tMOVW\tR[0-9]+@>25,"},
   453  	},
   454  	{"arm", "linux", `
   455  	func f(x uint32) uint32 {
   456  		return x<<7 | x>>25
   457  	}
   458  `,
   459  		[]string{"\tMOVW\tR[0-9]+@>25,"},
   460  	},
   461  	{"arm", "linux", `
   462  	func f(x uint32) uint32 {
   463  		return x<<7 ^ x>>25
   464  	}
   465  `,
   466  		[]string{"\tMOVW\tR[0-9]+@>25,"},
   467  	},
   468  
   469  	{"arm64", "linux", `
   470  	func f(x uint64) uint64 {
   471  		return x<<7 + x>>57
   472  	}
   473  `,
   474  		[]string{"\tROR\t[$]57,"},
   475  	},
   476  	{"arm64", "linux", `
   477  	func f(x uint64) uint64 {
   478  		return x<<7 | x>>57
   479  	}
   480  `,
   481  		[]string{"\tROR\t[$]57,"},
   482  	},
   483  	{"arm64", "linux", `
   484  	func f(x uint64) uint64 {
   485  		return x<<7 ^ x>>57
   486  	}
   487  `,
   488  		[]string{"\tROR\t[$]57,"},
   489  	},
   490  	{"arm64", "linux", `
   491  	func f(x uint32) uint32 {
   492  		return x<<7 + x>>25
   493  	}
   494  `,
   495  		[]string{"\tRORW\t[$]25,"},
   496  	},
   497  	{"arm64", "linux", `
   498  	func f(x uint32) uint32 {
   499  		return x<<7 | x>>25
   500  	}
   501  `,
   502  		[]string{"\tRORW\t[$]25,"},
   503  	},
   504  	{"arm64", "linux", `
   505  	func f(x uint32) uint32 {
   506  		return x<<7 ^ x>>25
   507  	}
   508  `,
   509  		[]string{"\tRORW\t[$]25,"},
   510  	},
   511  
   512  	{"s390x", "linux", `
   513  	func f(x uint64) uint64 {
   514  		return x<<7 + x>>57
   515  	}
   516  `,
   517  		[]string{"\tRLLG\t[$]7,"},
   518  	},
   519  	{"s390x", "linux", `
   520  	func f(x uint64) uint64 {
   521  		return x<<7 | x>>57
   522  	}
   523  `,
   524  		[]string{"\tRLLG\t[$]7,"},
   525  	},
   526  	{"s390x", "linux", `
   527  	func f(x uint64) uint64 {
   528  		return x<<7 ^ x>>57
   529  	}
   530  `,
   531  		[]string{"\tRLLG\t[$]7,"},
   532  	},
   533  	{"s390x", "linux", `
   534  	func f(x uint32) uint32 {
   535  		return x<<7 + x>>25
   536  	}
   537  `,
   538  		[]string{"\tRLL\t[$]7,"},
   539  	},
   540  	{"s390x", "linux", `
   541  	func f(x uint32) uint32 {
   542  		return x<<7 | x>>25
   543  	}
   544  `,
   545  		[]string{"\tRLL\t[$]7,"},
   546  	},
   547  	{"s390x", "linux", `
   548  	func f(x uint32) uint32 {
   549  		return x<<7 ^ x>>25
   550  	}
   551  `,
   552  		[]string{"\tRLL\t[$]7,"},
   553  	},
   554  
   555  	// Rotate after inlining (see issue 18254).
   556  	{"amd64", "linux", `
   557  	func f(x uint32, k uint) uint32 {
   558  		return x<<k | x>>(32-k)
   559  	}
   560  	func g(x uint32) uint32 {
   561  		return f(x, 7)
   562  	}
   563  `,
   564  		[]string{"\tROLL\t[$]7,"},
   565  	},
   566  
   567  	// Direct use of constants in fast map access calls. Issue 19015.
   568  	{"amd64", "linux", `
   569  	func f(m map[int]int) int {
   570  		return m[5]
   571  	}
   572  `,
   573  		[]string{"\tMOVQ\t[$]5,"},
   574  	},
   575  	{"amd64", "linux", `
   576  	func f(m map[int]int) bool {
   577  		_, ok := m[5]
   578  		return ok
   579  	}
   580  `,
   581  		[]string{"\tMOVQ\t[$]5,"},
   582  	},
   583  	{"amd64", "linux", `
   584  	func f(m map[string]int) int {
   585  		return m["abc"]
   586  	}
   587  `,
   588  		[]string{"\"abc\""},
   589  	},
   590  	{"amd64", "linux", `
   591  	func f(m map[string]int) bool {
   592  		_, ok := m["abc"]
   593  		return ok
   594  	}
   595  `,
   596  		[]string{"\"abc\""},
   597  	},
   598  }
   599  
   600  // mergeEnvLists merges the two environment lists such that
   601  // variables with the same name in "in" replace those in "out".
   602  // This always returns a newly allocated slice.
   603  func mergeEnvLists(in, out []string) []string {
   604  	out = append([]string(nil), out...)
   605  NextVar:
   606  	for _, inkv := range in {
   607  		k := strings.SplitAfterN(inkv, "=", 2)[0]
   608  		for i, outkv := range out {
   609  			if strings.HasPrefix(outkv, k) {
   610  				out[i] = inkv
   611  				continue NextVar
   612  			}
   613  		}
   614  		out = append(out, inkv)
   615  	}
   616  	return out
   617  }
   618  
   619  // TestLineNumber checks to make sure the generated assembly has line numbers
   620  // see issue #16214
   621  func TestLineNumber(t *testing.T) {
   622  	testenv.MustHaveGoBuild(t)
   623  	dir, err := ioutil.TempDir("", "TestLineNumber")
   624  	if err != nil {
   625  		t.Fatalf("could not create directory: %v", err)
   626  	}
   627  	defer os.RemoveAll(dir)
   628  
   629  	src := filepath.Join(dir, "x.go")
   630  	err = ioutil.WriteFile(src, []byte(issue16214src), 0644)
   631  	if err != nil {
   632  		t.Fatalf("could not write file: %v", err)
   633  	}
   634  
   635  	cmd := exec.Command(testenv.GoToolPath(t), "tool", "compile", "-S", "-o", filepath.Join(dir, "out.o"), src)
   636  	out, err := cmd.CombinedOutput()
   637  	if err != nil {
   638  		t.Fatalf("fail to run go tool compile: %v", err)
   639  	}
   640  
   641  	if strings.Contains(string(out), "unknown line number") {
   642  		t.Errorf("line number missing in assembly:\n%s", out)
   643  	}
   644  }
   645  
   646  var issue16214src = `
   647  package main
   648  
   649  func Mod32(x uint32) uint32 {
   650  	return x % 3 // frontend rewrites it as HMUL with 2863311531, the LITERAL node has unknown Pos
   651  }
   652  `