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
152 lines
3.2 KiB
Go
152 lines
3.2 KiB
Go
package operand
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/mmcloughlin/avo/reg"
|
|
)
|
|
|
|
// Op is an operand.
|
|
type Op interface {
|
|
Asm() string
|
|
}
|
|
|
|
// Symbol represents a symbol name.
|
|
type Symbol struct {
|
|
Name string
|
|
Static bool // only visible in current source file
|
|
}
|
|
|
|
// NewStaticSymbol builds a static Symbol. Static symbols are only visible in the current source file.
|
|
func NewStaticSymbol(name string) Symbol {
|
|
return Symbol{Name: name, Static: true}
|
|
}
|
|
|
|
func (s Symbol) String() string {
|
|
n := s.Name
|
|
if s.Static {
|
|
n += "<>"
|
|
}
|
|
return n
|
|
}
|
|
|
|
// Mem represents a memory reference.
|
|
type Mem struct {
|
|
Symbol Symbol
|
|
Disp int
|
|
Base reg.Register
|
|
Index reg.Register
|
|
Scale uint8
|
|
}
|
|
|
|
// NewParamAddr is a convenience to build a Mem operand pointing to a function
|
|
// parameter, which is a named offset from the frame pointer pseudo register.
|
|
func NewParamAddr(name string, offset int) Mem {
|
|
return Mem{
|
|
Symbol: Symbol{
|
|
Name: name,
|
|
Static: false,
|
|
},
|
|
Disp: offset,
|
|
Base: reg.FramePointer,
|
|
}
|
|
}
|
|
|
|
// NewStackAddr returns a memory reference relative to the stack pointer.
|
|
func NewStackAddr(offset int) Mem {
|
|
return Mem{
|
|
Disp: offset,
|
|
Base: reg.StackPointer,
|
|
}
|
|
}
|
|
|
|
// NewDataAddr returns a memory reference relative to the named data symbol.
|
|
func NewDataAddr(sym Symbol, offset int) Mem {
|
|
return Mem{
|
|
Symbol: sym,
|
|
Disp: offset,
|
|
Base: reg.StaticBase,
|
|
}
|
|
}
|
|
|
|
// Offset returns a reference to m plus idx bytes.
|
|
func (m Mem) Offset(idx int) Mem {
|
|
a := m
|
|
a.Disp += idx
|
|
return a
|
|
}
|
|
|
|
// Idx returns a new memory reference with (Index, Scale) set to (r, s).
|
|
func (m Mem) Idx(r reg.Register, s uint8) Mem {
|
|
a := m
|
|
a.Index = r
|
|
a.Scale = s
|
|
return a
|
|
}
|
|
|
|
// Asm returns an assembly syntax representation of m.
|
|
func (m Mem) Asm() string {
|
|
a := m.Symbol.String()
|
|
if a != "" {
|
|
a += fmt.Sprintf("%+d", m.Disp)
|
|
} else if m.Disp != 0 {
|
|
a += fmt.Sprintf("%d", m.Disp)
|
|
}
|
|
if m.Base != nil {
|
|
a += fmt.Sprintf("(%s)", m.Base.Asm())
|
|
}
|
|
if m.Index != nil && m.Scale != 0 {
|
|
a += fmt.Sprintf("(%s*%d)", m.Index.Asm(), m.Scale)
|
|
}
|
|
return a
|
|
}
|
|
|
|
// Rel is an offset relative to the instruction pointer.
|
|
type Rel int32
|
|
|
|
// Asm returns an assembly syntax representation of r.
|
|
func (r Rel) Asm() string {
|
|
return fmt.Sprintf(".%+d", r)
|
|
}
|
|
|
|
// LabelRef is a reference to a label.
|
|
type LabelRef string
|
|
|
|
// Asm returns an assembly syntax representation of l.
|
|
func (l LabelRef) Asm() string {
|
|
return string(l)
|
|
}
|
|
|
|
// Registers returns the list of all operands involved in the given operand.
|
|
func Registers(op Op) []reg.Register {
|
|
switch op := op.(type) {
|
|
case reg.Register:
|
|
return []reg.Register{op}
|
|
case Mem:
|
|
var r []reg.Register
|
|
if op.Base != nil {
|
|
r = append(r, op.Base)
|
|
}
|
|
if op.Index != nil {
|
|
r = append(r, op.Index)
|
|
}
|
|
return r
|
|
case Constant, Rel, LabelRef:
|
|
return nil
|
|
}
|
|
panic("unknown operand type")
|
|
}
|
|
|
|
// ApplyAllocation returns an operand with allocated registers replaced. Registers missing from the allocation are left alone.
|
|
func ApplyAllocation(op Op, a reg.Allocation) Op {
|
|
switch op := op.(type) {
|
|
case reg.Register:
|
|
return a.LookupRegisterDefault(op)
|
|
case Mem:
|
|
op.Base = a.LookupRegisterDefault(op.Base)
|
|
op.Index = a.LookupRegisterDefault(op.Index)
|
|
return op
|
|
}
|
|
return op
|
|
}
|