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>
258 lines
7.0 KiB
Go
258 lines
7.0 KiB
Go
package gen
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/mmcloughlin/avo/internal/api"
|
|
"github.com/mmcloughlin/avo/internal/inst"
|
|
)
|
|
|
|
// Enum is a generated enumeration type. This assists with mapping between the
|
|
// conceptual values of the enum, and it's materialization as Go code.
|
|
type Enum struct {
|
|
name string
|
|
doc []string
|
|
values []string
|
|
}
|
|
|
|
// NewEnum initializes an empty enum type with the given name.
|
|
func NewEnum(name string) *Enum {
|
|
return &Enum{name: name}
|
|
}
|
|
|
|
// Name returns the type name.
|
|
func (e *Enum) Name() string { return e.name }
|
|
|
|
// Receiver returns the receiver variable name.
|
|
func (e *Enum) Receiver() string {
|
|
return strings.ToLower(e.name[:1])
|
|
}
|
|
|
|
// SetDoc sets type documentation, as a list of lines.
|
|
func (e *Enum) SetDoc(doc ...string) {
|
|
e.doc = doc
|
|
}
|
|
|
|
// Doc returns the type documentation.
|
|
func (e *Enum) Doc() []string { return e.doc }
|
|
|
|
// AddValue adds a named enumerator.
|
|
func (e *Enum) AddValue(value string) {
|
|
e.values = append(e.values, value)
|
|
}
|
|
|
|
// Values returns all enumerators.
|
|
func (e *Enum) Values() []string { return e.values }
|
|
|
|
// None returns the name of the "unset" constant of this enumeration.
|
|
func (e *Enum) None() string {
|
|
return e.ConstName("None")
|
|
}
|
|
|
|
// ConstName returns the constant name that refers to the given enumerator
|
|
// value.
|
|
func (e *Enum) ConstName(value string) string {
|
|
return e.name + value
|
|
}
|
|
|
|
// ConstNames returns the constant names for all enumerator values.
|
|
func (e *Enum) ConstNames() []string {
|
|
var consts []string
|
|
for _, v := range e.values {
|
|
consts = append(consts, e.ConstName(v))
|
|
}
|
|
return consts
|
|
}
|
|
|
|
// MaxName returns the name of the constant that represents the maximum
|
|
// enumerator. This value is placed at the very end of the enum, so all values
|
|
// will be between the None and Max enumerators.
|
|
func (e *Enum) MaxName() string {
|
|
return strings.ToLower(e.ConstName("max"))
|
|
}
|
|
|
|
// Max returns the value of the maximum enumerator.
|
|
func (e *Enum) Max() int {
|
|
return len(e.values)
|
|
}
|
|
|
|
// UnderlyingType returns the underlying unsigned integer type used for the
|
|
// enumeration. This will be the smallest type that can represent all the
|
|
// values.
|
|
func (e *Enum) UnderlyingType() string {
|
|
b := uint(8)
|
|
for ; b < 64 && e.Max() > ((1<<b)-1); b <<= 1 {
|
|
}
|
|
return fmt.Sprintf("uint%d", b)
|
|
}
|
|
|
|
// Table represents all the types required to represent the instruction
|
|
// operation table (optab).
|
|
type Table struct {
|
|
instructions []inst.Instruction
|
|
|
|
operandType *Enum
|
|
implicitRegister *Enum
|
|
suffix *Enum
|
|
suffixesClass *Enum
|
|
isas *Enum
|
|
opcode *Enum
|
|
}
|
|
|
|
// NewTable builds optab types to represent the given instructions.
|
|
func NewTable(is []inst.Instruction) *Table {
|
|
t := &Table{instructions: is}
|
|
t.init()
|
|
return t
|
|
}
|
|
|
|
func (t *Table) init() {
|
|
// Operand type.
|
|
t.operandType = NewEnum("oprndtype")
|
|
types := inst.OperandTypes(t.instructions)
|
|
for _, typ := range types {
|
|
t.operandType.AddValue(api.OperandTypeIdentifier(typ))
|
|
}
|
|
|
|
// Implicit register.
|
|
registers := inst.ImplicitRegisters(t.instructions)
|
|
t.implicitRegister = NewEnum("implreg")
|
|
for _, r := range registers {
|
|
t.implicitRegister.AddValue(api.ImplicitRegisterIdentifier(r))
|
|
}
|
|
|
|
// Suffix.
|
|
t.suffix = NewEnum("sffx")
|
|
for _, s := range inst.UniqueSuffixes(t.instructions) {
|
|
t.suffix.AddValue(s.String())
|
|
}
|
|
|
|
// Suffixes class.
|
|
t.suffixesClass = NewEnum("sffxscls")
|
|
|
|
classes := inst.SuffixesClasses(t.instructions)
|
|
keys := make([]string, 0, len(classes))
|
|
for key := range classes {
|
|
keys = append(keys, key)
|
|
}
|
|
sort.Strings(keys)
|
|
|
|
for _, key := range keys {
|
|
t.suffixesClass.AddValue(api.SuffixesClassIdentifier(key))
|
|
}
|
|
|
|
// ISAs.
|
|
t.isas = NewEnum("isas")
|
|
for _, isas := range inst.ISACombinations(t.instructions) {
|
|
t.isas.AddValue(api.ISAsIdentifier(isas))
|
|
}
|
|
|
|
// Opcodes.
|
|
t.opcode = NewEnum("opc")
|
|
for _, i := range t.instructions {
|
|
t.opcode.AddValue(i.Opcode)
|
|
}
|
|
}
|
|
|
|
// OperandType returns the enumeration representing all possible operand types.
|
|
func (t *Table) OperandType() *Enum { return t.operandType }
|
|
|
|
// OperandTypeConst returns the constant name for the given operand type.
|
|
func (t *Table) OperandTypeConst(typ string) string {
|
|
return t.operandType.ConstName(api.OperandTypeIdentifier(typ))
|
|
}
|
|
|
|
// ImplicitRegister returns the enumeration representing all possible operand
|
|
// types.
|
|
func (t *Table) ImplicitRegister() *Enum { return t.implicitRegister }
|
|
|
|
// ImplicitRegisterConst returns the constant name for the given register.
|
|
func (t *Table) ImplicitRegisterConst(r string) string {
|
|
return t.implicitRegister.ConstName(api.ImplicitRegisterIdentifier(r))
|
|
}
|
|
|
|
// Suffix returns the enumeration representing instruction suffixes.
|
|
func (t *Table) Suffix() *Enum { return t.suffix }
|
|
|
|
// SuffixConst returns the constant name for the given instruction suffix.
|
|
func (t *Table) SuffixConst(s inst.Suffix) string { return t.suffix.ConstName(s.String()) }
|
|
|
|
// SuffixesTypeName returns the name of the array type for a list of suffixes.
|
|
func (t *Table) SuffixesTypeName() string {
|
|
return t.Suffix().Name() + "s"
|
|
}
|
|
|
|
// SuffixesConst returns the constant for a list of suffixes. Suffixes is a
|
|
// generated array type, so the list is a value not slice type.
|
|
func (t *Table) SuffixesConst(suffixes inst.Suffixes) string {
|
|
var parts []string
|
|
for _, suffix := range suffixes {
|
|
parts = append(parts, t.SuffixConst(suffix))
|
|
}
|
|
return t.SuffixesTypeName() + "{" + strings.Join(parts, ", ") + "}"
|
|
}
|
|
|
|
// SuffixesClass returns the enumeration representing all suffixes classes.
|
|
func (t *Table) SuffixesClass() *Enum { return t.suffixesClass }
|
|
|
|
// SuffixesClassConst returns the constant name for a given suffixes class. The
|
|
// key is as returned from inst.SuffixesClasses() function.
|
|
func (t *Table) SuffixesClassConst(key string) string {
|
|
ident := api.SuffixesClassIdentifier(key)
|
|
if ident == "" {
|
|
return t.suffixesClass.None()
|
|
}
|
|
return t.suffixesClass.ConstName(ident)
|
|
}
|
|
|
|
// ISAs returns the enumeration for all possible ISA combinations.
|
|
func (t *Table) ISAs() *Enum { return t.isas }
|
|
|
|
// ISAsConst returns the constant name for the given ISA combination.
|
|
func (t *Table) ISAsConst(isas []string) string {
|
|
return t.isas.ConstName(api.ISAsIdentifier(isas))
|
|
}
|
|
|
|
// Opcode returns the opcode enumeration.
|
|
func (t *Table) Opcode() *Enum { return t.opcode }
|
|
|
|
// OpcodeConst returns the constant name for the given opcode.
|
|
func (t *Table) OpcodeConst(opcode string) string {
|
|
return t.opcode.ConstName(opcode)
|
|
}
|
|
|
|
// Features returns code for the features constant describing the features of
|
|
// the given instruction form.
|
|
func Features(i inst.Instruction, f inst.Form) string {
|
|
var enabled []string
|
|
for _, feature := range []struct {
|
|
Name string
|
|
Enabled bool
|
|
}{
|
|
{"Terminal", i.IsTerminal()},
|
|
{"Branch", i.IsBranch()},
|
|
{"ConditionalBranch", i.IsConditionalBranch()},
|
|
{"CancellingInputs", f.CancellingInputs},
|
|
} {
|
|
if feature.Enabled {
|
|
enabled = append(enabled, "feature"+feature.Name)
|
|
}
|
|
}
|
|
|
|
if len(enabled) == 0 {
|
|
return "0"
|
|
}
|
|
return strings.Join(enabled, "|")
|
|
}
|
|
|
|
// Action returns code representing the instruction action.
|
|
func Action(a inst.Action) string {
|
|
c := strings.ToUpper(a.String())
|
|
if c == "" {
|
|
c = "N"
|
|
}
|
|
return "action" + c
|
|
}
|