internal/runtime/maps: proper capacity hint handling

When given a hint size, set the initial capacity large enough to avoid
requiring growth in the average case.

When not given a hint (or given 0), don't allocate anything at all.

For #54766.

Cq-Include-Trybots: luci.golang.try:gotip-linux-amd64-longtest-swissmap
Change-Id: I8844fc652b8d2d4e5136cd56f7e78999a07fe381
Reviewed-on: https://go-review.googlesource.com/c/go/+/616457
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Keith Randall <khr@golang.org>
Reviewed-by: Keith Randall <khr@google.com>
Auto-Submit: Michael Pratt <mpratt@google.com>
This commit is contained in:
Michael Pratt 2024-09-12 10:44:38 -04:00 committed by Gopher Robot
parent 89d7f03172
commit 3b424cfa9d
12 changed files with 160 additions and 129 deletions

View file

@ -89,8 +89,8 @@ var depsRules = `
< internal/runtime/syscall < internal/runtime/syscall
< internal/runtime/atomic < internal/runtime/atomic
< internal/runtime/exithook < internal/runtime/exithook
< internal/runtime/maps
< internal/runtime/math < internal/runtime/math
< internal/runtime/maps
< runtime < runtime
< internal/race < internal/race
< sync/atomic < sync/atomic

View file

@ -18,9 +18,13 @@ var AlignUpPow2 = alignUpPow2
const MaxTableCapacity = maxTableCapacity const MaxTableCapacity = maxTableCapacity
const MaxAvgGroupLoad = maxAvgGroupLoad const MaxAvgGroupLoad = maxAvgGroupLoad
func NewTestMap[K comparable, V any](length uint64) (*Map, *abi.SwissMapType) { // This isn't equivalent to runtime.maxAlloc. It is fine for basic testing but
// we can't properly test hint alloc overflows with this.
const maxAllocTest = 1 << 30
func NewTestMap[K comparable, V any](hint uintptr) (*Map, *abi.SwissMapType) {
mt := newTestMapType[K, V]() mt := newTestMapType[K, V]()
return NewMap(mt, length), mt return NewMap(mt, hint, maxAllocTest), mt
} }
func (m *Map) TableCount() int { func (m *Map) TableCount() int {

View file

@ -8,6 +8,7 @@ package maps
import ( import (
"internal/abi" "internal/abi"
"internal/goarch" "internal/goarch"
"internal/runtime/math"
"internal/runtime/sys" "internal/runtime/sys"
"unsafe" "unsafe"
) )
@ -240,17 +241,50 @@ func depthToShift(depth uint8) uint8 {
return 64 - depth return 64 - depth
} }
func NewMap(mt *abi.SwissMapType, capacity uint64) *Map { // maxAlloc should be runtime.maxAlloc.
if capacity < abi.SwissMapGroupSlots { //
// TODO: temporary to simplify initial implementation. // TODO(prattmic): Put maxAlloc somewhere accessible.
capacity = abi.SwissMapGroupSlots func NewMap(mt *abi.SwissMapType, hint, maxAlloc uintptr) *Map {
// Set initial capacity to hold hint entries without growing in the
// average case.
var targetCapacity uintptr
if hint <= abi.SwissMapGroupSlots {
// Small map can fill all 8 slots. We set the target to 0 here
// because an 8 slot small map is what the first assignment to
// an empty map will allocate anyway. Whether we allocate here
// or in the first assignment makes no difference. And if there
// is a chance that the caller won't write at all then it is
// better to delay.
targetCapacity = 0
} else {
targetCapacity = (hint * abi.SwissMapGroupSlots) / maxAvgGroupLoad
if targetCapacity < hint { // overflow
targetCapacity = 0
}
} }
dirSize := (capacity + maxTableCapacity - 1) / maxTableCapacity
dirSize := (uint64(targetCapacity) + maxTableCapacity - 1) / maxTableCapacity
dirSize, overflow := alignUpPow2(dirSize) dirSize, overflow := alignUpPow2(dirSize)
if overflow { if overflow || dirSize > uint64(math.MaxUintptr) {
panic("rounded-up capacity overflows uint64") targetCapacity = 0
} }
// Reject hints that are obviously too large.
groups, overflow := math.MulUintptr(uintptr(dirSize), maxTableCapacity)
if overflow {
targetCapacity = 0
} else {
mem, overflow := math.MulUintptr(groups, mt.Group.Size_)
if overflow || mem > maxAlloc {
targetCapacity = 0
}
}
globalDepth := uint8(sys.TrailingZeros64(dirSize)) globalDepth := uint8(sys.TrailingZeros64(dirSize))
if targetCapacity == 0 {
// TrailingZeros64 returns 64 for 0.
globalDepth = 0
}
m := &Map{ m := &Map{
//TODO //TODO
@ -262,25 +296,17 @@ func NewMap(mt *abi.SwissMapType, capacity uint64) *Map {
globalShift: depthToShift(globalDepth), globalShift: depthToShift(globalDepth),
} }
if capacity > abi.SwissMapGroupSlots { if targetCapacity > 0 {
// Full map.
directory := make([]*table, dirSize) directory := make([]*table, dirSize)
for i := range directory { for i := range directory {
// TODO: Think more about initial table capacity. // TODO: Think more about initial table capacity.
directory[i] = newTable(mt, capacity/dirSize, i, globalDepth) directory[i] = newTable(mt, uint64(targetCapacity)/dirSize, i, globalDepth)
} }
m.dirPtr = unsafe.Pointer(&directory[0]) m.dirPtr = unsafe.Pointer(&directory[0])
m.dirLen = len(directory) m.dirLen = len(directory)
} else {
grp := newGroups(mt, 1)
m.dirPtr = grp.data
m.dirLen = 0
g := groupReference{
data: m.dirPtr,
}
g.ctrls().setEmpty()
} }
return m return m
@ -356,6 +382,10 @@ func (m *Map) Get(typ *abi.SwissMapType, key unsafe.Pointer) (unsafe.Pointer, bo
} }
func (m *Map) getWithKey(typ *abi.SwissMapType, key unsafe.Pointer) (unsafe.Pointer, unsafe.Pointer, bool) { func (m *Map) getWithKey(typ *abi.SwissMapType, key unsafe.Pointer) (unsafe.Pointer, unsafe.Pointer, bool) {
if m.Used() == 0 {
return nil, nil, false
}
hash := typ.Hasher(key, m.seed) hash := typ.Hasher(key, m.seed)
if m.dirLen == 0 { if m.dirLen == 0 {
@ -367,6 +397,10 @@ func (m *Map) getWithKey(typ *abi.SwissMapType, key unsafe.Pointer) (unsafe.Poin
} }
func (m *Map) getWithoutKey(typ *abi.SwissMapType, key unsafe.Pointer) (unsafe.Pointer, bool) { func (m *Map) getWithoutKey(typ *abi.SwissMapType, key unsafe.Pointer) (unsafe.Pointer, bool) {
if m.Used() == 0 {
return nil, false
}
hash := typ.Hasher(key, m.seed) hash := typ.Hasher(key, m.seed)
if m.dirLen == 0 { if m.dirLen == 0 {
@ -414,6 +448,10 @@ func (m *Map) Put(typ *abi.SwissMapType, key, elem unsafe.Pointer) {
func (m *Map) PutSlot(typ *abi.SwissMapType, key unsafe.Pointer) unsafe.Pointer { func (m *Map) PutSlot(typ *abi.SwissMapType, key unsafe.Pointer) unsafe.Pointer {
hash := typ.Hasher(key, m.seed) hash := typ.Hasher(key, m.seed)
if m.dirPtr == nil {
m.growToSmall(typ)
}
if m.dirLen == 0 { if m.dirLen == 0 {
if m.used < abi.SwissMapGroupSlots { if m.used < abi.SwissMapGroupSlots {
return m.putSlotSmall(typ, hash, key) return m.putSlotSmall(typ, hash, key)
@ -464,7 +502,7 @@ func (m *Map) putSlotSmall(typ *abi.SwissMapType, hash uintptr, key unsafe.Point
// deleteSmall). // deleteSmall).
match = g.ctrls().matchEmpty() match = g.ctrls().matchEmpty()
if match == 0 { if match == 0 {
panic("small map with no empty slot") fatal("small map with no empty slot (concurrent map writes?)")
} }
i := match.first() i := match.first()
@ -479,6 +517,16 @@ func (m *Map) putSlotSmall(typ *abi.SwissMapType, hash uintptr, key unsafe.Point
return slotElem return slotElem
} }
func (m *Map) growToSmall(typ *abi.SwissMapType) {
grp := newGroups(typ, 1)
m.dirPtr = grp.data
g := groupReference{
data: m.dirPtr,
}
g.ctrls().setEmpty()
}
func (m *Map) growToTable(typ *abi.SwissMapType) { func (m *Map) growToTable(typ *abi.SwissMapType) {
tab := newTable(typ, 2*abi.SwissMapGroupSlots, 0, 0) tab := newTable(typ, 2*abi.SwissMapGroupSlots, 0, 0)
@ -508,6 +556,13 @@ func (m *Map) growToTable(typ *abi.SwissMapType) {
} }
func (m *Map) Delete(typ *abi.SwissMapType, key unsafe.Pointer) { func (m *Map) Delete(typ *abi.SwissMapType, key unsafe.Pointer) {
if m == nil || m.Used() == 0 {
if err := mapKeyError(typ, key); err != nil {
panic(err) // see issue 23734
}
return
}
hash := typ.Hasher(key, m.seed) hash := typ.Hasher(key, m.seed)
if m.dirLen == 0 { if m.dirLen == 0 {
@ -546,6 +601,10 @@ func (m *Map) deleteSmall(typ *abi.SwissMapType, hash uintptr, key unsafe.Pointe
// Clear deletes all entries from the map resulting in an empty map. // Clear deletes all entries from the map resulting in an empty map.
func (m *Map) Clear(typ *abi.SwissMapType) { func (m *Map) Clear(typ *abi.SwissMapType) {
if m == nil || m.Used() == 0 {
return
}
if m.dirLen == 0 { if m.dirLen == 0 {
m.clearSmall(typ) m.clearSmall(typ)
return return

View file

@ -55,53 +55,47 @@ func TestTableGroupCount(t *testing.T) {
{ {
n: -(1 << 30), n: -(1 << 30),
escape: mapCase{ escape: mapCase{
// TODO(go.dev/issue/54766): empty maps initialLit: mapCount{0, 0},
initialLit: mapCount{0, 1}, initialHint: mapCount{0, 0},
initialHint: mapCount{0, 1}, after: mapCount{0, 0},
after: mapCount{0, 1},
}, },
}, },
{ {
n: -1, n: -1,
escape: mapCase{ escape: mapCase{
// TODO(go.dev/issue/54766): empty maps initialLit: mapCount{0, 0},
initialLit: mapCount{0, 1}, initialHint: mapCount{0, 0},
initialHint: mapCount{0, 1}, after: mapCount{0, 0},
after: mapCount{0, 1},
}, },
}, },
{ {
n: 0, n: 0,
escape: mapCase{ escape: mapCase{
// TODO(go.dev/issue/54766): empty maps initialLit: mapCount{0, 0},
initialLit: mapCount{0, 1}, initialHint: mapCount{0, 0},
initialHint: mapCount{0, 1}, after: mapCount{0, 0},
after: mapCount{0, 1},
}, },
}, },
{ {
n: 1, n: 1,
escape: mapCase{ escape: mapCase{
// TODO(go.dev/issue/54766): empty maps initialLit: mapCount{0, 0},
initialLit: mapCount{0, 1}, initialHint: mapCount{0, 0},
initialHint: mapCount{0, 1},
after: mapCount{0, 1}, after: mapCount{0, 1},
}, },
}, },
{ {
n: abi.SwissMapGroupSlots, n: abi.SwissMapGroupSlots,
escape: mapCase{ escape: mapCase{
// TODO(go.dev/issue/54766): empty maps initialLit: mapCount{0, 0},
initialLit: mapCount{0, 1}, initialHint: mapCount{0, 0},
initialHint: mapCount{0, 1},
after: mapCount{0, 1}, after: mapCount{0, 1},
}, },
}, },
{ {
n: abi.SwissMapGroupSlots + 1, n: abi.SwissMapGroupSlots + 1,
escape: mapCase{ escape: mapCase{
// TODO(go.dev/issue/54766): empty maps initialLit: mapCount{0, 0},
initialLit: mapCount{0, 1},
initialHint: mapCount{1, 2}, initialHint: mapCount{1, 2},
after: mapCount{1, 2}, after: mapCount{1, 2},
}, },
@ -109,8 +103,7 @@ func TestTableGroupCount(t *testing.T) {
{ {
n: belowMax, // 1.5 group max = 2 groups @ 75% n: belowMax, // 1.5 group max = 2 groups @ 75%
escape: mapCase{ escape: mapCase{
// TODO(go.dev/issue/54766): empty maps initialLit: mapCount{0, 0},
initialLit: mapCount{0, 1},
initialHint: mapCount{1, 2}, initialHint: mapCount{1, 2},
after: mapCount{1, 2}, after: mapCount{1, 2},
}, },
@ -118,8 +111,7 @@ func TestTableGroupCount(t *testing.T) {
{ {
n: atMax, // 2 groups at max n: atMax, // 2 groups at max
escape: mapCase{ escape: mapCase{
// TODO(go.dev/issue/54766): empty maps initialLit: mapCount{0, 0},
initialLit: mapCount{0, 1},
initialHint: mapCount{1, 2}, initialHint: mapCount{1, 2},
after: mapCount{1, 2}, after: mapCount{1, 2},
}, },
@ -127,18 +119,15 @@ func TestTableGroupCount(t *testing.T) {
{ {
n: atMax + 1, // 2 groups at max + 1 -> grow to 4 groups n: atMax + 1, // 2 groups at max + 1 -> grow to 4 groups
escape: mapCase{ escape: mapCase{
// TODO(go.dev/issue/54766): empty maps initialLit: mapCount{0, 0},
initialLit: mapCount{0, 1}, initialHint: mapCount{1, 4},
// TODO(go.dev/issue/54766): Initial capacity should round hint up to avoid grow.
initialHint: mapCount{1, 2},
after: mapCount{1, 4}, after: mapCount{1, 4},
}, },
}, },
{ {
n: 2 * belowMax, // 3 * group max = 4 groups @75% n: 2 * belowMax, // 3 * group max = 4 groups @75%
escape: mapCase{ escape: mapCase{
// TODO(go.dev/issue/54766): empty maps initialLit: mapCount{0, 0},
initialLit: mapCount{0, 1},
initialHint: mapCount{1, 4}, initialHint: mapCount{1, 4},
after: mapCount{1, 4}, after: mapCount{1, 4},
}, },
@ -146,10 +135,8 @@ func TestTableGroupCount(t *testing.T) {
{ {
n: 2*atMax + 1, // 4 groups at max + 1 -> grow to 8 groups n: 2*atMax + 1, // 4 groups at max + 1 -> grow to 8 groups
escape: mapCase{ escape: mapCase{
// TODO(go.dev/issue/54766): empty maps initialLit: mapCount{0, 0},
initialLit: mapCount{0, 1}, initialHint: mapCount{1, 8},
// TODO(go.dev/issue/54766): Initial capacity should round hint up to avoid grow.
initialHint: mapCount{1, 4},
after: mapCount{1, 8}, after: mapCount{1, 8},
}, },
}, },

View file

@ -11,6 +11,9 @@ import (
// Functions below pushed from runtime. // Functions below pushed from runtime.
//go:linkname fatal
func fatal(s string)
//go:linkname rand //go:linkname rand
func rand() uint64 func rand() uint64

View file

@ -0,0 +1,17 @@
// 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"
)
// For testing, we don't ever need key errors.
func mapKeyError(typ *abi.SwissMapType, p unsafe.Pointer) error {
return nil
}

View file

@ -122,6 +122,10 @@ func runtime_mapassign(typ *abi.SwissMapType, m *Map, key unsafe.Pointer) unsafe
hash := typ.Hasher(key, m.seed) hash := typ.Hasher(key, m.seed)
if m.dirPtr == nil {
m.growToSmall(typ)
}
if m.dirLen == 0 { if m.dirLen == 0 {
if m.used < abi.SwissMapGroupSlots { if m.used < abi.SwissMapGroupSlots {
return m.putSlotSmall(typ, hash, key) return m.putSlotSmall(typ, hash, key)

View file

@ -812,9 +812,6 @@ func (t *table) rehash(typ *abi.SwissMapType, m *Map) {
// new allocation, so the existing grow support in iteration would // new allocation, so the existing grow support in iteration would
// continue to work. // continue to work.
// TODO(prattmic): split table
// TODO(prattmic): Avoid overflow (splitting the table will achieve this)
newCapacity := 2 * t.capacity newCapacity := 2 * t.capacity
if newCapacity <= maxTableCapacity { if newCapacity <= maxTableCapacity {
t.grow(typ, m, newCapacity) t.grow(typ, m, newCapacity)

View file

@ -256,11 +256,6 @@ func mapKeyError(t *maptype, p unsafe.Pointer) error {
return mapKeyError2(t.Key, p) return mapKeyError2(t.Key, p)
} }
//go:linkname maps_mapKeyError internal/runtime/maps.mapKeyError
func maps_mapKeyError(t *maptype, p unsafe.Pointer) error {
return mapKeyError(t, p)
}
func mapKeyError2(t *_type, p unsafe.Pointer) error { func mapKeyError2(t *_type, p unsafe.Pointer) error {
if t.TFlag&abi.TFlagRegularMemory != 0 { if t.TFlag&abi.TFlagRegularMemory != 0 {
return nil return nil

View file

@ -622,7 +622,10 @@ func TestConcurrentMapWrites(t *testing.T) {
testenv.MustHaveGoRun(t) testenv.MustHaveGoRun(t)
output := runTestProg(t, "testprog", "concurrentMapWrites") output := runTestProg(t, "testprog", "concurrentMapWrites")
want := "fatal error: concurrent map writes\n" want := "fatal error: concurrent map writes\n"
if !strings.HasPrefix(output, want) { // Concurrent writes can corrupt the map in a way that we
// detect with a separate throw.
want2 := "fatal error: small map with no empty slot (concurrent map writes?)\n"
if !strings.HasPrefix(output, want) && !strings.HasPrefix(output, want2) {
t.Fatalf("output does not start with %q:\n%s", want, output) t.Fatalf("output does not start with %q:\n%s", want, output)
} }
} }
@ -633,7 +636,10 @@ func TestConcurrentMapReadWrite(t *testing.T) {
testenv.MustHaveGoRun(t) testenv.MustHaveGoRun(t)
output := runTestProg(t, "testprog", "concurrentMapReadWrite") output := runTestProg(t, "testprog", "concurrentMapReadWrite")
want := "fatal error: concurrent map read and map write\n" want := "fatal error: concurrent map read and map write\n"
if !strings.HasPrefix(output, want) { // Concurrent writes can corrupt the map in a way that we
// detect with a separate throw.
want2 := "fatal error: small map with no empty slot (concurrent map writes?)\n"
if !strings.HasPrefix(output, want) && !strings.HasPrefix(output, want2) {
t.Fatalf("output does not start with %q:\n%s", want, output) t.Fatalf("output does not start with %q:\n%s", want, output)
} }
} }
@ -644,7 +650,10 @@ func TestConcurrentMapIterateWrite(t *testing.T) {
testenv.MustHaveGoRun(t) testenv.MustHaveGoRun(t)
output := runTestProg(t, "testprog", "concurrentMapIterateWrite") output := runTestProg(t, "testprog", "concurrentMapIterateWrite")
want := "fatal error: concurrent map iteration and map write\n" want := "fatal error: concurrent map iteration and map write\n"
if !strings.HasPrefix(output, want) { // Concurrent writes can corrupt the map in a way that we
// detect with a separate throw.
want2 := "fatal error: small map with no empty slot (concurrent map writes?)\n"
if !strings.HasPrefix(output, want) && !strings.HasPrefix(output, want2) {
t.Fatalf("output does not start with %q:\n%s", want, output) t.Fatalf("output does not start with %q:\n%s", want, output)
} }
} }
@ -667,7 +676,10 @@ func TestConcurrentMapWritesIssue69447(t *testing.T) {
continue continue
} }
want := "fatal error: concurrent map writes\n" want := "fatal error: concurrent map writes\n"
if !strings.HasPrefix(output, want) { // Concurrent writes can corrupt the map in a way that we
// detect with a separate throw.
want2 := "fatal error: small map with no empty slot (concurrent map writes?)\n"
if !strings.HasPrefix(output, want) && !strings.HasPrefix(output, want2) {
t.Fatalf("output does not start with %q:\n%s", want, output) t.Fatalf("output does not start with %q:\n%s", want, output)
} }
} }

View file

@ -9,7 +9,6 @@ package runtime
import ( import (
"internal/abi" "internal/abi"
"internal/runtime/maps" "internal/runtime/maps"
"internal/runtime/math"
"internal/runtime/sys" "internal/runtime/sys"
"unsafe" "unsafe"
) )
@ -25,6 +24,11 @@ type maptype = abi.SwissMapType
//go:linkname maps_errNilAssign internal/runtime/maps.errNilAssign //go:linkname maps_errNilAssign internal/runtime/maps.errNilAssign
var maps_errNilAssign error = plainError("assignment to entry in nil map") var maps_errNilAssign error = plainError("assignment to entry in nil map")
//go:linkname maps_mapKeyError internal/runtime/maps.mapKeyError
func maps_mapKeyError(t *abi.SwissMapType, p unsafe.Pointer) error {
return mapKeyError(t, p)
}
func makemap64(t *abi.SwissMapType, hint int64, m *maps.Map) *maps.Map { func makemap64(t *abi.SwissMapType, hint int64, m *maps.Map) *maps.Map {
if int64(int(hint)) != hint { if int64(int(hint)) != hint {
hint = 0 hint = 0
@ -39,63 +43,18 @@ func makemap_small() *maps.Map {
panic("unimplemented") panic("unimplemented")
} }
// checkHint verifies that hint is reasonable, adjusting as necessary.
func checkHint(t *abi.SwissMapType, hint int) uint64 {
if hint <= 0 {
return 0
}
capacity := uint64(hint)
// Ensure a groups allocation for a capacity this high doesn't exceed
// the maximum allocation size.
//
// TODO(prattmic): Once we split tables, a large hint will result in
// splitting the tables up front, which will use smaller individual
// allocations.
//
// TODO(prattmic): This logic is largely duplicated from maps.newTable
// / maps.(*table).reset.
capacity, overflow := alignUpPow2(capacity)
if !overflow {
groupCount := capacity / abi.SwissMapGroupSlots
mem, overflow := math.MulUintptr(uintptr(groupCount), t.Group.Size_)
if overflow || mem > maxAlloc {
return 0
}
} else {
return 0
}
return capacity
}
// makemap implements Go map creation for make(map[k]v, hint). // makemap implements Go map creation for make(map[k]v, hint).
// If the compiler has determined that the map or the first bucket // If the compiler has determined that the map or the first bucket
// can be created on the stack, h and/or bucket may be non-nil. // can be created on the stack, h and/or bucket may be non-nil.
// If h != nil, the map can be created directly in h. // If h != nil, the map can be created directly in h.
// If h.buckets != nil, bucket pointed to can be used as the first bucket. // If h.buckets != nil, bucket pointed to can be used as the first bucket.
func makemap(t *abi.SwissMapType, hint int, m *maps.Map) *maps.Map { func makemap(t *abi.SwissMapType, hint int, m *maps.Map) *maps.Map {
capacity := checkHint(t, hint) if hint < 0 {
hint = 0
}
// TODO: use existing m // TODO: use existing m
return maps.NewMap(t, capacity) return maps.NewMap(t, uintptr(hint), maxAlloc)
}
// alignUpPow2 rounds n up to the next power of 2.
//
// Returns true if round up causes overflow.
//
// TODO(prattmic): deduplicate from internal/runtime/maps.
func alignUpPow2(n uint64) (uint64, bool) {
if n == 0 {
return 0, false
}
v := (uint64(1) << sys.Len64(n-1))
if v == 0 {
return 0, true
}
return v, false
} }
// mapaccess1 returns a pointer to h[key]. Never returns nil, instead // mapaccess1 returns a pointer to h[key]. Never returns nil, instead
@ -176,13 +135,6 @@ func mapdelete(t *abi.SwissMapType, m *maps.Map, key unsafe.Pointer) {
asanread(key, t.Key.Size_) asanread(key, t.Key.Size_)
} }
if m == nil || m.Used() == 0 {
if err := mapKeyError(t, key); err != nil {
panic(err) // see issue 23734
}
return
}
m.Delete(t, key) m.Delete(t, key)
} }
@ -219,10 +171,6 @@ func mapclear(t *abi.SwissMapType, m *maps.Map) {
racewritepc(unsafe.Pointer(m), callerpc, pc) racewritepc(unsafe.Pointer(m), callerpc, pc)
} }
if m == nil || m.Used() == 0 {
return
}
m.Clear(t) m.Clear(t)
} }

View file

@ -1043,6 +1043,11 @@ func fips_fatal(s string) {
fatal(s) fatal(s)
} }
//go:linkname maps_fatal internal/runtime/maps.fatal
func maps_fatal(s string) {
fatal(s)
}
// throw triggers a fatal error that dumps a stack trace and exits. // throw triggers a fatal error that dumps a stack trace and exits.
// //
// throw should be used for runtime-internal fatal errors where Go itself, // throw should be used for runtime-internal fatal errors where Go itself,