Issue #100 demonstrated that register allocation for aliased registers is fundamentally broken. The root of the issue is that currently accesses to the same virtual register with different masks are treated as different registers. This PR takes a different approach: * Liveness analysis is masked: we now properly consider which parts of a register are live * Register allocation produces a mapping from virtual to physical ID, and aliasing is applied later In addition, a new pass ZeroExtend32BitOutputs accounts for the fact that 32-bit writes in 64-bit mode should actually be treated as 64-bit writes (the result is zero-extended). Closes #100
351 lines
7.4 KiB
Go
351 lines
7.4 KiB
Go
package ir
|
|
|
|
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() {}
|
|
|
|
// Comment represents a multi-line comment.
|
|
type Comment struct {
|
|
Lines []string
|
|
}
|
|
|
|
func (c *Comment) node() {}
|
|
|
|
// NewComment builds a Comment consisting of the provided lines.
|
|
func NewComment(lines ...string) *Comment {
|
|
return &Comment{
|
|
Lines: lines,
|
|
}
|
|
}
|
|
|
|
// 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
|
|
CancellingInputs bool
|
|
|
|
// ISA is the list of required instruction set extensions.
|
|
ISA []string
|
|
|
|
// CFG.
|
|
Pred []*Instruction
|
|
Succ []*Instruction
|
|
|
|
// LiveIn/LiveOut are sets of live register IDs pre/post execution.
|
|
LiveIn reg.MaskSet
|
|
LiveOut reg.MaskSet
|
|
}
|
|
|
|
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)...)
|
|
}
|
|
if i.CancellingInputs && rs[0] == rs[1] {
|
|
rs = []reg.Register{}
|
|
}
|
|
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
|
|
}
|
|
|
|
// Pragma represents a function compiler directive.
|
|
type Pragma struct {
|
|
Directive string
|
|
Arguments []string
|
|
}
|
|
|
|
// Function represents an assembly function.
|
|
type Function struct {
|
|
Name string
|
|
Attributes attr.Attribute
|
|
Pragmas []Pragma
|
|
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
|
|
|
|
// ISA is the list of required instruction set extensions.
|
|
ISA []string
|
|
}
|
|
|
|
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(),
|
|
}
|
|
}
|
|
|
|
// AddPragma adds a pragma to this function.
|
|
func (f *Function) AddPragma(directive string, args ...string) {
|
|
f.Pragmas = append(f.Pragmas, Pragma{
|
|
Directive: directive,
|
|
Arguments: args,
|
|
})
|
|
}
|
|
|
|
// 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)
|
|
}
|
|
|
|
// AddComment adds comment lines to f.
|
|
func (f *Function) AddComment(lines ...string) {
|
|
f.AddNode(NewComment(lines...))
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// Labels returns just the list of label nodes.
|
|
func (f *Function) Labels() []Label {
|
|
var lbls []Label
|
|
for _, n := range f.Nodes {
|
|
lbl, ok := n.(Label)
|
|
if ok {
|
|
lbls = append(lbls, lbl)
|
|
}
|
|
}
|
|
return lbls
|
|
}
|
|
|
|
// 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)
|
|
}
|