github.com/FenixAra/go@v0.0.0-20170127160404-96ea0918e670/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  
   239  // mergeEnvLists merges the two environment lists such that
   240  // variables with the same name in "in" replace those in "out".
   241  // This always returns a newly allocated slice.
   242  func mergeEnvLists(in, out []string) []string {
   243  	out = append([]string(nil), out...)
   244  NextVar:
   245  	for _, inkv := range in {
   246  		k := strings.SplitAfterN(inkv, "=", 2)[0]
   247  		for i, outkv := range out {
   248  			if strings.HasPrefix(outkv, k) {
   249  				out[i] = inkv
   250  				continue NextVar
   251  			}
   252  		}
   253  		out = append(out, inkv)
   254  	}
   255  	return out
   256  }
   257  
   258  // TestLineNumber checks to make sure the generated assembly has line numbers
   259  // see issue #16214
   260  func TestLineNumber(t *testing.T) {
   261  	testenv.MustHaveGoBuild(t)
   262  	dir, err := ioutil.TempDir("", "TestLineNumber")
   263  	if err != nil {
   264  		t.Fatalf("could not create directory: %v", err)
   265  	}
   266  	defer os.RemoveAll(dir)
   267  
   268  	src := filepath.Join(dir, "x.go")
   269  	err = ioutil.WriteFile(src, []byte(issue16214src), 0644)
   270  	if err != nil {
   271  		t.Fatalf("could not write file: %v", err)
   272  	}
   273  
   274  	cmd := exec.Command(testenv.GoToolPath(t), "tool", "compile", "-S", "-o", filepath.Join(dir, "out.o"), src)
   275  	out, err := cmd.CombinedOutput()
   276  	if err != nil {
   277  		t.Fatalf("fail to run go tool compile: %v", err)
   278  	}
   279  
   280  	if strings.Contains(string(out), "unknown line number") {
   281  		t.Errorf("line number missing in assembly:\n%s", out)
   282  	}
   283  }
   284  
   285  var issue16214src = `
   286  package main
   287  
   288  func Mod32(x uint32) uint32 {
   289  	return x % 3 // frontend rewrites it as HMUL with 2863311531, the LITERAL node has Lineno 0
   290  }
   291  `