Supporting extra instructions not included in the Opcodes database is currently a challenge. Short of migrating to an entirely different source (such as #23), the options are either to patch the XML data file or to append additional instructions at the loading phase. An example of patching the XML was shown in the as-yet unlanded PR #234. This shows the XML patching approach is unwieldy and requires more information than we actually need (for example instruction form encodings). In #335 we discussed the alternative of adding extra instructions during loading. This has the advantage of using avo's simpler internal data structure. This PR prepares for using that approach by adding an `internal/opcodesextra` package, intended to contain manually curated lists of extra instructions to add to the instruction database during loading. At the moment, the only instruction added here is the `MOVLQZX` instruction that's already handled this way. Updates #335 #234 #23
869 lines
23 KiB
Go
869 lines
23 KiB
Go
package load
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"path/filepath"
|
|
"reflect"
|
|
"regexp"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/mmcloughlin/avo/internal/inst"
|
|
"github.com/mmcloughlin/avo/internal/opcodescsv"
|
|
"github.com/mmcloughlin/avo/internal/opcodesextra"
|
|
"github.com/mmcloughlin/avo/internal/opcodesxml"
|
|
)
|
|
|
|
// This file is a mess. Some of this complexity is unavoidable, since the state
|
|
// of x86 instruction databases is also a mess, especially when it comes to
|
|
// idiosyncrasies of the Go assembler implementation. Some of the complexity is
|
|
// probably avoidable by migrating to using Intel XED
|
|
// (https://github.com/mmcloughlin/avo/issues/23), but for now this is an unholy
|
|
// mix of PeachPy's Opcodes database and Go's x86 CSV file.
|
|
//
|
|
// The goal is simply to keep as much of the uglyness in this file as possible,
|
|
// producing a clean instruction database for the rest of avo to use. Any nasty
|
|
// logic here should be backed up with a test somewhere to ensure the result is
|
|
// correct, even if the code that produced it is awful.
|
|
|
|
// Expected data source filenames.
|
|
const (
|
|
DefaultCSVName = "x86.v0.2.csv"
|
|
DefaultOpcodesXMLName = "x86_64.xml"
|
|
)
|
|
|
|
// Loader builds an instruction database from underlying datasources.
|
|
type Loader struct {
|
|
X86CSVPath string
|
|
OpcodesXMLPath string
|
|
|
|
alias map[opcodescsv.Alias]string
|
|
order map[string]opcodescsv.OperandOrder
|
|
}
|
|
|
|
// NewLoaderFromDataDir constructs an instruction loader from datafiles in the
|
|
// given directory. The files themselves are expected to have the standard
|
|
// names: see Default*Name constants.
|
|
func NewLoaderFromDataDir(dir string) *Loader {
|
|
return &Loader{
|
|
X86CSVPath: filepath.Join(dir, DefaultCSVName),
|
|
OpcodesXMLPath: filepath.Join(dir, DefaultOpcodesXMLName),
|
|
}
|
|
}
|
|
|
|
// Load performs instruction loading.
|
|
func (l *Loader) Load() ([]inst.Instruction, error) {
|
|
if err := l.init(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Load Opcodes XML file.
|
|
iset, err := opcodesxml.ReadFile(l.OpcodesXMLPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Load opcodes XML data, grouped by Go opcode.
|
|
im := map[string]*inst.Instruction{}
|
|
for _, i := range iset.Instructions {
|
|
for _, f := range i.Forms {
|
|
if !l.include(f) {
|
|
continue
|
|
}
|
|
|
|
for _, opcode := range l.gonames(f) {
|
|
if im[opcode] == nil {
|
|
im[opcode] = &inst.Instruction{
|
|
Opcode: opcode,
|
|
Summary: i.Summary,
|
|
}
|
|
}
|
|
forms := l.forms(opcode, f)
|
|
im[opcode].Forms = append(im[opcode].Forms, forms...)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add extras to our list.
|
|
for _, e := range opcodesextra.Instructions() {
|
|
im[e.Opcode] = e
|
|
}
|
|
|
|
// Merge aliased forms. This is primarily for MOVQ (issue #50).
|
|
for _, a := range aliases {
|
|
if existing, found := im[a.From]; found {
|
|
im[a.To].Forms = append(im[a.To].Forms, existing.Forms...)
|
|
}
|
|
}
|
|
|
|
// Apply list of aliases.
|
|
for _, a := range aliases {
|
|
cpy := *im[a.To]
|
|
cpy.Opcode = a.From
|
|
cpy.AliasOf = a.To
|
|
im[a.From] = &cpy
|
|
}
|
|
|
|
// Dedupe forms.
|
|
for _, i := range im {
|
|
i.Forms = dedupe(i.Forms)
|
|
}
|
|
|
|
// Resolve forms that have VEX and EVEX encoded forms.
|
|
for _, i := range im {
|
|
i.Forms, err = vexevex(i.Forms)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// Convert to a slice. Sort instructions and forms for reproducibility.
|
|
is := make([]inst.Instruction, 0, len(im))
|
|
for _, i := range im {
|
|
is = append(is, *i)
|
|
}
|
|
|
|
sort.Slice(is, func(i, j int) bool {
|
|
return is[i].Opcode < is[j].Opcode
|
|
})
|
|
|
|
for _, i := range im {
|
|
sortforms(i.Forms)
|
|
}
|
|
|
|
return is, nil
|
|
}
|
|
|
|
func (l *Loader) init() error {
|
|
icsv, err := opcodescsv.ReadFile(l.X86CSVPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
l.alias, err = opcodescsv.BuildAliasMap(icsv)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
l.order = opcodescsv.BuildOrderMap(icsv)
|
|
|
|
return nil
|
|
}
|
|
|
|
// include decides whether to include the instruction form in the avo listing.
|
|
// This discards some opcodes that are not supported in Go.
|
|
func (l Loader) include(f opcodesxml.Form) bool {
|
|
// Exclude certain ISAs simply not present in Go
|
|
for _, isa := range f.ISA {
|
|
switch isa.ID {
|
|
// AMD-only.
|
|
case "TBM", "CLZERO", "FMA4", "XOP", "SSE4A", "3dnow!", "3dnow!+":
|
|
return false
|
|
// AVX512PF doesn't work without some special case handling, and is only on Knights Landing/Knights Mill.
|
|
case "AVX512PF":
|
|
return false
|
|
// Incomplete support for some prefetching instructions.
|
|
case "PREFETCH", "PREFETCHW", "PREFETCHWT1", "CLWB":
|
|
return false
|
|
// Remaining oddities.
|
|
case "MONITORX", "FEMMS":
|
|
return false
|
|
}
|
|
}
|
|
|
|
// Go appears to have skeleton support for MMX instructions. See the many TODO lines in the testcases:
|
|
// Reference: https://github.com/golang/go/blob/649b89377e91ad6dbe710784f9e662082d31a1ff/src/cmd/asm/internal/asm/testdata/amd64enc.s#L3310-L3312
|
|
//
|
|
// //TODO: PALIGNR $7, (BX), M2 // 0f3a0f1307
|
|
// //TODO: PALIGNR $7, (R11), M2 // 410f3a0f1307
|
|
// //TODO: PALIGNR $7, M2, M2 // 0f3a0fd207
|
|
//
|
|
if f.MMXMode == "MMX" {
|
|
return false
|
|
}
|
|
|
|
// x86 csv contains a number of CMOV* instructions which are actually not valid
|
|
// Go instructions. The valid Go forms should have different opcodes from GNU.
|
|
// Therefore a decent "heuristic" is CMOV* instructions that do not have
|
|
// aliases.
|
|
if strings.HasPrefix(f.GASName, "cmov") && l.lookupAlias(f) == "" {
|
|
return false
|
|
}
|
|
|
|
// Some specific exclusions.
|
|
switch f.GASName {
|
|
// Certain branch instructions appear to not be supported.
|
|
//
|
|
// Reference: https://github.com/golang/go/blob/649b89377e91ad6dbe710784f9e662082d31a1ff/src/cmd/asm/internal/asm/testdata/amd64enc.s#L757
|
|
//
|
|
// //TODO: CALLQ* (BX) // ff13
|
|
//
|
|
// Reference: https://github.com/golang/go/blob/649b89377e91ad6dbe710784f9e662082d31a1ff/src/cmd/asm/internal/asm/testdata/amd64enc.s#L2108
|
|
//
|
|
// //TODO: LJMPL* (R11) // 41ff2b
|
|
//
|
|
case "callq", "jmpl":
|
|
return false
|
|
// MOVABS doesn't seem to be supported either.
|
|
//
|
|
// Reference: https://github.com/golang/go/blob/1ac84999b93876bb06887e483ae45b27e03d7423/src/cmd/asm/internal/asm/testdata/amd64enc.s#L2354
|
|
//
|
|
// //TODO: MOVABSB 0x123456789abcdef1, AL // a0f1debc9a78563412
|
|
//
|
|
case "movabs":
|
|
return false
|
|
// Only one XLAT form is supported.
|
|
//
|
|
// Reference: https://github.com/golang/arch/blob/b19384d3c130858bb31a343ea8fce26be71b5998/x86/x86.v0.2.csv#L2221-L2222
|
|
//
|
|
// "XLATB","XLAT","xlat","D7","V","V","","ignoreREXW","","",""
|
|
// "XLATB","XLAT","xlat","REX.W D7","N.E.","V","","pseudo","","",""
|
|
//
|
|
// Reference: https://github.com/golang/go/blob/b3294d9491b898396e134bad5412d85337c37b64/src/cmd/internal/obj/x86/asm6.go#L1519
|
|
//
|
|
// {AXLAT, ynone, Px, opBytes{0xd7}},
|
|
//
|
|
// TODO(mbm): confirm the Px prefix means non REX mode
|
|
case "xlatb":
|
|
return f.Encoding.REX == nil
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func (l Loader) lookupAlias(f opcodesxml.Form) string {
|
|
// Attempt lookup with datasize.
|
|
k := opcodescsv.Alias{
|
|
Opcode: f.GASName,
|
|
DataSize: datasize(f),
|
|
NumOperands: len(f.Operands),
|
|
}
|
|
if a := l.alias[k]; a != "" {
|
|
return a
|
|
}
|
|
|
|
// Fallback to unknown datasize.
|
|
k.DataSize = 0
|
|
return l.alias[k]
|
|
}
|
|
|
|
func (l Loader) gonames(f opcodesxml.Form) []string {
|
|
s := datasize(f)
|
|
|
|
// Suspect a "bug" in x86 CSV for the CVTTSD2SQ instruction, as CVTTSD2SL appears twice.
|
|
//
|
|
// Reference: https://github.com/golang/arch/blob/b19384d3c130858bb31a343ea8fce26be71b5998/x86/x86.v0.2.csv#L345-L346
|
|
//
|
|
// "CVTTSD2SI r32, xmm2/m64","CVTTSD2SL xmm2/m64, r32","cvttsd2si xmm2/m64, r32","F2 0F 2C /r","V","V","SSE2","operand16,operand32","w,r","Y","32"
|
|
// "CVTTSD2SI r64, xmm2/m64","CVTTSD2SL xmm2/m64, r64","cvttsd2siq xmm2/m64, r64","F2 REX.W 0F 2C /r","N.E.","V","SSE2","","w,r","Y","64"
|
|
//
|
|
// Reference: https://github.com/golang/go/blob/048c9164a0c5572df18325e377473e7893dbfb07/src/cmd/internal/obj/x86/asm6.go#L1073-L1074
|
|
//
|
|
// {ACVTTSD2SL, yxcvfl, Pf2, opBytes{0x2c}},
|
|
// {ACVTTSD2SQ, yxcvfq, Pw, opBytes{Pf2, 0x2c}},
|
|
//
|
|
if f.GASName == "cvttsd2si" && s == 64 {
|
|
return []string{"CVTTSD2SQ"}
|
|
}
|
|
|
|
// Return alias if available.
|
|
if a := l.lookupAlias(f); a != "" {
|
|
return []string{a}
|
|
}
|
|
|
|
// Some odd special cases.
|
|
// TODO(mbm): can this be handled by processing csv entries with slashes /
|
|
if f.GoName == "RET" && len(f.Operands) == 1 {
|
|
return []string{"RETFW", "RETFL", "RETFQ"}
|
|
}
|
|
|
|
// IMUL 3-operand forms are not recorded correctly in either x86 CSV or opcodes. They are supposed to be called IMUL3{W,L,Q}
|
|
//
|
|
// Reference: https://github.com/golang/go/blob/649b89377e91ad6dbe710784f9e662082d31a1ff/src/cmd/internal/obj/x86/asm6.go#L1112-L1114
|
|
//
|
|
// {AIMUL3W, yimul3, Pe, opBytes{0x6b, 00, 0x69, 00}},
|
|
// {AIMUL3L, yimul3, Px, opBytes{0x6b, 00, 0x69, 00}},
|
|
// {AIMUL3Q, yimul3, Pw, opBytes{0x6b, 00, 0x69, 00}},
|
|
//
|
|
// Reference: https://github.com/golang/arch/blob/b19384d3c130858bb31a343ea8fce26be71b5998/x86/x86.v0.2.csv#L549
|
|
//
|
|
// "IMUL r32, r/m32, imm32","IMULL imm32, r/m32, r32","imull imm32, r/m32, r32","69 /r id","V","V","","operand32","rw,r,r","Y","32"
|
|
//
|
|
if strings.HasPrefix(f.GASName, "imul") && len(f.Operands) == 3 {
|
|
return []string{strings.ToUpper(f.GASName[:4] + "3" + f.GASName[4:])}
|
|
}
|
|
|
|
// Use go opcode from Opcodes XML where available.
|
|
if f.GoName != "" {
|
|
return []string{f.GoName}
|
|
}
|
|
|
|
// Fallback to GAS name.
|
|
n := strings.ToUpper(f.GASName)
|
|
|
|
// Some need data sizes added to them.
|
|
n += sizesuffix(n, f)
|
|
|
|
return []string{n}
|
|
}
|
|
|
|
func (l Loader) forms(opcode string, f opcodesxml.Form) []inst.Form {
|
|
// Map operands to avo format and ensure correct order.
|
|
ops := operands(f.Operands)
|
|
|
|
switch l.order[opcode] {
|
|
case opcodescsv.IntelOrder:
|
|
// Nothing to do.
|
|
case opcodescsv.CMP3Order:
|
|
ops[0], ops[1] = ops[1], ops[0]
|
|
case opcodescsv.UnknownOrder:
|
|
// Instructions not in x86 CSV are assumed to have reverse intel order.
|
|
fallthrough
|
|
case opcodescsv.ReverseIntelOrder:
|
|
for l, r := 0, len(ops)-1; l < r; l, r = l+1, r-1 {
|
|
ops[l], ops[r] = ops[r], ops[l]
|
|
}
|
|
}
|
|
|
|
// Handle some exceptions.
|
|
// TODO(mbm): consider if there's some nicer way to handle the list of special cases.
|
|
switch opcode {
|
|
// Go assembler has an internal Yu2 operand type for unsigned 2-bit immediates.
|
|
//
|
|
// Reference: https://github.com/golang/go/blob/6d5caf38e37bf9aeba3291f1f0b0081f934b1187/src/cmd/internal/obj/x86/asm6.go#L109
|
|
//
|
|
// Yu2 // $x, x fits in uint2
|
|
//
|
|
// Reference: https://github.com/golang/go/blob/6d5caf38e37bf9aeba3291f1f0b0081f934b1187/src/cmd/internal/obj/x86/asm6.go#L858-L864
|
|
//
|
|
// var yextractps = []ytab{
|
|
// {Zibr_m, 2, argList{Yu2, Yxr, Yml}},
|
|
// }
|
|
//
|
|
// var ysha1rnds4 = []ytab{
|
|
// {Zibm_r, 2, argList{Yu2, Yxm, Yxr}},
|
|
// }
|
|
//
|
|
case "SHA1RNDS4", "EXTRACTPS":
|
|
ops[0].Type = "imm2u"
|
|
}
|
|
|
|
// Extract implicit operands.
|
|
var implicits []inst.ImplicitOperand
|
|
for _, implicit := range f.ImplicitOperands {
|
|
implicits = append(implicits, inst.ImplicitOperand{
|
|
Register: implicit.ID,
|
|
Action: inst.ActionFromReadWrite(implicit.Input, implicit.Output),
|
|
})
|
|
}
|
|
|
|
// Extract ISA flags.
|
|
var isas []string
|
|
for _, isa := range f.ISA {
|
|
isas = append(isas, isa.ID)
|
|
}
|
|
sort.Strings(isas)
|
|
|
|
// Initialize form.
|
|
form := inst.Form{
|
|
ISA: isas,
|
|
Operands: ops,
|
|
ImplicitOperands: implicits,
|
|
EncodingType: enctype(f),
|
|
CancellingInputs: f.CancellingInputs,
|
|
}
|
|
|
|
// Apply modification stages to produce final list of forms.
|
|
stages := []func(string, inst.Form) []inst.Form{
|
|
avx512rounding,
|
|
avx512sae,
|
|
avx512bcst,
|
|
avx512masking,
|
|
avx512zeroing,
|
|
}
|
|
|
|
forms := []inst.Form{form}
|
|
for _, stage := range stages {
|
|
var next []inst.Form
|
|
for _, f := range forms {
|
|
next = append(next, stage(opcode, f)...)
|
|
}
|
|
forms = next
|
|
}
|
|
|
|
return forms
|
|
}
|
|
|
|
// operands maps Opcodes XML operands to avo format. Returned in Intel order.
|
|
func operands(ops []opcodesxml.Operand) []inst.Operand {
|
|
n := len(ops)
|
|
r := make([]inst.Operand, n)
|
|
for i, op := range ops {
|
|
r[i] = operand(op)
|
|
}
|
|
return r
|
|
}
|
|
|
|
// operand maps an Opcodes XML operand to avo format.
|
|
func operand(op opcodesxml.Operand) inst.Operand {
|
|
return inst.Operand{
|
|
Type: op.Type,
|
|
Action: inst.ActionFromReadWrite(op.Input, op.Output),
|
|
}
|
|
}
|
|
|
|
// avx512rounding handles AVX-512 embedded rounding. Opcodes database represents
|
|
// these as {er} operands, whereas Go uses instruction suffixes. Remove the
|
|
// operand if present and set the corresponding flag.
|
|
func avx512rounding(opcode string, f inst.Form) []inst.Form {
|
|
i, found := findoperand(f.Operands, "{er}")
|
|
if !found {
|
|
return []inst.Form{f}
|
|
}
|
|
|
|
// Delete the {er} operand.
|
|
f.Operands = append(f.Operands[:i], f.Operands[i+1:]...)
|
|
|
|
// Create a second form with the rounding flag.
|
|
er := f.Clone()
|
|
er.EmbeddedRounding = true
|
|
|
|
return []inst.Form{f, er}
|
|
}
|
|
|
|
// avx512sae handles AVX-512 "suppress all exceptions". Opcodes database
|
|
// represents these as {sae} operands, whereas Go uses instruction suffixes.
|
|
// Remove the operand if present and set the corresponding flag.
|
|
func avx512sae(opcode string, f inst.Form) []inst.Form {
|
|
i, found := findoperand(f.Operands, "{sae}")
|
|
if !found {
|
|
return []inst.Form{f}
|
|
}
|
|
|
|
// Delete the {sae} operand.
|
|
f.Operands = append(f.Operands[:i], f.Operands[i+1:]...)
|
|
|
|
// Create a second form with the rounding flag.
|
|
sae := f.Clone()
|
|
sae.SuppressAllExceptions = true
|
|
|
|
return []inst.Form{f, sae}
|
|
}
|
|
|
|
// avx512bcst handles AVX-512 broadcast. Opcodes database uses operands like
|
|
// "m512/m64bcst" to indicate broadcast. Go uses the BCST suffix to enable it.
|
|
// Split the form into two, the regular and broadcast versions.
|
|
func avx512bcst(opcode string, f inst.Form) []inst.Form {
|
|
// Look for broadcast operand.
|
|
idx := -1
|
|
for i, op := range f.Operands {
|
|
if bcstrx.MatchString(op.Type) {
|
|
idx = i
|
|
break
|
|
}
|
|
}
|
|
|
|
if idx < 0 {
|
|
return []inst.Form{f}
|
|
}
|
|
|
|
// Create two forms.
|
|
match := bcstrx.FindStringSubmatch(f.Operands[idx].Type)
|
|
|
|
mem := f.Clone()
|
|
mem.Operands[idx].Type = match[1]
|
|
|
|
bcst := f.Clone()
|
|
bcst.Broadcast = true
|
|
bcst.Operands[idx].Type = match[2]
|
|
|
|
return []inst.Form{mem, bcst}
|
|
}
|
|
|
|
var bcstrx = regexp.MustCompile(`^(m\d+)/(m\d+)bcst$`)
|
|
|
|
// avx512masking handles AVX-512 masking forms.
|
|
func avx512masking(opcode string, f inst.Form) []inst.Form {
|
|
// In order to support implicit masking (with K0), Go has two instruction
|
|
// forms, one with the mask and one without. The mask register precedes the
|
|
// output register. The Opcodes database (similar to Intel manuals)
|
|
// represents masking with the {k} operand suffix, possibly with {z} for
|
|
// zeroing.
|
|
|
|
// Look for masking with possible zeroing. Zeroing is handled by a later
|
|
// processing stage, but we need to be sure to notice and preserve it here.
|
|
masking := false
|
|
zeroing := false
|
|
idx := -1
|
|
for i := range f.Operands {
|
|
op := &f.Operands[i]
|
|
if strings.HasSuffix(op.Type, "{z}") {
|
|
zeroing = true
|
|
op.Type = strings.TrimSuffix(op.Type, "{z}")
|
|
}
|
|
if strings.HasSuffix(op.Type, "{k}") {
|
|
masking = true
|
|
idx = i
|
|
op.Type = strings.TrimSuffix(op.Type, "{k}")
|
|
break
|
|
}
|
|
}
|
|
|
|
// Bail if no masking.
|
|
if !masking {
|
|
return []inst.Form{f}
|
|
}
|
|
|
|
// Unmasked variant.
|
|
unmasked := f.Clone()
|
|
|
|
// Masked form has "k" operand inserted.
|
|
masked := f.Clone()
|
|
mask := inst.Operand{Type: "k", Action: inst.R}
|
|
ops := append([]inst.Operand(nil), masked.Operands[:idx]...)
|
|
ops = append(ops, mask)
|
|
ops = append(ops, masked.Operands[idx:]...)
|
|
masked.Operands = ops
|
|
|
|
// Restore zeroing suffix, so it can he handled later.
|
|
if zeroing {
|
|
masked.Operands[idx+1].Type += "{z}"
|
|
}
|
|
|
|
// Almost all instructions take an optional mask, apart from a few
|
|
// special cases.
|
|
if maskrequired[opcode] {
|
|
return []inst.Form{masked}
|
|
}
|
|
return []inst.Form{unmasked, masked}
|
|
}
|
|
|
|
// avx512zeroing handles AVX-512 zeroing forms.
|
|
func avx512zeroing(opcode string, f inst.Form) []inst.Form {
|
|
// Zeroing in Go is handled with the Z opcode suffix. Note that zeroing has
|
|
// an important effect on the instruction form, since the merge masking form
|
|
// has an input dependency for the output register, and the zeroing form
|
|
// does not.
|
|
|
|
// Look for zeroing operand.
|
|
idx := -1
|
|
for i := range f.Operands {
|
|
op := &f.Operands[i]
|
|
if strings.HasSuffix(op.Type, "{z}") {
|
|
idx = i
|
|
op.Type = strings.TrimSuffix(op.Type, "{z}")
|
|
}
|
|
}
|
|
|
|
if idx < 0 {
|
|
return []inst.Form{f}
|
|
}
|
|
|
|
// Duplicate into two forms for merging and zeroing.
|
|
merging := f.Clone()
|
|
merging.Operands[idx].Action |= inst.R
|
|
|
|
zeroing := f.Clone()
|
|
zeroing.Zeroing = true
|
|
|
|
return []inst.Form{merging, zeroing}
|
|
}
|
|
|
|
// findoperand looks for an operand type and returns its index, if found.
|
|
func findoperand(ops []inst.Operand, t string) (int, bool) {
|
|
for i, op := range ops {
|
|
if op.Type == t {
|
|
return i, true
|
|
}
|
|
}
|
|
return 0, false
|
|
}
|
|
|
|
// enctype selects the encoding type for the instruction form.
|
|
func enctype(f opcodesxml.Form) inst.EncodingType {
|
|
switch {
|
|
case f.Encoding.EVEX != nil:
|
|
return inst.EncodingTypeEVEX
|
|
case f.Encoding.VEX != nil:
|
|
return inst.EncodingTypeVEX
|
|
case f.Encoding.REX != nil:
|
|
return inst.EncodingTypeREX
|
|
default:
|
|
return inst.EncodingTypeLegacy
|
|
}
|
|
}
|
|
|
|
// datasize (intelligently) guesses the datasize of an instruction form.
|
|
func datasize(f opcodesxml.Form) int {
|
|
// Determine from encoding bits.
|
|
e := f.Encoding
|
|
if e.VEX != nil && e.VEX.W == nil {
|
|
return 128 << e.VEX.L
|
|
}
|
|
|
|
// Guess from operand types.
|
|
size := 0
|
|
for _, op := range f.Operands {
|
|
s := operandsize(op)
|
|
if s != 0 && (size == 0 || op.Output) {
|
|
size = s
|
|
}
|
|
}
|
|
|
|
return size
|
|
}
|
|
|
|
func operandsize(op opcodesxml.Operand) int {
|
|
for s := 256; s >= 8; s /= 2 {
|
|
if strings.HasSuffix(op.Type, strconv.Itoa(s)) {
|
|
return s
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// sizesuffix returns an optional size suffix to be added to the opcode name.
|
|
func sizesuffix(n string, f opcodesxml.Form) string {
|
|
// Reference: https://github.com/golang/arch/blob/5de9028c2478e6cb4e1c1b1f4386f3f0a93e383a/x86/x86avxgen/main.go#L275-L322
|
|
//
|
|
// func addGoSuffixes(ctx *context) {
|
|
// var opcodeSuffixMatchers map[string][]string
|
|
// {
|
|
// opXY := []string{"VL=0", "X", "VL=1", "Y"}
|
|
// opXYZ := []string{"VL=0", "X", "VL=1", "Y", "VL=2", "Z"}
|
|
// opQ := []string{"REXW=1", "Q"}
|
|
// opLQ := []string{"REXW=0", "L", "REXW=1", "Q"}
|
|
//
|
|
// opcodeSuffixMatchers = map[string][]string{
|
|
// "VCVTPD2DQ": opXY,
|
|
// "VCVTPD2PS": opXY,
|
|
// "VCVTTPD2DQ": opXY,
|
|
// "VCVTQQ2PS": opXY,
|
|
// "VCVTUQQ2PS": opXY,
|
|
// "VCVTPD2UDQ": opXY,
|
|
// "VCVTTPD2UDQ": opXY,
|
|
//
|
|
// "VFPCLASSPD": opXYZ,
|
|
// "VFPCLASSPS": opXYZ,
|
|
//
|
|
// "VCVTSD2SI": opQ,
|
|
// "VCVTTSD2SI": opQ,
|
|
// "VCVTTSS2SI": opQ,
|
|
// "VCVTSS2SI": opQ,
|
|
//
|
|
// "VCVTSD2USI": opLQ,
|
|
// "VCVTSS2USI": opLQ,
|
|
// "VCVTTSD2USI": opLQ,
|
|
// "VCVTTSS2USI": opLQ,
|
|
// "VCVTUSI2SD": opLQ,
|
|
// "VCVTUSI2SS": opLQ,
|
|
// "VCVTSI2SD": opLQ,
|
|
// "VCVTSI2SS": opLQ,
|
|
// "ANDN": opLQ,
|
|
// "BEXTR": opLQ,
|
|
// "BLSI": opLQ,
|
|
// "BLSMSK": opLQ,
|
|
// "BLSR": opLQ,
|
|
// "BZHI": opLQ,
|
|
// "MULX": opLQ,
|
|
// "PDEP": opLQ,
|
|
// "PEXT": opLQ,
|
|
// "RORX": opLQ,
|
|
// "SARX": opLQ,
|
|
// "SHLX": opLQ,
|
|
// "SHRX": opLQ,
|
|
// }
|
|
// }
|
|
//
|
|
|
|
type rule struct {
|
|
Size func(opcodesxml.Form) int
|
|
Suffix map[int]string
|
|
}
|
|
|
|
var (
|
|
XY = rule{evexLLsize, map[int]string{128: "X", 256: "Y"}}
|
|
XYZ = rule{evexLLsize, map[int]string{128: "X", 256: "Y", 512: "Z"}}
|
|
Q = rule{rexWsize, map[int]string{64: "Q"}}
|
|
LQ = rule{rexWsize, map[int]string{32: "L", 64: "Q"}}
|
|
WLQ = rule{datasize, map[int]string{16: "W", 32: "L", 64: "Q"}}
|
|
)
|
|
|
|
rules := map[string]rule{
|
|
"VCVTPD2DQ": XY,
|
|
"VCVTPD2PS": XY,
|
|
"VCVTTPD2DQ": XY,
|
|
"VCVTQQ2PS": XY,
|
|
"VCVTUQQ2PS": XY,
|
|
"VCVTPD2UDQ": XY,
|
|
"VCVTTPD2UDQ": XY,
|
|
|
|
"VFPCLASSPD": XYZ,
|
|
"VFPCLASSPS": XYZ,
|
|
|
|
"VCVTSD2SI": Q,
|
|
"VCVTTSD2SI": Q,
|
|
"VCVTTSS2SI": Q,
|
|
"VCVTSS2SI": Q,
|
|
|
|
"VCVTSD2USI": LQ,
|
|
"VCVTSS2USI": LQ,
|
|
"VCVTTSD2USI": LQ,
|
|
"VCVTTSS2USI": LQ,
|
|
"VCVTUSI2SD": LQ,
|
|
"VCVTUSI2SS": LQ,
|
|
"VCVTSI2SD": LQ,
|
|
"VCVTSI2SS": LQ,
|
|
"ANDN": LQ,
|
|
"BEXTR": LQ,
|
|
"BLSI": LQ,
|
|
"BLSMSK": LQ,
|
|
"BLSR": LQ,
|
|
"BZHI": LQ,
|
|
"MULX": LQ,
|
|
"PDEP": LQ,
|
|
"PEXT": LQ,
|
|
"RORX": LQ,
|
|
"SARX": LQ,
|
|
"SHLX": LQ,
|
|
"SHRX": LQ,
|
|
|
|
"RDRAND": LQ,
|
|
"RDSEED": LQ,
|
|
|
|
// MOVEBE* instructions seem to be inconsistent with x86 CSV.
|
|
//
|
|
// Reference: https://github.com/golang/arch/blob/b19384d3c130858bb31a343ea8fce26be71b5998/x86/x86spec/format.go#L282-L287
|
|
//
|
|
// "MOVBE r16, m16": "movbeww",
|
|
// "MOVBE m16, r16": "movbeww",
|
|
// "MOVBE m32, r32": "movbell",
|
|
// "MOVBE r32, m32": "movbell",
|
|
// "MOVBE m64, r64": "movbeqq",
|
|
// "MOVBE r64, m64": "movbeqq",
|
|
//
|
|
"MOVBEW": WLQ,
|
|
"MOVBEL": WLQ,
|
|
"MOVBEQ": WLQ,
|
|
}
|
|
|
|
r, ok := rules[n]
|
|
if !ok {
|
|
return ""
|
|
}
|
|
|
|
s := r.Size(f)
|
|
return r.Suffix[s]
|
|
}
|
|
|
|
func rexWsize(f opcodesxml.Form) int {
|
|
e := f.Encoding
|
|
switch {
|
|
case e.EVEX != nil && e.EVEX.W != nil:
|
|
return 32 << *e.EVEX.W
|
|
default:
|
|
return 32
|
|
}
|
|
}
|
|
|
|
func evexLLsize(f opcodesxml.Form) int {
|
|
e := f.Encoding
|
|
if e.EVEX == nil {
|
|
return 0
|
|
}
|
|
size := map[string]int{"00": 128, "01": 256, "10": 512}
|
|
return size[e.EVEX.LL]
|
|
}
|
|
|
|
// vexevex fixes instructions that have both VEX and EVEX encoded forms with the
|
|
// same operand types. Go uses the VEX encoded form unless EVEX-only features
|
|
// are used. This function will only keep the VEX encoded version in the case
|
|
// where both exist.
|
|
//
|
|
// Note this is somewhat of a hack. There are real reasons to use the EVEX
|
|
// encoded version even when both exist. The main reason to use the EVEX version
|
|
// rather than VEX is to use the registers Z16, Z17, ... and up. However, avo
|
|
// does not implement the logic to distinguish between the two halfs of the
|
|
// vector registers. So in its current state the only reason to need the EVEX
|
|
// version is to encode suffixes, and these are represented by other instruction
|
|
// forms.
|
|
//
|
|
// TODO(mbm): restrict use of vector registers https://github.com/mmcloughlin/avo/issues/146
|
|
func vexevex(fs []inst.Form) ([]inst.Form, error) {
|
|
// Group forms by deduping ID.
|
|
byid := map[string][]inst.Form{}
|
|
for _, f := range fs {
|
|
id := fmt.Sprintf(
|
|
"%s {%t,%t,%t,%t}",
|
|
strings.Join(f.Signature(), "_"),
|
|
f.Zeroing,
|
|
f.EmbeddedRounding,
|
|
f.SuppressAllExceptions,
|
|
f.Broadcast,
|
|
)
|
|
byid[id] = append(byid[id], f)
|
|
}
|
|
|
|
// Resolve overlaps.
|
|
var results []inst.Form
|
|
for id, group := range byid {
|
|
if len(group) < 2 {
|
|
results = append(results, group...)
|
|
continue
|
|
}
|
|
|
|
// We expect these conflicts are caused by VEX/EVEX pairs. Bail if it's
|
|
// something else.
|
|
if len(group) > 2 {
|
|
return nil, fmt.Errorf("more than two forms of type %q", id)
|
|
}
|
|
|
|
if group[0].EncodingType == inst.EncodingTypeEVEX {
|
|
group[0], group[1] = group[1], group[0]
|
|
}
|
|
|
|
if group[0].EncodingType != inst.EncodingTypeVEX || group[1].EncodingType != inst.EncodingTypeEVEX {
|
|
fmt.Println(group)
|
|
return nil, errors.New("expected pair of VEX/EVEX encoded forms")
|
|
}
|
|
|
|
vex := group[0]
|
|
|
|
// In this case we only keep the VEX encoded form.
|
|
results = append(results, vex)
|
|
}
|
|
|
|
return results, nil
|
|
}
|
|
|
|
// dedupe a list of forms.
|
|
func dedupe(fs []inst.Form) []inst.Form {
|
|
uniq := make([]inst.Form, 0, len(fs))
|
|
for _, f := range fs {
|
|
have := false
|
|
for _, u := range uniq {
|
|
if reflect.DeepEqual(u, f) {
|
|
have = true
|
|
break
|
|
}
|
|
}
|
|
if !have {
|
|
uniq = append(uniq, f)
|
|
}
|
|
}
|
|
return uniq
|
|
}
|
|
|
|
// sortforms sorts a list of forms.
|
|
func sortforms(fs []inst.Form) {
|
|
sort.Slice(fs, func(i, j int) bool {
|
|
return sortkey(fs[i]) < sortkey(fs[j])
|
|
})
|
|
}
|
|
|
|
func sortkey(f inst.Form) string {
|
|
return fmt.Sprintf("%d %v %v", f.EncodingType, f.ISA, f)
|
|
}
|