summaryrefslogtreecommitdiffstats
path: root/src/cmd/compile/internal/amd64/versions_test.go
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:25:22 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:25:22 +0000
commitf6ad4dcef54c5ce997a4bad5a6d86de229015700 (patch)
tree7cfa4e31ace5c2bd95c72b154d15af494b2bcbef /src/cmd/compile/internal/amd64/versions_test.go
parentInitial commit. (diff)
downloadgolang-1.22-f6ad4dcef54c5ce997a4bad5a6d86de229015700.tar.xz
golang-1.22-f6ad4dcef54c5ce997a4bad5a6d86de229015700.zip
Adding upstream version 1.22.1.upstream/1.22.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/cmd/compile/internal/amd64/versions_test.go')
-rw-r--r--src/cmd/compile/internal/amd64/versions_test.go433
1 files changed, 433 insertions, 0 deletions
diff --git a/src/cmd/compile/internal/amd64/versions_test.go b/src/cmd/compile/internal/amd64/versions_test.go
new file mode 100644
index 0000000..fc0046a
--- /dev/null
+++ b/src/cmd/compile/internal/amd64/versions_test.go
@@ -0,0 +1,433 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// When using GOEXPERIMENT=boringcrypto, the test program links in the boringcrypto syso,
+// which does not respect GOAMD64, so we skip the test if boringcrypto is enabled.
+//go:build !boringcrypto
+
+package amd64_test
+
+import (
+ "bufio"
+ "debug/elf"
+ "debug/macho"
+ "errors"
+ "fmt"
+ "go/build"
+ "internal/testenv"
+ "io"
+ "math"
+ "math/bits"
+ "os"
+ "os/exec"
+ "regexp"
+ "runtime"
+ "strconv"
+ "strings"
+ "testing"
+)
+
+// Test to make sure that when building for GOAMD64=v1, we don't
+// use any >v1 instructions.
+func TestGoAMD64v1(t *testing.T) {
+ if runtime.GOARCH != "amd64" {
+ t.Skip("amd64-only test")
+ }
+ if runtime.GOOS != "linux" && runtime.GOOS != "darwin" {
+ t.Skip("test only works on elf or macho platforms")
+ }
+ for _, tag := range build.Default.ToolTags {
+ if tag == "amd64.v2" {
+ t.Skip("compiling for GOAMD64=v2 or higher")
+ }
+ }
+ if os.Getenv("TESTGOAMD64V1") != "" {
+ t.Skip("recursive call")
+ }
+
+ // Make a binary which will be a modified version of the
+ // currently running binary.
+ dst, err := os.CreateTemp("", "TestGoAMD64v1")
+ if err != nil {
+ t.Fatalf("failed to create temp file: %v", err)
+ }
+ defer os.Remove(dst.Name())
+ dst.Chmod(0500) // make executable
+
+ // Clobber all the non-v1 opcodes.
+ opcodes := map[string]bool{}
+ var features []string
+ for feature, opcodeList := range featureToOpcodes {
+ if runtimeFeatures[feature] {
+ features = append(features, fmt.Sprintf("cpu.%s=off", feature))
+ }
+ for _, op := range opcodeList {
+ opcodes[op] = true
+ }
+ }
+ clobber(t, os.Args[0], dst, opcodes)
+ if err = dst.Close(); err != nil {
+ t.Fatalf("can't close binary: %v", err)
+ }
+
+ // Run the resulting binary.
+ cmd := testenv.Command(t, dst.Name())
+ testenv.CleanCmdEnv(cmd)
+ cmd.Env = append(cmd.Env, "TESTGOAMD64V1=yes")
+ cmd.Env = append(cmd.Env, fmt.Sprintf("GODEBUG=%s", strings.Join(features, ",")))
+ out, err := cmd.CombinedOutput()
+ if err != nil {
+ t.Fatalf("couldn't execute test: %s", err)
+ }
+ // Expect to see output of the form "PASS\n", unless the test binary
+ // was compiled for coverage (in which case there will be an extra line).
+ success := false
+ lines := strings.Split(string(out), "\n")
+ if len(lines) == 2 {
+ success = lines[0] == "PASS" && lines[1] == ""
+ } else if len(lines) == 3 {
+ success = lines[0] == "PASS" &&
+ strings.HasPrefix(lines[1], "coverage") && lines[2] == ""
+ }
+ if !success {
+ t.Fatalf("test reported error: %s lines=%+v", string(out), lines)
+ }
+}
+
+// Clobber copies the binary src to dst, replacing all the instructions in opcodes with
+// faulting instructions.
+func clobber(t *testing.T, src string, dst *os.File, opcodes map[string]bool) {
+ // Run objdump to get disassembly.
+ var re *regexp.Regexp
+ var disasm io.Reader
+ if false {
+ // TODO: go tool objdump doesn't disassemble the bmi1 instructions
+ // in question correctly. See issue 48584.
+ cmd := testenv.Command(t, "go", "tool", "objdump", src)
+ var err error
+ disasm, err = cmd.StdoutPipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := cmd.Start(); err != nil {
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := cmd.Wait(); err != nil {
+ t.Error(err)
+ }
+ })
+ re = regexp.MustCompile(`^[^:]*:[-\d]+\s+0x([\da-f]+)\s+([\da-f]+)\s+([A-Z]+)`)
+ } else {
+ // TODO: we're depending on platform-native objdump here. Hence the Skipf
+ // below if it doesn't run for some reason.
+ cmd := testenv.Command(t, "objdump", "-d", src)
+ var err error
+ disasm, err = cmd.StdoutPipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ if err := cmd.Start(); err != nil {
+ if errors.Is(err, exec.ErrNotFound) {
+ t.Skipf("can't run test due to missing objdump: %s", err)
+ }
+ t.Fatal(err)
+ }
+ t.Cleanup(func() {
+ if err := cmd.Wait(); err != nil {
+ t.Error(err)
+ }
+ })
+ re = regexp.MustCompile(`^\s*([\da-f]+):\s*((?:[\da-f][\da-f] )+)\s*([a-z\d]+)`)
+ }
+
+ // Find all the instruction addresses we need to edit.
+ virtualEdits := map[uint64]bool{}
+ scanner := bufio.NewScanner(disasm)
+ for scanner.Scan() {
+ line := scanner.Text()
+ parts := re.FindStringSubmatch(line)
+ if len(parts) == 0 {
+ continue
+ }
+ addr, err := strconv.ParseUint(parts[1], 16, 64)
+ if err != nil {
+ continue // not a hex address
+ }
+ opcode := strings.ToLower(parts[3])
+ if !opcodes[opcode] {
+ continue
+ }
+ t.Logf("clobbering instruction %s", line)
+ n := (len(parts[2]) - strings.Count(parts[2], " ")) / 2 // number of bytes in instruction encoding
+ for i := 0; i < n; i++ {
+ // Only really need to make the first byte faulting, but might
+ // as well make all the bytes faulting.
+ virtualEdits[addr+uint64(i)] = true
+ }
+ }
+
+ // Figure out where in the binary the edits must be done.
+ physicalEdits := map[uint64]bool{}
+ if e, err := elf.Open(src); err == nil {
+ for _, sec := range e.Sections {
+ vaddr := sec.Addr
+ paddr := sec.Offset
+ size := sec.Size
+ for a := range virtualEdits {
+ if a >= vaddr && a < vaddr+size {
+ physicalEdits[paddr+(a-vaddr)] = true
+ }
+ }
+ }
+ } else if m, err2 := macho.Open(src); err2 == nil {
+ for _, sec := range m.Sections {
+ vaddr := sec.Addr
+ paddr := uint64(sec.Offset)
+ size := sec.Size
+ for a := range virtualEdits {
+ if a >= vaddr && a < vaddr+size {
+ physicalEdits[paddr+(a-vaddr)] = true
+ }
+ }
+ }
+ } else {
+ t.Log(err)
+ t.Log(err2)
+ t.Fatal("executable format not elf or macho")
+ }
+ if len(virtualEdits) != len(physicalEdits) {
+ t.Fatal("couldn't find an instruction in text sections")
+ }
+
+ // Copy source to destination, making edits along the way.
+ f, err := os.Open(src)
+ if err != nil {
+ t.Fatal(err)
+ }
+ r := bufio.NewReader(f)
+ w := bufio.NewWriter(dst)
+ a := uint64(0)
+ done := 0
+ for {
+ b, err := r.ReadByte()
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ t.Fatal("can't read")
+ }
+ if physicalEdits[a] {
+ b = 0xcc // INT3 opcode
+ done++
+ }
+ err = w.WriteByte(b)
+ if err != nil {
+ t.Fatal("can't write")
+ }
+ a++
+ }
+ if done != len(physicalEdits) {
+ t.Fatal("physical edits remaining")
+ }
+ w.Flush()
+ f.Close()
+}
+
+func setOf(keys ...string) map[string]bool {
+ m := make(map[string]bool, len(keys))
+ for _, key := range keys {
+ m[key] = true
+ }
+ return m
+}
+
+var runtimeFeatures = setOf(
+ "adx", "aes", "avx", "avx2", "bmi1", "bmi2", "erms", "fma",
+ "pclmulqdq", "popcnt", "rdtscp", "sse3", "sse41", "sse42", "ssse3",
+)
+
+var featureToOpcodes = map[string][]string{
+ // Note: we include *q, *l, and plain opcodes here.
+ // go tool objdump doesn't include a [QL] on popcnt instructions, until CL 351889
+ // native objdump doesn't include [QL] on linux.
+ "popcnt": {"popcntq", "popcntl", "popcnt"},
+ "bmi1": {
+ "andnq", "andnl", "andn",
+ "blsiq", "blsil", "blsi",
+ "blsmskq", "blsmskl", "blsmsk",
+ "blsrq", "blsrl", "blsr",
+ "tzcntq", "tzcntl", "tzcnt",
+ },
+ "bmi2": {
+ "sarxq", "sarxl", "sarx",
+ "shlxq", "shlxl", "shlx",
+ "shrxq", "shrxl", "shrx",
+ },
+ "sse41": {
+ "roundsd",
+ "pinsrq", "pinsrl", "pinsrd", "pinsrb", "pinsr",
+ "pextrq", "pextrl", "pextrd", "pextrb", "pextr",
+ "pminsb", "pminsd", "pminuw", "pminud", // Note: ub and sw are ok.
+ "pmaxsb", "pmaxsd", "pmaxuw", "pmaxud",
+ "pmovzxbw", "pmovzxbd", "pmovzxbq", "pmovzxwd", "pmovzxwq", "pmovzxdq",
+ "pmovsxbw", "pmovsxbd", "pmovsxbq", "pmovsxwd", "pmovsxwq", "pmovsxdq",
+ "pblendvb",
+ },
+ "fma": {"vfmadd231sd"},
+ "movbe": {"movbeqq", "movbeq", "movbell", "movbel", "movbe"},
+ "lzcnt": {"lzcntq", "lzcntl", "lzcnt"},
+}
+
+// Test to use POPCNT instruction, if available
+func TestPopCnt(t *testing.T) {
+ for _, tt := range []struct {
+ x uint64
+ want int
+ }{
+ {0b00001111, 4},
+ {0b00001110, 3},
+ {0b00001100, 2},
+ {0b00000000, 0},
+ } {
+ if got := bits.OnesCount64(tt.x); got != tt.want {
+ t.Errorf("OnesCount64(%#x) = %d, want %d", tt.x, got, tt.want)
+ }
+ if got := bits.OnesCount32(uint32(tt.x)); got != tt.want {
+ t.Errorf("OnesCount32(%#x) = %d, want %d", tt.x, got, tt.want)
+ }
+ }
+}
+
+// Test to use ANDN, if available
+func TestAndNot(t *testing.T) {
+ for _, tt := range []struct {
+ x, y, want uint64
+ }{
+ {0b00001111, 0b00000011, 0b1100},
+ {0b00001111, 0b00001100, 0b0011},
+ {0b00000000, 0b00000000, 0b0000},
+ } {
+ if got := tt.x &^ tt.y; got != tt.want {
+ t.Errorf("%#x &^ %#x = %#x, want %#x", tt.x, tt.y, got, tt.want)
+ }
+ if got := uint32(tt.x) &^ uint32(tt.y); got != uint32(tt.want) {
+ t.Errorf("%#x &^ %#x = %#x, want %#x", tt.x, tt.y, got, tt.want)
+ }
+ }
+}
+
+// Test to use BLSI, if available
+func TestBLSI(t *testing.T) {
+ for _, tt := range []struct {
+ x, want uint64
+ }{
+ {0b00001111, 0b001},
+ {0b00001110, 0b010},
+ {0b00001100, 0b100},
+ {0b11000110, 0b010},
+ {0b00000000, 0b000},
+ } {
+ if got := tt.x & -tt.x; got != tt.want {
+ t.Errorf("%#x & (-%#x) = %#x, want %#x", tt.x, tt.x, got, tt.want)
+ }
+ if got := uint32(tt.x) & -uint32(tt.x); got != uint32(tt.want) {
+ t.Errorf("%#x & (-%#x) = %#x, want %#x", tt.x, tt.x, got, tt.want)
+ }
+ }
+}
+
+// Test to use BLSMSK, if available
+func TestBLSMSK(t *testing.T) {
+ for _, tt := range []struct {
+ x, want uint64
+ }{
+ {0b00001111, 0b001},
+ {0b00001110, 0b011},
+ {0b00001100, 0b111},
+ {0b11000110, 0b011},
+ {0b00000000, 1<<64 - 1},
+ } {
+ if got := tt.x ^ (tt.x - 1); got != tt.want {
+ t.Errorf("%#x ^ (%#x-1) = %#x, want %#x", tt.x, tt.x, got, tt.want)
+ }
+ if got := uint32(tt.x) ^ (uint32(tt.x) - 1); got != uint32(tt.want) {
+ t.Errorf("%#x ^ (%#x-1) = %#x, want %#x", tt.x, tt.x, got, uint32(tt.want))
+ }
+ }
+}
+
+// Test to use BLSR, if available
+func TestBLSR(t *testing.T) {
+ for _, tt := range []struct {
+ x, want uint64
+ }{
+ {0b00001111, 0b00001110},
+ {0b00001110, 0b00001100},
+ {0b00001100, 0b00001000},
+ {0b11000110, 0b11000100},
+ {0b00000000, 0b00000000},
+ } {
+ if got := tt.x & (tt.x - 1); got != tt.want {
+ t.Errorf("%#x & (%#x-1) = %#x, want %#x", tt.x, tt.x, got, tt.want)
+ }
+ if got := uint32(tt.x) & (uint32(tt.x) - 1); got != uint32(tt.want) {
+ t.Errorf("%#x & (%#x-1) = %#x, want %#x", tt.x, tt.x, got, tt.want)
+ }
+ }
+}
+
+func TestTrailingZeros(t *testing.T) {
+ for _, tt := range []struct {
+ x uint64
+ want int
+ }{
+ {0b00001111, 0},
+ {0b00001110, 1},
+ {0b00001100, 2},
+ {0b00001000, 3},
+ {0b00000000, 64},
+ } {
+ if got := bits.TrailingZeros64(tt.x); got != tt.want {
+ t.Errorf("TrailingZeros64(%#x) = %d, want %d", tt.x, got, tt.want)
+ }
+ want := tt.want
+ if want == 64 {
+ want = 32
+ }
+ if got := bits.TrailingZeros32(uint32(tt.x)); got != want {
+ t.Errorf("TrailingZeros64(%#x) = %d, want %d", tt.x, got, want)
+ }
+ }
+}
+
+func TestRound(t *testing.T) {
+ for _, tt := range []struct {
+ x, want float64
+ }{
+ {1.4, 1},
+ {1.5, 2},
+ {1.6, 2},
+ {2.4, 2},
+ {2.5, 2},
+ {2.6, 3},
+ } {
+ if got := math.RoundToEven(tt.x); got != tt.want {
+ t.Errorf("RoundToEven(%f) = %f, want %f", tt.x, got, tt.want)
+ }
+ }
+}
+
+func TestFMA(t *testing.T) {
+ for _, tt := range []struct {
+ x, y, z, want float64
+ }{
+ {2, 3, 4, 10},
+ {3, 4, 5, 17},
+ } {
+ if got := math.FMA(tt.x, tt.y, tt.z); got != tt.want {
+ t.Errorf("FMA(%f,%f,%f) = %f, want %f", tt.x, tt.y, tt.z, got, tt.want)
+ }
+ }
+}