mirror of
https://github.com/golang/go
synced 2024-11-11 19:51:37 -07:00
[dev.regabi] cmd/compile: exporting, importing, and inlining functions with OCLOSURE
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>
This commit is contained in:
parent
92cb157cf3
commit
1760d736f6
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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())
|
||||
|
@ -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.
|
||||
|
@ -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++
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
}
|
||||
|
11
test/closure5.dir/a.go
Normal file
11
test/closure5.dir/a.go
Normal file
@ -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 } }
|
15
test/closure5.dir/main.go
Normal file
15
test/closure5.dir/main.go
Normal file
@ -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")
|
||||
}
|
||||
}
|
10
test/closure5.go
Normal file
10
test/closure5.go
Normal file
@ -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
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user