loadertest: add memory operands

This commit is contained in:
Michael McLoughlin
2018-11-22 14:12:20 -06:00
parent c67dcb7fa9
commit e97da03f19
5 changed files with 163 additions and 35 deletions

View File

@@ -16,10 +16,13 @@ func (l LoaderTest) Generate(w io.Writer, is []*inst.Instruction) error {
p.printf("TEXT loadertest(SB), 0, $0\n") p.printf("TEXT loadertest(SB), 0, $0\n")
counts := map[string]int{}
for _, i := range is { for _, i := range is {
p.printf("\t// %s %s\n", i.Opcode, i.Summary) p.printf("\t// %s %s\n", i.Opcode, i.Summary)
if skip, msg := l.skip(i.Opcode); skip { if skip, msg := l.skip(i.Opcode); skip {
p.printf("\t// SKIP: %s\n", msg) p.printf("\t// SKIP: %s\n", msg)
counts["skip"]++
continue continue
} }
@@ -28,15 +31,21 @@ func (l LoaderTest) Generate(w io.Writer, is []*inst.Instruction) error {
p.printf("\t// %#v\n", f.Operands) p.printf("\t// %#v\n", f.Operands)
if as == nil { if as == nil {
p.printf("\t// TODO\n") p.printf("\t// TODO\n")
counts["todo"]++
continue continue
} }
p.printf("\t%s\t%s\n", i.Opcode, strings.Join(as, ", ")) p.printf("\t%s\t%s\n", i.Opcode, strings.Join(as, ", "))
counts["total"]++
} }
p.printf("\n") p.printf("\n")
} }
p.printf("\tRET\n") p.printf("\tRET\n")
for m, c := range counts {
p.printf("// %s: %d\n", m, c)
}
return p.Err() return p.Err()
} }
@@ -68,29 +77,30 @@ func args(ops []inst.Operand) []string {
// arg generates an argument for an operand of the given type. // arg generates an argument for an operand of the given type.
func arg(t string) string { func arg(t string) string {
m := map[string]string{ m := map[string]string{
// <xs:enumeration value="1" /> "1": "$1", // <xs:enumeration value="1" />
// <xs:enumeration value="3" /> "3": "$3", // <xs:enumeration value="3" />
"imm2u": "$3", "imm2u": "$3",
// <xs:enumeration value="imm4" /> // <xs:enumeration value="imm4" />
"imm8": fmt.Sprintf("$%d", math.MaxInt8), // <xs:enumeration value="imm8" /> "imm8": fmt.Sprintf("$%d", math.MaxInt8), // <xs:enumeration value="imm8" />
"imm16": fmt.Sprintf("$%d", math.MaxInt16), // <xs:enumeration value="imm16" /> "imm16": fmt.Sprintf("$%d", math.MaxInt16), // <xs:enumeration value="imm16" />
"imm32": fmt.Sprintf("$%d", math.MaxInt32), // <xs:enumeration value="imm32" /> "imm32": fmt.Sprintf("$%d", math.MaxInt32), // <xs:enumeration value="imm32" />
"imm64": fmt.Sprintf("$%d", math.MaxInt64), // <xs:enumeration value="imm64" /> "imm64": fmt.Sprintf("$%d", math.MaxInt64), // <xs:enumeration value="imm64" />
// <xs:enumeration value="al" /> "al": "AL", // <xs:enumeration value="al" />
// <xs:enumeration value="cl" /> "cl": "CL", // <xs:enumeration value="cl" />
// <xs:enumeration value="r8" /> // <xs:enumeration value="r8" />
// <xs:enumeration value="r8l" /> // <xs:enumeration value="r8l" />
// <xs:enumeration value="ax" /> // <xs:enumeration value="ax" />
// <xs:enumeration value="r16" /> // <xs:enumeration value="r16" />
// <xs:enumeration value="r16l" /> // <xs:enumeration value="r16l" />
// <xs:enumeration value="eax" /> "eax": "AX", // <xs:enumeration value="eax" />
// <xs:enumeration value="r32" /> // <xs:enumeration value="r32" />
// <xs:enumeration value="r32l" /> // <xs:enumeration value="r32l" />
// <xs:enumeration value="rax" /> "rax": "AX", // <xs:enumeration value="rax" />
"r64": "R15", // <xs:enumeration value="r64" /> "r64": "R15", // <xs:enumeration value="r64" />
// <xs:enumeration value="mm" /> // <xs:enumeration value="mm" />
// <xs:enumeration value="xmm0" /> "xmm0": "X0", // <xs:enumeration value="xmm0" />
"xmm": "X7", // <xs:enumeration value="xmm" /> "xmm": "X7", // <xs:enumeration value="xmm" />
// <xs:enumeration value="xmm{k}" /> // <xs:enumeration value="xmm{k}" />
// <xs:enumeration value="xmm{k}{z}" /> // <xs:enumeration value="xmm{k}{z}" />
// <xs:enumeration value="ymm" /> // <xs:enumeration value="ymm" />
@@ -104,16 +114,16 @@ func arg(t string) string {
// <xs:enumeration value="moffs32" /> // <xs:enumeration value="moffs32" />
// <xs:enumeration value="moffs64" /> // <xs:enumeration value="moffs64" />
// <xs:enumeration value="m" /> // <xs:enumeration value="m" />
// <xs:enumeration value="m8" /> "m8": "8(AX)(CX*2)", // <xs:enumeration value="m8" />
// <xs:enumeration value="m16" /> "m16": "16(AX)(CX*2)", // <xs:enumeration value="m16" />
// <xs:enumeration value="m16{k}{z}" /> // <xs:enumeration value="m16{k}{z}" />
// <xs:enumeration value="m32" /> "m32": "32(AX)(CX*2)", // <xs:enumeration value="m32" />
// <xs:enumeration value="m32{k}" /> // <xs:enumeration value="m32{k}" />
// <xs:enumeration value="m32{k}{z}" /> // <xs:enumeration value="m32{k}{z}" />
// <xs:enumeration value="m64" /> "m64": "64(AX)(CX*2)", // <xs:enumeration value="m64" />
// <xs:enumeration value="m64{k}" /> // <xs:enumeration value="m64{k}" />
// <xs:enumeration value="m64{k}{z}" /> // <xs:enumeration value="m64{k}{z}" />
// <xs:enumeration value="m128" /> "m128": "128(AX)(CX*2)", // <xs:enumeration value="m128" />
// <xs:enumeration value="m128{k}{z}" /> // <xs:enumeration value="m128{k}{z}" />
// <xs:enumeration value="m256" /> // <xs:enumeration value="m256" />
// <xs:enumeration value="m256{k}{z}" /> // <xs:enumeration value="m256{k}{z}" />

View File

@@ -19,8 +19,8 @@ type Loader struct {
X86CSVPath string X86CSVPath string
OpcodesXMLPath string OpcodesXMLPath string
alias map[opcodescsv.Alias]string alias map[opcodescsv.Alias]string
usesIntelOrder map[string]bool order map[string]opcodescsv.OperandOrder
} }
func NewLoaderFromDataDir(dir string) *Loader { func NewLoaderFromDataDir(dir string) *Loader {
@@ -81,7 +81,11 @@ func (l *Loader) init() error {
return err return err
} }
l.usesIntelOrder = opcodescsv.BuildIntelOrderSet(icsv) // for a, op := range l.alias {
// log.Printf("alias %#v -> %s", a, op)
// }
l.order = opcodescsv.BuildOrderMap(icsv)
return nil return nil
} }
@@ -89,14 +93,14 @@ func (l *Loader) init() error {
// include decides whether to include the instruction form in the avo listing. // include decides whether to include the instruction form in the avo listing.
// This discards some opcodes that are not supported in Go. // This discards some opcodes that are not supported in Go.
func (l Loader) include(f opcodesxml.Form) bool { func (l Loader) include(f opcodesxml.Form) bool {
// Exclude certain ISAs simply not present in Go (AMD-only is a common reason). // Exclude certain ISAs simply not present in Go
for _, isa := range f.ISA { for _, isa := range f.ISA {
switch isa.ID { switch isa.ID {
// Most of these are AMD-only.
case "TBM", "CLZERO", "MONITORX", "FEMMS", "FMA4", "XOP", "SSE4A": case "TBM", "CLZERO", "MONITORX", "FEMMS", "FMA4", "XOP", "SSE4A":
return false return false
} // Incomplete support for some prefetching instructions.
// TODO(mbm): support AVX512 case "PREFETCH", "PREFETCHW", "PREFETCHWT1", "CLWB":
if strings.HasPrefix(isa.ID, "AVX512") {
return false return false
} }
} }
@@ -119,12 +123,19 @@ func (l Loader) include(f opcodesxml.Form) bool {
} }
func (l Loader) lookupAlias(f opcodesxml.Form) string { func (l Loader) lookupAlias(f opcodesxml.Form) string {
a := opcodescsv.Alias{ // Attempt lookup with datasize.
k := opcodescsv.Alias{
Opcode: f.GASName, Opcode: f.GASName,
DataSize: datasize(f), DataSize: datasize(f),
NumOperands: len(f.Operands), NumOperands: len(f.Operands),
} }
return l.alias[a] 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 { func (l Loader) gonames(f opcodesxml.Form) []string {
@@ -152,7 +163,9 @@ func (l Loader) gonames(f opcodesxml.Form) []string {
s := datasize(f) s := datasize(f)
suffix := map[int]string{16: "W", 32: "L", 64: "Q", 128: "X", 256: "Y"} suffix := map[int]string{16: "W", 32: "L", 64: "Q", 128: "X", 256: "Y"}
switch n { switch n {
case "RDRAND", "RDSEED": case "VCVTUSI2SS", "VCVTSD2USI", "VCVTSS2USI", "VCVTUSI2SD", "VCVTTSS2USI", "VCVTTSD2USI":
fallthrough
case "RDRAND", "RDSEED", "MOVBEQ":
n += suffix[s] n += suffix[s]
} }
@@ -162,7 +175,16 @@ func (l Loader) gonames(f opcodesxml.Form) []string {
func (l Loader) form(opcode string, f opcodesxml.Form) inst.Form { func (l Loader) form(opcode string, f opcodesxml.Form) inst.Form {
// Map operands to avo format and ensure correct order. // Map operands to avo format and ensure correct order.
ops := operands(f.Operands) ops := operands(f.Operands)
if !l.usesIntelOrder[opcode] {
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 { for l, r := 0, len(ops)-1; l < r; l, r = l+1, r-1 {
ops[l], ops[r] = ops[r], ops[l] ops[l], ops[r] = ops[r], ops[l]
} }
@@ -171,7 +193,23 @@ func (l Loader) form(opcode string, f opcodesxml.Form) inst.Form {
// Handle some exceptions. // Handle some exceptions.
// TODO(mbm): consider if there's some nicer way to handle the list of special cases. // TODO(mbm): consider if there's some nicer way to handle the list of special cases.
switch opcode { switch opcode {
case "SHA1RNDS4": // 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" ops[0].Type = "imm2u"
} }
@@ -220,7 +258,7 @@ func datasize(f opcodesxml.Form) int {
} }
func operandsize(op opcodesxml.Operand) int { func operandsize(op opcodesxml.Operand) int {
for s := 8; s <= 256; s *= 2 { for s := 256; s >= 8; s /= 2 {
if strings.HasSuffix(op.Type, strconv.Itoa(s)) { if strings.HasSuffix(op.Type, strconv.Itoa(s)) {
return s return s
} }

View File

@@ -18,15 +18,15 @@ type Alias struct {
func BuildAliasMap(is []*x86csv.Inst) (map[Alias]string, error) { func BuildAliasMap(is []*x86csv.Inst) (map[Alias]string, error) {
m := map[Alias]string{} m := map[Alias]string{}
for _, i := range is { for _, i := range is {
if skip(i) {
continue
}
s, err := strconv.Atoi("0" + i.DataSize) s, err := strconv.Atoi("0" + i.DataSize)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if strings.Contains(i.GoOpcode(), "/") {
continue
}
for _, alt := range []string{i.IntelOpcode(), i.GNUOpcode()} { for _, alt := range []string{i.IntelOpcode(), i.GNUOpcode()} {
if strings.ToUpper(alt) != i.GoOpcode() { if strings.ToUpper(alt) != i.GoOpcode() {
a := Alias{ a := Alias{
@@ -41,13 +41,72 @@ func BuildAliasMap(is []*x86csv.Inst) (map[Alias]string, error) {
return m, nil return m, nil
} }
// BuildIntelOrderSet builds the set of instructions that use intel order rather than the usual GNU/AT&T order. type OperandOrder uint8
func BuildIntelOrderSet(is []*x86csv.Inst) map[string]bool {
s := map[string]bool{} const (
UnknownOrder = iota
IntelOrder
ReverseIntelOrder
CMP3Order
)
// BuildOrderMap collects operand order information from the instruction list.
func BuildOrderMap(is []*x86csv.Inst) map[string]OperandOrder {
s := map[string]OperandOrder{}
for _, i := range is { for _, i := range is {
if !reflect.DeepEqual(i.GoArgs(), i.GNUArgs()) { if skip(i) {
s[i.GoOpcode()] = true continue
} }
s[i.GoOpcode()] = order(i)
} }
return s return s
} }
// order categorizes the operand order of an instruction.
func order(i *x86csv.Inst) OperandOrder {
// Is it Intel order already?
intel := i.IntelArgs()
if reflect.DeepEqual(i.GoArgs(), intel) {
return IntelOrder
}
// Check if it's reverse Intel.
for l, r := 0, len(intel)-1; l < r; l, r = l+1, r-1 {
intel[l], intel[r] = intel[r], intel[l]
}
if reflect.DeepEqual(i.GoArgs(), intel) {
return ReverseIntelOrder
}
// Otherwise we could be in the bizarre special-case of 3-argument CMP instructions.
//
// Reference: https://github.com/golang/arch/blob/b19384d3c130858bb31a343ea8fce26be71b5998/x86/x86spec/format.go#L138-L144
//
// case "CMPPD", "CMPPS", "CMPSD", "CMPSS":
// // rotate destination to end but don't swap comparison operands
// if len(args) == 3 {
// args[0], args[1], args[2] = args[2], args[0], args[1]
// break
// }
// fallthrough
//
switch i.GoOpcode() {
case "CMPPD", "CMPPS", "CMPSD", "CMPSS":
if len(i.GoArgs()) == 3 {
return CMP3Order
}
}
return UnknownOrder
}
// skip decides whether to ignore the instruction for analysis purposes.
func skip(i *x86csv.Inst) bool {
switch {
case strings.Contains(i.GoOpcode(), "/"):
return true
case i.Mode64 == "I": // Invalid in 64-bit mode.
return true
}
return false
}

View File

@@ -0,0 +1,20 @@
package opcodescsv
import (
"testing"
)
func TestBuildOrderMap(t *testing.T) {
is, err := ReadFile("testdata/x86.v0.2.csv")
if err != nil {
t.Fatal(err)
}
orders := BuildOrderMap(is)
for opcode, order := range orders {
if order == UnknownOrder {
t.Errorf("%s has unknown order", opcode)
}
}
}

View File

@@ -0,0 +1 @@
../data