github.com/dannin/go@v0.0.0-20161031215817-d35dfd405eaa/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  	testenv.MustHaveGoBuild(t)
    25  	if runtime.GOOS == "windows" {
    26  		// TODO: remove if we can get "go tool compile -S" to work on windows.
    27  		t.Skipf("skipping test: recursive windows compile not working")
    28  	}
    29  	dir, err := ioutil.TempDir("", "TestAssembly")
    30  	if err != nil {
    31  		t.Fatalf("could not create directory: %v", err)
    32  	}
    33  	defer os.RemoveAll(dir)
    34  
    35  	for _, test := range asmTests {
    36  		asm := compileToAsm(t, dir, test.arch, test.os, fmt.Sprintf(template, test.function))
    37  		// Get rid of code for "".init. Also gets rid of type algorithms & other junk.
    38  		if i := strings.Index(asm, "\n\"\".init "); i >= 0 {
    39  			asm = asm[:i+1]
    40  		}
    41  		for _, r := range test.regexps {
    42  			if b, err := regexp.MatchString(r, asm); !b || err != nil {
    43  				t.Errorf("expected:%s\ngo:%s\nasm:%s\n", r, test.function, asm)
    44  			}
    45  		}
    46  	}
    47  }
    48  
    49  // compile compiles the package pkg for architecture arch and
    50  // returns the generated assembly.  dir is a scratch directory.
    51  func compileToAsm(t *testing.T, dir, goarch, goos, pkg string) string {
    52  	// Create source.
    53  	src := filepath.Join(dir, "test.go")
    54  	f, err := os.Create(src)
    55  	if err != nil {
    56  		panic(err)
    57  	}
    58  	f.Write([]byte(pkg))
    59  	f.Close()
    60  
    61  	// First, install any dependencies we need.  This builds the required export data
    62  	// for any packages that are imported.
    63  	// TODO: extract dependencies automatically?
    64  	var stdout, stderr bytes.Buffer
    65  	cmd := exec.Command(testenv.GoToolPath(t), "build", "-o", filepath.Join(dir, "encoding/binary.a"), "encoding/binary")
    66  	cmd.Env = mergeEnvLists([]string{"GOARCH=" + goarch, "GOOS=" + goos}, os.Environ())
    67  	cmd.Stdout = &stdout
    68  	cmd.Stderr = &stderr
    69  	if err := cmd.Run(); err != nil {
    70  		panic(err)
    71  	}
    72  	if s := stdout.String(); s != "" {
    73  		panic(fmt.Errorf("Stdout = %s\nWant empty", s))
    74  	}
    75  	if s := stderr.String(); s != "" {
    76  		panic(fmt.Errorf("Stderr = %s\nWant empty", s))
    77  	}
    78  
    79  	// Now, compile the individual file for which we want to see the generated assembly.
    80  	cmd = exec.Command(testenv.GoToolPath(t), "tool", "compile", "-I", dir, "-S", "-o", filepath.Join(dir, "out.o"), src)
    81  	cmd.Env = mergeEnvLists([]string{"GOARCH=" + goarch, "GOOS=" + goos}, os.Environ())
    82  	cmd.Stdout = &stdout
    83  	cmd.Stderr = &stderr
    84  	if err := cmd.Run(); err != nil {
    85  		panic(err)
    86  	}
    87  	if s := stderr.String(); s != "" {
    88  		panic(fmt.Errorf("Stderr = %s\nWant empty", s))
    89  	}
    90  	return stdout.String()
    91  }
    92  
    93  // template to convert a function to a full file
    94  const template = `
    95  package main
    96  %s
    97  `
    98  
    99  type asmTest struct {
   100  	// architecture to compile to
   101  	arch string
   102  	// os to compile to
   103  	os string
   104  	// function to compile
   105  	function string
   106  	// regexps that must match the generated assembly
   107  	regexps []string
   108  }
   109  
   110  var asmTests = [...]asmTest{
   111  	{"amd64", "linux", `
   112  func f(x int) int {
   113  	return x * 64
   114  }
   115  `,
   116  		[]string{"\tSHLQ\t\\$6,"},
   117  	},
   118  	{"amd64", "linux", `
   119  func f(x int) int {
   120  	return x * 96
   121  }`,
   122  		[]string{"\tSHLQ\t\\$5,", "\tLEAQ\t\\(.*\\)\\(.*\\*2\\),"},
   123  	},
   124  	// Load-combining tests.
   125  	{"amd64", "linux", `
   126  import "encoding/binary"
   127  func f(b []byte) uint64 {
   128  	return binary.LittleEndian.Uint64(b)
   129  }
   130  `,
   131  		[]string{"\tMOVQ\t\\(.*\\),"},
   132  	},
   133  	{"amd64", "linux", `
   134  import "encoding/binary"
   135  func f(b []byte, i int) uint64 {
   136  	return binary.LittleEndian.Uint64(b[i:])
   137  }
   138  `,
   139  		[]string{"\tMOVQ\t\\(.*\\)\\(.*\\*1\\),"},
   140  	},
   141  	{"amd64", "linux", `
   142  import "encoding/binary"
   143  func f(b []byte) uint32 {
   144  	return binary.LittleEndian.Uint32(b)
   145  }
   146  `,
   147  		[]string{"\tMOVL\t\\(.*\\),"},
   148  	},
   149  	{"amd64", "linux", `
   150  import "encoding/binary"
   151  func f(b []byte, i int) uint32 {
   152  	return binary.LittleEndian.Uint32(b[i:])
   153  }
   154  `,
   155  		[]string{"\tMOVL\t\\(.*\\)\\(.*\\*1\\),"},
   156  	},
   157  	{"386", "linux", `
   158  import "encoding/binary"
   159  func f(b []byte) uint32 {
   160  	return binary.LittleEndian.Uint32(b)
   161  }
   162  `,
   163  		[]string{"\tMOVL\t\\(.*\\),"},
   164  	},
   165  	{"386", "linux", `
   166  import "encoding/binary"
   167  func f(b []byte, i int) uint32 {
   168  	return binary.LittleEndian.Uint32(b[i:])
   169  }
   170  `,
   171  		[]string{"\tMOVL\t\\(.*\\)\\(.*\\*1\\),"},
   172  	},
   173  }
   174  
   175  // mergeEnvLists merges the two environment lists such that
   176  // variables with the same name in "in" replace those in "out".
   177  // This always returns a newly allocated slice.
   178  func mergeEnvLists(in, out []string) []string {
   179  	out = append([]string(nil), out...)
   180  NextVar:
   181  	for _, inkv := range in {
   182  		k := strings.SplitAfterN(inkv, "=", 2)[0]
   183  		for i, outkv := range out {
   184  			if strings.HasPrefix(outkv, k) {
   185  				out[i] = inkv
   186  				continue NextVar
   187  			}
   188  		}
   189  		out = append(out, inkv)
   190  	}
   191  	return out
   192  }
   193  
   194  // TestLineNumber checks to make sure the generated assembly has line numbers
   195  // see issue #16214
   196  func TestLineNumber(t *testing.T) {
   197  	testenv.MustHaveGoBuild(t)
   198  	dir, err := ioutil.TempDir("", "TestLineNumber")
   199  	if err != nil {
   200  		t.Fatalf("could not create directory: %v", err)
   201  	}
   202  	defer os.RemoveAll(dir)
   203  
   204  	src := filepath.Join(dir, "x.go")
   205  	err = ioutil.WriteFile(src, []byte(issue16214src), 0644)
   206  	if err != nil {
   207  		t.Fatalf("could not write file: %v", err)
   208  	}
   209  
   210  	cmd := exec.Command(testenv.GoToolPath(t), "tool", "compile", "-S", "-o", filepath.Join(dir, "out.o"), src)
   211  	out, err := cmd.CombinedOutput()
   212  	if err != nil {
   213  		t.Fatalf("fail to run go tool compile: %v", err)
   214  	}
   215  
   216  	if strings.Contains(string(out), "unknown line number") {
   217  		t.Errorf("line number missing in assembly:\n%s", out)
   218  	}
   219  }
   220  
   221  var issue16214src = `
   222  package main
   223  
   224  func Mod32(x uint32) uint32 {
   225  	return x % 3 // frontend rewrites it as HMUL with 2863311531, the LITERAL node has Lineno 0
   226  }
   227  `