go/src/cmd/compile/internal/amd64/versions_test.go
Russ Cox cdcb4b6ef3 [dev.boringcrypto] cmd/compile: remove the awful boringcrypto kludge
CL 60271 introduced this “AwfulBoringCryptoKludge.”
iant approved that CL saying “As long as it stays out of master...”

Now that the rsa and ecdsa code uses boring.Cache, the
“boring unsafe.Pointer” fields are gone from the key structs, and this
code is no longer needed. So delete it.

With the kludge deleted, we are one step closer to being able to merge
dev.boringcrypto into master.

For #51940.

Change-Id: Ie549db14b0b699c306dded2a2163f18f31d45530
Reviewed-on: https://go-review.googlesource.com/c/go/+/395884
Reviewed-by: Ian Lance Taylor <iant@golang.org>
Reviewed-by: Ian Lance Taylor <iant@google.com>
2022-04-29 14:24:53 +00:00

404 lines
10 KiB
Go

// 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"
"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")
}
if v := os.Getenv("GOAMD64"); v != "" && v != "v1" {
// Test runs only on v1 (which is the default).
// TODO: use build tags from #45454 instead.
t.Skip("GOAMD64 already set")
}
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 := exec.Command(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 := exec.Command("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)
}
re = regexp.MustCompile(`^[^:]*:[-0-9]+\s+0x([0-9a-f]+)\s+([0-9a-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 := exec.Command("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)
}
re = regexp.MustCompile(`^\s*([0-9a-f]+):\s*((?:[0-9a-f][0-9a-f] )+)\s*([a-z0-9]+)`)
}
// 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"},
"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)
}
}
}