1
0
mirror of https://github.com/golang/go synced 2024-11-22 08:54:39 -07:00

all: wire up swisstable maps

Use the new SwissTable-based map in internal/runtime/maps as the basis
for the runtime map when GOEXPERIMENT=swissmap.

Integration is complete enough to pass all.bash. Notable missing
features:

* Race integration / concurrent write detection
* Stack-allocated maps
* Specialized "fast" map variants
* Indirect key / elem

For #54766.

Cq-Include-Trybots: luci.golang.try:gotip-linux-ppc64_power10,gotip-linux-amd64-longtest-swissmap
Change-Id: Ie97b656b6d8e05c0403311ae08fef9f51756a639
Reviewed-on: https://go-review.googlesource.com/c/go/+/594596
Reviewed-by: Keith Randall <khr@golang.org>
Reviewed-by: Keith Randall <khr@google.com>
Reviewed-by: Michael Knyszek <mknyszek@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Michael Pratt 2024-05-03 13:03:04 -04:00
parent 48849e0866
commit c39bc22c14
50 changed files with 930 additions and 2484 deletions

View File

@ -104,6 +104,13 @@ func Main(archInit func(*ssagen.ArchInfo)) {
ir.Pkgs.Runtime = types.NewPkg("go.runtime", "runtime")
ir.Pkgs.Runtime.Prefix = "runtime"
if buildcfg.Experiment.SwissMap {
// Pseudo-package that contains the compiler's builtin
// declarations for maps.
ir.Pkgs.InternalMaps = types.NewPkg("go.internal/runtime/maps", "internal/runtime/maps")
ir.Pkgs.InternalMaps.Prefix = "internal/runtime/maps"
}
// pseudo-packages used in symbol tables
ir.Pkgs.Itab = types.NewPkg("go.itab", "go.itab")
ir.Pkgs.Itab.Prefix = "go:itab"

View File

@ -73,8 +73,9 @@ type symsStruct struct {
// Pkgs holds known packages.
var Pkgs struct {
Go *types.Pkg
Itab *types.Pkg
Runtime *types.Pkg
Coverage *types.Pkg
Go *types.Pkg
Itab *types.Pkg
Runtime *types.Pkg
InternalMaps *types.Pkg
Coverage *types.Pkg
}

View File

@ -6,7 +6,6 @@ package reflectdata
import (
"internal/abi"
"cmd/compile/internal/base"
"cmd/compile/internal/ir"
"cmd/compile/internal/rttype"
@ -16,161 +15,100 @@ import (
"cmd/internal/src"
)
// SwissMapBucketType makes the map bucket type given the type of the map.
func SwissMapBucketType(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.
// SwissMapGroupType makes the map slot group type given the type of the map.
func SwissMapGroupType(t *types.Type) *types.Type {
if t.MapType().SwissGroup != nil {
return t.MapType().SwissGroup
}
// Builds a type representing a group structure for the given map type.
// This type is not visible to users, we include it so we can generate
// a correct GC program for it.
//
// A "bucket" is a "struct" {
// tophash [abi.SwissMapBucketCount]uint8
// keys [abi.SwissMapBucketCount]keyType
// elems [abi.SwissMapBucketCount]elemType
// overflow *bucket
// }
if t.MapType().SwissBucket != nil {
return t.MapType().SwissBucket
// Make sure this stays in sync with internal/runtime/maps/group.go.
//
// type group struct {
// ctrl uint64
// slots [abi.SwissMapGroupSlots]struct {
// key keyType
// elem elemType
// }
// }
slotFields := []*types.Field{
makefield("key", t.Key()),
makefield("typ", t.Elem()),
}
slot := types.NewStruct(slotFields)
slot.SetNoalg(true)
slotArr := types.NewArray(slot, abi.SwissMapGroupSlots)
slotArr.SetNoalg(true)
fields := []*types.Field{
makefield("ctrl", types.Types[types.TUINT64]),
makefield("slots", slotArr),
}
keytype := t.Key()
elemtype := t.Elem()
types.CalcSize(keytype)
types.CalcSize(elemtype)
if keytype.Size() > abi.SwissMapMaxKeyBytes {
keytype = types.NewPtr(keytype)
}
if elemtype.Size() > abi.SwissMapMaxElemBytes {
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.SwissMapBucketCount)
field = append(field, makefield("topbits", arr))
arr = types.NewArray(keytype, abi.SwissMapBucketCount)
arr.SetNoalg(true)
keys := makefield("keys", arr)
field = append(field, keys)
arr = types.NewArray(elemtype, abi.SwissMapBucketCount)
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)
group := types.NewStruct(fields)
group.SetNoalg(true)
types.CalcSize(group)
// Check invariants that map code depends on.
if !types.IsComparable(t.Key()) {
base.Fatalf("unsupported map key type for %v", t)
}
if abi.SwissMapBucketCount < 8 {
base.Fatalf("bucket size %d too small for proper alignment %d", abi.SwissMapBucketCount, 8)
}
if uint8(keytype.Alignment()) > abi.SwissMapBucketCount {
base.Fatalf("key align too big for %v", t)
}
if uint8(elemtype.Alignment()) > abi.SwissMapBucketCount {
base.Fatalf("elem align %d too big for %v, BUCKETSIZE=%d", elemtype.Alignment(), t, abi.SwissMapBucketCount)
}
if keytype.Size() > abi.SwissMapMaxKeyBytes {
base.Fatalf("key size too large for %v", t)
}
if elemtype.Size() > abi.SwissMapMaxElemBytes {
base.Fatalf("elem size too large for %v", t)
}
if t.Key().Size() > abi.SwissMapMaxKeyBytes && !keytype.IsPtr() {
base.Fatalf("key indirect incorrect for %v", t)
}
if t.Elem().Size() > abi.SwissMapMaxElemBytes && !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)
if group.Size() <= 8 {
// internal/runtime/maps creates pointers to slots, even if
// both key and elem are size zero. In this case, each slot is
// size 0, but group should still reserve a word of padding at
// the end to ensure pointers are valid.
base.Fatalf("bad group size 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().SwissBucket = bucket
bucket.StructType().Map = t
return bucket
t.MapType().SwissGroup = group
group.StructType().Map = t
return group
}
var swissHmapType *types.Type
// SwissMapType returns a type interchangeable with runtime.hmap.
// Make sure this stays in sync with runtime/map.go.
// SwissMapType returns a type interchangeable with internal/runtime/maps.Map.
// Make sure this stays in sync with internal/runtime/maps/map.go.
func SwissMapType() *types.Type {
if swissHmapType != nil {
return swissHmapType
}
// build a struct:
// type hmap struct {
// count int
// flags uint8
// B uint8
// noverflow uint16
// hash0 uint32
// buckets unsafe.Pointer
// oldbuckets unsafe.Pointer
// nevacuate uintptr
// extra unsafe.Pointer // *mapextra
// type table struct {
// used uint64
// typ unsafe.Pointer // *abi.SwissMapType
// seed uintptr
//
// // From groups.
// groups_typ unsafe.Pointer // *abi.SwissMapType
// groups_data unsafe.Pointer
// groups_lengthMask uint64
//
// capacity uint64
// growthLeft uint64
//
// clearSeq uint64
// }
// must match runtime/map.go:hmap.
// must match internal/runtime/maps/map.go:Map.
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("extra", types.Types[types.TUNSAFEPTR]),
makefield("used", types.Types[types.TUINT64]),
makefield("typ", types.Types[types.TUNSAFEPTR]),
makefield("seed", types.Types[types.TUINTPTR]),
makefield("groups_typ", types.Types[types.TUNSAFEPTR]),
makefield("groups_data", types.Types[types.TUNSAFEPTR]),
makefield("groups_lengthMask", types.Types[types.TUINT64]),
makefield("capacity", types.Types[types.TUINT64]),
makefield("growthLeft", types.Types[types.TUINT64]),
makefield("clearSeq", types.Types[types.TUINT64]),
}
n := ir.NewDeclNameAt(src.NoXPos, ir.OTYPE, ir.Pkgs.Runtime.Lookup("hmap"))
n := ir.NewDeclNameAt(src.NoXPos, ir.OTYPE, ir.Pkgs.InternalMaps.Lookup("table"))
hmap := types.NewNamed(n)
n.SetType(hmap)
n.SetTypecheck(1)
@ -178,10 +116,10 @@ func SwissMapType() *types.Type {
hmap.SetUnderlying(types.NewStruct(fields))
types.CalcSize(hmap)
// The size of hmap should be 48 bytes on 64 bit
// and 28 bytes on 32 bit platforms.
if size := int64(8 + 5*types.PtrSize); hmap.Size() != size {
base.Fatalf("hmap size not correct: got %d, want %d", hmap.Size(), size)
// The size of Map should be 64 bytes on 64 bit
// and 48 bytes on 32 bit platforms.
if size := int64(5*8 + 4*types.PtrSize); hmap.Size() != size {
base.Fatalf("internal/runtime/maps.Map size not correct: got %d, want %d", hmap.Size(), size)
}
swissHmapType = hmap
@ -200,52 +138,54 @@ func SwissMapIterType() *types.Type {
hmap := SwissMapType()
// build a struct:
// type hiter struct {
// key unsafe.Pointer // *Key
// elem unsafe.Pointer // *Elem
// t unsafe.Pointer // *SwissMapType
// 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
// type Iter struct {
// key unsafe.Pointer // *Key
// elem unsafe.Pointer // *Elem
// typ unsafe.Pointer // *SwissMapType
// m *Map
//
// // From groups.
// groups_typ unsafe.Pointer // *abi.SwissMapType
// groups_data unsafe.Pointer
// groups_lengthMask uint64
//
// clearSeq uint64
//
// offset uint64
// groupIdx uint64
// slotIdx uint32
//
// // 4 bytes of padding on 64-bit arches.
// }
// must match runtime/map.go:hiter.
// must match internal/runtime/maps/table.go:Iter.
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("typ", types.Types[types.TUNSAFEPTR]),
makefield("m", types.NewPtr(hmap)),
makefield("groups_typ", types.Types[types.TUNSAFEPTR]),
makefield("groups_data", types.Types[types.TUNSAFEPTR]),
makefield("groups_lengthMask", types.Types[types.TUINT64]),
makefield("clearSeq", types.Types[types.TUINT64]),
makefield("offset", types.Types[types.TUINT64]),
makefield("groupIdx", types.Types[types.TUINT64]),
makefield("slotIdx", types.Types[types.TUINT32]),
}
// build iterator struct hswissing the above fields
n := ir.NewDeclNameAt(src.NoXPos, ir.OTYPE, ir.Pkgs.Runtime.Lookup("hiter"))
n := ir.NewDeclNameAt(src.NoXPos, ir.OTYPE, ir.Pkgs.InternalMaps.Lookup("Iter"))
hiter := types.NewNamed(n)
n.SetType(hiter)
n.SetTypecheck(1)
hiter.SetUnderlying(types.NewStruct(fields))
types.CalcSize(hiter)
if hiter.Size() != int64(12*types.PtrSize) {
base.Fatalf("hash_iter size not correct %d %d", hiter.Size(), 12*types.PtrSize)
want := 6*types.PtrSize + 4*8 + 1*4
if types.PtrSize == 8 {
want += 4 // tailing padding
}
if hiter.Size() != int64(want) {
base.Fatalf("hash_iter size not correct %d %d", hiter.Size(), want)
}
swissHiterType = hiter
@ -254,40 +194,27 @@ func SwissMapIterType() *types.Type {
func writeSwissMapType(t *types.Type, lsym *obj.LSym, c rttype.Cursor) {
// internal/abi.SwissMapType
gtyp := SwissMapGroupType(t)
s1 := writeType(t.Key())
s2 := writeType(t.Elem())
s3 := writeType(SwissMapBucketType(t))
s3 := writeType(gtyp)
hasher := genhash(t.Key())
slotTyp := gtyp.Field(1).Type.Elem()
elemOff := slotTyp.Field(1).Offset
c.Field("Key").WritePtr(s1)
c.Field("Elem").WritePtr(s2)
c.Field("Bucket").WritePtr(s3)
c.Field("Group").WritePtr(s3)
c.Field("Hasher").WritePtr(hasher)
c.Field("SlotSize").WriteUintptr(uint64(slotTyp.Size()))
c.Field("ElemOff").WriteUintptr(uint64(elemOff))
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.SwissMapMaxKeyBytes {
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.SwissMapMaxElemBytes {
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(SwissMapBucketType(t).Size()))
if types.IsReflexive(t.Key()) {
flags |= 4 // reflexive key
}
if needkeyupdate(t.Key()) {
flags |= 8 // need key update
flags |= abi.SwissMapNeedKeyUpdate
}
if hashMightPanic(t.Key()) {
flags |= 16 // hash might panic
flags |= abi.SwissMapHashMightPanic
}
c.Field("Flags").WriteUint32(flags)

View File

@ -89,7 +89,7 @@ func InitConfig() {
_ = types.NewPtr(types.Types[types.TINT64]) // *int64
_ = types.NewPtr(types.ErrorType) // *error
if buildcfg.Experiment.SwissMap {
_ = types.NewPtr(reflectdata.SwissMapType()) // *runtime.hmap
_ = types.NewPtr(reflectdata.SwissMapType()) // *internal/runtime/maps.Map
} else {
_ = types.NewPtr(reflectdata.OldMapType()) // *runtime.hmap
}
@ -5480,8 +5480,13 @@ func (s *state) referenceTypeBuiltin(n *ir.UnaryExpr, x *ssa.Value) *ssa.Value {
s.startBlock(bElse)
switch n.Op() {
case ir.OLEN:
// length is stored in the first word for map/chan
s.vars[n] = s.load(lenType, x)
if buildcfg.Experiment.SwissMap && n.X.Type().IsMap() {
// length is stored in the first word.
s.vars[n] = s.load(lenType, x)
} else {
// length is stored in the first word for map/chan
s.vars[n] = s.load(lenType, x)
}
case ir.OCAP:
// capacity is stored in the second word for chan
sw := s.newValue1I(ssa.OpOffPtr, lenType.PtrTo(), lenType.Size(), x)

View File

@ -39,10 +39,7 @@ func TestIntendedInlining(t *testing.T) {
"adjustpointer",
"alignDown",
"alignUp",
"bucketMask",
"bucketShift",
"chanbuf",
"evacuated",
"fastlog2",
"float64bits",
"funcspdelta",
@ -62,9 +59,6 @@ func TestIntendedInlining(t *testing.T) {
"stringStructOf",
"subtract1",
"subtractb",
"tophash",
"(*bmap).keys",
"(*bmap).overflow",
"(*waitq).enqueue",
"funcInfo.entry",
@ -236,6 +230,15 @@ func TestIntendedInlining(t *testing.T) {
},
}
if !goexperiment.SwissMap {
// Maps
want["runtime"] = append(want["runtime"], "bucketMask")
want["runtime"] = append(want["runtime"], "bucketShift")
want["runtime"] = append(want["runtime"], "evacuated")
want["runtime"] = append(want["runtime"], "tophash")
want["runtime"] = append(want["runtime"], "(*bmap).keys")
want["runtime"] = append(want["runtime"], "(*bmap).overflow")
}
if runtime.GOARCH != "386" && runtime.GOARCH != "loong64" && runtime.GOARCH != "mips64" && runtime.GOARCH != "mips64le" && runtime.GOARCH != "riscv64" {
// nextFreeFast calls sys.TrailingZeros64, which on 386 is implemented in asm and is not inlinable.
// We currently don't have midstack inlining so nextFreeFast is also not inlinable on 386.

View File

@ -474,8 +474,10 @@ func tconv2(b *bytes.Buffer, t *Type, verb rune, mode fmtMode, visited map[*Type
// Format the bucket struct for map[x]y as map.bucket[x]y.
// This avoids a recursive print that generates very long names.
switch t {
case mt.OldBucket, mt.SwissBucket:
case mt.OldBucket:
b.WriteString("map.bucket[")
case mt.SwissGroup:
b.WriteString("map.group[")
default:
base.Fatalf("unknown internal map type")
}

View File

@ -291,7 +291,7 @@ type Map struct {
OldBucket *Type // internal struct type representing a hash bucket
// GOEXPERIMENT=swissmap fields
SwissBucket *Type // internal struct type representing a hash bucket
SwissGroup *Type // internal struct type representing a slot group
}
// MapType returns t's extra map-specific fields.
@ -1192,15 +1192,9 @@ func (t *Type) cmp(x *Type) Cmp {
// to the fallthrough
} else if x.StructType().Map == nil {
return CMPgt // nil > non-nil
} else if t.StructType().Map.MapType().SwissBucket == t {
// Both have non-nil Map
// Special case for Maps which include a recursive type where the recursion is not broken with a named type
if x.StructType().Map.MapType().SwissBucket != x {
return CMPlt // bucket maps are least
}
} else {
// TODO: I am confused by the purpose of the OldBucket stuff below.
return t.StructType().Map.cmp(x.StructType().Map)
} else if x.StructType().Map.MapType().SwissBucket == x {
return CMPgt // bucket maps are least
} // If t != t.Map.SwissBucket, fall through to general case
} else {
if t.StructType().Map == nil {

View File

@ -1,4 +1,4 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Copyright 2009 The Go Authors. All rights reserved.walk/bui
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
@ -332,62 +332,8 @@ func walkMakeSwissMap(n *ir.MakeExpr, init *ir.Nodes) ir.Node {
// h = &hv
h = stackTempAddr(init, hmapType)
// Allocate one bucket pointed to by hmap.buckets on stack if hint
// is not larger than BUCKETSIZE. In case hint is larger than
// BUCKETSIZE runtime.makemap will allocate the buckets on the heap.
// Maximum key and elem size is 128 bytes, larger objects
// are stored with an indirection. So max bucket size is 2048+eps.
if !ir.IsConst(hint, constant.Int) ||
constant.Compare(hint.Val(), token.LEQ, constant.MakeInt64(abi.SwissMapBucketCount)) {
// In case hint is larger than BUCKETSIZE runtime.makemap
// will allocate the buckets on the heap, see #20184
//
// if hint <= BUCKETSIZE {
// var bv bmap
// b = &bv
// h.buckets = b
// }
nif := ir.NewIfStmt(base.Pos, ir.NewBinaryExpr(base.Pos, ir.OLE, hint, ir.NewInt(base.Pos, abi.SwissMapBucketCount)), nil, nil)
nif.Likely = true
// var bv bmap
// b = &bv
b := stackTempAddr(&nif.Body, reflectdata.SwissMapBucketType(t))
// h.buckets = b
bsym := hmapType.Field(5).Sym // hmap.buckets see reflect.go:hmap
na := ir.NewAssignStmt(base.Pos, ir.NewSelectorExpr(base.Pos, ir.ODOT, h, bsym), typecheck.ConvNop(b, types.Types[types.TUNSAFEPTR]))
nif.Body.Append(na)
appendWalkStmt(init, nif)
}
}
if ir.IsConst(hint, constant.Int) && constant.Compare(hint.Val(), token.LEQ, constant.MakeInt64(abi.SwissMapBucketCount)) {
// Handling make(map[any]any) and
// make(map[any]any, hint) where hint <= BUCKETSIZE
// special allows for faster map initialization and
// improves binary size by using calls with fewer arguments.
// For hint <= BUCKETSIZE overLoadFactor(hint, 0) is false
// and no buckets will be allocated by makemap. Therefore,
// no buckets need to be allocated in this code path.
if n.Esc() == ir.EscNone {
// Only need to initialize h.hash0 since
// hmap h has been allocated on the stack already.
// h.hash0 = rand32()
rand := mkcall("rand32", types.Types[types.TUINT32], init)
hashsym := hmapType.Field(4).Sym // hmap.hash0 see reflect.go:hmap
appendWalkStmt(init, ir.NewAssignStmt(base.Pos, ir.NewSelectorExpr(base.Pos, ir.ODOT, h, hashsym), rand))
return typecheck.ConvNop(h, t)
}
// Call runtime.makehmap to allocate an
// hmap on the heap and initialize hmap's hash0 field.
fn := typecheck.LookupRuntime("makemap_small", t.Key(), t.Elem())
return mkcall1(fn, n.Type(), init)
}
if n.Esc() != ir.EscNone {
// TODO(go.dev/issue/54766): Stack allocated table/groups.
} else {
h = typecheck.NodNil()
}
// Map initialization with a variable or large hint is

View File

@ -5,6 +5,7 @@
package walk
import (
"internal/buildcfg"
"unicode/utf8"
"cmd/compile/internal/base"
@ -242,8 +243,14 @@ func walkRange(nrange *ir.RangeStmt) ir.Node {
th := hit.Type()
// depends on layout of iterator struct.
// See cmd/compile/internal/reflectdata/reflect.go:MapIterType
keysym := th.Field(0).Sym
elemsym := th.Field(1).Sym // ditto
var keysym, elemsym *types.Sym
if buildcfg.Experiment.SwissMap {
keysym = th.Field(0).Sym
elemsym = th.Field(1).Sym // ditto
} else {
keysym = th.Field(0).Sym
elemsym = th.Field(1).Sym // ditto
}
fn := typecheck.LookupRuntime("mapiterinit", t.Key(), t.Elem(), th)
init = append(init, mkcallstmt1(fn, reflectdata.RangeMapRType(base.Pos, nrange), ha, typecheck.NodAddr(hit)))

View File

@ -929,15 +929,16 @@ func runTest(ctx context.Context, cmd *base.Command, args []string) {
// unlikely to be useful. Most of these are used by the testing or
// internal/fuzz packages concurrently with fuzzing.
var skipInstrumentation = map[string]bool{
"context": true,
"internal/fuzz": true,
"reflect": true,
"runtime": true,
"sync": true,
"sync/atomic": true,
"syscall": true,
"testing": true,
"time": true,
"context": true,
"internal/fuzz": true,
"internal/runtime/maps": true,
"reflect": true,
"runtime": true,
"sync": true,
"sync/atomic": true,
"syscall": true,
"testing": true,
"time": true,
}
for _, p := range load.TestPackageList(ctx, pkgOpts, pkgs) {
if !skipInstrumentation[p.ImportPath] {

View File

@ -67,6 +67,7 @@ func TestPrefixToPathError(t *testing.T) {
}
func TestRuntimePackageList(t *testing.T) {
t.Skip("TODO: XXX")
// Test that all packages imported by the runtime are marked as runtime
// packages.
testenv.MustHaveGoBuild(t)

View File

@ -47,6 +47,7 @@ var runtimePkgs = []string{
"internal/runtime/atomic",
"internal/runtime/exithook",
"internal/runtime/maps",
"internal/runtime/math",
"internal/runtime/sys",
"internal/runtime/syscall",

View File

@ -561,7 +561,10 @@ func (d *deadcodePass) decodetypeMethods(ldr *loader.Loader, arch *sys.Arch, sym
off += 2 * arch.PtrSize
case abi.Map:
if buildcfg.Experiment.SwissMap {
off += 4*arch.PtrSize + 8 // internal/abi.SwissMapType
off += 6*arch.PtrSize + 4 // internal/abi.SwissMapType
if arch.PtrSize == 8 {
off += 4 // padding for final uint32 field (Flags).
}
} else {
off += 4*arch.PtrSize + 8 // internal/abi.OldMapType
}

View File

@ -810,7 +810,7 @@ func (d *dwctxt) findprotodie(ctxt *Link, name string) *dwarf.DWDie {
die = prototypedies[name]
}
if die == nil {
log.Fatalf("internal error: DIE generation failed for %s\n", name)
log.Fatalf("internal error: DIE generation failed for %s\nprototypedies: %+v", name, prototypedies)
}
return die
}
@ -873,8 +873,8 @@ func (d *dwctxt) synthesizemaptypes(ctxt *Link, die *dwarf.DWDie) {
}
func (d *dwctxt) synthesizemaptypesSwiss(ctxt *Link, die *dwarf.DWDie) {
hash := walktypedef(d.findprotodie(ctxt, "type:runtime.hmap"))
bucket := walktypedef(d.findprotodie(ctxt, "type:runtime.bmap"))
hash := walktypedef(d.findprotodie(ctxt, "type:internal/runtime/maps.table"))
//bucket := walktypedef(d.findprotodie(ctxt, "type:internal/runtime/maps.Map"))
if hash == nil {
return
@ -887,79 +887,82 @@ func (d *dwctxt) synthesizemaptypesSwiss(ctxt *Link, die *dwarf.DWDie) {
gotype := loader.Sym(getattr(die, dwarf.DW_AT_type).Data.(dwSym))
keytype := decodetypeMapKey(d.ldr, d.arch, gotype)
valtype := decodetypeMapValue(d.ldr, d.arch, gotype)
keydata := d.ldr.Data(keytype)
valdata := d.ldr.Data(valtype)
keysize, valsize := decodetypeSize(d.arch, keydata), decodetypeSize(d.arch, valdata)
//keydata := d.ldr.Data(keytype)
//valdata := d.ldr.Data(valtype)
//keysize, valsize := decodetypeSize(d.arch, keydata), decodetypeSize(d.arch, valdata)
keytype, valtype = d.walksymtypedef(d.defgotype(keytype)), d.walksymtypedef(d.defgotype(valtype))
// compute size info like hashmap.c does.
indirectKey, indirectVal := false, false
if keysize > abi.SwissMapMaxKeyBytes {
keysize = int64(d.arch.PtrSize)
indirectKey = true
}
if valsize > abi.SwissMapMaxElemBytes {
valsize = int64(d.arch.PtrSize)
indirectVal = true
}
//indirectKey, indirectVal := false, false
//if keysize > abi.SwissMapMaxKeyBytes {
// keysize = int64(d.arch.PtrSize)
// indirectKey = true
//}
//if valsize > abi.SwissMapMaxElemBytes {
// valsize = int64(d.arch.PtrSize)
// indirectVal = true
//}
// Construct type to represent an array of BucketSize keys
// TODO
keyname := d.nameFromDIESym(keytype)
dwhks := d.mkinternaltype(ctxt, dwarf.DW_ABRV_ARRAYTYPE, "[]key", keyname, "", func(dwhk *dwarf.DWDie) {
newattr(dwhk, dwarf.DW_AT_byte_size, dwarf.DW_CLS_CONSTANT, abi.SwissMapBucketCount*keysize, 0)
t := keytype
if indirectKey {
t = d.defptrto(keytype)
}
d.newrefattr(dwhk, dwarf.DW_AT_type, t)
fld := d.newdie(dwhk, dwarf.DW_ABRV_ARRAYRANGE, "size")
newattr(fld, dwarf.DW_AT_count, dwarf.DW_CLS_CONSTANT, abi.SwissMapBucketCount, 0)
d.newrefattr(fld, dwarf.DW_AT_type, d.uintptrInfoSym)
})
//dwhks := d.mkinternaltype(ctxt, dwarf.DW_ABRV_ARRAYTYPE, "[]key", keyname, "", func(dwhk *dwarf.DWDie) {
// newattr(dwhk, dwarf.DW_AT_byte_size, dwarf.DW_CLS_CONSTANT, abi.SwissMapBucketCount*keysize, 0)
// t := keytype
// if indirectKey {
// t = d.defptrto(keytype)
// }
// d.newrefattr(dwhk, dwarf.DW_AT_type, t)
// fld := d.newdie(dwhk, dwarf.DW_ABRV_ARRAYRANGE, "size")
// newattr(fld, dwarf.DW_AT_count, dwarf.DW_CLS_CONSTANT, abi.SwissMapBucketCount, 0)
// d.newrefattr(fld, dwarf.DW_AT_type, d.uintptrInfoSym)
//})
// Construct type to represent an array of BucketSize values
// TODO
valname := d.nameFromDIESym(valtype)
dwhvs := d.mkinternaltype(ctxt, dwarf.DW_ABRV_ARRAYTYPE, "[]val", valname, "", func(dwhv *dwarf.DWDie) {
newattr(dwhv, dwarf.DW_AT_byte_size, dwarf.DW_CLS_CONSTANT, abi.SwissMapBucketCount*valsize, 0)
t := valtype
if indirectVal {
t = d.defptrto(valtype)
}
d.newrefattr(dwhv, dwarf.DW_AT_type, t)
fld := d.newdie(dwhv, dwarf.DW_ABRV_ARRAYRANGE, "size")
newattr(fld, dwarf.DW_AT_count, dwarf.DW_CLS_CONSTANT, abi.SwissMapBucketCount, 0)
d.newrefattr(fld, dwarf.DW_AT_type, d.uintptrInfoSym)
})
//dwhvs := d.mkinternaltype(ctxt, dwarf.DW_ABRV_ARRAYTYPE, "[]val", valname, "", func(dwhv *dwarf.DWDie) {
// newattr(dwhv, dwarf.DW_AT_byte_size, dwarf.DW_CLS_CONSTANT, abi.SwissMapBucketCount*valsize, 0)
// t := valtype
// if indirectVal {
// t = d.defptrto(valtype)
// }
// d.newrefattr(dwhv, dwarf.DW_AT_type, t)
// fld := d.newdie(dwhv, dwarf.DW_ABRV_ARRAYRANGE, "size")
// newattr(fld, dwarf.DW_AT_count, dwarf.DW_CLS_CONSTANT, abi.SwissMapBucketCount, 0)
// d.newrefattr(fld, dwarf.DW_AT_type, d.uintptrInfoSym)
//})
// Construct bucket<K,V>
dwhbs := d.mkinternaltype(ctxt, dwarf.DW_ABRV_STRUCTTYPE, "bucket", keyname, valname, func(dwhb *dwarf.DWDie) {
// Copy over all fields except the field "data" from the generic
// bucket. "data" will be replaced with keys/values below.
d.copychildrenexcept(ctxt, dwhb, bucket, findchild(bucket, "data"))
// TODO
//dwhbs := d.mkinternaltype(ctxt, dwarf.DW_ABRV_STRUCTTYPE, "bucket", keyname, valname, func(dwhb *dwarf.DWDie) {
// // Copy over all fields except the field "data" from the generic
// // bucket. "data" will be replaced with keys/values below.
// d.copychildrenexcept(ctxt, dwhb, bucket, findchild(bucket, "data"))
fld := d.newdie(dwhb, dwarf.DW_ABRV_STRUCTFIELD, "keys")
d.newrefattr(fld, dwarf.DW_AT_type, dwhks)
newmemberoffsetattr(fld, abi.SwissMapBucketCount)
fld = d.newdie(dwhb, dwarf.DW_ABRV_STRUCTFIELD, "values")
d.newrefattr(fld, dwarf.DW_AT_type, dwhvs)
newmemberoffsetattr(fld, abi.SwissMapBucketCount+abi.SwissMapBucketCount*int32(keysize))
fld = d.newdie(dwhb, dwarf.DW_ABRV_STRUCTFIELD, "overflow")
d.newrefattr(fld, dwarf.DW_AT_type, d.defptrto(d.dtolsym(dwhb.Sym)))
newmemberoffsetattr(fld, abi.SwissMapBucketCount+abi.SwissMapBucketCount*(int32(keysize)+int32(valsize)))
if d.arch.RegSize > d.arch.PtrSize {
fld = d.newdie(dwhb, dwarf.DW_ABRV_STRUCTFIELD, "pad")
d.newrefattr(fld, dwarf.DW_AT_type, d.uintptrInfoSym)
newmemberoffsetattr(fld, abi.SwissMapBucketCount+abi.SwissMapBucketCount*(int32(keysize)+int32(valsize))+int32(d.arch.PtrSize))
}
// fld := d.newdie(dwhb, dwarf.DW_ABRV_STRUCTFIELD, "keys")
// d.newrefattr(fld, dwarf.DW_AT_type, dwhks)
// newmemberoffsetattr(fld, abi.SwissMapBucketCount)
// fld = d.newdie(dwhb, dwarf.DW_ABRV_STRUCTFIELD, "values")
// d.newrefattr(fld, dwarf.DW_AT_type, dwhvs)
// newmemberoffsetattr(fld, abi.SwissMapBucketCount+abi.SwissMapBucketCount*int32(keysize))
// fld = d.newdie(dwhb, dwarf.DW_ABRV_STRUCTFIELD, "overflow")
// d.newrefattr(fld, dwarf.DW_AT_type, d.defptrto(d.dtolsym(dwhb.Sym)))
// newmemberoffsetattr(fld, abi.SwissMapBucketCount+abi.SwissMapBucketCount*(int32(keysize)+int32(valsize)))
// if d.arch.RegSize > d.arch.PtrSize {
// fld = d.newdie(dwhb, dwarf.DW_ABRV_STRUCTFIELD, "pad")
// d.newrefattr(fld, dwarf.DW_AT_type, d.uintptrInfoSym)
// newmemberoffsetattr(fld, abi.SwissMapBucketCount+abi.SwissMapBucketCount*(int32(keysize)+int32(valsize))+int32(d.arch.PtrSize))
// }
newattr(dwhb, dwarf.DW_AT_byte_size, dwarf.DW_CLS_CONSTANT, abi.SwissMapBucketCount+abi.SwissMapBucketCount*keysize+abi.SwissMapBucketCount*valsize+int64(d.arch.RegSize), 0)
})
// newattr(dwhb, dwarf.DW_AT_byte_size, dwarf.DW_CLS_CONSTANT, abi.SwissMapBucketCount+abi.SwissMapBucketCount*keysize+abi.SwissMapBucketCount*valsize+int64(d.arch.RegSize), 0)
//})
// Construct hash<K,V>
dwhs := d.mkinternaltype(ctxt, dwarf.DW_ABRV_STRUCTTYPE, "hash", keyname, valname, func(dwh *dwarf.DWDie) {
d.copychildren(ctxt, dwh, hash)
d.substitutetype(dwh, "buckets", d.defptrto(dwhbs))
d.substitutetype(dwh, "oldbuckets", d.defptrto(dwhbs))
//d.substitutetype(dwh, "buckets", d.defptrto(dwhbs))
//d.substitutetype(dwh, "oldbuckets", d.defptrto(dwhbs))
newattr(dwh, dwarf.DW_AT_byte_size, dwarf.DW_CLS_CONSTANT, getattr(hash, dwarf.DW_AT_byte_size).Value, nil)
})
@ -1874,12 +1877,16 @@ func dwarfGenerateDebugInfo(ctxt *Link) {
prototypedies = map[string]*dwarf.DWDie{
"type:runtime.stringStructDWARF": nil,
"type:runtime.slice": nil,
"type:runtime.hmap": nil,
"type:runtime.bmap": nil,
"type:runtime.sudog": nil,
"type:runtime.waitq": nil,
"type:runtime.hchan": nil,
}
if buildcfg.Experiment.SwissMap {
prototypedies["type:internal/runtime/maps.table"] = nil
} else {
prototypedies["type:runtime.hmap"] = nil
prototypedies["type:runtime.bmap"] = nil
}
// Needed by the prettyprinter code for interface inspection.
for _, typ := range []string{

View File

@ -87,7 +87,6 @@ var depsRules = `
< internal/runtime/syscall
< internal/runtime/atomic
< internal/runtime/exithook
< internal/runtime/maps/internal/abi
< internal/runtime/maps
< internal/runtime/math
< runtime

View File

@ -52,4 +52,3 @@ func (mt *OldMapType) NeedKeyUpdate() bool { // true if we need to update key on
func (mt *OldMapType) HashMightPanic() bool { // true if hash function might panic
return mt.Flags&16 != 0
}

View File

@ -11,45 +11,31 @@ import (
// Map constants common to several packages
// runtime/runtime-gdb.py:MapTypePrinter contains its own copy
const (
// Maximum number of key/elem pairs a bucket can hold.
SwissMapBucketCountBits = 3 // log2 of number of elements in a bucket.
SwissMapBucketCount = 1 << SwissMapBucketCountBits
// Maximum key or elem size to keep inline (instead of mallocing per element).
// Must fit in a uint8.
// Note: fast map functions cannot handle big elems (bigger than MapMaxElemBytes).
SwissMapMaxKeyBytes = 128
SwissMapMaxElemBytes = 128 // Must fit in a uint8.
// Number of slots in a group.
SwissMapGroupSlots = 8
)
type SwissMapType struct {
Type
Key *Type
Elem *Type
Bucket *Type // internal type representing a hash bucket
Key *Type
Elem *Type
Group *Type // internal type representing a slot group
// function for hashing keys (ptr to key, seed) -> hash
Hasher func(unsafe.Pointer, uintptr) uintptr
KeySize uint8 // size of key slot
ValueSize uint8 // size of elem slot
BucketSize uint16 // size of bucket
Flags uint32
Hasher func(unsafe.Pointer, uintptr) uintptr
SlotSize uintptr // size of key/elem slot
ElemOff uintptr // offset of elem in key/elem slot
Flags uint32
}
// Note: flag values must match those used in the TMAP case
// in ../cmd/compile/internal/reflectdata/reflect.go:writeType.
func (mt *SwissMapType) IndirectKey() bool { // store ptr to key instead of key itself
return mt.Flags&1 != 0
}
func (mt *SwissMapType) IndirectElem() bool { // store ptr to elem instead of elem itself
return mt.Flags&2 != 0
}
func (mt *SwissMapType) ReflexiveKey() bool { // true if k==k for all keys
return mt.Flags&4 != 0
}
// Flag values
const (
SwissMapNeedKeyUpdate = 1 << iota
SwissMapHashMightPanic
)
func (mt *SwissMapType) NeedKeyUpdate() bool { // true if we need to update key on an overwrite
return mt.Flags&8 != 0
return mt.Flags&SwissMapNeedKeyUpdate != 0
}
func (mt *SwissMapType) HashMightPanic() bool { // true if hash function might panic
return mt.Flags&16 != 0
return mt.Flags&SwissMapHashMightPanic != 0
}

View File

@ -52,6 +52,7 @@ var rtPkgs = [...]string{
"internal/chacha8rand",
"internal/runtime/sys",
"internal/abi",
"internal/runtime/maps",
"internal/runtime/math",
"internal/bytealg",
"internal/goexperiment",

View File

@ -0,0 +1,50 @@
// 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.
//go:build !goexperiment.swissmap
// This file allows non-GOEXPERIMENT=swissmap builds (i.e., old map builds) to
// construct a swissmap table for running the tests in this package.
package maps
import (
"internal/abi"
"unsafe"
)
type instantiatedGroup[K comparable, V any] struct {
ctrls ctrlGroup
slots [abi.SwissMapGroupSlots]instantiatedSlot[K, V]
}
type instantiatedSlot[K comparable, V any] struct {
key K
elem V
}
func NewTestTable[K comparable, V any](length uint64) *table {
var m map[K]V
mTyp := abi.TypeOf(m)
omt := (*abi.OldMapType)(unsafe.Pointer(mTyp))
var grp instantiatedGroup[K, V]
var slot instantiatedSlot[K, V]
mt := &abi.SwissMapType{
Key: omt.Key,
Elem: omt.Elem,
Group: abi.TypeOf(grp),
Hasher: omt.Hasher,
SlotSize: unsafe.Sizeof(slot),
ElemOff: unsafe.Offsetof(slot.elem),
}
if omt.NeedKeyUpdate() {
mt.Flags |= abi.SwissMapNeedKeyUpdate
}
if omt.HashMightPanic() {
mt.Flags |= abi.SwissMapHashMightPanic
}
return newTable(mt, length)
}

View File

@ -0,0 +1,19 @@
// 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.
//go:build goexperiment.swissmap
package maps
import (
"internal/abi"
"unsafe"
)
func NewTestTable[K comparable, V any](length uint64) *table {
var m map[K]V
mTyp := abi.TypeOf(m)
mt := (*abi.SwissMapType)(unsafe.Pointer(mTyp))
return newTable(mt, length)
}

View File

@ -6,7 +6,6 @@ package maps
import (
"internal/abi"
sabi "internal/runtime/maps/internal/abi"
"unsafe"
)
@ -16,41 +15,16 @@ const DebugLog = debugLog
var AlignUpPow2 = alignUpPow2
type instantiatedGroup[K comparable, V any] struct {
ctrls ctrlGroup
slots [sabi.SwissMapGroupSlots]instantiatedSlot[K, V]
}
type instantiatedSlot[K comparable, V any] struct {
key K
elem V
}
func NewTestTable[K comparable, V any](length uint64) *table {
var m map[K]V
mTyp := abi.TypeOf(m)
omt := (*abi.OldMapType)(unsafe.Pointer(mTyp))
var grp instantiatedGroup[K, V]
var slot instantiatedSlot[K, V]
mt := &sabi.SwissMapType{
Key: omt.Key,
Elem: omt.Elem,
Group: abi.TypeOf(grp),
Hasher: omt.Hasher,
SlotSize: unsafe.Sizeof(slot),
ElemOff: unsafe.Offsetof(slot.elem),
}
if omt.NeedKeyUpdate() {
mt.Flags |= sabi.SwissMapNeedKeyUpdate
}
if omt.HashMightPanic() {
mt.Flags |= sabi.SwissMapHashMightPanic
}
return newTable(mt, length)
}
func (t *table) Type() *sabi.SwissMapType {
func (t *table) Type() *abi.SwissMapType {
return t.typ
}
// Returns the start address of the groups array.
func (t *table) GroupsStart() unsafe.Pointer {
return t.groups.data
}
// Returns the length of the groups array.
func (t *table) GroupsLength() uintptr {
return uintptr(t.groups.lengthMask + 1)
}

View File

@ -5,8 +5,8 @@
package maps
import (
"internal/abi"
"internal/goarch"
"internal/runtime/maps/internal/abi"
"internal/runtime/sys"
"unsafe"
)

View File

@ -1,44 +0,0 @@
// Copyright 2023 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 abi is a temporary copy of the swissmap abi. It will be eliminated
// once swissmaps are integrated into the runtime.
package abi
import (
"internal/abi"
"unsafe"
)
// Map constants common to several packages
// runtime/runtime-gdb.py:MapTypePrinter contains its own copy
const (
// Number of slots in a group.
SwissMapGroupSlots = 8
)
type SwissMapType struct {
abi.Type
Key *abi.Type
Elem *abi.Type
Group *abi.Type // internal type representing a slot group
// function for hashing keys (ptr to key, seed) -> hash
Hasher func(unsafe.Pointer, uintptr) uintptr
SlotSize uintptr // size of key/elem slot
ElemOff uintptr // offset of elem in key/elem slot
Flags uint32
}
// Flag values
const (
SwissMapNeedKeyUpdate = 1 << iota
SwissMapHashMightPanic
)
func (mt *SwissMapType) NeedKeyUpdate() bool { // true if we need to update key on an overwrite
return mt.Flags&SwissMapNeedKeyUpdate != 0
}
func (mt *SwissMapType) HashMightPanic() bool { // true if hash function might panic
return mt.Flags&SwissMapHashMightPanic != 0
}

View File

@ -6,8 +6,8 @@ package maps_test
import (
"fmt"
"internal/abi"
"internal/runtime/maps"
"internal/runtime/maps/internal/abi"
"math"
"testing"
"unsafe"
@ -444,4 +444,11 @@ func TestTableZeroSizeSlot(t *testing.T) {
if gotElem != elem {
t.Errorf("Get(%d) got elem %d want %d", key, gotElem, elem)
}
start := tab.GroupsStart()
length := tab.GroupsLength()
end := unsafe.Pointer(uintptr(start) + length*tab.Type().Group.Size() - 1) // inclusive to ensure we have a valid pointer
if uintptr(got) < uintptr(start) || uintptr(got) > uintptr(end) {
t.Errorf("elem address outside groups allocation; got %p want [%p, %p]", got, start, end)
}
}

View File

@ -6,7 +6,7 @@
package maps
import (
"internal/runtime/maps/internal/abi"
"internal/abi"
"unsafe"
)

View File

@ -6,7 +6,7 @@
package maps
import (
sabi "internal/runtime/maps/internal/abi"
"internal/abi"
"unsafe"
)
@ -24,7 +24,7 @@ func (t *table) checkInvariants() {
var empty uint64
for i := uint64(0); i <= t.groups.lengthMask; i++ {
g := t.groups.group(i)
for j := uint32(0); j < sabi.SwissMapGroupSlots; j++ {
for j := uint32(0); j < abi.SwissMapGroupSlots; j++ {
c := g.ctrls().get(j)
switch {
case c == ctrlDeleted:
@ -60,7 +60,7 @@ func (t *table) checkInvariants() {
panic("invariant failed: found mismatched used slot count")
}
growthLeft := (t.capacity*maxAvgGroupLoad)/sabi.SwissMapGroupSlots - t.used - deleted
growthLeft := (t.capacity*maxAvgGroupLoad)/abi.SwissMapGroupSlots - t.used - deleted
if growthLeft != t.growthLeft {
print("invariant failed: found ", t.growthLeft, " growthLeft, but expected ", growthLeft, "\n")
t.Print()
@ -93,7 +93,7 @@ func (t *table) Print() {
g := t.groups.group(i)
ctrls := g.ctrls()
for j := uint32(0); j < sabi.SwissMapGroupSlots; j++ {
for j := uint32(0); j < abi.SwissMapGroupSlots; j++ {
print("\t\t\tslot ", j, "\n")
c := ctrls.get(j)

View File

@ -10,7 +10,6 @@ import (
"flag"
"fmt"
"go/token"
"internal/abi"
"internal/goarch"
"internal/goexperiment"
"internal/testenv"
@ -1134,13 +1133,15 @@ var deepEqualTests = []DeepEqualTest{
}
func TestDeepEqual(t *testing.T) {
for _, test := range deepEqualTests {
if test.b == (self{}) {
test.b = test.a
}
if r := DeepEqual(test.a, test.b); r != test.eq {
t.Errorf("DeepEqual(%#v, %#v) = %v, want %v", test.a, test.b, r, test.eq)
}
for i, test := range deepEqualTests {
t.Run(fmt.Sprint(i), func(t *testing.T) {
if test.b == (self{}) {
test.b = test.a
}
if r := DeepEqual(test.a, test.b); r != test.eq {
t.Errorf("DeepEqual(%#v, %#v) = %v, want %v", test.a, test.b, r, test.eq)
}
})
}
}
@ -1273,6 +1274,11 @@ var deepEqualPerfTests = []struct {
}
func TestDeepEqualAllocs(t *testing.T) {
// TODO(prattmic): maps on stack
if goexperiment.SwissMap {
t.Skipf("Maps on stack not yet implemented")
}
for _, tt := range deepEqualPerfTests {
t.Run(ValueOf(tt.x).Type().String(), func(t *testing.T) {
got := testing.AllocsPerRun(100, func() {
@ -7171,60 +7177,61 @@ func verifyGCBitsSlice(t *testing.T, typ Type, cap int, bits []byte) {
t.Errorf("line %d: heapBits incorrect for make(%v, 0, %v)\nhave %v\nwant %v", line, typ, cap, heapBits, bits)
}
func TestGCBits(t *testing.T) {
verifyGCBits(t, TypeOf((*byte)(nil)), []byte{1})
// Building blocks for types seen by the compiler (like [2]Xscalar).
// The compiler will create the type structures for the derived types,
// including their GC metadata.
type Xscalar struct{ x uintptr }
type Xptr struct{ x *byte }
type Xptrscalar struct {
*byte
uintptr
}
type Xscalarptr struct {
uintptr
*byte
}
type Xbigptrscalar struct {
_ [100]*byte
_ [100]uintptr
}
// Building blocks for types seen by the compiler (like [2]Xscalar).
// The compiler will create the type structures for the derived types,
// including their GC metadata.
type Xscalar struct{ x uintptr }
type Xptr struct{ x *byte }
type Xptrscalar struct {
var Tscalar, Tint64, Tptr, Tscalarptr, Tptrscalar, Tbigptrscalar Type
func init() {
// Building blocks for types constructed by reflect.
// This code is in a separate block so that code below
// cannot accidentally refer to these.
// The compiler must NOT see types derived from these
// (for example, [2]Scalar must NOT appear in the program),
// or else reflect will use it instead of having to construct one.
// The goal is to test the construction.
type Scalar struct{ x uintptr }
type Ptr struct{ x *byte }
type Ptrscalar struct {
*byte
uintptr
}
type Xscalarptr struct {
type Scalarptr struct {
uintptr
*byte
}
type Xbigptrscalar struct {
type Bigptrscalar struct {
_ [100]*byte
_ [100]uintptr
}
type Int64 int64
Tscalar = TypeOf(Scalar{})
Tint64 = TypeOf(Int64(0))
Tptr = TypeOf(Ptr{})
Tscalarptr = TypeOf(Scalarptr{})
Tptrscalar = TypeOf(Ptrscalar{})
Tbigptrscalar = TypeOf(Bigptrscalar{})
}
var Tscalar, Tint64, Tptr, Tscalarptr, Tptrscalar, Tbigptrscalar Type
{
// Building blocks for types constructed by reflect.
// This code is in a separate block so that code below
// cannot accidentally refer to these.
// The compiler must NOT see types derived from these
// (for example, [2]Scalar must NOT appear in the program),
// or else reflect will use it instead of having to construct one.
// The goal is to test the construction.
type Scalar struct{ x uintptr }
type Ptr struct{ x *byte }
type Ptrscalar struct {
*byte
uintptr
}
type Scalarptr struct {
uintptr
*byte
}
type Bigptrscalar struct {
_ [100]*byte
_ [100]uintptr
}
type Int64 int64
Tscalar = TypeOf(Scalar{})
Tint64 = TypeOf(Int64(0))
Tptr = TypeOf(Ptr{})
Tscalarptr = TypeOf(Scalarptr{})
Tptrscalar = TypeOf(Ptrscalar{})
Tbigptrscalar = TypeOf(Bigptrscalar{})
}
var empty = []byte{}
empty := []byte{}
func TestGCBits(t *testing.T) {
verifyGCBits(t, TypeOf((*byte)(nil)), []byte{1})
verifyGCBits(t, TypeOf(Xscalar{}), empty)
verifyGCBits(t, Tscalar, empty)
@ -7304,95 +7311,7 @@ func TestGCBits(t *testing.T) {
verifyGCBits(t, TypeOf(([][10000]Xscalar)(nil)), lit(1))
verifyGCBits(t, SliceOf(ArrayOf(10000, Tscalar)), lit(1))
if goexperiment.SwissMap {
const bucketCount = abi.SwissMapBucketCount
hdr := make([]byte, bucketCount/goarch.PtrSize)
verifyMapBucket := func(t *testing.T, k, e Type, m any, want []byte) {
verifyGCBits(t, MapBucketOf(k, e), want)
verifyGCBits(t, CachedBucketOf(TypeOf(m)), want)
}
verifyMapBucket(t,
Tscalar, Tptr,
map[Xscalar]Xptr(nil),
join(hdr, rep(bucketCount, lit(0)), rep(bucketCount, lit(1)), lit(1)))
verifyMapBucket(t,
Tscalarptr, Tptr,
map[Xscalarptr]Xptr(nil),
join(hdr, rep(bucketCount, lit(0, 1)), rep(bucketCount, lit(1)), lit(1)))
verifyMapBucket(t, Tint64, Tptr,
map[int64]Xptr(nil),
join(hdr, rep(bucketCount, rep(8/goarch.PtrSize, lit(0))), rep(bucketCount, lit(1)), lit(1)))
verifyMapBucket(t,
Tscalar, Tscalar,
map[Xscalar]Xscalar(nil),
empty)
verifyMapBucket(t,
ArrayOf(2, Tscalarptr), ArrayOf(3, Tptrscalar),
map[[2]Xscalarptr][3]Xptrscalar(nil),
join(hdr, rep(bucketCount*2, lit(0, 1)), rep(bucketCount*3, lit(1, 0)), lit(1)))
verifyMapBucket(t,
ArrayOf(64/goarch.PtrSize, Tscalarptr), ArrayOf(64/goarch.PtrSize, Tptrscalar),
map[[64 / goarch.PtrSize]Xscalarptr][64 / goarch.PtrSize]Xptrscalar(nil),
join(hdr, rep(bucketCount*64/goarch.PtrSize, lit(0, 1)), rep(bucketCount*64/goarch.PtrSize, lit(1, 0)), lit(1)))
verifyMapBucket(t,
ArrayOf(64/goarch.PtrSize+1, Tscalarptr), ArrayOf(64/goarch.PtrSize, Tptrscalar),
map[[64/goarch.PtrSize + 1]Xscalarptr][64 / goarch.PtrSize]Xptrscalar(nil),
join(hdr, rep(bucketCount, lit(1)), rep(bucketCount*64/goarch.PtrSize, lit(1, 0)), lit(1)))
verifyMapBucket(t,
ArrayOf(64/goarch.PtrSize, Tscalarptr), ArrayOf(64/goarch.PtrSize+1, Tptrscalar),
map[[64 / goarch.PtrSize]Xscalarptr][64/goarch.PtrSize + 1]Xptrscalar(nil),
join(hdr, rep(bucketCount*64/goarch.PtrSize, lit(0, 1)), rep(bucketCount, lit(1)), lit(1)))
verifyMapBucket(t,
ArrayOf(64/goarch.PtrSize+1, Tscalarptr), ArrayOf(64/goarch.PtrSize+1, Tptrscalar),
map[[64/goarch.PtrSize + 1]Xscalarptr][64/goarch.PtrSize + 1]Xptrscalar(nil),
join(hdr, rep(bucketCount, lit(1)), rep(bucketCount, lit(1)), lit(1)))
} else {
const bucketCount = abi.OldMapBucketCount
hdr := make([]byte, bucketCount/goarch.PtrSize)
verifyMapBucket := func(t *testing.T, k, e Type, m any, want []byte) {
verifyGCBits(t, MapBucketOf(k, e), want)
verifyGCBits(t, CachedBucketOf(TypeOf(m)), want)
}
verifyMapBucket(t,
Tscalar, Tptr,
map[Xscalar]Xptr(nil),
join(hdr, rep(bucketCount, lit(0)), rep(bucketCount, lit(1)), lit(1)))
verifyMapBucket(t,
Tscalarptr, Tptr,
map[Xscalarptr]Xptr(nil),
join(hdr, rep(bucketCount, lit(0, 1)), rep(bucketCount, lit(1)), lit(1)))
verifyMapBucket(t, Tint64, Tptr,
map[int64]Xptr(nil),
join(hdr, rep(bucketCount, rep(8/goarch.PtrSize, lit(0))), rep(bucketCount, lit(1)), lit(1)))
verifyMapBucket(t,
Tscalar, Tscalar,
map[Xscalar]Xscalar(nil),
empty)
verifyMapBucket(t,
ArrayOf(2, Tscalarptr), ArrayOf(3, Tptrscalar),
map[[2]Xscalarptr][3]Xptrscalar(nil),
join(hdr, rep(bucketCount*2, lit(0, 1)), rep(bucketCount*3, lit(1, 0)), lit(1)))
verifyMapBucket(t,
ArrayOf(64/goarch.PtrSize, Tscalarptr), ArrayOf(64/goarch.PtrSize, Tptrscalar),
map[[64 / goarch.PtrSize]Xscalarptr][64 / goarch.PtrSize]Xptrscalar(nil),
join(hdr, rep(bucketCount*64/goarch.PtrSize, lit(0, 1)), rep(bucketCount*64/goarch.PtrSize, lit(1, 0)), lit(1)))
verifyMapBucket(t,
ArrayOf(64/goarch.PtrSize+1, Tscalarptr), ArrayOf(64/goarch.PtrSize, Tptrscalar),
map[[64/goarch.PtrSize + 1]Xscalarptr][64 / goarch.PtrSize]Xptrscalar(nil),
join(hdr, rep(bucketCount, lit(1)), rep(bucketCount*64/goarch.PtrSize, lit(1, 0)), lit(1)))
verifyMapBucket(t,
ArrayOf(64/goarch.PtrSize, Tscalarptr), ArrayOf(64/goarch.PtrSize+1, Tptrscalar),
map[[64 / goarch.PtrSize]Xscalarptr][64/goarch.PtrSize + 1]Xptrscalar(nil),
join(hdr, rep(bucketCount*64/goarch.PtrSize, lit(0, 1)), rep(bucketCount, lit(1)), lit(1)))
verifyMapBucket(t,
ArrayOf(64/goarch.PtrSize+1, Tscalarptr), ArrayOf(64/goarch.PtrSize+1, Tptrscalar),
map[[64/goarch.PtrSize + 1]Xscalarptr][64/goarch.PtrSize + 1]Xptrscalar(nil),
join(hdr, rep(bucketCount, lit(1)), rep(bucketCount, lit(1)), lit(1)))
}
testGCBitsMap(t)
}
func rep(n int, b []byte) []byte { return bytes.Repeat(b, n) }

View File

@ -0,0 +1,25 @@
// Copyright 2024 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.
//go:build !goexperiment.swissmap
package reflect
import (
"internal/abi"
"unsafe"
)
func MapBucketOf(x, y Type) Type {
return toType(bucketOf(x.common(), y.common()))
}
func CachedBucketOf(m Type) Type {
t := m.(*rtype)
if Kind(t.t.Kind_&abi.KindMask) != Map {
panic("not map")
}
tt := (*mapType)(unsafe.Pointer(t))
return toType(tt.Bucket)
}

View File

@ -0,0 +1,12 @@
// Copyright 2024 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.
//go:build goexperiment.swissmap
package reflect
func MapGroupOf(x, y Type) Type {
grp, _ := groupAndSlotOf(x, y)
return grp
}

View File

@ -91,19 +91,6 @@ var GCBits = gcbits
func gcbits(any) []byte // provided by runtime
func MapBucketOf(x, y Type) Type {
return toType(bucketOf(x.common(), y.common()))
}
func CachedBucketOf(m Type) Type {
t := m.(*rtype)
if Kind(t.t.Kind_&abi.KindMask) != Map {
panic("not map")
}
tt := (*mapType)(unsafe.Pointer(t))
return toType(tt.Bucket)
}
type EmbedWithUnexpMeth struct{}
func (EmbedWithUnexpMeth) f() {}

View File

@ -0,0 +1,60 @@
// 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.
//go:build !goexperiment.swissmap
package reflect_test
import (
"internal/abi"
"internal/goarch"
. "reflect"
"testing"
)
func testGCBitsMap(t *testing.T) {
const bucketCount = abi.OldMapBucketCount
hdr := make([]byte, bucketCount/goarch.PtrSize)
verifyMapBucket := func(t *testing.T, k, e Type, m any, want []byte) {
verifyGCBits(t, MapBucketOf(k, e), want)
verifyGCBits(t, CachedBucketOf(TypeOf(m)), want)
}
verifyMapBucket(t,
Tscalar, Tptr,
map[Xscalar]Xptr(nil),
join(hdr, rep(bucketCount, lit(0)), rep(bucketCount, lit(1)), lit(1)))
verifyMapBucket(t,
Tscalarptr, Tptr,
map[Xscalarptr]Xptr(nil),
join(hdr, rep(bucketCount, lit(0, 1)), rep(bucketCount, lit(1)), lit(1)))
verifyMapBucket(t, Tint64, Tptr,
map[int64]Xptr(nil),
join(hdr, rep(bucketCount, rep(8/goarch.PtrSize, lit(0))), rep(bucketCount, lit(1)), lit(1)))
verifyMapBucket(t,
Tscalar, Tscalar,
map[Xscalar]Xscalar(nil),
empty)
verifyMapBucket(t,
ArrayOf(2, Tscalarptr), ArrayOf(3, Tptrscalar),
map[[2]Xscalarptr][3]Xptrscalar(nil),
join(hdr, rep(bucketCount*2, lit(0, 1)), rep(bucketCount*3, lit(1, 0)), lit(1)))
verifyMapBucket(t,
ArrayOf(64/goarch.PtrSize, Tscalarptr), ArrayOf(64/goarch.PtrSize, Tptrscalar),
map[[64 / goarch.PtrSize]Xscalarptr][64 / goarch.PtrSize]Xptrscalar(nil),
join(hdr, rep(bucketCount*64/goarch.PtrSize, lit(0, 1)), rep(bucketCount*64/goarch.PtrSize, lit(1, 0)), lit(1)))
verifyMapBucket(t,
ArrayOf(64/goarch.PtrSize+1, Tscalarptr), ArrayOf(64/goarch.PtrSize, Tptrscalar),
map[[64/goarch.PtrSize + 1]Xscalarptr][64 / goarch.PtrSize]Xptrscalar(nil),
join(hdr, rep(bucketCount, lit(1)), rep(bucketCount*64/goarch.PtrSize, lit(1, 0)), lit(1)))
verifyMapBucket(t,
ArrayOf(64/goarch.PtrSize, Tscalarptr), ArrayOf(64/goarch.PtrSize+1, Tptrscalar),
map[[64 / goarch.PtrSize]Xscalarptr][64/goarch.PtrSize + 1]Xptrscalar(nil),
join(hdr, rep(bucketCount*64/goarch.PtrSize, lit(0, 1)), rep(bucketCount, lit(1)), lit(1)))
verifyMapBucket(t,
ArrayOf(64/goarch.PtrSize+1, Tscalarptr), ArrayOf(64/goarch.PtrSize+1, Tptrscalar),
map[[64/goarch.PtrSize + 1]Xscalarptr][64/goarch.PtrSize + 1]Xptrscalar(nil),
join(hdr, rep(bucketCount, lit(1)), rep(bucketCount, lit(1)), lit(1)))
}

View File

@ -8,7 +8,7 @@ package reflect
import (
"internal/abi"
"internal/goarch"
"internal/runtime/maps"
"unsafe"
)
@ -55,6 +55,8 @@ func MapOf(key, elem Type) Type {
}
}
group, slot := groupAndSlotOf(key, elem)
// Make a map type.
// Note: flag values must match those used in the TMAP case
// in ../cmd/compile/internal/reflectdata/reflect.go:writeType.
@ -65,32 +67,19 @@ func MapOf(key, elem Type) Type {
mt.Hash = fnv1(etyp.Hash, 'm', byte(ktyp.Hash>>24), byte(ktyp.Hash>>16), byte(ktyp.Hash>>8), byte(ktyp.Hash))
mt.Key = ktyp
mt.Elem = etyp
mt.Bucket = bucketOf(ktyp, etyp)
mt.Group = group.common()
mt.Hasher = func(p unsafe.Pointer, seed uintptr) uintptr {
return typehash(ktyp, p, seed)
}
mt.SlotSize = slot.Size()
mt.ElemOff = slot.Field(1).Offset
mt.Flags = 0
if ktyp.Size_ > abi.SwissMapMaxKeyBytes {
mt.KeySize = uint8(goarch.PtrSize)
mt.Flags |= 1 // indirect key
} else {
mt.KeySize = uint8(ktyp.Size_)
}
if etyp.Size_ > abi.SwissMapMaxElemBytes {
mt.ValueSize = uint8(goarch.PtrSize)
mt.Flags |= 2 // indirect value
} else {
mt.ValueSize = uint8(etyp.Size_)
}
mt.BucketSize = uint16(mt.Bucket.Size_)
if isReflexive(ktyp) {
mt.Flags |= 4
}
// TODO(prattmic): indirect key/elem flags
if needKeyUpdate(ktyp) {
mt.Flags |= 8
mt.Flags |= abi.SwissMapNeedKeyUpdate
}
if hashMightPanic(ktyp) {
mt.Flags |= 16
mt.Flags |= abi.SwissMapHashMightPanic
}
mt.PtrToThis = 0
@ -98,67 +87,41 @@ func MapOf(key, elem Type) Type {
return ti.(Type)
}
func bucketOf(ktyp, etyp *abi.Type) *abi.Type {
if ktyp.Size_ > abi.SwissMapMaxKeyBytes {
ktyp = ptrTo(ktyp)
func groupAndSlotOf(ktyp, etyp Type) (Type, Type) {
// TODO(prattmic): indirect key/elem flags
// type group struct {
// ctrl uint64
// slots [abi.SwissMapGroupSlots]struct {
// key keyType
// elem elemType
// }
// }
fields := []StructField{
{
Name: "Key",
Type: ktyp,
},
{
Name: "Elem",
Type: etyp,
},
}
if etyp.Size_ > abi.SwissMapMaxElemBytes {
etyp = ptrTo(etyp)
slot := StructOf(fields)
fields = []StructField{
{
Name: "Ctrl",
Type: TypeFor[uint64](),
},
{
Name: "Slots",
Type: ArrayOf(abi.SwissMapGroupSlots, slot),
},
}
// Prepare GC data if any.
// A bucket is at most bucketSize*(1+maxKeySize+maxValSize)+ptrSize bytes,
// or 2064 bytes, or 258 pointer-size words, or 33 bytes of pointer bitmap.
// Note that since the key and value are known to be <= 128 bytes,
// they're guaranteed to have bitmaps instead of GC programs.
var gcdata *byte
var ptrdata uintptr
size := abi.SwissMapBucketCount*(1+ktyp.Size_+etyp.Size_) + goarch.PtrSize
if size&uintptr(ktyp.Align_-1) != 0 || size&uintptr(etyp.Align_-1) != 0 {
panic("reflect: bad size computation in MapOf")
}
if ktyp.Pointers() || etyp.Pointers() {
nptr := (abi.SwissMapBucketCount*(1+ktyp.Size_+etyp.Size_) + goarch.PtrSize) / goarch.PtrSize
n := (nptr + 7) / 8
// Runtime needs pointer masks to be a multiple of uintptr in size.
n = (n + goarch.PtrSize - 1) &^ (goarch.PtrSize - 1)
mask := make([]byte, n)
base := uintptr(abi.SwissMapBucketCount / goarch.PtrSize)
if ktyp.Pointers() {
emitGCMask(mask, base, ktyp, abi.SwissMapBucketCount)
}
base += abi.SwissMapBucketCount * ktyp.Size_ / goarch.PtrSize
if etyp.Pointers() {
emitGCMask(mask, base, etyp, abi.SwissMapBucketCount)
}
base += abi.SwissMapBucketCount * etyp.Size_ / goarch.PtrSize
word := base
mask[word/8] |= 1 << (word % 8)
gcdata = &mask[0]
ptrdata = (word + 1) * goarch.PtrSize
// overflow word must be last
if ptrdata != size {
panic("reflect: bad layout computation in MapOf")
}
}
b := &abi.Type{
Align_: goarch.PtrSize,
Size_: size,
Kind_: abi.Struct,
PtrBytes: ptrdata,
GCData: gcdata,
}
s := "bucket(" + stringFor(ktyp) + "," + stringFor(etyp) + ")"
b.Str = resolveReflectName(newName(s, "", false, false))
return b
group := StructOf(fields)
return group, slot
}
var stringType = rtypeOf("")
@ -181,7 +144,8 @@ func (v Value) MapIndex(key Value) Value {
var e unsafe.Pointer
// TODO(#54766): temporarily disable specialized variants.
if false && (tt.Key == stringType || key.kind() == String) && tt.Key == key.typ() && tt.Elem.Size() <= abi.SwissMapMaxElemBytes {
//if (tt.Key == stringType || key.kind() == String) && tt.Key == key.typ() && tt.Elem.Size() <= abi.SwissMapMaxElemBytes {
if false {
k := *(*string)(key.ptr)
e = mapaccess_faststr(v.typ(), v.pointer(), k)
} else {
@ -219,12 +183,12 @@ func (v Value) MapKeys() []Value {
if m != nil {
mlen = maplen(m)
}
var it hiter
var it maps.Iter
mapiterinit(v.typ(), m, &it)
a := make([]Value, mlen)
var i int
for i = 0; i < len(a); i++ {
key := it.key
key := it.Key()
if key == nil {
// Someone deleted an entry from the map since we
// called maplen above. It's a data race, but nothing
@ -237,45 +201,23 @@ func (v Value) MapKeys() []Value {
return a[:i]
}
// hiter's structure matches runtime.hiter's structure.
// Having a clone here allows us to embed a map iterator
// inside type MapIter so that MapIters can be re-used
// without doing any allocations.
type hiter struct {
key unsafe.Pointer
elem unsafe.Pointer
t unsafe.Pointer
h unsafe.Pointer
buckets unsafe.Pointer
bptr unsafe.Pointer
overflow *[]unsafe.Pointer
oldoverflow *[]unsafe.Pointer
startBucket uintptr
offset uint8
wrapped bool
B uint8
i uint8
bucket uintptr
checkBucket uintptr
}
func (h *hiter) initialized() bool {
return h.t != nil
}
// A MapIter is an iterator for ranging over a map.
// See [Value.MapRange].
type MapIter struct {
m Value
hiter hiter
hiter maps.Iter
}
// TODO(prattmic): only for sharing the linkname declarations with old maps.
// Remove with old maps.
type hiter = maps.Iter
// Key returns the key of iter's current map entry.
func (iter *MapIter) Key() Value {
if !iter.hiter.initialized() {
if !iter.hiter.Initialized() {
panic("MapIter.Key called before Next")
}
iterkey := iter.hiter.key
iterkey := iter.hiter.Key()
if iterkey == nil {
panic("MapIter.Key called on exhausted iterator")
}
@ -290,10 +232,10 @@ func (iter *MapIter) Key() Value {
// As in Go, the key must be assignable to v's type and
// must not be derived from an unexported field.
func (v Value) SetIterKey(iter *MapIter) {
if !iter.hiter.initialized() {
if !iter.hiter.Initialized() {
panic("reflect: Value.SetIterKey called before Next")
}
iterkey := iter.hiter.key
iterkey := iter.hiter.Key()
if iterkey == nil {
panic("reflect: Value.SetIterKey called on exhausted iterator")
}
@ -315,10 +257,10 @@ func (v Value) SetIterKey(iter *MapIter) {
// Value returns the value of iter's current map entry.
func (iter *MapIter) Value() Value {
if !iter.hiter.initialized() {
if !iter.hiter.Initialized() {
panic("MapIter.Value called before Next")
}
iterelem := iter.hiter.elem
iterelem := iter.hiter.Elem()
if iterelem == nil {
panic("MapIter.Value called on exhausted iterator")
}
@ -333,10 +275,10 @@ func (iter *MapIter) Value() Value {
// As in Go, the value must be assignable to v's type and
// must not be derived from an unexported field.
func (v Value) SetIterValue(iter *MapIter) {
if !iter.hiter.initialized() {
if !iter.hiter.Initialized() {
panic("reflect: Value.SetIterValue called before Next")
}
iterelem := iter.hiter.elem
iterelem := iter.hiter.Elem()
if iterelem == nil {
panic("reflect: Value.SetIterValue called on exhausted iterator")
}
@ -363,15 +305,15 @@ func (iter *MapIter) Next() bool {
if !iter.m.IsValid() {
panic("MapIter.Next called on an iterator that does not have an associated map Value")
}
if !iter.hiter.initialized() {
if !iter.hiter.Initialized() {
mapiterinit(iter.m.typ(), iter.m.pointer(), &iter.hiter)
} else {
if iter.hiter.key == nil {
if iter.hiter.Key() == nil {
panic("MapIter.Next called on exhausted iterator")
}
mapiternext(&iter.hiter)
}
return iter.hiter.key != nil
return iter.hiter.Key() != nil
}
// Reset modifies iter to iterate over v.
@ -383,7 +325,7 @@ func (iter *MapIter) Reset(v Value) {
v.mustBe(Map)
}
iter.m = v
iter.hiter = hiter{}
iter.hiter = maps.Iter{}
}
// MapRange returns a range iterator for a map.
@ -425,7 +367,8 @@ func (v Value) SetMapIndex(key, elem Value) {
tt := (*mapType)(unsafe.Pointer(v.typ()))
// TODO(#54766): temporarily disable specialized variants.
if false && (tt.Key == stringType || key.kind() == String) && tt.Key == key.typ() && tt.Elem.Size() <= abi.SwissMapMaxElemBytes {
//if (tt.Key == stringType || key.kind() == String) && tt.Key == key.typ() && tt.Elem.Size() <= abi.SwissMapMaxElemBytes {
if false {
k := *(*string)(key.ptr)
if elem.typ() == nil {
mapdelete_faststr(v.typ(), v.pointer(), k)

View File

@ -0,0 +1,30 @@
// 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.
//go:build goexperiment.swissmap
package reflect_test
import (
"reflect"
"testing"
)
func testGCBitsMap(t *testing.T) {
// Unlike old maps, we don't manually construct GC data for swiss maps,
// instead using the public reflect API in groupAndSlotOf.
}
// See also runtime_test.TestGroupSizeZero.
func TestGroupSizeZero(t *testing.T) {
st := reflect.TypeFor[struct{}]()
grp := reflect.MapGroupOf(st, st)
// internal/runtime/maps when create pointers to slots, even if slots
// are size 0. We should have reserved an extra word to ensure that
// pointers to the zero-size type at the end of group are valid.
if grp.Size() <= 8 {
t.Errorf("Group size got %d want >8", grp.Size())
}
}

View File

@ -11,6 +11,12 @@ import (
"unsafe"
)
const RuntimeHmapSize = unsafe.Sizeof(hmap{})
func OverLoadFactor(count int, B uint8) bool {
return overLoadFactor(count, B)
}
func MapBucketsCount(m map[int]int) int {
h := *(**hmap)(unsafe.Pointer(&m))
return 1 << h.B

View File

@ -6,53 +6,6 @@
package runtime
import (
"internal/abi"
"unsafe"
)
func MapBucketsCount(m map[int]int) int {
h := *(**hmap)(unsafe.Pointer(&m))
return 1 << h.B
}
func MapBucketsPointerIsNil(m map[int]int) bool {
h := *(**hmap)(unsafe.Pointer(&m))
return h.buckets == nil
}
func MapTombstoneCheck(m map[int]int) {
// Make sure emptyOne and emptyRest are distributed correctly.
// We should have a series of filled and emptyOne cells, followed by
// a series of emptyRest cells.
h := *(**hmap)(unsafe.Pointer(&m))
i := any(m)
t := *(**maptype)(unsafe.Pointer(&i))
for x := 0; x < 1<<h.B; x++ {
b0 := (*bmap)(add(h.buckets, uintptr(x)*uintptr(t.BucketSize)))
n := 0
for b := b0; b != nil; b = b.overflow(t) {
for i := 0; i < abi.SwissMapBucketCount; i++ {
if b.tophash[i] != emptyRest {
n++
}
}
}
k := 0
for b := b0; b != nil; b = b.overflow(t) {
for i := 0; i < abi.SwissMapBucketCount; i++ {
if k < n && b.tophash[i] == emptyRest {
panic("early emptyRest")
}
if k >= n && b.tophash[i] != emptyRest {
panic("late non-emptyRest")
}
if k == n-1 && b.tophash[i] == emptyOne {
panic("last non-emptyRest entry is emptyOne")
}
k++
}
}
}
// TODO
}

View File

@ -481,12 +481,6 @@ func (rw *RWMutex) Unlock() {
rw.rw.unlock()
}
const RuntimeHmapSize = unsafe.Sizeof(hmap{})
func OverLoadFactor(count int, B uint8) bool {
return overLoadFactor(count, B)
}
func LockOSCounts() (external, internal uint32) {
gp := getg()
if gp.m.lockedExt+gp.m.lockedInt == 0 {

View File

@ -7,29 +7,31 @@
package runtime
import (
"internal/abi"
"internal/runtime/maps"
"unsafe"
)
func mapaccess1_fast32(t *maptype, h *hmap, key uint32) unsafe.Pointer {
func mapaccess1_fast32(t *abi.SwissMapType, m *maps.Map, key uint32) unsafe.Pointer {
throw("mapaccess1_fast32 unimplemented")
panic("unreachable")
}
func mapaccess2_fast32(t *maptype, h *hmap, key uint32) (unsafe.Pointer, bool) {
func mapaccess2_fast32(t *abi.SwissMapType, m *maps.Map, key uint32) (unsafe.Pointer, bool) {
throw("mapaccess2_fast32 unimplemented")
panic("unreachable")
}
func mapassign_fast32(t *maptype, h *hmap, key uint32) unsafe.Pointer {
func mapassign_fast32(t *abi.SwissMapType, m *maps.Map, key uint32) unsafe.Pointer {
throw("mapassign_fast32 unimplemented")
panic("unreachable")
}
func mapassign_fast32ptr(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
func mapassign_fast32ptr(t *abi.SwissMapType, m *maps.Map, key unsafe.Pointer) unsafe.Pointer {
throw("mapassign_fast32ptr unimplemented")
panic("unreachable")
}
func mapdelete_fast32(t *maptype, h *hmap, key uint32) {
func mapdelete_fast32(t *abi.SwissMapType, m *maps.Map, key uint32) {
throw("mapdelete_fast32 unimplemented")
}

View File

@ -7,29 +7,31 @@
package runtime
import (
"internal/abi"
"internal/runtime/maps"
"unsafe"
)
func mapaccess1_fast64(t *maptype, h *hmap, key uint64) unsafe.Pointer {
func mapaccess1_fast64(t *abi.SwissMapType, m *maps.Map, key uint64) unsafe.Pointer {
throw("mapaccess1_fast64 unimplemented")
panic("unreachable")
}
func mapaccess2_fast64(t *maptype, h *hmap, key uint64) (unsafe.Pointer, bool) {
func mapaccess2_fast64(t *abi.SwissMapType, m *maps.Map, key uint64) (unsafe.Pointer, bool) {
throw("mapaccess2_fast64 unimplemented")
panic("unreachable")
}
func mapassign_fast64(t *maptype, h *hmap, key uint64) unsafe.Pointer {
func mapassign_fast64(t *abi.SwissMapType, m *maps.Map, key uint64) unsafe.Pointer {
throw("mapassign_fast64 unimplemented")
panic("unreachable")
}
func mapassign_fast64ptr(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
func mapassign_fast64ptr(t *abi.SwissMapType, m *maps.Map, key unsafe.Pointer) unsafe.Pointer {
throw("mapassign_fast64ptr unimplemented")
panic("unreachable")
}
func mapdelete_fast64(t *maptype, h *hmap, key uint64) {
func mapdelete_fast64(t *abi.SwissMapType, m *maps.Map, key uint64) {
throw("mapdelete_fast64 unimplemented")
}

View File

@ -7,24 +7,26 @@
package runtime
import (
"internal/abi"
"internal/runtime/maps"
"unsafe"
)
func mapaccess1_faststr(t *maptype, h *hmap, ky string) unsafe.Pointer {
func mapaccess1_faststr(t *abi.SwissMapType, m *maps.Map, ky string) unsafe.Pointer {
throw("mapaccess1_faststr unimplemented")
panic("unreachable")
}
func mapaccess2_faststr(t *maptype, h *hmap, ky string) (unsafe.Pointer, bool) {
func mapaccess2_faststr(t *abi.SwissMapType, m *maps.Map, ky string) (unsafe.Pointer, bool) {
throw("mapaccess2_faststr unimplemented")
panic("unreachable")
}
func mapassign_faststr(t *maptype, h *hmap, s string) unsafe.Pointer {
func mapassign_faststr(t *abi.SwissMapType, m *maps.Map, s string) unsafe.Pointer {
throw("mapassign_faststr unimplemented")
panic("unreachable")
}
func mapdelete_faststr(t *maptype, h *hmap, ky string) {
func mapdelete_faststr(t *abi.SwissMapType, m *maps.Map, ky string) {
throw("mapdelete_faststr unimplemented")
}

View File

@ -8,11 +8,37 @@ package runtime_test
import (
"internal/abi"
"internal/goarch"
"runtime"
"slices"
"testing"
)
func TestHmapSize(t *testing.T) {
// The structure of hmap is defined in runtime/map.go
// and in cmd/compile/internal/reflectdata/map.go and must be in sync.
// The size of hmap should be 48 bytes on 64 bit and 28 bytes on 32 bit platforms.
var hmapSize = uintptr(8 + 5*goarch.PtrSize)
if runtime.RuntimeHmapSize != hmapSize {
t.Errorf("sizeof(runtime.hmap{})==%d, want %d", runtime.RuntimeHmapSize, hmapSize)
}
}
func TestLoadFactor(t *testing.T) {
for b := uint8(0); b < 20; b++ {
count := 13 * (1 << b) / 2 // 6.5
if b == 0 {
count = 8
}
if runtime.OverLoadFactor(count, b) {
t.Errorf("OverLoadFactor(%d,%d)=true, want false", count, b)
}
if !runtime.OverLoadFactor(count+1, b) {
t.Errorf("OverLoadFactor(%d,%d)=false, want true", count+1, b)
}
}
}
func TestMapIterOrder(t *testing.T) {
sizes := []int{3, 7, 9, 15}
if abi.OldMapBucketCountBits >= 5 {

File diff suppressed because it is too large Load Diff

View File

@ -8,17 +8,41 @@ package runtime_test
import (
"internal/abi"
"runtime"
"internal/goarch"
"internal/runtime/maps"
"slices"
"testing"
"unsafe"
)
func TestHmapSize(t *testing.T) {
// The structure of Map is defined in internal/runtime/maps/map.go
// and in cmd/compile/internal/reflectdata/map_swiss.go and must be in sync.
// The size of Map should be 72 bytes on 64 bit and 56 bytes on 32 bit platforms.
wantSize := uintptr(4*goarch.PtrSize + 5*8)
gotSize := unsafe.Sizeof(maps.Map{})
if gotSize != wantSize {
t.Errorf("sizeof(maps.Map{})==%d, want %d", gotSize, wantSize)
}
}
// See also reflect_test.TestGroupSizeZero.
func TestGroupSizeZero(t *testing.T) {
var m map[struct{}]struct{}
mTyp := abi.TypeOf(m)
mt := (*abi.SwissMapType)(unsafe.Pointer(mTyp))
// internal/runtime/maps when create pointers to slots, even if slots
// are size 0. The compiler should have reserved an extra word to
// ensure that pointers to the zero-size type at the end of group are
// valid.
if mt.Group.Size() <= 8 {
t.Errorf("Group size got %d want >8", mt.Group.Size())
}
}
func TestMapIterOrder(t *testing.T) {
sizes := []int{3, 7, 9, 15}
if abi.SwissMapBucketCountBits >= 5 {
// it gets flaky (often only one iteration order) at size 3 when abi.MapBucketCountBits >=5.
t.Fatalf("This test becomes flaky if abi.MapBucketCountBits(=%d) is 5 or larger", abi.SwissMapBucketCountBits)
}
for _, n := range sizes {
for i := 0; i < 1000; i++ {
// Make m be {0: true, 1: true, ..., n-1: true}.
@ -50,139 +74,6 @@ func TestMapIterOrder(t *testing.T) {
}
}
const bs = abi.SwissMapBucketCount
// belowOverflow should be a pretty-full pair of buckets;
// atOverflow is 1/8 bs larger = 13/8 buckets or two buckets
// that are 13/16 full each, which is the overflow boundary.
// Adding one to that should ensure overflow to the next higher size.
const (
belowOverflow = bs * 3 / 2 // 1.5 bs = 2 buckets @ 75%
atOverflow = belowOverflow + bs/8 // 2 buckets at 13/16 fill.
)
var mapBucketTests = [...]struct {
n int // n is the number of map elements
noescape int // number of expected buckets for non-escaping map
escape int // number of expected buckets for escaping map
}{
{-(1 << 30), 1, 1},
{-1, 1, 1},
{0, 1, 1},
{1, 1, 1},
{bs, 1, 1},
{bs + 1, 2, 2},
{belowOverflow, 2, 2}, // 1.5 bs = 2 buckets @ 75%
{atOverflow + 1, 4, 4}, // 13/8 bs + 1 == overflow to 4
{2 * belowOverflow, 4, 4}, // 3 bs = 4 buckets @75%
{2*atOverflow + 1, 8, 8}, // 13/4 bs + 1 = overflow to 8
{4 * belowOverflow, 8, 8}, // 6 bs = 8 buckets @ 75%
{4*atOverflow + 1, 16, 16}, // 13/2 bs + 1 = overflow to 16
}
func TestMapBuckets(t *testing.T) {
// Test that maps of different sizes have the right number of buckets.
// Non-escaping maps with small buckets (like map[int]int) never
// have a nil bucket pointer due to starting with preallocated buckets
// on the stack. Escaping maps start with a non-nil bucket pointer if
// hint size is above bucketCnt and thereby have more than one bucket.
// These tests depend on bucketCnt and loadFactor* in map.go.
t.Run("mapliteral", func(t *testing.T) {
for _, tt := range mapBucketTests {
localMap := map[int]int{}
if runtime.MapBucketsPointerIsNil(localMap) {
t.Errorf("no escape: buckets pointer is nil for non-escaping map")
}
for i := 0; i < tt.n; i++ {
localMap[i] = i
}
if got := runtime.MapBucketsCount(localMap); got != tt.noescape {
t.Errorf("no escape: n=%d want %d buckets, got %d", tt.n, tt.noescape, got)
}
escapingMap := runtime.Escape(map[int]int{})
if count := runtime.MapBucketsCount(escapingMap); count > 1 && runtime.MapBucketsPointerIsNil(escapingMap) {
t.Errorf("escape: buckets pointer is nil for n=%d buckets", count)
}
for i := 0; i < tt.n; i++ {
escapingMap[i] = i
}
if got := runtime.MapBucketsCount(escapingMap); got != tt.escape {
t.Errorf("escape n=%d want %d buckets, got %d", tt.n, tt.escape, got)
}
}
})
t.Run("nohint", func(t *testing.T) {
for _, tt := range mapBucketTests {
localMap := make(map[int]int)
if runtime.MapBucketsPointerIsNil(localMap) {
t.Errorf("no escape: buckets pointer is nil for non-escaping map")
}
for i := 0; i < tt.n; i++ {
localMap[i] = i
}
if got := runtime.MapBucketsCount(localMap); got != tt.noescape {
t.Errorf("no escape: n=%d want %d buckets, got %d", tt.n, tt.noescape, got)
}
escapingMap := runtime.Escape(make(map[int]int))
if count := runtime.MapBucketsCount(escapingMap); count > 1 && runtime.MapBucketsPointerIsNil(escapingMap) {
t.Errorf("escape: buckets pointer is nil for n=%d buckets", count)
}
for i := 0; i < tt.n; i++ {
escapingMap[i] = i
}
if got := runtime.MapBucketsCount(escapingMap); got != tt.escape {
t.Errorf("escape: n=%d want %d buckets, got %d", tt.n, tt.escape, got)
}
}
})
t.Run("makemap", func(t *testing.T) {
for _, tt := range mapBucketTests {
localMap := make(map[int]int, tt.n)
if runtime.MapBucketsPointerIsNil(localMap) {
t.Errorf("no escape: buckets pointer is nil for non-escaping map")
}
for i := 0; i < tt.n; i++ {
localMap[i] = i
}
if got := runtime.MapBucketsCount(localMap); got != tt.noescape {
t.Errorf("no escape: n=%d want %d buckets, got %d", tt.n, tt.noescape, got)
}
escapingMap := runtime.Escape(make(map[int]int, tt.n))
if count := runtime.MapBucketsCount(escapingMap); count > 1 && runtime.MapBucketsPointerIsNil(escapingMap) {
t.Errorf("escape: buckets pointer is nil for n=%d buckets", count)
}
for i := 0; i < tt.n; i++ {
escapingMap[i] = i
}
if got := runtime.MapBucketsCount(escapingMap); got != tt.escape {
t.Errorf("escape: n=%d want %d buckets, got %d", tt.n, tt.escape, got)
}
}
})
t.Run("makemap64", func(t *testing.T) {
for _, tt := range mapBucketTests {
localMap := make(map[int]int, int64(tt.n))
if runtime.MapBucketsPointerIsNil(localMap) {
t.Errorf("no escape: buckets pointer is nil for non-escaping map")
}
for i := 0; i < tt.n; i++ {
localMap[i] = i
}
if got := runtime.MapBucketsCount(localMap); got != tt.noescape {
t.Errorf("no escape: n=%d want %d buckets, got %d", tt.n, tt.noescape, got)
}
escapingMap := runtime.Escape(make(map[int]int, tt.n))
if count := runtime.MapBucketsCount(escapingMap); count > 1 && runtime.MapBucketsPointerIsNil(escapingMap) {
t.Errorf("escape: buckets pointer is nil for n=%d buckets", count)
}
for i := 0; i < tt.n; i++ {
escapingMap[i] = i
}
if got := runtime.MapBucketsCount(escapingMap); got != tt.escape {
t.Errorf("escape: n=%d want %d buckets, got %d", tt.n, tt.escape, got)
}
}
})
t.Skipf("todo")
}

View File

@ -6,7 +6,7 @@ package runtime_test
import (
"fmt"
"internal/goarch"
"internal/goexperiment"
"internal/testenv"
"math"
"os"
@ -20,17 +20,6 @@ import (
"unsafe"
)
func TestHmapSize(t *testing.T) {
// The structure of hmap is defined in runtime/map.go
// and in cmd/compile/internal/gc/reflect.go and must be in sync.
// The size of hmap should be 48 bytes on 64 bit and 28 bytes on 32 bit platforms.
var hmapSize = uintptr(8 + 5*goarch.PtrSize)
if runtime.RuntimeHmapSize != hmapSize {
t.Errorf("sizeof(runtime.hmap{})==%d, want %d", runtime.RuntimeHmapSize, hmapSize)
}
}
// negative zero is a good test because:
// 1. 0 and -0 are equal, yet have distinct representations.
// 2. 0 is represented as all zeros, -0 isn't.
@ -430,6 +419,12 @@ func TestEmptyKeyAndValue(t *testing.T) {
if len(a) != 1 {
t.Errorf("empty value insert problem")
}
if len(b) != 1 {
t.Errorf("empty key insert problem")
}
if len(c) != 1 {
t.Errorf("empty key+value insert problem")
}
if b[empty{}] != 1 {
t.Errorf("empty key returned wrong value")
}
@ -668,33 +663,37 @@ func BenchmarkMapPop10000(b *testing.B) { benchmarkMapPop(b, 10000) }
var testNonEscapingMapVariable int = 8
func TestNonEscapingMap(t *testing.T) {
if goexperiment.SwissMap {
t.Skip("TODO(go.dev/issue/54766): implement stack allocated maps")
}
n := testing.AllocsPerRun(1000, func() {
m := map[int]int{}
m[0] = 0
})
if n != 0 {
t.Fatalf("mapliteral: want 0 allocs, got %v", n)
t.Errorf("mapliteral: want 0 allocs, got %v", n)
}
n = testing.AllocsPerRun(1000, func() {
m := make(map[int]int)
m[0] = 0
})
if n != 0 {
t.Fatalf("no hint: want 0 allocs, got %v", n)
t.Errorf("no hint: want 0 allocs, got %v", n)
}
n = testing.AllocsPerRun(1000, func() {
m := make(map[int]int, 8)
m[0] = 0
})
if n != 0 {
t.Fatalf("with small hint: want 0 allocs, got %v", n)
t.Errorf("with small hint: want 0 allocs, got %v", n)
}
n = testing.AllocsPerRun(1000, func() {
m := make(map[int]int, testNonEscapingMapVariable)
m[0] = 0
})
if n != 0 {
t.Fatalf("with variable hint: want 0 allocs, got %v", n)
t.Errorf("with variable hint: want 0 allocs, got %v", n)
}
}
@ -1246,22 +1245,11 @@ func TestEmptyMapWithInterfaceKey(t *testing.T) {
})
}
func TestLoadFactor(t *testing.T) {
for b := uint8(0); b < 20; b++ {
count := 13 * (1 << b) / 2 // 6.5
if b == 0 {
count = 8
}
if runtime.OverLoadFactor(count, b) {
t.Errorf("OverLoadFactor(%d,%d)=true, want false", count, b)
}
if !runtime.OverLoadFactor(count+1, b) {
t.Errorf("OverLoadFactor(%d,%d)=false, want true", count+1, b)
}
}
}
func TestMapKeys(t *testing.T) {
if goexperiment.SwissMap {
t.Skip("mapkeys not implemented for swissmaps")
}
type key struct {
s string
pad [128]byte // sizeof(key) > abi.MapMaxKeyBytes
@ -1277,6 +1265,10 @@ func TestMapKeys(t *testing.T) {
}
func TestMapValues(t *testing.T) {
if goexperiment.SwissMap {
t.Skip("mapvalues not implemented for swissmaps")
}
type val struct {
s string
pad [128]byte // sizeof(val) > abi.MapMaxElemBytes

View File

@ -9,6 +9,7 @@ import (
"flag"
"fmt"
"internal/abi"
"internal/goexperiment"
"internal/testenv"
"os"
"os/exec"
@ -185,6 +186,9 @@ func TestGdbPythonCgo(t *testing.T) {
}
func testGdbPython(t *testing.T, cgo bool) {
if goexperiment.SwissMap {
t.Skip("TODO(prattmic): swissmap DWARF")
}
if cgo {
testenv.MustHaveCGO(t)
}
@ -527,6 +531,10 @@ func main() {
// TestGdbAutotmpTypes ensures that types of autotmp variables appear in .debug_info
// See bug #17830.
func TestGdbAutotmpTypes(t *testing.T) {
if goexperiment.SwissMap {
t.Skip("TODO(prattmic): swissmap DWARF")
}
checkGdbEnvironment(t)
t.Parallel()
checkGdbVersion(t)

View File

@ -1,5 +1,7 @@
// run
//go:build !goexperiment.swissmap
// 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.

View File

@ -438,7 +438,7 @@ func f28(b bool) {
func f29(b bool) {
if b {
for k := range m { // ERROR "live at call to mapiterinit: .autotmp_[0-9]+$" "live at call to mapiternext: .autotmp_[0-9]+$" "stack object .autotmp_[0-9]+ runtime.hiter$"
for k := range m { // ERROR "live at call to mapiterinit: .autotmp_[0-9]+$" "live at call to mapiternext: .autotmp_[0-9]+$" "stack object .autotmp_[0-9]+ (runtime.hiter|internal/runtime/maps.Iter)$"
printstring(k) // ERROR "live at call to printstring: .autotmp_[0-9]+$"
}
}
@ -647,7 +647,7 @@ func bad40() {
func good40() {
ret := T40{} // ERROR "stack object ret T40$"
ret.m = make(map[int]int) // ERROR "live at call to rand32: .autotmp_[0-9]+$" "stack object .autotmp_[0-9]+ runtime.hmap$"
ret.m = make(map[int]int) // ERROR "live at call to rand32: .autotmp_[0-9]+$" "stack object .autotmp_[0-9]+ (runtime.hmap|internal/runtime/maps.table)$"
t := &ret
printnl() // ERROR "live at call to printnl: ret$"
// Note: ret is live at the printnl because the compiler moves &ret

View File

@ -27,14 +27,14 @@ func newT40() *T40 {
}
func bad40() {
t := newT40() // ERROR "stack object ret T40$" "stack object .autotmp_[0-9]+ runtime.hmap$"
t := newT40() // ERROR "stack object ret T40$" "stack object .autotmp_[0-9]+ (runtime.hmap|internal/runtime/maps.table)$"
printnl() // ERROR "live at call to printnl: ret$"
useT40(t)
}
func good40() {
ret := T40{} // ERROR "stack object ret T40$"
ret.m = make(map[int]int, 42) // ERROR "stack object .autotmp_[0-9]+ runtime.hmap$"
ret.m = make(map[int]int, 42) // ERROR "stack object .autotmp_[0-9]+ (runtime.hmap|internal/runtime/maps.table)$"
t := &ret
printnl() // ERROR "live at call to printnl: ret$"
useT40(t)

View File

@ -434,7 +434,7 @@ func f28(b bool) {
func f29(b bool) {
if b {
for k := range m { // ERROR "live at call to mapiterinit: .autotmp_[0-9]+$" "live at call to mapiternext: .autotmp_[0-9]+$" "stack object .autotmp_[0-9]+ runtime.hiter$"
for k := range m { // ERROR "live at call to mapiterinit: .autotmp_[0-9]+$" "live at call to mapiternext: .autotmp_[0-9]+$" "stack object .autotmp_[0-9]+ (runtime.hiter|internal/runtime/maps.Iter)$"
printstring(k) // ERROR "live at call to printstring: .autotmp_[0-9]+$"
}
}
@ -641,16 +641,6 @@ func bad40() {
printnl()
}
func good40() {
ret := T40{} // ERROR "stack object ret T40$"
ret.m = make(map[int]int) // ERROR "live at call to rand32: .autotmp_[0-9]+$" "stack object .autotmp_[0-9]+ runtime.hmap$"
t := &ret
printnl() // ERROR "live at call to printnl: ret$"
// Note: ret is live at the printnl because the compiler moves &ret
// from before the printnl to after.
useT40(t)
}
func ddd1(x, y *int) { // ERROR "live at entry to ddd1: x y$"
ddd2(x, y) // ERROR "stack object .autotmp_[0-9]+ \[2\]\*int$"
printnl()

View File

@ -36,3 +36,22 @@ func f17c() {
}
func f17d() *byte
func printnl()
type T40 struct {
m map[int]int
}
//go:noescape
func useT40(*T40)
func good40() {
ret := T40{} // ERROR "stack object ret T40$"
ret.m = make(map[int]int) // ERROR "live at call to rand32: .autotmp_[0-9]+$" "stack object .autotmp_[0-9]+ runtime.hmap$"
t := &ret
printnl() // ERROR "live at call to printnl: ret$"
// Note: ret is live at the printnl because the compiler moves &ret
// from before the printnl to after.
useT40(t)
}

View File

@ -38,3 +38,22 @@ func f17c() {
}
func f17d() *byte
func printnl()
type T40 struct {
m map[int]int
}
//go:noescape
func useT40(*T40)
func good40() {
ret := T40{} // ERROR "stack object ret T40$"
ret.m = make(map[int]int) // ERROR "stack object .autotmp_[0-9]+ internal/runtime/maps.table$"
t := &ret
printnl() // ERROR "live at call to printnl: ret$"
// Note: ret is live at the printnl because the compiler moves &ret
// from before the printnl to after.
useT40(t)
}