Skip to content

Commit

Permalink
[dev.typeparams] cmd/compile: refactor closure construction
Browse files Browse the repository at this point in the history
typecheck.tcClosure is complicated with many code flows because all of
its callers setup the closure funcs in slightly different ways. E.g.,
it's non-obvious who's responsible for setting the underlying func's
Sym or adding it to target.Decls, or how to write new code that
constructs a closure without interfering with existing code.

This CL refactors everything to use three common functions in package
ir: NewClosureFunc (which handle creating the Func, Name, and
ClosureExpr and wiring them together), NameClosure (which generates
and assigns its unique Sym), and UseClosure (which handles adding the
Func to target.Decls).

Most IR builders can actually name the closure right away, but the
legacy noder+typecheck path may not yet know the name of the enclosing
function. In particular, for methods declared with aliased receiver
parameters, we need to wait until after typechecking top-level
declarations to know the method's true name. So they're left anonymous
until typecheck.

UseClosure does relatively little work today, but it serves as a
useful spot to check that the code setting up closures got it right.
It may also eventually serve as an optimization point for early
lifting of trivial closures, which may or may not ultimately be
beneficial.

Change-Id: I7da1e93c70d268f575b12d6aaeb2336eb910a6f1
Reviewed-on: https://go-review.googlesource.com/c/go/+/327051
Trust: Matthew Dempsky <mdempsky@google.com>
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
  • Loading branch information
