cmd/compile: rework marking of dead hidden closure functions

[This is a roll-forward of CL 484859, this time including a fix for
issue #59709. The call to do dead function marking was taking place in
the wrong spot, causing it to run more than once if generics were
instantiated.]

This patch generalizes the code in the inliner that marks unreferenced
hidden closure functions as dead. Rather than doing the marking on the
fly (previous approach), this new approach does a single pass at the
end of inlining, which catches more dead functions.

Change-Id: I0e079ad755c21295477201acbd7e1a732a98fffd
Reviewed-on: https://go-review.googlesource.com/c/go/+/492016
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
Run-TryBot: Than McIntosh <thanm@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
This commit is contained in:
Than McIntosh 2023-04-14 14:07:37 -04:00
parent 10ad6c91de
commit ea69de9b92
8 changed files with 260 additions and 37 deletions

View file

@ -178,6 +178,11 @@ func pgoInlineEpilogue(p *pgo.Profile, decls []ir.Node) {
// InlinePackage finds functions that can be inlined and clones them before walk expands them.
func InlinePackage(p *pgo.Profile) {
InlineDecls(p, typecheck.Target.Decls, true)
// Perform a garbage collection of hidden closures functions that
// are no longer reachable from top-level functions following
// inlining. See #59404 and #59638 for more context.
garbageCollectUnreferencedHiddenClosures()
}
// InlineDecls applies inlining to the given batch of declarations.
@ -229,24 +234,64 @@ func InlineDecls(p *pgo.Profile, decls []ir.Node, doInline bool) {
}
})
// Rewalk post-inlining functions to check for closures that are
// still visible but were (over-agressively) marked as dead, and
// undo that marking here. See #59404 for more context.
ir.VisitFuncsBottomUp(decls, func(list []*ir.Func, recursive bool) {
for _, n := range list {
ir.Visit(n, func(node ir.Node) {
if clo, ok := node.(*ir.ClosureExpr); ok && clo.Func.IsHiddenClosure() {
clo.Func.SetIsDeadcodeClosure(false)
}
})
}
})
if p != nil {
pgoInlineEpilogue(p, decls)
}
}
// garbageCollectUnreferencedHiddenClosures makes a pass over all the
// top-level (non-hidden-closure) functions looking for nested closure
// functions that are reachable, then sweeps through the Target.Decls
// list and marks any non-reachable hidden closure function as dead.
// See issues #59404 and #59638 for more context.
func garbageCollectUnreferencedHiddenClosures() {
liveFuncs := make(map[*ir.Func]bool)
var markLiveFuncs func(fn *ir.Func)
markLiveFuncs = func(fn *ir.Func) {
if liveFuncs[fn] {
return
}
liveFuncs[fn] = true
ir.Visit(fn, func(n ir.Node) {
if clo, ok := n.(*ir.ClosureExpr); ok {
markLiveFuncs(clo.Func)
}
})
}
for i := 0; i < len(typecheck.Target.Decls); i++ {
if fn, ok := typecheck.Target.Decls[i].(*ir.Func); ok {
if fn.IsHiddenClosure() {
continue
}
markLiveFuncs(fn)
}
}
for i := 0; i < len(typecheck.Target.Decls); i++ {
if fn, ok := typecheck.Target.Decls[i].(*ir.Func); ok {
if !fn.IsHiddenClosure() {
continue
}
if fn.IsDeadcodeClosure() {
continue
}
if liveFuncs[fn] {
continue
}
fn.SetIsDeadcodeClosure(true)
if base.Flag.LowerM > 2 {
fmt.Printf("%v: unreferenced closure %v marked as dead\n", ir.Line(fn), fn)
}
if fn.Inl != nil && fn.LSym == nil {
ir.InitLSym(fn, true)
}
}
}
}
// CanInline determines whether fn is inlineable.
// If so, CanInline saves copies of fn.Body and fn.Dcl in fn.Inl.
// fn and fn.Body will already have been typechecked.
@ -893,30 +938,6 @@ func inlnode(n ir.Node, bigCaller bool, inlCalls *[]*ir.InlinedCallExpr, edit fu
}
if fn := inlCallee(call.X, profile); fn != nil && typecheck.HaveInlineBody(fn) {
n = mkinlcall(call, fn, bigCaller, inlCalls)
if fn.IsHiddenClosure() {
// Visit function to pick out any contained hidden
// closures to mark them as dead, since they will no
// longer be reachable (if we leave them live, they
// will get skipped during escape analysis, which
// could mean that go/defer statements don't get
// desugared, causing later problems in walk). See
// #59404 for more context. Note also that the code
// below can sometimes be too aggressive (marking a closure
// dead even though it was captured by a local var).
// In this case we'll undo the dead marking in a cleanup
// pass that happens at the end of InlineDecls.
var vis func(node ir.Node)
vis = func(node ir.Node) {
if clo, ok := node.(*ir.ClosureExpr); ok && clo.Func.IsHiddenClosure() && !clo.Func.IsDeadcodeClosure() {
if base.Flag.LowerM > 2 {
fmt.Printf("%v: closure %v marked as dead\n", ir.Line(clo.Func), clo.Func)
}
clo.Func.SetIsDeadcodeClosure(true)
ir.Visit(clo.Func, vis)
}
}
ir.Visit(fn, vis)
}
}
}