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

@@ -10,7 +10,7 @@ type Instruction struct {
Opcode string // Golang assembly mnemonic
AliasOf string // Opcode of instruction that this is an alias for
Summary string // Description of the instruction
Forms []Form // Accepted operand forms
Forms // Accepted operand forms
}
// IsTerminal reports whether the instruction exits a function.
@@ -40,10 +40,13 @@ func (i Instruction) IsConditionalBranch() bool {
return i.IsBranch() && i.Opcode != "JMP"
}
// Forms is a collection of instruction forms.
type Forms []Form
// Arities returns the unique arities among the instruction forms.
func (i Instruction) Arities() []int {
func (fs Forms) Arities() []int {
s := map[int]bool{}
for _, f := range i.Forms {
for _, f := range fs {
s[f.Arity()] = true
}
a := make([]int, 0, len(s))
@@ -56,22 +59,22 @@ func (i Instruction) Arities() []int {
// Arity is a convenience for returning the unique instruction arity when you
// know it is not variadic. Panics for a variadic instruction.
func (i Instruction) Arity() int {
if i.IsVariadic() {
func (fs Forms) Arity() int {
if fs.IsVariadic() {
panic("variadic")
}
a := i.Arities()
a := fs.Arities()
return a[0]
}
// IsVariadic reports whether the instruction has more than one arity.
func (i Instruction) IsVariadic() bool {
return len(i.Arities()) > 1
func (fs Forms) IsVariadic() bool {
return len(fs.Arities()) > 1
}
// IsNiladic reports whether the instruction takes no operands.
func (i Instruction) IsNiladic() bool {
a := i.Arities()
func (fs Forms) IsNiladic() bool {
a := fs.Arities()
return len(a) == 1 && a[0] == 0
}
@@ -86,12 +89,35 @@ type Form struct {
// Registers read or written but not explicitly passed to the instruction.
ImplicitOperands []ImplicitOperand
// Encoding type required for this instruction form.
EncodingType EncodingType
// CancellingInputs indicates this instruction form has no dependency on the
// input operands when they refer to the same register. The classic example of
// this is "XORQ RAX, RAX", in which case the output has no dependence on the
// value of RAX. Instruction forms with cancelling inputs have only two input
// operands, which have the same register type.
CancellingInputs bool
// Zeroing indicates whether the instruction form uses AVX-512 zeroing. This
// is the .Z suffix in Go, usually indicated with {z} operand suffix in
// Intel manuals.
Zeroing bool
// EmbeddedRounding indicates whether the instruction form uses AVX-512
// embedded rounding. This is the RN_SAE, RZ_SAE, RD_SAE and RU_SAE suffixes
// in Go, usually indicated with {er} in Intel manuals.
EmbeddedRounding bool
// SuppressAllExceptions indicates whether the instruction form uses AVX-512
// "suppress all exceptions". This is the SAE suffix in Go, usually
// indicated with {sae} in Intel manuals.
SuppressAllExceptions bool
// Broadcast indicates whether the instruction form uses AVX-512
// broadcast. This is the BCST suffix in Go, usually indicated by operand
// types like "m64bcst" in Intel manuals.
Broadcast bool
}
// Arity returns the number of operands this form expects.
@@ -108,6 +134,143 @@ func (f Form) Signature() []string {
return s
}
// Clone the instruction form.
func (f Form) Clone() Form {
c := f
c.ISA = append([]string(nil), f.ISA...)
c.Operands = append([]Operand(nil), f.Operands...)
c.ImplicitOperands = append([]ImplicitOperand(nil), f.ImplicitOperands...)
return c
}
// AcceptsSuffixes reports whether this form takes any opcode suffixes.
func (f Form) AcceptsSuffixes() bool {
return f.Broadcast || f.EmbeddedRounding || f.SuppressAllExceptions || f.Zeroing
}
// SuffixesClass returns a key representing the class of instruction suffixes it
// accepts. All instructions sharing a suffix class accept the same suffixes.
func (f Form) SuffixesClass() string {
if !f.AcceptsSuffixes() {
return "nil"
}
var parts []string
for _, flag := range []struct {
Name string
Enabled bool
}{
{"er", f.EmbeddedRounding},
{"sae", f.SuppressAllExceptions},
{"bcst", f.Broadcast},
{"z", f.Zeroing},
} {
if flag.Enabled {
parts = append(parts, flag.Name)
}
}
return strings.Join(parts, "_")
}
// SupportedSuffixes returns the list of all possible suffix combinations
// supported by this instruction form.
func (f Form) SupportedSuffixes() []Suffixes {
suffixes := []Suffixes{
{},
}
add := func(ss ...Suffix) {
var exts []Suffixes
for _, s := range ss {
for _, suffix := range suffixes {
ext := append(Suffixes(nil), suffix...)
ext = append(ext, s)
exts = append(exts, ext)
}
}
suffixes = exts
}
if f.Broadcast {
add(BCST)
}
if f.EmbeddedRounding {
add(RN_SAE, RZ_SAE, RD_SAE, RU_SAE)
}
if f.SuppressAllExceptions {
add(SAE)
}
if f.Zeroing {
add(Z)
}
return suffixes
}
// Suffix is an opcode suffix.
type Suffix string
// Supported opcode suffixes in x86 assembly.
const (
BCST Suffix = "BCST"
RN_SAE Suffix = "RN_SAE"
RZ_SAE Suffix = "RZ_SAE"
RD_SAE Suffix = "RD_SAE"
RU_SAE Suffix = "RU_SAE"
SAE Suffix = "SAE"
Z Suffix = "Z"
)
func (s Suffix) String() string {
return string(s)
}
// Summary of the opcode suffix, for documentation purposes.
func (s Suffix) Summary() string {
return suffixsummary[s]
}
var suffixsummary = map[Suffix]string{
BCST: "Broadcast",
RN_SAE: "Round Towards Nearest",
RZ_SAE: "Round Towards Zero",
RD_SAE: "Round Towards Negative Infinity",
RU_SAE: "Round Towards Positive Infinity",
SAE: "Suppress All Exceptions",
Z: "Zeroing Masking",
}
// Suffixes is a list of opcode suffixes.
type Suffixes []Suffix
// String returns the dot-separated suffixes.
func (s Suffixes) String() string { return s.Join(".") }
// Join suffixes with the given separator.
func (s Suffixes) Join(sep string) string {
return strings.Join(s.Strings(), sep)
}
// Strings returns the suffixes as strings.
func (s Suffixes) Strings() []string {
var ss []string
for _, suffix := range s {
ss = append(ss, suffix.String())
}
return ss
}
// Summaries returns all the suffix summaries.
func (s Suffixes) Summaries() []string {
var summaries []string
for _, suffix := range s {
summaries = append(summaries, suffix.Summary())
}
return summaries
}
// Operand is an operand to an instruction, describing the expected type and read/write action.
type Operand struct {
Type string
@@ -125,9 +288,10 @@ type Action uint8
// Possible Action types.
const (
R Action = 0x1
W Action = 0x2
RW Action = R | W
R Action = 1 << iota // Read
W // Write
RW Action = R | W // Read-Write
)
// ActionFromReadWrite builds an Action from boolean flags.
@@ -142,19 +306,24 @@ func ActionFromReadWrite(r, w bool) Action {
return a
}
// Contains reports whether a supports all actions in s.
func (a Action) Contains(s Action) bool {
// ContainsAll reports whether a supports all actions in s.
func (a Action) ContainsAll(s Action) bool {
return (a & s) == s
}
// ContainsAny reports whether a supports any actions in s.
func (a Action) ContainsAny(s Action) bool {
return (a & s) != 0
}
// Read reports whether a supports read.
func (a Action) Read() bool {
return a.Contains(R)
return a.ContainsAll(R)
}
// Write reports whether a supports write.
func (a Action) Write() bool {
return a.Contains(W)
return a.ContainsAll(W)
}
// String represents a as a human-readable string.
@@ -168,3 +337,14 @@ func (a Action) String() string {
}
return s
}
// EncodingType specifies a category of encoding types.
type EncodingType uint8
// Supported encoding types.
const (
EncodingTypeLegacy EncodingType = 1 + iota
EncodingTypeREX
EncodingTypeVEX
EncodingTypeEVEX
)