The Context.Label method and LABEL global function did not agree. Also breaks the convention I'd like to set that capitalized functions must agree with existing Go assembly syntax. To help avoid a conflict with `avo.Label`, attributes were moved to their own package. Fixes #35
295 lines
6.3 KiB
Go
295 lines
6.3 KiB
Go
package avo
|
|
|
|
import (
|
|
"errors"
|
|
|
|
"github.com/mmcloughlin/avo/attr"
|
|
"github.com/mmcloughlin/avo/buildtags"
|
|
"github.com/mmcloughlin/avo/gotypes"
|
|
"github.com/mmcloughlin/avo/operand"
|
|
"github.com/mmcloughlin/avo/reg"
|
|
)
|
|
|
|
// Node is a part of a Function.
|
|
type Node interface {
|
|
node()
|
|
}
|
|
|
|
// Label within a function.
|
|
type Label string
|
|
|
|
func (l Label) node() {}
|
|
|
|
// Instruction is a single instruction in a function.
|
|
type Instruction struct {
|
|
Opcode string
|
|
Operands []operand.Op
|
|
|
|
Inputs []operand.Op
|
|
Outputs []operand.Op
|
|
|
|
IsTerminal bool
|
|
IsBranch bool
|
|
IsConditional bool
|
|
|
|
// CFG.
|
|
Pred []*Instruction
|
|
Succ []*Instruction
|
|
|
|
// LiveIn/LiveOut are sets of live register IDs pre/post execution.
|
|
LiveIn reg.Set
|
|
LiveOut reg.Set
|
|
}
|
|
|
|
func (i *Instruction) node() {}
|
|
|
|
// TargetLabel returns the label referenced by this instruction. Returns nil if
|
|
// no label is referenced.
|
|
func (i Instruction) TargetLabel() *Label {
|
|
if !i.IsBranch {
|
|
return nil
|
|
}
|
|
if len(i.Operands) == 0 {
|
|
return nil
|
|
}
|
|
if ref, ok := i.Operands[0].(operand.LabelRef); ok {
|
|
lbl := Label(ref)
|
|
return &lbl
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Registers returns all registers involved in the instruction.
|
|
func (i Instruction) Registers() []reg.Register {
|
|
var rs []reg.Register
|
|
for _, op := range i.Operands {
|
|
rs = append(rs, operand.Registers(op)...)
|
|
}
|
|
return rs
|
|
}
|
|
|
|
// InputRegisters returns all registers read by this instruction.
|
|
func (i Instruction) InputRegisters() []reg.Register {
|
|
var rs []reg.Register
|
|
for _, op := range i.Inputs {
|
|
rs = append(rs, operand.Registers(op)...)
|
|
}
|
|
for _, op := range i.Outputs {
|
|
if operand.IsMem(op) {
|
|
rs = append(rs, operand.Registers(op)...)
|
|
}
|
|
}
|
|
return rs
|
|
}
|
|
|
|
// OutputRegisters returns all registers written by this instruction.
|
|
func (i Instruction) OutputRegisters() []reg.Register {
|
|
var rs []reg.Register
|
|
for _, op := range i.Outputs {
|
|
if r, ok := op.(reg.Register); ok {
|
|
rs = append(rs, r)
|
|
}
|
|
}
|
|
return rs
|
|
}
|
|
|
|
// Section is a part of a file.
|
|
type Section interface {
|
|
section()
|
|
}
|
|
|
|
// File represents an assembly file.
|
|
type File struct {
|
|
Constraints buildtags.Constraints
|
|
Includes []string
|
|
Sections []Section
|
|
}
|
|
|
|
// NewFile initializes an empty file.
|
|
func NewFile() *File {
|
|
return &File{}
|
|
}
|
|
|
|
// AddSection appends a Section to the file.
|
|
func (f *File) AddSection(s Section) {
|
|
f.Sections = append(f.Sections, s)
|
|
}
|
|
|
|
// Functions returns all functions in the file.
|
|
func (f *File) Functions() []*Function {
|
|
var fns []*Function
|
|
for _, s := range f.Sections {
|
|
if fn, ok := s.(*Function); ok {
|
|
fns = append(fns, fn)
|
|
}
|
|
}
|
|
return fns
|
|
}
|
|
|
|
// Function represents an assembly function.
|
|
type Function struct {
|
|
Name string
|
|
Attributes attr.Attribute
|
|
Doc []string
|
|
Signature *gotypes.Signature
|
|
LocalSize int
|
|
|
|
Nodes []Node
|
|
|
|
// LabelTarget maps from label name to the following instruction.
|
|
LabelTarget map[Label]*Instruction
|
|
|
|
// Register allocation.
|
|
Allocation reg.Allocation
|
|
}
|
|
|
|
func (f *Function) section() {}
|
|
|
|
// NewFunction builds an empty function of the given name.
|
|
func NewFunction(name string) *Function {
|
|
return &Function{
|
|
Name: name,
|
|
Signature: gotypes.NewSignatureVoid(),
|
|
}
|
|
}
|
|
|
|
// SetSignature sets the function signature.
|
|
func (f *Function) SetSignature(s *gotypes.Signature) {
|
|
f.Signature = s
|
|
}
|
|
|
|
// AllocLocal allocates size bytes in this function's stack.
|
|
// Returns a reference to the base pointer for the newly allocated region.
|
|
func (f *Function) AllocLocal(size int) operand.Mem {
|
|
ptr := operand.NewStackAddr(f.LocalSize)
|
|
f.LocalSize += size
|
|
return ptr
|
|
}
|
|
|
|
// AddInstruction appends an instruction to f.
|
|
func (f *Function) AddInstruction(i *Instruction) {
|
|
f.AddNode(i)
|
|
}
|
|
|
|
// AddLabel appends a label to f.
|
|
func (f *Function) AddLabel(l Label) {
|
|
f.AddNode(l)
|
|
}
|
|
|
|
// AddNode appends a Node to f.
|
|
func (f *Function) AddNode(n Node) {
|
|
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
|
|
}
|
|
|
|
// Stub returns the Go function declaration.
|
|
func (f *Function) Stub() string {
|
|
return "func " + f.Name + f.Signature.String()
|
|
}
|
|
|
|
// FrameBytes returns the size of the stack frame in bytes.
|
|
func (f *Function) FrameBytes() int {
|
|
return f.LocalSize
|
|
}
|
|
|
|
// ArgumentBytes returns the size of the arguments in bytes.
|
|
func (f *Function) ArgumentBytes() int {
|
|
return f.Signature.Bytes()
|
|
}
|
|
|
|
// Datum represents a data element at a particular offset of a data section.
|
|
type Datum struct {
|
|
Offset int
|
|
Value operand.Constant
|
|
}
|
|
|
|
// NewDatum builds a Datum from the given constant.
|
|
func NewDatum(offset int, v operand.Constant) Datum {
|
|
return Datum{
|
|
Offset: offset,
|
|
Value: v,
|
|
}
|
|
}
|
|
|
|
// Interval returns the range of bytes this datum will occupy within its section.
|
|
func (d Datum) Interval() (int, int) {
|
|
return d.Offset, d.Offset + d.Value.Bytes()
|
|
}
|
|
|
|
// Overlaps returns true
|
|
func (d Datum) Overlaps(other Datum) bool {
|
|
s, e := d.Interval()
|
|
so, eo := other.Interval()
|
|
return !(eo <= s || e <= so)
|
|
}
|
|
|
|
// Global represents a DATA section.
|
|
type Global struct {
|
|
Symbol operand.Symbol
|
|
Attributes attr.Attribute
|
|
Data []Datum
|
|
Size int
|
|
}
|
|
|
|
// NewGlobal constructs an empty DATA section.
|
|
func NewGlobal(sym operand.Symbol) *Global {
|
|
return &Global{
|
|
Symbol: sym,
|
|
}
|
|
}
|
|
|
|
// NewStaticGlobal is a convenience for building a static DATA section.
|
|
func NewStaticGlobal(name string) *Global {
|
|
return NewGlobal(operand.NewStaticSymbol(name))
|
|
}
|
|
|
|
func (g *Global) section() {}
|
|
|
|
// Base returns a pointer to the start of the data section.
|
|
func (g *Global) Base() operand.Mem {
|
|
return operand.NewDataAddr(g.Symbol, 0)
|
|
}
|
|
|
|
// Grow ensures that the data section has at least the given size.
|
|
func (g *Global) Grow(size int) {
|
|
if g.Size < size {
|
|
g.Size = size
|
|
}
|
|
}
|
|
|
|
// AddDatum adds d to this data section, growing it if necessary. Errors if the datum overlaps with existing data.
|
|
func (g *Global) AddDatum(d Datum) error {
|
|
for _, other := range g.Data {
|
|
if d.Overlaps(other) {
|
|
return errors.New("overlaps existing datum")
|
|
}
|
|
}
|
|
g.add(d)
|
|
return nil
|
|
}
|
|
|
|
// Append the constant to the end of the data section.
|
|
func (g *Global) Append(v operand.Constant) {
|
|
g.add(Datum{
|
|
Offset: g.Size,
|
|
Value: v,
|
|
})
|
|
}
|
|
|
|
func (g *Global) add(d Datum) {
|
|
_, end := d.Interval()
|
|
g.Grow(end)
|
|
g.Data = append(g.Data, d)
|
|
}
|