Commit 7b8f5118 authored by Matthew Dempsky's avatar Matthew Dempsky

cmd/compile/internal/gc: refactor liveness bitmap generation

Keep liveness bit vectors as simple live-variable vectors during
liveness analysis. We can defer expanding them into runtime heap
bitmaps until we're actually writing out the symbol data, and then we
only need temporary memory to expand one bitmap at a time.

This is logically cleaner (e.g., we no longer depend on stack frame
layout during analysis) and saves a little bit on allocations.

name       old alloc/op    new alloc/op    delta
Template      41.4MB ± 0%     41.3MB ± 0%  -0.28%        (p=0.000 n=60+60)
Unicode       32.6MB ± 0%     32.6MB ± 0%  -0.11%        (p=0.000 n=59+60)
GoTypes        119MB ± 0%      119MB ± 0%  -0.35%        (p=0.000 n=60+59)
Compiler       483MB ± 0%      481MB ± 0%  -0.47%        (p=0.000 n=59+60)

name       old allocs/op   new allocs/op   delta
Template        381k ± 1%       380k ± 1%  -0.32%        (p=0.000 n=60+60)
Unicode         325k ± 1%       325k ± 1%    ~           (p=0.867 n=60+60)
GoTypes        1.16M ± 0%      1.15M ± 0%  -0.40%        (p=0.000 n=60+59)
Compiler       4.22M ± 0%      4.19M ± 0%  -0.61%        (p=0.000 n=59+60)

Passes toolstash -cmp.

