Source file src/cmd/compile/internal/reflectdata/map_swiss.go

     1  // Copyright 2024 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package reflectdata
     6  
     7  import (
     8  	"cmd/compile/internal/base"
     9  	"cmd/compile/internal/ir"
    10  	"cmd/compile/internal/rttype"
    11  	"cmd/compile/internal/types"
    12  	"cmd/internal/obj"
    13  	"cmd/internal/objabi"
    14  	"cmd/internal/src"
    15  	"internal/abi"
    16  )
    17  
    18  // SwissMapGroupType makes the map slot group type given the type of the map.
    19  func SwissMapGroupType(t *types.Type) *types.Type {
    20  	if t.MapType().SwissGroup != nil {
    21  		return t.MapType().SwissGroup
    22  	}
    23  
    24  	// Builds a type representing a group structure for the given map type.
    25  	// This type is not visible to users, we include it so we can generate
    26  	// a correct GC program for it.
    27  	//
    28  	// Make sure this stays in sync with internal/runtime/maps/group.go.
    29  	//
    30  	// type group struct {
    31  	//     ctrl uint64
    32  	//     slots [abi.SwissMapGroupSlots]struct {
    33  	//         key  keyType
    34  	//         elem elemType
    35  	//     }
    36  	// }
    37  
    38  	keytype := t.Key()
    39  	elemtype := t.Elem()
    40  	types.CalcSize(keytype)
    41  	types.CalcSize(elemtype)
    42  	if keytype.Size() > abi.SwissMapMaxKeyBytes {
    43  		keytype = types.NewPtr(keytype)
    44  	}
    45  	if elemtype.Size() > abi.SwissMapMaxElemBytes {
    46  		elemtype = types.NewPtr(elemtype)
    47  	}
    48  
    49  	slotFields := []*types.Field{
    50  		makefield("key", keytype),
    51  		makefield("elem", elemtype),
    52  	}
    53  	slot := types.NewStruct(slotFields)
    54  	slot.SetNoalg(true)
    55  
    56  	slotArr := types.NewArray(slot, abi.SwissMapGroupSlots)
    57  	slotArr.SetNoalg(true)
    58  
    59  	fields := []*types.Field{
    60  		makefield("ctrl", types.Types[types.TUINT64]),
    61  		makefield("slots", slotArr),
    62  	}
    63  
    64  	group := types.NewStruct(fields)
    65  	group.SetNoalg(true)
    66  	types.CalcSize(group)
    67  
    68  	// Check invariants that map code depends on.
    69  	if !types.IsComparable(t.Key()) {
    70  		base.Fatalf("unsupported map key type for %v", t)
    71  	}
    72  	if group.Size() <= 8 {
    73  		// internal/runtime/maps creates pointers to slots, even if
    74  		// both key and elem are size zero. In this case, each slot is
    75  		// size 0, but group should still reserve a word of padding at
    76  		// the end to ensure pointers are valid.
    77  		base.Fatalf("bad group size for %v", t)
    78  	}
    79  	if t.Key().Size() > abi.SwissMapMaxKeyBytes && !keytype.IsPtr() {
    80  		base.Fatalf("key indirect incorrect for %v", t)
    81  	}
    82  	if t.Elem().Size() > abi.SwissMapMaxElemBytes && !elemtype.IsPtr() {
    83  		base.Fatalf("elem indirect incorrect for %v", t)
    84  	}
    85  
    86  	t.MapType().SwissGroup = group
    87  	group.StructType().Map = t
    88  	return group
    89  }
    90  
    91  var cachedSwissTableType *types.Type
    92  
    93  // swissTableType returns a type interchangeable with internal/runtime/maps.table.
    94  // Make sure this stays in sync with internal/runtime/maps/table.go.
    95  func swissTableType() *types.Type {
    96  	if cachedSwissTableType != nil {
    97  		return cachedSwissTableType
    98  	}
    99  
   100  	// type table struct {
   101  	//     used       uint16
   102  	//     capacity   uint16
   103  	//     growthLeft uint16
   104  	//     localDepth uint8
   105  	//     // N.B Padding
   106  	//
   107  	//     index int
   108  	//
   109  	//     // From groups.
   110  	//     groups_data       unsafe.Pointer
   111  	//     groups_lengthMask uint64
   112  	// }
   113  	// must match internal/runtime/maps/table.go:table.
   114  	fields := []*types.Field{
   115  		makefield("used", types.Types[types.TUINT16]),
   116  		makefield("capacity", types.Types[types.TUINT16]),
   117  		makefield("growthLeft", types.Types[types.TUINT16]),
   118  		makefield("localDepth", types.Types[types.TUINT8]),
   119  		makefield("index", types.Types[types.TINT]),
   120  		makefield("groups_data", types.Types[types.TUNSAFEPTR]),
   121  		makefield("groups_lengthMask", types.Types[types.TUINT64]),
   122  	}
   123  
   124  	n := ir.NewDeclNameAt(src.NoXPos, ir.OTYPE, ir.Pkgs.InternalMaps.Lookup("table"))
   125  	table := types.NewNamed(n)
   126  	n.SetType(table)
   127  	n.SetTypecheck(1)
   128  
   129  	table.SetUnderlying(types.NewStruct(fields))
   130  	types.CalcSize(table)
   131  
   132  	// The size of table should be 32 bytes on 64 bit
   133  	// and 24 bytes on 32 bit platforms.
   134  	if size := int64(3*2 + 2*1 /* one extra for padding */ + 1*8 + 2*types.PtrSize); table.Size() != size {
   135  		base.Fatalf("internal/runtime/maps.table size not correct: got %d, want %d", table.Size(), size)
   136  	}
   137  
   138  	cachedSwissTableType = table
   139  	return table
   140  }
   141  
   142  var cachedSwissMapType *types.Type
   143  
   144  // SwissMapType returns a type interchangeable with internal/runtime/maps.Map.
   145  // Make sure this stays in sync with internal/runtime/maps/map.go.
   146  func SwissMapType() *types.Type {
   147  	if cachedSwissMapType != nil {
   148  		return cachedSwissMapType
   149  	}
   150  
   151  	// type Map struct {
   152  	//     used uint64
   153  	//     seed uintptr
   154  	//
   155  	//     dirPtr unsafe.Pointer
   156  	//     dirLen int
   157  	//
   158  	//     globalDepth uint8
   159  	//     globalShift uint8
   160  	//
   161  	//     writing uint8
   162  	//     // N.B Padding
   163  	//
   164  	//     clearSeq uint64
   165  	// }
   166  	// must match internal/runtime/maps/map.go:Map.
   167  	fields := []*types.Field{
   168  		makefield("used", types.Types[types.TUINT64]),
   169  		makefield("seed", types.Types[types.TUINTPTR]),
   170  		makefield("dirPtr", types.Types[types.TUNSAFEPTR]),
   171  		makefield("dirLen", types.Types[types.TINT]),
   172  		makefield("globalDepth", types.Types[types.TUINT8]),
   173  		makefield("globalShift", types.Types[types.TUINT8]),
   174  		makefield("writing", types.Types[types.TUINT8]),
   175  		makefield("clearSeq", types.Types[types.TUINT64]),
   176  	}
   177  
   178  	n := ir.NewDeclNameAt(src.NoXPos, ir.OTYPE, ir.Pkgs.InternalMaps.Lookup("Map"))
   179  	m := types.NewNamed(n)
   180  	n.SetType(m)
   181  	n.SetTypecheck(1)
   182  
   183  	m.SetUnderlying(types.NewStruct(fields))
   184  	types.CalcSize(m)
   185  
   186  	// The size of Map should be 48 bytes on 64 bit
   187  	// and 32 bytes on 32 bit platforms.
   188  	if size := int64(2*8 + 4*types.PtrSize /* one extra for globalDepth/globalShift/writing + padding */); m.Size() != size {
   189  		base.Fatalf("internal/runtime/maps.Map size not correct: got %d, want %d", m.Size(), size)
   190  	}
   191  
   192  	cachedSwissMapType = m
   193  	return m
   194  }
   195  
   196  var cachedSwissIterType *types.Type
   197  
   198  // SwissMapIterType returns a type interchangeable with runtime.hiter.
   199  // Make sure this stays in sync with runtime/map.go.
   200  func SwissMapIterType() *types.Type {
   201  	if cachedSwissIterType != nil {
   202  		return cachedSwissIterType
   203  	}
   204  
   205  	// type Iter struct {
   206  	//    key  unsafe.Pointer // *Key
   207  	//    elem unsafe.Pointer // *Elem
   208  	//    typ  unsafe.Pointer // *SwissMapType
   209  	//    m    *Map
   210  	//
   211  	//    groupSlotOffset uint64
   212  	//    dirOffset       uint64
   213  	//
   214  	//    clearSeq uint64
   215  	//
   216  	//    globalDepth uint8
   217  	//    // N.B. padding
   218  	//
   219  	//    dirIdx int
   220  	//
   221  	//    tab *table
   222  	//
   223  	//    group unsafe.Pointer // actually groupReference.data
   224  	//
   225  	//    entryIdx uint64
   226  	// }
   227  	// must match internal/runtime/maps/table.go:Iter.
   228  	fields := []*types.Field{
   229  		makefield("key", types.Types[types.TUNSAFEPTR]),  // Used in range.go for TMAP.
   230  		makefield("elem", types.Types[types.TUNSAFEPTR]), // Used in range.go for TMAP.
   231  		makefield("typ", types.Types[types.TUNSAFEPTR]),
   232  		makefield("m", types.NewPtr(SwissMapType())),
   233  		makefield("groupSlotOffset", types.Types[types.TUINT64]),
   234  		makefield("dirOffset", types.Types[types.TUINT64]),
   235  		makefield("clearSeq", types.Types[types.TUINT64]),
   236  		makefield("globalDepth", types.Types[types.TUINT8]),
   237  		makefield("dirIdx", types.Types[types.TINT]),
   238  		makefield("tab", types.NewPtr(swissTableType())),
   239  		makefield("group", types.Types[types.TUNSAFEPTR]),
   240  		makefield("entryIdx", types.Types[types.TUINT64]),
   241  	}
   242  
   243  	// build iterator struct holding the above fields
   244  	n := ir.NewDeclNameAt(src.NoXPos, ir.OTYPE, ir.Pkgs.InternalMaps.Lookup("Iter"))
   245  	iter := types.NewNamed(n)
   246  	n.SetType(iter)
   247  	n.SetTypecheck(1)
   248  
   249  	iter.SetUnderlying(types.NewStruct(fields))
   250  	types.CalcSize(iter)
   251  
   252  	// The size of Iter should be 96 bytes on 64 bit
   253  	// and 64 bytes on 32 bit platforms.
   254  	if size := 8*types.PtrSize /* one extra for globalDepth + padding */ + 4*8; iter.Size() != int64(size) {
   255  		base.Fatalf("internal/runtime/maps.Iter size not correct: got %d, want %d", iter.Size(), size)
   256  	}
   257  
   258  	cachedSwissIterType = iter
   259  	return iter
   260  }
   261  
   262  func writeSwissMapType(t *types.Type, lsym *obj.LSym, c rttype.Cursor) {
   263  	// internal/abi.SwissMapType
   264  	gtyp := SwissMapGroupType(t)
   265  	s1 := writeType(t.Key())
   266  	s2 := writeType(t.Elem())
   267  	s3 := writeType(gtyp)
   268  	hasher := genhash(t.Key())
   269  
   270  	slotTyp := gtyp.Field(1).Type.Elem()
   271  	elemOff := slotTyp.Field(1).Offset
   272  	if AlgType(t.Key()) == types.AMEM64 && elemOff != 8 {
   273  		base.Fatalf("runtime assumes elemOff for 8-byte keys is 8, got %d", elemOff)
   274  	}
   275  	if AlgType(t.Key()) == types.ASTRING && elemOff != int64(2*types.PtrSize) {
   276  		base.Fatalf("runtime assumes elemOff for string keys is %d, got %d", 2*types.PtrSize, elemOff)
   277  	}
   278  
   279  	c.Field("Key").WritePtr(s1)
   280  	c.Field("Elem").WritePtr(s2)
   281  	c.Field("Group").WritePtr(s3)
   282  	c.Field("Hasher").WritePtr(hasher)
   283  	c.Field("GroupSize").WriteUintptr(uint64(gtyp.Size()))
   284  	c.Field("SlotSize").WriteUintptr(uint64(slotTyp.Size()))
   285  	c.Field("ElemOff").WriteUintptr(uint64(elemOff))
   286  	var flags uint32
   287  	if needkeyupdate(t.Key()) {
   288  		flags |= abi.SwissMapNeedKeyUpdate
   289  	}
   290  	if hashMightPanic(t.Key()) {
   291  		flags |= abi.SwissMapHashMightPanic
   292  	}
   293  	if t.Key().Size() > abi.SwissMapMaxKeyBytes {
   294  		flags |= abi.SwissMapIndirectKey
   295  	}
   296  	if t.Elem().Size() > abi.SwissMapMaxKeyBytes {
   297  		flags |= abi.SwissMapIndirectElem
   298  	}
   299  	c.Field("Flags").WriteUint32(flags)
   300  
   301  	if u := t.Underlying(); u != t {
   302  		// If t is a named map type, also keep the underlying map
   303  		// type live in the binary. This is important to make sure that
   304  		// a named map and that same map cast to its underlying type via
   305  		// reflection, use the same hash function. See issue 37716.
   306  		lsym.AddRel(base.Ctxt, obj.Reloc{Type: objabi.R_KEEP, Sym: writeType(u)})
   307  	}
   308  }
   309  

View as plain text