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 <thanm@google.com>
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
Auto-Submit: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
This commit is contained in:
Matthew Dempsky 2023-08-11 18:21:22 -07:00 committed by Gopher Robot
parent e95ca9154a
commit 3be2176d92
4 changed files with 51 additions and 19 deletions

View File

@ -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")
}

View File

@ -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
}

View File

@ -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 }

View File

@ -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() {