1
0
mirror of https://github.com/golang/go synced 2024-09-29 04:14:27 -06: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)
// 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 {
switch n.Op {
case ONAME:
// Mark any referenced global variables or
// functions for reexport. Skip methods,
// because they're reexported alongside their
// receiver type.
if n.Class() == PEXTERN || n.Class() == PFUNC && !n.isMethodExpression() {
switch n.Class() {
case PFUNC:
if n.isMethodExpression() {
inlFlood(asNode(n.Type.Nname()))
} else {
inlFlood(n)
exportsym(n)
}
case PEXTERN:
exportsym(n)
}
case OCALLFUNC, OCALLMETH:
// Recursively flood any functions called by
// this one.
inlFlood(asNode(n.Left.Type.Nname()))
case ODOTMETH:
fn := asNode(n.Type.Nname())
inlFlood(fn)
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
})
@ -706,7 +724,14 @@ func inlCallee(fn *Node) *Node {
switch {
case fn.Op == ONAME && fn.Class() == PFUNC:
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
case fn.Op == OCLOSURE:
@ -719,6 +744,11 @@ func inlCallee(fn *Node) *Node {
func staticValue(n *Node) *Node {
for {
if n.Op == OCONVNOP {
n = n.Left
continue
}
n1 := staticValue1(n)
if n1 == nil {
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.
var retvars []*Node
for i, t := range fn.Type.Results().Fields().Slice() {
var m *Node
mpos := t.Pos
if n := asNode(t.Nname); n != nil && !n.isBlank() {
m = inlvar(n)
m = typecheck(m, ctxExpr)
inlvars[n] = m
delayretvars = false // found a named result parameter
} else {
// anonymous return values, synthesize names for use in assignment that replaces return
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.
if !strings.HasPrefix(m.Sym.Name, "~R") {
m.Name.SetInlFormal(true)
m.Pos = mpos
m.Pos = t.Pos
inlfvars = append(inlfvars, m)
}
}
ninit.Append(nod(ODCL, m, nil))
retvars = append(retvars, m)
}
@ -1095,11 +1137,14 @@ func mkinlcall(n, fn *Node, maxCost int32, inlMap map[*Node]bool) *Node {
ninit.Append(vas)
}
// Zero the return parameters.
for _, n := range retvars {
ras := nod(OAS, n, nil)
ras = typecheck(ras, ctxStmt)
ninit.Append(ras)
if !delayretvars {
// Zero the return parameters.
for _, n := range retvars {
ninit.Append(nod(ODCL, n, nil))
ras := nod(OAS, n, nil)
ras = typecheck(ras, ctxStmt)
ninit.Append(ras)
}
}
retlabel := autolabel(".i")
@ -1130,11 +1175,12 @@ func mkinlcall(n, fn *Node, maxCost int32, inlMap map[*Node]bool) *Node {
}
subst := inlsubst{
retlabel: retlabel,
retvars: retvars,
inlvars: inlvars,
bases: make(map[*src.PosBase]*src.PosBase),
newInlIndex: newIndex,
retlabel: retlabel,
retvars: retvars,
delayretvars: delayretvars,
inlvars: inlvars,
bases: make(map[*src.PosBase]*src.PosBase),
newInlIndex: newIndex,
}
body := subst.list(asNodes(fn.Func.Inl.Body))
@ -1230,6 +1276,10 @@ type inlsubst struct {
// Temporary result variables.
retvars []*Node
// Whether result variables should be initialized at the
// "return" statement.
delayretvars bool
inlvars map[*Node]*Node
// 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.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)
m.Ninit.Append(as)
}

View File

@ -75,8 +75,19 @@ func (v *bottomUpVisitor) visit(n *Node) uint32 {
inspectList(n.Nbody, func(n *Node) bool {
switch n.Op {
case OCALLFUNC, OCALLMETH:
fn := asNode(n.Left.Type.Nname())
case ONAME:
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 m := v.visit(fn.Name.Defn); m < min {
min = m