Change-Id: I8175efe55201ffb5017f79ae6cb90df03f1b7e99
Reviewed-on: https://go-review.googlesource.com/37458
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarJosh Bleecher Snyder <josharian@gmail.com>
parent 88f423ed
...@@ -90,10 +90,9 @@ type Liveness struct { ...@@ -90,10 +90,9 @@ type Liveness struct {
vars []*Node vars []*Node
cfg []*BasicBlock cfg []*BasicBlock
// An array with a bit vector for each safe point tracking live pointers // An array with a bit vector for each safe point tracking
// in the arguments and locals area, indexed by bb.rpo. // live variables, indexed by bb.rpo.
argslivepointers []bvec livevars []bvec
livepointers []bvec
cache progeffectscache cache progeffectscache
} }
...@@ -826,7 +825,7 @@ func livenessprintblock(lv *Liveness, bb *BasicBlock) { ...@@ -826,7 +825,7 @@ func livenessprintblock(lv *Liveness, bb *BasicBlock) {
fmt.Printf("\t\t%v", prog) fmt.Printf("\t\t%v", prog)
if prog.As == obj.APCDATA && prog.From.Offset == obj.PCDATA_StackMapIndex { if prog.As == obj.APCDATA && prog.From.Offset == obj.PCDATA_StackMapIndex {
pos := int32(prog.To.Offset) pos := int32(prog.To.Offset)
live := lv.livepointers[pos] live := lv.livevars[pos]
fmt.Printf(" %s", live.String()) fmt.Printf(" %s", live.String())
} }
...@@ -1110,7 +1109,6 @@ func livenesssolve(lv *Liveness) { ...@@ -1110,7 +1109,6 @@ func livenesssolve(lv *Liveness) {
// These temporary bitvectors exist to avoid successive allocations and // These temporary bitvectors exist to avoid successive allocations and
// frees within the loop. // frees within the loop.
newlivein := bvalloc(int32(len(lv.vars))) newlivein := bvalloc(int32(len(lv.vars)))
newliveout := bvalloc(int32(len(lv.vars))) newliveout := bvalloc(int32(len(lv.vars)))
any := bvalloc(int32(len(lv.vars))) any := bvalloc(int32(len(lv.vars)))
all := bvalloc(int32(len(lv.vars))) all := bvalloc(int32(len(lv.vars)))
...@@ -1191,34 +1189,11 @@ func livenesssolve(lv *Liveness) { ...@@ -1191,34 +1189,11 @@ func livenesssolve(lv *Liveness) {
// //
// in[b] = uevar[b] \cup (out[b] \setminus varkill[b]) // in[b] = uevar[b] \cup (out[b] \setminus varkill[b])
newlivein.AndNot(bb.liveout, bb.varkill) newlivein.AndNot(bb.liveout, bb.varkill)
bb.livein.Or(newlivein, bb.uevar) bb.livein.Or(newlivein, bb.uevar)
} }
} }
} }
// This function is slow but it is only used for generating debug prints.
// Check whether n is marked live in args/locals.
func islive(n *Node, args bvec, locals bvec) bool {
switch n.Class {
case PPARAM, PPARAMOUT:
for i := 0; int64(i) < n.Type.Width/int64(Widthptr); i++ {
if args.Get(int32(n.Xoffset/int64(Widthptr) + int64(i))) {
return true
}
}
case PAUTO:
for i := 0; int64(i) < n.Type.Width/int64(Widthptr); i++ {
if locals.Get(int32((n.Xoffset+stkptrsize)/int64(Widthptr) + int64(i))) {
return true
}
}
}
return false
}
// Visits all instructions in a basic block and computes a bit vector of live // Visits all instructions in a basic block and computes a bit vector of live
// variables at each safe point locations. // variables at each safe point locations.
func livenessepilogue(lv *Liveness) { func livenessepilogue(lv *Liveness) {
...@@ -1227,8 +1202,7 @@ func livenessepilogue(lv *Liveness) { ...@@ -1227,8 +1202,7 @@ func livenessepilogue(lv *Liveness) {
liveout := bvalloc(nvars) liveout := bvalloc(nvars)
any := bvalloc(nvars) any := bvalloc(nvars)
all := bvalloc(nvars) all := bvalloc(nvars)
outLive := bvalloc(argswords()) // always-live output params livedefer := bvalloc(nvars) // always-live variables
outLiveHeap := bvalloc(localswords()) // always-live pointers to heap-allocated copies of output params
// If there is a defer (that could recover), then all output // If there is a defer (that could recover), then all output
// parameters are live all the time. In addition, any locals // parameters are live all the time. In addition, any locals
...@@ -1238,7 +1212,7 @@ func livenessepilogue(lv *Liveness) { ...@@ -1238,7 +1212,7 @@ func livenessepilogue(lv *Liveness) {
// TODO: if the output parameter is heap-allocated, then we // TODO: if the output parameter is heap-allocated, then we
// don't need to keep the stack copy live? // don't need to keep the stack copy live?
if hasdefer { if hasdefer {
for _, n := range lv.vars { for i, n := range lv.vars {
if n.Class == PPARAMOUT { if n.Class == PPARAMOUT {
if n.IsOutputParamHeapAddr() { if n.IsOutputParamHeapAddr() {
// Just to be paranoid. // Just to be paranoid.
...@@ -1246,13 +1220,11 @@ func livenessepilogue(lv *Liveness) { ...@@ -1246,13 +1220,11 @@ func livenessepilogue(lv *Liveness) {
} }
// Needzero not necessary, as the compiler // Needzero not necessary, as the compiler
// explicitly zeroes output vars at start of fn. // explicitly zeroes output vars at start of fn.
xoffset := n.Xoffset livedefer.Set(int32(i))
onebitwalktype1(n.Type, &xoffset, outLive)
} }
if n.IsOutputParamHeapAddr() { if n.IsOutputParamHeapAddr() {
n.Name.Needzero = true n.Name.Needzero = true
xoffset := n.Xoffset + stkptrsize livedefer.Set(int32(i))
onebitwalktype1(n.Type, &xoffset, outLiveHeap)
} }
} }
} }
...@@ -1262,7 +1234,6 @@ func livenessepilogue(lv *Liveness) { ...@@ -1262,7 +1234,6 @@ func livenessepilogue(lv *Liveness) {
// This duplicates information known during livenesssolve // This duplicates information known during livenesssolve
// but avoids storing two more vectors for each block. // but avoids storing two more vectors for each block.
any.Clear() any.Clear()
all.Clear() all.Clear()
for j := 0; j < len(bb.pred); j++ { for j := 0; j < len(bb.pred); j++ {
pred := bb.pred[j] pred := bb.pred[j]
...@@ -1316,23 +1287,14 @@ func livenessepilogue(lv *Liveness) { ...@@ -1316,23 +1287,14 @@ func livenessepilogue(lv *Liveness) {
// value we are tracking. // value we are tracking.
// Live stuff first. // Live stuff first.
args := bvalloc(argswords()) live := bvalloc(nvars)
live.Copy(any)
lv.argslivepointers = append(lv.argslivepointers, args) lv.livevars = append(lv.livevars, live)
locals := bvalloc(localswords())
lv.livepointers = append(lv.livepointers, locals)
if debuglive >= 3 { if debuglive >= 3 {
fmt.Printf("%v\n", p) fmt.Printf("%v\n", p)
printvars("avarinitany", any, lv.vars) printvars("avarinitany", any, lv.vars)
} }
// Record any values with an "address taken" reaching
// this code position as live. Must do now instead of below
// because the any/all calculation requires walking forward
// over the block (as this loop does), while the liveout
// requires walking backward (as the next loop does).
onebitlivepointermap(lv, any, lv.vars, args, locals)
} }
if p == bb.last { if p == bb.last {
...@@ -1340,14 +1302,14 @@ func livenessepilogue(lv *Liveness) { ...@@ -1340,14 +1302,14 @@ func livenessepilogue(lv *Liveness) {
} }
} }
bb.lastbitmapindex = len(lv.livepointers) - 1 bb.lastbitmapindex = len(lv.livevars) - 1
} }
var msg []string var msg []string
var nmsg, startmsg int var nmsg, startmsg int
for _, bb := range lv.cfg { for _, bb := range lv.cfg {
if debuglive >= 1 && Curfn.Func.Nname.Sym.Name != "init" && Curfn.Func.Nname.Sym.Name[0] != '.' { if debuglive >= 1 && Curfn.Func.Nname.Sym.Name != "init" && Curfn.Func.Nname.Sym.Name[0] != '.' {
nmsg = len(lv.livepointers) nmsg = len(lv.livevars)
startmsg = nmsg startmsg = nmsg
msg = make([]string, nmsg) msg = make([]string, nmsg)
for j := 0; j < nmsg; j++ { for j := 0; j < nmsg; j++ {
...@@ -1406,22 +1368,16 @@ func livenessepilogue(lv *Liveness) { ...@@ -1406,22 +1368,16 @@ func livenessepilogue(lv *Liveness) {
} }
} }
// Record live pointers. // Record live variables.
args := lv.argslivepointers[pos] live := lv.livevars[pos]
live.Or(live, liveout)
locals := lv.livepointers[pos]
onebitlivepointermap(lv, liveout, lv.vars, args, locals)
// Mark pparamout variables (as described above) // Mark pparamout variables (as described above)
if p.As == obj.ACALL { if p.As == obj.ACALL {
args.Or(args, outLive) live.Or(live, livedefer)
locals.Or(locals, outLiveHeap)
} }
// Show live pointer bitmaps. // Show live variables.
// We're interpreting the args and locals bitmap instead of liveout so that we
// include the bits added by the avarinit logic in the
// previous loop.
if msg != nil { if msg != nil {
fmt_ := fmt.Sprintf("%v: live at ", p.Line()) fmt_ := fmt.Sprintf("%v: live at ", p.Line())
if p.As == obj.ACALL && p.To.Sym != nil { if p.As == obj.ACALL && p.To.Sym != nil {
...@@ -1437,9 +1393,8 @@ func livenessepilogue(lv *Liveness) { ...@@ -1437,9 +1393,8 @@ func livenessepilogue(lv *Liveness) {
fmt_ += fmt.Sprintf("entry to %s:", ((p.From.Node).(*Node)).Sym.Name) fmt_ += fmt.Sprintf("entry to %s:", ((p.From.Node).(*Node)).Sym.Name)
} }
numlive := 0 numlive := 0
for j := 0; j < len(lv.vars); j++ { for j, n := range lv.vars {
n := lv.vars[j] if live.Get(int32(j)) {
if islive(n, args, locals) {
fmt_ += fmt.Sprintf(" %v", n) fmt_ += fmt.Sprintf(" %v", n)
numlive++ numlive++
} }
...@@ -1457,6 +1412,7 @@ func livenessepilogue(lv *Liveness) { ...@@ -1457,6 +1412,7 @@ func livenessepilogue(lv *Liveness) {
// Only CALL instructions need a PCDATA annotation. // Only CALL instructions need a PCDATA annotation.
// The TEXT instruction annotation is implicit. // The TEXT instruction annotation is implicit.
if p.As == obj.ACALL { if p.As == obj.ACALL {
before := p
if isdeferreturn(p) { if isdeferreturn(p) {
// runtime.deferreturn modifies its return address to return // runtime.deferreturn modifies its return address to return
// back to the CALL, not to the subsequent instruction. // back to the CALL, not to the subsequent instruction.
...@@ -1464,17 +1420,15 @@ func livenessepilogue(lv *Liveness) { ...@@ -1464,17 +1420,15 @@ func livenessepilogue(lv *Liveness) {
// the PCDATA must begin one instruction early too. // the PCDATA must begin one instruction early too.
// The instruction before a call to deferreturn is always a // The instruction before a call to deferreturn is always a
// no-op, to keep PC-specific data unambiguous. // no-op, to keep PC-specific data unambiguous.
prev := p.Opt.(*obj.Prog) before = p.Opt.(*obj.Prog)
if Ctxt.Arch.Family == sys.PPC64 { if Ctxt.Arch.Family == sys.PPC64 {
// On ppc64 there is an additional instruction // On ppc64 there is an additional instruction
// (another no-op or reload of toc pointer) before // (another no-op or reload of toc pointer) before
// the call. // the call.
prev = prev.Opt.(*obj.Prog) before = before.Opt.(*obj.Prog)
} }
splicebefore(lv, bb, newpcdataprog(prev, pos), prev)
} else {
splicebefore(lv, bb, newpcdataprog(p, pos), p)
} }
splicebefore(lv, bb, newpcdataprog(before, pos), before)
} }
pos-- pos--
...@@ -1534,7 +1488,7 @@ func livenesscompact(lv *Liveness) { ...@@ -1534,7 +1488,7 @@ func livenesscompact(lv *Liveness) {
// Linear probing hash table of bitmaps seen so far. // Linear probing hash table of bitmaps seen so far.
// The hash table has 4n entries to keep the linear // The hash table has 4n entries to keep the linear
// scan short. An entry of -1 indicates an empty slot. // scan short. An entry of -1 indicates an empty slot.
n := len(lv.livepointers) n := len(lv.livevars)
tablesize := 4 * n tablesize := 4 * n
table := make([]int, tablesize) table := make([]int, tablesize)
...@@ -1544,7 +1498,6 @@ func livenesscompact(lv *Liveness) { ...@@ -1544,7 +1498,6 @@ func livenesscompact(lv *Liveness) {
// remap[i] = the new index of the old bit vector #i. // remap[i] = the new index of the old bit vector #i.
remap := make([]int, n) remap := make([]int, n)
for i := range remap { for i := range remap {
remap[i] = -1 remap[i] = -1
} }
...@@ -1552,24 +1505,22 @@ func livenesscompact(lv *Liveness) { ...@@ -1552,24 +1505,22 @@ func livenesscompact(lv *Liveness) {
// Consider bit vectors in turn. // Consider bit vectors in turn.
// If new, assign next number using uniq, // If new, assign next number using uniq,
// record in remap, record in lv.livepointers and lv.argslivepointers // record in remap, record in lv.livevars
// under the new index, and add entry to hash table. // under the new index, and add entry to hash table.
// If already seen, record earlier index in remap and free bitmaps. // If already seen, record earlier index in remap.
for i := 0; i < n; i++ { Outer:
local := lv.livepointers[i] for i, live := range lv.livevars {
arg := lv.argslivepointers[i] h := hashbitmap(H0, live) % uint32(tablesize)
h := hashbitmap(hashbitmap(H0, local), arg) % uint32(tablesize)
for { for {
j := table[h] j := table[h]
if j < 0 { if j < 0 {
break break
} }
jlocal := lv.livepointers[j] jlive := lv.livevars[j]
jarg := lv.argslivepointers[j] if live.Eq(jlive) {
if local.Eq(jlocal) && arg.Eq(jarg) {
remap[i] = j remap[i] = j
goto Next continue Outer
} }
h++ h++
...@@ -1580,21 +1531,17 @@ func livenesscompact(lv *Liveness) { ...@@ -1580,21 +1531,17 @@ func livenesscompact(lv *Liveness) {
table[h] = uniq table[h] = uniq
remap[i] = uniq remap[i] = uniq
lv.livepointers[uniq] = local lv.livevars[uniq] = live
lv.argslivepointers[uniq] = arg
uniq++ uniq++
Next:
} }
// We've already reordered lv.livepointers[0:uniq] // We've already reordered lv.livevars[0:uniq]. Clear the
// and lv.argslivepointers[0:uniq] and freed the bitmaps // pointers later in the array so they can be GC'd.
// we don't need anymore. Clear the pointers later in the tail := lv.livevars[uniq:]
// array so that we can tell where the coalesced bitmaps stop for i := range tail { // memclr loop pattern
// and so that we don't double-free when cleaning up. tail[i] = bvec{}
for j := uniq; j < n; j++ {
lv.livepointers[j] = bvec{}
lv.argslivepointers[j] = bvec{}
} }
lv.livevars = lv.livevars[:uniq]
// Rewrite PCDATA instructions to use new numbering. // Rewrite PCDATA instructions to use new numbering.
for p := lv.ptxt; p != nil; p = p.Link { for p := lv.ptxt; p != nil; p = p.Link {
...@@ -1688,13 +1635,11 @@ func livenessprintdebug(lv *Liveness) { ...@@ -1688,13 +1635,11 @@ func livenessprintdebug(lv *Liveness) {
fmt.Printf("\n") fmt.Printf("\n")
} }
if issafepoint(p) { if issafepoint(p) {
args := lv.argslivepointers[pcdata] live := lv.livevars[pcdata]
locals := lv.livepointers[pcdata]
fmt.Printf("\tlive=") fmt.Printf("\tlive=")
printed = false printed = false
for j := 0; j < len(lv.vars); j++ { for j, n := range lv.vars {
n := lv.vars[j] if live.Get(int32(j)) {
if islive(n, args, locals) {
if printed { if printed {
fmt.Printf(",") fmt.Printf(",")
} }
...@@ -1726,25 +1671,7 @@ func livenessprintdebug(lv *Liveness) { ...@@ -1726,25 +1671,7 @@ func livenessprintdebug(lv *Liveness) {
fmt.Printf("\n") fmt.Printf("\n")
} }
// Dumps a slice of bitmaps to a symbol as a sequence of uint32 values. The func finishgclocals(sym *Sym) {
// first word dumped is the total number of bitmaps. The second word is the
// length of the bitmaps. All bitmaps are assumed to be of equal length. The
// remaining bytes are the raw bitmaps.
func onebitwritesymbol(arr []bvec, sym *Sym) {
off := 4 // number of bitmaps, to fill in later
off = duint32(sym, off, uint32(arr[0].n)) // number of bits in each bitmap
var i int
for i = 0; i < len(arr); i++ {
// bitmap words
bv := arr[i]
if bv.b == nil {
break
}
off = dbvec(sym, off, bv)
}
duint32(sym, 0, uint32(i)) // number of bitmaps
ls := Linksym(sym) ls := Linksym(sym)
ls.Name = fmt.Sprintf("gclocals·%x", md5.Sum(ls.P)) ls.Name = fmt.Sprintf("gclocals·%x", md5.Sum(ls.P))
ls.Set(obj.AttrDuplicateOK, true) ls.Set(obj.AttrDuplicateOK, true)
...@@ -1754,10 +1681,37 @@ func onebitwritesymbol(arr []bvec, sym *Sym) { ...@@ -1754,10 +1681,37 @@ func onebitwritesymbol(arr []bvec, sym *Sym) {
sym.Lsym = ls2 sym.Lsym = ls2
} else { } else {
Ctxt.Hash[sv] = ls Ctxt.Hash[sv] = ls
ggloblsym(sym, int32(off), obj.RODATA) ggloblsym(sym, int32(ls.Size), obj.RODATA)
} }
} }
// Dumps a slice of bitmaps to a symbol as a sequence of uint32 values. The
// first word dumped is the total number of bitmaps. The second word is the
// length of the bitmaps. All bitmaps are assumed to be of equal length. The
// remaining bytes are the raw bitmaps.
func livenessemit(lv *Liveness, argssym, livesym *Sym) {
args := bvalloc(argswords())
aoff := duint32(argssym, 0, uint32(len(lv.livevars))) // number of bitmaps
aoff = duint32(argssym, aoff, uint32(args.n)) // number of bits in each bitmap
locals := bvalloc(localswords())
loff := duint32(livesym, 0, uint32(len(lv.livevars))) // number of bitmaps
loff = duint32(livesym, loff, uint32(locals.n)) // number of bits in each bitmap
for _, live := range lv.livevars {
args.Clear()
locals.Clear()
onebitlivepointermap(lv, live, lv.vars, args, locals)
aoff = dbvec(argssym, aoff, args)
loff = dbvec(livesym, loff, locals)
}
finishgclocals(livesym)
finishgclocals(argssym)
}
func printprog(p *obj.Prog) { func printprog(p *obj.Prog) {
for p != nil { for p != nil {
fmt.Printf("%v\n", p) fmt.Printf("%v\n", p)
...@@ -1814,9 +1768,7 @@ func liveness(fn *Node, firstp *obj.Prog, argssym *Sym, livesym *Sym) { ...@@ -1814,9 +1768,7 @@ func liveness(fn *Node, firstp *obj.Prog, argssym *Sym, livesym *Sym) {
} }
// Emit the live pointer map data structures // Emit the live pointer map data structures
onebitwritesymbol(lv.livepointers, livesym) livenessemit(lv, argssym, livesym)
onebitwritesymbol(lv.argslivepointers, argssym)
// Free everything. // Free everything.
for _, ln := range fn.Func.Dcl { for _, ln := range fn.Func.Dcl {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment