This CL extends ir.StaticValue to also work on closure variables.
Also, it extracts the code from escape analysis that's responsible for
determining the static callee of a function. This will be useful when
go/defer statement normalization is moved to typecheck.
Change-Id: I69e1f7fb185658dc9fbfdc69d0f511c84df1d3ac
Reviewed-on: https://go-review.googlesource.com/c/go/+/518959
Reviewed-by: Than McIntosh <thanm@google.com>
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
Auto-Submit: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
call.X.(*ir.ClosureExpr).Func.SetClosureCalled(true)
}
- switch v := ir.StaticValue(call.X); v.Op() {
- case ir.ONAME:
- if v := v.(*ir.Name); v.Class == ir.PFUNC {
- fn = v
- }
- case ir.OCLOSURE:
- fn = v.(*ir.ClosureExpr).Func.Nname
- case ir.OMETHEXPR:
- fn = ir.MethodExprName(v)
- }
+ v := ir.StaticValue(call.X)
+ fn = ir.StaticCalleeName(v)
case ir.OCALLMETH:
base.FatalfAt(call.Pos(), "OCALLMETH missed by typecheck")
}
// Determine if the callee edge is for an inlinable hot callee or not.
if v.profile != nil && v.curFunc != nil {
- if fn := inlCallee(n.X, v.profile); fn != nil && typecheck.HaveInlineBody(fn) {
+ if fn := inlCallee(v.curFunc, n.X, v.profile); fn != nil && typecheck.HaveInlineBody(fn) {
lineOffset := pgo.NodeLineOffset(n, fn)
csi := pgo.CallSiteInfo{LineOffset: lineOffset, Caller: v.curFunc}
if _, o := candHotEdgeMap[csi]; o {
break
}
- if fn := inlCallee(n.X, v.profile); fn != nil && typecheck.HaveInlineBody(fn) {
+ if fn := inlCallee(v.curFunc, n.X, v.profile); fn != nil && typecheck.HaveInlineBody(fn) {
v.budget -= fn.Inl.Cost
break
}
if ir.IsIntrinsicCall(call) {
break
}
- if fn := inlCallee(call.X, profile); fn != nil && typecheck.HaveInlineBody(fn) {
+ if fn := inlCallee(ir.CurFunc, call.X, profile); fn != nil && typecheck.HaveInlineBody(fn) {
n = mkinlcall(call, fn, bigCaller, inlCalls)
}
}
// inlCallee takes a function-typed expression and returns the underlying function ONAME
// that it refers to if statically known. Otherwise, it returns nil.
-func inlCallee(fn ir.Node, profile *pgo.Profile) *ir.Func {
+func inlCallee(caller *ir.Func, fn ir.Node, profile *pgo.Profile) (res *ir.Func) {
fn = ir.StaticValue(fn)
switch fn.Op() {
case ir.OMETHEXPR:
case ir.OCLOSURE:
fn := fn.(*ir.ClosureExpr)
c := fn.Func
+ if len(c.ClosureVars) != 0 && c.ClosureVars[0].Outer.Curfn != caller {
+ return nil // inliner doesn't support inlining across closure frames
+ }
CanInline(c, profile)
return c
}
return false
}
+// StaticValue analyzes n to find the earliest expression that always
+// evaluates to the same value as n, which might be from an enclosing
+// function.
+//
+// For example, given:
+//
+// var x int = g()
+// func() {
+// y := x
+// *p = int(y)
+// }
+//
+// calling StaticValue on the "int(y)" expression returns the outer
+// "g()" expression.
func StaticValue(n Node) Node {
for {
if n.Op() == OCONVNOP {
}
}
-// staticValue1 implements a simple SSA-like optimization. If n is a local variable
-// that is initialized and never reassigned, staticValue1 returns the initializer
-// expression. Otherwise, it returns nil.
func staticValue1(nn Node) Node {
if nn.Op() != ONAME {
return nil
}
- n := nn.(*Name)
+ n := nn.(*Name).Canonical()
if n.Class != PAUTO {
return nil
}
return true
}
+ if name.Addrtaken() {
+ return true // conservatively assume it's reassigned indirectly
+ }
+
// TODO(mdempsky): This is inefficient and becoming increasingly
// unwieldy. Figure out a way to generalize escape analysis's
// reassignment detection for use by inlining and devirtualization.
case OADDR:
n := n.(*AddrExpr)
if isName(n.X) {
- return true
+ base.FatalfAt(n.Pos(), "%v not marked addrtaken", name)
}
case ORANGE:
n := n.(*RangeStmt)
return Any(name.Curfn, do)
}
+// StaticCalleeName returns the ONAME/PFUNC for n, if known.
+func StaticCalleeName(n Node) *Name {
+ switch n.Op() {
+ case OMETHEXPR:
+ n := n.(*SelectorExpr)
+ return MethodExprName(n)
+ case ONAME:
+ n := n.(*Name)
+ if n.Class == PFUNC {
+ return n
+ }
+ case OCLOSURE:
+ return n.(*ClosureExpr).Func.Nname
+ }
+ return nil
+}
+
// IsIntrinsicCall reports whether the compiler back end will treat the call as an intrinsic operation.
var IsIntrinsicCall = func(*CallExpr) bool { return false }
r.closureVars = make([]*ir.Name, len(r.inlFunc.ClosureVars))
for i, cv := range r.inlFunc.ClosureVars {
+ // TODO(mdempsky): It should be possible to support this case, but
+ // for now we rely on the inliner avoiding it.
+ if cv.Outer.Curfn != callerfn {
+ base.FatalfAt(call.Pos(), "inlining closure call across frames")
+ }
r.closureVars[i] = cv.Outer
}
if len(r.closureVars) != 0 && r.hasTypeParams() {