From 3be2176d92ad318b9085980b0b7f09f30541afb3 Mon Sep 17 00:00:00 2001 From: Matthew Dempsky Date: Fri, 11 Aug 2023 18:21:22 -0700 Subject: [PATCH] cmd/compile: improve ir.StaticValue and extract ir.StaticCalleeName 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 Run-TryBot: Matthew Dempsky Auto-Submit: Matthew Dempsky TryBot-Result: Gopher Robot --- src/cmd/compile/internal/escape/call.go | 12 ++----- src/cmd/compile/internal/inline/inl.go | 11 ++++--- src/cmd/compile/internal/ir/expr.go | 42 +++++++++++++++++++++--- src/cmd/compile/internal/noder/reader.go | 5 +++ 4 files changed, 51 insertions(+), 19 deletions(-) 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() { -- 2.50.0