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:
Michael McLoughlin
2021-11-12 18:35:36 -08:00
parent 2867bd7e01
commit b76e849b5c
71 changed files with 257395 additions and 61474 deletions

View File

@@ -3,6 +3,7 @@ package inst_test
import (
"io/ioutil"
"reflect"
"sort"
"strings"
"testing"
@@ -70,6 +71,84 @@ func TestInstructionProperties(t *testing.T) {
}
}
func TestHaveSuffixes(t *testing.T) {
for _, i := range inst.Instructions {
for _, f := range i.Forms {
if len(f.SupportedSuffixes()) == 0 {
t.Errorf("%s: no supported suffixes", i.Opcode)
}
}
}
}
func TestAcceptsSuffixes(t *testing.T) {
// Verify consistency between the AcceptsSuffixes and SupportedSuffixes methods.
for _, i := range inst.Instructions {
for _, f := range i.Forms {
expect := false
for _, suffixes := range f.SupportedSuffixes() {
if len(suffixes) > 0 {
expect = true
}
}
if got := f.AcceptsSuffixes(); got != expect {
t.Errorf("%s: AcceptsSuffixes() = %v; expect %v", i.Opcode, got, expect)
}
}
}
}
func TestSuffixesClasses(t *testing.T) {
// Verify that all instructions in a suffix class support the same suffixes.
reps := map[string][]inst.Suffixes{}
for _, i := range inst.Instructions {
for _, f := range i.Forms {
class := f.SuffixesClass()
expect, ok := reps[class]
if !ok {
t.Logf("new class %q: representative from instruction %s", class, i.Opcode)
reps[class] = f.SupportedSuffixes()
continue
}
got := f.SupportedSuffixes()
if !reflect.DeepEqual(expect, got) {
t.Fatalf("suffixes mismatch for class %q", class)
}
}
}
}
func TestSuffixesHaveSummaries(t *testing.T) {
set := map[inst.Suffix]bool{}
for _, i := range inst.Instructions {
for _, f := range i.Forms {
for _, suffixes := range f.SupportedSuffixes() {
for _, suffix := range suffixes {
set[suffix] = true
}
}
}
}
for suffix := range set {
if suffix.Summary() == "" {
t.Errorf("suffix %q missing summary", suffix)
}
}
}
func TestISASorted(t *testing.T) {
for _, i := range inst.Instructions {
for _, f := range i.Forms {
if !sort.StringsAreSorted(f.ISA) {
t.Fatalf("%s: isa not sorted", i.Opcode)
}
}
}
}
func TestAssembles(t *testing.T) {
g := gen.NewAsmTest(printer.NewArgvConfig())
b, err := g.Generate(inst.Instructions)
@@ -143,17 +222,86 @@ func TestCancellingInputs(t *testing.T) {
n := 0
for _, op := range f.Operands {
if op.Action.Read() {
n++
switch op.Type {
case "r8", "r16", "r32", "r64", "xmm", "ymm":
// pass
case "r8", "r16", "r32", "r64", "xmm", "ymm", "zmm":
n++
case "k":
// skip mask registers
default:
t.Errorf("%s: unexpected operand type %q for self-cancelling input", i.Opcode, op.Type)
}
}
}
if n != 2 {
t.Errorf("%s: expected two inputs for self-cancelling form", i.Opcode)
if n < 2 {
t.Log(f)
t.Errorf("%s: expected at least two inputs for self-cancelling form", i.Opcode)
}
}
}
}
func TestFlagOperandTypes(t *testing.T) {
for _, i := range inst.Instructions {
for _, f := range i.Forms {
// Check for memory operands.
mem := false
for _, op := range f.Operands {
if strings.HasPrefix(op.Type, "m") {
mem = true
}
}
// Broadcast applies to memory instructions only.
if f.Broadcast && !mem {
t.Errorf("%s: expect broadcast form to have memory operand", i.Opcode)
}
// Embedded rounding must be register-only.
if f.EmbeddedRounding && mem {
t.Errorf("%s: embedded-rounding only supported for register-only forms", i.Opcode)
}
// Suppress all exceptions is register only.
if f.SuppressAllExceptions && mem {
t.Errorf("%s: embedded-rounding only supported for register-only forms", i.Opcode)
}
}
}
}
func TestFlagCombinations(t *testing.T) {
for _, i := range inst.Instructions {
for _, f := range i.Forms {
if f.EmbeddedRounding && f.SuppressAllExceptions {
t.Errorf("%s: embedded-rounding cannot be combined with suppress-all-exceptions", i.Opcode)
}
if f.Broadcast && f.EmbeddedRounding {
t.Errorf("%s: broadcast cannot be combined with embedded-rounding", i.Opcode)
}
if f.Broadcast && f.SuppressAllExceptions {
t.Errorf("%s: broadcast cannot be combined with suppress-all-exceptions", i.Opcode)
}
}
}
}
func TestZeroingHasMask(t *testing.T) {
for _, i := range inst.Instructions {
for _, f := range i.Forms {
if !f.Zeroing {
continue
}
// Expect mask operand.
mask := false
for _, op := range f.Operands {
if op.Type == "k" {
mask = true
}
}
if !mask {
t.Errorf("%s: expect mask operand if zeroing is enabled", i.Opcode)
}
}
}