]> Cypherpunks repositories - gostls13.git/commitdiff
[dev.regabi] cmd/compile: exporting, importing, and inlining functions with OCLOSURE
authorDan Scales <danscales@google.com>
Tue, 1 Dec 2020 22:48:03 +0000 (14:48 -0800)
committerDan Scales <danscales@google.com>
Wed, 20 Jan 2021 22:53:32 +0000 (22:53 +0000)
I have exporting, importing, and inlining of functions with closures
working in all cases (issue #28727). all.bash runs successfully without
errors.

Approach:
  - Write out the Func type, Dcls, ClosureVars, and Body when exporting
    an OCLOSURE.

  - When importing an OCLOSURE, read in the type, dcls, closure vars,
    and body, and then do roughly equivalent code to (*noder).funcLit

  - During inlining of a closure within inlined function, create new
    nodes for all params and local variables (including closure
    variables), so they can have a new Curfn and some other field
    values. Must substitute not only on the Nbody of the closure, but
    also the Type, Cvars, and Dcl fields.

Fixes #28727

Change-Id: I4da1e2567c3fa31a5121afbe82dc4e5ee32b3170
Reviewed-on: https://go-review.googlesource.com/c/go/+/283112
Run-TryBot: Dan Scales <danscales@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
Reviewed-by: Matthew Dempsky <mdempsky@google.com>
Trust: Dan Scales <danscales@google.com>

13 files changed:
src/cmd/compile/internal/escape/escape.go
src/cmd/compile/internal/inline/inl.go
src/cmd/compile/internal/ir/fmt.go
src/cmd/compile/internal/ir/node.go
src/cmd/compile/internal/noder/noder.go
src/cmd/compile/internal/typecheck/func.go
src/cmd/compile/internal/typecheck/iexport.go
src/cmd/compile/internal/typecheck/iimport.go
test/closure3.dir/main.go
test/closure5.dir/a.go [new file with mode: 0644]
test/closure5.dir/main.go [new file with mode: 0644]
test/closure5.go [new file with mode: 0644]
test/inline.go

index 5ee6d4f498dd41f303f562d8c00e73bad73d1149..883e68a730c1c3fc5dbf78f637a804590427bad4 100644 (file)
@@ -218,6 +218,10 @@ func Batch(fns []*ir.Func, recursive bool) {
 
        // Construct data-flow graph from syntax trees.
        for _, fn := range fns {
+               if base.Flag.W > 1 {
+                       s := fmt.Sprintf("\nbefore escape %v", fn)
+                       ir.Dump(s, fn)
+               }
                b.initFunc(fn)
        }
        for _, fn := range fns {
index aa194ebab2b6e1897209a94aabfeb8e3891cd628..7778bc56c4cdd19423f7af499438ef3bb951b332 100644 (file)
@@ -180,7 +180,7 @@ func CanInline(fn *ir.Func) {
        n.Func.Inl = &ir.Inline{
                Cost: inlineMaxBudget - visitor.budget,
                Dcl:  pruneUnusedAutos(n.Defn.(*ir.Func).Dcl, &visitor),
-               Body: ir.DeepCopyList(src.NoXPos, fn.Body),
+               Body: inlcopylist(fn.Body),
        }
 
        if base.Flag.LowerM > 1 {
@@ -217,10 +217,8 @@ func Inline_Flood(n *ir.Name, exportsym func(*ir.Name)) {
 
        typecheck.ImportedBody(fn)
 
-       // Recursively identify all referenced functions for
-       // reexport. We want to include even non-called functions,
-       // because after inlining they might be callable.
-       ir.VisitList(ir.Nodes(fn.Inl.Body), func(n ir.Node) {
+       var doFlood func(n ir.Node)
+       doFlood = func(n ir.Node) {
                switch n.Op() {
                case ir.OMETHEXPR, ir.ODOTMETH:
                        Inline_Flood(ir.MethodExprName(n), exportsym)
@@ -239,15 +237,16 @@ func Inline_Flood(n *ir.Name, exportsym func(*ir.Name)) {
                        // Okay, because we don't yet inline indirect
                        // calls to method values.
                case ir.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)
-                       base.Fatalf("unexpected closure in inlinable function")
+                       // VisitList doesn't visit closure bodies, so force a
+                       // recursive call to VisitList on the body of the closure.
+                       ir.VisitList(n.(*ir.ClosureExpr).Func.Body, doFlood)
                }
-       })
+       }
+
+       // Recursively identify all referenced functions for
+       // reexport. We want to include even non-called functions,
+       // because after inlining they might be callable.
+       ir.VisitList(ir.Nodes(fn.Inl.Body), doFlood)
 }
 
 // hairyVisitor visits a function body to determine its inlining
@@ -360,8 +359,13 @@ func (v *hairyVisitor) doNode(n ir.Node) error {
                // the right panic value, so it needs an argument frame.
                return errors.New("call to recover")
 
-       case ir.OCLOSURE,
-               ir.ORANGE,
+       case ir.OCLOSURE:
+               // TODO(danscales) - fix some bugs when budget is lowered below 30
+               // Maybe make budget proportional to number of closure variables, e.g.:
+               //v.budget -= int32(len(n.(*ir.ClosureExpr).Func.ClosureVars) * 3)
+               v.budget -= 30
+
+       case ir.ORANGE,
                ir.OSELECT,
                ir.OGO,
                ir.ODEFER,
@@ -449,6 +453,52 @@ func isBigFunc(fn *ir.Func) bool {
        })
 }
 
+// inlcopylist (together with inlcopy) recursively copies a list of nodes, except
+// that it keeps the same ONAME, OTYPE, and OLITERAL nodes. It is used for copying
+// the body and dcls of an inlineable function.
+func inlcopylist(ll []ir.Node) []ir.Node {
+       s := make([]ir.Node, len(ll))
+       for i, n := range ll {
+               s[i] = inlcopy(n)
+       }
+       return s
+}
+
+// inlcopy is like DeepCopy(), but does extra work to copy closures.
+func inlcopy(n ir.Node) ir.Node {
+       var edit func(ir.Node) ir.Node
+       edit = func(x ir.Node) ir.Node {
+               switch x.Op() {
+               case ir.ONAME, ir.OTYPE, ir.OLITERAL, ir.ONIL:
+                       return x
+               }
+               m := ir.Copy(x)
+               ir.EditChildren(m, edit)
+               if x.Op() == ir.OCLOSURE {
+                       x := x.(*ir.ClosureExpr)
+                       // Need to save/duplicate x.Func.Nname,
+                       // x.Func.Nname.Ntype, x.Func.Dcl, x.Func.ClosureVars, and
+                       // x.Func.Body for iexport and local inlining.
+                       oldfn := x.Func
+                       newfn := ir.NewFunc(oldfn.Pos())
+                       if oldfn.ClosureCalled() {
+                               newfn.SetClosureCalled(true)
+                       }
+                       m.(*ir.ClosureExpr).Func = newfn
+                       newfn.Nname = ir.NewNameAt(oldfn.Nname.Pos(), oldfn.Nname.Sym())
+                       // XXX OK to share fn.Type() ??
+                       newfn.Nname.SetType(oldfn.Nname.Type())
+                       newfn.Nname.Ntype = inlcopy(oldfn.Nname.Ntype).(ir.Ntype)
+                       newfn.Body = inlcopylist(oldfn.Body)
+                       // Make shallow copy of the Dcl and ClosureVar slices
+                       newfn.Dcl = append([]*ir.Name(nil), oldfn.Dcl...)
+                       newfn.ClosureVars = append([]*ir.Name(nil), oldfn.ClosureVars...)
+               }
+               return m
+       }
+       return edit(n)
+}
+
 // Inlcalls/nodelist/node walks fn's statements and expressions and substitutes any
 // calls made to inlineable functions. This is the external entry point.
 func InlineCalls(fn *ir.Func) {
@@ -925,6 +975,7 @@ func mkinlcall(n *ir.CallExpr, fn *ir.Func, maxCost int32, inlMap map[*ir.Func]b
                inlvars:      inlvars,
                bases:        make(map[*src.PosBase]*src.PosBase),
                newInlIndex:  newIndex,
+               fn:           fn,
        }
        subst.edit = subst.node
 
@@ -1031,6 +1082,12 @@ type inlsubst struct {
        newInlIndex int
 
        edit func(ir.Node) ir.Node // cached copy of subst.node method value closure
+
+       // If non-nil, we are inside a closure inside the inlined function, and
+       // newclofn is the Func of the new inlined closure.
+       newclofn *ir.Func
+
+       fn *ir.Func // For debug -- the func that is being inlined
 }
 
 // list inlines a list of nodes.
@@ -1042,6 +1099,157 @@ func (subst *inlsubst) list(ll ir.Nodes) []ir.Node {
        return s
 }
 
+// fields returns a list of the fields of a struct type representing receiver,
+// params, or results, after duplicating the field nodes and substituting the
+// Nname nodes inside the field nodes.
+func (subst *inlsubst) fields(oldt *types.Type) []*types.Field {
+       oldfields := oldt.FieldSlice()
+       newfields := make([]*types.Field, len(oldfields))
+       for i := range oldfields {
+               newfields[i] = oldfields[i].Copy()
+               if oldfields[i].Nname != nil {
+                       newfields[i].Nname = subst.node(oldfields[i].Nname.(*ir.Name))
+               }
+       }
+       return newfields
+}
+
+// clovar creates a new ONAME node for a local variable or param of a closure
+// inside a function being inlined.
+func (subst *inlsubst) clovar(n *ir.Name) *ir.Name {
+       // TODO(danscales): want to get rid of this shallow copy, with code like the
+       // following, but it is hard to copy all the necessary flags in a maintainable way.
+       // m := ir.NewNameAt(n.Pos(), n.Sym())
+       // m.Class = n.Class
+       // m.SetType(n.Type())
+       // m.SetTypecheck(1)
+       //if n.IsClosureVar() {
+       //      m.SetIsClosureVar(true)
+       //}
+       m := &ir.Name{}
+       *m = *n
+       m.Curfn = subst.newclofn
+       if n.Defn != nil && n.Defn.Op() == ir.ONAME {
+               if !n.IsClosureVar() {
+                       base.FatalfAt(n.Pos(), "want closure variable, got: %+v", n)
+               }
+               if n.Sym().Pkg != types.LocalPkg {
+                       // If the closure came from inlining a function from
+                       // another package, must change package of captured
+                       // variable to localpkg, so that the fields of the closure
+                       // struct are local package and can be accessed even if
+                       // name is not exported. If you disable this code, you can
+                       // reproduce the problem by running 'go test
+                       // go/internal/srcimporter'. TODO(mdempsky) - maybe change
+                       // how we create closure structs?
+                       m.SetSym(types.LocalPkg.Lookup(n.Sym().Name))
+               }
+               // Make sure any inlvar which is the Defn
+               // of an ONAME closure var is rewritten
+               // during inlining. Don't substitute
+               // if Defn node is outside inlined function.
+               if subst.inlvars[n.Defn.(*ir.Name)] != nil {
+                       m.Defn = subst.node(n.Defn)
+               }
+       }
+       if n.Outer != nil {
+               // Either the outer variable is defined in function being inlined,
+               // and we will replace it with the substituted variable, or it is
+               // defined outside the function being inlined, and we should just
+               // skip the outer variable (the closure variable of the function
+               // being inlined).
+               s := subst.node(n.Outer).(*ir.Name)
+               if s == n.Outer {
+                       s = n.Outer.Outer
+               }
+               m.Outer = s
+       }
+       return m
+}
+
+// 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)
+       m.SetPos(subst.updatedPos(m.Pos()))
+       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
+       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.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")
+       }
+       prevxfunc := subst.newclofn
+
+       // Mark that we are now substituting within a closure (within the
+       // inlined function), and create new nodes for all the local
+       // vars/params inside this closure.
+       subst.newclofn = newfn
+       newfn.Dcl = nil
+       newfn.ClosureVars = nil
+       for _, oldv := range oldfn.Dcl {
+               newv := subst.clovar(oldv)
+               subst.inlvars[oldv] = newv
+               newfn.Dcl = append(newfn.Dcl, newv)
+       }
+       for _, oldv := range oldfn.ClosureVars {
+               newv := subst.clovar(oldv)
+               subst.inlvars[oldv] = newv
+               newfn.ClosureVars = append(newfn.ClosureVars, newv)
+       }
+
+       // Need to replace ONAME nodes in
+       // newfn.Type().FuncType().Receiver/Params/Results.FieldSlice().Nname
+       oldt := oldfn.Type()
+       newrecvs := subst.fields(oldt.Recvs())
+       var newrecv *types.Field
+       if len(newrecvs) > 0 {
+               newrecv = newrecvs[0]
+       }
+       newt := types.NewSignature(oldt.Pkg(), newrecv,
+               subst.fields(oldt.Params()), subst.fields(oldt.Results()))
+
+       newfn.Nname.SetType(newt)
+       newfn.Body = subst.list(oldfn.Body)
+
+       // Remove the nodes for the current closure from subst.inlvars
+       for _, oldv := range oldfn.Dcl {
+               delete(subst.inlvars, oldv)
+       }
+       for _, oldv := range oldfn.ClosureVars {
+               delete(subst.inlvars, oldv)
+       }
+       // Go back to previous closure func
+       subst.newclofn = prevxfunc
+
+       // Actually create the named function for the closure, now that
+       // the closure is inlined in a specific function.
+       m.SetTypecheck(0)
+       if oldfn.ClosureCalled() {
+               typecheck.Callee(m)
+       } else {
+               typecheck.Expr(m)
+       }
+       return m
+}
+
 // node recursively copies a node from the saved pristine body of the
 // inlined function, substituting references to input/output
 // parameters with ones to the tmpnames, and substituting returns with
@@ -1056,13 +1264,17 @@ func (subst *inlsubst) node(n ir.Node) ir.Node {
                n := n.(*ir.Name)
 
                // Handle captured variables when inlining closures.
-               if n.IsClosureVar() {
+               if n.IsClosureVar() && subst.newclofn == nil {
                        o := n.Outer
 
+                       // Deal with case where sequence of closures are inlined.
+                       // TODO(danscales) - write test case to see if we need to
+                       // go up multiple levels.
+                       if o.Curfn != ir.CurFunc {
+                               o = o.Outer
+                       }
+
                        // make sure the outer param matches the inlining location
-                       // NB: if we enabled inlining of functions containing OCLOSURE or refined
-                       // the reassigned check via some sort of copy propagation this would most
-                       // likely need to be changed to a loop to walk up to the correct Param
                        if o == nil || o.Curfn != ir.CurFunc {
                                base.Fatalf("%v: unresolvable capture %v\n", ir.Line(n), n)
                        }
@@ -1098,6 +1310,10 @@ func (subst *inlsubst) node(n ir.Node) ir.Node {
                }
 
        case ir.ORETURN:
+               if subst.newclofn != nil {
+                       // Don't do special substitutions if inside a closure
+                       break
+               }
                // Since we don't handle bodies with closures,
                // this return is guaranteed to belong to the current inlined function.
                n := n.(*ir.ReturnStmt)
@@ -1136,6 +1352,10 @@ func (subst *inlsubst) node(n ir.Node) ir.Node {
                return m
 
        case ir.OLABEL:
+               if subst.newclofn != nil {
+                       // Don't do special substitutions if inside a closure
+                       break
+               }
                n := n.(*ir.LabelStmt)
                m := ir.Copy(n).(*ir.LabelStmt)
                m.SetPos(subst.updatedPos(m.Pos()))
@@ -1143,10 +1363,10 @@ func (subst *inlsubst) node(n ir.Node) ir.Node {
                p := fmt.Sprintf("%s·%d", n.Label.Name, inlgen)
                m.Label = typecheck.Lookup(p)
                return m
-       }
 
-       if n.Op() == ir.OCLOSURE {
-               base.Fatalf("cannot inline function containing closure: %+v", n)
+       case ir.OCLOSURE:
+               return subst.closure(n.(*ir.ClosureExpr))
+
        }
 
        m := ir.Copy(n)
index 01197ad2724188611e25cc2bf293c20fc0bcbee5..1a05079dac8ee45b43cbb86f177aca0c2726fb77 100644 (file)
@@ -1020,6 +1020,15 @@ func dumpNodeHeader(w io.Writer, n Node) {
                fmt.Fprintf(w, " defn(%p)", n.Name().Defn)
        }
 
+       if base.Debug.DumpPtrs != 0 && n.Name() != nil && n.Name().Curfn != nil {
+               // Useful to see where Defn is set and what node it points to
+               fmt.Fprintf(w, " curfn(%p)", n.Name().Curfn)
+       }
+       if base.Debug.DumpPtrs != 0 && n.Name() != nil && n.Name().Outer != nil {
+               // Useful to see where Defn is set and what node it points to
+               fmt.Fprintf(w, " outer(%p)", n.Name().Outer)
+       }
+
        if EscFmt != nil {
                if esc := EscFmt(n); esc != "" {
                        fmt.Fprintf(w, " %s", esc)
@@ -1187,6 +1196,18 @@ func dumpNode(w io.Writer, n Node, depth int) {
                                dumpNode(w, dcl, depth+1)
                        }
                }
+               if len(fn.ClosureVars) > 0 {
+                       indent(w, depth)
+                       fmt.Fprintf(w, "%+v-ClosureVars", n.Op())
+                       for _, cv := range fn.ClosureVars {
+                               dumpNode(w, cv, depth+1)
+                       }
+               }
+               if len(fn.Enter) > 0 {
+                       indent(w, depth)
+                       fmt.Fprintf(w, "%+v-Enter", n.Op())
+                       dumpNodes(w, fn.Enter, depth+1)
+               }
                if len(fn.Body) > 0 {
                        indent(w, depth)
                        fmt.Fprintf(w, "%+v-body", n.Op())
index 291e1286bb8f12017a3dfc9affecdc48d3794fe6..ffa7daf6b2be7e8f14005625c088a67358df96fb 100644 (file)
@@ -291,6 +291,10 @@ const (
        OTSLICE // []int
 
        // misc
+       // intermediate representation of an inlined call.  Uses Init (assignments
+       // for the captured variables, parameters, retvars, & INLMARK op),
+       // Body (body of the inlined function), and ReturnVars (list of
+       // return values)
        OINLCALL       // intermediary representation of an inlined call.
        OEFACE         // itable and data words of an empty-interface value.
        OITAB          // itable word of an interface value.
index 99c0e4addeb851d4b85e9861574c1d52e3e46e1f..0ea72a28dcf9c6b1fdf123d3991813d69e908934 100644 (file)
@@ -142,7 +142,15 @@ func Package() {
        for i := 0; i < len(typecheck.Target.Decls); i++ {
                n := typecheck.Target.Decls[i]
                if n.Op() == ir.ODCLFUNC {
+                       if base.Flag.W > 1 {
+                               s := fmt.Sprintf("\nbefore typecheck %v", n)
+                               ir.Dump(s, n)
+                       }
                        typecheck.FuncBody(n.(*ir.Func))
+                       if base.Flag.W > 1 {
+                               s := fmt.Sprintf("\nafter typecheck %v", n)
+                               ir.Dump(s, n)
+                       }
                        fcount++
                }
        }
index b576590d4d92b1081153b2b1edd9e9e4fe1b88cc..f624773c8f0fea62ab7aacdfa5d7f3e7aeb282f9 100644 (file)
@@ -145,7 +145,7 @@ func ImportedBody(fn *ir.Func) {
        // declarations are added to fn.Func.Dcl by funcBody(). Move them
        // to fn.Func.Inl.Dcl for consistency with how local functions
        // behave. (Append because ImportedBody may be called multiple
-       // times.)
+       // times on same fn.)
        fn.Inl.Dcl = append(fn.Inl.Dcl, fn.Dcl...)
        fn.Dcl = nil
 
@@ -303,8 +303,15 @@ func tcClosure(clo *ir.ClosureExpr, top int) {
                return
        }
 
-       fn.Nname.SetSym(closurename(ir.CurFunc))
-       ir.MarkFunc(fn.Nname)
+       // Don't give a name and add to xtop if we are typechecking an inlined
+       // body in ImportedBody(), since we only want to create the named function
+       // when the closure is actually inlined (and then we force a typecheck
+       // explicitly in (*inlsubst).node()).
+       inTypeCheckInl := ir.CurFunc != nil && ir.CurFunc.Body == nil
+       if !inTypeCheckInl {
+               fn.Nname.SetSym(closurename(ir.CurFunc))
+               ir.MarkFunc(fn.Nname)
+       }
        Func(fn)
        clo.SetType(fn.Type())
 
@@ -338,7 +345,14 @@ func tcClosure(clo *ir.ClosureExpr, top int) {
        }
        fn.ClosureVars = fn.ClosureVars[:out]
 
-       Target.Decls = append(Target.Decls, fn)
+       if base.Flag.W > 1 {
+               s := fmt.Sprintf("New closure func: %s", ir.FuncName(fn))
+               ir.Dump(s, fn)
+       }
+       if !inTypeCheckInl {
+               // Add function to xtop once only when we give it a name
+               Target.Decls = append(Target.Decls, fn)
+       }
 }
 
 // type check function definition
index 1ba877113988064d2765e4b54eb08e35bc6e0cd6..be4a689836cec2b258b28b4ea37f4db91f79eb73 100644 (file)
@@ -423,9 +423,13 @@ type exportWriter struct {
        prevLine   int64
        prevColumn int64
 
-       // dclIndex maps function-scoped declarations to their index
-       // within their respective Func's Dcl list.
-       dclIndex map[*ir.Name]int
+       // dclIndex maps function-scoped declarations to an int used to refer to
+       // them later in the function. For local variables/params, the int is
+       // non-negative and in order of the appearance in the Func's Dcl list. For
+       // closure variables, the index is negative starting at -2.
+       dclIndex           map[*ir.Name]int
+       maxDclIndex        int
+       maxClosureVarIndex int
 }
 
 func (p *iexporter) doDecl(n *ir.Name) {
@@ -1038,14 +1042,19 @@ func (w *exportWriter) typeExt(t *types.Type) {
 
 // Inline bodies.
 
-func (w *exportWriter) funcBody(fn *ir.Func) {
-       w.int64(int64(len(fn.Inl.Dcl)))
-       for i, n := range fn.Inl.Dcl {
+func (w *exportWriter) writeNames(dcl []*ir.Name) {
+       w.int64(int64(len(dcl)))
+       for i, n := range dcl {
                w.pos(n.Pos())
                w.localIdent(n.Sym())
                w.typ(n.Type())
-               w.dclIndex[n] = i
+               w.dclIndex[n] = w.maxDclIndex + i
        }
+       w.maxDclIndex += len(dcl)
+}
+
+func (w *exportWriter) funcBody(fn *ir.Func) {
+       w.writeNames(fn.Inl.Dcl)
 
        w.stmtList(fn.Inl.Body)
 }
@@ -1315,8 +1324,30 @@ func (w *exportWriter) expr(n ir.Node) {
        // case OTARRAY, OTMAP, OTCHAN, OTSTRUCT, OTINTER, OTFUNC:
        //      should have been resolved by typechecking - handled by default case
 
-       // case OCLOSURE:
-       //      unimplemented - handled by default case
+       case ir.OCLOSURE:
+               n := n.(*ir.ClosureExpr)
+               w.op(ir.OCLOSURE)
+               w.pos(n.Pos())
+               w.signature(n.Type())
+
+               // Write out id for the Outer of each conditional variable. The
+               // conditional variable itself for this closure will be re-created
+               // during import.
+               w.int64(int64(len(n.Func.ClosureVars)))
+               for i, cv := range n.Func.ClosureVars {
+                       w.pos(cv.Pos())
+                       w.localName(cv.Outer)
+                       // Closure variable (which will be re-created during
+                       // import) is given via a negative id, starting at -2,
+                       // which is used to refer to it later in the function
+                       // during export. -1 represents blanks.
+                       w.dclIndex[cv] = -(i + 2) - w.maxClosureVarIndex
+               }
+               w.maxClosureVarIndex += len(n.Func.ClosureVars)
+
+               // like w.funcBody(n.Func), but not for .Inl
+               w.writeNames(n.Func.Dcl)
+               w.stmtList(n.Func.Body)
 
        // case OCOMPLIT:
        //      should have been resolved by typechecking - handled by default case
index c2610229ec5b26d1378e4d71c5c004d617b31122..f2682257f3456d9d641f8307d0a6c3af53b971e9 100644 (file)
@@ -265,6 +265,9 @@ type importReader struct {
 
        // curfn is the current function we're importing into.
        curfn *ir.Func
+       // Slice of all dcls for function, including any interior closures
+       allDcls        []*ir.Name
+       allClosureVars []*ir.Name
 }
 
 func (p *iimporter) newReader(off uint64, pkg *types.Pkg) *importReader {
@@ -721,6 +724,7 @@ func (r *importReader) doInline(fn *ir.Func) {
                base.Fatalf("%v already has inline body", fn)
        }
 
+       //fmt.Printf("Importing %v\n", n)
        r.funcBody(fn)
 
        importlist = append(importlist, fn)
@@ -754,6 +758,24 @@ func (r *importReader) funcBody(fn *ir.Func) {
        r.curfn = fn
 
        // Import local declarations.
+       fn.Inl.Dcl = r.readFuncDcls(fn)
+
+       // Import function body.
+       body := r.stmtList()
+       if body == nil {
+               // Make sure empty body is not interpreted as
+               // no inlineable body (see also parser.fnbody)
+               // (not doing so can cause significant performance
+               // degradation due to unnecessary calls to empty
+               // functions).
+               body = []ir.Node{}
+       }
+       fn.Inl.Body = body
+
+       r.curfn = outerfn
+}
+
+func (r *importReader) readNames(fn *ir.Func) []*ir.Name {
        dcls := make([]*ir.Name, r.int64())
        for i := range dcls {
                n := ir.NewDeclNameAt(r.pos(), ir.ONAME, r.localIdent())
@@ -762,7 +784,12 @@ func (r *importReader) funcBody(fn *ir.Func) {
                n.SetType(r.typ())
                dcls[i] = n
        }
-       fn.Inl.Dcl = dcls
+       r.allDcls = append(r.allDcls, dcls...)
+       return dcls
+}
+
+func (r *importReader) readFuncDcls(fn *ir.Func) []*ir.Name {
+       dcls := r.readNames(fn)
 
        // Fixup parameter classes and associate with their
        // signature's type fields.
@@ -787,28 +814,18 @@ func (r *importReader) funcBody(fn *ir.Func) {
        for _, f := range typ.Results().FieldSlice() {
                fix(f, ir.PPARAMOUT)
        }
-
-       // Import function body.
-       body := r.stmtList()
-       if body == nil {
-               // Make sure empty body is not interpreted as
-               // no inlineable body (see also parser.fnbody)
-               // (not doing so can cause significant performance
-               // degradation due to unnecessary calls to empty
-               // functions).
-               body = []ir.Node{}
-       }
-       fn.Inl.Body = body
-
-       r.curfn = outerfn
+       return dcls
 }
 
 func (r *importReader) localName() *ir.Name {
        i := r.int64()
-       if i < 0 {
+       if i == -1 {
                return ir.BlankNode.(*ir.Name)
        }
-       return r.curfn.Inl.Dcl[i]
+       if i < 0 {
+               return r.allClosureVars[-i-2]
+       }
+       return r.allDcls[i]
 }
 
 func (r *importReader) stmtList() []ir.Node {
@@ -924,8 +941,38 @@ func (r *importReader) node() ir.Node {
        // case OTARRAY, OTMAP, OTCHAN, OTSTRUCT, OTINTER, OTFUNC:
        //      unreachable - should have been resolved by typechecking
 
-       // case OCLOSURE:
-       //      unimplemented
+       case ir.OCLOSURE:
+               //println("Importing CLOSURE")
+               pos := r.pos()
+               typ := r.signature(nil)
+
+               // All the remaining code below is similar to (*noder).funcLit(), but
+               // with Dcls and ClosureVars lists already set up
+               fn := ir.NewFunc(pos)
+               fn.SetIsHiddenClosure(true)
+               fn.Nname = ir.NewNameAt(pos, ir.BlankNode.Sym())
+               fn.Nname.Func = fn
+               fn.Nname.Ntype = ir.TypeNode(typ)
+               fn.Nname.Defn = fn
+               fn.Nname.SetType(typ)
+
+               cvars := make([]*ir.Name, r.int64())
+               for i := range cvars {
+                       cvars[i] = ir.CaptureName(r.pos(), fn, r.localName().Canonical())
+               }
+               fn.ClosureVars = cvars
+               r.allClosureVars = append(r.allClosureVars, cvars...)
+
+               fn.Dcl = r.readFuncDcls(fn)
+               body := r.stmtList()
+               ir.FinishCaptureNames(pos, r.curfn, fn)
+
+               clo := ir.NewClosureExpr(pos, fn)
+               fn.OClosure = clo
+
+               fn.Body = body
+
+               return clo
 
        // case OPTRLIT:
        //      unreachable - mapped to case OADDR below by exporter
index e8e1e99860f5c1ae28b273ad448b74eca6bde713..2fc33753ed7f8c0c8ea96c78292a82a0c61da0f7 100644 (file)
@@ -93,11 +93,11 @@ func main() {
                y := func(x int) int { // ERROR "can inline main.func11" "func literal does not escape"
                        return x + 2
                }
-               y, sink = func() (func(int) int, int) { // ERROR "func literal does not escape"
-                       return func(x int) int { // ERROR "can inline main.func12" "func literal escapes"
+               y, sink = func() (func(int) int, int) { // ERROR "can inline main.func12"
+                       return func(x int) int { // ERROR "can inline main.func12"
                                return x + 1
                        }, 42
-               }()
+               }() // ERROR "func literal does not escape" "inlining call to main.func12"
                if y(40) != 41 {
                        ppanic("y(40) != 41")
                }
@@ -105,14 +105,14 @@ func main() {
 
        {
                func() { // ERROR "func literal does not escape"
-                       y := func(x int) int { // ERROR "can inline main.func13.1" "func literal does not escape"
+                       y := func(x int) int { // ERROR "func literal does not escape" "can inline main.func13.1"
                                return x + 2
                        }
-                       y, sink = func() (func(int) int, int) { // ERROR "func literal does not escape"
-                               return func(x int) int { // ERROR "can inline main.func13.2" "func literal escapes"
+                       y, sink = func() (func(int) int, int) { // ERROR "can inline main.func13.2"
+                               return func(x int) int { // ERROR "can inline main.func13.2"
                                        return x + 1
                                }, 42
-                       }()
+                       }() // ERROR "inlining call to main.func13.2" "func literal does not escape"
                        if y(40) != 41 {
                                ppanic("y(40) != 41")
                        }
@@ -187,29 +187,29 @@ func main() {
 
        {
                x := 42
-               if z := func(y int) int { // ERROR "func literal does not escape"
-                       return func() int { // ERROR "can inline main.func22.1"
+               if z := func(y int) int { // ERROR "can inline main.func22"
+                       return func() int { // ERROR "can inline main.func22.1" "can inline main.func30"
                                return x + y
                        }() // ERROR "inlining call to main.func22.1"
-               }(1); z != 43 {
+               }(1); z != 43 { // ERROR "inlining call to main.func22" "inlining call to main.func30"
                        ppanic("z != 43")
                }
-               if z := func(y int) int { // ERROR "func literal does not escape"
-                       return func() int { // ERROR "can inline main.func23.1"
+               if z := func(y int) int { // ERROR "func literal does not escape" "can inline main.func23"
+                       return func() int { // ERROR "can inline main.func23.1" "can inline main.func31"
                                return x + y
                        }() // ERROR "inlining call to main.func23.1"
-               }; z(1) != 43 {
+               }; z(1) != 43 { // ERROR "inlining call to main.func23" "inlining call to main.func31"
                        ppanic("z(1) != 43")
                }
        }
 
        {
                a := 1
-               func() { // ERROR "func literal does not escape"
-                       func() { // ERROR "can inline main.func24"
+               func() { // ERROR "can inline main.func24"
+                       func() { // ERROR "can inline main.func24" "can inline main.func32"
                                a = 2
                        }() // ERROR "inlining call to main.func24"
-               }()
+               }() // ERROR "inlining call to main.func24" "inlining call to main.func32"
                if a != 2 {
                        ppanic("a != 2")
                }
@@ -250,12 +250,12 @@ func main() {
                a := 2
                if r := func(x int) int { // ERROR "func literal does not escape"
                        b := 3
-                       return func(y int) int { // ERROR "func literal does not escape"
+                       return func(y int) int { // ERROR "can inline main.func27.1"
                                c := 5
-                               return func(z int) int { // ERROR "can inline main.func27.1.1"
+                               return func(z int) int { // ERROR "can inline main.func27.1.1" "can inline main.func27.2"
                                        return a*x + b*y + c*z
                                }(10) // ERROR "inlining call to main.func27.1.1"
-                       }(100)
+                       }(100) // ERROR "inlining call to main.func27.1" "inlining call to main.func27.2"
                }(1000); r != 2350 {
                        ppanic("r != 2350")
                }
@@ -265,15 +265,15 @@ func main() {
                a := 2
                if r := func(x int) int { // ERROR "func literal does not escape"
                        b := 3
-                       return func(y int) int { // ERROR "func literal does not escape"
+                       return func(y int) int { // ERROR "can inline main.func28.1"
                                c := 5
-                               func(z int) { // ERROR "can inline main.func28.1.1"
+                               func(z int) { // ERROR "can inline main.func28.1.1" "can inline main.func28.2"
                                        a = a * x
                                        b = b * y
                                        c = c * z
                                }(10) // ERROR "inlining call to main.func28.1.1"
                                return a + c
-                       }(100) + b
+                       }(100) + b // ERROR "inlining call to main.func28.1" "inlining call to main.func28.2"
                }(1000); r != 2350 {
                        ppanic("r != 2350")
                }
diff --git a/test/closure5.dir/a.go b/test/closure5.dir/a.go
new file mode 100644 (file)
index 0000000..de8082b
--- /dev/null
@@ -0,0 +1,11 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Check correctness of various closure corner cases
+// that are expected to be inlined
+
+package a
+
+func f() bool               { return true }
+func G() func() func() bool { return func() func() bool { return f } }
diff --git a/test/closure5.dir/main.go b/test/closure5.dir/main.go
new file mode 100644 (file)
index 0000000..ee5dba6
--- /dev/null
@@ -0,0 +1,15 @@
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Check correctness of various closure corner cases
+// that are expected to be inlined
+package main
+
+import "a"
+
+func main() {
+       if !a.G()()() {
+               panic("FAIL")
+       }
+}
diff --git a/test/closure5.go b/test/closure5.go
new file mode 100644 (file)
index 0000000..a7022b2
--- /dev/null
@@ -0,0 +1,10 @@
+// compiledir
+
+// Copyright 2021 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Check correctness of various closure corner cases
+// that are expected to be inlined
+
+package ignored
index d754f06e0340c47025c53e80ad21c9b0ade18d56..37965c0d9dc8aa45ca0f2127f1adc38eb532d19c 100644 (file)
@@ -58,7 +58,7 @@ func _() int { // ERROR "can inline _"
 var somethingWrong error
 
 // local closures can be inlined
-func l(x, y int) (int, int, error) {
+func l(x, y int) (int, int, error) { // ERROR "can inline l"
        e := func(err error) (int, int, error) { // ERROR "can inline l.func1" "func literal does not escape" "leaking param: err to result"
                return 0, 0, err
        }
@@ -90,19 +90,19 @@ func n() int {
 // make sure assignment inside closure is detected
 func o() int {
        foo := func() int { return 1 } // ERROR "can inline o.func1" "func literal does not escape"
-       func(x int) {                  // ERROR "func literal does not escape"
+       func(x int) {                  // ERROR "can inline o.func2"
                if x > 10 {
-                       foo = func() int { return 2 } // ERROR "can inline o.func2" "func literal escapes"
+                       foo = func() int { return 2 } // ERROR "can inline o.func2"
                }
-       }(11)
+       }(11) // ERROR "func literal does not escape" "inlining call to o.func2"
        return foo()
 }
 
-func p() int {
+func p() int { // ERROR "can inline p"
        return func() int { return 42 }() // ERROR "can inline p.func1" "inlining call to p.func1"
 }
 
-func q(x int) int {
+func q(x int) int { // ERROR "can inline q"
        foo := func() int { return x * 2 } // ERROR "can inline q.func1" "func literal does not escape"
        return foo()                       // ERROR "inlining call to q.func1"
 }
@@ -111,15 +111,15 @@ func r(z int) int {
        foo := func(x int) int { // ERROR "can inline r.func1" "func literal does not escape"
                return x + z
        }
-       bar := func(x int) int { // ERROR "func literal does not escape"
-               return x + func(y int) int { // ERROR "can inline r.func2.1"
+       bar := func(x int) int { // ERROR "func literal does not escape" "can inline r.func2"
+               return x + func(y int) int { // ERROR "can inline r.func2.1" "can inline r.func3"
                        return 2*y + x*z
                }(x) // ERROR "inlining call to r.func2.1"
        }
-       return foo(42) + bar(42) // ERROR "inlining call to r.func1"
+       return foo(42) + bar(42) // ERROR "inlining call to r.func1" "inlining call to r.func2" "inlining call to r.func3"
 }
 
-func s0(x int) int {
+func s0(x int) int { // ERROR "can inline s0"
        foo := func() { // ERROR "can inline s0.func1" "func literal does not escape"
                x = x + 1
        }
@@ -127,7 +127,7 @@ func s0(x int) int {
        return x
 }
 
-func s1(x int) int {
+func s1(x int) int { // ERROR "can inline s1"
        foo := func() int { // ERROR "can inline s1.func1" "func literal does not escape"
                return x
        }