1
0
mirror of https://github.com/golang/go synced 2024-11-23 16:40:03 -07:00

cmd/compile: improve inlining and static analysis

When inlining a function call "f()", if "f" contains exactly 1
"return" statement and doesn't name its result parameters, it's
inlined to declare+initialize the result value using the AST
representation that's compatible with staticValue.

Also, extend staticValue to skip over OCONVNOP nodes (often introduced
by inlining), and fix various bits of code related to handling method
expressions.

Updates #33160.

Change-Id: If8652e319f0a5700cf9d40a7a62e369a2a359229
Reviewed-on: https://go-review.googlesource.com/c/go/+/266199
Trust: Matthew Dempsky <mdempsky@google.com>
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: David Chase <drchase@google.com>
This commit is contained in:
Matthew Dempsky 2020-10-24 02:08:06 -07:00
parent 0b798c46cd
commit f2c0c2b902
2 changed files with 94 additions and 25 deletions

View File

@ -257,21 +257,39 @@ func inlFlood(n *Node) {
typecheckinl(n) typecheckinl(n)
// Recursively identify all referenced functions for
// reexport. We want to include even non-called functions,
// because after inlining they might be callable.
inspectList(asNodes(n.Func.Inl.Body), func(n *Node) bool { inspectList(asNodes(n.Func.Inl.Body), func(n *Node) bool {
switch n.Op { switch n.Op {
case ONAME: case ONAME:
// Mark any referenced global variables or switch n.Class() {
// functions for reexport. Skip methods, case PFUNC:
// because they're reexported alongside their if n.isMethodExpression() {
// receiver type. inlFlood(asNode(n.Type.Nname()))
if n.Class() == PEXTERN || n.Class() == PFUNC && !n.isMethodExpression() { } else {
inlFlood(n)
exportsym(n)
}
case PEXTERN:
exportsym(n) exportsym(n)
} }
case OCALLFUNC, OCALLMETH: case ODOTMETH:
// Recursively flood any functions called by fn := asNode(n.Type.Nname())
// this one. inlFlood(fn)
inlFlood(asNode(n.Left.Type.Nname()))
case OCALLPART:
// Okay, because we don't yet inline indirect
// calls to method values.
case OCLOSURE:
// If the closure is inlinable, we'll need to
// flood it too. But today we don't support
// inlining functions that contain closures.
//
// When we do, we'll probably want:
// inlFlood(n.Func.Closure.Func.Nname)
Fatalf("unexpected closure in inlinable function")
} }
return true return true
}) })
@ -706,7 +724,14 @@ func inlCallee(fn *Node) *Node {
switch { switch {
case fn.Op == ONAME && fn.Class() == PFUNC: case fn.Op == ONAME && fn.Class() == PFUNC:
if fn.isMethodExpression() { if fn.isMethodExpression() {
return asNode(fn.Sym.Def) n := asNode(fn.Type.Nname())
// Check that receiver type matches fn.Left.
// TODO(mdempsky): Handle implicit dereference
// of pointer receiver argument?
if n == nil || !types.Identical(n.Type.Recv().Type, fn.Left.Type) {
return nil
}
return n
} }
return fn return fn
case fn.Op == OCLOSURE: case fn.Op == OCLOSURE:
@ -719,6 +744,11 @@ func inlCallee(fn *Node) *Node {
func staticValue(n *Node) *Node { func staticValue(n *Node) *Node {
for { for {
if n.Op == OCONVNOP {
n = n.Left
continue
}
n1 := staticValue1(n) n1 := staticValue1(n)
if n1 == nil { if n1 == nil {
return n return n
@ -1009,15 +1039,28 @@ func mkinlcall(n, fn *Node, maxCost int32, inlMap map[*Node]bool) *Node {
} }
} }
nreturns := 0
inspectList(asNodes(fn.Func.Inl.Body), func(n *Node) bool {
if n != nil && n.Op == ORETURN {
nreturns++
}
return true
})
// We can delay declaring+initializing result parameters if:
// (1) there's only one "return" statement in the inlined
// function, and (2) the result parameters aren't named.
delayretvars := nreturns == 1
// temporaries for return values. // temporaries for return values.
var retvars []*Node var retvars []*Node
for i, t := range fn.Type.Results().Fields().Slice() { for i, t := range fn.Type.Results().Fields().Slice() {
var m *Node var m *Node
mpos := t.Pos
if n := asNode(t.Nname); n != nil && !n.isBlank() { if n := asNode(t.Nname); n != nil && !n.isBlank() {
m = inlvar(n) m = inlvar(n)
m = typecheck(m, ctxExpr) m = typecheck(m, ctxExpr)
inlvars[n] = m inlvars[n] = m
delayretvars = false // found a named result parameter
} else { } else {
// anonymous return values, synthesize names for use in assignment that replaces return // anonymous return values, synthesize names for use in assignment that replaces return
m = retvar(t, i) m = retvar(t, i)
@ -1029,12 +1072,11 @@ func mkinlcall(n, fn *Node, maxCost int32, inlMap map[*Node]bool) *Node {
// were not part of the original callee. // were not part of the original callee.
if !strings.HasPrefix(m.Sym.Name, "~R") { if !strings.HasPrefix(m.Sym.Name, "~R") {
m.Name.SetInlFormal(true) m.Name.SetInlFormal(true)
m.Pos = mpos m.Pos = t.Pos
inlfvars = append(inlfvars, m) inlfvars = append(inlfvars, m)
} }
} }
ninit.Append(nod(ODCL, m, nil))
retvars = append(retvars, m) retvars = append(retvars, m)
} }
@ -1095,12 +1137,15 @@ func mkinlcall(n, fn *Node, maxCost int32, inlMap map[*Node]bool) *Node {
ninit.Append(vas) ninit.Append(vas)
} }
if !delayretvars {
// Zero the return parameters. // Zero the return parameters.
for _, n := range retvars { for _, n := range retvars {
ninit.Append(nod(ODCL, n, nil))
ras := nod(OAS, n, nil) ras := nod(OAS, n, nil)
ras = typecheck(ras, ctxStmt) ras = typecheck(ras, ctxStmt)
ninit.Append(ras) ninit.Append(ras)
} }
}
retlabel := autolabel(".i") retlabel := autolabel(".i")
@ -1132,6 +1177,7 @@ func mkinlcall(n, fn *Node, maxCost int32, inlMap map[*Node]bool) *Node {
subst := inlsubst{ subst := inlsubst{
retlabel: retlabel, retlabel: retlabel,
retvars: retvars, retvars: retvars,
delayretvars: delayretvars,
inlvars: inlvars, inlvars: inlvars,
bases: make(map[*src.PosBase]*src.PosBase), bases: make(map[*src.PosBase]*src.PosBase),
newInlIndex: newIndex, newInlIndex: newIndex,
@ -1230,6 +1276,10 @@ type inlsubst struct {
// Temporary result variables. // Temporary result variables.
retvars []*Node retvars []*Node
// Whether result variables should be initialized at the
// "return" statement.
delayretvars bool
inlvars map[*Node]*Node inlvars map[*Node]*Node
// bases maps from original PosBase to PosBase with an extra // bases maps from original PosBase to PosBase with an extra
@ -1298,6 +1348,14 @@ func (subst *inlsubst) node(n *Node) *Node {
as.List.Append(n) as.List.Append(n)
} }
as.Rlist.Set(subst.list(n.List)) as.Rlist.Set(subst.list(n.List))
if subst.delayretvars {
for _, n := range as.List.Slice() {
as.Ninit.Append(nod(ODCL, n, nil))
n.Name.Defn = as
}
}
as = typecheck(as, ctxStmt) as = typecheck(as, ctxStmt)
m.Ninit.Append(as) m.Ninit.Append(as)
} }

View File

@ -75,8 +75,19 @@ func (v *bottomUpVisitor) visit(n *Node) uint32 {
inspectList(n.Nbody, func(n *Node) bool { inspectList(n.Nbody, func(n *Node) bool {
switch n.Op { switch n.Op {
case OCALLFUNC, OCALLMETH: case ONAME:
fn := asNode(n.Left.Type.Nname()) if n.Class() == PFUNC {
if n.isMethodExpression() {
n = asNode(n.Type.Nname())
}
if n != nil && n.Name.Defn != nil {
if m := v.visit(n.Name.Defn); m < min {
min = m
}
}
}
case ODOTMETH:
fn := asNode(n.Type.Nname())
if fn != nil && fn.Op == ONAME && fn.Class() == PFUNC && fn.Name.Defn != nil { if fn != nil && fn.Op == ONAME && fn.Class() == PFUNC && fn.Name.Defn != nil {
if m := v.visit(fn.Name.Defn); m < min { if m := v.visit(fn.Name.Defn); m < min {
min = m min = m