Files
avo/internal/inst/table_test.go

309 lines
7.3 KiB
Go
Raw Normal View History

2018-11-24 14:20:04 -08:00
package inst_test
2018-11-24 14:08:55 -08:00
2018-11-24 14:20:04 -08:00
import (
"os"
2018-11-25 18:25:51 -08:00
"reflect"
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>
2021-11-12 18:35:36 -08:00
"sort"
2018-11-24 14:55:51 -08:00
"strings"
2018-11-24 14:20:04 -08:00
"testing"
"github.com/mmcloughlin/avo/internal/gen"
"github.com/mmcloughlin/avo/internal/inst"
"github.com/mmcloughlin/avo/internal/test"
2018-12-11 22:35:01 -08:00
"github.com/mmcloughlin/avo/printer"
2018-11-24 14:20:04 -08:00
)
2018-11-24 14:08:55 -08:00
func TestHaveInstructions(t *testing.T) {
2018-11-24 14:20:04 -08:00
n := len(inst.Instructions)
2018-11-24 14:08:55 -08:00
t.Logf("number of instructions = %d", n)
if n == 0 {
t.Fatalf("no instructions")
}
}
func TestOpcodeDupes(t *testing.T) {
count := map[string]int{}
2018-11-24 14:20:04 -08:00
for _, i := range inst.Instructions {
2018-11-24 14:08:55 -08:00
count[i.Opcode]++
}
for opcode, n := range count {
if n > 1 {
t.Errorf("opcode %s appears %d times", opcode, n)
}
}
}
func TestFormDupes(t *testing.T) {
for _, i := range inst.Instructions {
if HasFormDupe(i) {
t.Errorf("%s has duplicate forms", i.Opcode)
}
}
}
func HasFormDupe(i inst.Instruction) bool {
n := len(i.Forms)
for a := 0; a < n; a++ {
for b := a + 1; b < n; b++ {
if reflect.DeepEqual(i.Forms[a], i.Forms[b]) {
return true
}
}
}
return false
}
2018-11-24 14:08:55 -08:00
func TestInstructionProperties(t *testing.T) {
2018-11-24 14:20:04 -08:00
for _, i := range inst.Instructions {
2018-11-24 14:08:55 -08:00
if len(i.Opcode) == 0 {
t.Errorf("empty opcode")
}
if len(i.Forms) == 0 {
t.Errorf("instruction %s has no forms", i.Opcode)
}
if len(i.Arities()) == 0 {
t.Errorf("instruction %s has empty arities list", i.Opcode)
}
if i.IsNiladic() && len(i.Forms) != 1 {
t.Errorf("%s breaks our expectation that niladic functions have one form", i.Opcode)
}
2018-11-24 14:08:55 -08:00
}
}
2018-11-24 14:20:04 -08:00
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>
2021-11-12 18:35:36 -08:00
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)
}
}
}
}
2018-11-24 14:20:04 -08:00
func TestAssembles(t *testing.T) {
2018-12-18 22:57:26 -08:00
g := gen.NewAsmTest(printer.NewArgvConfig())
2018-11-24 14:20:04 -08:00
b, err := g.Generate(inst.Instructions)
if err != nil {
t.Fatal(err)
}
test.Assembles(t, b)
}
2018-11-24 14:55:51 -08:00
func TestLookup(t *testing.T) {
if _, found := inst.Lookup("CPUID"); !found {
t.Fatalf("missing CPUID")
}
if _, found := inst.Lookup(strings.Repeat("XXX", 13)); found {
t.Fatalf("lookup returns true on an absurd opcode")
}
}
2018-11-25 18:25:51 -08:00
func TestInstructionArities(t *testing.T) {
cases := map[string][]int{
"AESDEC": {2},
"EXTRACTPS": {3},
"SHRQ": {2, 3},
"VMOVHPD": {2, 3},
}
for opcode, expect := range cases {
i, ok := inst.Lookup(opcode)
if !ok {
t.Fatalf("could not find %s", opcode)
}
got := i.Arities()
if !reflect.DeepEqual(got, expect) {
t.Errorf("arity of %s is %v expected %v", opcode, got, expect)
}
}
}
2018-11-24 14:55:51 -08:00
func TestStdLibOpcodes(t *testing.T) {
b, err := os.ReadFile("testdata/stdlibopcodes.txt")
2018-11-24 14:55:51 -08:00
if err != nil {
t.Fatal(err)
}
opcodes := strings.Fields(string(b))
for _, opcode := range opcodes {
if _, found := inst.Lookup(opcode); !found {
t.Errorf("missing instruction %s (used in stdlib asm)", opcode)
}
}
}
func TestCancellingInputs(t *testing.T) {
// Expect all instruction forms with cancelling inputs to have two input operands of register type.
//
// Reference: https://github.com/Maratyszcza/PeachPy/blob/01d15157a973a4ae16b8046313ddab371ea582db/peachpy/x86_64/instructions.py#L136-L138
//
// assert len(input_operands) == 2, "Instruction forms with cancelling inputs must have two inputs"
// assert all(map(lambda op: isinstance(op, Register), input_operands)), \
// "Both inputs of instruction form with cancelling inputs must be registers"
//
for _, i := range inst.Instructions {
for _, f := range i.Forms {
if !f.CancellingInputs {
continue
}
if len(f.ImplicitOperands) > 0 {
t.Errorf("%s: expected no implicit operands", i.Opcode)
}
// Expect two register inputs.
n := 0
for _, op := range f.Operands {
if op.Action.Read() {
switch op.Type {
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>
2021-11-12 18:35:36 -08:00
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)
}
}
}
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>
2021-11-12 18:35:36 -08:00
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)
}
}
}
}