blob: a6fab4cbac8f5d973637f7f8144230d66499c506 [file] [edit]
// Copyright 2024 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.
package reflectdata
import (
"internal/abi"
"cmd/compile/internal/base"
"cmd/compile/internal/ir"
"cmd/compile/internal/rttype"
"cmd/compile/internal/types"
"cmd/internal/obj"
"cmd/internal/objabi"
"cmd/internal/src"
)
// OldMapBucketType makes the map bucket type given the type of the map.
func OldMapBucketType(t *types.Type) *types.Type {
// Builds a type representing a Bucket structure for
// the given map type. This type is not visible to users -
// we include only enough information to generate a correct GC
// program for it.
// Make sure this stays in sync with runtime/map.go.
//
// A "bucket" is a "struct" {
// tophash [abi.OldMapBucketCount]uint8
// keys [abi.OldMapBucketCount]keyType
// elems [abi.OldMapBucketCount]elemType
// overflow *bucket
// }
if t.MapType().OldBucket != nil {
return t.MapType().OldBucket
}
keytype := t.Key()
elemtype := t.Elem()
types.CalcSize(keytype)
types.CalcSize(elemtype)
if keytype.Size() > abi.OldMapMaxKeyBytes {
keytype = types.NewPtr(keytype)
}
if elemtype.Size() > abi.OldMapMaxElemBytes {
elemtype = types.NewPtr(elemtype)
}
field := make([]*types.Field, 0, 5)
// The first field is: uint8 topbits[BUCKETSIZE].
arr := types.NewArray(types.Types[types.TUINT8], abi.OldMapBucketCount)
field = append(field, makefield("topbits", arr))
arr = types.NewArray(keytype, abi.OldMapBucketCount)
arr.SetNoalg(true)
keys := makefield("keys", arr)
field = append(field, keys)
arr = types.NewArray(elemtype, abi.OldMapBucketCount)
arr.SetNoalg(true)
elems := makefield("elems", arr)
field = append(field, elems)
// If keys and elems have no pointers, the map implementation
// can keep a list of overflow pointers on the side so that
// buckets can be marked as having no pointers.
// Arrange for the bucket to have no pointers by changing
// the type of the overflow field to uintptr in this case.
// See comment on hmap.overflow in runtime/map.go.
otyp := types.Types[types.TUNSAFEPTR]
if !elemtype.HasPointers() && !keytype.HasPointers() {
otyp = types.Types[types.TUINTPTR]
}
overflow := makefield("overflow", otyp)
field = append(field, overflow)
// link up fields
bucket := types.NewStruct(field[:])
bucket.SetNoalg(true)
types.CalcSize(bucket)
// Check invariants that map code depends on.
if !types.IsComparable(t.Key()) {
base.Fatalf("unsupported map key type for %v", t)
}
if abi.OldMapBucketCount < 8 {
base.Fatalf("bucket size %d too small for proper alignment %d", abi.OldMapBucketCount, 8)
}
if uint8(keytype.Alignment()) > abi.OldMapBucketCount {
base.Fatalf("key align too big for %v", t)
}
if uint8(elemtype.Alignment()) > abi.OldMapBucketCount {
base.Fatalf("elem align %d too big for %v, BUCKETSIZE=%d", elemtype.Alignment(), t, abi.OldMapBucketCount)
}
if keytype.Size() > abi.OldMapMaxKeyBytes {
base.Fatalf("key size too large for %v", t)
}
if elemtype.Size() > abi.OldMapMaxElemBytes {
base.Fatalf("elem size too large for %v", t)
}
if t.Key().Size() > abi.OldMapMaxKeyBytes && !keytype.IsPtr() {
base.Fatalf("key indirect incorrect for %v", t)
}
if t.Elem().Size() > abi.OldMapMaxElemBytes && !elemtype.IsPtr() {
base.Fatalf("elem indirect incorrect for %v", t)
}
if keytype.Size()%keytype.Alignment() != 0 {
base.Fatalf("key size not a multiple of key align for %v", t)
}
if elemtype.Size()%elemtype.Alignment() != 0 {
base.Fatalf("elem size not a multiple of elem align for %v", t)
}
if uint8(bucket.Alignment())%uint8(keytype.Alignment()) != 0 {
base.Fatalf("bucket align not multiple of key align %v", t)
}
if uint8(bucket.Alignment())%uint8(elemtype.Alignment()) != 0 {
base.Fatalf("bucket align not multiple of elem align %v", t)
}
if keys.Offset%keytype.Alignment() != 0 {
base.Fatalf("bad alignment of keys in bmap for %v", t)
}
if elems.Offset%elemtype.Alignment() != 0 {
base.Fatalf("bad alignment of elems in bmap for %v", t)
}
// Double-check that overflow field is final memory in struct,
// with no padding at end.
if overflow.Offset != bucket.Size()-int64(types.PtrSize) {
base.Fatalf("bad offset of overflow in bmap for %v, overflow.Offset=%d, bucket.Size()-int64(types.PtrSize)=%d",
t, overflow.Offset, bucket.Size()-int64(types.PtrSize))
}
t.MapType().OldBucket = bucket
bucket.StructType().Map = t
return bucket
}
var oldHmapType *types.Type
// OldMapType returns a type interchangeable with runtime.hmap.
// Make sure this stays in sync with runtime/map.go.
func OldMapType() *types.Type {
if oldHmapType != nil {
return oldHmapType
}
// build a struct:
// type hmap struct {
// count int
// flags uint8
// B uint8
// noverflow uint16
// hash0 uint32
// buckets unsafe.Pointer
// oldbuckets unsafe.Pointer
// nevacuate uintptr
// clearSeq uint64
// extra unsafe.Pointer // *mapextra
// }
// must match runtime/map.go:hmap.
fields := []*types.Field{
makefield("count", types.Types[types.TINT]),
makefield("flags", types.Types[types.TUINT8]),
makefield("B", types.Types[types.TUINT8]),
makefield("noverflow", types.Types[types.TUINT16]),
makefield("hash0", types.Types[types.TUINT32]), // Used in walk.go for OMAKEMAP.
makefield("buckets", types.Types[types.TUNSAFEPTR]), // Used in walk.go for OMAKEMAP.
makefield("oldbuckets", types.Types[types.TUNSAFEPTR]),
makefield("nevacuate", types.Types[types.TUINTPTR]),
makefield("clearSeq", types.Types[types.TUINT64]),
makefield("extra", types.Types[types.TUNSAFEPTR]),
}
n := ir.NewDeclNameAt(src.NoXPos, ir.OTYPE, ir.Pkgs.Runtime.Lookup("hmap"))
hmap := types.NewNamed(n)
n.SetType(hmap)
n.SetTypecheck(1)
hmap.SetUnderlying(types.NewStruct(fields))
types.CalcSize(hmap)
// The size of hmap should be 56 bytes on 64 bit
// and 36 bytes on 32 bit platforms.
if size := int64(2*8 + 5*types.PtrSize); hmap.Size() != size {
base.Fatalf("hmap size not correct: got %d, want %d", hmap.Size(), size)
}
oldHmapType = hmap
return hmap
}
var oldHiterType *types.Type
// OldMapIterType returns a type interchangeable with runtime.hiter.
// Make sure this stays in sync with runtime/map.go.
func OldMapIterType() *types.Type {
if oldHiterType != nil {
return oldHiterType
}
hmap := OldMapType()
// build a struct:
// type hiter struct {
// key unsafe.Pointer // *Key
// elem unsafe.Pointer // *Elem
// t unsafe.Pointer // *OldMapType
// h *hmap
// buckets unsafe.Pointer
// bptr unsafe.Pointer // *bmap
// overflow unsafe.Pointer // *[]*bmap
// oldoverflow unsafe.Pointer // *[]*bmap
// startBucket uintptr
// offset uint8
// wrapped bool
// B uint8
// i uint8
// bucket uintptr
// checkBucket uintptr
// clearSeq uint64
// }
// must match runtime/map.go:hiter.
fields := []*types.Field{
makefield("key", types.Types[types.TUNSAFEPTR]), // Used in range.go for TMAP.
makefield("elem", types.Types[types.TUNSAFEPTR]), // Used in range.go for TMAP.
makefield("t", types.Types[types.TUNSAFEPTR]),
makefield("h", types.NewPtr(hmap)),
makefield("buckets", types.Types[types.TUNSAFEPTR]),
makefield("bptr", types.Types[types.TUNSAFEPTR]),
makefield("overflow", types.Types[types.TUNSAFEPTR]),
makefield("oldoverflow", types.Types[types.TUNSAFEPTR]),
makefield("startBucket", types.Types[types.TUINTPTR]),
makefield("offset", types.Types[types.TUINT8]),
makefield("wrapped", types.Types[types.TBOOL]),
makefield("B", types.Types[types.TUINT8]),
makefield("i", types.Types[types.TUINT8]),
makefield("bucket", types.Types[types.TUINTPTR]),
makefield("checkBucket", types.Types[types.TUINTPTR]),
makefield("clearSeq", types.Types[types.TUINT64]),
}
// build iterator struct holding the above fields
n := ir.NewDeclNameAt(src.NoXPos, ir.OTYPE, ir.Pkgs.Runtime.Lookup("hiter"))
hiter := types.NewNamed(n)
n.SetType(hiter)
n.SetTypecheck(1)
hiter.SetUnderlying(types.NewStruct(fields))
types.CalcSize(hiter)
if hiter.Size() != int64(8+12*types.PtrSize) {
base.Fatalf("hash_iter size not correct %d %d", hiter.Size(), 8+12*types.PtrSize)
}
oldHiterType = hiter
return hiter
}
func writeOldMapType(t *types.Type, lsym *obj.LSym, c rttype.Cursor) {
// internal/abi.OldMapType
s1 := writeType(t.Key())
s2 := writeType(t.Elem())
s3 := writeType(OldMapBucketType(t))
hasher := genhash(t.Key())
c.Field("Key").WritePtr(s1)
c.Field("Elem").WritePtr(s2)
c.Field("Bucket").WritePtr(s3)
c.Field("Hasher").WritePtr(hasher)
var flags uint32
// Note: flags must match maptype accessors in ../../../../runtime/type.go
// and maptype builder in ../../../../reflect/type.go:MapOf.
if t.Key().Size() > abi.OldMapMaxKeyBytes {
c.Field("KeySize").WriteUint8(uint8(types.PtrSize))
flags |= 1 // indirect key
} else {
c.Field("KeySize").WriteUint8(uint8(t.Key().Size()))
}
if t.Elem().Size() > abi.OldMapMaxElemBytes {
c.Field("ValueSize").WriteUint8(uint8(types.PtrSize))
flags |= 2 // indirect value
} else {
c.Field("ValueSize").WriteUint8(uint8(t.Elem().Size()))
}
c.Field("BucketSize").WriteUint16(uint16(OldMapBucketType(t).Size()))
if types.IsReflexive(t.Key()) {
flags |= 4 // reflexive key
}
if needkeyupdate(t.Key()) {
flags |= 8 // need key update
}
if hashMightPanic(t.Key()) {
flags |= 16 // hash might panic
}
c.Field("Flags").WriteUint32(flags)
if u := t.Underlying(); u != t {
// If t is a named map type, also keep the underlying map
// type live in the binary. This is important to make sure that
// a named map and that same map cast to its underlying type via
// reflection, use the same hash function. See issue 37716.
lsym.AddRel(base.Ctxt, obj.Reloc{Type: objabi.R_KEEP, Sym: writeType(u)})
}
}