mirror of
https://github.com/golang/go.git
synced 2025-05-05 15:43:04 +00:00
cmd/compile: defer transitive inlining until after AST is edited
This CL changes the inliner to process transitive inlining iteratively after the AST has actually been edited, rather than recursively and immediately. This is important for handling indirect function calls correctly, because ir.reassigned walks the function body looking for reassignments; whereas previously the inlined reassignments might not have been actually added to the AST yet. Fixes #54632. Change-Id: I0dd69813c8a70b965174e0072335bc00afedf286 Reviewed-on: https://go-review.googlesource.com/c/go/+/425257 Run-TryBot: Matthew Dempsky <mdempsky@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com> Reviewed-by: David Chase <drchase@google.com>
This commit is contained in:
parent
b5a9459cd0
commit
f983a9340d
@ -532,18 +532,23 @@ func InlineCalls(fn *ir.Func) {
|
||||
if isBigFunc(fn) {
|
||||
maxCost = inlineBigFunctionMaxCost
|
||||
}
|
||||
// Map to keep track of functions that have been inlined at a particular
|
||||
// call site, in order to stop inlining when we reach the beginning of a
|
||||
// recursion cycle again. We don't inline immediately recursive functions,
|
||||
// but allow inlining if there is a recursion cycle of many functions.
|
||||
// Most likely, the inlining will stop before we even hit the beginning of
|
||||
// the cycle again, but the map catches the unusual case.
|
||||
inlMap := make(map[*ir.Func]bool)
|
||||
var inlCalls []*ir.InlinedCallExpr
|
||||
var edit func(ir.Node) ir.Node
|
||||
edit = func(n ir.Node) ir.Node {
|
||||
return inlnode(n, maxCost, inlMap, edit)
|
||||
return inlnode(n, maxCost, &inlCalls, edit)
|
||||
}
|
||||
ir.EditChildren(fn, edit)
|
||||
|
||||
// If we inlined any calls, we want to recursively visit their
|
||||
// bodies for further inlining. However, we need to wait until
|
||||
// *after* the original function body has been expanded, or else
|
||||
// inlCallee can have false positives (e.g., #54632).
|
||||
for len(inlCalls) > 0 {
|
||||
call := inlCalls[0]
|
||||
inlCalls = inlCalls[1:]
|
||||
ir.EditChildren(call, edit)
|
||||
}
|
||||
|
||||
ir.CurFunc = savefn
|
||||
}
|
||||
|
||||
@ -561,7 +566,7 @@ func InlineCalls(fn *ir.Func) {
|
||||
// The result of inlnode MUST be assigned back to n, e.g.
|
||||
//
|
||||
// n.Left = inlnode(n.Left)
|
||||
func inlnode(n ir.Node, maxCost int32, inlMap map[*ir.Func]bool, edit func(ir.Node) ir.Node) ir.Node {
|
||||
func inlnode(n ir.Node, maxCost int32, inlCalls *[]*ir.InlinedCallExpr, edit func(ir.Node) ir.Node) ir.Node {
|
||||
if n == nil {
|
||||
return n
|
||||
}
|
||||
@ -623,7 +628,7 @@ func inlnode(n ir.Node, maxCost int32, inlMap map[*ir.Func]bool, edit func(ir.No
|
||||
break
|
||||
}
|
||||
if fn := inlCallee(call.X); fn != nil && typecheck.HaveInlineBody(fn) {
|
||||
n = mkinlcall(call, fn, maxCost, inlMap, edit)
|
||||
n = mkinlcall(call, fn, maxCost, inlCalls, edit)
|
||||
}
|
||||
}
|
||||
|
||||
@ -696,7 +701,7 @@ var NewInline = oldInline
|
||||
// The result of mkinlcall MUST be assigned back to n, e.g.
|
||||
//
|
||||
// n.Left = mkinlcall(n.Left, fn, isddd)
|
||||
func mkinlcall(n *ir.CallExpr, fn *ir.Func, maxCost int32, inlMap map[*ir.Func]bool, edit func(ir.Node) ir.Node) ir.Node {
|
||||
func mkinlcall(n *ir.CallExpr, fn *ir.Func, maxCost int32, inlCalls *[]*ir.InlinedCallExpr, edit func(ir.Node) ir.Node) ir.Node {
|
||||
if fn.Inl == nil {
|
||||
if logopt.Enabled() {
|
||||
logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", ir.FuncName(ir.CurFunc),
|
||||
@ -775,22 +780,27 @@ func mkinlcall(n *ir.CallExpr, fn *ir.Func, maxCost int32, inlMap map[*ir.Func]b
|
||||
return n
|
||||
}
|
||||
|
||||
if inlMap[fn] {
|
||||
if base.Flag.LowerM > 1 {
|
||||
fmt.Printf("%v: cannot inline %v into %v: repeated recursive cycle\n", ir.Line(n), fn, ir.FuncName(ir.CurFunc))
|
||||
parent := base.Ctxt.PosTable.Pos(n.Pos()).Base().InliningIndex()
|
||||
sym := fn.Linksym()
|
||||
|
||||
// Check if we've already inlined this function at this particular
|
||||
// call site, in order to stop inlining when we reach the beginning
|
||||
// of a recursion cycle again. We don't inline immediately recursive
|
||||
// functions, but allow inlining if there is a recursion cycle of
|
||||
// many functions. Most likely, the inlining will stop before we
|
||||
// even hit the beginning of the cycle again, but this catches the
|
||||
// unusual case.
|
||||
for inlIndex := parent; inlIndex >= 0; inlIndex = base.Ctxt.InlTree.Parent(inlIndex) {
|
||||
if base.Ctxt.InlTree.InlinedFunction(inlIndex) == sym {
|
||||
if base.Flag.LowerM > 1 {
|
||||
fmt.Printf("%v: cannot inline %v into %v: repeated recursive cycle\n", ir.Line(n), fn, ir.FuncName(ir.CurFunc))
|
||||
}
|
||||
return n
|
||||
}
|
||||
return n
|
||||
}
|
||||
inlMap[fn] = true
|
||||
defer func() {
|
||||
inlMap[fn] = false
|
||||
}()
|
||||
|
||||
typecheck.FixVariadicCall(n)
|
||||
|
||||
parent := base.Ctxt.PosTable.Pos(n.Pos()).Base().InliningIndex()
|
||||
|
||||
sym := fn.Linksym()
|
||||
inlIndex := base.Ctxt.InlTree.Add(parent, n.Pos(), sym)
|
||||
|
||||
if base.Flag.GenDwarfInl > 0 {
|
||||
@ -812,18 +822,12 @@ func mkinlcall(n *ir.CallExpr, fn *ir.Func, maxCost int32, inlMap map[*ir.Func]b
|
||||
return n
|
||||
}
|
||||
|
||||
// transitive inlining
|
||||
// might be nice to do this before exporting the body,
|
||||
// but can't emit the body with inlining expanded.
|
||||
// instead we emit the things that the body needs
|
||||
// and each use must redo the inlining.
|
||||
// luckily these are small.
|
||||
ir.EditChildren(res, edit)
|
||||
|
||||
if base.Flag.LowerM > 2 {
|
||||
fmt.Printf("%v: After inlining %+v\n\n", ir.Line(res), res)
|
||||
}
|
||||
|
||||
*inlCalls = append(*inlCalls, res)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
|
31
test/fixedbugs/issue54632.go
Normal file
31
test/fixedbugs/issue54632.go
Normal file
@ -0,0 +1,31 @@
|
||||
// run
|
||||
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// The inliner would erroneously scan the caller function's body for
|
||||
// reassignments *before* substituting the inlined function call body,
|
||||
// which could cause false positives in deciding when it's safe to
|
||||
// transitively inline indirect function calls.
|
||||
|
||||
package main
|
||||
|
||||
func main() {
|
||||
bug1()
|
||||
bug2(fail)
|
||||
}
|
||||
|
||||
func bug1() {
|
||||
fn := fail
|
||||
fn = pass
|
||||
fn()
|
||||
}
|
||||
|
||||
func bug2(fn func()) {
|
||||
fn = pass
|
||||
fn()
|
||||
}
|
||||
|
||||
func pass() {}
|
||||
func fail() { panic("FAIL") }
|
Loading…
x
Reference in New Issue
Block a user