diff --git a/src/cmd/compile/internal/gc/inl.go b/src/cmd/compile/internal/gc/inl.go index ea31da9b15..e54bb97ed2 100644 --- a/src/cmd/compile/internal/gc/inl.go +++ b/src/cmd/compile/internal/gc/inl.go @@ -280,6 +280,26 @@ func (v *hairyVisitor) visit(n *Node) bool { v.budget -= fn.InlCost break } + if n.Left.Op == OCLOSURE { + if fn := inlinableClosure(n.Left); fn != nil { + v.budget -= fn.Func.InlCost + break + } + } else if n.Left.Op == ONAME && n.Left.Name != nil && n.Left.Name.Defn != nil { + // NB: this case currently cannot trigger since closure definition + // prevents inlining + // NB: ideally we would also handle captured variables defined as + // closures in the outer scope this brings us back to the idea of + // function value propagation, which if available would both avoid + // the "reassigned" check and neatly handle multiple use cases in a + // single code path + if d := n.Left.Name.Defn; d.Op == OAS && d.Right.Op == OCLOSURE { + if fn := inlinableClosure(d.Right); fn != nil { + v.budget -= fn.Func.InlCost + break + } + } + } if n.Left.isMethodExpression() { if d := asNode(n.Left.Sym.Def); d != nil && d.Func.Inl.Len() != 0 { @@ -629,22 +649,16 @@ func inlnode(n *Node) *Node { return n } +// inlinableClosure takes an OCLOSURE node and follows linkage to the matching ONAME with +// the inlinable body. Returns nil if the function is not inlinable. func inlinableClosure(n *Node) *Node { c := n.Func.Closure caninl(c) f := c.Func.Nname - if f != nil && f.Func.Inl.Len() != 0 { - if n.Func.Cvars.Len() != 0 { - // FIXME: support closure with captured variables - // they currently result in invariant violation in the SSA phase - if Debug['m'] > 1 { - fmt.Printf("%v: cannot inline closure w/ captured vars %v\n", n.Line(), n.Left) - } - return nil - } - return f + if f == nil || f.Func.Inl.Len() == 0 { + return nil } - return nil + return f } // reassigned takes an ONAME node, walks the function in which it is defined, and returns a boolean @@ -792,16 +806,53 @@ func mkinlcall1(n, fn *Node, isddd bool) *Node { ninit := n.Ninit + // Make temp names to use instead of the originals. + inlvars := make(map[*Node]*Node) + // Find declarations corresponding to inlineable body. var dcl []*Node if fn.Name.Defn != nil { dcl = fn.Func.Inldcl.Slice() // local function + + // handle captured variables when inlining closures + if c := fn.Name.Defn.Func.Closure; c != nil { + for _, v := range c.Func.Cvars.Slice() { + if v.Op == OXXX { + continue + } + + o := v.Name.Param.Outer + // make sure the outer param matches the inlining location + // NB: if we enabled inlining of functions containing OCLOSURE or refined + // the reassigned check via some sort of copy propagation this would most + // likely need to be changed to a loop to walk up to the correct Param + if o == nil || (o.Name.Curfn != Curfn && o.Name.Curfn.Func.Closure != Curfn) { + Fatalf("%v: unresolvable capture %v %v\n", n.Line(), fn, v) + } + + if v.Name.Byval() { + iv := typecheck(inlvar(v), Erv) + ninit.Append(nod(ODCL, iv, nil)) + ninit.Append(typecheck(nod(OAS, iv, o), Etop)) + inlvars[v] = iv + } else { + addr := newname(lookup("&" + v.Sym.Name)) + addr.Type = types.NewPtr(v.Type) + ia := typecheck(inlvar(addr), Erv) + ninit.Append(nod(ODCL, ia, nil)) + ninit.Append(typecheck(nod(OAS, ia, nod(OADDR, o, nil)), Etop)) + inlvars[addr] = ia + + // When capturing by reference, all occurrence of the captured var + // must be substituted with dereference of the temporary address + inlvars[v] = typecheck(nod(OIND, ia, nil), Erv) + } + } + } } else { dcl = fn.Func.Dcl // imported function } - // Make temp names to use instead of the originals. - inlvars := make(map[*Node]*Node) for _, ln := range dcl { if ln.Op != ONAME { continue diff --git a/test/closure3.dir/main.go b/test/closure3.dir/main.go index 5629a522d7..4364343160 100644 --- a/test/closure3.dir/main.go +++ b/test/closure3.dir/main.go @@ -170,4 +170,114 @@ func main() { } }() } + + { + x := 42 + if y := func() int { // ERROR "can inline main.func20" + return x + }(); y != 42 { // ERROR "inlining call to main.func20" + panic("y != 42") + } + if y := func() int { // ERROR "can inline main.func21" "func literal does not escape" + return x + }; y() != 42 { // ERROR "inlining call to main.func21" + panic("y() != 42") + } + } + + { + x := 42 + if z := func(y int) int { // ERROR "func literal does not escape" + return func() int { // ERROR "can inline main.func22.1" + return x + y + }() // ERROR "inlining call to main.func22.1" + }(1); z != 43 { + panic("z != 43") + } + if z := func(y int) int { // ERROR "func literal does not escape" + return func() int { // ERROR "can inline main.func23.1" + return x + y + }() // ERROR "inlining call to main.func23.1" + }; z(1) != 43 { + panic("z(1) != 43") + } + } + + { + a := 1 + func() { // ERROR "func literal does not escape" + func() { // ERROR "can inline main.func24" + a = 2 + }() // ERROR "inlining call to main.func24" "&a does not escape" + }() + if a != 2 { + panic("a != 2") + } + } + + { + b := 2 + func(b int) { // ERROR "func literal does not escape" + func() { // ERROR "can inline main.func25.1" + b = 3 + }() // ERROR "inlining call to main.func25.1" "&b does not escape" + if b != 3 { + panic("b != 3") + } + }(b) + if b != 2 { + panic("b != 2") + } + } + + { + c := 3 + func() { // ERROR "func literal does not escape" + c = 4 + func() { // ERROR "func literal does not escape" + if c != 4 { + panic("c != 4") + } + }() + }() + if c != 4 { + panic("c != 4") + } + } + + { + a := 2 + if r := func(x int) int { // ERROR "func literal does not escape" + b := 3 + return func(y int) int { // ERROR "func literal does not escape" + c := 5 + return func(z int) int { // ERROR "can inline main.func27.1.1" + return a*x + b*y + c*z + }(10) // ERROR "inlining call to main.func27.1.1" + }(100) + }(1000); r != 2350 { + panic("r != 2350") + } + } + + { + a := 2 + if r := func(x int) int { // ERROR "func literal does not escape" + b := 3 + return func(y int) int { // ERROR "func literal does not escape" + c := 5 + func(z int) { // ERROR "can inline main.func28.1.1" + a = a * x + b = b * y + c = c * z + }(10) // ERROR "inlining call to main.func28.1.1" "&a does not escape" "&b does not escape" "&c does not escape" + return a + c + }(100) + b + }(1000); r != 2350 { + panic("r != 2350") + } + if a != 2000 { + panic("a != 2000") + } + } } diff --git a/test/fixedbugs/issue20250.go b/test/fixedbugs/issue20250.go index 4a8fe30935..525192a46b 100644 --- a/test/fixedbugs/issue20250.go +++ b/test/fixedbugs/issue20250.go @@ -1,4 +1,4 @@ -// errorcheck -0 -live -d=compilelater,eagerwb +// errorcheck -0 -live -l -d=compilelater,eagerwb // Copyright 2017 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style diff --git a/test/inline.go b/test/inline.go index 7d8b2ceba9..2553230462 100644 --- a/test/inline.go +++ b/test/inline.go @@ -93,6 +93,39 @@ func p() int { return func() int { return 42 }() // ERROR "can inline p.func1" "inlining call to p.func1" } +func q(x int) int { + foo := func() int { return x * 2 } // ERROR "can inline q.func1" "q func literal does not escape" + return foo() // ERROR "inlining call to q.func1" +} + +func r(z int) int { + foo := func(x int) int { // ERROR "can inline r.func1" "r func literal does not escape" + return x + z + } + bar := func(x int) int { // ERROR "r func literal does not escape" + return x + func(y int) int { // ERROR "can inline r.func2.1" + return 2*y + x*z + }(x) // ERROR "inlining call to r.func2.1" + } + return foo(42) + bar(42) // ERROR "inlining call to r.func1" +} + +func s0(x int) int { + foo := func() { // ERROR "can inline s0.func1" "s0 func literal does not escape" + x = x + 1 + } + foo() // ERROR "inlining call to s0.func1" "&x does not escape" + return x +} + +func s1(x int) int { + foo := func() int { // ERROR "can inline s1.func1" "s1 func literal does not escape" + return x + } + x = x + 1 + return foo() // ERROR "inlining call to s1.func1" "&x does not escape" +} + // can't currently inline functions with a break statement func switchBreak(x, y int) int { var n int