start at some basic passes
This commit is contained in:
46
ast.go
46
ast.go
@@ -1,5 +1,9 @@
|
|||||||
package avo
|
package avo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mmcloughlin/avo/operand"
|
||||||
|
)
|
||||||
|
|
||||||
type Asm interface {
|
type Asm interface {
|
||||||
Asm() string
|
Asm() string
|
||||||
}
|
}
|
||||||
@@ -28,7 +32,26 @@ func (l Label) node() {}
|
|||||||
// Instruction is a single instruction in a function.
|
// Instruction is a single instruction in a function.
|
||||||
type Instruction struct {
|
type Instruction struct {
|
||||||
Opcode string
|
Opcode string
|
||||||
Operands []Operand
|
Operands []operand.Op
|
||||||
|
|
||||||
|
IsTerminal bool
|
||||||
|
IsBranch bool
|
||||||
|
IsConditional bool
|
||||||
|
|
||||||
|
// CFG.
|
||||||
|
Pred []*Instruction
|
||||||
|
Succ []*Instruction
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i Instruction) TargetLabel() *Label {
|
||||||
|
if !i.IsBranch {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if ref, ok := i.Operands[0].(operand.LabelRef); ok {
|
||||||
|
lbl := Label(ref)
|
||||||
|
return &lbl
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i Instruction) node() {}
|
func (i Instruction) node() {}
|
||||||
@@ -46,7 +69,10 @@ func NewFile() *File {
|
|||||||
type Function struct {
|
type Function struct {
|
||||||
name string
|
name string
|
||||||
params []Parameter
|
params []Parameter
|
||||||
nodes []Node
|
Nodes []Node
|
||||||
|
|
||||||
|
// LabelTarget maps from label name to the following instruction.
|
||||||
|
LabelTarget map[Label]*Instruction
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewFunction(name string) *Function {
|
func NewFunction(name string) *Function {
|
||||||
@@ -55,7 +81,7 @@ func NewFunction(name string) *Function {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Function) AddInstruction(i Instruction) {
|
func (f *Function) AddInstruction(i *Instruction) {
|
||||||
f.AddNode(i)
|
f.AddNode(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +90,19 @@ func (f *Function) AddLabel(l Label) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *Function) AddNode(n Node) {
|
func (f *Function) AddNode(n Node) {
|
||||||
f.nodes = append(f.nodes, n)
|
f.Nodes = append(f.Nodes, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instructions returns just the list of instruction nodes.
|
||||||
|
func (f *Function) Instructions() []*Instruction {
|
||||||
|
var is []*Instruction
|
||||||
|
for _, n := range f.Nodes {
|
||||||
|
i, ok := n.(*Instruction)
|
||||||
|
if ok {
|
||||||
|
is = append(is, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return is
|
||||||
}
|
}
|
||||||
|
|
||||||
// Name returns the function name.
|
// Name returns the function name.
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -16,7 +16,7 @@ func (b *build) Generate(is []inst.Instruction) ([]byte, error) {
|
|||||||
b.Printf("package build\n\n")
|
b.Printf("package build\n\n")
|
||||||
|
|
||||||
b.Printf("import (\n")
|
b.Printf("import (\n")
|
||||||
b.Printf("\t\"%s\"\n", pkg)
|
b.Printf("\t\"%s/operand\"\n", pkg)
|
||||||
b.Printf("\t\"%s/x86\"\n", pkg)
|
b.Printf("\t\"%s/x86\"\n", pkg)
|
||||||
b.Printf(")\n\n")
|
b.Printf(")\n\n")
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func (c *ctors) instruction(i inst.Instruction) {
|
|||||||
|
|
||||||
c.Printf("func %s(%s) (*avo.Instruction, error) {\n", i.Opcode, s.ParameterList())
|
c.Printf("func %s(%s) (*avo.Instruction, error) {\n", i.Opcode, s.ParameterList())
|
||||||
c.checkargs(i, s)
|
c.checkargs(i, s)
|
||||||
c.Printf("\treturn &avo.Instruction{Opcode: %#v, Operands: %s}, nil\n", i.Opcode, s.ParameterSlice())
|
c.Printf("\treturn &%s, nil\n", construct(i, s))
|
||||||
c.Printf("}\n\n")
|
c.Printf("}\n\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,6 +72,19 @@ func (c *ctors) doc(i inst.Instruction) []string {
|
|||||||
return lines
|
return lines
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func construct(i inst.Instruction, s signature) string {
|
||||||
|
buf := bytes.NewBuffer(nil)
|
||||||
|
fmt.Fprintf(buf, "avo.Instruction{\n")
|
||||||
|
fmt.Fprintf(buf, "\tOpcode: %#v,\n", i.Opcode)
|
||||||
|
fmt.Fprintf(buf, "\tOperands: %s,\n", s.ParameterSlice())
|
||||||
|
if i.IsBranch() {
|
||||||
|
fmt.Fprintf(buf, "\tIsBranch: true,\n")
|
||||||
|
fmt.Fprintf(buf, "\tIsConditional: %#v,\n", i.IsConditionalBranch())
|
||||||
|
}
|
||||||
|
fmt.Fprintf(buf, "}")
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
func (c *ctors) checkargs(i inst.Instruction, s signature) {
|
func (c *ctors) checkargs(i inst.Instruction, s signature) {
|
||||||
if i.IsNiladic() {
|
if i.IsNiladic() {
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ import (
|
|||||||
"github.com/mmcloughlin/avo/internal/inst"
|
"github.com/mmcloughlin/avo/internal/inst"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// operandType
|
||||||
|
const operandType = "operand.Op"
|
||||||
|
|
||||||
// signature provides access to details about the signature of an instruction function.
|
// signature provides access to details about the signature of an instruction function.
|
||||||
type signature interface {
|
type signature interface {
|
||||||
ParameterList() string
|
ParameterList() string
|
||||||
@@ -21,11 +24,11 @@ type signature interface {
|
|||||||
// argslist is the signature for a function with the given named parameters.
|
// argslist is the signature for a function with the given named parameters.
|
||||||
type argslist []string
|
type argslist []string
|
||||||
|
|
||||||
func (a argslist) ParameterList() string { return strings.Join(a, ", ") + " avo.Operand" }
|
func (a argslist) ParameterList() string { return strings.Join(a, ", ") + " " + operandType }
|
||||||
func (a argslist) Arguments() string { return strings.Join(a, ", ") }
|
func (a argslist) Arguments() string { return strings.Join(a, ", ") }
|
||||||
func (a argslist) ParameterName(i int) string { return a[i] }
|
func (a argslist) ParameterName(i int) string { return a[i] }
|
||||||
func (a argslist) ParameterSlice() string {
|
func (a argslist) ParameterSlice() string {
|
||||||
return fmt.Sprintf("[]avo.Operand{%s}", strings.Join(a, ", "))
|
return fmt.Sprintf("[]%s{%s}", operandType, strings.Join(a, ", "))
|
||||||
}
|
}
|
||||||
func (a argslist) Length() string { return strconv.Itoa(len(a)) }
|
func (a argslist) Length() string { return strconv.Itoa(len(a)) }
|
||||||
|
|
||||||
@@ -34,7 +37,7 @@ type variadic struct {
|
|||||||
name string
|
name string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v variadic) ParameterList() string { return v.name + " ...avo.Operand" }
|
func (v variadic) ParameterList() string { return v.name + " ..." + operandType }
|
||||||
func (v variadic) Arguments() string { return v.name + "..." }
|
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) ParameterName(i int) string { return fmt.Sprintf("%s[%d]", v.name, i) }
|
||||||
func (v variadic) ParameterSlice() string { return v.name }
|
func (v variadic) ParameterSlice() string { return v.name }
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package inst
|
package inst
|
||||||
|
|
||||||
import "sort"
|
import (
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
type Instruction struct {
|
type Instruction struct {
|
||||||
Opcode string
|
Opcode string
|
||||||
@@ -9,6 +12,29 @@ type Instruction struct {
|
|||||||
Forms []Form
|
Forms []Form
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i Instruction) IsTerminal() bool {
|
||||||
|
// TODO(mbm): how about the RETF* instructions
|
||||||
|
return i.Opcode == "RET"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i Instruction) IsBranch() bool {
|
||||||
|
if i.Opcode == "CALL" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, f := range i.Forms {
|
||||||
|
for _, op := range f.Operands {
|
||||||
|
if strings.HasPrefix(op.Type, "rel") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i Instruction) IsConditionalBranch() bool {
|
||||||
|
return i.IsBranch() && i.Opcode != "JMP"
|
||||||
|
}
|
||||||
|
|
||||||
func (i Instruction) Arities() []int {
|
func (i Instruction) Arities() []int {
|
||||||
s := map[int]bool{}
|
s := map[int]bool{}
|
||||||
for _, f := range i.Forms {
|
for _, f := range i.Forms {
|
||||||
|
|||||||
@@ -4,132 +4,130 @@ import (
|
|||||||
"math"
|
"math"
|
||||||
|
|
||||||
"github.com/mmcloughlin/avo/reg"
|
"github.com/mmcloughlin/avo/reg"
|
||||||
|
|
||||||
"github.com/mmcloughlin/avo"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Is1 returns true if op is the immediate constant 1.
|
// Is1 returns true if op is the immediate constant 1.
|
||||||
func Is1(op avo.Operand) bool {
|
func Is1(op Op) bool {
|
||||||
i, ok := op.(Imm)
|
i, ok := op.(Imm)
|
||||||
return ok && i == 1
|
return ok && i == 1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is3 returns true if op is the immediate constant 3.
|
// Is3 returns true if op is the immediate constant 3.
|
||||||
func Is3(op avo.Operand) bool {
|
func Is3(op Op) bool {
|
||||||
i, ok := op.(Imm)
|
i, ok := op.(Imm)
|
||||||
return ok && i == 3
|
return ok && i == 3
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsImm2u returns true if op is a 2-bit unsigned immediate (less than 4).
|
// IsImm2u returns true if op is a 2-bit unsigned immediate (less than 4).
|
||||||
func IsImm2u(op avo.Operand) bool {
|
func IsImm2u(op Op) bool {
|
||||||
i, ok := op.(Imm)
|
i, ok := op.(Imm)
|
||||||
return ok && i < 4
|
return ok && i < 4
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsImm8 returns true is op is an 8-bit immediate.
|
// IsImm8 returns true is op is an 8-bit immediate.
|
||||||
func IsImm8(op avo.Operand) bool {
|
func IsImm8(op Op) bool {
|
||||||
i, ok := op.(Imm)
|
i, ok := op.(Imm)
|
||||||
return ok && i <= math.MaxUint8
|
return ok && i <= math.MaxUint8
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsImm16 returns true is op is a 16-bit immediate.
|
// IsImm16 returns true is op is a 16-bit immediate.
|
||||||
func IsImm16(op avo.Operand) bool {
|
func IsImm16(op Op) bool {
|
||||||
i, ok := op.(Imm)
|
i, ok := op.(Imm)
|
||||||
return ok && i <= math.MaxUint16
|
return ok && i <= math.MaxUint16
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsImm32 returns true is op is a 32-bit immediate.
|
// IsImm32 returns true is op is a 32-bit immediate.
|
||||||
func IsImm32(op avo.Operand) bool {
|
func IsImm32(op Op) bool {
|
||||||
i, ok := op.(Imm)
|
i, ok := op.(Imm)
|
||||||
return ok && i <= math.MaxUint32
|
return ok && i <= math.MaxUint32
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsImm64 returns true is op is a 64-bit immediate.
|
// IsImm64 returns true is op is a 64-bit immediate.
|
||||||
func IsImm64(op avo.Operand) bool {
|
func IsImm64(op Op) bool {
|
||||||
_, ok := op.(Imm)
|
_, ok := op.(Imm)
|
||||||
return ok
|
return ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsAl returns true if op is the AL register.
|
// IsAl returns true if op is the AL register.
|
||||||
func IsAl(op avo.Operand) bool {
|
func IsAl(op Op) bool {
|
||||||
return op == reg.AL
|
return op == reg.AL
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsCl returns true if op is the CL register.
|
// IsCl returns true if op is the CL register.
|
||||||
func IsCl(op avo.Operand) bool {
|
func IsCl(op Op) bool {
|
||||||
return op == reg.CL
|
return op == reg.CL
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsAx returns true if op is the 16-bit AX register.
|
// IsAx returns true if op is the 16-bit AX register.
|
||||||
func IsAx(op avo.Operand) bool {
|
func IsAx(op Op) bool {
|
||||||
return op == reg.AX
|
return op == reg.AX
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsEax returns true if op is the 32-bit EAX register.
|
// IsEax returns true if op is the 32-bit EAX register.
|
||||||
func IsEax(op avo.Operand) bool {
|
func IsEax(op Op) bool {
|
||||||
return op == reg.EAX
|
return op == reg.EAX
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsRax returns true if op is the 64-bit RAX register.
|
// IsRax returns true if op is the 64-bit RAX register.
|
||||||
func IsRax(op avo.Operand) bool {
|
func IsRax(op Op) bool {
|
||||||
return op == reg.RAX
|
return op == reg.RAX
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsR8 returns true if op is an 8-bit general-purpose register.
|
// IsR8 returns true if op is an 8-bit general-purpose register.
|
||||||
func IsR8(op avo.Operand) bool {
|
func IsR8(op Op) bool {
|
||||||
return IsGP(op, 1)
|
return IsGP(op, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsR16 returns true if op is a 16-bit general-purpose register.
|
// IsR16 returns true if op is a 16-bit general-purpose register.
|
||||||
func IsR16(op avo.Operand) bool {
|
func IsR16(op Op) bool {
|
||||||
return IsGP(op, 2)
|
return IsGP(op, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsR32 returns true if op is a 32-bit general-purpose register.
|
// IsR32 returns true if op is a 32-bit general-purpose register.
|
||||||
func IsR32(op avo.Operand) bool {
|
func IsR32(op Op) bool {
|
||||||
return IsGP(op, 4)
|
return IsGP(op, 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsR64 returns true if op is a 64-bit general-purpose register.
|
// IsR64 returns true if op is a 64-bit general-purpose register.
|
||||||
func IsR64(op avo.Operand) bool {
|
func IsR64(op Op) bool {
|
||||||
return IsGP(op, 8)
|
return IsGP(op, 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsGP returns true if op is a general-purpose register of size n bytes.
|
// IsGP returns true if op is a general-purpose register of size n bytes.
|
||||||
func IsGP(op avo.Operand, n uint) bool {
|
func IsGP(op Op, n uint) bool {
|
||||||
return IsRegisterKindSize(op, reg.GP, n)
|
return IsRegisterKindSize(op, reg.GP, n)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsXmm0 returns true if op is the X0 register.
|
// IsXmm0 returns true if op is the X0 register.
|
||||||
func IsXmm0(op avo.Operand) bool {
|
func IsXmm0(op Op) bool {
|
||||||
return op == reg.X0
|
return op == reg.X0
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsXmm returns true if op is a 128-bit XMM register.
|
// IsXmm returns true if op is a 128-bit XMM register.
|
||||||
func IsXmm(op avo.Operand) bool {
|
func IsXmm(op Op) bool {
|
||||||
return IsRegisterKindSize(op, reg.SSEAVX, 16)
|
return IsRegisterKindSize(op, reg.SSEAVX, 16)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsYmm returns true if op is a 256-bit YMM register.
|
// IsYmm returns true if op is a 256-bit YMM register.
|
||||||
func IsYmm(op avo.Operand) bool {
|
func IsYmm(op Op) bool {
|
||||||
return IsRegisterKindSize(op, reg.SSEAVX, 32)
|
return IsRegisterKindSize(op, reg.SSEAVX, 32)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsRegisterKindSize returns true if op is a register of the given kind and size in bytes.
|
// IsRegisterKindSize returns true if op is a register of the given kind and size in bytes.
|
||||||
func IsRegisterKindSize(op avo.Operand, k reg.Kind, n uint) bool {
|
func IsRegisterKindSize(op Op, k reg.Kind, n uint) bool {
|
||||||
r, ok := op.(reg.Register)
|
r, ok := op.(reg.Register)
|
||||||
return ok && r.Kind() == k && r.Bytes() == n
|
return ok && r.Kind() == k && r.Bytes() == n
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsM returns true if op is a 16-, 32- or 64-bit memory operand.
|
// IsM returns true if op is a 16-, 32- or 64-bit memory operand.
|
||||||
func IsM(op avo.Operand) bool {
|
func IsM(op Op) bool {
|
||||||
// TODO(mbm): confirm "m" check is defined correctly
|
// TODO(mbm): confirm "m" check is defined correctly
|
||||||
// Intel manual: "A 16-, 32- or 64-bit operand in memory."
|
// Intel manual: "A 16-, 32- or 64-bit operand in memory."
|
||||||
return IsM16(op) || IsM32(op) || IsM64(op)
|
return IsM16(op) || IsM32(op) || IsM64(op)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsM8 returns true if op is an 8-bit memory operand.
|
// IsM8 returns true if op is an 8-bit memory operand.
|
||||||
func IsM8(op avo.Operand) bool {
|
func IsM8(op Op) bool {
|
||||||
// TODO(mbm): confirm "m8" check is defined correctly
|
// TODO(mbm): confirm "m8" check is defined correctly
|
||||||
// Intel manual: "A byte operand in memory, usually expressed as a variable or
|
// Intel manual: "A byte operand in memory, usually expressed as a variable or
|
||||||
// array name, but pointed to by the DS:(E)SI or ES:(E)DI registers. In 64-bit
|
// array name, but pointed to by the DS:(E)SI or ES:(E)DI registers. In 64-bit
|
||||||
@@ -138,84 +136,84 @@ func IsM8(op avo.Operand) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// IsM16 returns true if op is a 16-bit memory operand.
|
// IsM16 returns true if op is a 16-bit memory operand.
|
||||||
func IsM16(op avo.Operand) bool {
|
func IsM16(op Op) bool {
|
||||||
return IsMSize(op, 2)
|
return IsMSize(op, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsM32 returns true if op is a 16-bit memory operand.
|
// IsM32 returns true if op is a 16-bit memory operand.
|
||||||
func IsM32(op avo.Operand) bool {
|
func IsM32(op Op) bool {
|
||||||
return IsMSize(op, 4)
|
return IsMSize(op, 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsM64 returns true if op is a 64-bit memory operand.
|
// IsM64 returns true if op is a 64-bit memory operand.
|
||||||
func IsM64(op avo.Operand) bool {
|
func IsM64(op Op) bool {
|
||||||
return IsMSize(op, 8)
|
return IsMSize(op, 8)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsMSize returns true if op is a memory operand using general-purpose address
|
// IsMSize returns true if op is a memory operand using general-purpose address
|
||||||
// registers of the given size in bytes.
|
// registers of the given size in bytes.
|
||||||
func IsMSize(op avo.Operand, n uint) bool {
|
func IsMSize(op Op, n uint) bool {
|
||||||
// TODO(mbm): should memory operands have a size attribute as well?
|
// TODO(mbm): should memory operands have a size attribute as well?
|
||||||
m, ok := op.(Mem)
|
m, ok := op.(Mem)
|
||||||
return ok && IsGP(m.Base, n) && (m.Index == nil || IsGP(m.Index, n))
|
return ok && IsGP(m.Base, n) && (m.Index == nil || IsGP(m.Index, n))
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsM128 returns true if op is a 128-bit memory operand.
|
// IsM128 returns true if op is a 128-bit memory operand.
|
||||||
func IsM128(op avo.Operand) bool {
|
func IsM128(op Op) bool {
|
||||||
// TODO(mbm): should "m128" be the same as "m64"?
|
// TODO(mbm): should "m128" be the same as "m64"?
|
||||||
return IsM64(op)
|
return IsM64(op)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsM256 returns true if op is a 256-bit memory operand.
|
// IsM256 returns true if op is a 256-bit memory operand.
|
||||||
func IsM256(op avo.Operand) bool {
|
func IsM256(op Op) bool {
|
||||||
// TODO(mbm): should "m256" be the same as "m64"?
|
// TODO(mbm): should "m256" be the same as "m64"?
|
||||||
return IsM64(op)
|
return IsM64(op)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsVm32x returns true if op is a vector memory operand with 32-bit XMM index.
|
// IsVm32x returns true if op is a vector memory operand with 32-bit XMM index.
|
||||||
func IsVm32x(op avo.Operand) bool {
|
func IsVm32x(op Op) bool {
|
||||||
return IsVmx(op)
|
return IsVmx(op)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsVm64x returns true if op is a vector memory operand with 64-bit XMM index.
|
// IsVm64x returns true if op is a vector memory operand with 64-bit XMM index.
|
||||||
func IsVm64x(op avo.Operand) bool {
|
func IsVm64x(op Op) bool {
|
||||||
return IsVmx(op)
|
return IsVmx(op)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsVmx returns true if op is a vector memory operand with XMM index.
|
// IsVmx returns true if op is a vector memory operand with XMM index.
|
||||||
func IsVmx(op avo.Operand) bool {
|
func IsVmx(op Op) bool {
|
||||||
return isvm(op, IsXmm)
|
return isvm(op, IsXmm)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsVm32y returns true if op is a vector memory operand with 32-bit YMM index.
|
// IsVm32y returns true if op is a vector memory operand with 32-bit YMM index.
|
||||||
func IsVm32y(op avo.Operand) bool {
|
func IsVm32y(op Op) bool {
|
||||||
return IsVmy(op)
|
return IsVmy(op)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsVm64y returns true if op is a vector memory operand with 64-bit YMM index.
|
// IsVm64y returns true if op is a vector memory operand with 64-bit YMM index.
|
||||||
func IsVm64y(op avo.Operand) bool {
|
func IsVm64y(op Op) bool {
|
||||||
return IsVmy(op)
|
return IsVmy(op)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsVmy returns true if op is a vector memory operand with YMM index.
|
// IsVmy returns true if op is a vector memory operand with YMM index.
|
||||||
func IsVmy(op avo.Operand) bool {
|
func IsVmy(op Op) bool {
|
||||||
return isvm(op, IsYmm)
|
return isvm(op, IsYmm)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isvm(op avo.Operand, idx func(avo.Operand) bool) bool {
|
func isvm(op Op, idx func(Op) bool) bool {
|
||||||
m, ok := op.(Mem)
|
m, ok := op.(Mem)
|
||||||
return ok && IsR64(m.Base) && idx(m.Index)
|
return ok && IsR64(m.Base) && idx(m.Index)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsRel8 returns true if op is an 8-bit offset relative to instruction pointer.
|
// IsRel8 returns true if op is an 8-bit offset relative to instruction pointer.
|
||||||
func IsRel8(op avo.Operand) bool {
|
func IsRel8(op Op) bool {
|
||||||
r, ok := op.(Rel)
|
r, ok := op.(Rel)
|
||||||
return ok && r == Rel(int8(r))
|
return ok && r == Rel(int8(r))
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsRel32 returns true if op is an offset relative to instruction pointer, or a
|
// IsRel32 returns true if op is an offset relative to instruction pointer, or a
|
||||||
// label reference.
|
// label reference.
|
||||||
func IsRel32(op avo.Operand) bool {
|
func IsRel32(op Op) bool {
|
||||||
// TODO(mbm): should labels be considered separately?
|
// TODO(mbm): should labels be considered separately?
|
||||||
_, rel := op.(Rel)
|
_, rel := op.(Rel)
|
||||||
_, label := op.(LabelRef)
|
_, label := op.(LabelRef)
|
||||||
|
|||||||
@@ -6,14 +6,13 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/mmcloughlin/avo"
|
|
||||||
"github.com/mmcloughlin/avo/reg"
|
"github.com/mmcloughlin/avo/reg"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestChecks(t *testing.T) {
|
func TestChecks(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
Predicate func(avo.Operand) bool
|
Predicate func(Op) bool
|
||||||
Operand avo.Operand
|
Operand Op
|
||||||
Expect bool
|
Expect bool
|
||||||
}{
|
}{
|
||||||
// Immediates
|
// Immediates
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ import (
|
|||||||
"github.com/mmcloughlin/avo/reg"
|
"github.com/mmcloughlin/avo/reg"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Op interface {
|
||||||
|
Asm() string
|
||||||
|
}
|
||||||
|
|
||||||
type Mem struct {
|
type Mem struct {
|
||||||
Disp int
|
Disp int
|
||||||
Base reg.Register
|
Base reg.Register
|
||||||
|
|||||||
83
pass/cfg.go
Normal file
83
pass/cfg.go
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
package pass
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/mmcloughlin/avo"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LabelTarget populates the LabelTarget of the given function. This maps from
|
||||||
|
// label name to the following instruction.
|
||||||
|
func LabelTarget(fn *avo.Function) error {
|
||||||
|
target := map[avo.Label]*avo.Instruction{}
|
||||||
|
for idx := 0; idx < len(fn.Nodes); idx++ {
|
||||||
|
// Is this a label?
|
||||||
|
lbl, ok := fn.Nodes[idx].(avo.Label)
|
||||||
|
if !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Check for a duplicate label.
|
||||||
|
if _, found := target[lbl]; found {
|
||||||
|
return fmt.Errorf("duplicate label \"%s\"", lbl)
|
||||||
|
}
|
||||||
|
// Advance to next node.
|
||||||
|
if idx == len(fn.Nodes)-1 {
|
||||||
|
return errors.New("function ends with label")
|
||||||
|
}
|
||||||
|
idx++
|
||||||
|
// Should be an instruction.
|
||||||
|
i, ok := fn.Nodes[idx].(*avo.Instruction)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("instruction should follow a label")
|
||||||
|
}
|
||||||
|
target[lbl] = i
|
||||||
|
}
|
||||||
|
fn.LabelTarget = target
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CFG constructs the call-flow-graph of each function.
|
||||||
|
func CFG(fn *avo.Function) error {
|
||||||
|
is := fn.Instructions()
|
||||||
|
n := len(is)
|
||||||
|
|
||||||
|
// Populate successors.
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
cur := is[i]
|
||||||
|
var nxt *avo.Instruction
|
||||||
|
if i+1 < n {
|
||||||
|
nxt = is[i+1]
|
||||||
|
}
|
||||||
|
|
||||||
|
// If it's a branch, locate the target.
|
||||||
|
if cur.IsBranch {
|
||||||
|
lbl := cur.TargetLabel()
|
||||||
|
if lbl == nil {
|
||||||
|
return errors.New("no label for branch instruction")
|
||||||
|
}
|
||||||
|
target, found := fn.LabelTarget[*lbl]
|
||||||
|
if !found {
|
||||||
|
return errors.New("unknown label")
|
||||||
|
}
|
||||||
|
cur.Succ = append(cur.Succ, target)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, could continue to the following instruction.
|
||||||
|
switch {
|
||||||
|
case cur.IsTerminal:
|
||||||
|
case cur.IsBranch && !cur.IsConditional:
|
||||||
|
default:
|
||||||
|
cur.Succ = append(cur.Succ, nxt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate predecessors.
|
||||||
|
for _, i := range is {
|
||||||
|
for _, s := range i.Succ {
|
||||||
|
s.Pred = append(s.Pred, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
77
pass/cfg_test.go
Normal file
77
pass/cfg_test.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
package pass
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mmcloughlin/avo"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLabelTarget(t *testing.T) {
|
||||||
|
expect := map[avo.Label]*avo.Instruction{
|
||||||
|
"lblA": &avo.Instruction{Opcode: "A"},
|
||||||
|
"lblB": &avo.Instruction{Opcode: "B"},
|
||||||
|
}
|
||||||
|
|
||||||
|
f := avo.NewFunction("happypath")
|
||||||
|
for lbl, i := range expect {
|
||||||
|
f.AddLabel(lbl)
|
||||||
|
f.AddInstruction(i)
|
||||||
|
f.AddInstruction(&avo.Instruction{Opcode: "IDK"})
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := LabelTarget(f); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !reflect.DeepEqual(expect, f.LabelTarget) {
|
||||||
|
t.Fatalf("incorrect LabelTarget value\ngot=%#v\nexpext=%#v\n", f.LabelTarget, expect)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLabelTargetDuplicate(t *testing.T) {
|
||||||
|
f := avo.NewFunction("dupelabel")
|
||||||
|
f.AddLabel(avo.Label("lblA"))
|
||||||
|
f.AddInstruction(&avo.Instruction{Opcode: "A"})
|
||||||
|
f.AddLabel(avo.Label("lblA"))
|
||||||
|
f.AddInstruction(&avo.Instruction{Opcode: "A"})
|
||||||
|
|
||||||
|
err := LabelTarget(f)
|
||||||
|
|
||||||
|
if err == nil || err.Error() != "duplicate label \"lblA\"" {
|
||||||
|
t.Fatalf("expected error on duplcate label; got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLabelTargetEndsWithLabel(t *testing.T) {
|
||||||
|
f := avo.NewFunction("endswithlabel")
|
||||||
|
f.AddInstruction(&avo.Instruction{Opcode: "A"})
|
||||||
|
f.AddLabel(avo.Label("theend"))
|
||||||
|
|
||||||
|
err := LabelTarget(f)
|
||||||
|
|
||||||
|
if err == nil || err.Error() != "function ends with label" {
|
||||||
|
t.Fatalf("expected error when function ends with label; got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestLabelTargetInstructionFollowLabel(t *testing.T) {
|
||||||
|
f := avo.NewFunction("expectinstafterlabel")
|
||||||
|
f.AddLabel(avo.Label("lblA"))
|
||||||
|
f.AddLabel(avo.Label("lblB"))
|
||||||
|
f.AddInstruction(&avo.Instruction{Opcode: "A"})
|
||||||
|
|
||||||
|
err := LabelTarget(f)
|
||||||
|
|
||||||
|
if err == nil || err.Error() != "instruction should follow a label" {
|
||||||
|
t.Fatalf("expected error when label is not followed by instruction; got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCFG(t *testing.T) {
|
||||||
|
// TODO(mbm): jump backward
|
||||||
|
// TODO(mbm): jump forward
|
||||||
|
// TODO(mbm): multiple returns
|
||||||
|
// TODO(mbm): infinite loop
|
||||||
|
// TODO(mbm): very short infinite loop
|
||||||
|
}
|
||||||
17
pass/pass.go
Normal file
17
pass/pass.go
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package pass
|
||||||
|
|
||||||
|
import "github.com/mmcloughlin/avo"
|
||||||
|
|
||||||
|
// TODO(mbm): pass types
|
||||||
|
|
||||||
|
// FunctionPass builds a full pass that operates on all functions independently.
|
||||||
|
func FunctionPass(p func(*avo.Function) error) func(*avo.File) error {
|
||||||
|
return func(f *avo.File) error {
|
||||||
|
for _, fn := range f.Functions {
|
||||||
|
if err := p(fn); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mmcloughlin/avo/operand"
|
||||||
)
|
)
|
||||||
|
|
||||||
// dot is the pesky unicode dot used in Go assembly.
|
// dot is the pesky unicode dot used in Go assembly.
|
||||||
@@ -69,7 +71,7 @@ func (p *GoPrinter) multicomment(lines []string) {
|
|||||||
func (p *GoPrinter) function(f *Function) {
|
func (p *GoPrinter) function(f *Function) {
|
||||||
p.printf("TEXT %s%s(SB),0,$%d-%d\n", dot, f.Name(), f.FrameBytes(), f.ArgumentBytes())
|
p.printf("TEXT %s%s(SB),0,$%d-%d\n", dot, f.Name(), f.FrameBytes(), f.ArgumentBytes())
|
||||||
|
|
||||||
for _, node := range f.nodes {
|
for _, node := range f.Nodes {
|
||||||
switch n := node.(type) {
|
switch n := node.(type) {
|
||||||
case Instruction:
|
case Instruction:
|
||||||
p.printf("\t%s\t%s\n", n.Opcode, joinOperands(n.Operands))
|
p.printf("\t%s\t%s\n", n.Opcode, joinOperands(n.Operands))
|
||||||
@@ -91,7 +93,7 @@ func (p *GoPrinter) printf(format string, args ...interface{}) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func joinOperands(operands []Operand) string {
|
func joinOperands(operands []operand.Op) string {
|
||||||
asm := make([]string, len(operands))
|
asm := make([]string, len(operands))
|
||||||
for i, op := range operands {
|
for i, op := range operands {
|
||||||
asm[i] = op.Asm()
|
asm[i] = op.Asm()
|
||||||
|
|||||||
6921
x86/zctors.go
6921
x86/zctors.go
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user