2018-12-02 23:59:29 -08:00
|
|
|
|
package pass
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
2018-12-05 00:05:57 -08:00
|
|
|
|
"errors"
|
|
|
|
|
|
|
2026-03-06 20:14:02 +00:00
|
|
|
|
"sources.truenas.cloud/code/avo/gotypes"
|
|
|
|
|
|
"sources.truenas.cloud/code/avo/ir"
|
|
|
|
|
|
"sources.truenas.cloud/code/avo/operand"
|
|
|
|
|
|
"sources.truenas.cloud/code/avo/reg"
|
2018-12-02 23:59:29 -08:00
|
|
|
|
)
|
|
|
|
|
|
|
2020-01-22 22:50:40 -08:00
|
|
|
|
// ZeroExtend32BitOutputs applies the rule that "32-bit operands generate a
|
|
|
|
|
|
// 32-bit result, zero-extended to a 64-bit result in the destination
|
|
|
|
|
|
// general-purpose register" (Intel Software Developer’s Manual, Volume 1,
|
|
|
|
|
|
// 3.4.1.1).
|
|
|
|
|
|
func ZeroExtend32BitOutputs(i *ir.Instruction) error {
|
|
|
|
|
|
for j, op := range i.Outputs {
|
|
|
|
|
|
if !operand.IsR32(op) {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
r, ok := op.(reg.GP)
|
|
|
|
|
|
if !ok {
|
|
|
|
|
|
panic("r32 operand should satisfy reg.GP")
|
|
|
|
|
|
}
|
|
|
|
|
|
i.Outputs[j] = r.As64()
|
|
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-12-02 23:59:29 -08:00
|
|
|
|
// Liveness computes register liveness.
|
2019-01-06 14:21:10 -08:00
|
|
|
|
func Liveness(fn *ir.Function) error {
|
2018-12-05 00:05:57 -08:00
|
|
|
|
// Note this implementation is initially naive so as to be "obviously correct".
|
|
|
|
|
|
// There are a well-known optimizations we can apply if necessary.
|
|
|
|
|
|
|
2018-12-02 23:59:29 -08:00
|
|
|
|
is := fn.Instructions()
|
|
|
|
|
|
|
2018-12-24 12:48:29 -08:00
|
|
|
|
// Process instructions in reverse: poor approximation to topological sort.
|
|
|
|
|
|
// TODO(mbm): process instructions in topological sort order
|
|
|
|
|
|
for l, r := 0, len(is)-1; l < r; l, r = l+1, r-1 {
|
|
|
|
|
|
is[l], is[r] = is[r], is[l]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Initialize.
|
2018-12-02 23:59:29 -08:00
|
|
|
|
for _, i := range is {
|
2020-01-22 22:50:40 -08:00
|
|
|
|
i.LiveIn = reg.NewMaskSetFromRegisters(i.InputRegisters())
|
|
|
|
|
|
i.LiveOut = reg.NewEmptyMaskSet()
|
2018-12-02 23:59:29 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Iterative dataflow analysis.
|
|
|
|
|
|
for {
|
|
|
|
|
|
changes := false
|
|
|
|
|
|
|
|
|
|
|
|
for _, i := range is {
|
|
|
|
|
|
// out[n] = UNION[s IN succ[n]] in[s]
|
|
|
|
|
|
for _, s := range i.Succ {
|
|
|
|
|
|
if s == nil {
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
2020-01-22 22:50:40 -08:00
|
|
|
|
changes = i.LiveOut.Update(s.LiveIn) || changes
|
2018-12-02 23:59:29 -08:00
|
|
|
|
}
|
2018-12-24 12:48:29 -08:00
|
|
|
|
|
|
|
|
|
|
// in[n] = use[n] UNION (out[n] - def[n])
|
2020-01-22 22:50:40 -08:00
|
|
|
|
def := reg.NewMaskSetFromRegisters(i.OutputRegisters())
|
|
|
|
|
|
changes = i.LiveIn.Update(i.LiveOut.Difference(def)) || changes
|
2018-12-02 23:59:29 -08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if !changes {
|
|
|
|
|
|
break
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
2018-12-03 22:39:43 -08:00
|
|
|
|
|
2019-01-04 20:00:22 -08:00
|
|
|
|
// AllocateRegisters performs register allocation.
|
2019-01-06 14:21:10 -08:00
|
|
|
|
func AllocateRegisters(fn *ir.Function) error {
|
2021-04-18 19:22:09 -07:00
|
|
|
|
// Initialize one allocator per kind.
|
2018-12-05 00:05:57 -08:00
|
|
|
|
as := map[reg.Kind]*Allocator{}
|
|
|
|
|
|
for _, i := range fn.Instructions() {
|
2018-12-12 00:02:22 -08:00
|
|
|
|
for _, r := range i.Registers() {
|
|
|
|
|
|
k := r.Kind()
|
2018-12-05 00:05:57 -08:00
|
|
|
|
if _, found := as[k]; !found {
|
|
|
|
|
|
a, err := NewAllocatorForKind(k)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
as[k] = a
|
|
|
|
|
|
}
|
2021-04-18 19:22:09 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// De-prioritize the base pointer register. This can be used as a general
|
|
|
|
|
|
// purpose register, but it's callee-save so needs to be saved/restored if
|
|
|
|
|
|
// it is clobbered. For this reason we prefer to avoid using it unless
|
|
|
|
|
|
// forced to by register pressure.
|
|
|
|
|
|
for k, a := range as {
|
|
|
|
|
|
f := reg.FamilyOfKind(k)
|
|
|
|
|
|
for _, r := range f.Registers() {
|
|
|
|
|
|
if (r.Info() & reg.BasePointer) != 0 {
|
|
|
|
|
|
// Negative priority penalizes this register relative to all
|
|
|
|
|
|
// others (having default zero priority).
|
|
|
|
|
|
a.SetPriority(r.ID(), -1)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Populate registers to be allocated.
|
|
|
|
|
|
for _, i := range fn.Instructions() {
|
|
|
|
|
|
for _, r := range i.Registers() {
|
|
|
|
|
|
as[r.Kind()].Add(r.ID())
|
2018-12-12 00:02:22 -08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2018-12-05 00:05:57 -08:00
|
|
|
|
|
2018-12-12 00:02:22 -08:00
|
|
|
|
// Record register interferences.
|
|
|
|
|
|
for _, i := range fn.Instructions() {
|
|
|
|
|
|
for _, d := range i.OutputRegisters() {
|
|
|
|
|
|
k := d.Kind()
|
2018-12-05 00:05:57 -08:00
|
|
|
|
out := i.LiveOut.OfKind(k)
|
2020-01-22 22:50:40 -08:00
|
|
|
|
out.DiscardRegister(d)
|
2018-12-05 00:05:57 -08:00
|
|
|
|
as[k].AddInterferenceSet(d, out)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Execute register allocation.
|
|
|
|
|
|
fn.Allocation = reg.NewEmptyAllocation()
|
|
|
|
|
|
for _, a := range as {
|
|
|
|
|
|
al, err := a.Allocate()
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
if err := fn.Allocation.Merge(al); err != nil {
|
|
|
|
|
|
return err
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-01-04 20:00:22 -08:00
|
|
|
|
// BindRegisters applies the result of register allocation, replacing all virtual registers with their assigned physical registers.
|
2019-01-06 14:21:10 -08:00
|
|
|
|
func BindRegisters(fn *ir.Function) error {
|
2018-12-05 00:05:57 -08:00
|
|
|
|
for _, i := range fn.Instructions() {
|
|
|
|
|
|
for idx := range i.Operands {
|
|
|
|
|
|
i.Operands[idx] = operand.ApplyAllocation(i.Operands[idx], fn.Allocation)
|
|
|
|
|
|
}
|
2021-04-18 18:37:56 -07:00
|
|
|
|
for idx := range i.Inputs {
|
|
|
|
|
|
i.Inputs[idx] = operand.ApplyAllocation(i.Inputs[idx], fn.Allocation)
|
|
|
|
|
|
}
|
|
|
|
|
|
for idx := range i.Outputs {
|
|
|
|
|
|
i.Outputs[idx] = operand.ApplyAllocation(i.Outputs[idx], fn.Allocation)
|
|
|
|
|
|
}
|
2018-12-05 00:05:57 -08:00
|
|
|
|
}
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2019-01-04 20:00:22 -08:00
|
|
|
|
// VerifyAllocation performs sanity checks following register allocation.
|
2019-01-06 14:21:10 -08:00
|
|
|
|
func VerifyAllocation(fn *ir.Function) error {
|
2018-12-05 00:05:57 -08:00
|
|
|
|
// All registers should be physical.
|
|
|
|
|
|
for _, i := range fn.Instructions() {
|
|
|
|
|
|
for _, r := range i.Registers() {
|
|
|
|
|
|
if reg.ToPhysical(r) == nil {
|
|
|
|
|
|
return errors.New("non physical register found")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-12-03 22:39:43 -08:00
|
|
|
|
return nil
|
|
|
|
|
|
}
|
2021-04-18 18:37:56 -07:00
|
|
|
|
|
|
|
|
|
|
// EnsureBasePointerCalleeSaved ensures that the base pointer register will be
|
|
|
|
|
|
// saved and restored if it has been clobbered by the function.
|
|
|
|
|
|
func EnsureBasePointerCalleeSaved(fn *ir.Function) error {
|
|
|
|
|
|
// Check to see if the base pointer is written to.
|
|
|
|
|
|
clobbered := false
|
|
|
|
|
|
for _, i := range fn.Instructions() {
|
|
|
|
|
|
for _, r := range i.OutputRegisters() {
|
|
|
|
|
|
if p := reg.ToPhysical(r); p != nil && (p.Info()®.BasePointer) != 0 {
|
|
|
|
|
|
clobbered = true
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if !clobbered {
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// This function clobbers the base pointer register so we need to ensure it
|
|
|
|
|
|
// will be saved and restored. The Go assembler will do this automatically,
|
|
|
|
|
|
// with a few exceptions detailed below. In summary, we can usually ensure
|
|
|
|
|
|
// this happens by ensuring the function is not frameless (apart from
|
|
|
|
|
|
// NOFRAME functions).
|
|
|
|
|
|
//
|
|
|
|
|
|
// Reference: https://github.com/golang/go/blob/3f4977bd5800beca059defb5de4dc64cd758cbb9/src/cmd/internal/obj/x86/obj6.go#L591-L609
|
|
|
|
|
|
//
|
|
|
|
|
|
// var bpsize int
|
|
|
|
|
|
// if ctxt.Arch.Family == sys.AMD64 &&
|
|
|
|
|
|
// !p.From.Sym.NoFrame() && // (1) below
|
|
|
|
|
|
// !(autoffset == 0 && p.From.Sym.NoSplit()) && // (2) below
|
|
|
|
|
|
// !(autoffset == 0 && !hasCall) { // (3) below
|
|
|
|
|
|
// // Make room to save a base pointer.
|
|
|
|
|
|
// // There are 2 cases we must avoid:
|
|
|
|
|
|
// // 1) If noframe is set (which we do for functions which tail call).
|
|
|
|
|
|
// // 2) Scary runtime internals which would be all messed up by frame pointers.
|
|
|
|
|
|
// // We detect these using a heuristic: frameless nosplit functions.
|
|
|
|
|
|
// // TODO: Maybe someday we label them all with NOFRAME and get rid of this heuristic.
|
|
|
|
|
|
// // For performance, we also want to avoid:
|
|
|
|
|
|
// // 3) Frameless leaf functions
|
|
|
|
|
|
// bpsize = ctxt.Arch.PtrSize
|
|
|
|
|
|
// autoffset += int32(bpsize)
|
|
|
|
|
|
// p.To.Offset += int64(bpsize)
|
|
|
|
|
|
// } else {
|
|
|
|
|
|
// bpsize = 0
|
|
|
|
|
|
// }
|
|
|
|
|
|
//
|
|
|
|
|
|
if fn.Attributes.NOFRAME() {
|
|
|
|
|
|
return errors.New("NOFRAME function clobbers base pointer register")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if fn.LocalSize == 0 {
|
|
|
|
|
|
fn.AllocLocal(int(gotypes.PointerSize))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
}
|