mdempsky committed Jun 12, 2021
1 parent 8f00eb0 commit 0132b91
Show file tree
Hide file tree
Showing 10 changed files with 200 additions and 196 deletions.
32 changes: 11 additions & 21 deletions src/cmd/compile/internal/inline/inl.go
Expand Up @@ -1143,38 +1143,28 @@ func (subst *inlsubst) clovar(n *ir.Name) *ir.Name {
// closure does the necessary substitions for a ClosureExpr n and returns the new
// closure node.
func (subst *inlsubst) closure(n *ir.ClosureExpr) ir.Node {
m := ir.Copy(n)

// Prior to the subst edit, set a flag in the inlsubst to
// indicated that we don't want to update the source positions in
// the new closure. If we do this, it will appear that the closure
// itself has things inlined into it, which is not the case. See
// issue #46234 for more details.
defer func(prev bool) { subst.noPosUpdate = prev }(subst.noPosUpdate)
subst.noPosUpdate = true
ir.EditChildren(m, subst.edit)

//fmt.Printf("Inlining func %v with closure into %v\n", subst.fn, ir.FuncName(ir.CurFunc))

// The following is similar to funcLit
outerfunc := subst.newclofn
if outerfunc == nil {
outerfunc = ir.CurFunc
}

oldfn := n.Func
newfn := ir.NewFunc(oldfn.Pos())
// These three lines are not strictly necessary, but just to be clear
// that new function needs to redo typechecking and inlinability.
newfn.SetTypecheck(0)
newfn.SetInlinabilityChecked(false)
newfn.Inl = nil
newfn.SetIsHiddenClosure(true)
newfn.Nname = ir.NewNameAt(n.Pos(), ir.BlankNode.Sym())
newfn.Nname.Func = newfn
newfn := ir.NewClosureFunc(oldfn.Pos(), outerfunc)

// Ntype can be nil for -G=3 mode.
if oldfn.Nname.Ntype != nil {
newfn.Nname.Ntype = subst.node(oldfn.Nname.Ntype).(ir.Ntype)
}
newfn.Nname.Defn = newfn

m.(*ir.ClosureExpr).Func = newfn
newfn.OClosure = m.(*ir.ClosureExpr)

if subst.newclofn != nil {
//fmt.Printf("Inlining a closure with a nested closure\n")
Expand Down Expand Up @@ -1224,13 +1214,13 @@ func (subst *inlsubst) closure(n *ir.ClosureExpr) ir.Node {

// Actually create the named function for the closure, now that
// the closure is inlined in a specific function.
m.SetTypecheck(0)
newclo := newfn.OClosure
newclo.SetInit(subst.list(n.Init()))
if oldfn.ClosureCalled() {
typecheck.Callee(m)
return typecheck.Callee(newclo)
} else {
typecheck.Expr(m)
return typecheck.Expr(newclo)
}
return m
}

// node recursively copies a node from the saved pristine body of the
Expand Down
1 change: 1 addition & 0 deletions src/cmd/compile/internal/ir/expr.go
Expand Up @@ -195,6 +195,7 @@ type ClosureExpr struct {
IsGoWrap bool // whether this is wrapper closure of a go statement
}

// Deprecated: Use NewClosureFunc instead.
func NewClosureExpr(pos src.XPos, fn *Func) *ClosureExpr {
n := &ClosureExpr{Func: fn}
n.op = OCLOSURE
Expand Down
100 changes: 100 additions & 0 deletions src/cmd/compile/internal/ir/func.go
Expand Up @@ -9,6 +9,7 @@ import (
"cmd/compile/internal/types"
"cmd/internal/obj"
"cmd/internal/src"
"fmt"
)

// A Func corresponds to a single function in a Go program
Expand Down Expand Up @@ -311,3 +312,102 @@ func ClosureDebugRuntimeCheck(clo *ClosureExpr) {
func IsTrivialClosure(clo *ClosureExpr) bool {
return len(clo.Func.ClosureVars) == 0
}

// globClosgen is like Func.Closgen, but for the global scope.
var globClosgen int32

// closureName generates a new unique name for a closure within outerfn.
func closureName(outerfn *Func) *types.Sym {
pkg := types.LocalPkg
outer := "glob."
prefix := "func"
gen := &globClosgen

if outerfn != nil {
if outerfn.OClosure != nil {
prefix = ""
}

pkg = outerfn.Sym().Pkg
outer = FuncName(outerfn)

// There may be multiple functions named "_". In those
// cases, we can't use their individual Closgens as it
// would lead to name clashes.
if !IsBlank(outerfn.Nname) {
gen = &outerfn.Closgen
}
}

*gen++
return pkg.Lookup(fmt.Sprintf("%s.%s%d", outer, prefix, *gen))
}

// NewClosureFunc creates a new Func to represent a function literal
// within outerfn.
func NewClosureFunc(pos src.XPos, outerfn *Func) *Func {
fn := NewFunc(pos)
fn.SetIsHiddenClosure(outerfn != nil)

fn.Nname = NewNameAt(pos, BlankNode.Sym())
fn.Nname.Func = fn
fn.Nname.Defn = fn

fn.OClosure = NewClosureExpr(pos, fn)

return fn
}

// NameClosure generates a unique for the given function literal,
// which must have appeared within outerfn.
func NameClosure(clo *ClosureExpr, outerfn *Func) {
name := clo.Func.Nname
if !IsBlank(name) {
base.FatalfAt(clo.Pos(), "closure already named: %v", name)
}

name.SetSym(closureName(outerfn))
MarkFunc(name)
}

// UseClosure checks that the ginen function literal has been setup
// correctly, and then returns it as an expression.
// It must be called after clo.Func.ClosureVars has been set.
func UseClosure(clo *ClosureExpr, pkg *Package) Node {
fn := clo.Func
name := fn.Nname

if IsBlank(name) {
base.FatalfAt(fn.Pos(), "unnamed closure func: %v", fn)
}
// Caution: clo.Typecheck() is still 0 when UseClosure is called by
// tcClosure.
if fn.Typecheck() != 1 || name.Typecheck() != 1 {
base.FatalfAt(fn.Pos(), "missed typecheck: %v", fn)
}
if clo.Type() == nil || name.Type() == nil {
base.FatalfAt(fn.Pos(), "missing types: %v", fn)
}
if !types.Identical(clo.Type(), name.Type()) {
base.FatalfAt(fn.Pos(), "mismatched types: %v", fn)
}

if base.Flag.W > 1 {
s := fmt.Sprintf("new closure func: %v", fn)
Dump(s, fn)
}

if pkg != nil {
pkg.Decls = append(pkg.Decls, fn)
}

if false && IsTrivialClosure(clo) {
// TODO(mdempsky): Investigate if we can/should optimize this
// case. walkClosure already handles it later, but it could be
// useful to recognize earlier (e.g., it might allow multiple
// inlined calls to a function to share a common trivial closure
// func, rather than cloning it for each inlined call).
}

return clo
}
16 changes: 4 additions & 12 deletions src/cmd/compile/internal/noder/expr.go
Expand Up @@ -373,19 +373,13 @@ func (g *irgen) compLit(typ types2.Type, lit *syntax.CompositeLit) ir.Node {
}

func (g *irgen) funcLit(typ2 types2.Type, expr *syntax.FuncLit) ir.Node {
fn := ir.NewFunc(g.pos(expr))
fn.SetIsHiddenClosure(ir.CurFunc != nil)
fn := ir.NewClosureFunc(g.pos(expr), ir.CurFunc)
ir.NameClosure(fn.OClosure, ir.CurFunc)

fn.Nname = ir.NewNameAt(g.pos(expr), typecheck.ClosureName(ir.CurFunc))
ir.MarkFunc(fn.Nname)
typ := g.typ(typ2)
fn.Nname.Func = fn
fn.Nname.Defn = fn
typed(typ, fn.Nname)
fn.SetTypecheck(1)

fn.OClosure = ir.NewClosureExpr(g.pos(expr), fn)
typed(typ, fn.OClosure)
fn.SetTypecheck(1)

g.funcBody(fn, nil, expr.Type, expr.Body)

Expand All @@ -399,9 +393,7 @@ func (g *irgen) funcLit(typ2 types2.Type, expr *syntax.FuncLit) ir.Node {
cv.SetWalkdef(1)
}

g.target.Decls = append(g.target.Decls, fn)

return fn.OClosure
return ir.UseClosure(fn.OClosure, g.target)
}

func (g *irgen) typeExpr(typ syntax.Expr) *types.Type {
Expand Down
69 changes: 34 additions & 35 deletions src/cmd/compile/internal/noder/noder.go
Expand Up @@ -110,25 +110,35 @@ func LoadPackage(filenames []string) {
// We also defer type alias declarations until phase 2
// to avoid cycles like #18640.
// TODO(gri) Remove this again once we have a fix for #25838.

// Don't use range--typecheck can add closures to Target.Decls.
base.Timer.Start("fe", "typecheck", "top1")
for i := 0; i < len(typecheck.Target.Decls); i++ {
n := typecheck.Target.Decls[i]
if op := n.Op(); op != ir.ODCL && op != ir.OAS && op != ir.OAS2 && (op != ir.ODCLTYPE || !n.(*ir.Decl).X.Alias()) {
typecheck.Target.Decls[i] = typecheck.Stmt(n)
}
}

//
// Phase 2: Variable assignments.
// To check interface assignments, depends on phase 1.

// Don't use range--typecheck can add closures to Target.Decls.
base.Timer.Start("fe", "typecheck", "top2")
for i := 0; i < len(typecheck.Target.Decls); i++ {
n := typecheck.Target.Decls[i]
if op := n.Op(); op == ir.ODCL || op == ir.OAS || op == ir.OAS2 || op == ir.ODCLTYPE && n.(*ir.Decl).X.Alias() {
typecheck.Target.Decls[i] = typecheck.Stmt(n)
for phase, name := range []string{"top1", "top2"} {
base.Timer.Start("fe", "typecheck", name)
for i := 0; i < len(typecheck.Target.Decls); i++ {
n := typecheck.Target.Decls[i]
op := n.Op()

// Closure function declarations are typechecked as part of the
// closure expression.
if fn, ok := n.(*ir.Func); ok && fn.OClosure != nil {
continue
}

// We don't actually add ir.ODCL nodes to Target.Decls. Make sure of that.
if op == ir.ODCL {
base.FatalfAt(n.Pos(), "unexpected top declaration: %v", op)
}

// Identify declarations that should be deferred to the second
// iteration.
late := op == ir.OAS || op == ir.OAS2 || op == ir.ODCLTYPE && n.(*ir.Decl).X.Alias()

if late == (phase == 1) {
typecheck.Target.Decls[i] = typecheck.Stmt(n)
}
}
}

Expand All @@ -137,16 +147,15 @@ func LoadPackage(filenames []string) {
base.Timer.Start("fe", "typecheck", "func")
var fcount int64
for i := 0; i < len(typecheck.Target.Decls); i++ {
n := typecheck.Target.Decls[i]
if n.Op() == ir.ODCLFUNC {
if fn, ok := typecheck.Target.Decls[i].(*ir.Func); ok {
if base.Flag.W > 1 {
s := fmt.Sprintf("\nbefore typecheck %v", n)
ir.Dump(s, n)
s := fmt.Sprintf("\nbefore typecheck %v", fn)
ir.Dump(s, fn)
}
typecheck.FuncBody(n.(*ir.Func))
typecheck.FuncBody(fn)
if base.Flag.W > 1 {
s := fmt.Sprintf("\nafter typecheck %v", n)
ir.Dump(s, n)
s := fmt.Sprintf("\nafter typecheck %v", fn)
ir.Dump(s, fn)
}
fcount++
}
Expand Down Expand Up @@ -1794,24 +1803,14 @@ func fakeRecv() *ir.Field {
}

func (p *noder) funcLit(expr *syntax.FuncLit) ir.Node {
xtype := p.typeExpr(expr.Type)

fn := ir.NewFunc(p.pos(expr))
fn.SetIsHiddenClosure(ir.CurFunc != nil)

fn.Nname = ir.NewNameAt(p.pos(expr), ir.BlankNode.Sym()) // filled in by tcClosure
fn.Nname.Func = fn
fn.Nname.Ntype = xtype
fn.Nname.Defn = fn

clo := ir.NewClosureExpr(p.pos(expr), fn)
fn.OClosure = clo
fn := ir.NewClosureFunc(p.pos(expr), ir.CurFunc)
fn.Nname.Ntype = p.typeExpr(expr.Type)

p.funcBody(fn, expr.Body)

ir.FinishCaptureNames(base.Pos, ir.CurFunc, fn)

return clo
return fn.OClosure
}

// A function named init is a special case.
Expand Down

0 comments on commit 0132b91

Please sign in to comment.