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>
137 lines
2.7 KiB
Go
137 lines
2.7 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|