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.
type gfInfo struct {
tparams []*types.Type
// dictInfo is the dictionary format for an instantiation of a generic function with
// particular shapes. shapeParams, derivedTypes, subDictCalls, and itabConvs describe
// 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
// 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
// values/expressions (OXDOT).
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.
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
// HasTParam type, to the interface type we're switching from.
// TODO: what if the type we're switching from is a shape type?
// HasShape type, to the interface type we're switching from.
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
startItabConv int // Start of dict entries for itab conversions
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
// instInfo is information gathered on an shape 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
dictInfo *dictInfo
}
type irgen struct {
@ -155,13 +158,8 @@ type irgen struct {
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
// its instantiated function, associated generic function/method, and the
// mapping from IR nodes to dictionary entries.
// its instantiated function (including dictionary format).
instInfoMap map[*types.Sym]*instInfo
// dictionary syms which we need to finish, by writing out any itabconv
@ -179,10 +177,11 @@ func (g *irgen) later(fn func()) {
}
type delayInfo struct {
gf *ir.Name
targs []*types.Type
sym *types.Sym
off int
gf *ir.Name
targs []*types.Type
sym *types.Sym
off int
isMeth bool
}
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.
targs2 := make([]*types.Type, len(targs))
for i, t := range targs {
targs2[i] = typecheck.Shapify(t)
targs2[i] = typecheck.Shapify(t, i)
}
targs = targs2

View file

@ -1019,10 +1019,11 @@ type Tsubster struct {
SubstForwFunc func(*types.Type) *types.Type
}
// Typ computes the type obtained by substituting any type parameter in t with the
// corresponding type argument in subst. If t contains no type parameters, the
// result is t; otherwise the result is a new type. It deals with recursive types
// by using TFORW types and finding partially or fully created types via sym.Def.
// Typ computes the type obtained by substituting any type parameter or shape in t
// that appears in subst.Tparams with the corresponding type argument in subst.Targs.
// If t contains no type parameters, the result is t; otherwise the result is a new
// 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 {
// Defer the CheckSize calls until we have fully-defined
// (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 {
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
// types of closures may contain declarations that need
// to be copied. See #45738.
return t
}
if t.IsTypeParam() {
if t.IsTypeParam() || t.IsShape() {
for i, tp := range ts.Tparams {
if tp == t {
return ts.Targs[i]
@ -1072,14 +1073,14 @@ func (ts *Tsubster) typ1(t *types.Type) *types.Type {
var targsChanged bool
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.
// Translate the type params for this type according to
// the tparam/targs mapping from subst.
neededTargs = make([]*types.Type, len(t.RParams()))
for i, rparam := range t.RParams() {
neededTargs[i] = ts.typ1(rparam)
if !types.Identical(neededTargs[i], rparam) {
if !types.IdenticalStrict(neededTargs[i], rparam) {
targsChanged = true
}
}
@ -1286,7 +1287,7 @@ func (ts *Tsubster) typ1(t *types.Type) *types.Type {
// fields, set force to true.
func (ts *Tsubster) tstruct(t *types.Type, force bool) *types.Type {
if t.NumFields() == 0 {
if t.HasTParam() {
if t.HasTParam() || t.HasShape() {
// For an empty struct, we need to return a new type,
// since it may now be fully instantiated (HasTParam
// becomes false).
@ -1388,19 +1389,20 @@ func genericTypeName(sym *types.Sym) string {
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.
// No methods are added - all methods calls directly on a shape should
// be done by converting to an interface using the dictionary.
//
// TODO: this could take the generic function and base its decisions
// on how that generic function uses this type argument. For instance,
// 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
// differ in how they get passed as arguments). For now, we only
// unify two different types if they are identical in every possible way.
func Shapify(t *types.Type) *types.Type {
assert(!t.HasShape())
// For now, we only consider two types to have the same shape, if they have exactly
// the same underlying type or they are both pointer types.
//
// Shape types are also distinguished by the index of the type in a type param/arg
// list. We need to do this so we can distinguish and substitute properly for two
// type params in the same function that have the same shape for a particular
// instantiation.
func Shapify(t *types.Type, index int) *types.Type {
assert(!t.IsShape())
// Map all types with the same underlying type to the same shape.
u := t.Underlying()
@ -1410,15 +1412,24 @@ func Shapify(t *types.Type) *types.Type {
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
}
sym := types.ShapePkg.Lookup(u.LinkString())
nm := fmt.Sprintf("%s_%d", u.LinkString(), index)
sym := types.ShapePkg.Lookup(nm)
if sym.Def != nil {
// Use any existing type with the same name
shaped[u] = sym.Def.Type()
return shaped[u]
submap[u] = sym.Def.Type()
return submap[u]
}
name := ir.NewDeclNameAt(u.Pos(), ir.OTYPE, sym)
s := types.NewNamed(name)
@ -1428,8 +1439,8 @@ func Shapify(t *types.Type) *types.Type {
s.SetHasShape(true)
name.SetType(s)
name.SetTypecheck(1)
shaped[u] = s
submap[u] = 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
const (
identIgnoreTags = 1 << iota
identStrict
)
// 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
// 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
// (shape or not) if their underlying types are the same, or they are both pointers.
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
// for struct identity.
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 {
@ -24,7 +35,7 @@ type typePair struct {
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 {
return true
}
@ -32,7 +43,7 @@ func identical(t1, t2 *Type, cmpTags bool, assumedEqual map[typePair]struct{}) b
return false
}
if t1.sym != nil || t2.sym != nil {
if t1.HasShape() || t2.HasShape() {
if flags&identStrict == 0 && (t1.HasShape() || t2.HasShape()) {
switch t1.kind {
case TINT8, TUINT8, TINT16, TUINT16, TINT32, TUINT32, TINT64, TUINT64, TINT, TUINT, TUINTPTR, TCOMPLEX64, TCOMPLEX128, TFLOAT32, TFLOAT64, TBOOL, TSTRING, TPTR, TUNSAFEPTR:
return true
@ -78,7 +89,7 @@ cont:
}
for i, f1 := range t1.AllMethods().Slice() {
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
}
}
@ -90,10 +101,10 @@ cont:
}
for i, f1 := range t1.FieldSlice() {
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
}
if cmpTags && f1.Note != f2.Note {
if (flags&identIgnoreTags) == 0 && f1.Note != f2.Note {
return false
}
}
@ -111,7 +122,7 @@ cont:
}
for i, f1 := range fs1 {
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
}
}
@ -129,10 +140,10 @@ cont:
}
case TMAP:
if !identical(t1.Key(), t2.Key(), cmpTags, assumedEqual) {
if !identical(t1.Key(), t2.Key(), flags, assumedEqual) {
return false
}
}
return identical(t1.Elem(), t2.Elem(), cmpTags, assumedEqual)
return identical(t1.Elem(), t2.Elem(), flags, assumedEqual)
}