mirror of https://github.com/golang/go.git
cmd/compile: include liveness info in GOSSAFUNC output
For this function
```
func test(a, b int, c string, s []int, r [3]int, f ifn) {
in(a)
in(b)
sl(s)
ar(r)
fu(f)
}
```
this output
```
HASH live at entry to test: f s
HASH /Users/drchase/work/src/live/main.go
00000 (15) TEXT main.test(SB), ABIInternal
00001 (15) FUNCDATA $0, gclocals·vYpXgR4/KsH5nhFsqkHG1Q==(SB)
00002 (15) FUNCDATA $1, gclocals·Soq6RzO4SX8YA1O9euewoQ==(SB)
00003 (15) FUNCDATA $5, main.test.arginfo1(SB)
00004 (15) FUNCDATA $6, main.test.argliveinfo(SB)
b1 00005 (15) PCDATA $3, $1
v32 00006 (21) MOVD R6, main.s+72(RSP)
v27 00007 (21) MOVD R5, main.s+64(RSP)
v30 00008 (21) MOVD R4, main.s+56(RSP)
v7 00009 (21) MOVD R1, main.b+32(RSP)
v34 00010 (21) MOVD R7, main.f+80(RSP)
v34 00011 (21) PCDATA $3, $2
v15 00012 (+16) PCDATA $1, $0
HASH live at call to in: f s
v15 00013 (+16) CALL main.in(SB)
v3 00014 (+17) MOVD main.b+32(RSP), R0
HASH live at call to in: f s
v17 00015 (+17) CALL main.in(SB)
v8 00016 (+18) MOVD main.s+56(RSP), R0
v21 00017 (18) MOVD main.s+64(RSP), R1
v33 00018 (18) MOVD main.s+72(RSP), R2
v19 00019 (+18) PCDATA $1, $1
HASH live at call to sl: f
v19 00020 (+18) CALL main.sl(SB)
v29 00021 (+19) LDP main.r(RSP), (R1, R2)
v9 00022 (19) STP (R1, R2), 8(RSP)
v12 00023 (19) MOVD main.r+16(RSP), R1
v31 00024 (19) MOVD R1, 24(RSP)
HASH live at call to ar: f
v22 00025 (+19) CALL main.ar(SB)
v35 00026 (+20) MOVD main.f+80(RSP), R0
v24 00027 (+20) PCDATA $1, $2
HASH live at call to fu:
v24 00028 (+20) CALL main.fu(SB)
b1 00029 (21) RET
00030 (?) END
```
Where "HASH" is the git commit comment character I don't know how
to escape and this was easier than fighting with git.
Change-Id: I0691a3f7988db111d11d69388ace83641a841e57
Reviewed-on: https://go-review.googlesource.com/c/go/+/641360
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
This commit is contained in:
parent
46fd6b4e37
commit
7472b4c324
|
|
@ -56,7 +56,7 @@ type candRegion struct {
|
|||
type cstate struct {
|
||||
fn *ir.Func
|
||||
f *ssa.Func
|
||||
lv *liveness
|
||||
lv *Liveness
|
||||
cands []*ir.Name
|
||||
nameToSlot map[*ir.Name]int32
|
||||
regions []candRegion
|
||||
|
|
|
|||
|
|
@ -102,8 +102,8 @@ type blockEffects struct {
|
|||
liveout bitvec.BitVec
|
||||
}
|
||||
|
||||
// A collection of global state used by liveness analysis.
|
||||
type liveness struct {
|
||||
// A collection of global state used by Liveness analysis.
|
||||
type Liveness struct {
|
||||
fn *ir.Func
|
||||
f *ssa.Func
|
||||
vars []*ir.Name
|
||||
|
|
@ -235,7 +235,7 @@ func getvariables(fn *ir.Func) ([]*ir.Name, map[*ir.Name]int32) {
|
|||
return vars, idx
|
||||
}
|
||||
|
||||
func (lv *liveness) initcache() {
|
||||
func (lv *Liveness) initcache() {
|
||||
if lv.cache.initialized {
|
||||
base.Fatalf("liveness cache initialized twice")
|
||||
return
|
||||
|
|
@ -281,7 +281,7 @@ const (
|
|||
// valueEffects returns the index of a variable in lv.vars and the
|
||||
// liveness effects v has on that variable.
|
||||
// If v does not affect any tracked variables, it returns -1, 0.
|
||||
func (lv *liveness) valueEffects(v *ssa.Value) (int32, liveEffect) {
|
||||
func (lv *Liveness) valueEffects(v *ssa.Value) (int32, liveEffect) {
|
||||
n, e := affectedVar(v)
|
||||
if e == 0 || n == nil { // cheapest checks first
|
||||
return -1, 0
|
||||
|
|
@ -392,8 +392,8 @@ type livenessFuncCache struct {
|
|||
// Constructs a new liveness structure used to hold the global state of the
|
||||
// liveness computation. The cfg argument is a slice of *BasicBlocks and the
|
||||
// vars argument is a slice of *Nodes.
|
||||
func newliveness(fn *ir.Func, f *ssa.Func, vars []*ir.Name, idx map[*ir.Name]int32, stkptrsize int64) *liveness {
|
||||
lv := &liveness{
|
||||
func newliveness(fn *ir.Func, f *ssa.Func, vars []*ir.Name, idx map[*ir.Name]int32, stkptrsize int64) *Liveness {
|
||||
lv := &Liveness{
|
||||
fn: fn,
|
||||
f: f,
|
||||
vars: vars,
|
||||
|
|
@ -447,14 +447,14 @@ func newliveness(fn *ir.Func, f *ssa.Func, vars []*ir.Name, idx map[*ir.Name]int
|
|||
return lv
|
||||
}
|
||||
|
||||
func (lv *liveness) blockEffects(b *ssa.Block) *blockEffects {
|
||||
func (lv *Liveness) blockEffects(b *ssa.Block) *blockEffects {
|
||||
return &lv.be[b.ID]
|
||||
}
|
||||
|
||||
// Generates live pointer value maps for arguments and local variables. The
|
||||
// this argument and the in arguments are always assumed live. The vars
|
||||
// argument is a slice of *Nodes.
|
||||
func (lv *liveness) pointerMap(liveout bitvec.BitVec, vars []*ir.Name, args, locals bitvec.BitVec) {
|
||||
func (lv *Liveness) pointerMap(liveout bitvec.BitVec, vars []*ir.Name, args, locals bitvec.BitVec) {
|
||||
var slotsSeen map[int64]*ir.Name
|
||||
checkForDuplicateSlots := base.Debug.MergeLocals != 0
|
||||
if checkForDuplicateSlots {
|
||||
|
|
@ -504,7 +504,7 @@ func IsUnsafe(f *ssa.Func) bool {
|
|||
}
|
||||
|
||||
// markUnsafePoints finds unsafe points and computes lv.unsafePoints.
|
||||
func (lv *liveness) markUnsafePoints() {
|
||||
func (lv *Liveness) markUnsafePoints() {
|
||||
if IsUnsafe(lv.f) {
|
||||
// No complex analysis necessary.
|
||||
lv.allUnsafe = true
|
||||
|
|
@ -647,7 +647,7 @@ func (lv *liveness) markUnsafePoints() {
|
|||
// This does not necessarily mean the instruction is a safe-point. In
|
||||
// particular, call Values can have a stack map in case the callee
|
||||
// grows the stack, but not themselves be a safe-point.
|
||||
func (lv *liveness) hasStackMap(v *ssa.Value) bool {
|
||||
func (lv *Liveness) hasStackMap(v *ssa.Value) bool {
|
||||
if !v.Op.IsCall() {
|
||||
return false
|
||||
}
|
||||
|
|
@ -663,7 +663,7 @@ func (lv *liveness) hasStackMap(v *ssa.Value) bool {
|
|||
// Initializes the sets for solving the live variables. Visits all the
|
||||
// instructions in each basic block to summarizes the information at each basic
|
||||
// block
|
||||
func (lv *liveness) prologue() {
|
||||
func (lv *Liveness) prologue() {
|
||||
lv.initcache()
|
||||
|
||||
for _, b := range lv.f.Blocks {
|
||||
|
|
@ -685,7 +685,7 @@ func (lv *liveness) prologue() {
|
|||
}
|
||||
|
||||
// Solve the liveness dataflow equations.
|
||||
func (lv *liveness) solve() {
|
||||
func (lv *Liveness) solve() {
|
||||
// These temporary bitvectors exist to avoid successive allocations and
|
||||
// frees within the loop.
|
||||
nvars := int32(len(lv.vars))
|
||||
|
|
@ -745,7 +745,7 @@ func (lv *liveness) solve() {
|
|||
|
||||
// Visits all instructions in a basic block and computes a bit vector of live
|
||||
// variables at each safe point locations.
|
||||
func (lv *liveness) epilogue() {
|
||||
func (lv *Liveness) epilogue() {
|
||||
nvars := int32(len(lv.vars))
|
||||
liveout := bitvec.New(nvars)
|
||||
livedefer := bitvec.New(nvars) // always-live variables
|
||||
|
|
@ -914,7 +914,7 @@ func (lv *liveness) epilogue() {
|
|||
// is actually a net loss: we save about 50k of argument bitmaps but the new
|
||||
// PCDATA tables cost about 100k. So for now we keep using a single index for
|
||||
// both bitmap lists.
|
||||
func (lv *liveness) compact(b *ssa.Block) {
|
||||
func (lv *Liveness) compact(b *ssa.Block) {
|
||||
pos := 0
|
||||
if b == lv.f.Entry {
|
||||
// Handle entry stack map.
|
||||
|
|
@ -939,7 +939,7 @@ func (lv *liveness) compact(b *ssa.Block) {
|
|||
lv.livevars = lv.livevars[:0]
|
||||
}
|
||||
|
||||
func (lv *liveness) enableClobber() {
|
||||
func (lv *Liveness) enableClobber() {
|
||||
// The clobberdead experiment inserts code to clobber pointer slots in all
|
||||
// the dead variables (locals and args) at every synchronous safepoint.
|
||||
if !base.Flag.ClobberDead {
|
||||
|
|
@ -994,7 +994,7 @@ func (lv *liveness) enableClobber() {
|
|||
|
||||
// Inserts code to clobber pointer slots in all the dead variables (locals and args)
|
||||
// at every synchronous safepoint in b.
|
||||
func (lv *liveness) clobber(b *ssa.Block) {
|
||||
func (lv *Liveness) clobber(b *ssa.Block) {
|
||||
// Copy block's values to a temporary.
|
||||
oldSched := append([]*ssa.Value{}, b.Values...)
|
||||
b.Values = b.Values[:0]
|
||||
|
|
@ -1029,7 +1029,7 @@ func (lv *liveness) clobber(b *ssa.Block) {
|
|||
// clobber generates code to clobber pointer slots in all dead variables
|
||||
// (those not marked in live). Clobbering instructions are added to the end
|
||||
// of b.Values.
|
||||
func clobber(lv *liveness, b *ssa.Block, live bitvec.BitVec) {
|
||||
func clobber(lv *Liveness, b *ssa.Block, live bitvec.BitVec) {
|
||||
for i, n := range lv.vars {
|
||||
if !live.Get(int32(i)) && !n.Addrtaken() && !n.OpenDeferSlot() && !n.IsOutputParamHeapAddr() {
|
||||
// Don't clobber stack objects (address-taken). They are
|
||||
|
|
@ -1102,7 +1102,7 @@ func clobberPtr(b *ssa.Block, v *ir.Name, offset int64) {
|
|||
b.NewValue0IA(src.NoXPos, ssa.OpClobber, types.TypeVoid, offset, v)
|
||||
}
|
||||
|
||||
func (lv *liveness) showlive(v *ssa.Value, live bitvec.BitVec) {
|
||||
func (lv *Liveness) showlive(v *ssa.Value, live bitvec.BitVec) {
|
||||
if base.Flag.Live == 0 || ir.FuncName(lv.fn) == "init" || strings.HasPrefix(ir.FuncName(lv.fn), ".") {
|
||||
return
|
||||
}
|
||||
|
|
@ -1119,6 +1119,24 @@ func (lv *liveness) showlive(v *ssa.Value, live bitvec.BitVec) {
|
|||
return
|
||||
}
|
||||
|
||||
pos, s := lv.format(v, live)
|
||||
|
||||
base.WarnfAt(pos, "%s", s)
|
||||
}
|
||||
|
||||
func (lv *Liveness) Format(v *ssa.Value) string {
|
||||
if v == nil {
|
||||
_, s := lv.format(nil, lv.stackMaps[0])
|
||||
return s
|
||||
}
|
||||
if idx := lv.livenessMap.Get(v); idx.StackMapValid() {
|
||||
_, s := lv.format(v, lv.stackMaps[idx])
|
||||
return s
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (lv *Liveness) format(v *ssa.Value, live bitvec.BitVec) (src.XPos, string) {
|
||||
pos := lv.fn.Nname.Pos()
|
||||
if v != nil {
|
||||
pos = v.Pos
|
||||
|
|
@ -1149,11 +1167,10 @@ func (lv *liveness) showlive(v *ssa.Value, live bitvec.BitVec) {
|
|||
for _, v := range names {
|
||||
s += " " + v
|
||||
}
|
||||
|
||||
base.WarnfAt(pos, "%s", s)
|
||||
return pos, s
|
||||
}
|
||||
|
||||
func (lv *liveness) printbvec(printed bool, name string, live bitvec.BitVec) bool {
|
||||
func (lv *Liveness) printbvec(printed bool, name string, live bitvec.BitVec) bool {
|
||||
if live.IsEmpty() {
|
||||
return printed
|
||||
}
|
||||
|
|
@ -1177,7 +1194,7 @@ func (lv *liveness) printbvec(printed bool, name string, live bitvec.BitVec) boo
|
|||
}
|
||||
|
||||
// printeffect is like printbvec, but for valueEffects.
|
||||
func (lv *liveness) printeffect(printed bool, name string, pos int32, x bool) bool {
|
||||
func (lv *Liveness) printeffect(printed bool, name string, pos int32, x bool) bool {
|
||||
if !x {
|
||||
return printed
|
||||
}
|
||||
|
|
@ -1197,7 +1214,7 @@ func (lv *liveness) printeffect(printed bool, name string, pos int32, x bool) bo
|
|||
// Prints the computed liveness information and inputs, for debugging.
|
||||
// This format synthesizes the information used during the multiple passes
|
||||
// into a single presentation.
|
||||
func (lv *liveness) printDebug() {
|
||||
func (lv *Liveness) printDebug() {
|
||||
fmt.Printf("liveness: %s\n", ir.FuncName(lv.fn))
|
||||
|
||||
for i, b := range lv.f.Blocks {
|
||||
|
|
@ -1309,7 +1326,7 @@ func (lv *liveness) printDebug() {
|
|||
// 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 (lv *liveness) emit() (argsSym, liveSym *obj.LSym) {
|
||||
func (lv *Liveness) emit() (argsSym, liveSym *obj.LSym) {
|
||||
// Size args bitmaps to be just large enough to hold the largest pointer.
|
||||
// First, find the largest Xoffset node we care about.
|
||||
// (Nodes without pointers aren't in lv.vars; see ShouldTrack.)
|
||||
|
|
@ -1370,7 +1387,7 @@ func (lv *liveness) emit() (argsSym, liveSym *obj.LSym) {
|
|||
// structure read by the garbage collector.
|
||||
// Returns a map from GC safe points to their corresponding stack map index,
|
||||
// and a map that contains all input parameters that may be partially live.
|
||||
func Compute(curfn *ir.Func, f *ssa.Func, stkptrsize int64, pp *objw.Progs) (Map, map[*ir.Name]bool) {
|
||||
func Compute(curfn *ir.Func, f *ssa.Func, stkptrsize int64, pp *objw.Progs, retLiveness bool) (Map, map[*ir.Name]bool, *Liveness) {
|
||||
// Construct the global liveness state.
|
||||
vars, idx := getvariables(curfn)
|
||||
lv := newliveness(curfn, f, vars, idx, stkptrsize)
|
||||
|
|
@ -1432,10 +1449,15 @@ func Compute(curfn *ir.Func, f *ssa.Func, stkptrsize int64, pp *objw.Progs) (Map
|
|||
p.To.Sym = x
|
||||
}
|
||||
|
||||
return lv.livenessMap, lv.partLiveArgs
|
||||
retLv := lv
|
||||
if !retLiveness {
|
||||
retLv = nil
|
||||
}
|
||||
|
||||
return lv.livenessMap, lv.partLiveArgs, retLv
|
||||
}
|
||||
|
||||
func (lv *liveness) emitStackObjects() *obj.LSym {
|
||||
func (lv *Liveness) emitStackObjects() *obj.LSym {
|
||||
var vars []*ir.Name
|
||||
for _, n := range lv.fn.Dcl {
|
||||
if shouldTrack(n) && n.Addrtaken() && n.Esc() != ir.EscHeap {
|
||||
|
|
|
|||
|
|
@ -6335,7 +6335,10 @@ func genssa(f *ssa.Func, pp *objw.Progs) {
|
|||
|
||||
e := f.Frontend().(*ssafn)
|
||||
|
||||
s.livenessMap, s.partLiveArgs = liveness.Compute(e.curfn, f, e.stkptrsize, pp)
|
||||
gatherPrintInfo := f.PrintOrHtmlSSA || ssa.GenssaDump[f.Name]
|
||||
|
||||
var lv *liveness.Liveness
|
||||
s.livenessMap, s.partLiveArgs, lv = liveness.Compute(e.curfn, f, e.stkptrsize, pp, gatherPrintInfo)
|
||||
emitArgInfo(e, f, pp)
|
||||
argLiveBlockMap, argLiveValueMap := liveness.ArgLiveness(e.curfn, f, pp)
|
||||
|
||||
|
|
@ -6358,7 +6361,6 @@ func genssa(f *ssa.Func, pp *objw.Progs) {
|
|||
var progToValue map[*obj.Prog]*ssa.Value
|
||||
var progToBlock map[*obj.Prog]*ssa.Block
|
||||
var valueToProgAfter []*obj.Prog // The first Prog following computation of a value v; v is visible at this point.
|
||||
gatherPrintInfo := f.PrintOrHtmlSSA || ssa.GenssaDump[f.Name]
|
||||
if gatherPrintInfo {
|
||||
progToValue = make(map[*obj.Prog]*ssa.Value, f.NumValues())
|
||||
progToBlock = make(map[*obj.Prog]*ssa.Block, f.NumBlocks())
|
||||
|
|
@ -6766,6 +6768,14 @@ func genssa(f *ssa.Func, pp *objw.Progs) {
|
|||
buf.WriteString("<code>")
|
||||
buf.WriteString("<dl class=\"ssa-gen\">")
|
||||
filename := ""
|
||||
|
||||
liveness := lv.Format(nil)
|
||||
if liveness != "" {
|
||||
buf.WriteString("<dt class=\"ssa-prog-src\"></dt><dd class=\"ssa-prog\">")
|
||||
buf.WriteString(html.EscapeString("# " + liveness))
|
||||
buf.WriteString("</dd>")
|
||||
}
|
||||
|
||||
for p := s.pp.Text; p != nil; p = p.Link {
|
||||
// Don't spam every line with the file name, which is often huge.
|
||||
// Only print changes, and "unknown" is not a change.
|
||||
|
|
@ -6778,6 +6788,19 @@ func genssa(f *ssa.Func, pp *objw.Progs) {
|
|||
|
||||
buf.WriteString("<dt class=\"ssa-prog-src\">")
|
||||
if v, ok := progToValue[p]; ok {
|
||||
|
||||
// Prefix calls with their liveness, if any
|
||||
if p.As != obj.APCDATA {
|
||||
if liveness := lv.Format(v); liveness != "" {
|
||||
// Steal this line, and restart a line
|
||||
buf.WriteString("</dt><dd class=\"ssa-prog\">")
|
||||
buf.WriteString(html.EscapeString("# " + liveness))
|
||||
buf.WriteString("</dd>")
|
||||
// restarting a line
|
||||
buf.WriteString("<dt class=\"ssa-prog-src\">")
|
||||
}
|
||||
}
|
||||
|
||||
buf.WriteString(v.HTML())
|
||||
} else if b, ok := progToBlock[p]; ok {
|
||||
buf.WriteString("<b>" + b.HTML() + "</b>")
|
||||
|
|
|
|||
Loading…
Reference in New Issue