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

View as plain text