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