diff --git a/src/cmd/compile/internal/noder/irgen.go b/src/cmd/compile/internal/noder/irgen.go index d5ef0c0ef4..3f362e9d2b 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 36a6f2e6d0..08c09c6fb1 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 2859089e69..90d38fe514 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 d452d4f194..604cec6096 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 f49718d442..d83f385fcb 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 cca14a0d91..4c31e47378 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 9eac802dab..3e7799b35b 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 08855f518c..7a05230a78 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 3c8736997f..dbb1796ec0 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 0000000000..9ce7c540ca --- /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()) +}