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:
David Chase 2025-01-08 17:01:05 -05:00
parent 46fd6b4e37
commit 7472b4c324
3 changed files with 75 additions and 30 deletions

View File

@ -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

View File

@ -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 {

View File

@ -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>")