Files
avo/internal/load/load.go

334 lines
8.8 KiB
Go
Raw Normal View History

2018-11-21 13:02:18 -06:00
package load
import (
"path/filepath"
"strconv"
"strings"
"github.com/mmcloughlin/avo/internal/inst"
"github.com/mmcloughlin/avo/internal/opcodescsv"
"github.com/mmcloughlin/avo/internal/opcodesxml"
)
const (
defaultCSVName = "x86.v0.2.csv"
defaultOpcodesXMLName = "x86_64.xml"
)
type Loader struct {
X86CSVPath string
OpcodesXMLPath string
2018-11-22 14:12:20 -06:00
alias map[opcodescsv.Alias]string
order map[string]opcodescsv.OperandOrder
2018-11-21 13:02:18 -06:00
}
func NewLoaderFromDataDir(dir string) *Loader {
return &Loader{
X86CSVPath: filepath.Join(dir, defaultCSVName),
OpcodesXMLPath: filepath.Join(dir, defaultOpcodesXMLName),
}
}
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
}
2018-11-22 11:17:46 -06:00
for _, opcode := range l.gonames(f) {
if im[opcode] == nil {
im[opcode] = &inst.Instruction{
Opcode: opcode,
Summary: i.Summary,
}
2018-11-21 13:02:18 -06:00
}
2018-11-22 11:17:46 -06:00
im[opcode].Forms = append(im[opcode].Forms, l.form(opcode, f))
2018-11-21 13:02:18 -06:00
}
}
}
// Convert to a slice to return.
is := make([]*inst.Instruction, 0, len(im))
for _, i := range im {
is = append(is, i)
}
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
}
2018-11-22 14:12:20 -06:00
// for a, op := range l.alias {
// log.Printf("alias %#v -> %s", a, op)
// }
l.order = opcodescsv.BuildOrderMap(icsv)
2018-11-21 22:28:55 -06:00
2018-11-21 13:02:18 -06:00
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 {
2018-11-22 14:12:20 -06:00
// Exclude certain ISAs simply not present in Go
2018-11-21 13:02:18 -06:00
for _, isa := range f.ISA {
switch isa.ID {
2018-11-22 14:58:31 -06:00
// AMD-only.
2018-11-22 15:24:28 -06:00
case "TBM", "CLZERO", "FMA4", "XOP", "SSE4A", "3dnow!", "3dnow!+":
2018-11-21 22:28:55 -06:00
return false
2018-11-22 14:12:20 -06:00
// Incomplete support for some prefetching instructions.
case "PREFETCH", "PREFETCHW", "PREFETCHWT1", "CLWB":
2018-11-21 13:02:18 -06:00
return false
2018-11-22 14:58:31 -06:00
// Remaining oddities.
case "MONITORX", "FEMMS":
return false
}
// TODO(mbm): support AVX512
if strings.HasPrefix(isa.ID, "AVX512") {
return false
2018-11-21 13:02:18 -06:00
}
}
2018-11-22 15:24:28 -06:00
// 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
}
2018-11-21 13:02:18 -06:00
// 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 {
2018-11-22 16:21:05 -06:00
// 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":
2018-11-21 13:02:18 -06:00
return false
2018-11-23 16:14:05 -06:00
// 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
2018-11-21 13:02:18 -06:00
}
return true
}
func (l Loader) lookupAlias(f opcodesxml.Form) string {
2018-11-22 14:12:20 -06:00
// Attempt lookup with datasize.
k := opcodescsv.Alias{
2018-11-21 22:28:55 -06:00
Opcode: f.GASName,
DataSize: datasize(f),
NumOperands: len(f.Operands),
}
2018-11-22 14:12:20 -06:00
if a := l.alias[k]; a != "" {
return a
}
// Fallback to unknown datasize.
k.DataSize = 0
return l.alias[k]
2018-11-21 13:02:18 -06:00
}
2018-11-22 11:17:46 -06:00
func (l Loader) gonames(f opcodesxml.Form) []string {
2018-11-21 13:02:18 -06:00
// Return alias if available.
if a := l.lookupAlias(f); a != "" {
2018-11-22 11:17:46 -06:00
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"}
2018-11-21 13:02:18 -06:00
}
2018-11-22 14:58:31 -06:00
// 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:])}
}
2018-11-21 22:28:55 -06:00
// Use go opcode from Opcodes XML where available.
if f.GoName != "" {
2018-11-22 11:17:46 -06:00
return []string{f.GoName}
2018-11-21 22:28:55 -06:00
}
// Fallback to GAS name.
2018-11-21 13:02:18 -06:00
n := strings.ToUpper(f.GASName)
// Some need data sizes added to them.
// TODO(mbm): is there a better way of determining which ones these are?
s := datasize(f)
2018-11-21 22:28:55 -06:00
suffix := map[int]string{16: "W", 32: "L", 64: "Q", 128: "X", 256: "Y"}
2018-11-21 13:02:18 -06:00
switch n {
2018-11-22 14:12:20 -06:00
case "VCVTUSI2SS", "VCVTSD2USI", "VCVTSS2USI", "VCVTUSI2SD", "VCVTTSS2USI", "VCVTTSD2USI":
fallthrough
2018-11-22 14:58:31 -06:00
case "MOVBEW", "MOVBEL", "MOVBEQ":
// 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",
//
fallthrough
case "RDRAND", "RDSEED":
2018-11-21 13:02:18 -06:00
n += suffix[s]
}
2018-11-22 11:17:46 -06:00
return []string{n}
2018-11-21 13:02:18 -06:00
}
2018-11-21 23:06:29 -06:00
func (l Loader) form(opcode string, f opcodesxml.Form) inst.Form {
2018-11-22 11:17:46 -06:00
// Map operands to avo format and ensure correct order.
2018-11-21 23:06:29 -06:00
ops := operands(f.Operands)
2018-11-22 14:12:20 -06:00
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:
2018-11-21 23:06:29 -06:00
for l, r := 0, len(ops)-1; l < r; l, r = l+1, r-1 {
ops[l], ops[r] = ops[r], ops[l]
}
}
2018-11-22 11:17:46 -06:00
// Handle some exceptions.
// TODO(mbm): consider if there's some nicer way to handle the list of special cases.
switch opcode {
2018-11-22 14:12:20 -06:00
// 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":
2018-11-22 11:17:46 -06:00
ops[0].Type = "imm2u"
}
2018-11-21 13:02:18 -06:00
return inst.Form{
2018-11-21 23:06:29 -06:00
Operands: ops,
2018-11-21 13:02:18 -06:00
}
}
2018-11-21 23:06:29 -06:00
// operands maps Opcodes XML operands to avo format. Returned in Intel order.
2018-11-21 13:02:18 -06:00
func operands(ops []opcodesxml.Operand) []inst.Operand {
2018-11-21 22:28:55 -06:00
n := len(ops)
2018-11-21 23:06:29 -06:00
r := make([]inst.Operand, n)
2018-11-21 22:28:55 -06:00
for i, op := range ops {
2018-11-21 23:06:29 -06:00
r[i] = operand(op)
2018-11-21 13:02:18 -06:00
}
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),
}
}
// datasize (intelligently) guesses the datasize of an instruction form.
func datasize(f opcodesxml.Form) int {
2018-11-21 22:28:55 -06:00
// Determine from encoding bits.
e := f.Encoding
switch {
case e.VEX != nil && e.VEX.W == nil:
return 128 << e.VEX.L
}
// Guess from operand types.
size := 0
2018-11-21 13:02:18 -06:00
for _, op := range f.Operands {
2018-11-21 22:28:55 -06:00
s := operandsize(op)
if s != 0 && (size == 0 || op.Output) {
size = s
2018-11-21 13:02:18 -06:00
}
2018-11-21 22:28:55 -06:00
}
return size
}
func operandsize(op opcodesxml.Operand) int {
2018-11-22 14:12:20 -06:00
for s := 256; s >= 8; s /= 2 {
2018-11-21 22:28:55 -06:00
if strings.HasSuffix(op.Type, strconv.Itoa(s)) {
return s
2018-11-21 13:02:18 -06:00
}
}
return 0
}