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

View as plain text