Files
avo/internal/gen/asmtest.go
Michael McLoughlin b76e849b5c 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 19:02:39 -08:00

180 lines
6.2 KiB
Go

package gen
import (
"fmt"
"math"
"strconv"
"strings"
"github.com/mmcloughlin/avo/internal/inst"
"github.com/mmcloughlin/avo/internal/prnt"
"github.com/mmcloughlin/avo/printer"
)
type asmtest struct {
cfg printer.Config
sym string // reference to the test function symbol
rel8 string // label to be used for near jumps
rel32 string // label for far jumps
prnt.Generator
}
// NewAsmTest prints one massive assembly function containing a line for every
// instruction form in the database. The intention is to pass this to the Go
// assembler and confirm there are no errors, thus helping to ensure our
// database is compatible.
func NewAsmTest(cfg printer.Config) Interface {
return &asmtest{cfg: cfg}
}
func (a *asmtest) Generate(is []inst.Instruction) ([]byte, error) {
a.Printf("// %s\n\n", a.cfg.GeneratedWarning())
a.sym = "\u00b7loadertest(SB)"
a.Printf("TEXT %s, 0, $0\n", a.sym)
// Define a label for far jumps.
a.Printf("rel32:\n")
a.rel32 = "rel32"
for _, i := range is {
a.Printf("\t// %s %s\n", i.Opcode, i.Summary)
if skip, msg := a.skip(i.Opcode); skip {
a.Printf("\t// SKIP: %s\n", msg)
continue
}
if i.Opcode[0] == 'J' {
label := fmt.Sprintf("rel8_%s", strings.ToLower(i.Opcode))
a.Printf("%s:\n", label)
a.rel8 = label
}
for _, f := range i.Forms {
as, err := a.args(i.Opcode, f.Operands)
if err != nil {
return nil, fmt.Errorf("tests for %s: %w", i.Opcode, err)
}
for _, suffixes := range f.SupportedSuffixes() {
opcode := i.Opcode
if len(suffixes) > 0 {
opcode += "." + suffixes.String()
}
a.Printf("\t%s\t%s\n", opcode, strings.Join(as, ", "))
}
}
a.Printf("\n")
}
a.Printf("\tRET\n")
return a.Result()
}
func (a asmtest) skip(opcode string) (bool, string) {
prefixes := map[string]string{
"PUSH": "PUSH can produce 'unbalanced PUSH/POP' assembler error",
"POP": "POP can produce 'unbalanced PUSH/POP' assembler error",
}
for p, m := range prefixes {
if strings.HasPrefix(opcode, p) {
return true, m
}
}
return false, ""
}
func (a asmtest) args(opcode string, ops []inst.Operand) ([]string, error) {
// Special case for CALL, since it needs a different type of rel32 argument than others.
if opcode == "CALL" {
return []string{a.sym}, nil
}
as := make([]string, len(ops))
for i, op := range ops {
a := a.arg(op.Type, i)
if a == "" {
return nil, fmt.Errorf("unsupported operand type %q", op.Type)
}
as[i] = a
}
return as, nil
}
// arg generates an argument for an operand of the given type.
func (a asmtest) arg(t string, i int) string {
m := map[string]string{
"1": "$1", // <xs:enumeration value="1" />
"3": "$3", // <xs:enumeration value="3" />
"imm2u": "$3",
// <xs:enumeration value="imm4" />
"imm8": fmt.Sprintf("$%d", math.MaxInt8), // <xs:enumeration value="imm8" />
"imm16": fmt.Sprintf("$%d", math.MaxInt16), // <xs:enumeration value="imm16" />
"imm32": fmt.Sprintf("$%d", math.MaxInt32), // <xs:enumeration value="imm32" />
"imm64": fmt.Sprintf("$%d", math.MaxInt64), // <xs:enumeration value="imm64" />
"al": "AL", // <xs:enumeration value="al" />
"cl": "CL", // <xs:enumeration value="cl" />
"r8": "CH", // <xs:enumeration value="r8" />
"ax": "AX", // <xs:enumeration value="ax" />
"r16": "SI", // <xs:enumeration value="r16" />
"eax": "AX", // <xs:enumeration value="eax" />
"r32": "DX", // <xs:enumeration value="r32" />
"rax": "AX", // <xs:enumeration value="rax" />
"r64": "R15", // <xs:enumeration value="r64" />
"mm": "M5", // <xs:enumeration value="mm" />
"xmm0": "X0", // <xs:enumeration value="xmm0" />
"xmm": "X" + strconv.Itoa(7+i), // <xs:enumeration value="xmm" />
// <xs:enumeration value="xmm{k}" />
// <xs:enumeration value="xmm{k}{z}" />
"ymm": "Y" + strconv.Itoa(3+i), // <xs:enumeration value="ymm" />
// <xs:enumeration value="ymm{k}" />
// <xs:enumeration value="ymm{k}{z}" />
"zmm": "Z" + strconv.Itoa(16+i), // <xs:enumeration value="zmm" />
// <xs:enumeration value="zmm{k}" />
// <xs:enumeration value="zmm{k}{z}" />
"k": "K" + strconv.Itoa(1+i), // <xs:enumeration value="k" />
// <xs:enumeration value="k{k}" />
// <xs:enumeration value="moffs32" />
// <xs:enumeration value="moffs64" />
"m": "0(AX)(CX*2)", // <xs:enumeration value="m" />
"m8": "8(AX)(CX*2)", // <xs:enumeration value="m8" />
"m16": "16(AX)(CX*2)", // <xs:enumeration value="m16" />
// <xs:enumeration value="m16{k}{z}" />
"m32": "32(AX)(CX*2)", // <xs:enumeration value="m32" />
// <xs:enumeration value="m32{k}" />
// <xs:enumeration value="m32{k}{z}" />
"m64": "64(AX)(CX*2)", // <xs:enumeration value="m64" />
// <xs:enumeration value="m64{k}" />
// <xs:enumeration value="m64{k}{z}" />
"m128": "128(AX)(CX*2)", // <xs:enumeration value="m128" />
// <xs:enumeration value="m128{k}{z}" />
"m256": "256(AX)(CX*2)", // <xs:enumeration value="m256" />
// <xs:enumeration value="m256{k}{z}" />
"m512": "512(AX)(CX*2)", // <xs:enumeration value="m512" />
// <xs:enumeration value="m512{k}{z}" />
"vm32x": "32(X14*8)", // <xs:enumeration value="vm32x" />
// <xs:enumeration value="vm32x{k}" />
"vm64x": "64(X14*8)", // <xs:enumeration value="vm64x" />
// <xs:enumeration value="vm64x{k}" />
"vm32y": "32(Y13*8)", // <xs:enumeration value="vm32y" />
// <xs:enumeration value="vm32y{k}" />
"vm64y": "64(Y13*8)", // <xs:enumeration value="vm64y" />
// <xs:enumeration value="vm64y{k}" />
"vm32z": "32(Z13*8)", // <xs:enumeration value="vm32z" />
// <xs:enumeration value="vm32z{k}" />
"vm64z": "64(Z13*8)", // <xs:enumeration value="vm64z" />
// <xs:enumeration value="vm64z{k}" />
"rel8": a.rel8, // <xs:enumeration value="rel8" />
"rel32": a.rel32, // <xs:enumeration value="rel32" />
// <xs:enumeration value="{er}" />
// <xs:enumeration value="{sae}" />
// Appear unused:
"r8l": "????", // <xs:enumeration value="r8l" />
"r16l": "????", // <xs:enumeration value="r16l" />
"r32l": "????", // <xs:enumeration value="r32l" />
}
return m[t]
}