Source file src/cmd/compile/internal/amd64/versions_test.go

     1  // Copyright 2021 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  // When using GOEXPERIMENT=boringcrypto, the test program links in the boringcrypto syso,
     6  // which does not respect GOAMD64, so we skip the test if boringcrypto is enabled.
     7  //go:build !boringcrypto
     8  
     9  package amd64_test
    10  
    11  import (
    12  	"bufio"
    13  	"debug/elf"
    14  	"debug/macho"
    15  	"errors"
    16  	"fmt"
    17  	"go/build"
    18  	"internal/testenv"
    19  	"io"
    20  	"math"
    21  	"math/bits"
    22  	"os"
    23  	"os/exec"
    24  	"regexp"
    25  	"runtime"
    26  	"strconv"
    27  	"strings"
    28  	"testing"
    29  )
    30  
    31  // Test to make sure that when building for GOAMD64=v1, we don't
    32  // use any >v1 instructions.
    33  func TestGoAMD64v1(t *testing.T) {
    34  	if runtime.GOARCH != "amd64" {
    35  		t.Skip("amd64-only test")
    36  	}
    37  	if runtime.GOOS != "linux" && runtime.GOOS != "darwin" {
    38  		t.Skip("test only works on elf or macho platforms")
    39  	}
    40  	for _, tag := range build.Default.ToolTags {
    41  		if tag == "amd64.v2" {
    42  			t.Skip("compiling for GOAMD64=v2 or higher")
    43  		}
    44  	}
    45  	if os.Getenv("TESTGOAMD64V1") != "" {
    46  		t.Skip("recursive call")
    47  	}
    48  
    49  	// Make a binary which will be a modified version of the
    50  	// currently running binary.
    51  	dst, err := os.CreateTemp("", "TestGoAMD64v1")
    52  	if err != nil {
    53  		t.Fatalf("failed to create temp file: %v", err)
    54  	}
    55  	defer os.Remove(dst.Name())
    56  	dst.Chmod(0500) // make executable
    57  
    58  	// Clobber all the non-v1 opcodes.
    59  	opcodes := map[string]bool{}
    60  	var features []string
    61  	for feature, opcodeList := range featureToOpcodes {
    62  		if runtimeFeatures[feature] {
    63  			features = append(features, fmt.Sprintf("cpu.%s=off", feature))
    64  		}
    65  		for _, op := range opcodeList {
    66  			opcodes[op] = true
    67  		}
    68  	}
    69  	clobber(t, os.Args[0], dst, opcodes)
    70  	if err = dst.Close(); err != nil {
    71  		t.Fatalf("can't close binary: %v", err)
    72  	}
    73  
    74  	// Run the resulting binary.
    75  	cmd := testenv.Command(t, dst.Name())
    76  	testenv.CleanCmdEnv(cmd)
    77  	cmd.Env = append(cmd.Env, "TESTGOAMD64V1=yes")
    78  	// Disable FIPS 140-3 mode, since it would detect the modified binary.
    79  	cmd.Env = append(cmd.Env, fmt.Sprintf("GODEBUG=%s,fips140=off", strings.Join(features, ",")))
    80  	out, err := cmd.CombinedOutput()
    81  	if err != nil {
    82  		t.Fatalf("couldn't execute test: %s\n%s", err, out)
    83  	}
    84  	// Expect to see output of the form "PASS\n", unless the test binary
    85  	// was compiled for coverage (in which case there will be an extra line).
    86  	success := false
    87  	lines := strings.Split(string(out), "\n")
    88  	if len(lines) == 2 {
    89  		success = lines[0] == "PASS" && lines[1] == ""
    90  	} else if len(lines) == 3 {
    91  		success = lines[0] == "PASS" &&
    92  			strings.HasPrefix(lines[1], "coverage") && lines[2] == ""
    93  	}
    94  	if !success {
    95  		t.Fatalf("test reported error: %s lines=%+v", string(out), lines)
    96  	}
    97  }
    98  
    99  // Clobber copies the binary src to dst, replacing all the instructions in opcodes with
   100  // faulting instructions.
   101  func clobber(t *testing.T, src string, dst *os.File, opcodes map[string]bool) {
   102  	// Run objdump to get disassembly.
   103  	var re *regexp.Regexp
   104  	var disasm io.Reader
   105  	if false {
   106  		// TODO: go tool objdump doesn't disassemble the bmi1 instructions
   107  		// in question correctly. See issue 48584.
   108  		cmd := testenv.Command(t, "go", "tool", "objdump", src)
   109  		var err error
   110  		disasm, err = cmd.StdoutPipe()
   111  		if err != nil {
   112  			t.Fatal(err)
   113  		}
   114  		if err := cmd.Start(); err != nil {
   115  			t.Fatal(err)
   116  		}
   117  		t.Cleanup(func() {
   118  			if err := cmd.Wait(); err != nil {
   119  				t.Error(err)
   120  			}
   121  		})
   122  		re = regexp.MustCompile(`^[^:]*:[-\d]+\s+0x([\da-f]+)\s+([\da-f]+)\s+([A-Z]+)`)
   123  	} else {
   124  		// TODO: we're depending on platform-native objdump here. Hence the Skipf
   125  		// below if it doesn't run for some reason.
   126  		cmd := testenv.Command(t, "objdump", "-d", src)
   127  		var err error
   128  		disasm, err = cmd.StdoutPipe()
   129  		if err != nil {
   130  			t.Fatal(err)
   131  		}
   132  		if err := cmd.Start(); err != nil {
   133  			if errors.Is(err, exec.ErrNotFound) {
   134  				t.Skipf("can't run test due to missing objdump: %s", err)
   135  			}
   136  			t.Fatal(err)
   137  		}
   138  		t.Cleanup(func() {
   139  			if err := cmd.Wait(); err != nil {
   140  				t.Error(err)
   141  			}
   142  		})
   143  		re = regexp.MustCompile(`^\s*([\da-f]+):\s*((?:[\da-f][\da-f] )+)\s*([a-z\d]+)`)
   144  	}
   145  
   146  	// Find all the instruction addresses we need to edit.
   147  	virtualEdits := map[uint64]bool{}
   148  	scanner := bufio.NewScanner(disasm)
   149  	for scanner.Scan() {
   150  		line := scanner.Text()
   151  		parts := re.FindStringSubmatch(line)
   152  		if len(parts) == 0 {
   153  			continue
   154  		}
   155  		addr, err := strconv.ParseUint(parts[1], 16, 64)
   156  		if err != nil {
   157  			continue // not a hex address
   158  		}
   159  		opcode := strings.ToLower(parts[3])
   160  		if !opcodes[opcode] {
   161  			continue
   162  		}
   163  		t.Logf("clobbering instruction %s", line)
   164  		n := (len(parts[2]) - strings.Count(parts[2], " ")) / 2 // number of bytes in instruction encoding
   165  		for i := 0; i < n; i++ {
   166  			// Only really need to make the first byte faulting, but might
   167  			// as well make all the bytes faulting.
   168  			virtualEdits[addr+uint64(i)] = true
   169  		}
   170  	}
   171  
   172  	// Figure out where in the binary the edits must be done.
   173  	physicalEdits := map[uint64]bool{}
   174  	if e, err := elf.Open(src); err == nil {
   175  		for _, sec := range e.Sections {
   176  			vaddr := sec.Addr
   177  			paddr := sec.Offset
   178  			size := sec.Size
   179  			for a := range virtualEdits {
   180  				if a >= vaddr && a < vaddr+size {
   181  					physicalEdits[paddr+(a-vaddr)] = true
   182  				}
   183  			}
   184  		}
   185  	} else if m, err2 := macho.Open(src); err2 == nil {
   186  		for _, sec := range m.Sections {
   187  			vaddr := sec.Addr
   188  			paddr := uint64(sec.Offset)
   189  			size := sec.Size
   190  			for a := range virtualEdits {
   191  				if a >= vaddr && a < vaddr+size {
   192  					physicalEdits[paddr+(a-vaddr)] = true
   193  				}
   194  			}
   195  		}
   196  	} else {
   197  		t.Log(err)
   198  		t.Log(err2)
   199  		t.Fatal("executable format not elf or macho")
   200  	}
   201  	if len(virtualEdits) != len(physicalEdits) {
   202  		t.Fatal("couldn't find an instruction in text sections")
   203  	}
   204  
   205  	// Copy source to destination, making edits along the way.
   206  	f, err := os.Open(src)
   207  	if err != nil {
   208  		t.Fatal(err)
   209  	}
   210  	r := bufio.NewReader(f)
   211  	w := bufio.NewWriter(dst)
   212  	a := uint64(0)
   213  	done := 0
   214  	for {
   215  		b, err := r.ReadByte()
   216  		if err == io.EOF {
   217  			break
   218  		}
   219  		if err != nil {
   220  			t.Fatal("can't read")
   221  		}
   222  		if physicalEdits[a] {
   223  			b = 0xcc // INT3 opcode
   224  			done++
   225  		}
   226  		err = w.WriteByte(b)
   227  		if err != nil {
   228  			t.Fatal("can't write")
   229  		}
   230  		a++
   231  	}
   232  	if done != len(physicalEdits) {
   233  		t.Fatal("physical edits remaining")
   234  	}
   235  	w.Flush()
   236  	f.Close()
   237  }
   238  
   239  func setOf(keys ...string) map[string]bool {
   240  	m := make(map[string]bool, len(keys))
   241  	for _, key := range keys {
   242  		m[key] = true
   243  	}
   244  	return m
   245  }
   246  
   247  var runtimeFeatures = setOf(
   248  	"adx", "aes", "avx", "avx2", "bmi1", "bmi2", "erms", "fma",
   249  	"pclmulqdq", "popcnt", "rdtscp", "sse3", "sse41", "sse42", "ssse3",
   250  )
   251  
   252  var featureToOpcodes = map[string][]string{
   253  	// Note: we include *q, *l, and plain opcodes here.
   254  	// go tool objdump doesn't include a [QL] on popcnt instructions, until CL 351889
   255  	// native objdump doesn't include [QL] on linux.
   256  	"popcnt": {"popcntq", "popcntl", "popcnt"},
   257  	"bmi1": {
   258  		"andnq", "andnl", "andn",
   259  		"blsiq", "blsil", "blsi",
   260  		"blsmskq", "blsmskl", "blsmsk",
   261  		"blsrq", "blsrl", "blsr",
   262  		"tzcntq", "tzcntl", "tzcnt",
   263  	},
   264  	"bmi2": {
   265  		"sarxq", "sarxl", "sarx",
   266  		"shlxq", "shlxl", "shlx",
   267  		"shrxq", "shrxl", "shrx",
   268  	},
   269  	"sse41": {
   270  		"roundsd",
   271  		"pinsrq", "pinsrl", "pinsrd", "pinsrb", "pinsr",
   272  		"pextrq", "pextrl", "pextrd", "pextrb", "pextr",
   273  		"pminsb", "pminsd", "pminuw", "pminud", // Note: ub and sw are ok.
   274  		"pmaxsb", "pmaxsd", "pmaxuw", "pmaxud",
   275  		"pmovzxbw", "pmovzxbd", "pmovzxbq", "pmovzxwd", "pmovzxwq", "pmovzxdq",
   276  		"pmovsxbw", "pmovsxbd", "pmovsxbq", "pmovsxwd", "pmovsxwq", "pmovsxdq",
   277  		"pblendvb",
   278  	},
   279  	"fma":   {"vfmadd231sd"},
   280  	"movbe": {"movbeqq", "movbeq", "movbell", "movbel", "movbe"},
   281  	"lzcnt": {"lzcntq", "lzcntl", "lzcnt"},
   282  }
   283  
   284  // Test to use POPCNT instruction, if available
   285  func TestPopCnt(t *testing.T) {
   286  	for _, tt := range []struct {
   287  		x    uint64
   288  		want int
   289  	}{
   290  		{0b00001111, 4},
   291  		{0b00001110, 3},
   292  		{0b00001100, 2},
   293  		{0b00000000, 0},
   294  	} {
   295  		if got := bits.OnesCount64(tt.x); got != tt.want {
   296  			t.Errorf("OnesCount64(%#x) = %d, want %d", tt.x, got, tt.want)
   297  		}
   298  		if got := bits.OnesCount32(uint32(tt.x)); got != tt.want {
   299  			t.Errorf("OnesCount32(%#x) = %d, want %d", tt.x, got, tt.want)
   300  		}
   301  	}
   302  }
   303  
   304  // Test to use ANDN, if available
   305  func TestAndNot(t *testing.T) {
   306  	for _, tt := range []struct {
   307  		x, y, want uint64
   308  	}{
   309  		{0b00001111, 0b00000011, 0b1100},
   310  		{0b00001111, 0b00001100, 0b0011},
   311  		{0b00000000, 0b00000000, 0b0000},
   312  	} {
   313  		if got := tt.x &^ tt.y; got != tt.want {
   314  			t.Errorf("%#x &^ %#x = %#x, want %#x", tt.x, tt.y, got, tt.want)
   315  		}
   316  		if got := uint32(tt.x) &^ uint32(tt.y); got != uint32(tt.want) {
   317  			t.Errorf("%#x &^ %#x = %#x, want %#x", tt.x, tt.y, got, tt.want)
   318  		}
   319  	}
   320  }
   321  
   322  // Test to use BLSI, if available
   323  func TestBLSI(t *testing.T) {
   324  	for _, tt := range []struct {
   325  		x, want uint64
   326  	}{
   327  		{0b00001111, 0b001},
   328  		{0b00001110, 0b010},
   329  		{0b00001100, 0b100},
   330  		{0b11000110, 0b010},
   331  		{0b00000000, 0b000},
   332  	} {
   333  		if got := tt.x & -tt.x; got != tt.want {
   334  			t.Errorf("%#x & (-%#x) = %#x, want %#x", tt.x, tt.x, got, tt.want)
   335  		}
   336  		if got := uint32(tt.x) & -uint32(tt.x); got != uint32(tt.want) {
   337  			t.Errorf("%#x & (-%#x) = %#x, want %#x", tt.x, tt.x, got, tt.want)
   338  		}
   339  	}
   340  }
   341  
   342  // Test to use BLSMSK, if available
   343  func TestBLSMSK(t *testing.T) {
   344  	for _, tt := range []struct {
   345  		x, want uint64
   346  	}{
   347  		{0b00001111, 0b001},
   348  		{0b00001110, 0b011},
   349  		{0b00001100, 0b111},
   350  		{0b11000110, 0b011},
   351  		{0b00000000, 1<<64 - 1},
   352  	} {
   353  		if got := tt.x ^ (tt.x - 1); got != tt.want {
   354  			t.Errorf("%#x ^ (%#x-1) = %#x, want %#x", tt.x, tt.x, got, tt.want)
   355  		}
   356  		if got := uint32(tt.x) ^ (uint32(tt.x) - 1); got != uint32(tt.want) {
   357  			t.Errorf("%#x ^ (%#x-1) = %#x, want %#x", tt.x, tt.x, got, uint32(tt.want))
   358  		}
   359  	}
   360  }
   361  
   362  // Test to use BLSR, if available
   363  func TestBLSR(t *testing.T) {
   364  	for _, tt := range []struct {
   365  		x, want uint64
   366  	}{
   367  		{0b00001111, 0b00001110},
   368  		{0b00001110, 0b00001100},
   369  		{0b00001100, 0b00001000},
   370  		{0b11000110, 0b11000100},
   371  		{0b00000000, 0b00000000},
   372  	} {
   373  		if got := tt.x & (tt.x - 1); got != tt.want {
   374  			t.Errorf("%#x & (%#x-1) = %#x, want %#x", tt.x, tt.x, got, tt.want)
   375  		}
   376  		if got := uint32(tt.x) & (uint32(tt.x) - 1); got != uint32(tt.want) {
   377  			t.Errorf("%#x & (%#x-1) = %#x, want %#x", tt.x, tt.x, got, tt.want)
   378  		}
   379  	}
   380  }
   381  
   382  func TestTrailingZeros(t *testing.T) {
   383  	for _, tt := range []struct {
   384  		x    uint64
   385  		want int
   386  	}{
   387  		{0b00001111, 0},
   388  		{0b00001110, 1},
   389  		{0b00001100, 2},
   390  		{0b00001000, 3},
   391  		{0b00000000, 64},
   392  	} {
   393  		if got := bits.TrailingZeros64(tt.x); got != tt.want {
   394  			t.Errorf("TrailingZeros64(%#x) = %d, want %d", tt.x, got, tt.want)
   395  		}
   396  		want := tt.want
   397  		if want == 64 {
   398  			want = 32
   399  		}
   400  		if got := bits.TrailingZeros32(uint32(tt.x)); got != want {
   401  			t.Errorf("TrailingZeros64(%#x) = %d, want %d", tt.x, got, want)
   402  		}
   403  	}
   404  }
   405  
   406  func TestRound(t *testing.T) {
   407  	for _, tt := range []struct {
   408  		x, want float64
   409  	}{
   410  		{1.4, 1},
   411  		{1.5, 2},
   412  		{1.6, 2},
   413  		{2.4, 2},
   414  		{2.5, 2},
   415  		{2.6, 3},
   416  	} {
   417  		if got := math.RoundToEven(tt.x); got != tt.want {
   418  			t.Errorf("RoundToEven(%f) = %f, want %f", tt.x, got, tt.want)
   419  		}
   420  	}
   421  }
   422  
   423  func TestFMA(t *testing.T) {
   424  	for _, tt := range []struct {
   425  		x, y, z, want float64
   426  	}{
   427  		{2, 3, 4, 10},
   428  		{3, 4, 5, 17},
   429  	} {
   430  		if got := math.FMA(tt.x, tt.y, tt.z); got != tt.want {
   431  			t.Errorf("FMA(%f,%f,%f) = %f, want %f", tt.x, tt.y, tt.z, got, tt.want)
   432  		}
   433  	}
   434  }
   435  

View as plain text