From: Matthew Dempsky Date: Sat, 24 Oct 2020 09:08:06 +0000 (-0700) Subject: cmd/compile: improve inlining and static analysis X-Git-Tag: go1.16beta1~414 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=f2c0c2b90200b470c39a2db821b7c707604fe083;p=gostls13.git 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 Run-TryBot: Matthew Dempsky TryBot-Result: Go Bot Reviewed-by: David Chase --- diff --git a/src/cmd/compile/internal/gc/inl.go b/src/cmd/compile/internal/gc/inl.go index 137675aa20..098c0c99d5 100644 --- a/src/cmd/compile/internal/gc/inl.go +++ b/src/cmd/compile/internal/gc/inl.go @@ -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) } diff --git a/src/cmd/compile/internal/gc/scc.go b/src/cmd/compile/internal/gc/scc.go index 60e0a9b8b5..5c7935aa87 100644 --- a/src/cmd/compile/internal/gc/scc.go +++ b/src/cmd/compile/internal/gc/scc.go @@ -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