From 7b876def6c4936cfae774d3007f8265876a9fbf7 Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Fri, 16 Apr 2021 14:06:50 -0700 Subject: [PATCH] [dev.typeparams] cmd/compile: add dictionary argument to generic functions When converting from a generic function to a concrete implementation, add a dictionary argument to the generic function (both an actual argument at each callsite, and a formal argument of each implementation). The dictionary argument comes before all other arguments (including any receiver). The dictionary argument is checked for validity, but is otherwise unused. Subsequent CLs will start using the dictionary for, e.g., converting a value of generic type to interface{}. Import/export required adding support for LINKSYMOFFSET, which is used by the dictionary checking code. Change-Id: I16a7a8d23c7bd6a897e0da87c69f273be9103bd7 Reviewed-on: https://go-review.googlesource.com/c/go/+/323272 Trust: Keith Randall Trust: Dan Scales Run-TryBot: Keith Randall TryBot-Result: Go Bot Reviewed-by: Dan Scales --- src/cmd/compile/internal/noder/irgen.go | 2 + src/cmd/compile/internal/noder/stencil.go | 440 +++++++++++++++--- src/cmd/compile/internal/noder/transform.go | 74 ++- .../compile/internal/reflectdata/reflect.go | 151 +++++- src/cmd/compile/internal/typecheck/iexport.go | 10 +- src/cmd/compile/internal/typecheck/iimport.go | 7 + src/cmd/compile/internal/typecheck/subr.go | 13 +- src/cmd/compile/internal/types/type.go | 2 +- src/runtime/internal/atomic/atomic_arm64.go | 2 +- test/typeparam/dictionaryCapture.go | 100 ++++ 10 files changed, 688 insertions(+), 113 deletions(-) create mode 100644 test/typeparam/dictionaryCapture.go diff --git a/src/cmd/compile/internal/noder/irgen.go b/src/cmd/compile/internal/noder/irgen.go index d5ef0c0ef46..3f362e9d2bc 100644 --- a/src/cmd/compile/internal/noder/irgen.go +++ b/src/cmd/compile/internal/noder/irgen.go @@ -105,6 +105,8 @@ type irgen struct { // Fully-instantiated generic types whose methods should be instantiated instTypeList []*types.Type + + dnum int // for generating unique dictionary variables } func (g *irgen) generate(noders []*noder) { diff --git a/src/cmd/compile/internal/noder/stencil.go b/src/cmd/compile/internal/noder/stencil.go index 36a6f2e6d02..08c09c6fb13 100644 --- a/src/cmd/compile/internal/noder/stencil.go +++ b/src/cmd/compile/internal/noder/stencil.go @@ -10,9 +10,11 @@ package noder import ( "cmd/compile/internal/base" "cmd/compile/internal/ir" + "cmd/compile/internal/reflectdata" "cmd/compile/internal/typecheck" "cmd/compile/internal/types" "fmt" + "go/constant" "strings" ) @@ -70,72 +72,89 @@ func (g *irgen) stencil() { // instantiated function if it hasn't been created yet, and change // to calling that function directly. modified := false - foundFuncInst := false + closureRequired := false ir.Visit(decl, func(n ir.Node) { if n.Op() == ir.OFUNCINST { - // We found a function instantiation that is not - // immediately called. - foundFuncInst = true + // generic F, not immediately called + closureRequired = true } - if n.Op() != ir.OCALL || n.(*ir.CallExpr).X.Op() != ir.OFUNCINST { - return + if n.Op() == ir.OMETHEXPR && len(n.(*ir.SelectorExpr).X.Type().RParams()) > 0 { + // T.M, T a type which is generic, not immediately called + closureRequired = true } - // We have found a function call using a generic function - // instantiation. - call := n.(*ir.CallExpr) - inst := call.X.(*ir.InstExpr) - // Replace the OFUNCINST with a direct reference to the - // new stenciled function - st := g.getInstantiationForNode(inst) - call.X = st.Nname - if inst.X.Op() == ir.OCALLPART { - // When we create an instantiation of a method - // call, we make it a function. So, move the - // receiver to be the first arg of the function - // call. - withRecv := make([]ir.Node, len(call.Args)+1) - dot := inst.X.(*ir.SelectorExpr) - withRecv[0] = dot.X - copy(withRecv[1:], call.Args) - call.Args = withRecv + if n.Op() == ir.OCALL && n.(*ir.CallExpr).X.Op() == ir.OFUNCINST { + // We have found a function call using a generic function + // instantiation. + call := n.(*ir.CallExpr) + inst := call.X.(*ir.InstExpr) + st := g.getInstantiationForNode(inst) + // Replace the OFUNCINST with a direct reference to the + // new stenciled function + call.X = st.Nname + if inst.X.Op() == ir.OCALLPART { + // When we create an instantiation of a method + // call, we make it a function. So, move the + // receiver to be the first arg of the function + // call. + call.Args.Prepend(inst.X.(*ir.SelectorExpr).X) + } + // Add dictionary to argument list. + dict := reflectdata.GetDictionaryForInstantiation(inst) + call.Args.Prepend(dict) + // Transform the Call now, which changes OCALL + // to OCALLFUNC and does typecheckaste/assignconvfn. + transformCall(call) + modified = true + } + if n.Op() == ir.OCALLMETH && n.(*ir.CallExpr).X.Op() == ir.ODOTMETH && len(deref(n.(*ir.CallExpr).X.Type().Recv().Type).RParams()) > 0 { + // Method call on a generic type, which was instantiated by stenciling. + // Method calls on explicitly instantiated types will have an OFUNCINST + // and are handled above. + call := n.(*ir.CallExpr) + meth := call.X.(*ir.SelectorExpr) + targs := deref(meth.Type().Recv().Type).RParams() + + t := meth.X.Type() + baseSym := deref(t).OrigSym + baseType := baseSym.Def.(*ir.Name).Type() + var gf *ir.Name + for _, m := range baseType.Methods().Slice() { + if meth.Sel == m.Sym { + gf = m.Nname.(*ir.Name) + break + } + } + + st := g.getInstantiation(gf, targs, true) + call.SetOp(ir.OCALL) + call.X = st.Nname + dict := reflectdata.GetDictionaryForMethod(gf, targs) + call.Args.Prepend(dict, meth.X) + // Transform the Call now, which changes OCALL + // to OCALLFUNC and does typecheckaste/assignconvfn. + transformCall(call) + modified = true } - // Transform the Call now, which changes OCALL - // to OCALLFUNC and does typecheckaste/assignconvfn. - transformCall(call) - modified = true }) - // If we found an OFUNCINST without a corresponding call in the - // above decl, then traverse the nodes of decl again (with + // If we found a reference to a generic instantiation that wasn't an + // immediate call, then traverse the nodes of decl again (with // EditChildren rather than Visit), where we actually change the - // OFUNCINST node to an ONAME for the instantiated function. + // reference to the instantiation to a closure that captures the + // dictionary, then does a direct call. // EditChildren is more expensive than Visit, so we only do this // in the infrequent case of an OFUNCINST without a corresponding // call. - if foundFuncInst { + if closureRequired { var edit func(ir.Node) ir.Node edit = func(x ir.Node) ir.Node { - if x.Op() == ir.OFUNCINST { - // inst.X is either a function name node - // or a selector expression for a method. - inst := x.(*ir.InstExpr) - st := g.getInstantiationForNode(inst) - modified = true - if inst.X.Op() == ir.ONAME { - return st.Nname - } - assert(inst.X.Op() == ir.OCALLPART) - - // Return a new selector expression referring - // to the newly stenciled function. - oldse := inst.X.(*ir.SelectorExpr) - newse := ir.NewSelectorExpr(oldse.Pos(), ir.OCALLPART, oldse.X, oldse.Sel) - newse.Selection = types.NewField(oldse.Pos(), st.Sym(), st.Type()) - newse.Selection.Nname = st - typed(inst.Type(), newse) - return newse - } ir.EditChildren(x, edit) + switch { + case x.Op() == ir.OFUNCINST: + return g.buildClosure(decl.(*ir.Func), x) + case x.Op() == ir.OMETHEXPR && len(deref(x.(*ir.SelectorExpr).X.Type()).RParams()) > 0: // TODO: test for ptr-to-method case + return g.buildClosure(decl.(*ir.Func), x) + } return x } edit(decl) @@ -153,6 +172,228 @@ func (g *irgen) stencil() { } +// buildClosure makes a closure to implement x, a OFUNCINST or OMETHEXPR +// of generic type. outer is the containing function. +func (g *irgen) buildClosure(outer *ir.Func, x ir.Node) ir.Node { + pos := x.Pos() + var target *ir.Func // target instantiated function/method + var dictValue ir.Node // dictionary to use + var rcvrValue ir.Node // receiver, if a method value + typ := x.Type() // type of the closure + if x.Op() == ir.OFUNCINST { + inst := x.(*ir.InstExpr) + + // Type arguments we're instantiating with. + targs := typecheck.TypesOf(inst.Targs) + + // Find the generic function/method. + var gf *ir.Name + if inst.X.Op() == ir.ONAME { + // Instantiating a generic function call. + gf = inst.X.(*ir.Name) + } else if inst.X.Op() == ir.OCALLPART { + // Instantiating a method value x.M. + se := inst.X.(*ir.SelectorExpr) + rcvrValue = se.X + gf = se.Selection.Nname.(*ir.Name) + } else { + panic("unhandled") + } + + // target is the instantiated function we're trying to call. + // For functions, the target expects a dictionary as its first argument. + // For method values, the target expects a dictionary and the receiver + // as its first two arguments. + target = g.getInstantiation(gf, targs, rcvrValue != nil) + + // The value to use for the dictionary argument. + if rcvrValue == nil { + dictValue = reflectdata.GetDictionaryForFunc(gf, targs) + } else { + dictValue = reflectdata.GetDictionaryForMethod(gf, targs) + } + } else { // ir.OMETHEXPR + // Method expression T.M where T is a generic type. + // TODO: Is (*T).M right? + se := x.(*ir.SelectorExpr) + targs := se.X.Type().RParams() + if len(targs) == 0 { + if se.X.Type().IsPtr() { + targs = se.X.Type().Elem().RParams() + if len(targs) == 0 { + panic("bad") + } + } + } + t := se.X.Type() + baseSym := t.OrigSym + baseType := baseSym.Def.(*ir.Name).Type() + var gf *ir.Name + for _, m := range baseType.Methods().Slice() { + if se.Sel == m.Sym { + gf = m.Nname.(*ir.Name) + break + } + } + target = g.getInstantiation(gf, targs, true) + dictValue = reflectdata.GetDictionaryForMethod(gf, targs) + } + + // Build a closure to implement a function instantiation. + // + // func f[T any] (int, int) (int, int) { ...whatever... } + // + // Then any reference to f[int] not directly called gets rewritten to + // + // .dictN := ... dictionary to use ... + // func(a0, a1 int) (r0, r1 int) { + // return .inst.f[int](.dictN, a0, a1) + // } + // + // Similarly for method expressions, + // + // type g[T any] .... + // func (rcvr g[T]) f(a0, a1 int) (r0, r1 int) { ... } + // + // Any reference to g[int].f not directly called gets rewritten to + // + // .dictN := ... dictionary to use ... + // func(rcvr g[int], a0, a1 int) (r0, r1 int) { + // return .inst.g[int].f(.dictN, rcvr, a0, a1) + // } + // + // Also method values + // + // var x g[int] + // + // Any reference to x.f not directly called gets rewritten to + // + // .dictN := ... dictionary to use ... + // x2 := x + // func(a0, a1 int) (r0, r1 int) { + // return .inst.g[int].f(.dictN, x2, a0, a1) + // } + + // Make a new internal function. + fn := ir.NewFunc(pos) + fn.SetIsHiddenClosure(true) + + // This is the dictionary we want to use. + // Note: for now this is a compile-time constant, so we don't really need a closure + // to capture it (a wrapper function would work just as well). But eventually it + // will be a read of a subdictionary from the parent dictionary. + dictVar := ir.NewNameAt(pos, typecheck.LookupNum(".dict", g.dnum)) + g.dnum++ + dictVar.Class = ir.PAUTO + typed(types.Types[types.TUINTPTR], dictVar) + dictVar.Curfn = outer + dictAssign := ir.NewAssignStmt(pos, dictVar, dictValue) + dictAssign.SetTypecheck(1) + dictVar.Defn = dictAssign + outer.Dcl = append(outer.Dcl, dictVar) + + // assign the receiver to a temporary. + var rcvrVar *ir.Name + var rcvrAssign ir.Node + if rcvrValue != nil { + rcvrVar = ir.NewNameAt(pos, typecheck.LookupNum(".rcvr", g.dnum)) + g.dnum++ + rcvrVar.Class = ir.PAUTO + typed(rcvrValue.Type(), rcvrVar) + rcvrVar.Curfn = outer + rcvrAssign = ir.NewAssignStmt(pos, rcvrVar, rcvrValue) + rcvrAssign.SetTypecheck(1) + rcvrVar.Defn = rcvrAssign + outer.Dcl = append(outer.Dcl, rcvrVar) + } + + // Build formal argument and return lists. + var formalParams []*types.Field // arguments of closure + var formalResults []*types.Field // returns of closure + for i := 0; i < typ.NumParams(); i++ { + t := typ.Params().Field(i).Type + arg := ir.NewNameAt(pos, typecheck.LookupNum("a", i)) + arg.Class = ir.PPARAM + typed(t, arg) + arg.Curfn = fn + fn.Dcl = append(fn.Dcl, arg) + f := types.NewField(pos, arg.Sym(), t) + f.Nname = arg + formalParams = append(formalParams, f) + } + for i := 0; i < typ.NumResults(); i++ { + t := typ.Results().Field(i).Type + result := ir.NewNameAt(pos, typecheck.LookupNum("r", i)) // TODO: names not needed? + result.Class = ir.PPARAMOUT + typed(t, result) + result.Curfn = fn + fn.Dcl = append(fn.Dcl, result) + f := types.NewField(pos, result.Sym(), t) + f.Nname = result + formalResults = append(formalResults, f) + } + + // Build an internal function with the right signature. + closureType := types.NewSignature(x.Type().Pkg(), nil, nil, formalParams, formalResults) + sym := typecheck.ClosureName(outer) + sym.SetFunc(true) + fn.Nname = ir.NewNameAt(pos, sym) + fn.Nname.Func = fn + fn.Nname.Defn = fn + typed(closureType, fn.Nname) + fn.SetTypecheck(1) + + // Build body of closure. This involves just calling the wrapped function directly + // with the additional dictionary argument. + + // First, capture the dictionary variable for use in the closure. + dict2Var := ir.CaptureName(pos, fn, dictVar) + // Also capture the receiver variable. + var rcvr2Var *ir.Name + if rcvrValue != nil { + rcvr2Var = ir.CaptureName(pos, fn, rcvrVar) + } + + // Build arguments to call inside the closure. + var args []ir.Node + + // First the dictionary argument. + args = append(args, dict2Var) + // Then the receiver. + if rcvrValue != nil { + args = append(args, rcvr2Var) + } + // Then all the other arguments (including receiver for method expressions). + for i := 0; i < typ.NumParams(); i++ { + args = append(args, formalParams[i].Nname.(*ir.Name)) + } + + // Build call itself. + var innerCall ir.Node = ir.NewCallExpr(pos, ir.OCALL, target.Nname, args) + if len(formalResults) > 0 { + innerCall = ir.NewReturnStmt(pos, []ir.Node{innerCall}) + } + // Finish building body of closure. + ir.CurFunc = fn + // TODO: set types directly here instead of using typecheck.Stmt + typecheck.Stmt(innerCall) + ir.CurFunc = nil + fn.Body = []ir.Node{innerCall} + + // We're all done with the captured dictionary (and receiver, for method values). + ir.FinishCaptureNames(pos, outer, fn) + + // Make a closure referencing our new internal function. + c := ir.NewClosureExpr(pos, fn) + init := []ir.Node{dictAssign} + if rcvrValue != nil { + init = append(init, rcvrAssign) + } + c.SetInit(init) + typed(x.Type(), c) + return c +} + // instantiateMethods instantiates all the methods of all fully-instantiated // generic types that have been added to g.instTypeList. func (g *irgen) instantiateMethods() { @@ -167,14 +408,17 @@ func (g *irgen) instantiateMethods() { // not be set on imported instantiated types. baseSym := typ.OrigSym baseType := baseSym.Def.(*ir.Name).Type() - for j, m := range typ.Methods().Slice() { - name := m.Nname.(*ir.Name) + for j, _ := range typ.Methods().Slice() { baseNname := baseType.Methods().Slice()[j].Nname.(*ir.Name) - // Note: we are breaking an invariant here: - // m.Nname is now not equal m.Nname.Func.Nname. - // m.Nname has the type of a method, whereas m.Nname.Func.Nname has - // the type of a function, since it is an function instantiation. - name.Func = g.getInstantiation(baseNname, typ.RParams(), true) + // Eagerly generate the instantiations that implement these methods. + // We don't use the instantiations here, just generate them (and any + // further instantiations those generate, etc.). + // Note that we don't set the Func for any methods on instantiated + // types. Their signatures don't match so that would be confusing. + // Direct method calls go directly to the instantiations, implemented above. + // Indirect method calls use wrappers generated in reflectcall. Those wrappers + // will use these instantiations if they are needed (for interface tables or reflection). + _ = g.getInstantiation(baseNname, typ.RParams(), true) } } g.instTypeList = nil @@ -287,10 +531,7 @@ func (g *irgen) genericSubst(newsym *types.Sym, nameNode *ir.Name, targs []*type vars: make(map[*ir.Name]*ir.Name), } - newf.Dcl = make([]*ir.Name, len(gf.Dcl)) - for i, n := range gf.Dcl { - newf.Dcl[i] = subst.localvar(n) - } + newf.Dcl = make([]*ir.Name, 0, len(gf.Dcl)+1) // Replace the types in the function signature. // Ugly: also, we have to insert the Name nodes of the parameters/results into @@ -298,18 +539,40 @@ func (g *irgen) genericSubst(newsym *types.Sym, nameNode *ir.Name, targs []*type // because it came via conversion from the types2 type. oldt := nameNode.Type() // We also transform a generic method type to the corresponding - // instantiated function type where the receiver is the first parameter. + // instantiated function type where the dictionary is the first parameter. + dictionarySym := types.LocalPkg.Lookup(".dict") + dictionaryType := types.Types[types.TUINTPTR] + dictionaryName := ir.NewNameAt(gf.Pos(), dictionarySym) + typed(dictionaryType, dictionaryName) + dictionaryName.Class = ir.PPARAM + dictionaryName.Curfn = newf + newf.Dcl = append(newf.Dcl, dictionaryName) + for _, n := range gf.Dcl { + if n.Sym().Name == ".dict" { + panic("already has dictionary") + } + newf.Dcl = append(newf.Dcl, subst.localvar(n)) + } + dictionaryArg := types.NewField(gf.Pos(), dictionarySym, dictionaryType) + dictionaryArg.Nname = dictionaryName + var args []*types.Field + args = append(args, dictionaryArg) + args = append(args, oldt.Recvs().FieldSlice()...) + args = append(args, oldt.Params().FieldSlice()...) newt := types.NewSignature(oldt.Pkg(), nil, nil, - subst.fields(ir.PPARAM, append(oldt.Recvs().FieldSlice(), oldt.Params().FieldSlice()...), newf.Dcl), + subst.fields(ir.PPARAM, args, newf.Dcl), subst.fields(ir.PPARAMOUT, oldt.Results().FieldSlice(), newf.Dcl)) - newf.Nname.SetType(newt) + typed(newt, newf.Nname) ir.MarkFunc(newf.Nname) newf.SetTypecheck(1) - newf.Nname.SetTypecheck(1) // Make sure name/type of newf is set before substituting the body. newf.Body = subst.list(gf.Body) + + // Add code to check that the dictionary is correct. + newf.Body.Prepend(g.checkDictionary(dictionaryName, targs)...) + ir.CurFunc = savef return newf @@ -334,6 +597,44 @@ func (subst *subster) localvar(name *ir.Name) *ir.Name { return m } +// checkDictionary returns code that does runtime consistency checks +// between the dictionary and the types it should contain. +func (g *irgen) checkDictionary(name *ir.Name, targs []*types.Type) (code []ir.Node) { + if false { + return // checking turned off + } + // TODO: when moving to GCshape, this test will become harder. Call into + // runtime to check the expected shape is correct? + pos := name.Pos() + // Convert dictionary to *[N]uintptr + d := ir.NewConvExpr(pos, ir.OCONVNOP, types.Types[types.TUNSAFEPTR], name) + d.SetTypecheck(1) + d = ir.NewConvExpr(pos, ir.OCONVNOP, types.NewArray(types.Types[types.TUINTPTR], int64(len(targs))).PtrTo(), d) + d.SetTypecheck(1) + + // Check that each type entry in the dictionary is correct. + for i, t := range targs { + want := reflectdata.TypePtr(t) + typed(types.Types[types.TUINTPTR], want) + deref := ir.NewStarExpr(pos, d) + typed(d.Type().Elem(), deref) + idx := ir.NewConstExpr(constant.MakeUint64(uint64(i)), name) // TODO: what to set orig to? + typed(types.Types[types.TUINTPTR], idx) + got := ir.NewIndexExpr(pos, deref, idx) + typed(types.Types[types.TUINTPTR], got) + cond := ir.NewBinaryExpr(pos, ir.ONE, want, got) + typed(types.Types[types.TBOOL], cond) + panicArg := ir.NewNilExpr(pos) + typed(types.NewInterface(types.LocalPkg, nil), panicArg) + then := ir.NewUnaryExpr(pos, ir.OPANIC, panicArg) + then.SetTypecheck(1) + x := ir.NewIfStmt(pos, cond, []ir.Node{then}, nil) + x.SetTypecheck(1) + code = append(code, x) + } + return +} + // node is like DeepCopy(), but substitutes ONAME nodes based on subst.vars, and // also descends into closures. It substitutes type arguments for type parameters // in all the new nodes. @@ -837,13 +1138,14 @@ func (subst *subster) typ(t *types.Type) *types.Type { t2 := subst.typ(f.Type) oldsym := f.Nname.Sym() newsym := typecheck.MakeInstName(oldsym, subst.targs, true) + // TODO: use newsym? var nname *ir.Name if newsym.Def != nil { nname = newsym.Def.(*ir.Name) } else { - nname = ir.NewNameAt(f.Pos, newsym) + nname = ir.NewNameAt(f.Pos, oldsym) nname.SetType(t2) - newsym.Def = nname + oldsym.Def = nname } newfields[i] = types.NewField(f.Pos, f.Sym, t2) newfields[i].Nname = nname diff --git a/src/cmd/compile/internal/noder/transform.go b/src/cmd/compile/internal/noder/transform.go index 2859089e69b..90d38fe5144 100644 --- a/src/cmd/compile/internal/noder/transform.go +++ b/src/cmd/compile/internal/noder/transform.go @@ -340,12 +340,12 @@ assignOK: } } -// Corresponds to typecheck.typecheckargs. +// Corresponds to, but slightly more general than, typecheck.typecheckargs. func transformArgs(n ir.InitNode) { var list []ir.Node switch n := n.(type) { default: - base.Fatalf("typecheckargs %+v", n.Op()) + base.Fatalf("transformArgs %+v", n.Op()) case *ir.CallExpr: list = n.Args if n.IsDDD { @@ -354,25 +354,31 @@ func transformArgs(n ir.InitNode) { case *ir.ReturnStmt: list = n.Results } - if len(list) != 1 { + + // Look to see if we have any multi-return functions as arguments. + extra := 0 + for _, arg := range list { + t := arg.Type() + if t.IsFuncArgStruct() { + num := t.Fields().Len() + if num <= 1 { + base.Fatalf("multi-return type with only %d parts", num) + } + extra += num - 1 + } + } + // If not, nothing to do. + if extra == 0 { return } - t := list[0].Type() - if t == nil || !t.IsFuncArgStruct() { - return - } - - // Rewrite f(g()) into t1, t2, ... = g(); f(t1, t2, ...). + // Rewrite f(..., g(), ...) into t1, ..., tN = g(); f(..., t1, ..., tN, ...). // Save n as n.Orig for fmt.go. if ir.Orig(n) == n { n.(ir.OrigNode).SetOrig(ir.SepCopy(n)) } - as := ir.NewAssignListStmt(base.Pos, ir.OAS2, nil, nil) - as.Rhs.Append(list...) - // If we're outside of function context, then this call will // be executed during the generated init function. However, // init.go hasn't yet created it. Instead, associate the @@ -382,27 +388,42 @@ func transformArgs(n ir.InitNode) { if static { ir.CurFunc = typecheck.InitTodoFunc } - list = nil - for _, f := range t.FieldSlice() { - t := typecheck.Temp(f.Type) - as.PtrInit().Append(ir.NewDecl(base.Pos, ir.ODCL, t)) - as.Lhs.Append(t) - list = append(list, t) + + // Expand multi-return function calls. + // The spec only allows a multi-return function as an argument + // if it is the only argument. This code must handle calls to + // stenciled generic functions which have extra arguments + // (like the dictionary) so it must handle a slightly more general + // cases, like f(n, g()) where g is multi-return. + newList := make([]ir.Node, 0, len(list)+extra) + for _, arg := range list { + t := arg.Type() + if t.IsFuncArgStruct() { + as := ir.NewAssignListStmt(base.Pos, ir.OAS2, nil, []ir.Node{arg}) + for _, f := range t.FieldSlice() { + t := typecheck.Temp(f.Type) + as.PtrInit().Append(ir.NewDecl(base.Pos, ir.ODCL, t)) + as.Lhs.Append(t) + newList = append(newList, t) + } + transformAssign(as, as.Lhs, as.Rhs) + as.SetTypecheck(1) + n.PtrInit().Append(as) + } else { + newList = append(newList, arg) + } } + if static { ir.CurFunc = nil } switch n := n.(type) { case *ir.CallExpr: - n.Args = list + n.Args = newList case *ir.ReturnStmt: - n.Results = list + n.Results = newList } - - transformAssign(as, as.Lhs, as.Rhs) - as.SetTypecheck(1) - n.PtrInit().Append(as) } // assignconvfn converts node n for assignment to type t. Corresponds to @@ -562,6 +583,11 @@ func transformDot(n *ir.SelectorExpr, isCall bool) ir.Node { if (n.Op() == ir.ODOTINTER || n.Op() == ir.ODOTMETH) && !isCall { n.SetOp(ir.OCALLPART) + if len(n.X.Type().RParams()) > 0 || n.X.Type().IsPtr() && len(n.X.Type().Elem().RParams()) > 0 { + // TODO: MethodValueWrapper needed for generics? + // Or did we successfully desugar all that at stencil time? + return n + } n.SetType(typecheck.MethodValueWrapper(n).Type()) } return n diff --git a/src/cmd/compile/internal/reflectdata/reflect.go b/src/cmd/compile/internal/reflectdata/reflect.go index d452d4f1942..604cec6096d 100644 --- a/src/cmd/compile/internal/reflectdata/reflect.go +++ b/src/cmd/compile/internal/reflectdata/reflect.go @@ -321,13 +321,6 @@ func methods(t *types.Type) []*typeSig { } typecheck.CalcMethods(mt) - // type stored in interface word - it := t - - if !types.IsDirectIface(it) { - it = types.NewPtr(t) - } - // make list of methods for t, // generating code if necessary. var ms []*typeSig @@ -355,8 +348,8 @@ func methods(t *types.Type) []*typeSig { sig := &typeSig{ name: f.Sym, - isym: methodWrapper(it, f), - tsym: methodWrapper(t, f), + isym: methodWrapper(t, f, true), + tsym: methodWrapper(t, f, false), type_: typecheck.NewMethodType(f.Type, t), mtype: typecheck.NewMethodType(f.Type, nil), } @@ -394,7 +387,7 @@ func imethods(t *types.Type) []*typeSig { // IfaceType.Method is not in the reflect data. // Generate the method body, so that compiled // code can refer to it. - methodWrapper(t, f) + methodWrapper(t, f, false) } return methods @@ -1765,7 +1758,28 @@ func CollectPTabs() { // // rcvr - U // method - M func (t T)(), a TFIELD type struct -func methodWrapper(rcvr *types.Type, method *types.Field) *obj.LSym { +// +// Also wraps methods on instantiated generic types for use in itab entries. +// For an instantiated generic type G[int], we generate wrappers like: +// G[int] pointer shaped: +// func (x G[int]) f(arg) { +// .inst.G[int].f(dictionary, x, arg) +// } +// G[int] not pointer shaped: +// func (x *G[int]) f(arg) { +// .inst.G[int].f(dictionary, *x, arg) +// } +// These wrappers are always fully stenciled. +func methodWrapper(rcvr *types.Type, method *types.Field, forItab bool) *obj.LSym { + orig := rcvr + if forItab && !types.IsDirectIface(rcvr) { + rcvr = rcvr.PtrTo() + } + generic := false + if !rcvr.IsInterface() && len(rcvr.RParams()) > 0 || rcvr.IsPtr() && len(rcvr.Elem().RParams()) > 0 { // TODO: right detection? + // TODO: check that we do the right thing when rcvr.IsInterface(). + generic = true + } newnam := ir.MethodSym(rcvr, method.Sym) lsym := newnam.Linksym() if newnam.Siggen() { @@ -1773,7 +1787,7 @@ func methodWrapper(rcvr *types.Type, method *types.Field) *obj.LSym { } newnam.SetSiggen(true) - if types.Identical(rcvr, method.Type.Recv().Type) { + if !generic && types.Identical(rcvr, method.Type.Recv().Type) { return lsym } @@ -1808,9 +1822,10 @@ func methodWrapper(rcvr *types.Type, method *types.Field) *obj.LSym { nthis := ir.AsNode(tfn.Type().Recv().Nname) methodrcvr := method.Type.Recv().Type + indirect := rcvr.IsPtr() && rcvr.Elem() == methodrcvr // generate nil pointer check for better error - if rcvr.IsPtr() && rcvr.Elem() == methodrcvr { + if indirect { // generating wrapper from *T to T. n := ir.NewIfStmt(base.Pos, nil, nil, nil) n.Cond = ir.NewBinaryExpr(base.Pos, ir.OEQ, nthis, typecheck.NodNil()) @@ -1832,7 +1847,7 @@ func methodWrapper(rcvr *types.Type, method *types.Field) *obj.LSym { // Disable tailcall for RegabiArgs for now. The IR does not connect the // arguments with the OTAILCALL node, and the arguments are not marshaled // correctly. - if !base.Flag.Cfg.Instrumenting && rcvr.IsPtr() && methodrcvr.IsPtr() && method.Embedded != 0 && !types.IsInterfaceMethod(method.Type) && !(base.Ctxt.Arch.Name == "ppc64le" && base.Ctxt.Flag_dynlink) && !buildcfg.Experiment.RegabiArgs { + if !base.Flag.Cfg.Instrumenting && rcvr.IsPtr() && methodrcvr.IsPtr() && method.Embedded != 0 && !types.IsInterfaceMethod(method.Type) && !(base.Ctxt.Arch.Name == "ppc64le" && base.Ctxt.Flag_dynlink) && !buildcfg.Experiment.RegabiArgs && !generic { // generate tail call: adjust pointer receiver and jump to embedded method. left := dot.X // skip final .M if !left.Type().IsPtr() { @@ -1843,8 +1858,44 @@ func methodWrapper(rcvr *types.Type, method *types.Field) *obj.LSym { fn.Body.Append(ir.NewTailCallStmt(base.Pos, method.Nname.(*ir.Name))) } else { fn.SetWrapper(true) // ignore frame for panic+recover matching - call := ir.NewCallExpr(base.Pos, ir.OCALL, dot, nil) - call.Args = ir.ParamNames(tfn.Type()) + var call *ir.CallExpr + if generic { + var args []ir.Node + var targs []*types.Type + if rcvr.IsPtr() { // TODO: correct condition? + targs = rcvr.Elem().RParams() + } else { + targs = rcvr.RParams() + } + if strings.HasPrefix(ir.MethodSym(orig, method.Sym).Name, ".inst.") { + fmt.Printf("%s\n", ir.MethodSym(orig, method.Sym).Name) + panic("multiple .inst.") + } + args = append(args, getDictionary(".inst."+ir.MethodSym(orig, method.Sym).Name, targs)) // TODO: remove .inst. + if indirect { + args = append(args, ir.NewStarExpr(base.Pos, nthis)) + } else { + args = append(args, nthis) + } + args = append(args, ir.ParamNames(tfn.Type())...) + + // TODO: Once we enter the gcshape world, we'll need a way to look up + // the stenciled implementation to use for this concrete type. Essentially, + // erase the concrete types and replace them with gc shape representatives. + sym := typecheck.MakeInstName(ir.MethodSym(methodrcvr, method.Sym), targs, true) + if sym.Def == nil { + // Currently we make sure that we have all the instantiations + // we need by generating them all in ../noder/stencil.go:instantiateMethods + // TODO: maybe there's a better, more incremental way to generate + // only the instantiations we need? + base.Fatalf("instantiation %s not found", sym.Name) + } + target := ir.AsNode(sym.Def) + call = ir.NewCallExpr(base.Pos, ir.OCALL, target, args) + } else { + call = ir.NewCallExpr(base.Pos, ir.OCALL, dot, nil) + call.Args = ir.ParamNames(tfn.Type()) + } call.IsDDD = tfn.Type().IsVariadic() if method.Type.NumResults() > 0 { ret := ir.NewReturnStmt(base.Pos, nil) @@ -1909,3 +1960,71 @@ func MarkUsedIfaceMethod(n *ir.CallExpr) { r.Add = InterfaceMethodOffset(ityp, midx) r.Type = objabi.R_USEIFACEMETHOD } + +// getDictionaryForInstantiation returns the dictionary that should be used for invoking +// the concrete instantiation described by inst. +func GetDictionaryForInstantiation(inst *ir.InstExpr) ir.Node { + targs := typecheck.TypesOf(inst.Targs) + if meth, ok := inst.X.(*ir.SelectorExpr); ok { + return GetDictionaryForMethod(meth.Selection.Nname.(*ir.Name), targs) + } + return GetDictionaryForFunc(inst.X.(*ir.Name), targs) +} + +func GetDictionaryForFunc(fn *ir.Name, targs []*types.Type) ir.Node { + return getDictionary(typecheck.MakeInstName(fn.Sym(), targs, false).Name, targs) +} +func GetDictionaryForMethod(meth *ir.Name, targs []*types.Type) ir.Node { + return getDictionary(typecheck.MakeInstName(meth.Sym(), targs, true).Name, targs) +} + +// getDictionary returns the dictionary for the given named generic function +// or method, with the given type arguments. +// TODO: pass a reference to the generic function instead? We might need +// that to look up protodictionaries. +func getDictionary(name string, targs []*types.Type) ir.Node { + if len(targs) == 0 { + base.Fatalf("%s should have type arguments", name) + } + + // The dictionary for this instantiation is named after the function + // and concrete types it is instantiated with. + // TODO: decouple this naming from the instantiation naming. The instantiation + // naming will be based on GC shapes, this naming must be fully stenciled. + if !strings.HasPrefix(name, ".inst.") { + base.Fatalf("%s should start in .inst.", name) + } + name = ".dict." + name[6:] + + // Get a symbol representing the dictionary. + sym := typecheck.Lookup(name) + + // Initialize the dictionary, if we haven't yet already. + if lsym := sym.Linksym(); len(lsym.P) == 0 { + off := 0 + // Emit an entry for each concrete type. + for _, t := range targs { + s := TypeLinksym(t) + off = objw.SymPtr(lsym, off, s, 0) + } + // TODO: subdictionaries + objw.Global(lsym, int32(off), obj.DUPOK|obj.RODATA) + } + + // Make a node referencing the dictionary symbol. + n := typecheck.NewName(sym) + n.SetType(types.Types[types.TUINTPTR]) // should probably be [...]uintptr, but doesn't really matter + n.SetTypecheck(1) + n.Class = ir.PEXTERN + sym.Def = n + + // Return the address of the dictionary. + np := typecheck.NodAddr(n) + // Note: treat dictionary pointers as uintptrs, so they aren't pointers + // with respect to GC. That saves on stack scanning work, write barriers, etc. + // We can get away with it because dictionaries are global variables. + // TODO: use a cast, or is typing directly ok? + np.SetType(types.Types[types.TUINTPTR]) + np.SetTypecheck(1) + return np +} diff --git a/src/cmd/compile/internal/typecheck/iexport.go b/src/cmd/compile/internal/typecheck/iexport.go index f49718d4428..d83f385fcbf 100644 --- a/src/cmd/compile/internal/typecheck/iexport.go +++ b/src/cmd/compile/internal/typecheck/iexport.go @@ -1904,6 +1904,14 @@ func (w *exportWriter) expr(n ir.Node) { w.op(ir.OEND) } + case ir.OLINKSYMOFFSET: + n := n.(*ir.LinksymOffsetExpr) + w.op(ir.OLINKSYMOFFSET) + w.pos(n.Pos()) + w.string(n.Linksym.Name) + w.uint64(uint64(n.Offset_)) + w.typ(n.Type()) + // unary expressions case ir.OPLUS, ir.ONEG, ir.OBITNOT, ir.ONOT, ir.ORECV: n := n.(*ir.UnaryExpr) @@ -2068,7 +2076,7 @@ func (w *exportWriter) localIdent(s *types.Sym) { } // TODO(mdempsky): Fix autotmp hack. - if i := strings.LastIndex(name, "."); i >= 0 && !strings.HasPrefix(name, ".autotmp_") { + if i := strings.LastIndex(name, "."); i >= 0 && !strings.HasPrefix(name, ".autotmp_") && !strings.HasPrefix(name, ".dict") { // TODO: just use autotmp names for dictionaries? base.Fatalf("unexpected dot in identifier: %v", name) } diff --git a/src/cmd/compile/internal/typecheck/iimport.go b/src/cmd/compile/internal/typecheck/iimport.go index cca14a0d919..4c31e47378a 100644 --- a/src/cmd/compile/internal/typecheck/iimport.go +++ b/src/cmd/compile/internal/typecheck/iimport.go @@ -1467,6 +1467,13 @@ func (r *importReader) node() ir.Node { n.Args.Append(r.exprList()...) return n + case ir.OLINKSYMOFFSET: + pos := r.pos() + name := r.string() + off := r.uint64() + typ := r.typ() + return ir.NewLinksymOffsetExpr(pos, Lookup(name).Linksym(), int64(off), typ) + // unary expressions case ir.OPLUS, ir.ONEG, ir.OBITNOT, ir.ONOT, ir.ORECV: n := ir.NewUnaryExpr(r.pos(), op, r.expr()) diff --git a/src/cmd/compile/internal/typecheck/subr.go b/src/cmd/compile/internal/typecheck/subr.go index 9eac802dab8..3e7799b35b2 100644 --- a/src/cmd/compile/internal/typecheck/subr.go +++ b/src/cmd/compile/internal/typecheck/subr.go @@ -901,6 +901,14 @@ func TypesOf(x []ir.Node) []*types.Type { // '(*genType[int,bool]).methodName' for methods func MakeInstName(fnsym *types.Sym, targs []*types.Type, hasBrackets bool) *types.Sym { b := bytes.NewBufferString("") + + // marker to distinguish generic instantiations from fully stenciled wrapper functions. + // Once we move to GC shape implementations, this prefix will not be necessary as the + // GC shape naming will distinguish them. + // e.g. f[8bytenonpointer] vs. f[int]. + // For now, we use .inst.f[int] vs. f[int]. + b.WriteString(".inst.") + name := fnsym.Name i := strings.Index(name, "[") assert(hasBrackets == (i >= 0)) @@ -924,10 +932,13 @@ func MakeInstName(fnsym *types.Sym, targs []*types.Type, hasBrackets bool) *type } b.WriteString("]") if i >= 0 { - i2 := strings.Index(name[i:], "]") + i2 := strings.LastIndex(name[i:], "]") assert(i2 >= 0) b.WriteString(name[i+i2+1:]) } + if strings.HasPrefix(b.String(), ".inst..inst.") { + panic(fmt.Sprintf("multiple .inst. prefix in %s", b.String())) + } return fnsym.Pkg.Lookup(b.String()) } diff --git a/src/cmd/compile/internal/types/type.go b/src/cmd/compile/internal/types/type.go index 08855f518ca..7a05230a78e 100644 --- a/src/cmd/compile/internal/types/type.go +++ b/src/cmd/compile/internal/types/type.go @@ -958,7 +958,7 @@ func (t *Type) FuncArgs() *Type { return t.Extra.(FuncArgs).T } -// IsFuncArgStruct reports whether t is a struct representing function parameters. +// IsFuncArgStruct reports whether t is a struct representing function parameters or results. func (t *Type) IsFuncArgStruct() bool { return t.kind == TSTRUCT && t.Extra.(*Struct).Funarg != FunargNone } diff --git a/src/runtime/internal/atomic/atomic_arm64.go b/src/runtime/internal/atomic/atomic_arm64.go index 3c8736997f4..dbb1796ec09 100644 --- a/src/runtime/internal/atomic/atomic_arm64.go +++ b/src/runtime/internal/atomic/atomic_arm64.go @@ -8,8 +8,8 @@ package atomic import ( - "unsafe" "internal/cpu" + "unsafe" ) const ( diff --git a/test/typeparam/dictionaryCapture.go b/test/typeparam/dictionaryCapture.go new file mode 100644 index 00000000000..9ce7c540ca4 --- /dev/null +++ b/test/typeparam/dictionaryCapture.go @@ -0,0 +1,100 @@ +// run -gcflags=-G=3 + +// 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. + +// Test situations where functions/methods are not +// immediately called and we need to capture the dictionary +// required for later invocation. + +// TODO: copy this test file, add -l to gcflags. + +package main + +func main() { + functions() + methodExpressions() + methodValues() + interfaceMethods() +} + +func g0[T any](x T) { +} +func g1[T any](x T) T { + return x +} +func g2[T any](x T) (T, T) { + return x, x +} + +func functions() { + f0 := g0[int] + f0(7) + f1 := g1[int] + is7(f1(7)) + f2 := g2[int] + is77(f2(7)) +} + +func is7(x int) { + if x != 7 { + println(x) + panic("assertion failed") + } +} +func is77(x, y int) { + if x != 7 || y != 7 { + println(x,y) + panic("assertion failed") + } +} + +type s[T any] struct { + a T +} + +func (x s[T]) g0() { +} +func (x s[T]) g1() T { + return x.a +} +func (x s[T]) g2() (T, T) { + return x.a, x.a +} + +func methodExpressions() { + x := s[int]{a:7} + f0 := s[int].g0 + f0(x) + f1 := s[int].g1 + is7(f1(x)) + f2 := s[int].g2 + is77(f2(x)) +} + +func methodValues() { + x := s[int]{a:7} + f0 := x.g0 + f0() + f1 := x.g1 + is7(f1()) + f2 := x.g2 + is77(f2()) +} + +var x interface{ + g0() + g1()int + g2()(int,int) +} = s[int]{a:7} +var y interface{} = s[int]{a:7} + +func interfaceMethods() { + x.g0() + is7(x.g1()) + is77(x.g2()) + y.(interface{g0()}).g0() + is7(y.(interface{g1()int}).g1()) + is77(y.(interface{g2()(int,int)}).g2()) +}