1
0
mirror of https://github.com/golang/go synced 2024-09-29 22:14:29 -06:00

cmd/compile: switch to computing dict format on instantiated functions

Change to computing the dictionary form on each shape-instantiated
function, rather than once on the underlying generic function/method.
The problem with computing the dictionary format on the generic function
is that we had to force early transformations to create all the
needed/implicit CONVIFACE nodes, since many of these nodes cause the
need for a dictionary entry. Also, the dictionary entries needed can
different with different instantiations of the same generic function,
especially depending on whether a type argument is a non-interface or
interface type, or a instantiated type vs. a non-instantiated type.

By computing the dictionary format on the instantiated function, we are
scanning a function where all the transformations have been done to
create implicit CONVFIFACE nodes, and we know the above relevant
information about the type params (which are shapes).

Much of the change is more mechanical changes from typeparams to shapes,
and generic functions/info to instantiated functions/info. Some of the
most important non-mechanical changes are:

 - Separated out the dictionary transformations to nodes into a separate
   dictPass, since we need to analyze instantiated functions after
   stenciling, but before the dictionary transformations.

 - Added type param index to shape types, since we need to be able
   distinguish type params of an instantiation which are different but
   happen to have the same shape.

 - Allow the type substituter to work with shapes again (since for the
   dictionary entries we need to substitute shape params to the concrete
   type args).

 - Support types.IdentityStrict() that does strict type comparison (no
   special case for shapes). This needed for type substitution,
   formatting and creating dictionaries, etc. We can maybe create better
   names for this function.

 - Add new information to instInfo to contain a mapping from the shape
   type params to their instantiated type bound. This is needed when
   doing the dictionary transformations related to type bounds.

Change-Id: I1c3ca312c5384f318c4dd7d0858dba9766396ff6
Reviewed-on: https://go-review.googlesource.com/c/go/+/349613
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-09-13 13:53:18 -07:00
parent 0edc6c4fa0
commit 59a9a035ff
5 changed files with 427 additions and 360 deletions

View File

