diff --git a/src/cmd/compile/internal/escape/call.go b/src/cmd/compile/internal/escape/call.go index 704b2e9dd1..bfba651cef 100644 --- a/src/cmd/compile/internal/escape/call.go +++ b/src/cmd/compile/internal/escape/call.go @@ -75,16 +75,8 @@ func (e *escape) callCommon(ks []hole, call ir.Node, init *ir.Nodes, wrapper *ir 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") } diff --git a/src/cmd/compile/internal/inline/inl.go b/src/cmd/compile/internal/inline/inl.go index 9003cbab70..b51498a56c 100644 --- a/src/cmd/compile/internal/inline/inl.go +++ b/src/cmd/compile/internal/inline/inl.go @@ -583,7 +583,7 @@ func (v *hairyVisitor) doNode(n ir.Node) bool { // 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 { @@ -599,7 +599,7 @@ func (v *hairyVisitor) doNode(n ir.Node) bool { 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 } @@ -940,7 +940,7 @@ func inlnode(n ir.Node, bigCaller bool, inlCalls *[]*ir.InlinedCallExpr, edit fu 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) } } @@ -952,7 +952,7 @@ func inlnode(n ir.Node, bigCaller bool, inlCalls *[]*ir.InlinedCallExpr, edit fu // 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: @@ -973,6 +973,9 @@ func inlCallee(fn ir.Node, profile *pgo.Profile) *ir.Func { 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 } diff --git a/src/cmd/compile/internal/ir/expr.go b/src/cmd/compile/internal/ir/expr.go index 02b1733f04..0f44bd8e21 100644 --- a/src/cmd/compile/internal/ir/expr.go +++ b/src/cmd/compile/internal/ir/expr.go @@ -847,6 +847,20 @@ func IsAddressable(n Node) bool { 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 { @@ -867,14 +881,11 @@ func StaticValue(n Node) Node { } } -// 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 } @@ -928,6 +939,10 @@ func Reassigned(name *Name) bool { 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. @@ -964,7 +979,7 @@ func Reassigned(name *Name) bool { 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) @@ -982,6 +997,23 @@ func Reassigned(name *Name) bool { 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 } diff --git a/src/cmd/compile/internal/noder/reader.go b/src/cmd/compile/internal/noder/reader.go index 9448f234b7..42794da042 100644 --- a/src/cmd/compile/internal/noder/reader.go +++ b/src/cmd/compile/internal/noder/reader.go @@ -3483,6 +3483,11 @@ func unifiedInlineCall(call *ir.CallExpr, fn *ir.Func, inlIndex int) *ir.Inlined 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() {