aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/cmd/compile/internal/gc/inl.go51
-rw-r--r--src/cmd/compile/internal/gc/main.go17
-rw-r--r--test/inline.go18
-rw-r--r--test/nowritebarrier.go1
4 files changed, 69 insertions, 18 deletions
diff --git a/src/cmd/compile/internal/gc/inl.go b/src/cmd/compile/internal/gc/inl.go
index b9460ed6d6..29210ff8de 100644
--- a/src/cmd/compile/internal/gc/inl.go
+++ b/src/cmd/compile/internal/gc/inl.go
@@ -496,7 +496,14 @@ func inlcalls(fn *Node) {
if countNodes(fn) >= inlineBigFunctionNodes {
maxCost = inlineBigFunctionMaxCost
}
- fn = inlnode(fn, maxCost)
+ // 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[*Node]bool)
+ fn = inlnode(fn, maxCost, inlMap)
if fn != Curfn {
Fatalf("inlnode replaced curfn")
}
@@ -537,10 +544,10 @@ func inlconv2list(n *Node) []*Node {
return s
}
-func inlnodelist(l Nodes, maxCost int32) {
+func inlnodelist(l Nodes, maxCost int32, inlMap map[*Node]bool) {
s := l.Slice()
for i := range s {
- s[i] = inlnode(s[i], maxCost)
+ s[i] = inlnode(s[i], maxCost, inlMap)
}
}
@@ -557,7 +564,7 @@ func inlnodelist(l Nodes, maxCost int32) {
// shorter and less complicated.
// The result of inlnode MUST be assigned back to n, e.g.
// n.Left = inlnode(n.Left)
-func inlnode(n *Node, maxCost int32) *Node {
+func inlnode(n *Node, maxCost int32, inlMap map[*Node]bool) *Node {
if n == nil {
return n
}
@@ -585,19 +592,19 @@ func inlnode(n *Node, maxCost int32) *Node {
lno := setlineno(n)
- inlnodelist(n.Ninit, maxCost)
+ inlnodelist(n.Ninit, maxCost, inlMap)
for _, n1 := range n.Ninit.Slice() {
if n1.Op == OINLCALL {
inlconv2stmt(n1)
}
}
- n.Left = inlnode(n.Left, maxCost)
+ n.Left = inlnode(n.Left, maxCost, inlMap)
if n.Left != nil && n.Left.Op == OINLCALL {
n.Left = inlconv2expr(n.Left)
}
- n.Right = inlnode(n.Right, maxCost)
+ n.Right = inlnode(n.Right, maxCost, inlMap)
if n.Right != nil && n.Right.Op == OINLCALL {
if n.Op == OFOR || n.Op == OFORUNTIL {
inlconv2stmt(n.Right)
@@ -612,7 +619,7 @@ func inlnode(n *Node, maxCost int32) *Node {
}
}
- inlnodelist(n.List, maxCost)
+ inlnodelist(n.List, maxCost, inlMap)
if n.Op == OBLOCK {
for _, n2 := range n.List.Slice() {
if n2.Op == OINLCALL {
@@ -628,7 +635,7 @@ func inlnode(n *Node, maxCost int32) *Node {
}
}
- inlnodelist(n.Rlist, maxCost)
+ inlnodelist(n.Rlist, maxCost, inlMap)
s := n.Rlist.Slice()
for i1, n1 := range s {
if n1.Op == OINLCALL {
@@ -640,7 +647,7 @@ func inlnode(n *Node, maxCost int32) *Node {
}
}
- inlnodelist(n.Nbody, maxCost)
+ inlnodelist(n.Nbody, maxCost, inlMap)
for _, n := range n.Nbody.Slice() {
if n.Op == OINLCALL {
inlconv2stmt(n)
@@ -663,12 +670,12 @@ func inlnode(n *Node, maxCost int32) *Node {
fmt.Printf("%v:call to func %+v\n", n.Line(), n.Left)
}
if n.Left.Func != nil && n.Left.Func.Inl != nil && !isIntrinsicCall(n) { // normal case
- n = mkinlcall(n, n.Left, maxCost)
+ n = mkinlcall(n, n.Left, maxCost, inlMap)
} else if n.Left.isMethodExpression() && asNode(n.Left.Sym.Def) != nil {
- n = mkinlcall(n, asNode(n.Left.Sym.Def), maxCost)
+ n = mkinlcall(n, asNode(n.Left.Sym.Def), maxCost, inlMap)
} else if n.Left.Op == OCLOSURE {
if f := inlinableClosure(n.Left); f != nil {
- n = mkinlcall(n, f, maxCost)
+ n = mkinlcall(n, f, maxCost, inlMap)
}
} else if n.Left.Op == ONAME && n.Left.Name != nil && n.Left.Name.Defn != nil {
if d := n.Left.Name.Defn; d.Op == OAS && d.Right.Op == OCLOSURE {
@@ -694,7 +701,7 @@ func inlnode(n *Node, maxCost int32) *Node {
}
break
}
- n = mkinlcall(n, f, maxCost)
+ n = mkinlcall(n, f, maxCost, inlMap)
}
}
}
@@ -713,7 +720,7 @@ func inlnode(n *Node, maxCost int32) *Node {
Fatalf("no function definition for [%p] %+v\n", n.Left.Type, n.Left.Type)
}
- n = mkinlcall(n, asNode(n.Left.Type.FuncType().Nname), maxCost)
+ n = mkinlcall(n, asNode(n.Left.Type.FuncType().Nname), maxCost, inlMap)
}
lineno = lno
@@ -833,7 +840,7 @@ var inlgen int
// parameters.
// The result of mkinlcall MUST be assigned back to n, e.g.
// n.Left = mkinlcall(n.Left, fn, isddd)
-func mkinlcall(n, fn *Node, maxCost int32) *Node {
+func mkinlcall(n, fn *Node, maxCost int32, inlMap map[*Node]bool) *Node {
if fn.Func.Inl == nil {
// No inlinable body.
return n
@@ -866,6 +873,16 @@ func mkinlcall(n, fn *Node, maxCost int32) *Node {
return n
}
+ if inlMap[fn] {
+ if Debug['m'] > 1 {
+ fmt.Printf("%v: cannot inline %v into %v: repeated recursive cycle\n", n.Line(), fn, Curfn.funcname())
+ }
+ return n
+ }
+ inlMap[fn] = true
+ defer func() {
+ inlMap[fn] = false
+ }()
if Debug_typecheckinl == 0 {
typecheckinl(fn)
}
@@ -1129,7 +1146,7 @@ func mkinlcall(n, fn *Node, maxCost int32) *Node {
// instead we emit the things that the body needs
// and each use must redo the inlining.
// luckily these are small.
- inlnodelist(call.Nbody, maxCost)
+ inlnodelist(call.Nbody, maxCost, inlMap)
for _, n := range call.Nbody.Slice() {
if n.Op == OINLCALL {
inlconv2stmt(n)
diff --git a/src/cmd/compile/internal/gc/main.go b/src/cmd/compile/internal/gc/main.go
index d04c09c93e..f65b95a9c9 100644
--- a/src/cmd/compile/internal/gc/main.go
+++ b/src/cmd/compile/internal/gc/main.go
@@ -679,8 +679,12 @@ func Main(archInit func(*Arch)) {
if Debug['l'] != 0 {
// Find functions that can be inlined and clone them before walk expands them.
visitBottomUp(xtop, func(list []*Node, recursive bool) {
+ numfns := numNonClosures(list)
for _, n := range list {
- if !recursive {
+ if !recursive || numfns > 1 {
+ // We allow inlining if there is no
+ // recursion, or the recursion cycle is
+ // across more than one function.
caninl(n)
} else {
if Debug['m'] > 1 {
@@ -824,6 +828,17 @@ func Main(archInit func(*Arch)) {
}
}
+// numNonClosures returns the number of functions in list which are not closures.
+func numNonClosures(list []*Node) int {
+ count := 0
+ for _, n := range list {
+ if n.Func.Closure == nil {
+ count++
+ }
+ }
+ return count
+}
+
func writebench(filename string) error {
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
if err != nil {
diff --git a/test/inline.go b/test/inline.go
index 8ebffedfb7..0b3ad55d46 100644
--- a/test/inline.go
+++ b/test/inline.go
@@ -180,3 +180,21 @@ func (T) meth2(int, int) { // not inlineable - has 2 calls.
runtime.GC()
runtime.GC()
}
+
+// Issue #29737 - make sure we can do inlining for a chain of recursive functions
+func ee() { // ERROR "can inline ee"
+ ff(100) // ERROR "inlining call to ff" "inlining call to gg" "inlining call to hh"
+}
+
+func ff(x int) { // ERROR "can inline ff"
+ if x < 0 {
+ return
+ }
+ gg(x - 1)
+}
+func gg(x int) { // ERROR "can inline gg"
+ hh(x - 1)
+}
+func hh(x int) { // ERROR "can inline hh"
+ ff(x - 1) // ERROR "inlining call to ff" // ERROR "inlining call to gg"
+}
diff --git a/test/nowritebarrier.go b/test/nowritebarrier.go
index 64666fa5c6..654f16d0d2 100644
--- a/test/nowritebarrier.go
+++ b/test/nowritebarrier.go
@@ -67,6 +67,7 @@ func d2() {
d3()
}
+//go:noinline
func d3() {
x.f = y // ERROR "write barrier prohibited by caller"
d4()