Skip to content

Commit c0417df

Browse files
committed
cmd/compile: improve escape analysis of known calls
Escape analysis is currently very naive about identifying calls to known functions: it only recognizes direct calls to a declared function, or direct calls to a closure. This CL adds a new "staticValue" helper function that can trace back through local variables that were initialized and never reassigned based on a similar optimization already used by inlining. (And to be used by inlining in a followup CL.) Updates #41474. Change-Id: I8204fd3b1e150ab77a27f583985cf099a8572b2e Reviewed-on: https://go-review.googlesource.com/c/go/+/256458 Run-TryBot: Matthew Dempsky <[email protected]> TryBot-Result: Go Bot <[email protected]> Trust: Matthew Dempsky <[email protected]> Reviewed-by: Cuong Manh Le <[email protected]> Reviewed-by: David Chase <[email protected]>
1 parent cced777 commit c0417df

File tree

3 files changed

+69
-6
lines changed

3 files changed

+69
-6
lines changed

src/cmd/compile/internal/gc/escape.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -771,10 +771,11 @@ func (e *Escape) call(ks []EscHole, call, where *Node) {
771771
var fn *Node
772772
switch call.Op {
773773
case OCALLFUNC:
774-
if call.Left.Op == ONAME && call.Left.Class() == PFUNC {
775-
fn = call.Left
776-
} else if call.Left.Op == OCLOSURE {
777-
fn = call.Left.Func.Closure.Func.Nname
774+
switch v := staticValue(call.Left); {
775+
case v.Op == ONAME && v.Class() == PFUNC:
776+
fn = v
777+
case v.Op == OCLOSURE:
778+
fn = v.Func.Closure.Func.Nname
778779
}
779780
case OCALLMETH:
780781
fn = asNode(call.Left.Type.FuncType().Nname)

src/cmd/compile/internal/gc/inl.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -751,6 +751,55 @@ func inlinableClosure(n *Node) *Node {
751751
return f
752752
}
753753

754+
func staticValue(n *Node) *Node {
755+
for {
756+
n1 := staticValue1(n)
757+
if n1 == nil {
758+
return n
759+
}
760+
n = n1
761+
}
762+
}
763+
764+
// staticValue1 implements a simple SSA-like optimization.
765+
func staticValue1(n *Node) *Node {
766+
if n.Op != ONAME || n.Class() != PAUTO || n.Name.Addrtaken() {
767+
return nil
768+
}
769+
770+
defn := n.Name.Defn
771+
if defn == nil {
772+
return nil
773+
}
774+
775+
var rhs *Node
776+
FindRHS:
777+
switch defn.Op {
778+
case OAS:
779+
rhs = defn.Right
780+
case OAS2:
781+
for i, lhs := range defn.List.Slice() {
782+
if lhs == n {
783+
rhs = defn.Rlist.Index(i)
784+
break FindRHS
785+
}
786+
}
787+
Fatalf("%v missing from LHS of %v", n, defn)
788+
default:
789+
return nil
790+
}
791+
if rhs == nil {
792+
Fatalf("RHS is nil: %v", defn)
793+
}
794+
795+
unsafe, _ := reassigned(n)
796+
if unsafe {
797+
return nil
798+
}
799+
800+
return rhs
801+
}
802+
754803
// reassigned takes an ONAME node, walks the function in which it is defined, and returns a boolean
755804
// indicating whether the name has any assignments other than its declaration.
756805
// The second return value is the first such assignment encountered in the walk, if any. It is mostly

test/escape_closure.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ func ClosureCallArgs4() {
5050
}
5151

5252
func ClosureCallArgs5() {
53-
x := 0 // ERROR "moved to heap: x"
53+
x := 0 // ERROR "moved to heap: x"
5454
// TODO(mdempsky): We get "leaking param: p" here because the new escape analysis pass
5555
// can tell that p flows directly to sink, but it's a little weird. Re-evaluate.
5656
sink = func(p *int) *int { // ERROR "leaking param: p" "func literal does not escape"
@@ -132,7 +132,7 @@ func ClosureCallArgs14() {
132132
}
133133

134134
func ClosureCallArgs15() {
135-
x := 0 // ERROR "moved to heap: x"
135+
x := 0 // ERROR "moved to heap: x"
136136
p := &x
137137
sink = func(p **int) *int { // ERROR "leaking param content: p" "func literal does not escape"
138138
return *p
@@ -164,3 +164,16 @@ func ClosureLeak2a(a ...string) string { // ERROR "leaking param content: a"
164164
func ClosureLeak2b(f func() string) string { // ERROR "f does not escape"
165165
return f()
166166
}
167+
168+
func ClosureIndirect() {
169+
f := func(p *int) {} // ERROR "p does not escape" "func literal does not escape"
170+
f(new(int)) // ERROR "new\(int\) does not escape"
171+
172+
g := f
173+
g(new(int)) // ERROR "new\(int\) does not escape"
174+
175+
h := nopFunc
176+
h(new(int)) // ERROR "new\(int\) does not escape"
177+
}
178+
179+
func nopFunc(p *int) {} // ERROR "p does not escape"

0 commit comments

Comments
 (0)