1
0
mirror of https://github.com/golang/go synced 2024-10-05 16:41:21 -06:00

[dev.ssa] cmd/compile/internal/ssa: New register allocator

Implement a global (whole function) register allocator.
This replaces the local (per basic block) register allocator.

Clobbering of registers by instructions is handled properly.
A separate change will add the correct clobbers to all the instructions.

Change-Id: I38ce4dc7dccb8303c1c0e0295fe70247b0a3f2ea
Reviewed-on: https://go-review.googlesource.com/13622
Reviewed-by: Josh Bleecher Snyder <josharian@gmail.com>
Reviewed-by: Todd Neal <todd@tneal.org>
This commit is contained in:
Keith Randall 2015-08-11 12:51:33 -07:00
parent 759b9c3b80
commit 0b46b42943
12 changed files with 1447 additions and 750 deletions

View File

@ -2277,7 +2277,10 @@ func genValue(v *ssa.Value) {
p.To.Reg = x86.REG_SP
p.To.Offset = localOffset(v)
case ssa.OpPhi:
// just check to make sure regalloc did it right
// just check to make sure regalloc and stackalloc did it right
if v.Type.IsMemory() {
return
}
f := v.Block.Func
loc := f.RegAlloc[v.ID]
for _, a := range v.Args {
@ -2376,13 +2379,16 @@ func genValue(v *ssa.Value) {
case ssa.OpAMD64InvertFlags:
v.Fatalf("InvertFlags should never make it to codegen %v", v)
case ssa.OpAMD64REPSTOSQ:
p := Prog(x86.AXORL) // TODO: lift out zeroing into its own instruction?
p.From.Type = obj.TYPE_REG
p.From.Reg = x86.REG_AX
p.To.Type = obj.TYPE_REG
p.To.Reg = x86.REG_AX
Prog(x86.AREP)
Prog(x86.ASTOSQ)
v.Unimplementedf("REPSTOSQ clobbers not implemented: %s", v.LongString())
case ssa.OpAMD64REPMOVSB:
Prog(x86.AREP)
Prog(x86.AMOVSB)
v.Unimplementedf("REPMOVSB clobbers not implemented: %s", v.LongString())
default:
v.Unimplementedf("genValue not implemented: %s", v.LongString())
}

View File

@ -0,0 +1,57 @@
// run
// 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.
// Tests phi implementation
package main
func phiOverwrite_ssa() int {
var n int
for i := 0; i < 10; i++ {
if i == 6 {
break
}
n = i
}
return n
}
func phiOverwrite() {
want := 5
got := phiOverwrite_ssa()
if got != want {
println("phiOverwrite_ssa()=", want, ", got", got)
failed = true
}
}
func phiOverwriteBig_ssa() int {
var a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z int
a = 1
for idx := 0; idx < 26; idx++ {
a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z = b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z, a
}
return a*1 + b*2 + c*3 + d*4 + e*5 + f*6 + g*7 + h*8 + i*9 + j*10 + k*11 + l*12 + m*13 + n*14 + o*15 + p*16 + q*17 + r*18 + s*19 + t*20 + u*21 + v*22 + w*23 + x*24 + y*25 + z*26
}
func phiOverwriteBig() {
want := 1
got := phiOverwriteBig_ssa()
if got != want {
println("phiOverwriteBig_ssa()=", want, ", got", got)
failed = true
}
}
var failed = false
func main() {
phiOverwrite()
phiOverwriteBig()
if failed {
panic("failed")
}
}

View File

@ -59,6 +59,14 @@ func findlive(f *Func) (reachable []bool, live []bool) {
// deadcode removes dead code from f.
func deadcode(f *Func) {
// deadcode after regalloc is forbidden for now. Regalloc
// doesn't quite generate legal SSA which will lead to some
// required moves being eliminated. See the comment at the
// top of regalloc.go for details.
if f.RegAlloc != nil {
f.Fatalf("deadcode after regalloc")
}
reachable, live := findlive(f)
// Remove dead values from blocks' value list. Return dead

View File

@ -72,13 +72,14 @@ func init() {
// Common individual register masks
var (
cx = buildReg("CX")
x15 = buildReg("X15")
gp = buildReg("AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15")
fp = buildReg("X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15")
gpsp = gp | buildReg("SP")
gpspsb = gpsp | buildReg("SB")
flags = buildReg("FLAGS")
cx = buildReg("CX")
x15 = buildReg("X15")
gp = buildReg("AX CX DX BX BP SI DI R8 R9 R10 R11 R12 R13 R14 R15")
fp = buildReg("X0 X1 X2 X3 X4 X5 X6 X7 X8 X9 X10 X11 X12 X13 X14 X15")
gpsp = gp | buildReg("SP")
gpspsb = gpsp | buildReg("SB")
flags = buildReg("FLAGS")
callerSave = gp | fp | flags
)
// Common slices of register masks
@ -90,16 +91,16 @@ func init() {
// Common regInfo
var (
gp01 = regInfo{inputs: []regMask{}, outputs: gponly}
gp11 = regInfo{inputs: []regMask{gpsp}, outputs: gponly}
gp11sb = regInfo{inputs: []regMask{gpspsb}, outputs: gponly}
gp21 = regInfo{inputs: []regMask{gpsp, gpsp}, outputs: gponly}
gp21sb = regInfo{inputs: []regMask{gpspsb, gpsp}, outputs: gponly}
gp21shift = regInfo{inputs: []regMask{gpsp, cx}, outputs: []regMask{gp &^ cx}}
gp01 = regInfo{inputs: []regMask{}, outputs: gponly, clobbers: flags}
gp11 = regInfo{inputs: []regMask{gpsp}, outputs: gponly, clobbers: flags}
gp11sb = regInfo{inputs: []regMask{gpspsb}, outputs: gponly, clobbers: flags}
gp21 = regInfo{inputs: []regMask{gpsp, gpsp}, outputs: gponly, clobbers: flags}
gp21sb = regInfo{inputs: []regMask{gpspsb, gpsp}, outputs: gponly, clobbers: flags}
gp21shift = regInfo{inputs: []regMask{gpsp, cx}, outputs: []regMask{gp &^ cx}, clobbers: flags}
gp2flags = regInfo{inputs: []regMask{gpsp, gpsp}, outputs: flagsonly}
gp1flags = regInfo{inputs: []regMask{gpsp}, outputs: flagsonly}
flagsgp = regInfo{inputs: flagsonly, outputs: gponly}
flagsgp = regInfo{inputs: flagsonly, outputs: gponly, clobbers: flags}
gpload = regInfo{inputs: []regMask{gpspsb, 0}, outputs: gponly}
gploadidx = regInfo{inputs: []regMask{gpspsb, gpsp, 0}, outputs: gponly}
@ -122,6 +123,7 @@ func init() {
fpstore = regInfo{inputs: []regMask{gpspsb, fp, 0}}
fpstoreidx = regInfo{inputs: []regMask{gpspsb, gpsp, fp, 0}}
)
// TODO: most ops clobber flags
// Suffixes encode the bit width of various instructions.
// Q = 64 bit, L = 32 bit, W = 16 bit, B = 8 bit
@ -318,8 +320,8 @@ func init() {
{name: "REPSTOSQ", reg: regInfo{[]regMask{buildReg("DI"), buildReg("CX")}, buildReg("DI AX CX"), nil}}, // store arg1 8-byte words containing zero into arg0 using STOSQ. arg2=mem.
//TODO: set register clobber to everything?
{name: "CALLstatic"}, // call static function aux.(*gc.Sym). arg0=mem, returns mem
{name: "CALLclosure", reg: regInfo{[]regMask{gpsp, buildReg("DX"), 0}, 0, nil}}, // call function via closure. arg0=codeptr, arg1=closure, arg2=mem returns mem
{name: "CALLstatic", reg: regInfo{clobbers: callerSave}}, // call static function aux.(*gc.Sym). arg0=mem, returns mem
{name: "CALLclosure", reg: regInfo{[]regMask{gpsp, buildReg("DX"), 0}, callerSave, nil}}, // call function via closure. arg0=codeptr, arg1=closure, arg2=mem returns mem
{name: "REPMOVSB", reg: regInfo{[]regMask{buildReg("DI"), buildReg("SI"), buildReg("CX")}, buildReg("DI SI CX"), nil}}, // move arg2 bytes from arg1 to arg0. arg3=mem, returns memory

View File

@ -15,6 +15,7 @@ import (
"io/ioutil"
"log"
"regexp"
"sort"
)
type arch struct {
@ -125,11 +126,22 @@ func genOp() {
fmt.Fprintf(w, "asm: x86.A%s,\n", v.asm)
}
fmt.Fprintln(w, "reg:regInfo{")
// reg inputs
if len(v.reg.inputs) > 0 {
fmt.Fprintln(w, "inputs: []regMask{")
for _, r := range v.reg.inputs {
fmt.Fprintf(w, "%d,%s\n", r, a.regMaskComment(r))
// Compute input allocation order. We allocate from the
// most to the least constrained input. This order guarantees
// that we will always be able to find a register.
var s []intPair
for i, r := range v.reg.inputs {
if r != 0 {
s = append(s, intPair{countRegs(r), i})
}
}
if len(s) > 0 {
sort.Sort(byKey(s))
fmt.Fprintln(w, "inputs: []inputInfo{")
for _, p := range s {
r := v.reg.inputs[p.val]
fmt.Fprintf(w, "{%d,%d},%s\n", p.val, r, a.regMaskComment(r))
}
fmt.Fprintln(w, "},")
}
@ -205,3 +217,23 @@ func genLower() {
genRules(a)
}
}
// 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
}
// 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 }

View File

@ -362,7 +362,7 @@ func (v *Value) LongHTML() string {
s += fmt.Sprintf(" %s", a.HTML())
}
r := v.Block.Func.RegAlloc
if r != nil && r[v.ID] != nil {
if int(v.ID) < len(r) && r[v.ID] != nil {
s += " : " + r[v.ID].Name()
}

View File

@ -19,8 +19,13 @@ type opInfo struct {
generic bool // this is a generic (arch-independent) opcode
}
type inputInfo struct {
idx int // index in Args array
regs regMask // allowed input registers
}
type regInfo struct {
inputs []regMask
inputs []inputInfo // ordered in register allocation order
clobbers regMask
outputs []regMask // NOTE: values can only have 1 output for now.
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -4,6 +4,15 @@
package ssa
// setloc sets the home location of v to loc.
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
}
// stackalloc allocates storage in the stack frame for
// all Values that did not get a register.
func stackalloc(f *Func) {
@ -26,7 +35,7 @@ func stackalloc(f *Func) {
// so stackmap is smaller.
// Assign stack locations to phis first, because we
// must also assign the same locations to the phi copies
// must also assign the same locations to the phi stores
// introduced during regalloc.
for _, b := range f.Blocks {
for _, v := range b.Values {
@ -36,12 +45,19 @@ func stackalloc(f *Func) {
if v.Type.IsMemory() { // TODO: only "regallocable" types
continue
}
if int(v.ID) < len(home) && home[v.ID] != nil {
continue // register-based phi
}
// stack-based phi
n = align(n, v.Type.Alignment())
f.Logf("stackalloc: %d-%d for %v\n", n, n+v.Type.Size(), v)
loc := &LocalSlot{n}
n += v.Type.Size()
home = setloc(home, v, loc)
for _, w := range v.Args {
if w.Op != OpStoreReg {
f.Fatalf("stack-based phi must have StoreReg args")
}
home = setloc(home, w, loc)
}
}

View File

@ -57,13 +57,6 @@ func tighten(f *Func) {
if v.Op == OpPhi {
continue
}
if v.Op == OpSB || v.Op == OpSP {
// regalloc expects OpSP and OpSB values to be in the entry block,
// so don't move them.
// TODO: Handle this more gracefully in regalloc and
// remove this restriction.
continue
}
if uses[v.ID] == 1 && !phi[v.ID] && home[v.ID] != b && len(v.Args) < 2 {
// v is used in exactly one block, and it is not b.
// Furthermore, it takes at most one input,

View File

@ -11,7 +11,7 @@ import "fmt"
// if they preserve the value of the Value (e.g. changing a (mul 2 x) to an (add x x)).
type Value struct {
// A unique identifier for the value. For performance we allocate these IDs
// densely starting at 0. There is no guarantee that there won't be occasional holes, though.
// densely starting at 1. There is no guarantee that there won't be occasional holes, though.
ID ID
// The operation that computes this value. See op.go.
@ -69,7 +69,7 @@ func (v *Value) LongString() string {
s += fmt.Sprintf(" %v", a)
}
r := v.Block.Func.RegAlloc
if r != nil && r[v.ID] != nil {
if int(v.ID) < len(r) && r[v.ID] != nil {
s += " : " + r[v.ID].Name()
}
return s