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 }