all: AVX-512 (#217)
Extends avo to support most AVX-512 instruction sets.
The instruction type is extended to support suffixes. The K family of opmask
registers is added to the register package, and the operand package is updated
to support the new operand types. Move instruction deduction in `Load` and
`Store` is extended to support KMOV* and VMOV* forms.
Internal code generation packages were overhauled. Instruction database loading
required various messy changes to account for the additional complexities of the
AVX-512 instruction sets. The internal/api package was added to introduce a
separation between instruction forms in the database, and the functions avo
provides to create them. This was required since with instruction suffixes there
is no longer a one-to-one mapping between instruction constructors and opcodes.
AVX-512 bloated generated source code size substantially, initially increasing
compilation and CI test times to an unacceptable level. Two changes were made to
address this:
1. Instruction constructors in the `x86` package moved to an optab-based
approach. This compiles substantially faster than the verbose code
generation we had before.
2. The most verbose code-generated tests are moved under build tags and
limited to a stress test mode. Stress test builds are run on
schedule but not in regular CI.
An example of AVX-512 accelerated 16-lane MD5 is provided to demonstrate and
test the new functionality.
Updates #20 #163 #229
Co-authored-by: Vaughn Iverson <vsivsi@yahoo.com>
This commit is contained in:
136
examples/md5x16/md5x16_test.go
Normal file
136
examples/md5x16/md5x16_test.go
Normal file
@@ -0,0 +1,136 @@
|
||||
package md5x16
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
|
||||
"golang.org/x/sys/cpu"
|
||||
)
|
||||
|
||||
func RequireISA(t *testing.T) {
|
||||
t.Helper()
|
||||
if !cpu.X86.HasAVX512F {
|
||||
t.Skip("requires AVX512F instruction set")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVectors(t *testing.T) {
|
||||
RequireISA(t)
|
||||
|
||||
cases := []struct {
|
||||
Data string
|
||||
HexDigest string
|
||||
}{
|
||||
{"", "d41d8cd98f00b204e9800998ecf8427e"},
|
||||
{"The quick brown fox jumps over the lazy dog", "9e107d9d372bb6826bd81d3542a419d6"},
|
||||
{"The quick brown fox jumps over the lazy dog.", "e4d909c290d0fb1ca068ffaddf22cbd0"},
|
||||
}
|
||||
for _, c := range cases {
|
||||
digest := Single(t, []byte(c.Data))
|
||||
got := hex.EncodeToString(digest[:])
|
||||
if got != c.HexDigest {
|
||||
t.Errorf("Sum(%#v) = %s; expect %s", c.Data, got, c.HexDigest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestCmp(t *testing.T) {
|
||||
RequireISA(t)
|
||||
|
||||
sum := func(data []byte) [Size]byte { return Single(t, data) }
|
||||
if err := quick.CheckEqual(sum, md5.Sum, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLengths(t *testing.T) {
|
||||
RequireISA(t)
|
||||
|
||||
const max = BlockSize << 6
|
||||
data := make([]byte, max)
|
||||
rand.Read(data)
|
||||
|
||||
for n := 0; n <= max; n++ {
|
||||
got := Single(t, data[:n])
|
||||
expect := md5.Sum(data[:n])
|
||||
if got != expect {
|
||||
t.Fatalf("failed on length %d", n)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Single hashes a single data buffer in all 16 lanes and returns the result,
|
||||
// after asserting that all lanes are the same.
|
||||
func Single(t *testing.T, d []byte) [Size]byte {
|
||||
// Place the same data in every lane.
|
||||
var data [Lanes][]byte
|
||||
for l := range data {
|
||||
data[l] = d
|
||||
}
|
||||
|
||||
if err := Validate(data); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Hash and check the lanes are the same.
|
||||
digest := Sum(data)
|
||||
for l := range data {
|
||||
if digest[0] != digest[l] {
|
||||
t.Logf("lane %02d: %x", 0, digest[0])
|
||||
t.Logf("lane %02d: %x", l, digest[l])
|
||||
t.Fatal("lane mismatch")
|
||||
}
|
||||
}
|
||||
|
||||
return digest[0]
|
||||
}
|
||||
|
||||
func TestActiveLanes(t *testing.T) {
|
||||
RequireISA(t)
|
||||
|
||||
const trials = 1 << 10
|
||||
const maxlen = BlockSize << 6
|
||||
for trial := 0; trial < trials; trial++ {
|
||||
// Pick active lanes.
|
||||
lanes := 1 + rand.Intn(Lanes-1)
|
||||
active := rand.Perm(Lanes)[:lanes]
|
||||
|
||||
// Fill active lanes with random data.
|
||||
n := rand.Intn(maxlen)
|
||||
buffer := make([]byte, lanes*n)
|
||||
rand.Read(buffer)
|
||||
|
||||
var data [Lanes][]byte
|
||||
for i, l := range active {
|
||||
data[l] = buffer[i*n : (i+1)*n]
|
||||
}
|
||||
|
||||
// Hash.
|
||||
digest := Sum(data)
|
||||
|
||||
// Verify correct result in active lanes.
|
||||
for _, l := range active {
|
||||
expect := md5.Sum(data[l])
|
||||
if digest[l] != expect {
|
||||
t.Fatalf("lane %02d: mismatch", l)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify other lanes are zero.
|
||||
isactive := map[int]bool{}
|
||||
for _, l := range active {
|
||||
isactive[l] = true
|
||||
}
|
||||
for l := 0; l < Lanes; l++ {
|
||||
if !isactive[l] {
|
||||
var zero [Size]byte
|
||||
if digest[l] != zero {
|
||||
t.Fatalf("inactive lane %d is non-zero", l)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user