mirror of
https://github.com/golang/go.git
synced 2025-05-25 09:21:21 +00:00
Use *Node of type ONAME instead of string as the key for variable maps. This will prevent aliasing between two identically named but differently scoped variables. Introduce an Aux value that encodes the offset of a variable from a base pointer (either global base pointer or stack pointer). Allow LEAQ and derivatives (MOVQ, etc.) to also have such an Aux field. Allocate space for AUTO variables in stackalloc. Change-Id: Ibdccdaea4bbc63a1f4882959ac374f2b467e3acd Reviewed-on: https://go-review.googlesource.com/11238 Reviewed-by: Josh Bleecher Snyder <josharian@gmail.com>
483 lines
13 KiB
Go
483 lines
13 KiB
Go
// Copyright 2015 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package ssa
|
|
|
|
import "sort"
|
|
|
|
func setloc(home []Location, v *Value, loc Location) []Location {
|
|
for v.ID >= ID(len(home)) {
|
|
home = append(home, nil)
|
|
}
|
|
home[v.ID] = loc
|
|
return home
|
|
}
|
|
|
|
type register uint
|
|
|
|
type regMask uint64
|
|
|
|
// TODO: make arch-dependent
|
|
var numRegs register = 64
|
|
|
|
var registers = [...]Register{
|
|
Register{0, "AX"},
|
|
Register{1, "CX"},
|
|
Register{2, "DX"},
|
|
Register{3, "BX"},
|
|
Register{4, "SP"},
|
|
Register{5, "BP"},
|
|
Register{6, "SI"},
|
|
Register{7, "DI"},
|
|
Register{8, "R8"},
|
|
Register{9, "R9"},
|
|
Register{10, "R10"},
|
|
Register{11, "R11"},
|
|
Register{12, "R12"},
|
|
Register{13, "R13"},
|
|
Register{14, "R14"},
|
|
Register{15, "R15"},
|
|
Register{16, "X0"},
|
|
Register{17, "X1"},
|
|
Register{18, "X2"},
|
|
Register{19, "X3"},
|
|
Register{20, "X4"},
|
|
Register{21, "X5"},
|
|
Register{22, "X6"},
|
|
Register{23, "X7"},
|
|
Register{24, "X8"},
|
|
Register{25, "X9"},
|
|
Register{26, "X10"},
|
|
Register{27, "X11"},
|
|
Register{28, "X12"},
|
|
Register{29, "X13"},
|
|
Register{30, "X14"},
|
|
Register{31, "X15"},
|
|
Register{32, "SB"}, // pseudo-register for global base pointer (aka %rip)
|
|
Register{33, "FLAGS"},
|
|
|
|
// TODO: make arch-dependent
|
|
}
|
|
|
|
// countRegs returns the number of set bits in the register mask.
|
|
func countRegs(r regMask) int {
|
|
n := 0
|
|
for r != 0 {
|
|
n += int(r & 1)
|
|
r >>= 1
|
|
}
|
|
return n
|
|
}
|
|
|
|
// pickReg picks an arbitrary register from the register mask.
|
|
func pickReg(r regMask) register {
|
|
// pick the lowest one
|
|
if r == 0 {
|
|
panic("can't pick a register from an empty set")
|
|
}
|
|
for i := register(0); ; i++ {
|
|
if r&1 != 0 {
|
|
return i
|
|
}
|
|
r >>= 1
|
|
}
|
|
}
|
|
|
|
// regalloc performs register allocation on f. It sets f.RegAlloc
|
|
// to the resulting allocation.
|
|
func regalloc(f *Func) {
|
|
// For now, a very simple allocator. Everything has a home
|
|
// location on the stack (TBD as a subsequent stackalloc pass).
|
|
// Values live in the home locations at basic block boundaries.
|
|
// We use a simple greedy allocator within a basic block.
|
|
home := make([]Location, f.NumValues())
|
|
|
|
addPhiCopies(f) // add copies of phi inputs in preceeding blocks
|
|
|
|
// Compute live values at the end of each block.
|
|
live := live(f)
|
|
lastUse := make([]int, f.NumValues())
|
|
|
|
var oldSched []*Value
|
|
|
|
// Hack to find sp and sb Values and assign them a register. (TODO: make not so hacky)
|
|
var sp, sb *Value
|
|
for _, v := range f.Entry.Values {
|
|
switch v.Op {
|
|
case OpSP:
|
|
sp = v
|
|
home = setloc(home, v, ®isters[4]) // TODO: arch-dependent
|
|
case OpSB:
|
|
sb = v
|
|
home = setloc(home, v, ®isters[32]) // TODO: arch-dependent
|
|
}
|
|
}
|
|
|
|
// Register allocate each block separately. All live values will live
|
|
// in home locations (stack slots) between blocks.
|
|
for _, b := range f.Blocks {
|
|
|
|
// Compute the index of the last use of each Value in the Block.
|
|
// Scheduling has already happened, so Values are totally ordered.
|
|
// lastUse[x] = max(i) where b.Value[i] uses Value x.
|
|
for i, v := range b.Values {
|
|
lastUse[v.ID] = -1
|
|
for _, w := range v.Args {
|
|
// could condition this store on w.Block == b, but no need
|
|
lastUse[w.ID] = i
|
|
}
|
|
}
|
|
// Values which are live at block exit have a lastUse of len(b.Values).
|
|
if b.Control != nil {
|
|
lastUse[b.Control.ID] = len(b.Values)
|
|
}
|
|
// Values live after block exit have a lastUse of len(b.Values)+1.
|
|
for _, vid := range live[b.ID] {
|
|
lastUse[vid] = len(b.Values) + 1
|
|
}
|
|
|
|
// For each register, store which value it contains
|
|
type regInfo struct {
|
|
v *Value // stack-homed original value (or nil if empty)
|
|
c *Value // the register copy of v
|
|
dirty bool // if the stack-homed copy is out of date
|
|
}
|
|
regs := make([]regInfo, numRegs)
|
|
|
|
// TODO: hack: initialize fixed registers
|
|
regs[4] = regInfo{sp, sp, false}
|
|
regs[32] = regInfo{sb, sb, false}
|
|
|
|
var used regMask // has a 1 for each non-nil entry in regs
|
|
var dirty regMask // has a 1 for each dirty entry in regs
|
|
|
|
oldSched = append(oldSched[:0], b.Values...)
|
|
b.Values = b.Values[:0]
|
|
|
|
for idx, v := range oldSched {
|
|
// For each instruction, do:
|
|
// set up inputs to v in registers
|
|
// pick output register
|
|
// run insn
|
|
// mark output register as dirty
|
|
// Note that v represents the Value at "home" (on the stack), and c
|
|
// is its register equivalent. There are two ways to establish c:
|
|
// - use of v. c will be a load from v's home.
|
|
// - definition of v. c will be identical to v but will live in
|
|
// a register. v will be modified into a spill of c.
|
|
regspec := opcodeTable[v.Op].reg
|
|
if v.Op == OpCopy || v.Op == OpConvNop {
|
|
// TODO: make this less of a hack
|
|
regspec = opcodeTable[OpAMD64ADDQconst].reg
|
|
}
|
|
inputs := regspec.inputs
|
|
outputs := regspec.outputs
|
|
if len(inputs) == 0 && len(outputs) == 0 {
|
|
// No register allocation required (or none specified yet)
|
|
b.Values = append(b.Values, v)
|
|
continue
|
|
}
|
|
if v.Op == OpCopy && v.Type.IsMemory() {
|
|
b.Values = append(b.Values, v)
|
|
continue
|
|
}
|
|
|
|
// Compute a good input ordering. Start with the most constrained input.
|
|
order := make([]intPair, len(inputs))
|
|
for i, input := range inputs {
|
|
order[i] = intPair{countRegs(input), i}
|
|
}
|
|
sort.Sort(byKey(order))
|
|
|
|
// nospill contains registers that we can't spill because
|
|
// we already set them up for use by the current instruction.
|
|
var nospill regMask
|
|
nospill |= 0x100000010 // SP & SB can't be spilled (TODO: arch-specific)
|
|
|
|
// Move inputs into registers
|
|
for _, o := range order {
|
|
w := v.Args[o.val]
|
|
mask := inputs[o.val]
|
|
if mask == 0 {
|
|
// Input doesn't need a register
|
|
continue
|
|
}
|
|
// TODO: 2-address overwrite instructions
|
|
|
|
// Find registers that w is already in
|
|
var wreg regMask
|
|
for r := register(0); r < numRegs; r++ {
|
|
if regs[r].v == w {
|
|
wreg |= regMask(1) << r
|
|
}
|
|
}
|
|
|
|
var r register
|
|
if mask&wreg != 0 {
|
|
// w is already in an allowed register. We're done.
|
|
r = pickReg(mask & wreg)
|
|
} else {
|
|
// Pick a register for w
|
|
// Priorities (in order)
|
|
// - an unused register
|
|
// - a clean register
|
|
// - a dirty register
|
|
// TODO: for used registers, pick the one whose next use is the
|
|
// farthest in the future.
|
|
mask &^= nospill
|
|
if mask & ^dirty != 0 {
|
|
mask &^= dirty
|
|
}
|
|
if mask & ^used != 0 {
|
|
mask &^= used
|
|
}
|
|
r = pickReg(mask)
|
|
|
|
// Kick out whomever is using this register.
|
|
if regs[r].v != nil {
|
|
x := regs[r].v
|
|
c := regs[r].c
|
|
if regs[r].dirty && lastUse[x.ID] > idx {
|
|
// Write x back to home. Its value is currently held in c.
|
|
x.Op = OpStoreReg8
|
|
x.Aux = nil
|
|
x.resetArgs()
|
|
x.AddArg(c)
|
|
b.Values = append(b.Values, x)
|
|
regs[r].dirty = false
|
|
dirty &^= regMask(1) << r
|
|
}
|
|
regs[r].v = nil
|
|
regs[r].c = nil
|
|
used &^= regMask(1) << r
|
|
}
|
|
|
|
// Load w into this register
|
|
var c *Value
|
|
if len(w.Args) == 0 {
|
|
// Materialize w
|
|
if w.Op == OpSB {
|
|
c = w
|
|
} else if w.Op == OpSP {
|
|
c = b.NewValue1(w.Line, OpCopy, w.Type, w)
|
|
} else {
|
|
c = b.NewValue0IA(w.Line, w.Op, w.Type, w.AuxInt, w.Aux)
|
|
}
|
|
} else if len(w.Args) == 1 && (w.Args[0].Op == OpSP || w.Args[0].Op == OpSB) {
|
|
// Materialize offsets from SP/SB
|
|
c = b.NewValue1IA(w.Line, w.Op, w.Type, w.AuxInt, w.Aux, w.Args[0])
|
|
} else if wreg != 0 {
|
|
// Copy from another register.
|
|
// Typically just an optimization, but this is
|
|
// required if w is dirty.
|
|
s := pickReg(wreg)
|
|
// inv: s != r
|
|
c = b.NewValue1(w.Line, OpCopy, w.Type, regs[s].c)
|
|
} else {
|
|
// Load from home location
|
|
c = b.NewValue1(w.Line, OpLoadReg8, w.Type, w)
|
|
}
|
|
home = setloc(home, c, ®isters[r])
|
|
// Remember what we did
|
|
regs[r].v = w
|
|
regs[r].c = c
|
|
regs[r].dirty = false
|
|
used |= regMask(1) << r
|
|
}
|
|
|
|
// Replace w with its in-register copy.
|
|
v.SetArg(o.val, regs[r].c)
|
|
|
|
// Remember not to undo this register assignment until after
|
|
// the instruction is issued.
|
|
nospill |= regMask(1) << r
|
|
}
|
|
|
|
// TODO: do any clobbering
|
|
|
|
// pick a register for v itself.
|
|
if len(outputs) > 1 {
|
|
panic("can't do multi-output yet")
|
|
}
|
|
if len(outputs) == 0 || outputs[0] == 0 {
|
|
// output doesn't need a register
|
|
b.Values = append(b.Values, v)
|
|
} else {
|
|
mask := outputs[0]
|
|
if mask & ^dirty != 0 {
|
|
mask &^= dirty
|
|
}
|
|
if mask & ^used != 0 {
|
|
mask &^= used
|
|
}
|
|
r := pickReg(mask)
|
|
|
|
// Kick out whomever is using this register.
|
|
if regs[r].v != nil {
|
|
x := regs[r].v
|
|
c := regs[r].c
|
|
if regs[r].dirty && lastUse[x.ID] > idx {
|
|
// Write x back to home. Its value is currently held in c.
|
|
x.Op = OpStoreReg8
|
|
x.Aux = nil
|
|
x.resetArgs()
|
|
x.AddArg(c)
|
|
b.Values = append(b.Values, x)
|
|
regs[r].dirty = false
|
|
dirty &^= regMask(1) << r
|
|
}
|
|
regs[r].v = nil
|
|
regs[r].c = nil
|
|
used &^= regMask(1) << r
|
|
}
|
|
|
|
// Reissue v with new op, with r as its home.
|
|
c := b.NewValue0IA(v.Line, v.Op, v.Type, v.AuxInt, v.Aux)
|
|
c.AddArgs(v.Args...)
|
|
home = setloc(home, c, ®isters[r])
|
|
|
|
// Remember what we did
|
|
regs[r].v = v
|
|
regs[r].c = c
|
|
regs[r].dirty = true
|
|
used |= regMask(1) << r
|
|
dirty |= regMask(1) << r
|
|
}
|
|
}
|
|
|
|
// If the block ends in a call, we must put the call after the spill code.
|
|
var call *Value
|
|
if b.Kind == BlockCall {
|
|
call = b.Control
|
|
if call != b.Values[len(b.Values)-1] {
|
|
b.Fatalf("call not at end of block %b %v", b, call)
|
|
}
|
|
b.Values = b.Values[:len(b.Values)-1]
|
|
// TODO: do this for all control types?
|
|
}
|
|
|
|
// at the end of the block, spill any remaining dirty, live values
|
|
for r := register(0); r < numRegs; r++ {
|
|
if !regs[r].dirty {
|
|
continue
|
|
}
|
|
v := regs[r].v
|
|
c := regs[r].c
|
|
if lastUse[v.ID] <= len(oldSched) {
|
|
if v == v.Block.Control {
|
|
// link control value to register version
|
|
v.Block.Control = c
|
|
}
|
|
continue // not live after block
|
|
}
|
|
|
|
// change v to be a copy of c
|
|
v.Op = OpStoreReg8
|
|
v.Aux = nil
|
|
v.resetArgs()
|
|
v.AddArg(c)
|
|
b.Values = append(b.Values, v)
|
|
}
|
|
|
|
// add call back after spills
|
|
if b.Kind == BlockCall {
|
|
b.Values = append(b.Values, call)
|
|
}
|
|
}
|
|
f.RegAlloc = home
|
|
deadcode(f) // remove values that had all of their uses rematerialized. TODO: separate pass?
|
|
}
|
|
|
|
// addPhiCopies adds copies of phi inputs in the blocks
|
|
// immediately preceding the phi's block.
|
|
func addPhiCopies(f *Func) {
|
|
for _, b := range f.Blocks {
|
|
for _, v := range b.Values {
|
|
if v.Op != OpPhi {
|
|
break // all phis should appear first
|
|
}
|
|
if v.Type.IsMemory() { // TODO: only "regallocable" types
|
|
continue
|
|
}
|
|
for i, w := range v.Args {
|
|
c := b.Preds[i]
|
|
cpy := c.NewValue1(w.Line, OpCopy, v.Type, w)
|
|
v.Args[i] = cpy
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// live returns a map from block ID to a list of value IDs live at the end of that block
|
|
// TODO: this could be quadratic if lots of variables are live across lots of
|
|
// basic blocks. Figure out a way to make this function (or, more precisely, the user
|
|
// of this function) require only linear size & time.
|
|
func live(f *Func) [][]ID {
|
|
live := make([][]ID, f.NumBlocks())
|
|
var phis []*Value
|
|
|
|
s := newSparseSet(f.NumValues())
|
|
t := newSparseSet(f.NumValues())
|
|
for {
|
|
for _, b := range f.Blocks {
|
|
f.Logf("live %s %v\n", b, live[b.ID])
|
|
}
|
|
changed := false
|
|
|
|
for _, b := range f.Blocks {
|
|
// Start with known live values at the end of the block
|
|
s.clear()
|
|
s.addAll(live[b.ID])
|
|
|
|
// Propagate backwards to the start of the block
|
|
// Assumes Values have been scheduled.
|
|
phis := phis[:0]
|
|
for i := len(b.Values) - 1; i >= 0; i-- {
|
|
v := b.Values[i]
|
|
s.remove(v.ID)
|
|
if v.Op == OpPhi {
|
|
// save phi ops for later
|
|
phis = append(phis, v)
|
|
continue
|
|
}
|
|
s.addAllValues(v.Args)
|
|
}
|
|
|
|
// for each predecessor of b, expand its list of live-at-end values
|
|
// inv: s contains the values live at the start of b (excluding phi inputs)
|
|
for i, p := range b.Preds {
|
|
t.clear()
|
|
t.addAll(live[p.ID])
|
|
t.addAll(s.contents())
|
|
for _, v := range phis {
|
|
t.add(v.Args[i].ID)
|
|
}
|
|
if t.size() == len(live[p.ID]) {
|
|
continue
|
|
}
|
|
// grow p's live set
|
|
c := make([]ID, t.size())
|
|
copy(c, t.contents())
|
|
live[p.ID] = c
|
|
changed = true
|
|
}
|
|
}
|
|
|
|
if !changed {
|
|
break
|
|
}
|
|
}
|
|
return live
|
|
}
|
|
|
|
// for sorting a pair of integers by key
|
|
type intPair struct {
|
|
key, val int
|
|
}
|
|
type byKey []intPair
|
|
|
|
func (a byKey) Len() int { return len(a) }
|
|
func (a byKey) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
|
func (a byKey) Less(i, j int) bool { return a[i].key < a[j].key }
|