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
191 lines
4.4 KiB
Go
191 lines
4.4 KiB
Go
package pass
|
|
|
|
import (
|
|
"errors"
|
|
"math"
|
|
"sort"
|
|
|
|
"github.com/mmcloughlin/avo/reg"
|
|
)
|
|
|
|
// edge is an edge of the interference graph, indicating that registers X and Y
|
|
// must be in non-conflicting registers.
|
|
type edge struct {
|
|
X, Y reg.ID
|
|
}
|
|
|
|
// Allocator is a graph-coloring register allocator.
|
|
type Allocator struct {
|
|
registers []reg.ID
|
|
allocation reg.Allocation
|
|
edges []*edge
|
|
possible map[reg.ID][]reg.ID
|
|
}
|
|
|
|
// NewAllocator builds an allocator for the given physical registers.
|
|
func NewAllocator(rs []reg.Physical) (*Allocator, error) {
|
|
// Set of IDs, excluding restricted registers.
|
|
idset := map[reg.ID]bool{}
|
|
for _, r := range rs {
|
|
if (r.Info() & reg.Restricted) != 0 {
|
|
continue
|
|
}
|
|
idset[r.ID()] = true
|
|
}
|
|
|
|
if len(idset) == 0 {
|
|
return nil, errors.New("no allocatable registers")
|
|
}
|
|
|
|
// Produce slice of unique register IDs.
|
|
var ids []reg.ID
|
|
for id := range idset {
|
|
ids = append(ids, id)
|
|
}
|
|
sort.Slice(ids, func(i, j int) bool { return ids[i] < ids[j] })
|
|
|
|
return &Allocator{
|
|
registers: ids,
|
|
allocation: reg.NewEmptyAllocation(),
|
|
possible: map[reg.ID][]reg.ID{},
|
|
}, nil
|
|
}
|
|
|
|
// NewAllocatorForKind builds an allocator for the given kind of registers.
|
|
func NewAllocatorForKind(k reg.Kind) (*Allocator, error) {
|
|
f := reg.FamilyOfKind(k)
|
|
if f == nil {
|
|
return nil, errors.New("unknown register family")
|
|
}
|
|
return NewAllocator(f.Registers())
|
|
}
|
|
|
|
// AddInterferenceSet records that r interferes with every register in s. Convenience wrapper around AddInterference.
|
|
func (a *Allocator) AddInterferenceSet(r reg.Register, s reg.MaskSet) {
|
|
for id, mask := range s {
|
|
if (r.Mask() & mask) != 0 {
|
|
a.AddInterference(r.ID(), id)
|
|
}
|
|
}
|
|
}
|
|
|
|
// AddInterference records that x and y must be assigned to non-conflicting physical registers.
|
|
func (a *Allocator) AddInterference(x, y reg.ID) {
|
|
a.Add(x)
|
|
a.Add(y)
|
|
a.edges = append(a.edges, &edge{X: x, Y: y})
|
|
}
|
|
|
|
// Add adds a register to be allocated. Does nothing if the register has already been added.
|
|
func (a *Allocator) Add(v reg.ID) {
|
|
if !v.IsVirtual() {
|
|
return
|
|
}
|
|
if _, found := a.possible[v]; found {
|
|
return
|
|
}
|
|
a.possible[v] = a.possibleregisters(v)
|
|
}
|
|
|
|
// Allocate allocates physical registers.
|
|
func (a *Allocator) Allocate() (reg.Allocation, error) {
|
|
for {
|
|
if err := a.update(); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if a.remaining() == 0 {
|
|
break
|
|
}
|
|
|
|
v := a.mostrestricted()
|
|
if err := a.alloc(v); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return a.allocation, nil
|
|
}
|
|
|
|
// update possible allocations based on edges.
|
|
func (a *Allocator) update() error {
|
|
var rem []*edge
|
|
for _, e := range a.edges {
|
|
x := a.allocation.LookupDefault(e.X)
|
|
y := a.allocation.LookupDefault(e.Y)
|
|
switch {
|
|
case x.IsVirtual() && y.IsVirtual():
|
|
rem = append(rem, e)
|
|
continue
|
|
case x.IsPhysical() && y.IsPhysical():
|
|
if x == y {
|
|
return errors.New("impossible register allocation")
|
|
}
|
|
case x.IsPhysical() && y.IsVirtual():
|
|
a.discardconflicting(y, x)
|
|
case x.IsVirtual() && y.IsPhysical():
|
|
a.discardconflicting(x, y)
|
|
default:
|
|
panic("unreachable")
|
|
}
|
|
}
|
|
a.edges = rem
|
|
|
|
return nil
|
|
}
|
|
|
|
// mostrestricted returns the virtual register with the least possibilities.
|
|
func (a *Allocator) mostrestricted() reg.ID {
|
|
n := int(math.MaxInt32)
|
|
var v reg.ID
|
|
for w, p := range a.possible {
|
|
// On a tie, choose the smallest ID in numeric order. This avoids
|
|
// non-deterministic allocations due to map iteration order.
|
|
if len(p) < n || (len(p) == n && w < v) {
|
|
n = len(p)
|
|
v = w
|
|
}
|
|
}
|
|
return v
|
|
}
|
|
|
|
// discardconflicting removes registers from vs possible list that conflict with p.
|
|
func (a *Allocator) discardconflicting(v, p reg.ID) {
|
|
a.possible[v] = filterregisters(a.possible[v], func(r reg.ID) bool {
|
|
return r != p
|
|
})
|
|
}
|
|
|
|
// alloc attempts to allocate a register to v.
|
|
func (a *Allocator) alloc(v reg.ID) error {
|
|
ps := a.possible[v]
|
|
if len(ps) == 0 {
|
|
return errors.New("failed to allocate registers")
|
|
}
|
|
p := ps[0]
|
|
a.allocation[v] = p
|
|
delete(a.possible, v)
|
|
return nil
|
|
}
|
|
|
|
// remaining returns the number of unallocated registers.
|
|
func (a *Allocator) remaining() int {
|
|
return len(a.possible)
|
|
}
|
|
|
|
// possibleregisters returns all allocate-able registers for the given virtual.
|
|
func (a *Allocator) possibleregisters(v reg.ID) []reg.ID {
|
|
return filterregisters(a.registers, func(r reg.ID) bool {
|
|
return v.Kind() == r.Kind()
|
|
})
|
|
}
|
|
|
|
func filterregisters(in []reg.ID, predicate func(reg.ID) bool) []reg.ID {
|
|
var rs []reg.ID
|
|
for _, r := range in {
|
|
if predicate(r) {
|
|
rs = append(rs, r)
|
|
}
|
|
}
|
|
return rs
|
|
}
|