@ -97,39 +97,42 @@ func check2(noders []*noder) {
} }
} }
// gfInfo is information gathered on a generic function. // dictInfo is the dictionary format for an instantiation of a generic function with
type gfInfo struct { // particular shapes. shapeParams, derivedTypes, subDictCalls, and itabConvs describe
tparams []*types.Type // the actual dictionary entries in order, and the remaining fields are other info
// needed in doing dictionary processing during compilation.
type dictInfo struct {
// Types substituted for the type parameters, which are shape types.
shapeParams []*types.Type
// All types derived from those typeparams used in the instantiation.
derivedTypes []*types.Type derivedTypes []*types.Type
// Nodes in generic function that requires a subdictionary. Includes // Nodes in the instantiation that requires a subdictionary. Includes
// method and function calls (OCALL), function values (OFUNCINST), method // method and function calls (OCALL), function values (OFUNCINST), method
// values/expressions (OXDOT). // values/expressions (OXDOT).
subDictCalls []ir.Node subDictCalls []ir.Node
// Nodes in generic functions that are a conversion from a typeparam/derived // Nodes in the instantiation that are a conversion from a typeparam/derived
// type to a specific interface. // type to a specific interface.
itabConvs []ir.Node itabConvs []ir.Node
// Mapping from each shape type that substitutes a type param, to its
// type bound (which is also substitued with shapes if it is parameterized)
shapeToBound map[*types.Type]*types.Type
// For type switches on nonempty interfaces, a map from OTYPE entries of // For type switches on nonempty interfaces, a map from OTYPE entries of
// HasTParam type, to the interface type we're switching from. // HasShape type, to the interface type we're switching from.
// TODO: what if the type we're switching from is a shape type?
type2switchType map[ir.Node]*types.Type type2switchType map[ir.Node]*types.Type
}
// 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
gf *ir.Name // The associated generic function
gfInfo *gfInfo
startSubDict int // Start of dict entries for subdictionaries startSubDict int // Start of dict entries for subdictionaries
startItabConv int // Start of dict entries for itab conversions startItabConv int // Start of dict entries for itab conversions
dictLen int // Total number of entries in dictionary dictLen int // Total number of entries in dictionary
}
// Map from nodes in instantiated fun (OCALL, OCALLMETHOD, OFUNCINST, and // instInfo is information gathered on an shape instantiation of a function.
// OMETHEXPR) to the associated dictionary entry for a sub-dictionary type instInfo struct {
dictEntryMap map[ir.Node]int fun *ir.Func // The instantiated function (with body)
dictParam *ir.Name // The node inside fun that refers to the dictionary param
dictInfo *dictInfo
} }
type irgen struct { type irgen struct {
@ -155,13 +158,8 @@ type irgen struct {
dnum int // for generating unique dictionary variables dnum int // for generating unique dictionary variables
// 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 // Map from a name of function that been instantiated to information about
// its instantiated function, associated generic function/method, and the // its instantiated function (including dictionary format).
// mapping from IR nodes to dictionary entries.
instInfoMap map[*types.Sym]*instInfo instInfoMap map[*types.Sym]*instInfo
// dictionary syms which we need to finish, by writing out any itabconv // dictionary syms which we need to finish, by writing out any itabconv
@ -183,6 +181,7 @@ type delayInfo struct {
targs []*types.Type targs []*types.Type
sym *types.Sym sym *types.Sym
off int off int
isMeth bool
} }
type typeDelayInfo struct { type typeDelayInfo struct {

File diff suppressed because it is too large Load Diff

View File

@ -1921,7 +1921,7 @@ func methodWrapper(rcvr *types.Type, method *types.Field, forItab bool) *obj.LSy
// Target method uses shaped names. // Target method uses shaped names.
targs2 := make([]*types.Type, len(targs)) targs2 := make([]*types.Type, len(targs))
for i, t := range targs { for i, t := range targs {
targs2[i] = typecheck.Shapify(t) targs2[i] = typecheck.Shapify(t, i)
} }
targs = targs2 targs = targs2

View File

@ -1019,10 +1019,11 @@ type Tsubster struct {
SubstForwFunc func(*types.Type) *types.Type SubstForwFunc func(*types.Type) *types.Type
} }
// Typ computes the type obtained by substituting any type parameter in t with the // Typ computes the type obtained by substituting any type parameter or shape in t
// corresponding type argument in subst. If t contains no type parameters, the // that appears in subst.Tparams with the corresponding type argument in subst.Targs.
// result is t; otherwise the result is a new type. It deals with recursive types // If t contains no type parameters, the result is t; otherwise the result is a new
// by using TFORW types and finding partially or fully created types via sym.Def. // type. It deals with recursive types by using TFORW types and finding partially or
// fully created types via sym.Def.
func (ts *Tsubster) Typ(t *types.Type) *types.Type { func (ts *Tsubster) Typ(t *types.Type) *types.Type {
// Defer the CheckSize calls until we have fully-defined // Defer the CheckSize calls until we have fully-defined
// (possibly-recursive) top-level type. // (possibly-recursive) top-level type.
@ -1033,14 +1034,14 @@ func (ts *Tsubster) Typ(t *types.Type) *types.Type {
} }
func (ts *Tsubster) typ1(t *types.Type) *types.Type { func (ts *Tsubster) typ1(t *types.Type) *types.Type {
if !t.HasTParam() && t.Kind() != types.TFUNC { if !t.HasTParam() && !t.HasShape() && t.Kind() != types.TFUNC {
// Note: function types need to be copied regardless, as the // Note: function types need to be copied regardless, as the
// types of closures may contain declarations that need // types of closures may contain declarations that need
// to be copied. See #45738. // to be copied. See #45738.
return t return t
} }
if t.IsTypeParam() { if t.IsTypeParam() || t.IsShape() {
for i, tp := range ts.Tparams { for i, tp := range ts.Tparams {
if tp == t { if tp == t {
return ts.Targs[i] return ts.Targs[i]
@ -1072,14 +1073,14 @@ func (ts *Tsubster) typ1(t *types.Type) *types.Type {
var targsChanged bool var targsChanged bool
var forw *types.Type var forw *types.Type
if t.Sym() != nil && t.HasTParam() { if t.Sym() != nil && (t.HasTParam() || t.HasShape()) {
// Need to test for t.HasTParam() again because of special TFUNC case above. // Need to test for t.HasTParam() again because of special TFUNC case above.
// Translate the type params for this type according to // Translate the type params for this type according to
// the tparam/targs mapping from subst. // the tparam/targs mapping from subst.
neededTargs = make([]*types.Type, len(t.RParams())) neededTargs = make([]*types.Type, len(t.RParams()))
for i, rparam := range t.RParams() { for i, rparam := range t.RParams() {
neededTargs[i] = ts.typ1(rparam) neededTargs[i] = ts.typ1(rparam)
if !types.Identical(neededTargs[i], rparam) { if !types.IdenticalStrict(neededTargs[i], rparam) {
targsChanged = true targsChanged = true
} }
} }
@ -1286,7 +1287,7 @@ func (ts *Tsubster) typ1(t *types.Type) *types.Type {
// fields, set force to true. // fields, set force to true.
func (ts *Tsubster) tstruct(t *types.Type, force bool) *types.Type { func (ts *Tsubster) tstruct(t *types.Type, force bool) *types.Type {
if t.NumFields() == 0 { if t.NumFields() == 0 {
if t.HasTParam() { if t.HasTParam() || t.HasShape() {
// For an empty struct, we need to return a new type, // For an empty struct, we need to return a new type,
// since it may now be fully instantiated (HasTParam // since it may now be fully instantiated (HasTParam
// becomes false). // becomes false).
@ -1388,19 +1389,20 @@ func genericTypeName(sym *types.Sym) string {
return sym.Name[0:strings.Index(sym.Name, "[")] return sym.Name[0:strings.Index(sym.Name, "[")]
} }
// Shapify takes a concrete type and returns a GCshape type that can // Shapify takes a concrete type and a type param index, and returns a GCshape type that can
// be used in place of the input type and still generate identical code. // be used in place of the input type and still generate identical code.
// No methods are added - all methods calls directly on a shape should // No methods are added - all methods calls directly on a shape should
// be done by converting to an interface using the dictionary. // be done by converting to an interface using the dictionary.
// //
// TODO: this could take the generic function and base its decisions // For now, we only consider two types to have the same shape, if they have exactly
// on how that generic function uses this type argument. For instance, // the same underlying type or they are both pointer types.
// if it doesn't use it as a function argument/return value, then //
// we don't need to distinguish int64 and float64 (because they only // Shape types are also distinguished by the index of the type in a type param/arg
// differ in how they get passed as arguments). For now, we only // list. We need to do this so we can distinguish and substitute properly for two
// unify two different types if they are identical in every possible way. // type params in the same function that have the same shape for a particular
func Shapify(t *types.Type) *types.Type { // instantiation.
assert(!t.HasShape()) func Shapify(t *types.Type, index int) *types.Type {
assert(!t.IsShape())
// Map all types with the same underlying type to the same shape. // Map all types with the same underlying type to the same shape.
u := t.Underlying() u := t.Underlying()
@ -1410,15 +1412,24 @@ func Shapify(t *types.Type) *types.Type {
u = types.Types[types.TUINT8].PtrTo() u = types.Types[types.TUINT8].PtrTo()
} }
if s := shaped[u]; s != nil { if shapeMap == nil {
shapeMap = map[int]map[*types.Type]*types.Type{}
}
submap := shapeMap[index]
if submap == nil {
submap = map[*types.Type]*types.Type{}
shapeMap[index] = submap
}
if s := submap[u]; s != nil {
return s return s
} }
sym := types.ShapePkg.Lookup(u.LinkString()) nm := fmt.Sprintf("%s_%d", u.LinkString(), index)
sym := types.ShapePkg.Lookup(nm)
if sym.Def != nil { if sym.Def != nil {
// Use any existing type with the same name // Use any existing type with the same name
shaped[u] = sym.Def.Type() submap[u] = sym.Def.Type()
return shaped[u] return submap[u]
} }
name := ir.NewDeclNameAt(u.Pos(), ir.OTYPE, sym) name := ir.NewDeclNameAt(u.Pos(), ir.OTYPE, sym)
s := types.NewNamed(name) s := types.NewNamed(name)
@ -1428,8 +1439,8 @@ func Shapify(t *types.Type) *types.Type {
s.SetHasShape(true) s.SetHasShape(true)
name.SetType(s) name.SetType(s)
name.SetTypecheck(1) name.SetTypecheck(1)
shaped[u] = s submap[u] = s
return s return s
} }
var shaped = map[*types.Type]*types.Type{} var shapeMap map[int]map[*types.Type]*types.Type

View File

@ -4,19 +4,30 @@
package types package types
const (
identIgnoreTags = 1 << iota
identStrict
)
// Identical reports whether t1 and t2 are identical types, following the spec rules. // Identical reports whether t1 and t2 are identical types, following the spec rules.
// Receiver parameter types are ignored. Named (defined) types are only equal if they // Receiver parameter types are ignored. Named (defined) types are only equal if they
// are pointer-equal - i.e. there must be a unique types.Type for each specific named // are pointer-equal - i.e. there must be a unique types.Type for each specific named
// type. Also, a type containing a shape type is considered identical to another type // type. Also, a type containing a shape type is considered identical to another type
// (shape or not) if their underlying types are the same, or they are both pointers. // (shape or not) if their underlying types are the same, or they are both pointers.
func Identical(t1, t2 *Type) bool { func Identical(t1, t2 *Type) bool {
return identical(t1, t2, true, nil) return identical(t1, t2, 0, nil)
} }
// IdenticalIgnoreTags is like Identical, but it ignores struct tags // IdenticalIgnoreTags is like Identical, but it ignores struct tags
// for struct identity. // for struct identity.
func IdenticalIgnoreTags(t1, t2 *Type) bool { func IdenticalIgnoreTags(t1, t2 *Type) bool {
return identical(t1, t2, false, nil) return identical(t1, t2, identIgnoreTags, nil)
}
// IdenticalStrict is like Identical, but matches types exactly, without the
// exception for shapes.
func IdenticalStrict(t1, t2 *Type) bool {
return identical(t1, t2, identStrict, nil)
} }
type typePair struct { type typePair struct {
@ -24,7 +35,7 @@ type typePair struct {
t2 *Type t2 *Type
} }
func identical(t1, t2 *Type, cmpTags bool, assumedEqual map[typePair]struct{}) bool { func identical(t1, t2 *Type, flags int, assumedEqual map[typePair]struct{}) bool {
if t1 == t2 { if t1 == t2 {
return true return true
} }
@ -32,7 +43,7 @@ func identical(t1, t2 *Type, cmpTags bool, assumedEqual map[typePair]struct{}) b
return false return false
} }
if t1.sym != nil || t2.sym != nil { if t1.sym != nil || t2.sym != nil {
if t1.HasShape() || t2.HasShape() { if flags&identStrict == 0 && (t1.HasShape() || t2.HasShape()) {
switch t1.kind { switch t1.kind {
case TINT8, TUINT8, TINT16, TUINT16, TINT32, TUINT32, TINT64, TUINT64, TINT, TUINT, TUINTPTR, TCOMPLEX64, TCOMPLEX128, TFLOAT32, TFLOAT64, TBOOL, TSTRING, TPTR, TUNSAFEPTR: case TINT8, TUINT8, TINT16, TUINT16, TINT32, TUINT32, TINT64, TUINT64, TINT, TUINT, TUINTPTR, TCOMPLEX64, TCOMPLEX128, TFLOAT32, TFLOAT64, TBOOL, TSTRING, TPTR, TUNSAFEPTR:
return true return true
@ -78,7 +89,7 @@ cont:
} }
for i, f1 := range t1.AllMethods().Slice() { for i, f1 := range t1.AllMethods().Slice() {
f2 := t2.AllMethods().Index(i) f2 := t2.AllMethods().Index(i)
if f1.Sym != f2.Sym || !identical(f1.Type, f2.Type, cmpTags, assumedEqual) { if f1.Sym != f2.Sym || !identical(f1.Type, f2.Type, flags, assumedEqual) {
return false return false
} }
} }
@ -90,10 +101,10 @@ cont:
} }
for i, f1 := range t1.FieldSlice() { for i, f1 := range t1.FieldSlice() {
f2 := t2.Field(i) f2 := t2.Field(i)
if f1.Sym != f2.Sym || f1.Embedded != f2.Embedded || !identical(f1.Type, f2.Type, cmpTags, assumedEqual) { if f1.Sym != f2.Sym || f1.Embedded != f2.Embedded || !identical(f1.Type, f2.Type, flags, assumedEqual) {
return false return false
} }
if cmpTags && f1.Note != f2.Note { if (flags&identIgnoreTags) == 0 && f1.Note != f2.Note {
return false return false
} }
} }
@ -111,7 +122,7 @@ cont:
} }
for i, f1 := range fs1 { for i, f1 := range fs1 {
f2 := fs2[i] f2 := fs2[i]
if f1.IsDDD() != f2.IsDDD() || !identical(f1.Type, f2.Type, cmpTags, assumedEqual) { if f1.IsDDD() != f2.IsDDD() || !identical(f1.Type, f2.Type, flags, assumedEqual) {
return false return false
} }
} }
@ -129,10 +140,10 @@ cont:
} }
case TMAP: case TMAP:
if !identical(t1.Key(), t2.Key(), cmpTags, assumedEqual) { if !identical(t1.Key(), t2.Key(), flags, assumedEqual) {
return false return false
} }
} }
return identical(t1.Elem(), t2.Elem(), cmpTags, assumedEqual) return identical(t1.Elem(), t2.Elem(), flags, assumedEqual)
} }