This commit is contained in:
Michael McLoughlin
2018-11-20 11:44:44 -06:00
parent 33ef56f40e
commit 7c2990754f
7 changed files with 2519 additions and 4 deletions

14
ast.go
View File

@@ -1,5 +1,9 @@
package avo
type Asm interface {
Asm() string
}
// GoType represents a Golang type.
type GoType interface{}
@@ -9,10 +13,18 @@ type Parameter struct {
Type GoType
}
type Operand interface {
Asm
}
// Instruction is a single instruction in a function.
type Instruction struct {
Mnemonic string
Operands []string
Operands []Operand
}
type Node interface {
Asm
}
// File represents an assembly file.

2261
internal/inst/testdata/x86.v0.2.csv vendored Normal file

File diff suppressed because it is too large Load Diff

24
internal/inst/types.go Normal file
View File

@@ -0,0 +1,24 @@
package inst
type Instruction struct {
Opcode string
Forms []Form
}
type Form struct {
Operands []Operand
CPUID []string
}
type Operand struct {
Type string
Action Action
}
type Action uint8
const (
R Action = 0x1
W Action = 0x2
RW Action = R | W
)

116
internal/inst/x86csv.go Normal file
View File

@@ -0,0 +1,116 @@
package inst
import (
"io"
"strconv"
"strings"
"golang.org/x/arch/x86/x86csv"
)
// ReadFromX86CSV reads instruction list from the Go "x86.csv" format.
func ReadFromX86CSV(r io.Reader) ([]Instruction, error) {
c := x86csv.NewReader(r)
rows, err := c.ReadAll()
if err != nil {
return nil, err
}
// Group by Go opcode.
groups := map[string][]*x86csv.Inst{}
for _, row := range rows {
g := row.GoOpcode()
groups[g] = append(groups[g], row)
}
var is []Instruction
for opcode, group := range groups {
i := Instruction{
Opcode: opcode,
}
for _, row := range group {
forms := formsFromRow(row)
i.Forms = append(i.Forms, forms...)
}
is = append(is, i)
}
return is, nil
}
func formsFromRow(row *x86csv.Inst) []Form {
var fs []Form
signatures := argsToSignatures(row.GoArgs())
for _, signature := range signatures {
ops := make([]Operand, len(signature))
for i, t := range signature {
ops[i] = Operand{
Type: t,
}
}
f := Form{
Operands: ops,
CPUID: splitCPUID(row.CPUID),
}
fs = append(fs, f)
}
return fs
}
func expandArg(arg string) []string {
mmprefixes := []string{"", "x", "y"}
for e := 8; e <= 512; e *= 2 {
s := strconv.Itoa(e)
switch arg {
case "r/m" + s:
return []string{"r" + s, "m" + s}
case "r" + s + "V":
return []string{"r" + s}
}
for _, p := range mmprefixes {
if arg == p+"mm2/m"+s {
return []string{p + "mm", "m" + s}
}
}
}
for _, p := range []string{"", "x", "y"} {
switch arg {
case p + "mm1", p + "mm2", p + "mmV":
return []string{p + "mm"}
}
}
return []string{arg}
}
func argsToSignatures(args []string) [][]string {
n := len(args)
if n == 0 {
return [][]string{nil}
}
types := expandArg(args[n-1])
var expanded [][]string
for _, sub := range argsToSignatures(args[:n-1]) {
for _, t := range types {
f := make([]string, n-1, n)
copy(f, sub)
f = append(f, t)
expanded = append(expanded, f)
}
}
return expanded
}
// splitCPUID splits CPUID field into flags, plus handling a few oddities.
func splitCPUID(cpuid string) []string {
switch cpuid {
case "Both AES and AVX flags":
return []string{"AES", "AVX"}
case "HLE or RTM":
return []string{"HLE/RTM"}
}
return strings.Split(cpuid, ",")
}

View File

@@ -0,0 +1,94 @@
package inst
import (
"os"
"reflect"
"strings"
"testing"
)
//go:generate curl --output testdata/x86.v0.2.csv https://raw.githubusercontent.com/golang/arch/master/x86/x86.v0.2.csv
const csvpath = "testdata/x86.v0.2.csv"
func LoadX86CSV(t *testing.T) []Instruction {
t.Helper()
f, err := os.Open(csvpath)
if err != nil {
t.Fatal(err)
}
defer f.Close()
is, err := ReadFromX86CSV(f)
if err != nil {
t.Fatal(err)
}
return is
}
func TestExpandArg(t *testing.T) {
cases := []struct {
Arg string
Types []string
}{
{"imm8", []string{"imm8"}},
{"r/m32", []string{"r32", "m32"}},
{"r16", []string{"r16"}},
{"mm1", []string{"mm"}},
{"xmm1", []string{"xmm"}},
{"xmmV", []string{"xmm"}},
{"xmm2/m128", []string{"xmm", "m128"}},
{"xmm2/m64", []string{"xmm", "m64"}},
{"ymm1", []string{"ymm"}},
{"ymmV", []string{"ymm"}},
{"ymm2/m256", []string{"ymm", "m256"}},
}
for _, c := range cases {
types := expandArg(c.Arg)
if !reflect.DeepEqual(c.Types, types) {
t.Errorf("expanded %v to %s expected %s", c.Arg, types, c.Types)
}
}
}
func TestX86CSVOperandTypes(t *testing.T) {
t.Skip("have not handled all cases yet")
is := LoadX86CSV(t)
types := map[string]bool{}
for _, i := range is {
for _, f := range i.Forms {
for _, op := range f.Operands {
types[op.Type] = true
}
}
}
for tipe := range types {
if strings.Contains(tipe, "/") {
t.Errorf("operand type %#v contains a slash (should be split)", tipe)
}
}
}
// TestCPUIDFlags helps catch any oddities in x86csv CPUID flags.
func TestX86CSVCPUIDFlags(t *testing.T) {
is := LoadX86CSV(t)
flags := map[string]bool{}
for _, i := range is {
for _, f := range i.Forms {
for _, flag := range f.CPUID {
flags[flag] = true
}
}
}
for flag := range flags {
if strings.Contains(flag, " ") {
t.Errorf("CPUID flag %#v contains whitespace", flag)
}
}
}

View File

@@ -70,7 +70,7 @@ func (p *GoPrinter) function(f *Function) {
p.printf("TEXT %s%s(SB),0,$%d-%d\n", dot, f.Name(), f.FrameBytes(), f.ArgumentBytes())
for _, i := range f.inst {
p.printf("\t%s\t%s\n", i.Mnemonic, strings.Join(i.Operands, ", "))
p.printf("\t%s\t%s\n", i.Mnemonic, joinOperands(i.Operands))
}
}
@@ -83,3 +83,11 @@ func (p *GoPrinter) printf(format string, args ...interface{}) {
p.err = err
}
}
func joinOperands(operands []Operand) string {
asm := make([]string, len(operands))
for i, op := range operands {
asm[i] = op.Asm()
}
return strings.Join(asm, ", ")
}

View File

@@ -64,7 +64,7 @@ type Register interface {
Kind() Kind
Mask() uint16
Bytes() uint
Name() string
Asm() string
private
}
@@ -77,7 +77,7 @@ type register struct {
func (r register) PhysicalID() uint16 { return r.id }
func (r register) Kind() Kind { return r.kind }
func (r register) Name() string { return r.name }
func (r register) Asm() string { return r.name }
func (r register) private() {}
type Spec uint16