[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 <danscales@google.com>
Run-TryBot: Dan Scales <danscales@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Keith Randall <khr@golang.org>
This commit is contained in:
Dan Scales 2021-06-28 18:04:58 -07:00
parent a18726a648
commit 6dec18cc75
4 changed files with 309 additions and 112 deletions

View file

@ -32,7 +32,4 @@ type Package struct {
// Exported (or re-exported) symbols. // Exported (or re-exported) symbols.
Exports []*Name Exports []*Name
// Map from function names of stencils to already-created stencils.
Stencils map[*types.Sym]*Func
} }

View file

@ -100,11 +100,34 @@ func check2(noders []*noder) {
type gfInfo struct { type gfInfo struct {
tparams []*types.Type tparams []*types.Type
derivedTypes []*types.Type derivedTypes []*types.Type
// Node in generic function that requires a subdictionary. Some of these // Nodes in generic function that requires a subdictionary. Includes
// are not function/method values (not strictly calls). // method and function calls (OCALL), function values (OFUNCINST), method
// values/expressions (OXDOT).
subDictCalls []ir.Node 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 { type irgen struct {
target *ir.Package target *ir.Package
self *types2.Package self *types2.Package
@ -123,6 +146,11 @@ type irgen struct {
// Map from generic function to information about its type params, derived // Map from generic function to information about its type params, derived
// types, and subdictionaries. // types, and subdictionaries.
gfInfoMap map[*types.Sym]*gfInfo 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) { func (g *irgen) generate(noders []*noder) {

View file

@ -44,7 +44,7 @@ func infoPrint(format string, a ...interface{}) {
// encountered already or new ones that are encountered during the stenciling // encountered already or new ones that are encountered during the stenciling
// process. // process.
func (g *irgen) stencil() { 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) g.gfInfoMap = make(map[*types.Sym]*gfInfo)
// Instantiate the methods of instantiated generic types that we have seen so far. // 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. // to calling that function directly.
modified := false modified := false
closureRequired := 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) { ir.Visit(decl, func(n ir.Node) {
if n.Op() == ir.OFUNCINST { if n.Op() == ir.OFUNCINST {
// generic F, not immediately called // generic F, not immediately called
@ -103,11 +106,24 @@ func (g *irgen) stencil() {
call := n.(*ir.CallExpr) call := n.(*ir.CallExpr)
inst := call.X.(*ir.InstExpr) inst := call.X.(*ir.InstExpr)
st, dict := g.getInstantiationForNode(inst) 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 { 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 { } 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 // Replace the OFUNCINST with a direct reference to the
@ -147,6 +163,17 @@ func (g *irgen) stencil() {
} }
st, dict := g.getInstantiation(gf, targs, true) 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.SetOp(ir.OCALL)
call.X = st.Nname call.X = st.Nname
call.Args.Prepend(dict, meth.X) call.Args.Prepend(dict, meth.X)
@ -175,8 +202,6 @@ func (g *irgen) stencil() {
ir.EditChildren(x, edit) ir.EditChildren(x, edit)
switch { switch {
case x.Op() == ir.OFUNCINST: 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) return g.buildClosure(outer, x)
case x.Op() == ir.OMETHEXPR && len(deref(x.(*ir.SelectorExpr).X.Type()).RParams()) > 0 && 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 !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 dictValue ir.Node // dictionary to use
var rcvrValue ir.Node // receiver, if a method value var rcvrValue ir.Node // receiver, if a method value
typ := x.Type() // type of the closure 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 { if x.Op() == ir.OFUNCINST {
inst := x.(*ir.InstExpr) 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. // as its first two arguments.
// dictValue is the value to use for the dictionary argument. // dictValue is the value to use for the dictionary argument.
target, dictValue = g.getInstantiation(gf, targs, rcvrValue != nil) 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 { 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 { } 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 } 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) target, dictValue = g.getInstantiation(gf, targs, true)
if infoPrintMode && (outer == nil || g.target.Stencils[outer.Sym()] == nil) { dictkind := "Main dictionary"
fmt.Printf("Main dictionary in %v for method expression %v\n", outer, x) 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. // First, figure out the dictionary argument.
var dict2Var ir.Node var dict2Var ir.Node
if outer != nil { if usingSubdict {
// If there's an outer function, the dictionary value will be read from // Capture sub-dictionary calculated in the outer function
// 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.
dict2Var = ir.CaptureName(pos, fn, dictVar) dict2Var = ir.CaptureName(pos, fn, dictVar)
typed(types.Types[types.TUINTPTR], dict2Var)
} else { } else {
// No outer function, instantiating types are known concrete types. // Static dictionary, so can be used directly in the closure
dict2Var = dictValue dict2Var = dictValue
} }
// Also capture the receiver variable. // 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 nameNode.Func.Dcl = nameNode.Func.Inl.Dcl
} }
sym := typecheck.MakeInstName(nameNode.Sym(), targs, isMeth) sym := typecheck.MakeInstName(nameNode.Sym(), targs, isMeth)
st := g.target.Stencils[sym] info := g.instInfoMap[sym]
if st == nil { if info == nil {
if false { if false {
// Testing out gcshapeType() and gcshapeName() // Testing out gcshapeType() and gcshapeName()
for i, t := range targs { 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 // If instantiation doesn't exist yet, create it and add
// to the list of decls. // 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. // This ensures that the linker drops duplicates of this instantiation.
// All just works! // All just works!
st.SetDupok(true) st.SetDupok(true)
g.target.Stencils[sym] = st
g.target.Decls = append(g.target.Decls, st) g.target.Decls = append(g.target.Decls, st)
if base.Flag.W > 1 { if base.Flag.W > 1 {
ir.Dump(fmt.Sprintf("\nstenciled %v", st), st) 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 // Struct containing info needed for doing the substitution as we create the
// instantiation of a generic function with specified type arguments. // instantiation of a generic function with specified type arguments.
type subster struct { type subster struct {
g *irgen g *irgen
isMethod bool // If a method is being instantiated isMethod bool // If a method is being instantiated
newf *ir.Func // Func node for the new stenciled function newf *ir.Func // Func node for the new stenciled function
ts typecheck.Tsubster ts typecheck.Tsubster
dictionary *ir.Name // Name of dictionary variable info *instInfo // Place to put extra info in the instantiation
} }
// genericSubst returns a new function with name newsym. The function is an // 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 // args targs. For a method with a generic receiver, it returns an instantiated
// function type where the receiver becomes the first parameter. Otherwise the // function type where the receiver becomes the first parameter. Otherwise the
// instantiated method would still need to be transformed by later compiler // instantiated method would still need to be transformed by later compiler
// phases. // phases. genericSubst fills in info.dictParam and info.dictEntryMap.
func (g *irgen) genericSubst(newsym *types.Sym, nameNode *ir.Name, targs []*types.Type, isMethod bool) *ir.Func { func (g *irgen) genericSubst(newsym *types.Sym, nameNode *ir.Name, targs []*types.Type, isMethod bool, info *instInfo) *ir.Func {
var tparams []*types.Type var tparams []*types.Type
if isMethod { if isMethod {
// Get the type params from the method receiver (after skipping // 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, g: g,
isMethod: isMethod, isMethod: isMethod,
newf: newf, newf: newf,
info: info,
ts: typecheck.Tsubster{ ts: typecheck.Tsubster{
Tparams: tparams, Tparams: tparams,
Targs: targs, 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) newf.Dcl = make([]*ir.Name, 0, len(gf.Dcl)+1)
// Replace the types in the function signature. // Create the needed dictionary param
// 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.
dictionarySym := newsym.Pkg.Lookup(".dict") dictionarySym := newsym.Pkg.Lookup(".dict")
dictionaryType := types.Types[types.TUINTPTR] dictionaryType := types.Types[types.TUINTPTR]
dictionaryName := ir.NewNameAt(gf.Pos(), dictionarySym) 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 := types.NewField(gf.Pos(), dictionarySym, dictionaryType)
dictionaryArg.Nname = dictionaryName 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 var args []*types.Field
args = append(args, dictionaryArg) args = append(args, dictionaryArg)
args = append(args, oldt.Recvs().FieldSlice()...) args = append(args, oldt.Recvs().FieldSlice()...)
args = append(args, oldt.Params().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, newt := types.NewSignature(oldt.Pkg(), nil, nil,
subst.fields(ir.PPARAM, args, newf.Dcl), subst.fields(ir.PPARAM, args, newf.Dcl),
subst.fields(ir.PPARAMOUT, oldt.Results().FieldSlice(), 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 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. // 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). // 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 { 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)) base.Fatalf(fmt.Sprintf("couldn't find type param %+v", t))
} }
// Convert dictionary to *[N]uintptr r := getDictionaryEntry(pos, subst.info.dictParam, i, len(tparams))
// All entries in the dictionary are pointers. They all point to static data, though, so we // change type of retrieved dictionary entry to *byte, which is the
// treat them as uintptrs so the GC doesn't need to keep track of them. // standard typing of a *runtime._type in the compiler
d := ir.NewConvExpr(pos, ir.OCONVNOP, types.Types[types.TUNSAFEPTR], subst.dictionary) typed(types.Types[types.TUINT8].PtrTo(), r)
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
return r return r
} }
@ -957,6 +1028,18 @@ func (subst *subster) node(n ir.Node) ir.Node {
m.SetType(subst.ts.Typ(x.Type())) 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) ir.EditChildren(m, edit)
m.SetTypecheck(1) m.SetTypecheck(1)
@ -1109,7 +1192,26 @@ func (subst *subster) node(n ir.Node) ir.Node {
ir.CurFunc = newfn ir.CurFunc = newfn
subst.newf = newfn subst.newf = newfn
newfn.Dcl = subst.namelist(oldfn.Dcl) 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(subst.ts.Typ(oldfn.Nname.Type()), newfn.Nname)
typed(newfn.Nname.Type(), newfn.OClosure) 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. // Initialize the dictionary, if we haven't yet already.
if lsym := sym.Linksym(); len(lsym.P) == 0 { if lsym := sym.Linksym(); len(lsym.P) == 0 {
infoPrint("Creating dictionary %v\n", sym.Name) infoPrint("=== Creating dictionary %v\n", sym.Name)
off := 0 off := 0
// Emit an entry for each targ (concrete type or gcshape). // Emit an entry for each targ (concrete type or gcshape).
for _, t := range targs { 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) // Emit an entry for each subdictionary (after substituting targs)
for _, n := range info.subDictCalls { for _, n := range info.subDictCalls {
var sym *types.Sym var sym *types.Sym
if n.Op() == ir.OCALL { switch n.Op() {
case ir.OCALL:
call := n.(*ir.CallExpr) call := n.(*ir.CallExpr)
if call.X.Op() == ir.OXDOT { if call.X.Op() == ir.OXDOT {
subtargs := deref(call.X.(*ir.SelectorExpr).X.Type()).RParams() 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) subtargs[i] = subst.Typ(t)
} }
sym = g.getDictionarySym(nameNode, subtargs, isMeth) 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) inst := n.(*ir.InstExpr)
nameNode := inst.X.(*ir.Name) nameNode := inst.X.(*ir.Name)
subtargs := typecheck.TypesOf(inst.Targs) 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) subtargs[i] = subst.Typ(t)
} }
sym = g.getDictionarySym(nameNode, subtargs, false) sym = g.getDictionarySym(nameNode, subtargs, false)
// TODO: This can actually be a static
// main dictionary, if all of the subtargs case ir.OXDOT:
// are concrete types (!HasTParam)
} else if n.Op() == ir.OXDOT {
selExpr := n.(*ir.SelectorExpr) selExpr := n.(*ir.SelectorExpr)
subtargs := selExpr.X.Type().RParams() subtargs := selExpr.X.Type().RParams()
s2targs := make([]*types.Type, len(subtargs)) 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) nameNode := selExpr.Selection.Nname.(*ir.Name)
sym = g.getDictionarySym(nameNode, s2targs, true) 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)
off = objw.SymPtr(lsym, off, sym.Linksym(), 0) infoPrint(" - Subdict %v\n", sym.Name)
infoPrint(" - Subdict %v\n", sym.Name)
}
} }
objw.Global(lsym, int32(off), obj.DUPOK|obj.RODATA) 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. // Add any new, fully instantiated types seen during the substitution to g.instTypeList.
g.instTypeList = append(g.instTypeList, subst.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 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 // getGfInfo get information for a generic function - type params, derived generic
// types, and subdictionaries. // types, and subdictionaries.
func (g *irgen) getGfInfo(gn *ir.Name) *gfInfo { func (g *irgen) getGfInfo(gn *ir.Name) *gfInfo {
@ -1377,8 +1498,9 @@ func (g *irgen) getGfInfo(gn *ir.Name) *gfInfo {
if recv != nil { if recv != nil {
info.tparams = deref(recv.Type).RParams() info.tparams = deref(recv.Type).RParams()
} else { } else {
info.tparams = make([]*types.Type, len(gn.Type().TParams().FieldSlice())) tparams := gn.Type().TParams().FieldSlice()
for i, f := range gn.Type().TParams().FieldSlice() { info.tparams = make([]*types.Type, len(tparams))
for i, f := range tparams {
info.tparams[i] = f.Type info.tparams[i] = f.Type
} }
} }
@ -1387,23 +1509,28 @@ func (g *irgen) getGfInfo(gn *ir.Name) *gfInfo {
} }
if infoPrintMode { if infoPrintMode {
fmt.Printf(">>> Info for %v\n", gn) fmt.Printf(">>> GfInfo for %v\n", gn)
for _, t := range info.tparams { for _, t := range info.tparams {
fmt.Printf(" Typeparam %v\n", t) fmt.Printf(" Typeparam %v\n", t)
} }
for _, t := range info.derivedTypes {
fmt.Printf(" Derived type %v\n", t)
}
} }
for _, stmt := range gf.Body { var visitFunc func(ir.Node)
ir.Visit(stmt, func(n ir.Node) { visitFunc = func(n ir.Node) {
if n.Op() == ir.OFUNCINST && !n.(*ir.InstExpr).Implicit() { 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) infoPrint(" Closure&subdictionary required at generic function value %v\n", n.(*ir.InstExpr).X)
info.subDictCalls = append(info.subDictCalls, n) info.subDictCalls = append(info.subDictCalls, n)
} else if n.Op() == ir.OXDOT && !n.(*ir.SelectorExpr).Implicit() && }
n.(*ir.SelectorExpr).Selection != nil && } else if n.Op() == ir.OXDOT && !n.(*ir.SelectorExpr).Implicit() &&
len(n.(*ir.SelectorExpr).X.Type().RParams()) > 0 { 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 { if n.(*ir.SelectorExpr).X.Op() == ir.OTYPE {
infoPrint(" Closure&subdictionary required at generic meth expr %v\n", n) infoPrint(" Closure&subdictionary required at generic meth expr %v\n", n)
} else { } else {
@ -1411,40 +1538,43 @@ func (g *irgen) getGfInfo(gn *ir.Name) *gfInfo {
} }
info.subDictCalls = append(info.subDictCalls, n) 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) if n.Op() == ir.OCALL && n.(*ir.CallExpr).X.Op() == ir.OFUNCINST {
n.(*ir.CallExpr).X.(*ir.InstExpr).SetImplicit(true) 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) 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 && if n.Op() == ir.OCALL && n.(*ir.CallExpr).X.Op() == ir.OXDOT &&
len(deref(n.(*ir.CallExpr).X.(*ir.SelectorExpr).X.Type()).RParams()) > 0 { 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) infoPrint(" Subdictionary at generic method call: %v\n", n)
n.(*ir.CallExpr).X.(*ir.SelectorExpr).SetImplicit(true)
info.subDictCalls = append(info.subDictCalls, n) info.subDictCalls = append(info.subDictCalls, n)
} }
if n.Op() == ir.OCLOSURE { }
oldfn := n.(*ir.ClosureExpr).Func if n.Op() == ir.OCLOSURE {
needDict := false // Visit the closure body and add all relevant entries to the
if oldfn.Nname.Type().HasTParam() { // dictionary of the outer function (closure will just use
needDict = true // the dictionary of the outer function).
infoPrint(" Subdictionary for closure that has generic params: %v\n", oldfn) for _, n1 := range n.(*ir.ClosureExpr).Func.Body {
} else { ir.Visit(n1, visitFunc)
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)
}
} }
}
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 g.gfInfoMap[gn.Sym()] = &info
return &info return &info

42
test/typeparam/subdict.go Normal file
View file

@ -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))
}
}