mirror of
https://github.com/golang/go
synced 2024-10-05 18:21: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:
parent
759b9c3b80
commit
0b46b42943
@ -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())
|
||||
}
|
||||
|
57
src/cmd/compile/internal/gc/testdata/regalloc_ssa.go
vendored
Normal file
57
src/cmd/compile/internal/gc/testdata/regalloc_ssa.go
vendored
Normal 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")
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -79,6 +79,7 @@ func init() {
|
||||
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
|
||||
|
||||
|
@ -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 }
|
||||
|
@ -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()
|
||||
}
|
||||
|
||||
|
@ -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
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user