[dev.regabi] cmd/compile: split escape analysis state

In a future CL, I plan to change escape analysis to walk function
literal bodies at the point they appear within the AST, rather than
separately as their own standalone function declaration. This means
escape analysis's AST-walking code will become reentrant.

To make this easier to get right, this CL splits escape analysis's
state into two separate types: one that holds all of the state shared
across the entire batch, and another that holds only the state that's
used within initFunc and walkFunc.

Incidentally, this CL reveals that a bunch of logopt code was using
e.curfn outside of the AST-walking code paths where it's actually set,
so it was always nil. That code is in need of refactoring anyway, so
I'll come back and figure out the correct values to pass later when I
address that.

Passes toolstash -cmp.

Change-Id: I1d13f47d06f7583401afa1b53fcc5ee2adaea6c8
Reviewed-on: https://go-review.googlesource.com/c/go/+/280997
Trust: Matthew Dempsky <mdempsky@google.com>
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
This commit is contained in:
Matthew Dempsky 2021-01-01 03:57:21 -08:00
parent fad9a8b528
commit 67ad695416

View file

@ -85,20 +85,29 @@ import (
// u[2], etc. However, we do record the implicit dereference involved // u[2], etc. However, we do record the implicit dereference involved
// in indexing a slice. // in indexing a slice.
type escape struct { // A batch holds escape analysis state that's shared across an entire
// batch of functions being analyzed at once.
type batch struct {
allLocs []*location allLocs []*location
labels map[*types.Sym]labelState // known labels
curfn *ir.Func heapLoc location
blankLoc location
}
// An escape holds state specific to a single function being analyzed
// within a batch.
type escape struct {
*batch
curfn *ir.Func // function being analyzed
labels map[*types.Sym]labelState // known labels
// loopDepth counts the current loop nesting depth within // loopDepth counts the current loop nesting depth within
// curfn. It increments within each "for" loop and at each // curfn. It increments within each "for" loop and at each
// label with a corresponding backwards "goto" (i.e., // label with a corresponding backwards "goto" (i.e.,
// unstructured loop). // unstructured loop).
loopDepth int loopDepth int
heapLoc location
blankLoc location
} }
// An location represents an abstract location that stores a Go // An location represents an abstract location that stores a Go
@ -167,11 +176,11 @@ func Fmt(n ir.Node) string {
if n.Op() == ir.ONAME { if n.Op() == ir.ONAME {
n := n.(*ir.Name) n := n.(*ir.Name)
if e, ok := n.Opt.(*location); ok && e.loopDepth != 0 { if loc, ok := n.Opt.(*location); ok && loc.loopDepth != 0 {
if text != "" { if text != "" {
text += " " text += " "
} }
text += fmt.Sprintf("ld(%d)", e.loopDepth) text += fmt.Sprintf("ld(%d)", loc.loopDepth)
} }
} }
@ -187,23 +196,31 @@ func Batch(fns []*ir.Func, recursive bool) {
} }
} }
var e escape var b batch
e.heapLoc.escapes = true b.heapLoc.escapes = true
// Construct data-flow graph from syntax trees. // Construct data-flow graph from syntax trees.
for _, fn := range fns { for _, fn := range fns {
e.initFunc(fn) b.with(fn).initFunc()
} }
for _, fn := range fns { for _, fn := range fns {
e.walkFunc(fn) b.with(fn).walkFunc()
} }
e.curfn = nil
e.walkAll() b.walkAll()
e.finish(fns) b.finish(fns)
} }
func (e *escape) initFunc(fn *ir.Func) { func (b *batch) with(fn *ir.Func) *escape {
return &escape{
batch: b,
curfn: fn,
loopDepth: 1,
}
}
func (e *escape) initFunc() {
fn := e.curfn
if fn.Esc() != escFuncUnknown { if fn.Esc() != escFuncUnknown {
base.Fatalf("unexpected node: %v", fn) base.Fatalf("unexpected node: %v", fn)
} }
@ -212,9 +229,6 @@ func (e *escape) initFunc(fn *ir.Func) {
ir.Dump("escAnalyze", fn) ir.Dump("escAnalyze", fn)
} }
e.curfn = fn
e.loopDepth = 1
// Allocate locations for local variables. // Allocate locations for local variables.
for _, dcl := range fn.Dcl { for _, dcl := range fn.Dcl {
if dcl.Op() == ir.ONAME { if dcl.Op() == ir.ONAME {
@ -223,7 +237,8 @@ func (e *escape) initFunc(fn *ir.Func) {
} }
} }
func (e *escape) walkFunc(fn *ir.Func) { func (e *escape) walkFunc() {
fn := e.curfn
fn.SetEsc(escFuncStarted) fn.SetEsc(escFuncStarted)
// Identify labels that mark the head of an unstructured loop. // Identify labels that mark the head of an unstructured loop.
@ -246,8 +261,6 @@ func (e *escape) walkFunc(fn *ir.Func) {
} }
}) })
e.curfn = fn
e.loopDepth = 1
e.block(fn.Body) e.block(fn.Body)
if len(e.labels) != 0 { if len(e.labels) != 0 {
@ -680,9 +693,9 @@ func (e *escape) exprSkipInit(k hole, n ir.Node) {
case ir.OCLOSURE: case ir.OCLOSURE:
n := n.(*ir.ClosureExpr) n := n.(*ir.ClosureExpr)
k = e.spill(k, n)
// Link addresses of captured variables to closure. // Link addresses of captured variables to closure.
k = e.spill(k, n)
for _, v := range n.Func.ClosureVars { for _, v := range n.Func.ClosureVars {
k := k k := k
if !v.Byval() { if !v.Byval() {
@ -1174,7 +1187,7 @@ func (e *escape) newLoc(n ir.Node, transient bool) *location {
return loc return loc
} }
func (e *escape) oldLoc(n *ir.Name) *location { func (b *batch) oldLoc(n *ir.Name) *location {
n = canonicalNode(n).(*ir.Name) n = canonicalNode(n).(*ir.Name)
return n.Opt.(*location) return n.Opt.(*location)
} }
@ -1216,7 +1229,7 @@ func (e *escape) discardHole() hole { return e.blankLoc.asHole() }
// walkAll computes the minimal dereferences between all pairs of // walkAll computes the minimal dereferences between all pairs of
// locations. // locations.
func (e *escape) walkAll() { func (b *batch) walkAll() {
// We use a work queue to keep track of locations that we need // We use a work queue to keep track of locations that we need
// to visit, and repeatedly walk until we reach a fixed point. // to visit, and repeatedly walk until we reach a fixed point.
// //
@ -1226,7 +1239,7 @@ func (e *escape) walkAll() {
// happen at most once. So we take Θ(len(e.allLocs)) walks. // happen at most once. So we take Θ(len(e.allLocs)) walks.
// LIFO queue, has enough room for e.allLocs and e.heapLoc. // LIFO queue, has enough room for e.allLocs and e.heapLoc.
todo := make([]*location, 0, len(e.allLocs)+1) todo := make([]*location, 0, len(b.allLocs)+1)
enqueue := func(loc *location) { enqueue := func(loc *location) {
if !loc.queued { if !loc.queued {
todo = append(todo, loc) todo = append(todo, loc)
@ -1234,10 +1247,10 @@ func (e *escape) walkAll() {
} }
} }
for _, loc := range e.allLocs { for _, loc := range b.allLocs {
enqueue(loc) enqueue(loc)
} }
enqueue(&e.heapLoc) enqueue(&b.heapLoc)
var walkgen uint32 var walkgen uint32
for len(todo) > 0 { for len(todo) > 0 {
@ -1246,13 +1259,13 @@ func (e *escape) walkAll() {
root.queued = false root.queued = false
walkgen++ walkgen++
e.walkOne(root, walkgen, enqueue) b.walkOne(root, walkgen, enqueue)
} }
} }
// walkOne computes the minimal number of dereferences from root to // walkOne computes the minimal number of dereferences from root to
// all other locations. // all other locations.
func (e *escape) walkOne(root *location, walkgen uint32, enqueue func(*location)) { func (b *batch) walkOne(root *location, walkgen uint32, enqueue func(*location)) {
// The data flow graph has negative edges (from addressing // The data flow graph has negative edges (from addressing
// operations), so we use the Bellman-Ford algorithm. However, // operations), so we use the Bellman-Ford algorithm. However,
// we don't have to worry about infinite negative cycles since // we don't have to worry about infinite negative cycles since
@ -1287,7 +1300,7 @@ func (e *escape) walkOne(root *location, walkgen uint32, enqueue func(*location)
} }
} }
if e.outlives(root, l) { if b.outlives(root, l) {
// l's value flows to root. If l is a function // l's value flows to root. If l is a function
// parameter and root is the heap or a // parameter and root is the heap or a
// corresponding result parameter, then record // corresponding result parameter, then record
@ -1296,12 +1309,13 @@ func (e *escape) walkOne(root *location, walkgen uint32, enqueue func(*location)
if l.isName(ir.PPARAM) { if l.isName(ir.PPARAM) {
if (logopt.Enabled() || base.Flag.LowerM >= 2) && !l.escapes { if (logopt.Enabled() || base.Flag.LowerM >= 2) && !l.escapes {
if base.Flag.LowerM >= 2 { if base.Flag.LowerM >= 2 {
fmt.Printf("%s: parameter %v leaks to %s with derefs=%d:\n", base.FmtPos(l.n.Pos()), l.n, e.explainLoc(root), derefs) fmt.Printf("%s: parameter %v leaks to %s with derefs=%d:\n", base.FmtPos(l.n.Pos()), l.n, b.explainLoc(root), derefs)
} }
explanation := e.explainPath(root, l) explanation := b.explainPath(root, l)
if logopt.Enabled() { if logopt.Enabled() {
logopt.LogOpt(l.n.Pos(), "leak", "escape", ir.FuncName(e.curfn), var e_curfn *ir.Func // TODO(mdempsky): Fix.
fmt.Sprintf("parameter %v leaks to %s with derefs=%d", l.n, e.explainLoc(root), derefs), explanation) logopt.LogOpt(l.n.Pos(), "leak", "escape", ir.FuncName(e_curfn),
fmt.Sprintf("parameter %v leaks to %s with derefs=%d", l.n, b.explainLoc(root), derefs), explanation)
} }
} }
l.leakTo(root, derefs) l.leakTo(root, derefs)
@ -1315,9 +1329,10 @@ func (e *escape) walkOne(root *location, walkgen uint32, enqueue func(*location)
if base.Flag.LowerM >= 2 { if base.Flag.LowerM >= 2 {
fmt.Printf("%s: %v escapes to heap:\n", base.FmtPos(l.n.Pos()), l.n) fmt.Printf("%s: %v escapes to heap:\n", base.FmtPos(l.n.Pos()), l.n)
} }
explanation := e.explainPath(root, l) explanation := b.explainPath(root, l)
if logopt.Enabled() { if logopt.Enabled() {
logopt.LogOpt(l.n.Pos(), "escape", "escape", ir.FuncName(e.curfn), fmt.Sprintf("%v escapes to heap", l.n), explanation) var e_curfn *ir.Func // TODO(mdempsky): Fix.
logopt.LogOpt(l.n.Pos(), "escape", "escape", ir.FuncName(e_curfn), fmt.Sprintf("%v escapes to heap", l.n), explanation)
} }
} }
l.escapes = true l.escapes = true
@ -1343,7 +1358,7 @@ func (e *escape) walkOne(root *location, walkgen uint32, enqueue func(*location)
} }
// explainPath prints an explanation of how src flows to the walk root. // explainPath prints an explanation of how src flows to the walk root.
func (e *escape) explainPath(root, src *location) []*logopt.LoggedOpt { func (b *batch) explainPath(root, src *location) []*logopt.LoggedOpt {
visited := make(map[*location]bool) visited := make(map[*location]bool)
pos := base.FmtPos(src.n.Pos()) pos := base.FmtPos(src.n.Pos())
var explanation []*logopt.LoggedOpt var explanation []*logopt.LoggedOpt
@ -1362,7 +1377,7 @@ func (e *escape) explainPath(root, src *location) []*logopt.LoggedOpt {
base.Fatalf("path inconsistency: %v != %v", edge.src, src) base.Fatalf("path inconsistency: %v != %v", edge.src, src)
} }
explanation = e.explainFlow(pos, dst, src, edge.derefs, edge.notes, explanation) explanation = b.explainFlow(pos, dst, src, edge.derefs, edge.notes, explanation)
if dst == root { if dst == root {
break break
@ -1373,14 +1388,14 @@ func (e *escape) explainPath(root, src *location) []*logopt.LoggedOpt {
return explanation return explanation
} }
func (e *escape) explainFlow(pos string, dst, srcloc *location, derefs int, notes *note, explanation []*logopt.LoggedOpt) []*logopt.LoggedOpt { func (b *batch) explainFlow(pos string, dst, srcloc *location, derefs int, notes *note, explanation []*logopt.LoggedOpt) []*logopt.LoggedOpt {
ops := "&" ops := "&"
if derefs >= 0 { if derefs >= 0 {
ops = strings.Repeat("*", derefs) ops = strings.Repeat("*", derefs)
} }
print := base.Flag.LowerM >= 2 print := base.Flag.LowerM >= 2
flow := fmt.Sprintf(" flow: %s = %s%v:", e.explainLoc(dst), ops, e.explainLoc(srcloc)) flow := fmt.Sprintf(" flow: %s = %s%v:", b.explainLoc(dst), ops, b.explainLoc(srcloc))
if print { if print {
fmt.Printf("%s:%s\n", pos, flow) fmt.Printf("%s:%s\n", pos, flow)
} }
@ -1391,7 +1406,8 @@ func (e *escape) explainFlow(pos string, dst, srcloc *location, derefs int, note
} else if srcloc != nil && srcloc.n != nil { } else if srcloc != nil && srcloc.n != nil {
epos = srcloc.n.Pos() epos = srcloc.n.Pos()
} }
explanation = append(explanation, logopt.NewLoggedOpt(epos, "escflow", "escape", ir.FuncName(e.curfn), flow)) var e_curfn *ir.Func // TODO(mdempsky): Fix.
explanation = append(explanation, logopt.NewLoggedOpt(epos, "escflow", "escape", ir.FuncName(e_curfn), flow))
} }
for note := notes; note != nil; note = note.next { for note := notes; note != nil; note = note.next {
@ -1399,15 +1415,16 @@ func (e *escape) explainFlow(pos string, dst, srcloc *location, derefs int, note
fmt.Printf("%s: from %v (%v) at %s\n", pos, note.where, note.why, base.FmtPos(note.where.Pos())) fmt.Printf("%s: from %v (%v) at %s\n", pos, note.where, note.why, base.FmtPos(note.where.Pos()))
} }
if logopt.Enabled() { if logopt.Enabled() {
explanation = append(explanation, logopt.NewLoggedOpt(note.where.Pos(), "escflow", "escape", ir.FuncName(e.curfn), var e_curfn *ir.Func // TODO(mdempsky): Fix.
explanation = append(explanation, logopt.NewLoggedOpt(note.where.Pos(), "escflow", "escape", ir.FuncName(e_curfn),
fmt.Sprintf(" from %v (%v)", note.where, note.why))) fmt.Sprintf(" from %v (%v)", note.where, note.why)))
} }
} }
return explanation return explanation
} }
func (e *escape) explainLoc(l *location) string { func (b *batch) explainLoc(l *location) string {
if l == &e.heapLoc { if l == &b.heapLoc {
return "{heap}" return "{heap}"
} }
if l.n == nil { if l.n == nil {
@ -1422,7 +1439,7 @@ func (e *escape) explainLoc(l *location) string {
// outlives reports whether values stored in l may survive beyond // outlives reports whether values stored in l may survive beyond
// other's lifetime if stack allocated. // other's lifetime if stack allocated.
func (e *escape) outlives(l, other *location) bool { func (b *batch) outlives(l, other *location) bool {
// The heap outlives everything. // The heap outlives everything.
if l.escapes { if l.escapes {
return true return true
@ -1503,7 +1520,7 @@ func (l *location) leakTo(sink *location, derefs int) {
l.paramEsc.AddHeap(derefs) l.paramEsc.AddHeap(derefs)
} }
func (e *escape) finish(fns []*ir.Func) { func (b *batch) finish(fns []*ir.Func) {
// Record parameter tags for package export data. // Record parameter tags for package export data.
for _, fn := range fns { for _, fn := range fns {
fn.SetEsc(escFuncTagged) fn.SetEsc(escFuncTagged)
@ -1512,12 +1529,12 @@ func (e *escape) finish(fns []*ir.Func) {
for _, fs := range &types.RecvsParams { for _, fs := range &types.RecvsParams {
for _, f := range fs(fn.Type()).Fields().Slice() { for _, f := range fs(fn.Type()).Fields().Slice() {
narg++ narg++
f.Note = e.paramTag(fn, narg, f) f.Note = b.paramTag(fn, narg, f)
} }
} }
} }
for _, loc := range e.allLocs { for _, loc := range b.allLocs {
n := loc.n n := loc.n
if n == nil { if n == nil {
continue continue
@ -1535,7 +1552,8 @@ func (e *escape) finish(fns []*ir.Func) {
base.WarnfAt(n.Pos(), "%v escapes to heap", n) base.WarnfAt(n.Pos(), "%v escapes to heap", n)
} }
if logopt.Enabled() { if logopt.Enabled() {
logopt.LogOpt(n.Pos(), "escape", "escape", ir.FuncName(e.curfn)) var e_curfn *ir.Func // TODO(mdempsky): Fix.
logopt.LogOpt(n.Pos(), "escape", "escape", ir.FuncName(e_curfn))
} }
} }
n.SetEsc(ir.EscHeap) n.SetEsc(ir.EscHeap)
@ -2061,7 +2079,7 @@ const UnsafeUintptrNote = "unsafe-uintptr"
// marked go:uintptrescapes. // marked go:uintptrescapes.
const UintptrEscapesNote = "uintptr-escapes" const UintptrEscapesNote = "uintptr-escapes"
func (e *escape) paramTag(fn *ir.Func, narg int, f *types.Field) string { func (b *batch) paramTag(fn *ir.Func, narg int, f *types.Field) string {
name := func() string { name := func() string {
if f.Sym != nil { if f.Sym != nil {
return f.Sym.Name return f.Sym.Name
@ -2132,7 +2150,7 @@ func (e *escape) paramTag(fn *ir.Func, narg int, f *types.Field) string {
} }
n := f.Nname.(*ir.Name) n := f.Nname.(*ir.Name)
loc := e.oldLoc(n) loc := b.oldLoc(n)
esc := loc.paramEsc esc := loc.paramEsc
esc.Optimize() esc.Optimize()