Skip to content
Snippets Groups Projects
Select Git revision
  • 4b5d06d4ee4744facfbab5255578e50d044d8da5
  • master default protected
2 results

print.css

Blame
  • reg.go 6.13 KiB
    package pass
    
    import (
    	"errors"
    
    	"github.com/mmcloughlin/avo/gotypes"
    	"github.com/mmcloughlin/avo/ir"
    	"github.com/mmcloughlin/avo/operand"
    	"github.com/mmcloughlin/avo/reg"
    )
    
    // 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
    }
    
    // Liveness computes register liveness.
    func Liveness(fn *ir.Function) error {
    	// Note this implementation is initially naive so as to be "obviously correct".
    	// There are a well-known optimizations we can apply if necessary.
    
    	is := fn.Instructions()
    
    	// 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.
    	for _, i := range is {
    		i.LiveIn = reg.NewMaskSetFromRegisters(i.InputRegisters())
    		i.LiveOut = reg.NewEmptyMaskSet()
    	}
    
    	// 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
    				}
    				changes = i.LiveOut.Update(s.LiveIn) || changes
    			}
    
    			// in[n] = use[n] UNION (out[n] - def[n])
    			def := reg.NewMaskSetFromRegisters(i.OutputRegisters())
    			changes = i.LiveIn.Update(i.LiveOut.Difference(def)) || changes
    		}
    
    		if !changes {
    			break
    		}
    	}
    
    	return nil
    }
    
    // AllocateRegisters performs register allocation.
    func AllocateRegisters(fn *ir.Function) error {
    	// Initialize one allocator per kind.
    	as := map[reg.Kind]*Allocator{}
    	for _, i := range fn.Instructions() {
    		for _, r := range i.Registers() {
    			k := r.Kind()
    			if _, found := as[k]; !found {
    				a, err := NewAllocatorForKind(k)
    				if err != nil {
    					return err
    				}
    				as[k] = a
    			}
    		}
    	}
    
    	// 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())
    		}
    	}
    
    	// Record register interferences.
    	for _, i := range fn.Instructions() {
    		for _, d := range i.OutputRegisters() {
    			k := d.Kind()
    			out := i.LiveOut.OfKind(k)
    			out.DiscardRegister(d)
    			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
    }
    
    // BindRegisters applies the result of register allocation, replacing all virtual registers with their assigned physical registers.
    func BindRegisters(fn *ir.Function) error {
    	for _, i := range fn.Instructions() {
    		for idx := range i.Operands {
    			i.Operands[idx] = operand.ApplyAllocation(i.Operands[idx], fn.Allocation)
    		}
    		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)
    		}
    	}
    	return nil
    }
    
    // VerifyAllocation performs sanity checks following register allocation.
    func VerifyAllocation(fn *ir.Function) error {
    	// 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")
    			}
    		}
    	}
    
    	return nil
    }
    
    // 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()&reg.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
    }