cmd/compile: use inline-Pos-based recursion test

Look at the inlining stack of positions for a call site,
if the line/col/file of the call site appears in that
stack, do not inline.  This subsumes all the other
recently-added recursive inlining checks, but they are
left in to make this easier+safer to backport.

Fixes #72090

Change-Id: I0f487bb0d4c514015907c649312672b7be464abd
Reviewed-on: https://go-review.googlesource.com/c/go/+/655155
Reviewed-by: Keith Randall <khr@golang.org>
Reviewed-by: Keith Randall <khr@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
This commit is contained in:
David Chase 2025-03-05 13:44:12 -05:00
parent 9189921e47
commit cad4dca518
2 changed files with 107 additions and 0 deletions

View File

@ -42,6 +42,7 @@ import (
"cmd/compile/internal/types"
"cmd/internal/obj"
"cmd/internal/pgo"
"cmd/internal/src"
)
// Inlining budget parameters, gathered in one place
@ -974,6 +975,16 @@ func inlineCostOK(n *ir.CallExpr, caller, callee *ir.Func, bigCaller, closureCal
return true, 0, metric, hot
}
// parsePos returns all the inlining positions and the innermost position.
func parsePos(pos src.XPos, posTmp []src.Pos) ([]src.Pos, src.Pos) {
ctxt := base.Ctxt
ctxt.AllPos(pos, func(p src.Pos) {
posTmp = append(posTmp, p)
})
l := len(posTmp) - 1
return posTmp[:l], posTmp[l]
}
// canInlineCallExpr returns true if the call n from caller to callee
// can be inlined, plus the score computed for the call expr in question,
// and whether the callee is hot according to PGO.
@ -1001,6 +1012,17 @@ func canInlineCallExpr(callerfn *ir.Func, n *ir.CallExpr, callee *ir.Func, bigCa
return false, 0, false
}
callees, calleeInner := parsePos(n.Pos(), make([]src.Pos, 0, 10))
for _, p := range callees {
if p.Line() == calleeInner.Line() && p.Col() == calleeInner.Col() && p.AbsFilename() == calleeInner.AbsFilename() {
if log && logopt.Enabled() {
logopt.LogOpt(n.Pos(), "cannotInlineCall", "inline", fmt.Sprintf("recursive call to %s", ir.FuncName(callerfn)))
}
return false, 0, false
}
}
if callee == callerfn {
// Can't recursively inline a function into itself.
if log && logopt.Enabled() {

View File

@ -0,0 +1,85 @@
// build
// Copyright 2025 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.
package main
import (
"iter"
)
type leafSet map[rune]struct{}
type branchMap map[rune]*node
func (bm branchMap) findOrCreateBranch(r rune) *node {
if _, ok := bm[r]; !ok {
bm[r] = newNode()
}
return bm[r]
}
func (bm branchMap) allSuffixes() iter.Seq[string] {
return func(yield func(string) bool) {
for r, n := range bm {
for s := range n.allStrings() {
if !yield(string(r) + s) {
return
}
}
}
}
}
type node struct {
leafSet
branchMap
}
func newNode() *node {
return &node{make(leafSet), make(branchMap)}
}
func (n *node) add(s []rune) {
switch len(s) {
case 0:
return
case 1:
n.leafSet[s[0]] = struct{}{}
default:
n.branchMap.findOrCreateBranch(s[0]).add(s[1:])
}
}
func (n *node) addString(s string) {
n.add([]rune(s))
}
func (n *node) allStrings() iter.Seq[string] {
return func(yield func(string) bool) {
for s := range n.leafSet {
if !yield(string(s)) {
return
}
}
for r, n := range n.branchMap {
for s := range n.allSuffixes() {
if !yield(string(r) + s) {
return
}
}
}
}
}
func main() {
root := newNode()
for _, s := range []string{"foo", "bar", "baz", "a", "b", "c", "hello", "world"} {
root.addString(s)
}
for s := range root.allStrings() {
println(s)
}
}