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

257
internal/gen/api.go Normal file
View File

@@ -0,0 +1,257 @@
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
}

View File

@@ -37,13 +37,10 @@ func (a *asmtest) Generate(is []inst.Instruction) ([]byte, error) {
a.Printf("rel32:\n")
a.rel32 = "rel32"
counts := map[string]int{}
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)
counts["skip"]++
continue
}
@@ -54,24 +51,23 @@ func (a *asmtest) Generate(is []inst.Instruction) ([]byte, error) {
}
for _, f := range i.Forms {
as := a.args(i.Opcode, f.Operands)
if as == nil {
a.Printf("\t// TODO: %s %#v\n", i.Opcode, f.Operands)
counts["todo"]++
continue
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("\t%s\t%s\n", i.Opcode, strings.Join(as, ", "))
counts["total"]++
}
a.Printf("\n")
}
a.Printf("\tRET\n")
for m, c := range counts {
a.Printf("// %s: %d\n", m, c)
}
return a.Result()
}
@@ -88,21 +84,21 @@ func (a asmtest) skip(opcode string) (bool, string) {
return false, ""
}
func (a asmtest) args(opcode string, ops []inst.Operand) []string {
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}
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
return nil, fmt.Errorf("unsupported operand type %q", op.Type)
}
as[i] = a
}
return as
return as, nil
}
// arg generates an argument for an operand of the given type.
@@ -134,10 +130,10 @@ func (a asmtest) arg(t string, i int) string {
"ymm": "Y" + strconv.Itoa(3+i), // <xs:enumeration value="ymm" />
// <xs:enumeration value="ymm{k}" />
// <xs:enumeration value="ymm{k}{z}" />
// <xs:enumeration value="zmm" />
"zmm": "Z" + strconv.Itoa(16+i), // <xs:enumeration value="zmm" />
// <xs:enumeration value="zmm{k}" />
// <xs:enumeration value="zmm{k}{z}" />
// <xs:enumeration value="k" />
"k": "K" + strconv.Itoa(1+i), // <xs:enumeration value="k" />
// <xs:enumeration value="k{k}" />
// <xs:enumeration value="moffs32" />
// <xs:enumeration value="moffs64" />
@@ -155,15 +151,8 @@ func (a asmtest) arg(t string, i int) string {
// <xs:enumeration value="m128{k}{z}" />
"m256": "256(AX)(CX*2)", // <xs:enumeration value="m256" />
// <xs:enumeration value="m256{k}{z}" />
// <xs:enumeration value="m512" />
"m512": "512(AX)(CX*2)", // <xs:enumeration value="m512" />
// <xs:enumeration value="m512{k}{z}" />
// <xs:enumeration value="m64/m32bcst" />
// <xs:enumeration value="m128/m32bcst" />
// <xs:enumeration value="m256/m32bcst" />
// <xs:enumeration value="m512/m32bcst" />
// <xs:enumeration value="m128/m64bcst" />
// <xs:enumeration value="m256/m64bcst" />
// <xs:enumeration value="m512/m64bcst" />
"vm32x": "32(X14*8)", // <xs:enumeration value="vm32x" />
// <xs:enumeration value="vm32x{k}" />
"vm64x": "64(X14*8)", // <xs:enumeration value="vm64x" />
@@ -172,9 +161,9 @@ func (a asmtest) arg(t string, i int) string {
// <xs:enumeration value="vm32y{k}" />
"vm64y": "64(Y13*8)", // <xs:enumeration value="vm64y" />
// <xs:enumeration value="vm64y{k}" />
// <xs:enumeration value="vm32z" />
"vm32z": "32(Z13*8)", // <xs:enumeration value="vm32z" />
// <xs:enumeration value="vm32z{k}" />
// <xs:enumeration value="vm64z" />
"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" />

View File

@@ -1,22 +0,0 @@
package gen
import (
"fmt"
"strings"
)
const (
pkg = "github.com/mmcloughlin/avo"
operandType = "operand.Op"
)
// implicitRegister returns avo syntax for the given implicit register type (from Opcodes XML).
func implicitRegister(t string) string {
r := strings.Replace(t, "mm", "", 1) // handles the "xmm0" type
return fmt.Sprintf("reg.%s", strings.ToUpper(r))
}
// checkername returns the name of the function that checks an operand of type t.
func checkername(t string) string {
return "operand.Is" + strings.ToUpper(t)
}

View File

@@ -3,6 +3,7 @@ package gen
import (
"fmt"
"github.com/mmcloughlin/avo/internal/api"
"github.com/mmcloughlin/avo/internal/inst"
"github.com/mmcloughlin/avo/internal/prnt"
"github.com/mmcloughlin/avo/printer"
@@ -26,34 +27,41 @@ func (b *build) Generate(is []inst.Instruction) ([]byte, error) {
b.Printf("package build\n\n")
b.Printf("import (\n")
b.Printf("\t\"%s/operand\"\n", pkg)
b.Printf("\t\"%s/x86\"\n", pkg)
b.Printf("\t%q\n", api.ImportPath(api.IRPackage))
b.Printf("\t%q\n", api.ImportPath(api.OperandPackage))
b.Printf("\t%q\n", api.ImportPath("x86"))
b.Printf(")\n\n")
for _, i := range is {
b.instruction(i)
// Helper to reduce source code size a little.
b.Printf("func (c *Context) addinstruction(i *ir.Instruction, err error) {\n")
b.Printf("if err == nil { c.Instruction(i) }")
b.Printf(" else { c.adderror(err) }\n")
b.Printf("}\n\n")
// Generate build functions.
fns := api.InstructionsFunctions(is)
for _, fn := range fns {
b.function(fn)
}
return b.Result()
}
func (b *build) instruction(i inst.Instruction) {
s := params(i)
d := doc(i)
func (b *build) function(fn *api.Function) {
s := fn.Signature()
d := fn.Doc()
// Context method.
methoddoc := append([]string{}, d...)
methoddoc = append(methoddoc, fmt.Sprintf("Construct and append a %s instruction to the active function.", i.Opcode))
methoddoc = append(methoddoc, fmt.Sprintf("Construct and append a %s instruction to the active function.", fn.Opcode()))
b.Comment(methoddoc...)
b.Printf("func (c *Context) %s(%s) {\n", i.Opcode, s.ParameterList())
b.Printf("if inst, err := x86.%s(%s); err == nil", i.Opcode, s.Arguments())
b.Printf(" { c.Instruction(inst) }")
b.Printf(" else { c.adderror(err) }\n")
b.Printf("func (c *Context) %s(%s) {\n", fn.Name(), s.ParameterList())
b.Printf("c.addinstruction(x86.%s(%s))", fn.Name(), s.Arguments())
b.Printf("}\n\n")
// Global version.
globaldoc := append([]string{}, methoddoc...)
globaldoc = append(globaldoc, "Operates on the global context.")
b.Comment(globaldoc...)
b.Printf("func %s(%s) { ctx.%s(%s) }\n\n", i.Opcode, s.ParameterList(), i.Opcode, s.Arguments())
b.Printf("func %s(%s) { ctx.%s(%s) }\n\n", fn.Name(), s.ParameterList(), fn.Name(), s.Arguments())
}

51
internal/gen/buildtest.go Normal file
View File

@@ -0,0 +1,51 @@
package gen
import (
"github.com/mmcloughlin/avo/internal/api"
"github.com/mmcloughlin/avo/internal/inst"
"github.com/mmcloughlin/avo/internal/prnt"
"github.com/mmcloughlin/avo/printer"
)
type buildtest struct {
cfg printer.Config
prnt.Generator
}
// NewBuildTest autogenerates tests for instruction methods on the build
// context.
func NewBuildTest(cfg printer.Config) Interface {
return GoFmt(&buildtest{cfg: cfg})
}
func (b *buildtest) Generate(is []inst.Instruction) ([]byte, error) {
b.Printf("// %s\n\n", b.cfg.GeneratedWarning())
b.BuildTag("!integration")
b.NL()
b.Printf("package build\n\n")
b.Printf("import (\n")
b.Printf("\t\"math\"\n")
b.Printf("\t\"testing\"\n")
b.NL()
b.Printf("\t%q\n", api.ImportPath(api.OperandPackage))
b.Printf("\t%q\n", api.ImportPath(api.RegisterPackage))
b.Printf(")\n\n")
DeclareTestArguments(&b.Generator)
b.Printf("func TestContextInstructions(t *testing.T) {")
b.Printf("ctx := NewContext()\n")
b.Printf("ctx.Function(\"Instructions\")\n")
fns := api.InstructionsFunctions(is)
for _, fn := range fns {
f := fn.Forms[0]
s := TestSignature(f)
b.Printf("ctx.%s(%s)\n", fn.Name(), s.Arguments())
}
b.Printf("if _, err := ctx.Result(); err != nil { t.Fatal(err) }\n")
b.Printf("}\n\n")
return b.Result()
}

View File

@@ -1,10 +1,7 @@
package gen
import (
"bytes"
"fmt"
"strings"
"github.com/mmcloughlin/avo/internal/api"
"github.com/mmcloughlin/avo/internal/inst"
"github.com/mmcloughlin/avo/internal/prnt"
"github.com/mmcloughlin/avo/printer"
@@ -15,9 +12,9 @@ type ctors struct {
prnt.Generator
}
// NewCtors will build instruction constructors. Each constructor will check
// that the provided operands match one of the allowed instruction forms. If so
// it will return an Instruction object that can be added to an avo Function.
// NewCtors will build instruction constructors. Each constructor delegates to
// the optab-based instruction builder, providing it with a candidate list of
// forms to match against.
func NewCtors(cfg printer.Config) Interface {
return GoFmt(&ctors{cfg: cfg})
}
@@ -26,107 +23,30 @@ func (c *ctors) Generate(is []inst.Instruction) ([]byte, error) {
c.Printf("// %s\n\n", c.cfg.GeneratedWarning())
c.Printf("package x86\n\n")
c.Printf("import (\n")
c.Printf("\t\"errors\"\n")
c.NL()
c.Printf("\tintrep \"%s/ir\"\n", pkg)
c.Printf("\t\"%s/reg\"\n", pkg)
c.Printf("\t\"%s/operand\"\n", pkg)
c.Printf("\tintrep %q\n", api.ImportPath(api.IRPackage))
c.Printf("\t%q\n", api.ImportPath(api.OperandPackage))
c.Printf(")\n\n")
for _, i := range is {
c.instruction(i)
fns := api.InstructionsFunctions(is)
table := NewTable(is)
for _, fn := range fns {
c.function(fn, table)
}
return c.Result()
}
func (c *ctors) instruction(i inst.Instruction) {
c.Comment(doc(i)...)
func (c *ctors) function(fn *api.Function, table *Table) {
c.Comment(fn.Doc()...)
s := params(i)
s := fn.Signature()
c.Printf("func %s(%s) (*intrep.Instruction, error) {\n", i.Opcode, s.ParameterList())
c.forms(i, s)
c.Printf("func %s(%s) (*intrep.Instruction, error) {\n", fn.Name(), s.ParameterList())
c.Printf(
"return build(%s.Forms(), %s, %s)\n",
table.OpcodeConst(fn.Instruction.Opcode),
table.SuffixesConst(fn.Suffixes),
s.ParameterSlice(),
)
c.Printf("}\n\n")
}
func (c *ctors) forms(i inst.Instruction, s signature) {
if i.IsNiladic() {
if len(i.Forms) != 1 {
c.AddError(fmt.Errorf("%s breaks assumption that niladic instructions have one form", i.Opcode))
}
c.Printf("return &%s, nil\n", construct(i, i.Forms[0], s))
return
}
c.Printf("switch {\n")
for _, f := range i.Forms {
var conds []string
if i.IsVariadic() {
checklen := fmt.Sprintf("%s == %d", s.Length(), len(f.Operands))
conds = append(conds, checklen)
}
for j, op := range f.Operands {
checktype := fmt.Sprintf("%s(%s)", checkername(op.Type), s.ParameterName(j))
conds = append(conds, checktype)
}
c.Printf("case %s:\n", strings.Join(conds, " && "))
c.Printf("return &%s, nil\n", construct(i, f, s))
}
c.Printf("}\n")
c.Printf("return nil, errors.New(\"%s: bad operands\")\n", i.Opcode)
}
func construct(i inst.Instruction, f inst.Form, s signature) string {
buf := bytes.NewBuffer(nil)
fmt.Fprintf(buf, "intrep.Instruction{\n")
fmt.Fprintf(buf, "\tOpcode: %#v,\n", i.Opcode)
fmt.Fprintf(buf, "\tOperands: %s,\n", s.ParameterSlice())
// Input output.
fmt.Fprintf(buf, "\tInputs: %s,\n", operandsWithAction(f, inst.R, s))
fmt.Fprintf(buf, "\tOutputs: %s,\n", operandsWithAction(f, inst.W, s))
// ISAs.
if len(f.ISA) > 0 {
fmt.Fprintf(buf, "\tISA: %#v,\n", f.ISA)
}
// Branch variables.
if i.IsTerminal() {
fmt.Fprintf(buf, "\tIsTerminal: true,\n")
}
if i.IsBranch() {
fmt.Fprintf(buf, "\tIsBranch: true,\n")
fmt.Fprintf(buf, "\tIsConditional: %#v,\n", i.IsConditionalBranch())
}
// Cancelling inputs.
if f.CancellingInputs {
fmt.Fprintf(buf, "\tCancellingInputs: true,\n")
}
fmt.Fprintf(buf, "}")
return buf.String()
}
func operandsWithAction(f inst.Form, a inst.Action, s signature) string {
opexprs := []string{}
for i, op := range f.Operands {
if op.Action.Contains(a) {
opexprs = append(opexprs, s.ParameterName(i))
}
}
for _, op := range f.ImplicitOperands {
if op.Action.Contains(a) {
opexprs = append(opexprs, implicitRegister(op.Register))
}
}
return fmt.Sprintf("[]%s{%s}", operandType, strings.Join(opexprs, ", "))
}

View File

@@ -1,28 +0,0 @@
package gen
import (
"testing"
"github.com/mmcloughlin/avo/internal/inst"
)
func TestParamsUniqueArgNames(t *testing.T) {
for _, i := range inst.Instructions {
s := params(i)
for _, n := range i.Arities() {
if n == 0 {
continue
}
names := map[string]bool{}
for j := 0; j < n; j++ {
names[s.ParameterName(j)] = true
}
if len(names) != n {
t.Errorf("repeated argument for instruction %s", i.Opcode)
}
if _, found := names[""]; found {
t.Errorf("empty argument name for instruction %s", i.Opcode)
}
}
}
}

View File

@@ -1,8 +1,11 @@
package gen
import (
"bytes"
"fmt"
"strings"
"github.com/mmcloughlin/avo/internal/api"
"github.com/mmcloughlin/avo/internal/prnt"
"github.com/mmcloughlin/avo/printer"
@@ -21,93 +24,173 @@ func NewCtorsTest(cfg printer.Config) Interface {
func (c *ctorstest) Generate(is []inst.Instruction) ([]byte, error) {
c.Printf("// %s\n\n", c.cfg.GeneratedWarning())
c.BuildTag("!integration")
c.NL()
c.Printf("package x86\n\n")
c.Printf("import (\n")
c.Printf("\t\"testing\"\n")
c.Printf("\t\"math\"\n")
c.Printf("\t\"testing\"\n")
c.NL()
c.Printf("\t\"%s/reg\"\n", pkg)
c.Printf("\t\"%s/operand\"\n", pkg)
c.Printf("\t%q\n", api.ImportPath(api.OperandPackage))
c.Printf("\t%q\n", api.ImportPath(api.RegisterPackage))
c.Printf(")\n\n")
for _, i := range is {
c.instruction(i)
DeclareTestArguments(&c.Generator)
fns := api.InstructionsFunctions(is)
for _, fn := range fns {
c.function(fn)
}
return c.Result()
}
func (c *ctorstest) instruction(i inst.Instruction) {
c.Printf("func Test%sValidForms(t *testing.T) {", i.Opcode)
for _, f := range i.Forms {
name := strings.Join(f.Signature(), "_")
c.Printf("t.Run(\"form=%s\", func(t *testing.T) {\n", name)
for _, args := range validFormArgs(f) {
c.Printf("if _, err := %s(%s)", i.Opcode, strings.Join(args, ", "))
c.Printf("; err != nil { t.Fatal(err) }\n")
}
c.Printf("})\n")
func (c *ctorstest) function(fn *api.Function) {
c.Printf("func Test%sValidFormsNoError(t *testing.T) {", fn.Name())
for _, f := range fn.Forms {
s := TestSignature(f)
c.Printf("if _, err := %s(%s); err != nil { t.Fatal(err) }\n", fn.Name(), s.Arguments())
}
c.Printf("}\n\n")
}
func validFormArgs(f inst.Form) [][]string {
n := len(f.Operands)
args := make([][]string, n)
for i, op := range f.Operands {
valid, ok := validArgs[op.Type]
if !ok {
panic("missing operands for type " + op.Type)
}
args[i] = valid
type ctorsstress struct {
cfg printer.Config
prnt.Generator
}
// NewCtorsStress autogenerates stress tests for instruction constructors.
func NewCtorsStress(cfg printer.Config) Interface {
return GoFmt(&ctorsstress{cfg: cfg})
}
func (c *ctorsstress) Generate(is []inst.Instruction) ([]byte, error) {
c.Printf("// %s\n\n", c.cfg.GeneratedWarning())
c.BuildTag("stress")
c.NL()
c.Printf("package x86\n\n")
c.Printf("import (\n")
c.Printf("\t\"reflect\"\n")
c.Printf("\t\"testing\"\n")
c.NL()
c.Printf("\t%q\n", api.ImportPath(api.IRPackage))
c.Printf("\t%q\n", api.ImportPath(api.OperandPackage))
c.Printf("\t%q\n", api.ImportPath(api.RegisterPackage))
c.Printf(")\n\n")
fns := api.InstructionsFunctions(is)
for _, fn := range fns {
c.function(fn)
}
return cross(args)
return c.Result()
}
var validArgs = map[string][]string{
// Immediates
"1": {"operand.Imm(1)"},
"3": {"operand.Imm(3)"},
"imm2u": {"operand.Imm(1)", "operand.Imm(3)"},
"imm8": {"operand.Imm(math.MaxInt8)"},
"imm16": {"operand.Imm(math.MaxInt16)"},
"imm32": {"operand.Imm(math.MaxInt32)"},
"imm64": {"operand.Imm(math.MaxInt64)"},
// Registers
"al": {"reg.AL"},
"cl": {"reg.CL"},
"ax": {"reg.AX"},
"eax": {"reg.EAX"},
"rax": {"reg.RAX"},
"r8": {"reg.CH", "reg.BL", "reg.R13B"},
"r16": {"reg.CX", "reg.R9W"},
"r32": {"reg.R10L"},
"r64": {"reg.R11"},
"xmm0": {"reg.X0"},
"xmm": {"reg.X7"},
"ymm": {"reg.Y15"},
// Memory
"m": {"operand.Mem{Base: reg.BX, Index: reg.CX, Scale: 2}"},
"m8": {"operand.Mem{Base: reg.BL, Index: reg.CH, Scale: 1}"},
"m16": {"operand.Mem{Base: reg.BX, Index: reg.CX, Scale: 2}"},
"m32": {"operand.Mem{Base: reg.EBX, Index: reg.ECX, Scale: 4}"},
"m64": {"operand.Mem{Base: reg.RBX, Index: reg.RCX, Scale: 8}"},
"m128": {"operand.Mem{Base: reg.RBX, Index: reg.RCX, Scale: 8}"},
"m256": {"operand.Mem{Base: reg.RBX, Index: reg.RCX, Scale: 8}"},
// Vector memory
"vm32x": {"operand.Mem{Base: reg.R13, Index: reg.X4, Scale: 1}"},
"vm64x": {"operand.Mem{Base: reg.R13, Index: reg.X8, Scale: 1}"},
"vm32y": {"operand.Mem{Base: reg.R13, Index: reg.Y4, Scale: 1}"},
"vm64y": {"operand.Mem{Base: reg.R13, Index: reg.Y8, Scale: 1}"},
// Relative
"rel8": {"operand.Rel(math.MaxInt8)"},
"rel32": {"operand.Rel(math.MaxInt32)", "operand.LabelRef(\"lbl\")"},
func (c *ctorsstress) function(fn *api.Function) {
c.Printf("func Test%sValidFormsCorrectInstruction(t *testing.T) {", fn.Name())
for _, f := range fn.Forms {
name := strings.Join(f.Signature(), "_")
c.Printf("t.Run(\"form=%s\", func(t *testing.T) {\n", name)
s := TestSignature(f)
c.Printf("expect := &%s\n", construct(fn, f, s))
c.Printf("got, err := %s(%s);\n", fn.Name(), s.Arguments())
c.Printf("if err != nil { t.Fatal(err) }\n")
c.Printf("if !reflect.DeepEqual(got, expect) { t.Fatal(\"mismatch\") }\n")
c.Printf("})\n")
}
c.Printf("}\n\n")
}
type ctorsbench struct {
cfg printer.Config
prnt.Generator
}
// NewCtorsBench autogenerates a benchmark for the instruction constructors.
func NewCtorsBench(cfg printer.Config) Interface {
return GoFmt(&ctorsbench{cfg: cfg})
}
func (c *ctorsbench) Generate(is []inst.Instruction) ([]byte, error) {
c.Printf("// %s\n\n", c.cfg.GeneratedWarning())
c.BuildTag("stress")
c.NL()
c.Printf("package x86\n\n")
c.Printf("import (\n")
c.Printf("\t\"time\"\n")
c.Printf("\t\"testing\"\n")
c.Printf(")\n\n")
c.Printf("func BenchmarkConstructors(b *testing.B) {\n")
c.Printf("start := time.Now()\n")
c.Printf("for i := 0; i < b.N; i++ {\n")
n := 0
for _, fn := range api.InstructionsFunctions(is) {
for _, f := range fn.Forms {
n++
c.Printf("%s(%s)\n", fn.Name(), TestSignature(f).Arguments())
}
}
c.Printf("}\n")
c.Printf("elapsed := time.Since(start)\n")
c.Printf("\tb.ReportMetric(%d * float64(b.N) / elapsed.Seconds(), \"inst/s\")\n", n)
c.Printf("}\n\n")
return c.Result()
}
func construct(fn *api.Function, f inst.Form, s api.Signature) string {
buf := bytes.NewBuffer(nil)
fmt.Fprintf(buf, "ir.Instruction{\n")
fmt.Fprintf(buf, "\tOpcode: %#v,\n", fn.Instruction.Opcode)
if len(fn.Suffixes) > 0 {
fmt.Fprintf(buf, "\tSuffixes: %#v,\n", fn.Suffixes.Strings())
}
fmt.Fprintf(buf, "\tOperands: %s,\n", s.ParameterSlice())
// Inputs.
fmt.Fprintf(buf, "\tInputs: %s,\n", operandsWithAction(f, inst.R, s))
// Outputs.
fmt.Fprintf(buf, "\tOutputs: %s,\n", operandsWithAction(f, inst.W, s))
// ISAs.
if len(f.ISA) > 0 {
fmt.Fprintf(buf, "\tISA: %#v,\n", f.ISA)
}
// Branch variables.
if fn.Instruction.IsTerminal() {
fmt.Fprintf(buf, "\tIsTerminal: true,\n")
}
if fn.Instruction.IsBranch() {
fmt.Fprintf(buf, "\tIsBranch: true,\n")
fmt.Fprintf(buf, "\tIsConditional: %#v,\n", fn.Instruction.IsConditionalBranch())
}
// Cancelling inputs.
if f.CancellingInputs {
fmt.Fprintf(buf, "\tCancellingInputs: true,\n")
}
fmt.Fprintf(buf, "}")
return buf.String()
}
func operandsWithAction(f inst.Form, a inst.Action, s api.Signature) string {
var opexprs []string
for i, op := range f.Operands {
if op.Action.ContainsAny(a) {
opexprs = append(opexprs, s.ParameterName(i))
}
}
for _, op := range f.ImplicitOperands {
if op.Action.ContainsAny(a) {
opexprs = append(opexprs, api.ImplicitRegister(op.Register))
}
}
if len(opexprs) == 0 {
return "nil"
}
return fmt.Sprintf("[]%s{%s}", api.OperandType, strings.Join(opexprs, ", "))
}

View File

@@ -1,6 +1,7 @@
package gen
import (
"github.com/mmcloughlin/avo/internal/api"
"github.com/mmcloughlin/avo/internal/inst"
"github.com/mmcloughlin/avo/internal/prnt"
"github.com/mmcloughlin/avo/printer"
@@ -57,8 +58,21 @@ func (g *godata) Generate(is []inst.Instruction) ([]byte, error) {
g.Printf("},\n")
}
if f.CancellingInputs {
g.Printf("CancellingInputs: true,\n")
g.Printf("EncodingType: %#v,\n", f.EncodingType)
for _, flag := range []struct {
Field string
Enabled bool
}{
{"CancellingInputs", f.CancellingInputs},
{"Zeroing", f.Zeroing},
{"EmbeddedRounding", f.EmbeddedRounding},
{"SuppressAllExceptions", f.SuppressAllExceptions},
{"Broadcast", f.Broadcast},
} {
if flag.Enabled {
g.Printf("%s: true,\n", flag.Field)
}
}
g.Printf("},\n")
@@ -90,6 +104,7 @@ func NewGoDataTest(cfg printer.Config) Interface {
func (g *godatatest) Generate(is []inst.Instruction) ([]byte, error) {
g.Printf("// %s\n\n", g.cfg.GeneratedWarning())
g.NL()
g.Printf("package inst_test\n\n")
g.Printf(`import (
@@ -98,7 +113,7 @@ func (g *godatatest) Generate(is []inst.Instruction) ([]byte, error) {
"%s/internal/inst"
)
`, pkg)
`, api.Package)
g.Printf("var raw = %#v\n\n", is)

View File

@@ -6,6 +6,7 @@ import (
"sort"
"strings"
"github.com/mmcloughlin/avo/internal/api"
"github.com/mmcloughlin/avo/internal/inst"
"github.com/mmcloughlin/avo/internal/prnt"
"github.com/mmcloughlin/avo/printer"
@@ -29,7 +30,7 @@ func (m *mov) Generate(is []inst.Instruction) ([]byte, error) {
m.Printf("import (\n")
m.Printf("\t\"go/types\"\n")
m.NL()
m.Printf("\t\"%s/operand\"\n", pkg)
m.Printf("\t%q\n", api.ImportPath(api.OperandPackage))
m.Printf(")\n\n")
m.Printf("func (c *Context) mov(a, b operand.Op, an, bn int, t *types.Basic) {\n")
@@ -48,15 +49,17 @@ func (m *mov) Generate(is []inst.Instruction) ([]byte, error) {
func (m *mov) instruction(i inst.Instruction) {
f := flags(i)
sizes, err := formsizes(i)
mfs, err := movforms(i)
if err != nil {
m.AddError(err)
return
}
for _, size := range sizes {
for _, mf := range mfs {
conds := []string{
fmt.Sprintf("an == %d", size.A),
fmt.Sprintf("bn == %d", size.B),
fmt.Sprintf("an == %d", opsize[mf.A]),
fmt.Sprintf("%s(a)", api.CheckerName(mf.A)),
fmt.Sprintf("bn == %d", opsize[mf.B]),
fmt.Sprintf("%s(b)", api.CheckerName(mf.B)),
}
for c, on := range f {
cmp := map[bool]string{true: "!=", false: "=="}
@@ -71,15 +74,29 @@ func (m *mov) instruction(i inst.Instruction) {
// ismov decides whether the given instruction is a plain move instruction.
func ismov(i inst.Instruction) bool {
if i.AliasOf != "" || !strings.HasPrefix(i.Opcode, "MOV") {
// Ignore aliases.
if i.AliasOf != "" {
return false
}
// Accept specific move instruction prefixes.
prefixes := []string{"MOV", "KMOV", "VMOV"}
accept := false
for _, prefix := range prefixes {
accept = strings.HasPrefix(i.Opcode, prefix) || accept
}
if !accept {
return false
}
// Exclude some cases based on instruction descriptions.
exclude := []string{"Packed", "Duplicate", "Aligned", "Hint", "Swapping"}
for _, substring := range exclude {
if strings.Contains(i.Summary, substring) {
return false
}
}
return true
}
@@ -100,34 +117,27 @@ func flags(i inst.Instruction) map[string]bool {
return f
}
type movsize struct{ A, B int8 }
type movform struct{ A, B string }
func (s movsize) sortkey() uint16 { return (uint16(s.A) << 8) | uint16(s.B) }
func formsizes(i inst.Instruction) ([]movsize, error) {
set := map[movsize]bool{}
func movforms(i inst.Instruction) ([]movform, error) {
var mfs []movform
for _, f := range i.Forms {
if f.Arity() != 2 {
continue
}
s := movsize{
A: opsize[f.Operands[0].Type],
B: opsize[f.Operands[1].Type],
mf := movform{
A: f.Operands[0].Type,
B: f.Operands[1].Type,
}
if s.A < 0 || s.B < 0 {
if opsize[mf.A] < 0 || opsize[mf.B] < 0 {
continue
}
if s.A == 0 || s.B == 0 {
if opsize[mf.A] == 0 || opsize[mf.B] == 0 {
return nil, errors.New("unknown operand type")
}
set[s] = true
mfs = append(mfs, mf)
}
var ss []movsize
for s := range set {
ss = append(ss, s)
}
sort.Slice(ss, func(i, j int) bool { return ss[i].sortkey() < ss[j].sortkey() })
return ss, nil
return mfs, nil
}
var opsize = map[string]int8{
@@ -140,9 +150,14 @@ var opsize = map[string]int8{
"r32": 4,
"r64": 8,
"xmm": 16,
"ymm": 32,
"zmm": 64,
"m8": 1,
"m16": 2,
"m32": 4,
"m64": 8,
"m128": 16,
"m256": 32,
"m512": 64,
"k": 8,
}

288
internal/gen/optab.go Normal file
View File

@@ -0,0 +1,288 @@
package gen
import (
"fmt"
"sort"
"strconv"
"strings"
"github.com/mmcloughlin/avo/internal/api"
"github.com/mmcloughlin/avo/internal/inst"
"github.com/mmcloughlin/avo/internal/prnt"
"github.com/mmcloughlin/avo/printer"
)
type optab struct {
prnt.Generator
cfg printer.Config
table *Table
}
// NewOptab builds the operator table. This contains a more compact
// representation of the instruction database, containing the data needed for
// instruction builders to match against provided operands, and build the
// selected instruction.
func NewOptab(cfg printer.Config) Interface {
return GoFmt(&optab{cfg: cfg})
}
func (t *optab) Generate(is []inst.Instruction) ([]byte, error) {
t.Printf("// %s\n\n", t.cfg.GeneratedWarning())
t.Printf("package x86\n\n")
t.Printf("import (\n")
t.Printf("\t%q\n", api.ImportPath(api.OperandPackage))
t.Printf("\t%q\n", api.ImportPath(api.RegisterPackage))
t.Printf(")\n\n")
// Generate instruction data table.
t.table = NewTable(is)
// Size constants.
t.maxOperands(is)
// Types.
t.operandTypeEnum(is)
t.implicitRegisterEnum(is)
t.enum(t.table.Suffix())
t.suffixesType(is)
t.suffixesClassEnum(is)
t.isasEnum(is)
t.opcodeEnum(is)
// Forms table.
t.forms(is)
return t.Result()
}
func (t *optab) maxOperands(is []inst.Instruction) {
max := 0
for _, i := range inst.Instructions {
for _, f := range i.Forms {
a := len(f.Operands) + len(f.ImplicitOperands)
if a > max {
max = a
}
}
}
t.Comment("maxoperands is the maximum number of operands in an instruction form, including implicit operands.")
t.Printf("const maxoperands = %d\n\n", max)
}
func (t *optab) operandTypeEnum(is []inst.Instruction) {
// Operand type enum.
e := t.table.OperandType()
t.enum(e)
// Operand match function.
types := inst.OperandTypes(is)
t.Printf("func (%s %s) Match(op %s) bool {\n", e.Receiver(), e.Name(), api.OperandType)
t.Printf("\tswitch %s {\n", e.Receiver())
t.Printf("\t\tdefault: return false\n")
for _, typ := range types {
t.Printf("\t\tcase %s: return %s(op)\n", t.table.OperandTypeConst(typ), api.CheckerName(typ))
}
t.Printf("\t}\n")
t.Printf("}\n\n")
}
func (t *optab) implicitRegisterEnum(is []inst.Instruction) {
// Implicit register enum.
e := t.table.ImplicitRegister()
t.enum(e)
// Register conversion function.
registers := inst.ImplicitRegisters(is)
t.Printf("func (%s %s) Register() %s {\n", e.Receiver(), e.Name(), api.RegisterType)
t.Printf("\tswitch %s {\n", e.Receiver())
t.Printf("\t\tdefault: panic(\"unexpected implicit register type\")\n")
for _, r := range registers {
t.Printf("\t\tcase %s: return %s\n", t.table.ImplicitRegisterConst(r), api.ImplicitRegister(r))
}
t.Printf("\t}\n")
t.Printf("}\n\n")
}
func (t *optab) suffixesType(is []inst.Instruction) {
// Declare the type as an array. This requires us to know the maximum number
// of suffixes an instruction can have.
max := 0
for _, class := range inst.SuffixesClasses(is) {
for _, suffixes := range class {
if len(suffixes) > max {
max = len(suffixes)
}
}
}
t.Comment("maxsuffixes is the maximum number of suffixes an instruction can have.")
t.Printf("const maxsuffixes = %d\n\n", max)
name := t.table.SuffixesTypeName()
t.Printf("type %s [maxsuffixes]%s\n", name, t.table.Suffix().Name())
// Conversion function to list of strings.
mapname := name + "stringsmap"
t.Printf("func (s %s) Strings() []string {\n", name)
t.Printf("return %s[s]", mapname)
t.Printf("}\n")
var entries []string
for _, class := range inst.SuffixesClasses(is) {
for _, suffixes := range class {
entry := fmt.Sprintf("%s: %#v", t.table.SuffixesConst(suffixes), suffixes.Strings())
entries = append(entries, entry)
}
}
t.Printf("var %s = map[%s][]string{\n", mapname, name)
sort.Strings(entries)
for _, entry := range entries {
t.Printf("%s,\n", entry)
}
t.Printf("}\n")
}
func (t *optab) suffixesClassEnum(is []inst.Instruction) {
// Suffixes class enum.
e := t.table.SuffixesClass()
t.enum(e)
// Mapping method to the set of accepted suffixes.
sets := map[string]string{}
for key, class := range inst.SuffixesClasses(is) {
var entries []string
for _, suffixes := range class {
entry := fmt.Sprintf("%s: true", t.table.SuffixesConst(suffixes))
entries = append(entries, entry)
}
sort.Strings(entries)
sets[api.SuffixesClassIdentifier(key)] = "{" + strings.Join(entries, ", ") + "}"
}
settype := fmt.Sprintf("map[%s]bool", t.table.SuffixesTypeName())
t.mapping(e, "SuffixesSet", settype, "nil", sets)
}
func (t *optab) isasEnum(is []inst.Instruction) {
// ISAs enum.
e := t.table.ISAs()
t.enum(e)
// Mapping method to produce the list of ISAs.
lists := map[string]string{}
for _, isas := range inst.ISACombinations(is) {
lists[api.ISAsIdentifier(isas)] = fmt.Sprintf("%#v", isas)
}
t.mapping(e, "List", "[]string", "nil", lists)
}
func (t *optab) opcodeEnum(is []inst.Instruction) {
e := t.table.Opcode()
t.enum(e)
t.stringmethod(e)
}
func (t *optab) forms(is []inst.Instruction) {
// We require all instructions for a given opcode to be in a contiguous
// block. This is likely true already but we'll make a sorted copy to ensure
// the optab is robust to changes elsewhere.
is = append([]inst.Instruction(nil), is...)
sort.Slice(is, func(i, j int) bool {
return is[i].Opcode < is[j].Opcode
})
// Output instruction forms table.
table := "forms"
t.Printf("var %s = []form{\n", table)
for _, i := range is {
for _, f := range i.Forms {
t.Printf("{")
// Basic properties.
t.Printf("%s, ", t.table.OpcodeConst(i.Opcode))
t.Printf("%s, ", t.table.SuffixesClassConst(f.SuffixesClass()))
t.Printf("%s, ", Features(i, f))
t.Printf("%s, ", t.table.ISAsConst(f.ISA))
// Operands.
t.Printf("%d, ", len(f.Operands))
t.Printf("oprnds{")
for _, op := range f.Operands {
t.Printf(
"{uint8(%s),false,%s},",
t.table.OperandTypeConst(op.Type),
Action(op.Action),
)
}
for _, op := range f.ImplicitOperands {
t.Printf(
"{uint8(%s),true,%s},",
t.table.ImplicitRegisterConst(op.Register),
Action(op.Action),
)
}
t.Printf("}")
t.Printf("},\n")
}
}
t.Printf("}\n\n")
// Build mapping from opcode to corresponding forms.
forms := map[string]string{}
n := 0
for _, i := range is {
e := n + len(i.Forms)
forms[i.Opcode] = fmt.Sprintf("%s[%d:%d]", table, n, e)
n = e
}
t.mapping(t.table.Opcode(), "Forms", "[]form", "nil", forms)
}
func (t *optab) enum(e *Enum) {
// Type declaration.
t.Comment(e.Doc()...)
t.Printf("type %s %s\n\n", e.Name(), e.UnderlyingType())
// Supported values.
t.Printf("const (\n")
t.Printf("\t%s %s = iota\n", e.None(), e.name)
for _, name := range e.ConstNames() {
t.Printf("\t%s\n", name)
}
t.Printf("\t%s\n", e.MaxName())
t.Printf(")\n\n")
}
func (t *optab) mapping(e *Enum, name, ret, zero string, to map[string]string) {
table := strings.ToLower(e.Name() + name + "table")
r := e.Receiver()
t.Printf("func (%s %s) %s() %s {\n", r, e.Name(), name, ret)
t.Printf("if %s < %s && %s < %s {\n", e.None(), r, r, e.MaxName())
t.Printf("return %s[%s-1]\n", table, r)
t.Printf("}\n")
t.Printf("return %s\n", zero)
t.Printf("}\n\n")
t.Printf("var %s = []%s{\n", table, ret)
for _, value := range e.Values() {
t.Printf("\t%s,\n", to[value])
}
t.Printf("}\n\n")
}
func (t *optab) stringmethod(e *Enum) {
s := map[string]string{}
for _, value := range e.Values() {
s[value] = strconv.Quote(value)
}
t.mapping(e, "String", "string", `""`, s)
}

View File

@@ -1,122 +0,0 @@
package gen
import (
"bytes"
"fmt"
"sort"
"strconv"
"strings"
"text/tabwriter"
"github.com/mmcloughlin/avo/internal/inst"
)
// signature provides access to details about the signature of an instruction function.
type signature interface {
ParameterList() string
Arguments() string
ParameterName(int) string
ParameterSlice() string
Length() string
}
// argslist is the signature for a function with the given named parameters.
type argslist []string
func (a argslist) ParameterList() string { return strings.Join(a, ", ") + " " + operandType }
func (a argslist) Arguments() string { return strings.Join(a, ", ") }
func (a argslist) ParameterName(i int) string { return a[i] }
func (a argslist) ParameterSlice() string {
return fmt.Sprintf("[]%s{%s}", operandType, strings.Join(a, ", "))
}
func (a argslist) Length() string { return strconv.Itoa(len(a)) }
// variadic is the signature for a variadic function.
type variadic struct {
name string
}
func (v variadic) ParameterList() string { return v.name + " ..." + operandType }
func (v variadic) Arguments() string { return v.name + "..." }
func (v variadic) ParameterName(i int) string { return fmt.Sprintf("%s[%d]", v.name, i) }
func (v variadic) ParameterSlice() string { return v.name }
func (v variadic) Length() string { return fmt.Sprintf("len(%s)", v.name) }
// niladic is the signature for a function with no arguments.
type niladic struct{}
func (n niladic) ParameterList() string { return "" }
func (n niladic) Arguments() string { return "" }
func (n niladic) ParameterName(i int) string { panic("niladic function has no parameters") }
func (n niladic) ParameterSlice() string { return "nil" }
func (n niladic) Length() string { return "0" }
// params generates the function parameters and a function.
func params(i inst.Instruction) signature {
// Handle the case of forms with multiple arities.
switch {
case i.IsVariadic():
return variadic{name: "ops"}
case i.IsNiladic():
return niladic{}
}
// Generate nice-looking variable names.
n := i.Arity()
ops := make([]string, n)
count := map[string]int{}
for j := 0; j < n; j++ {
// Collect unique lowercase bytes from first characters of operand types.
s := map[byte]bool{}
for _, f := range i.Forms {
c := f.Operands[j].Type[0]
if 'a' <= c && c <= 'z' {
s[c] = true
}
}
// Operand name is the sorted bytes.
var b []byte
for c := range s {
b = append(b, c)
}
sort.Slice(b, func(i, j int) bool { return b[i] < b[j] })
name := string(b)
// Append a counter if we've seen it already.
m := count[name]
count[name]++
if m > 0 {
name += strconv.Itoa(m)
}
ops[j] = name
}
return argslist(ops)
}
// doc generates the lines of the function comment.
func doc(i inst.Instruction) []string {
lines := []string{
fmt.Sprintf("%s: %s.", i.Opcode, i.Summary),
"",
"Forms:",
"",
}
// Write a table of instruction forms.
buf := bytes.NewBuffer(nil)
w := tabwriter.NewWriter(buf, 0, 0, 1, ' ', 0)
for _, f := range i.Forms {
row := i.Opcode + "\t" + strings.Join(f.Signature(), "\t") + "\n"
fmt.Fprint(w, row)
}
w.Flush()
tbl := strings.TrimSpace(buf.String())
for _, line := range strings.Split(tbl, "\n") {
lines = append(lines, "\t"+line)
}
return lines
}

85
internal/gen/testing.go Normal file
View File

@@ -0,0 +1,85 @@
package gen
import (
"github.com/mmcloughlin/avo/internal/api"
"github.com/mmcloughlin/avo/internal/inst"
"github.com/mmcloughlin/avo/internal/prnt"
)
// DeclareTestArguments prints a block of variables declaring a valid operand of
// each operand type.
func DeclareTestArguments(g *prnt.Generator) {
g.Printf("var (\n")
for _, arg := range validArgs {
g.Printf("\t%s operand.Op = %s\n", TestArgumentName(arg.Type), arg.Code)
}
g.Printf(")\n")
}
// TestSignature returns a function signature with arguments matching the given
// instruction form. Requires variables declared by DeclareTestArguments().
func TestSignature(f inst.Form) api.Signature {
var names []string
for _, op := range f.Operands {
names = append(names, TestArgumentName(op.Type))
}
return api.ArgsList(names)
}
// TestArgumentName returns the name of the variable of operand type t declared
// by DeclareTestArguments().
func TestArgumentName(t string) string {
return "op" + t
}
var validArgs = []struct {
Type string
Code string
}{
// Immediates
{"1", "operand.Imm(1)"},
{"3", "operand.Imm(3)"},
{"imm2u", "operand.Imm(3)"},
{"imm8", "operand.Imm(math.MaxInt8)"},
{"imm16", "operand.Imm(math.MaxInt16)"},
{"imm32", "operand.Imm(math.MaxInt32)"},
{"imm64", "operand.Imm(math.MaxInt64)"},
// Registers
{"al", "reg.AL"},
{"cl", "reg.CL"},
{"ax", "reg.AX"},
{"eax", "reg.EAX"},
{"rax", "reg.RAX"},
{"r8", "reg.CH"},
{"r16", "reg.R9W"},
{"r32", "reg.R10L"},
{"r64", "reg.R11"},
{"xmm0", "reg.X0"},
{"xmm", "reg.X7"},
{"ymm", "reg.Y15"},
{"zmm", "reg.Z31"},
{"k", "reg.K7"},
// Memory
{"m", "operand.Mem{Base: reg.BX, Index: reg.CX, Scale: 2}"},
{"m8", "operand.Mem{Base: reg.BL, Index: reg.CH, Scale: 1}"},
{"m16", "operand.Mem{Base: reg.BX, Index: reg.CX, Scale: 2}"},
{"m32", "operand.Mem{Base: reg.EBX, Index: reg.ECX, Scale: 4}"},
{"m64", "operand.Mem{Base: reg.RBX, Index: reg.RCX, Scale: 8}"},
{"m128", "operand.Mem{Base: reg.RBX, Index: reg.RCX, Scale: 8}"},
{"m256", "operand.Mem{Base: reg.RBX, Index: reg.RCX, Scale: 8}"},
{"m512", "operand.Mem{Base: reg.RBX, Index: reg.RCX, Scale: 8}"},
// Vector memory
{"vm32x", "operand.Mem{Base: reg.R13, Index: reg.X4, Scale: 1}"},
{"vm64x", "operand.Mem{Base: reg.R13, Index: reg.X8, Scale: 1}"},
{"vm32y", "operand.Mem{Base: reg.R13, Index: reg.Y4, Scale: 1}"},
{"vm64y", "operand.Mem{Base: reg.R13, Index: reg.Y8, Scale: 1}"},
{"vm32z", "operand.Mem{Base: reg.R13, Index: reg.Z4, Scale: 1}"},
{"vm64z", "operand.Mem{Base: reg.R13, Index: reg.Z8, Scale: 1}"},
// Relative
{"rel8", "operand.Rel(math.MaxInt8)"},
{"rel32", "operand.LabelRef(\"lbl\")"},
}

View File

@@ -1,19 +0,0 @@
package gen
// cross returns the cross product of the lists in x.
func cross(x [][]string) [][]string {
r := [][]string{nil}
for _, s := range x {
var nxt [][]string
for _, pre := range r {
for _, a := range s {
concat := make([]string, len(pre), len(pre)+1)
copy(concat, pre)
concat = append(concat, a)
nxt = append(nxt, concat)
}
}
r = nxt
}
return r
}

View File

@@ -1,46 +0,0 @@
package gen
import (
"reflect"
"testing"
)
func TestCross(t *testing.T) {
x := [][]string{
{"a", "b", "c"},
{"1", "2"},
{"!", "?"},
}
expect := [][]string{
{"a", "1", "!"},
{"a", "1", "?"},
{"a", "2", "!"},
{"a", "2", "?"},
{"b", "1", "!"},
{"b", "1", "?"},
{"b", "2", "!"},
{"b", "2", "?"},
{"c", "1", "!"},
{"c", "1", "?"},
{"c", "2", "!"},
{"c", "2", "?"},
}
got := cross(x)
if !reflect.DeepEqual(got, expect) {
t.Errorf("bad cross product")
}
}
func TestCrossSimple(t *testing.T) {
x := [][]string{
{"a", "b"},
}
expect := [][]string{
{"a"},
{"b"},
}
got := cross(x)
if !reflect.DeepEqual(got, expect) {
t.Errorf("bad cross product")
}
}