From 6dec18cc75021bfbfac267941a567b257721208b Mon Sep 17 00:00:00 2001 From: Dan Scales Date: Mon, 28 Jun 2021 18:04:58 -0700 Subject: [PATCH] [dev.typeparams] cmd/compile: start using sub-dictionary entries where needed Added new struct instInfo for information about an instantiation (of a generic function/method with gcshapes or concrete types). We use this to remember the dictionary param node, the nodes where sub-dictionaries need to be used, etc. The instInfo map replaces the Stencil map in Package. Added code to access sub-dictionary entries at the appropriate call sites. We are currently still calculating the corresponding main dictionary, even when we really only need a sub-dictionary. I'll clean that up in a follow-up CL. Added code to deal with "generic" closures (closures that reference some generic variables/types). We decided that closures will share the same dictionary as the containing function (accessing the dictionary via a closure variable). So, the getGfInfo function now traverses all the nodes of each closure in a function that it is analyzing, so that a function's dictionary has all the entries needed for all its closures as well. Also, the instInfo of a closure is largely shared with its containing function. A good test for generic closures already exists with orderedmap.go. Other improvements: - Only create sub-dictionary entries when the function/method call/value or closure actually has type params in it. Added new test file subdict.go with an example where a generic method has an instantiated method call that does not depend not have type params. Change-Id: I691b9dc024a89d2305fcf1d8ba8540e53c9d103f Reviewed-on: https://go-review.googlesource.com/c/go/+/331516 Trust: Dan Scales Run-TryBot: Dan Scales TryBot-Result: Go Bot Reviewed-by: Keith Randall --- src/cmd/compile/internal/ir/package.go | 3 - src/cmd/compile/internal/noder/irgen.go | 32 +- src/cmd/compile/internal/noder/stencil.go | 344 +++++++++++++++------- test/typeparam/subdict.go | 42 +++ 4 files changed, 309 insertions(+), 112 deletions(-) create mode 100644 test/typeparam/subdict.go diff --git a/src/cmd/compile/internal/ir/package.go b/src/cmd/compile/internal/ir/package.go index e4b93d113e..3896e2b91b 100644 --- a/src/cmd/compile/internal/ir/package.go +++ b/src/cmd/compile/internal/ir/package.go @@ -32,7 +32,4 @@ type Package struct { // Exported (or re-exported) symbols. Exports []*Name - - // Map from function names of stencils to already-created stencils. - Stencils map[*types.Sym]*Func } diff --git a/src/cmd/compile/internal/noder/irgen.go b/src/cmd/compile/internal/noder/irgen.go index 8f39061250..299d468a15 100644 --- a/src/cmd/compile/internal/noder/irgen.go +++ b/src/cmd/compile/internal/noder/irgen.go @@ -100,11 +100,34 @@ func check2(noders []*noder) { type gfInfo struct { tparams []*types.Type derivedTypes []*types.Type - // Node in generic function that requires a subdictionary. Some of these - // are not function/method values (not strictly calls). + // Nodes in generic function that requires a subdictionary. Includes + // method and function calls (OCALL), function values (OFUNCINST), method + // values/expressions (OXDOT). subDictCalls []ir.Node } +// instInfo is information gathered on an gcshape (or fully concrete) +// instantiation of a function. +type instInfo struct { + fun *ir.Func // The instantiated function (with body) + dictParam *ir.Name // The node inside fun that refers to the dictionary param + // Addr of static dictionary associated with this instantiation. This is the + // dictionary you should pass if all the type args are concreate. Soon to be + // removed, when creating static dictionary and instantiated function are + // separated. + dictAddr ir.Node + + gf *ir.Name // The associated generic function + gfInfo *gfInfo + + startSubDict int // Start of dict entries for subdictionaries + dictLen int // Total number of entries in dictionary + + // Map from nodes in instantiated fun (OCALL, OCALLMETHOD, OFUNCINST, and + // OMETHEXPR) to the associated dictionary entry for a sub-dictionary + dictEntryMap map[ir.Node]int +} + type irgen struct { target *ir.Package self *types2.Package @@ -123,6 +146,11 @@ type irgen struct { // Map from generic function to information about its type params, derived // types, and subdictionaries. gfInfoMap map[*types.Sym]*gfInfo + + // Map from a name of function that been instantiated to information about + // its instantiated function, associated generic function/method, and the + // mapping from IR nodes to dictionary entries. + instInfoMap map[*types.Sym]*instInfo } 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 c04300a165..9d70e0e299 100644 --- a/src/cmd/compile/internal/noder/stencil.go +++ b/src/cmd/compile/internal/noder/stencil.go @@ -44,7 +44,7 @@ func infoPrint(format string, a ...interface{}) { // encountered already or new ones that are encountered during the stenciling // process. func (g *irgen) stencil() { - g.target.Stencils = make(map[*types.Sym]*ir.Func) + g.instInfoMap = make(map[*types.Sym]*instInfo) g.gfInfoMap = make(map[*types.Sym]*gfInfo) // Instantiate the methods of instantiated generic types that we have seen so far. @@ -86,6 +86,9 @@ func (g *irgen) stencil() { // to calling that function directly. modified := false closureRequired := false + // declInfo will be non-nil exactly if we are scanning an instantiated function + declInfo := g.instInfoMap[decl.Sym()] + ir.Visit(decl, func(n ir.Node) { if n.Op() == ir.OFUNCINST { // generic F, not immediately called @@ -103,11 +106,24 @@ func (g *irgen) stencil() { call := n.(*ir.CallExpr) inst := call.X.(*ir.InstExpr) st, dict := g.getInstantiationForNode(inst) - if infoPrintMode && g.target.Stencils[decl.Sym()] == nil { + dictkind := "Main dictionary" + if declInfo != nil { + // Get the dictionary arg via sub-dictionary reference + entry, ok := declInfo.dictEntryMap[n] + // If the entry is not found, it must be that + // this node was did not have any type args + // that depend on type params, so we need a + // main dictionary, not a sub-dictionary. + if ok { + dict = getDictionaryEntry(n.Pos(), declInfo.dictParam, entry, declInfo.dictLen) + dictkind = "Sub-dictionary" + } + } + if infoPrintMode { if inst.X.Op() == ir.OMETHVALUE { - fmt.Printf("Main dictionary in %v at generic method call: %v - %v\n", decl, inst.X, call) + fmt.Printf("%s in %v at generic method call: %v - %v\n", dictkind, decl, inst.X, call) } else { - fmt.Printf("Main dictionary in %v at generic function call: %v - %v\n", decl, inst.X, call) + fmt.Printf("%s in %v at generic function call: %v - %v\n", dictkind, decl, inst.X, call) } } // Replace the OFUNCINST with a direct reference to the @@ -147,6 +163,17 @@ func (g *irgen) stencil() { } st, dict := g.getInstantiation(gf, targs, true) + entry, ok := declInfo.dictEntryMap[n] + // TODO: Not creating sub-dictionary entry for + // absDifference in absdiff.go yet. Unusual case, + // where there are different generic method + // implementations of Abs in absDifference. + if ok { + if infoPrintMode { + fmt.Printf("Sub-dictionary in %v at generic method call: %v\n", decl, call) + } + dict = getDictionaryEntry(n.Pos(), declInfo.dictParam, entry, declInfo.dictLen) + } call.SetOp(ir.OCALL) call.X = st.Nname call.Args.Prepend(dict, meth.X) @@ -175,8 +202,6 @@ func (g *irgen) stencil() { ir.EditChildren(x, edit) switch { case x.Op() == ir.OFUNCINST: - // TODO: only set outer!=nil if this instantiation uses - // a type parameter from outer. See comment in buildClosure. return g.buildClosure(outer, x) case x.Op() == ir.OMETHEXPR && len(deref(x.(*ir.SelectorExpr).X.Type()).RParams()) > 0 && !types.IsInterfaceMethod(x.(*ir.SelectorExpr).Selection.Type): // TODO: test for ptr-to-method case @@ -208,6 +233,11 @@ func (g *irgen) buildClosure(outer *ir.Func, x ir.Node) ir.Node { var dictValue ir.Node // dictionary to use var rcvrValue ir.Node // receiver, if a method value typ := x.Type() // type of the closure + var outerInfo *instInfo + if outer != nil { + outerInfo = g.instInfoMap[outer.Sym()] + } + usingSubdict := false if x.Op() == ir.OFUNCINST { inst := x.(*ir.InstExpr) @@ -234,11 +264,20 @@ func (g *irgen) buildClosure(outer *ir.Func, x ir.Node) ir.Node { // as its first two arguments. // dictValue is the value to use for the dictionary argument. target, dictValue = g.getInstantiation(gf, targs, rcvrValue != nil) - if infoPrintMode && (outer == nil || g.target.Stencils[outer.Sym()] == nil) { + dictkind := "Main dictionary" + if outerInfo != nil { + entry, ok := outerInfo.dictEntryMap[x] + if ok { + dictValue = getDictionaryEntry(x.Pos(), outerInfo.dictParam, entry, outerInfo.dictLen) + dictkind = "Sub-dictionary" + usingSubdict = true + } + } + if infoPrintMode { if rcvrValue == nil { - fmt.Printf("Main dictionary in %v for function value %v\n", outer, inst.X) + fmt.Printf("%s in %v for generic function value %v\n", dictkind, outer, inst.X) } else { - fmt.Printf("Main dictionary in %v for method value %v\n", outer, inst.X) + fmt.Printf("%s in %v for generic method value %v\n", dictkind, outer, inst.X) } } } else { // ir.OMETHEXPR @@ -270,8 +309,17 @@ func (g *irgen) buildClosure(outer *ir.Func, x ir.Node) ir.Node { } } target, dictValue = g.getInstantiation(gf, targs, true) - if infoPrintMode && (outer == nil || g.target.Stencils[outer.Sym()] == nil) { - fmt.Printf("Main dictionary in %v for method expression %v\n", outer, x) + dictkind := "Main dictionary" + if outerInfo != nil { + entry, ok := outerInfo.dictEntryMap[x] + if ok { + dictValue = getDictionaryEntry(x.Pos(), outerInfo.dictParam, entry, outerInfo.dictLen) + dictkind = "Sub-dictionary" + usingSubdict = true + } + } + if infoPrintMode { + fmt.Printf("%s in %v for method expression %v\n", dictkind, outer, x) } } @@ -386,14 +434,12 @@ func (g *irgen) buildClosure(outer *ir.Func, x ir.Node) ir.Node { // First, figure out the dictionary argument. var dict2Var ir.Node - if outer != nil { - // If there's an outer function, the dictionary value will be read from - // the dictionary of the outer function. - // TODO: only use a subdictionary if any of the instantiating types - // depend on the type params of the outer function. + if usingSubdict { + // Capture sub-dictionary calculated in the outer function dict2Var = ir.CaptureName(pos, fn, dictVar) + typed(types.Types[types.TUINTPTR], dict2Var) } else { - // No outer function, instantiating types are known concrete types. + // Static dictionary, so can be used directly in the closure dict2Var = dictValue } // Also capture the receiver variable. @@ -695,8 +741,8 @@ func (g *irgen) getInstantiation(nameNode *ir.Name, targs []*types.Type, isMeth nameNode.Func.Dcl = nameNode.Func.Inl.Dcl } sym := typecheck.MakeInstName(nameNode.Sym(), targs, isMeth) - st := g.target.Stencils[sym] - if st == nil { + info := g.instInfoMap[sym] + if info == nil { if false { // Testing out gcshapeType() and gcshapeName() for i, t := range targs { @@ -706,27 +752,38 @@ func (g *irgen) getInstantiation(nameNode *ir.Name, targs []*types.Type, isMeth } // If instantiation doesn't exist yet, create it and add // to the list of decls. - st = g.genericSubst(sym, nameNode, targs, isMeth) + gfInfo := g.getGfInfo(nameNode) + info = &instInfo{ + gf: nameNode, + gfInfo: gfInfo, + startSubDict: len(targs) + len(gfInfo.derivedTypes), + dictLen: len(targs) + len(gfInfo.derivedTypes) + len(gfInfo.subDictCalls), + dictEntryMap: make(map[ir.Node]int), + } + // genericSubst fills in info.dictParam and info.dictEntryMap. + st := g.genericSubst(sym, nameNode, targs, isMeth, info) + info.fun = st + info.dictAddr = g.getDictionaryValue(nameNode, targs, isMeth) + g.instInfoMap[sym] = info // This ensures that the linker drops duplicates of this instantiation. // All just works! st.SetDupok(true) - g.target.Stencils[sym] = st g.target.Decls = append(g.target.Decls, st) if base.Flag.W > 1 { ir.Dump(fmt.Sprintf("\nstenciled %v", st), st) } } - return st, g.getDictionaryValue(nameNode, targs, isMeth) + return info.fun, info.dictAddr } // Struct containing info needed for doing the substitution as we create the // instantiation of a generic function with specified type arguments. type subster struct { - g *irgen - isMethod bool // If a method is being instantiated - newf *ir.Func // Func node for the new stenciled function - ts typecheck.Tsubster - dictionary *ir.Name // Name of dictionary variable + g *irgen + isMethod bool // If a method is being instantiated + newf *ir.Func // Func node for the new stenciled function + ts typecheck.Tsubster + info *instInfo // Place to put extra info in the instantiation } // genericSubst returns a new function with name newsym. The function is an @@ -734,8 +791,8 @@ type subster struct { // args targs. For a method with a generic receiver, it returns an instantiated // function type where the receiver becomes the first parameter. Otherwise the // instantiated method would still need to be transformed by later compiler -// phases. -func (g *irgen) genericSubst(newsym *types.Sym, nameNode *ir.Name, targs []*types.Type, isMethod bool) *ir.Func { +// phases. genericSubst fills in info.dictParam and info.dictEntryMap. +func (g *irgen) genericSubst(newsym *types.Sym, nameNode *ir.Name, targs []*types.Type, isMethod bool, info *instInfo) *ir.Func { var tparams []*types.Type if isMethod { // Get the type params from the method receiver (after skipping @@ -769,6 +826,7 @@ func (g *irgen) genericSubst(newsym *types.Sym, nameNode *ir.Name, targs []*type g: g, isMethod: isMethod, newf: newf, + info: info, ts: typecheck.Tsubster{ Tparams: tparams, Targs: targs, @@ -778,13 +836,7 @@ func (g *irgen) genericSubst(newsym *types.Sym, nameNode *ir.Name, targs []*type 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 - // the function type. The current function type has no Nname fields set, - // 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 dictionary is the first parameter. + // Create the needed dictionary param dictionarySym := newsym.Pkg.Lookup(".dict") dictionaryType := types.Types[types.TUINTPTR] dictionaryName := ir.NewNameAt(gf.Pos(), dictionarySym) @@ -800,11 +852,21 @@ func (g *irgen) genericSubst(newsym *types.Sym, nameNode *ir.Name, targs []*type } dictionaryArg := types.NewField(gf.Pos(), dictionarySym, dictionaryType) dictionaryArg.Nname = dictionaryName - subst.dictionary = dictionaryName + info.dictParam = dictionaryName + + // We add the dictionary as the first parameter in the function signature. + // We also transform a method type to the corresponding function type + // (make the receiver be the next parameter after the dictionary). + oldt := nameNode.Type() var args []*types.Field args = append(args, dictionaryArg) args = append(args, oldt.Recvs().FieldSlice()...) args = append(args, oldt.Params().FieldSlice()...) + + // Replace the types in the function signature via subst.fields. + // Ugly: also, we have to insert the Name nodes of the parameters/results into + // the function type. The current function type has no Nname fields set, + // because it came via conversion from the types2 type. newt := types.NewSignature(oldt.Pkg(), nil, nil, subst.fields(ir.PPARAM, args, newf.Dcl), subst.fields(ir.PPARAMOUT, oldt.Results().FieldSlice(), newf.Dcl)) @@ -884,6 +946,26 @@ func (g *irgen) checkDictionary(name *ir.Name, targs []*types.Type) (code []ir.N return } +// getDictionaryEntry gets the i'th entry in the dictionary dict. +func getDictionaryEntry(pos src.XPos, dict *ir.Name, i int, size int) ir.Node { + // Convert dictionary to *[N]uintptr + // All entries in the dictionary are pointers. They all point to static data, though, so we + // treat them as uintptrs so the GC doesn't need to keep track of them. + d := ir.NewConvExpr(pos, ir.OCONVNOP, types.Types[types.TUNSAFEPTR], dict) + d.SetTypecheck(1) + d = ir.NewConvExpr(pos, ir.OCONVNOP, types.NewArray(types.Types[types.TUINTPTR], int64(size)).PtrTo(), d) + d.SetTypecheck(1) + + // Load entry i out of the dictionary. + deref := ir.NewStarExpr(pos, d) + typed(d.Type().Elem(), deref) + idx := ir.NewConstExpr(constant.MakeUint64(uint64(i)), dict) // TODO: what to set orig to? + typed(types.Types[types.TUINTPTR], idx) + r := ir.NewIndexExpr(pos, deref, idx) + typed(types.Types[types.TUINTPTR], r) + return r +} + // getDictionaryType returns a *runtime._type from the dictionary corresponding to the input type. // The input type must be a type parameter (TODO: or a local derived type). func (subst *subster) getDictionaryType(pos src.XPos, t *types.Type) ir.Node { @@ -898,21 +980,10 @@ func (subst *subster) getDictionaryType(pos src.XPos, t *types.Type) ir.Node { base.Fatalf(fmt.Sprintf("couldn't find type param %+v", t)) } - // Convert dictionary to *[N]uintptr - // All entries in the dictionary are pointers. They all point to static data, though, so we - // treat them as uintptrs so the GC doesn't need to keep track of them. - d := ir.NewConvExpr(pos, ir.OCONVNOP, types.Types[types.TUNSAFEPTR], subst.dictionary) - d.SetTypecheck(1) - d = ir.NewConvExpr(pos, ir.OCONVNOP, types.NewArray(types.Types[types.TUINTPTR], int64(len(tparams))).PtrTo(), d) - d.SetTypecheck(1) - - // Load entry i out of the dictionary. - deref := ir.NewStarExpr(pos, d) - typed(d.Type().Elem(), deref) - idx := ir.NewConstExpr(constant.MakeUint64(uint64(i)), subst.dictionary) // TODO: what to set orig to? - typed(types.Types[types.TUINTPTR], idx) - r := ir.NewIndexExpr(pos, deref, idx) - typed(types.Types[types.TUINT8].PtrTo(), r) // standard typing of a *runtime._type in the compiler is *byte + r := getDictionaryEntry(pos, subst.info.dictParam, i, len(tparams)) + // change type of retrieved dictionary entry to *byte, which is the + // standard typing of a *runtime._type in the compiler + typed(types.Types[types.TUINT8].PtrTo(), r) return r } @@ -957,6 +1028,18 @@ func (subst *subster) node(n ir.Node) ir.Node { m.SetType(subst.ts.Typ(x.Type())) } } + + for i, de := range subst.info.gfInfo.subDictCalls { + if de == x { + // Remember the dictionary entry associated with this + // node in the instantiated function + // TODO: make sure this remains correct with respect to the + // transformations below. + subst.info.dictEntryMap[m] = subst.info.startSubDict + i + break + } + } + ir.EditChildren(m, edit) m.SetTypecheck(1) @@ -1109,7 +1192,26 @@ func (subst *subster) node(n ir.Node) ir.Node { ir.CurFunc = newfn subst.newf = newfn newfn.Dcl = subst.namelist(oldfn.Dcl) - newfn.ClosureVars = subst.namelist(oldfn.ClosureVars) + + // Make a closure variable for the dictionary of the + // containing function. + cdict := ir.CaptureName(oldfn.Pos(), newfn, subst.info.dictParam) + typed(types.Types[types.TUINTPTR], cdict) + ir.FinishCaptureNames(oldfn.Pos(), saveNewf, newfn) + newfn.ClosureVars = append(newfn.ClosureVars, subst.namelist(oldfn.ClosureVars)...) + + // Create inst info for the instantiated closure. The dict + // param is the closure variable for the dictionary of the + // outer function. Since the dictionary is shared, use the + // same entries for startSubDict, dictLen, dictEntryMap. + cinfo := &instInfo{ + fun: newfn, + dictParam: cdict, + startSubDict: subst.info.startSubDict, + dictLen: subst.info.dictLen, + dictEntryMap: subst.info.dictEntryMap, + } + subst.g.instInfoMap[newfn.Nname.Sym()] = cinfo typed(subst.ts.Typ(oldfn.Nname.Type()), newfn.Nname) typed(newfn.Nname.Type(), newfn.OClosure) @@ -1257,7 +1359,7 @@ func (g *irgen) getDictionarySym(gf *ir.Name, targs []*types.Type, isMeth bool) // Initialize the dictionary, if we haven't yet already. if lsym := sym.Linksym(); len(lsym.P) == 0 { - infoPrint("Creating dictionary %v\n", sym.Name) + infoPrint("=== Creating dictionary %v\n", sym.Name) off := 0 // Emit an entry for each targ (concrete type or gcshape). for _, t := range targs { @@ -1279,7 +1381,8 @@ func (g *irgen) getDictionarySym(gf *ir.Name, targs []*types.Type, isMeth bool) // Emit an entry for each subdictionary (after substituting targs) for _, n := range info.subDictCalls { var sym *types.Sym - if n.Op() == ir.OCALL { + switch n.Op() { + case ir.OCALL: call := n.(*ir.CallExpr) if call.X.Op() == ir.OXDOT { subtargs := deref(call.X.(*ir.SelectorExpr).X.Type()).RParams() @@ -1304,11 +1407,9 @@ func (g *irgen) getDictionarySym(gf *ir.Name, targs []*types.Type, isMeth bool) subtargs[i] = subst.Typ(t) } sym = g.getDictionarySym(nameNode, subtargs, isMeth) - // TODO: This can actually be a static - // main dictionary, if all of the subtargs - // are concrete types (!HasTParam) } - } else if n.Op() == ir.OFUNCINST { + + case ir.OFUNCINST: inst := n.(*ir.InstExpr) nameNode := inst.X.(*ir.Name) subtargs := typecheck.TypesOf(inst.Targs) @@ -1316,10 +1417,8 @@ func (g *irgen) getDictionarySym(gf *ir.Name, targs []*types.Type, isMeth bool) subtargs[i] = subst.Typ(t) } sym = g.getDictionarySym(nameNode, subtargs, false) - // TODO: This can actually be a static - // main dictionary, if all of the subtargs - // are concrete types (!HasTParam) - } else if n.Op() == ir.OXDOT { + + case ir.OXDOT: selExpr := n.(*ir.SelectorExpr) subtargs := selExpr.X.Type().RParams() s2targs := make([]*types.Type, len(subtargs)) @@ -1328,14 +1427,16 @@ func (g *irgen) getDictionarySym(gf *ir.Name, targs []*types.Type, isMeth bool) } nameNode := selExpr.Selection.Nname.(*ir.Name) sym = g.getDictionarySym(nameNode, s2targs, true) + + default: + assert(false) } - // TODO: handle closure cases that need sub-dictionaries, get rid of conditional - if sym != nil { - off = objw.SymPtr(lsym, off, sym.Linksym(), 0) - infoPrint(" - Subdict %v\n", sym.Name) - } + + off = objw.SymPtr(lsym, off, sym.Linksym(), 0) + infoPrint(" - Subdict %v\n", sym.Name) } objw.Global(lsym, int32(off), obj.DUPOK|obj.RODATA) + infoPrint("=== Done dictionary\n") // Add any new, fully instantiated types seen during the substitution to g.instTypeList. g.instTypeList = append(g.instTypeList, subst.InstTypeList...) @@ -1363,6 +1464,26 @@ func (g *irgen) getDictionaryValue(gf *ir.Name, targs []*types.Type, isMeth bool return np } +// hasTParamNodes returns true if the type of any node in targs has a typeparam. +func hasTParamNodes(targs []ir.Node) bool { + for _, n := range targs { + if n.Type().HasTParam() { + return true + } + } + return false +} + +// hasTParamNodes returns true if any type in targs has a typeparam. +func hasTParamTypes(targs []*types.Type) bool { + for _, t := range targs { + if t.HasTParam() { + return true + } + } + return false +} + // getGfInfo get information for a generic function - type params, derived generic // types, and subdictionaries. func (g *irgen) getGfInfo(gn *ir.Name) *gfInfo { @@ -1377,8 +1498,9 @@ func (g *irgen) getGfInfo(gn *ir.Name) *gfInfo { if recv != nil { info.tparams = deref(recv.Type).RParams() } else { - info.tparams = make([]*types.Type, len(gn.Type().TParams().FieldSlice())) - for i, f := range gn.Type().TParams().FieldSlice() { + tparams := gn.Type().TParams().FieldSlice() + info.tparams = make([]*types.Type, len(tparams)) + for i, f := range tparams { info.tparams[i] = f.Type } } @@ -1387,23 +1509,28 @@ func (g *irgen) getGfInfo(gn *ir.Name) *gfInfo { } if infoPrintMode { - fmt.Printf(">>> Info for %v\n", gn) + fmt.Printf(">>> GfInfo for %v\n", gn) for _, t := range info.tparams { fmt.Printf(" Typeparam %v\n", t) } - for _, t := range info.derivedTypes { - fmt.Printf(" Derived type %v\n", t) - } } - for _, stmt := range gf.Body { - ir.Visit(stmt, func(n ir.Node) { - if n.Op() == ir.OFUNCINST && !n.(*ir.InstExpr).Implicit() { + var visitFunc func(ir.Node) + visitFunc = func(n ir.Node) { + if n.Op() == ir.OFUNCINST && !n.(*ir.InstExpr).Implicit() { + if hasTParamNodes(n.(*ir.InstExpr).Targs) { infoPrint(" Closure&subdictionary required at generic function value %v\n", n.(*ir.InstExpr).X) info.subDictCalls = append(info.subDictCalls, n) - } else if n.Op() == ir.OXDOT && !n.(*ir.SelectorExpr).Implicit() && - n.(*ir.SelectorExpr).Selection != nil && - len(n.(*ir.SelectorExpr).X.Type().RParams()) > 0 { + } + } else if n.Op() == ir.OXDOT && !n.(*ir.SelectorExpr).Implicit() && + n.(*ir.SelectorExpr).Selection != nil && + len(n.(*ir.SelectorExpr).X.Type().RParams()) > 0 { + if n.(*ir.SelectorExpr).X.Op() == ir.OTYPE { + infoPrint(" Closure&subdictionary required at generic meth expr %v\n", n) + } else { + infoPrint(" Closure&subdictionary required at generic meth value %v\n", n) + } + if hasTParamTypes(deref(n.(*ir.SelectorExpr).X.Type()).RParams()) { if n.(*ir.SelectorExpr).X.Op() == ir.OTYPE { infoPrint(" Closure&subdictionary required at generic meth expr %v\n", n) } else { @@ -1411,40 +1538,43 @@ func (g *irgen) getGfInfo(gn *ir.Name) *gfInfo { } info.subDictCalls = append(info.subDictCalls, n) } - if n.Op() == ir.OCALL && n.(*ir.CallExpr).X.Op() == ir.OFUNCINST { - infoPrint(" Subdictionary at generic function call: %v - %v\n", n.(*ir.CallExpr).X.(*ir.InstExpr).X, n) - n.(*ir.CallExpr).X.(*ir.InstExpr).SetImplicit(true) + } + if n.Op() == ir.OCALL && n.(*ir.CallExpr).X.Op() == ir.OFUNCINST { + n.(*ir.CallExpr).X.(*ir.InstExpr).SetImplicit(true) + if hasTParamNodes(n.(*ir.CallExpr).X.(*ir.InstExpr).Targs) { + infoPrint(" Subdictionary at generic function/method call: %v - %v\n", n.(*ir.CallExpr).X.(*ir.InstExpr).X, n) info.subDictCalls = append(info.subDictCalls, n) } - if n.Op() == ir.OCALL && n.(*ir.CallExpr).X.Op() == ir.OXDOT && - n.(*ir.CallExpr).X.(*ir.SelectorExpr).Selection != nil && - len(deref(n.(*ir.CallExpr).X.(*ir.SelectorExpr).X.Type()).RParams()) > 0 { + } + if n.Op() == ir.OCALL && n.(*ir.CallExpr).X.Op() == ir.OXDOT && + n.(*ir.CallExpr).X.(*ir.SelectorExpr).Selection != nil && + len(deref(n.(*ir.CallExpr).X.(*ir.SelectorExpr).X.Type()).RParams()) > 0 { + n.(*ir.CallExpr).X.(*ir.SelectorExpr).SetImplicit(true) + if hasTParamTypes(deref(n.(*ir.CallExpr).X.(*ir.SelectorExpr).X.Type()).RParams()) { infoPrint(" Subdictionary at generic method call: %v\n", n) - n.(*ir.CallExpr).X.(*ir.SelectorExpr).SetImplicit(true) info.subDictCalls = append(info.subDictCalls, n) } - if n.Op() == ir.OCLOSURE { - oldfn := n.(*ir.ClosureExpr).Func - needDict := false - if oldfn.Nname.Type().HasTParam() { - needDict = true - infoPrint(" Subdictionary for closure that has generic params: %v\n", oldfn) - } else { - for _, cv := range oldfn.ClosureVars { - if cv.Type().HasTParam() { - needDict = true - infoPrint(" Subdictionary for closure that has generic capture: %v\n", oldfn) - break - } - } - } - if needDict { - info.subDictCalls = append(info.subDictCalls, n) - } + } + if n.Op() == ir.OCLOSURE { + // Visit the closure body and add all relevant entries to the + // dictionary of the outer function (closure will just use + // the dictionary of the outer function). + for _, n1 := range n.(*ir.ClosureExpr).Func.Body { + ir.Visit(n1, visitFunc) } + } - addType(&info, n, n.Type()) - }) + addType(&info, n, n.Type()) + } + + for _, stmt := range gf.Body { + ir.Visit(stmt, visitFunc) + } + if infoPrintMode { + for _, t := range info.derivedTypes { + fmt.Printf(" Derived type %v\n", t) + } + fmt.Printf(">>> Done Gfinfo\n") } g.gfInfoMap[gn.Sym()] = &info return &info diff --git a/test/typeparam/subdict.go b/test/typeparam/subdict.go new file mode 100644 index 0000000000..c899af6d0d --- /dev/null +++ b/test/typeparam/subdict.go @@ -0,0 +1,42 @@ +// 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 cases where a main dictionary is needed inside a generic function/method, because +// we are calling a method on a fully-instantiated type or a fully-instantiated function. +// (probably not common situations, of course) + +package main + +import ( + "fmt" +) + +type value[T comparable] struct { + val T +} + +func (v *value[T]) test(def T) bool { + return (v.val == def) +} + +func (v *value[T]) get(def T) T { + var c value[int] + if c.test(32) { + return def + } else if v.test(def) { + return def + } else { + return v.val + } +} + + +func main() { + var s value[string] + if got, want := s.get("ab"), ""; got != want { + panic(fmt.Sprintf("get() == %d, want %d", got, want)) + } +} -- 2.50.0