mirror of
https://github.com/golang/go
synced 2024-11-22 10:34:46 -07:00
all: create swissmap experiment and fork files
The _swiss.go files are identical to the originals (except build tag). Later CLs will change them. For #54766. Cq-Include-Trybots: luci.golang.try:gotip-linux-amd64-longtest-swissmap Change-Id: I9943e2d6f1cfa227ffbf27c9ddc9ce853695d225 Reviewed-on: https://go-review.googlesource.com/c/go/+/580778 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Keith Randall <khr@golang.org> Auto-Submit: Michael Pratt <mpratt@google.com> Commit-Queue: Michael Pratt <mpratt@google.com> Reviewed-by: Keith Randall <khr@google.com>
This commit is contained in:
parent
c5f2e4e56e
commit
057b703407
8
src/internal/goexperiment/exp_swissmap_off.go
Normal file
8
src/internal/goexperiment/exp_swissmap_off.go
Normal file
@ -0,0 +1,8 @@
|
||||
// Code generated by mkconsts.go. DO NOT EDIT.
|
||||
|
||||
//go:build !goexperiment.swissmap
|
||||
|
||||
package goexperiment
|
||||
|
||||
const SwissMap = false
|
||||
const SwissMapInt = 0
|
8
src/internal/goexperiment/exp_swissmap_on.go
Normal file
8
src/internal/goexperiment/exp_swissmap_on.go
Normal file
@ -0,0 +1,8 @@
|
||||
// Code generated by mkconsts.go. DO NOT EDIT.
|
||||
|
||||
//go:build goexperiment.swissmap
|
||||
|
||||
package goexperiment
|
||||
|
||||
const SwissMap = true
|
||||
const SwissMapInt = 1
|
@ -115,4 +115,7 @@ type Flags struct {
|
||||
// Requires that gotypesalias=1 is set with GODEBUG.
|
||||
// This flag will be removed with Go 1.24.
|
||||
AliasTypeParams bool
|
||||
|
||||
// SwissMap enables the SwissTable-based map implementation.
|
||||
SwissMap bool
|
||||
}
|
||||
|
473
src/reflect/map_noswiss.go
Normal file
473
src/reflect/map_noswiss.go
Normal file
@ -0,0 +1,473 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"internal/abi"
|
||||
"internal/goarch"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// mapType represents a map type.
|
||||
type mapType struct {
|
||||
abi.MapType
|
||||
}
|
||||
|
||||
func (t *rtype) Key() Type {
|
||||
if t.Kind() != Map {
|
||||
panic("reflect: Key of non-map type " + t.String())
|
||||
}
|
||||
tt := (*mapType)(unsafe.Pointer(t))
|
||||
return toType(tt.Key)
|
||||
}
|
||||
|
||||
// MapOf returns the map type with the given key and element types.
|
||||
// For example, if k represents int and e represents string,
|
||||
// MapOf(k, e) represents map[int]string.
|
||||
//
|
||||
// If the key type is not a valid map key type (that is, if it does
|
||||
// not implement Go's == operator), MapOf panics.
|
||||
func MapOf(key, elem Type) Type {
|
||||
ktyp := key.common()
|
||||
etyp := elem.common()
|
||||
|
||||
if ktyp.Equal == nil {
|
||||
panic("reflect.MapOf: invalid key type " + stringFor(ktyp))
|
||||
}
|
||||
|
||||
// Look in cache.
|
||||
ckey := cacheKey{Map, ktyp, etyp, 0}
|
||||
if mt, ok := lookupCache.Load(ckey); ok {
|
||||
return mt.(Type)
|
||||
}
|
||||
|
||||
// Look in known types.
|
||||
s := "map[" + stringFor(ktyp) + "]" + stringFor(etyp)
|
||||
for _, tt := range typesByString(s) {
|
||||
mt := (*mapType)(unsafe.Pointer(tt))
|
||||
if mt.Key == ktyp && mt.Elem == etyp {
|
||||
ti, _ := lookupCache.LoadOrStore(ckey, toRType(tt))
|
||||
return ti.(Type)
|
||||
}
|
||||
}
|
||||
|
||||
// Make a map type.
|
||||
// Note: flag values must match those used in the TMAP case
|
||||
// in ../cmd/compile/internal/reflectdata/reflect.go:writeType.
|
||||
var imap any = (map[unsafe.Pointer]unsafe.Pointer)(nil)
|
||||
mt := **(**mapType)(unsafe.Pointer(&imap))
|
||||
mt.Str = resolveReflectName(newName(s, "", false, false))
|
||||
mt.TFlag = 0
|
||||
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.Hasher = func(p unsafe.Pointer, seed uintptr) uintptr {
|
||||
return typehash(ktyp, p, seed)
|
||||
}
|
||||
mt.Flags = 0
|
||||
if ktyp.Size_ > abi.MapMaxKeyBytes {
|
||||
mt.KeySize = uint8(goarch.PtrSize)
|
||||
mt.Flags |= 1 // indirect key
|
||||
} else {
|
||||
mt.KeySize = uint8(ktyp.Size_)
|
||||
}
|
||||
if etyp.Size_ > abi.MapMaxElemBytes {
|
||||
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
|
||||
}
|
||||
if needKeyUpdate(ktyp) {
|
||||
mt.Flags |= 8
|
||||
}
|
||||
if hashMightPanic(ktyp) {
|
||||
mt.Flags |= 16
|
||||
}
|
||||
mt.PtrToThis = 0
|
||||
|
||||
ti, _ := lookupCache.LoadOrStore(ckey, toRType(&mt.Type))
|
||||
return ti.(Type)
|
||||
}
|
||||
|
||||
func bucketOf(ktyp, etyp *abi.Type) *abi.Type {
|
||||
if ktyp.Size_ > abi.MapMaxKeyBytes {
|
||||
ktyp = ptrTo(ktyp)
|
||||
}
|
||||
if etyp.Size_ > abi.MapMaxElemBytes {
|
||||
etyp = ptrTo(etyp)
|
||||
}
|
||||
|
||||
// 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.MapBucketCount*(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.MapBucketCount*(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.MapBucketCount / goarch.PtrSize)
|
||||
|
||||
if ktyp.Pointers() {
|
||||
emitGCMask(mask, base, ktyp, abi.MapBucketCount)
|
||||
}
|
||||
base += abi.MapBucketCount * ktyp.Size_ / goarch.PtrSize
|
||||
|
||||
if etyp.Pointers() {
|
||||
emitGCMask(mask, base, etyp, abi.MapBucketCount)
|
||||
}
|
||||
base += abi.MapBucketCount * 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
|
||||
}
|
||||
|
||||
var stringType = rtypeOf("")
|
||||
|
||||
// MapIndex returns the value associated with key in the map v.
|
||||
// It panics if v's Kind is not [Map].
|
||||
// It returns the zero Value if key is not found in the map or if v represents a nil map.
|
||||
// As in Go, the key's value must be assignable to the map's key type.
|
||||
func (v Value) MapIndex(key Value) Value {
|
||||
v.mustBe(Map)
|
||||
tt := (*mapType)(unsafe.Pointer(v.typ()))
|
||||
|
||||
// Do not require key to be exported, so that DeepEqual
|
||||
// and other programs can use all the keys returned by
|
||||
// MapKeys as arguments to MapIndex. If either the map
|
||||
// or the key is unexported, though, the result will be
|
||||
// considered unexported. This is consistent with the
|
||||
// behavior for structs, which allow read but not write
|
||||
// of unexported fields.
|
||||
|
||||
var e unsafe.Pointer
|
||||
if (tt.Key == stringType || key.kind() == String) && tt.Key == key.typ() && tt.Elem.Size() <= abi.MapMaxElemBytes {
|
||||
k := *(*string)(key.ptr)
|
||||
e = mapaccess_faststr(v.typ(), v.pointer(), k)
|
||||
} else {
|
||||
key = key.assignTo("reflect.Value.MapIndex", tt.Key, nil)
|
||||
var k unsafe.Pointer
|
||||
if key.flag&flagIndir != 0 {
|
||||
k = key.ptr
|
||||
} else {
|
||||
k = unsafe.Pointer(&key.ptr)
|
||||
}
|
||||
e = mapaccess(v.typ(), v.pointer(), k)
|
||||
}
|
||||
if e == nil {
|
||||
return Value{}
|
||||
}
|
||||
typ := tt.Elem
|
||||
fl := (v.flag | key.flag).ro()
|
||||
fl |= flag(typ.Kind())
|
||||
return copyVal(typ, fl, e)
|
||||
}
|
||||
|
||||
// MapKeys returns a slice containing all the keys present in the map,
|
||||
// in unspecified order.
|
||||
// It panics if v's Kind is not [Map].
|
||||
// It returns an empty slice if v represents a nil map.
|
||||
func (v Value) MapKeys() []Value {
|
||||
v.mustBe(Map)
|
||||
tt := (*mapType)(unsafe.Pointer(v.typ()))
|
||||
keyType := tt.Key
|
||||
|
||||
fl := v.flag.ro() | flag(keyType.Kind())
|
||||
|
||||
m := v.pointer()
|
||||
mlen := int(0)
|
||||
if m != nil {
|
||||
mlen = maplen(m)
|
||||
}
|
||||
var it hiter
|
||||
mapiterinit(v.typ(), m, &it)
|
||||
a := make([]Value, mlen)
|
||||
var i int
|
||||
for i = 0; i < len(a); i++ {
|
||||
key := mapiterkey(&it)
|
||||
if key == nil {
|
||||
// Someone deleted an entry from the map since we
|
||||
// called maplen above. It's a data race, but nothing
|
||||
// we can do about it.
|
||||
break
|
||||
}
|
||||
a[i] = copyVal(keyType, fl, key)
|
||||
mapiternext(&it)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// Key returns the key of iter's current map entry.
|
||||
func (iter *MapIter) Key() Value {
|
||||
if !iter.hiter.initialized() {
|
||||
panic("MapIter.Key called before Next")
|
||||
}
|
||||
iterkey := mapiterkey(&iter.hiter)
|
||||
if iterkey == nil {
|
||||
panic("MapIter.Key called on exhausted iterator")
|
||||
}
|
||||
|
||||
t := (*mapType)(unsafe.Pointer(iter.m.typ()))
|
||||
ktype := t.Key
|
||||
return copyVal(ktype, iter.m.flag.ro()|flag(ktype.Kind()), iterkey)
|
||||
}
|
||||
|
||||
// SetIterKey assigns to v the key of iter's current map entry.
|
||||
// It is equivalent to v.Set(iter.Key()), but it avoids allocating a new 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() {
|
||||
panic("reflect: Value.SetIterKey called before Next")
|
||||
}
|
||||
iterkey := mapiterkey(&iter.hiter)
|
||||
if iterkey == nil {
|
||||
panic("reflect: Value.SetIterKey called on exhausted iterator")
|
||||
}
|
||||
|
||||
v.mustBeAssignable()
|
||||
var target unsafe.Pointer
|
||||
if v.kind() == Interface {
|
||||
target = v.ptr
|
||||
}
|
||||
|
||||
t := (*mapType)(unsafe.Pointer(iter.m.typ()))
|
||||
ktype := t.Key
|
||||
|
||||
iter.m.mustBeExported() // do not let unexported m leak
|
||||
key := Value{ktype, iterkey, iter.m.flag | flag(ktype.Kind()) | flagIndir}
|
||||
key = key.assignTo("reflect.MapIter.SetKey", v.typ(), target)
|
||||
typedmemmove(v.typ(), v.ptr, key.ptr)
|
||||
}
|
||||
|
||||
// Value returns the value of iter's current map entry.
|
||||
func (iter *MapIter) Value() Value {
|
||||
if !iter.hiter.initialized() {
|
||||
panic("MapIter.Value called before Next")
|
||||
}
|
||||
iterelem := mapiterelem(&iter.hiter)
|
||||
if iterelem == nil {
|
||||
panic("MapIter.Value called on exhausted iterator")
|
||||
}
|
||||
|
||||
t := (*mapType)(unsafe.Pointer(iter.m.typ()))
|
||||
vtype := t.Elem
|
||||
return copyVal(vtype, iter.m.flag.ro()|flag(vtype.Kind()), iterelem)
|
||||
}
|
||||
|
||||
// SetIterValue assigns to v the value of iter's current map entry.
|
||||
// It is equivalent to v.Set(iter.Value()), but it avoids allocating a new 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() {
|
||||
panic("reflect: Value.SetIterValue called before Next")
|
||||
}
|
||||
iterelem := mapiterelem(&iter.hiter)
|
||||
if iterelem == nil {
|
||||
panic("reflect: Value.SetIterValue called on exhausted iterator")
|
||||
}
|
||||
|
||||
v.mustBeAssignable()
|
||||
var target unsafe.Pointer
|
||||
if v.kind() == Interface {
|
||||
target = v.ptr
|
||||
}
|
||||
|
||||
t := (*mapType)(unsafe.Pointer(iter.m.typ()))
|
||||
vtype := t.Elem
|
||||
|
||||
iter.m.mustBeExported() // do not let unexported m leak
|
||||
elem := Value{vtype, iterelem, iter.m.flag | flag(vtype.Kind()) | flagIndir}
|
||||
elem = elem.assignTo("reflect.MapIter.SetValue", v.typ(), target)
|
||||
typedmemmove(v.typ(), v.ptr, elem.ptr)
|
||||
}
|
||||
|
||||
// Next advances the map iterator and reports whether there is another
|
||||
// entry. It returns false when iter is exhausted; subsequent
|
||||
// calls to [MapIter.Key], [MapIter.Value], or [MapIter.Next] will panic.
|
||||
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() {
|
||||
mapiterinit(iter.m.typ(), iter.m.pointer(), &iter.hiter)
|
||||
} else {
|
||||
if mapiterkey(&iter.hiter) == nil {
|
||||
panic("MapIter.Next called on exhausted iterator")
|
||||
}
|
||||
mapiternext(&iter.hiter)
|
||||
}
|
||||
return mapiterkey(&iter.hiter) != nil
|
||||
}
|
||||
|
||||
// Reset modifies iter to iterate over v.
|
||||
// It panics if v's Kind is not [Map] and v is not the zero Value.
|
||||
// Reset(Value{}) causes iter to not to refer to any map,
|
||||
// which may allow the previously iterated-over map to be garbage collected.
|
||||
func (iter *MapIter) Reset(v Value) {
|
||||
if v.IsValid() {
|
||||
v.mustBe(Map)
|
||||
}
|
||||
iter.m = v
|
||||
iter.hiter = hiter{}
|
||||
}
|
||||
|
||||
// MapRange returns a range iterator for a map.
|
||||
// It panics if v's Kind is not [Map].
|
||||
//
|
||||
// Call [MapIter.Next] to advance the iterator, and [MapIter.Key]/[MapIter.Value] to access each entry.
|
||||
// [MapIter.Next] returns false when the iterator is exhausted.
|
||||
// MapRange follows the same iteration semantics as a range statement.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// iter := reflect.ValueOf(m).MapRange()
|
||||
// for iter.Next() {
|
||||
// k := iter.Key()
|
||||
// v := iter.Value()
|
||||
// ...
|
||||
// }
|
||||
func (v Value) MapRange() *MapIter {
|
||||
// This is inlinable to take advantage of "function outlining".
|
||||
// The allocation of MapIter can be stack allocated if the caller
|
||||
// does not allow it to escape.
|
||||
// See https://blog.filippo.io/efficient-go-apis-with-the-inliner/
|
||||
if v.kind() != Map {
|
||||
v.panicNotMap()
|
||||
}
|
||||
return &MapIter{m: v}
|
||||
}
|
||||
|
||||
// SetMapIndex sets the element associated with key in the map v to elem.
|
||||
// It panics if v's Kind is not [Map].
|
||||
// If elem is the zero Value, SetMapIndex deletes the key from the map.
|
||||
// Otherwise if v holds a nil map, SetMapIndex will panic.
|
||||
// As in Go, key's elem must be assignable to the map's key type,
|
||||
// and elem's value must be assignable to the map's elem type.
|
||||
func (v Value) SetMapIndex(key, elem Value) {
|
||||
v.mustBe(Map)
|
||||
v.mustBeExported()
|
||||
key.mustBeExported()
|
||||
tt := (*mapType)(unsafe.Pointer(v.typ()))
|
||||
|
||||
if (tt.Key == stringType || key.kind() == String) && tt.Key == key.typ() && tt.Elem.Size() <= abi.MapMaxElemBytes {
|
||||
k := *(*string)(key.ptr)
|
||||
if elem.typ() == nil {
|
||||
mapdelete_faststr(v.typ(), v.pointer(), k)
|
||||
return
|
||||
}
|
||||
elem.mustBeExported()
|
||||
elem = elem.assignTo("reflect.Value.SetMapIndex", tt.Elem, nil)
|
||||
var e unsafe.Pointer
|
||||
if elem.flag&flagIndir != 0 {
|
||||
e = elem.ptr
|
||||
} else {
|
||||
e = unsafe.Pointer(&elem.ptr)
|
||||
}
|
||||
mapassign_faststr(v.typ(), v.pointer(), k, e)
|
||||
return
|
||||
}
|
||||
|
||||
key = key.assignTo("reflect.Value.SetMapIndex", tt.Key, nil)
|
||||
var k unsafe.Pointer
|
||||
if key.flag&flagIndir != 0 {
|
||||
k = key.ptr
|
||||
} else {
|
||||
k = unsafe.Pointer(&key.ptr)
|
||||
}
|
||||
if elem.typ() == nil {
|
||||
mapdelete(v.typ(), v.pointer(), k)
|
||||
return
|
||||
}
|
||||
elem.mustBeExported()
|
||||
elem = elem.assignTo("reflect.Value.SetMapIndex", tt.Elem, nil)
|
||||
var e unsafe.Pointer
|
||||
if elem.flag&flagIndir != 0 {
|
||||
e = elem.ptr
|
||||
} else {
|
||||
e = unsafe.Pointer(&elem.ptr)
|
||||
}
|
||||
mapassign(v.typ(), v.pointer(), k, e)
|
||||
}
|
||||
|
||||
// Force slow panicking path not inlined, so it won't add to the
|
||||
// inlining budget of the caller.
|
||||
// TODO: undo when the inliner is no longer bottom-up only.
|
||||
//
|
||||
//go:noinline
|
||||
func (f flag) panicNotMap() {
|
||||
f.mustBe(Map)
|
||||
}
|
473
src/reflect/map_swiss.go
Normal file
473
src/reflect/map_swiss.go
Normal file
@ -0,0 +1,473 @@
|
||||
// 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
|
||||
|
||||
import (
|
||||
"internal/abi"
|
||||
"internal/goarch"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// mapType represents a map type.
|
||||
type mapType struct {
|
||||
abi.MapType
|
||||
}
|
||||
|
||||
func (t *rtype) Key() Type {
|
||||
if t.Kind() != Map {
|
||||
panic("reflect: Key of non-map type " + t.String())
|
||||
}
|
||||
tt := (*mapType)(unsafe.Pointer(t))
|
||||
return toType(tt.Key)
|
||||
}
|
||||
|
||||
// MapOf returns the map type with the given key and element types.
|
||||
// For example, if k represents int and e represents string,
|
||||
// MapOf(k, e) represents map[int]string.
|
||||
//
|
||||
// If the key type is not a valid map key type (that is, if it does
|
||||
// not implement Go's == operator), MapOf panics.
|
||||
func MapOf(key, elem Type) Type {
|
||||
ktyp := key.common()
|
||||
etyp := elem.common()
|
||||
|
||||
if ktyp.Equal == nil {
|
||||
panic("reflect.MapOf: invalid key type " + stringFor(ktyp))
|
||||
}
|
||||
|
||||
// Look in cache.
|
||||
ckey := cacheKey{Map, ktyp, etyp, 0}
|
||||
if mt, ok := lookupCache.Load(ckey); ok {
|
||||
return mt.(Type)
|
||||
}
|
||||
|
||||
// Look in known types.
|
||||
s := "map[" + stringFor(ktyp) + "]" + stringFor(etyp)
|
||||
for _, tt := range typesByString(s) {
|
||||
mt := (*mapType)(unsafe.Pointer(tt))
|
||||
if mt.Key == ktyp && mt.Elem == etyp {
|
||||
ti, _ := lookupCache.LoadOrStore(ckey, toRType(tt))
|
||||
return ti.(Type)
|
||||
}
|
||||
}
|
||||
|
||||
// Make a map type.
|
||||
// Note: flag values must match those used in the TMAP case
|
||||
// in ../cmd/compile/internal/reflectdata/reflect.go:writeType.
|
||||
var imap any = (map[unsafe.Pointer]unsafe.Pointer)(nil)
|
||||
mt := **(**mapType)(unsafe.Pointer(&imap))
|
||||
mt.Str = resolveReflectName(newName(s, "", false, false))
|
||||
mt.TFlag = 0
|
||||
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.Hasher = func(p unsafe.Pointer, seed uintptr) uintptr {
|
||||
return typehash(ktyp, p, seed)
|
||||
}
|
||||
mt.Flags = 0
|
||||
if ktyp.Size_ > abi.MapMaxKeyBytes {
|
||||
mt.KeySize = uint8(goarch.PtrSize)
|
||||
mt.Flags |= 1 // indirect key
|
||||
} else {
|
||||
mt.KeySize = uint8(ktyp.Size_)
|
||||
}
|
||||
if etyp.Size_ > abi.MapMaxElemBytes {
|
||||
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
|
||||
}
|
||||
if needKeyUpdate(ktyp) {
|
||||
mt.Flags |= 8
|
||||
}
|
||||
if hashMightPanic(ktyp) {
|
||||
mt.Flags |= 16
|
||||
}
|
||||
mt.PtrToThis = 0
|
||||
|
||||
ti, _ := lookupCache.LoadOrStore(ckey, toRType(&mt.Type))
|
||||
return ti.(Type)
|
||||
}
|
||||
|
||||
func bucketOf(ktyp, etyp *abi.Type) *abi.Type {
|
||||
if ktyp.Size_ > abi.MapMaxKeyBytes {
|
||||
ktyp = ptrTo(ktyp)
|
||||
}
|
||||
if etyp.Size_ > abi.MapMaxElemBytes {
|
||||
etyp = ptrTo(etyp)
|
||||
}
|
||||
|
||||
// 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.MapBucketCount*(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.MapBucketCount*(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.MapBucketCount / goarch.PtrSize)
|
||||
|
||||
if ktyp.Pointers() {
|
||||
emitGCMask(mask, base, ktyp, abi.MapBucketCount)
|
||||
}
|
||||
base += abi.MapBucketCount * ktyp.Size_ / goarch.PtrSize
|
||||
|
||||
if etyp.Pointers() {
|
||||
emitGCMask(mask, base, etyp, abi.MapBucketCount)
|
||||
}
|
||||
base += abi.MapBucketCount * 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
|
||||
}
|
||||
|
||||
var stringType = rtypeOf("")
|
||||
|
||||
// MapIndex returns the value associated with key in the map v.
|
||||
// It panics if v's Kind is not [Map].
|
||||
// It returns the zero Value if key is not found in the map or if v represents a nil map.
|
||||
// As in Go, the key's value must be assignable to the map's key type.
|
||||
func (v Value) MapIndex(key Value) Value {
|
||||
v.mustBe(Map)
|
||||
tt := (*mapType)(unsafe.Pointer(v.typ()))
|
||||
|
||||
// Do not require key to be exported, so that DeepEqual
|
||||
// and other programs can use all the keys returned by
|
||||
// MapKeys as arguments to MapIndex. If either the map
|
||||
// or the key is unexported, though, the result will be
|
||||
// considered unexported. This is consistent with the
|
||||
// behavior for structs, which allow read but not write
|
||||
// of unexported fields.
|
||||
|
||||
var e unsafe.Pointer
|
||||
if (tt.Key == stringType || key.kind() == String) && tt.Key == key.typ() && tt.Elem.Size() <= abi.MapMaxElemBytes {
|
||||
k := *(*string)(key.ptr)
|
||||
e = mapaccess_faststr(v.typ(), v.pointer(), k)
|
||||
} else {
|
||||
key = key.assignTo("reflect.Value.MapIndex", tt.Key, nil)
|
||||
var k unsafe.Pointer
|
||||
if key.flag&flagIndir != 0 {
|
||||
k = key.ptr
|
||||
} else {
|
||||
k = unsafe.Pointer(&key.ptr)
|
||||
}
|
||||
e = mapaccess(v.typ(), v.pointer(), k)
|
||||
}
|
||||
if e == nil {
|
||||
return Value{}
|
||||
}
|
||||
typ := tt.Elem
|
||||
fl := (v.flag | key.flag).ro()
|
||||
fl |= flag(typ.Kind())
|
||||
return copyVal(typ, fl, e)
|
||||
}
|
||||
|
||||
// MapKeys returns a slice containing all the keys present in the map,
|
||||
// in unspecified order.
|
||||
// It panics if v's Kind is not [Map].
|
||||
// It returns an empty slice if v represents a nil map.
|
||||
func (v Value) MapKeys() []Value {
|
||||
v.mustBe(Map)
|
||||
tt := (*mapType)(unsafe.Pointer(v.typ()))
|
||||
keyType := tt.Key
|
||||
|
||||
fl := v.flag.ro() | flag(keyType.Kind())
|
||||
|
||||
m := v.pointer()
|
||||
mlen := int(0)
|
||||
if m != nil {
|
||||
mlen = maplen(m)
|
||||
}
|
||||
var it hiter
|
||||
mapiterinit(v.typ(), m, &it)
|
||||
a := make([]Value, mlen)
|
||||
var i int
|
||||
for i = 0; i < len(a); i++ {
|
||||
key := mapiterkey(&it)
|
||||
if key == nil {
|
||||
// Someone deleted an entry from the map since we
|
||||
// called maplen above. It's a data race, but nothing
|
||||
// we can do about it.
|
||||
break
|
||||
}
|
||||
a[i] = copyVal(keyType, fl, key)
|
||||
mapiternext(&it)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// Key returns the key of iter's current map entry.
|
||||
func (iter *MapIter) Key() Value {
|
||||
if !iter.hiter.initialized() {
|
||||
panic("MapIter.Key called before Next")
|
||||
}
|
||||
iterkey := mapiterkey(&iter.hiter)
|
||||
if iterkey == nil {
|
||||
panic("MapIter.Key called on exhausted iterator")
|
||||
}
|
||||
|
||||
t := (*mapType)(unsafe.Pointer(iter.m.typ()))
|
||||
ktype := t.Key
|
||||
return copyVal(ktype, iter.m.flag.ro()|flag(ktype.Kind()), iterkey)
|
||||
}
|
||||
|
||||
// SetIterKey assigns to v the key of iter's current map entry.
|
||||
// It is equivalent to v.Set(iter.Key()), but it avoids allocating a new 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() {
|
||||
panic("reflect: Value.SetIterKey called before Next")
|
||||
}
|
||||
iterkey := mapiterkey(&iter.hiter)
|
||||
if iterkey == nil {
|
||||
panic("reflect: Value.SetIterKey called on exhausted iterator")
|
||||
}
|
||||
|
||||
v.mustBeAssignable()
|
||||
var target unsafe.Pointer
|
||||
if v.kind() == Interface {
|
||||
target = v.ptr
|
||||
}
|
||||
|
||||
t := (*mapType)(unsafe.Pointer(iter.m.typ()))
|
||||
ktype := t.Key
|
||||
|
||||
iter.m.mustBeExported() // do not let unexported m leak
|
||||
key := Value{ktype, iterkey, iter.m.flag | flag(ktype.Kind()) | flagIndir}
|
||||
key = key.assignTo("reflect.MapIter.SetKey", v.typ(), target)
|
||||
typedmemmove(v.typ(), v.ptr, key.ptr)
|
||||
}
|
||||
|
||||
// Value returns the value of iter's current map entry.
|
||||
func (iter *MapIter) Value() Value {
|
||||
if !iter.hiter.initialized() {
|
||||
panic("MapIter.Value called before Next")
|
||||
}
|
||||
iterelem := mapiterelem(&iter.hiter)
|
||||
if iterelem == nil {
|
||||
panic("MapIter.Value called on exhausted iterator")
|
||||
}
|
||||
|
||||
t := (*mapType)(unsafe.Pointer(iter.m.typ()))
|
||||
vtype := t.Elem
|
||||
return copyVal(vtype, iter.m.flag.ro()|flag(vtype.Kind()), iterelem)
|
||||
}
|
||||
|
||||
// SetIterValue assigns to v the value of iter's current map entry.
|
||||
// It is equivalent to v.Set(iter.Value()), but it avoids allocating a new 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() {
|
||||
panic("reflect: Value.SetIterValue called before Next")
|
||||
}
|
||||
iterelem := mapiterelem(&iter.hiter)
|
||||
if iterelem == nil {
|
||||
panic("reflect: Value.SetIterValue called on exhausted iterator")
|
||||
}
|
||||
|
||||
v.mustBeAssignable()
|
||||
var target unsafe.Pointer
|
||||
if v.kind() == Interface {
|
||||
target = v.ptr
|
||||
}
|
||||
|
||||
t := (*mapType)(unsafe.Pointer(iter.m.typ()))
|
||||
vtype := t.Elem
|
||||
|
||||
iter.m.mustBeExported() // do not let unexported m leak
|
||||
elem := Value{vtype, iterelem, iter.m.flag | flag(vtype.Kind()) | flagIndir}
|
||||
elem = elem.assignTo("reflect.MapIter.SetValue", v.typ(), target)
|
||||
typedmemmove(v.typ(), v.ptr, elem.ptr)
|
||||
}
|
||||
|
||||
// Next advances the map iterator and reports whether there is another
|
||||
// entry. It returns false when iter is exhausted; subsequent
|
||||
// calls to [MapIter.Key], [MapIter.Value], or [MapIter.Next] will panic.
|
||||
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() {
|
||||
mapiterinit(iter.m.typ(), iter.m.pointer(), &iter.hiter)
|
||||
} else {
|
||||
if mapiterkey(&iter.hiter) == nil {
|
||||
panic("MapIter.Next called on exhausted iterator")
|
||||
}
|
||||
mapiternext(&iter.hiter)
|
||||
}
|
||||
return mapiterkey(&iter.hiter) != nil
|
||||
}
|
||||
|
||||
// Reset modifies iter to iterate over v.
|
||||
// It panics if v's Kind is not [Map] and v is not the zero Value.
|
||||
// Reset(Value{}) causes iter to not to refer to any map,
|
||||
// which may allow the previously iterated-over map to be garbage collected.
|
||||
func (iter *MapIter) Reset(v Value) {
|
||||
if v.IsValid() {
|
||||
v.mustBe(Map)
|
||||
}
|
||||
iter.m = v
|
||||
iter.hiter = hiter{}
|
||||
}
|
||||
|
||||
// MapRange returns a range iterator for a map.
|
||||
// It panics if v's Kind is not [Map].
|
||||
//
|
||||
// Call [MapIter.Next] to advance the iterator, and [MapIter.Key]/[MapIter.Value] to access each entry.
|
||||
// [MapIter.Next] returns false when the iterator is exhausted.
|
||||
// MapRange follows the same iteration semantics as a range statement.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// iter := reflect.ValueOf(m).MapRange()
|
||||
// for iter.Next() {
|
||||
// k := iter.Key()
|
||||
// v := iter.Value()
|
||||
// ...
|
||||
// }
|
||||
func (v Value) MapRange() *MapIter {
|
||||
// This is inlinable to take advantage of "function outlining".
|
||||
// The allocation of MapIter can be stack allocated if the caller
|
||||
// does not allow it to escape.
|
||||
// See https://blog.filippo.io/efficient-go-apis-with-the-inliner/
|
||||
if v.kind() != Map {
|
||||
v.panicNotMap()
|
||||
}
|
||||
return &MapIter{m: v}
|
||||
}
|
||||
|
||||
// SetMapIndex sets the element associated with key in the map v to elem.
|
||||
// It panics if v's Kind is not [Map].
|
||||
// If elem is the zero Value, SetMapIndex deletes the key from the map.
|
||||
// Otherwise if v holds a nil map, SetMapIndex will panic.
|
||||
// As in Go, key's elem must be assignable to the map's key type,
|
||||
// and elem's value must be assignable to the map's elem type.
|
||||
func (v Value) SetMapIndex(key, elem Value) {
|
||||
v.mustBe(Map)
|
||||
v.mustBeExported()
|
||||
key.mustBeExported()
|
||||
tt := (*mapType)(unsafe.Pointer(v.typ()))
|
||||
|
||||
if (tt.Key == stringType || key.kind() == String) && tt.Key == key.typ() && tt.Elem.Size() <= abi.MapMaxElemBytes {
|
||||
k := *(*string)(key.ptr)
|
||||
if elem.typ() == nil {
|
||||
mapdelete_faststr(v.typ(), v.pointer(), k)
|
||||
return
|
||||
}
|
||||
elem.mustBeExported()
|
||||
elem = elem.assignTo("reflect.Value.SetMapIndex", tt.Elem, nil)
|
||||
var e unsafe.Pointer
|
||||
if elem.flag&flagIndir != 0 {
|
||||
e = elem.ptr
|
||||
} else {
|
||||
e = unsafe.Pointer(&elem.ptr)
|
||||
}
|
||||
mapassign_faststr(v.typ(), v.pointer(), k, e)
|
||||
return
|
||||
}
|
||||
|
||||
key = key.assignTo("reflect.Value.SetMapIndex", tt.Key, nil)
|
||||
var k unsafe.Pointer
|
||||
if key.flag&flagIndir != 0 {
|
||||
k = key.ptr
|
||||
} else {
|
||||
k = unsafe.Pointer(&key.ptr)
|
||||
}
|
||||
if elem.typ() == nil {
|
||||
mapdelete(v.typ(), v.pointer(), k)
|
||||
return
|
||||
}
|
||||
elem.mustBeExported()
|
||||
elem = elem.assignTo("reflect.Value.SetMapIndex", tt.Elem, nil)
|
||||
var e unsafe.Pointer
|
||||
if elem.flag&flagIndir != 0 {
|
||||
e = elem.ptr
|
||||
} else {
|
||||
e = unsafe.Pointer(&elem.ptr)
|
||||
}
|
||||
mapassign(v.typ(), v.pointer(), k, e)
|
||||
}
|
||||
|
||||
// Force slow panicking path not inlined, so it won't add to the
|
||||
// inlining budget of the caller.
|
||||
// TODO: undo when the inliner is no longer bottom-up only.
|
||||
//
|
||||
//go:noinline
|
||||
func (f flag) panicNotMap() {
|
||||
f.mustBe(Map)
|
||||
}
|
@ -262,7 +262,7 @@ type Type interface {
|
||||
/*
|
||||
* These data structures are known to the compiler (../cmd/compile/internal/reflectdata/reflect.go).
|
||||
* A few are known to ../runtime/type.go to convey to debuggers.
|
||||
* They are also known to ../runtime/type.go.
|
||||
* They are also known to ../internal/abi/type.go.
|
||||
*/
|
||||
|
||||
// A Kind represents the specific kind of type that a [Type] represents.
|
||||
@ -388,11 +388,6 @@ func (t *interfaceType) uncommon() *abi.UncommonType {
|
||||
return t.Uncommon()
|
||||
}
|
||||
|
||||
// mapType represents a map type.
|
||||
type mapType struct {
|
||||
abi.MapType
|
||||
}
|
||||
|
||||
// ptrType represents a pointer type.
|
||||
type ptrType struct {
|
||||
abi.PtrType
|
||||
@ -773,14 +768,6 @@ func (t *rtype) FieldByNameFunc(match func(string) bool) (StructField, bool) {
|
||||
return tt.FieldByNameFunc(match)
|
||||
}
|
||||
|
||||
func (t *rtype) Key() Type {
|
||||
if t.Kind() != Map {
|
||||
panic("reflect: Key of non-map type " + t.String())
|
||||
}
|
||||
tt := (*mapType)(unsafe.Pointer(t))
|
||||
return toType(tt.Key)
|
||||
}
|
||||
|
||||
func (t *rtype) Len() int {
|
||||
if t.Kind() != Array {
|
||||
panic("reflect: Len of non-array type " + t.String())
|
||||
@ -1801,79 +1788,6 @@ func ChanOf(dir ChanDir, t Type) Type {
|
||||
return ti.(Type)
|
||||
}
|
||||
|
||||
// MapOf returns the map type with the given key and element types.
|
||||
// For example, if k represents int and e represents string,
|
||||
// MapOf(k, e) represents map[int]string.
|
||||
//
|
||||
// If the key type is not a valid map key type (that is, if it does
|
||||
// not implement Go's == operator), MapOf panics.
|
||||
func MapOf(key, elem Type) Type {
|
||||
ktyp := key.common()
|
||||
etyp := elem.common()
|
||||
|
||||
if ktyp.Equal == nil {
|
||||
panic("reflect.MapOf: invalid key type " + stringFor(ktyp))
|
||||
}
|
||||
|
||||
// Look in cache.
|
||||
ckey := cacheKey{Map, ktyp, etyp, 0}
|
||||
if mt, ok := lookupCache.Load(ckey); ok {
|
||||
return mt.(Type)
|
||||
}
|
||||
|
||||
// Look in known types.
|
||||
s := "map[" + stringFor(ktyp) + "]" + stringFor(etyp)
|
||||
for _, tt := range typesByString(s) {
|
||||
mt := (*mapType)(unsafe.Pointer(tt))
|
||||
if mt.Key == ktyp && mt.Elem == etyp {
|
||||
ti, _ := lookupCache.LoadOrStore(ckey, toRType(tt))
|
||||
return ti.(Type)
|
||||
}
|
||||
}
|
||||
|
||||
// Make a map type.
|
||||
// Note: flag values must match those used in the TMAP case
|
||||
// in ../cmd/compile/internal/reflectdata/reflect.go:writeType.
|
||||
var imap any = (map[unsafe.Pointer]unsafe.Pointer)(nil)
|
||||
mt := **(**mapType)(unsafe.Pointer(&imap))
|
||||
mt.Str = resolveReflectName(newName(s, "", false, false))
|
||||
mt.TFlag = 0
|
||||
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.Hasher = func(p unsafe.Pointer, seed uintptr) uintptr {
|
||||
return typehash(ktyp, p, seed)
|
||||
}
|
||||
mt.Flags = 0
|
||||
if ktyp.Size_ > abi.MapMaxKeyBytes {
|
||||
mt.KeySize = uint8(goarch.PtrSize)
|
||||
mt.Flags |= 1 // indirect key
|
||||
} else {
|
||||
mt.KeySize = uint8(ktyp.Size_)
|
||||
}
|
||||
if etyp.Size_ > abi.MapMaxElemBytes {
|
||||
mt.ValueSize = uint8(goarch.PtrSize)
|
||||
mt.Flags |= 2 // indirect value
|
||||
} else {
|
||||
mt.MapType.ValueSize = uint8(etyp.Size_)
|
||||
}
|
||||
mt.MapType.BucketSize = uint16(mt.Bucket.Size_)
|
||||
if isReflexive(ktyp) {
|
||||
mt.Flags |= 4
|
||||
}
|
||||
if needKeyUpdate(ktyp) {
|
||||
mt.Flags |= 8
|
||||
}
|
||||
if hashMightPanic(ktyp) {
|
||||
mt.Flags |= 16
|
||||
}
|
||||
mt.PtrToThis = 0
|
||||
|
||||
ti, _ := lookupCache.LoadOrStore(ckey, toRType(&mt.Type))
|
||||
return ti.(Type)
|
||||
}
|
||||
|
||||
var funcTypes []Type
|
||||
var funcTypesMutex sync.Mutex
|
||||
|
||||
@ -2106,69 +2020,6 @@ func hashMightPanic(t *abi.Type) bool {
|
||||
}
|
||||
}
|
||||
|
||||
func bucketOf(ktyp, etyp *abi.Type) *abi.Type {
|
||||
if ktyp.Size_ > abi.MapMaxKeyBytes {
|
||||
ktyp = ptrTo(ktyp)
|
||||
}
|
||||
if etyp.Size_ > abi.MapMaxElemBytes {
|
||||
etyp = ptrTo(etyp)
|
||||
}
|
||||
|
||||
// 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.MapBucketCount*(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.MapBucketCount*(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.MapBucketCount / goarch.PtrSize)
|
||||
|
||||
if ktyp.Pointers() {
|
||||
emitGCMask(mask, base, ktyp, abi.MapBucketCount)
|
||||
}
|
||||
base += abi.MapBucketCount * ktyp.Size_ / goarch.PtrSize
|
||||
|
||||
if etyp.Pointers() {
|
||||
emitGCMask(mask, base, etyp, abi.MapBucketCount)
|
||||
}
|
||||
base += abi.MapBucketCount * 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
|
||||
}
|
||||
|
||||
func (t *rtype) gcSlice(begin, end uintptr) []byte {
|
||||
return (*[1 << 30]byte)(unsafe.Pointer(t.t.GCData))[begin:end:end]
|
||||
}
|
||||
|
@ -1779,265 +1779,6 @@ func (v Value) lenNonSlice() int {
|
||||
panic(&ValueError{"reflect.Value.Len", v.kind()})
|
||||
}
|
||||
|
||||
var stringType = rtypeOf("")
|
||||
|
||||
// MapIndex returns the value associated with key in the map v.
|
||||
// It panics if v's Kind is not [Map].
|
||||
// It returns the zero Value if key is not found in the map or if v represents a nil map.
|
||||
// As in Go, the key's value must be assignable to the map's key type.
|
||||
func (v Value) MapIndex(key Value) Value {
|
||||
v.mustBe(Map)
|
||||
tt := (*mapType)(unsafe.Pointer(v.typ()))
|
||||
|
||||
// Do not require key to be exported, so that DeepEqual
|
||||
// and other programs can use all the keys returned by
|
||||
// MapKeys as arguments to MapIndex. If either the map
|
||||
// or the key is unexported, though, the result will be
|
||||
// considered unexported. This is consistent with the
|
||||
// behavior for structs, which allow read but not write
|
||||
// of unexported fields.
|
||||
|
||||
var e unsafe.Pointer
|
||||
if (tt.Key == stringType || key.kind() == String) && tt.Key == key.typ() && tt.Elem.Size() <= abi.MapMaxElemBytes {
|
||||
k := *(*string)(key.ptr)
|
||||
e = mapaccess_faststr(v.typ(), v.pointer(), k)
|
||||
} else {
|
||||
key = key.assignTo("reflect.Value.MapIndex", tt.Key, nil)
|
||||
var k unsafe.Pointer
|
||||
if key.flag&flagIndir != 0 {
|
||||
k = key.ptr
|
||||
} else {
|
||||
k = unsafe.Pointer(&key.ptr)
|
||||
}
|
||||
e = mapaccess(v.typ(), v.pointer(), k)
|
||||
}
|
||||
if e == nil {
|
||||
return Value{}
|
||||
}
|
||||
typ := tt.Elem
|
||||
fl := (v.flag | key.flag).ro()
|
||||
fl |= flag(typ.Kind())
|
||||
return copyVal(typ, fl, e)
|
||||
}
|
||||
|
||||
// MapKeys returns a slice containing all the keys present in the map,
|
||||
// in unspecified order.
|
||||
// It panics if v's Kind is not [Map].
|
||||
// It returns an empty slice if v represents a nil map.
|
||||
func (v Value) MapKeys() []Value {
|
||||
v.mustBe(Map)
|
||||
tt := (*mapType)(unsafe.Pointer(v.typ()))
|
||||
keyType := tt.Key
|
||||
|
||||
fl := v.flag.ro() | flag(keyType.Kind())
|
||||
|
||||
m := v.pointer()
|
||||
mlen := int(0)
|
||||
if m != nil {
|
||||
mlen = maplen(m)
|
||||
}
|
||||
var it hiter
|
||||
mapiterinit(v.typ(), m, &it)
|
||||
a := make([]Value, mlen)
|
||||
var i int
|
||||
for i = 0; i < len(a); i++ {
|
||||
key := mapiterkey(&it)
|
||||
if key == nil {
|
||||
// Someone deleted an entry from the map since we
|
||||
// called maplen above. It's a data race, but nothing
|
||||
// we can do about it.
|
||||
break
|
||||
}
|
||||
a[i] = copyVal(keyType, fl, key)
|
||||
mapiternext(&it)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// Key returns the key of iter's current map entry.
|
||||
func (iter *MapIter) Key() Value {
|
||||
if !iter.hiter.initialized() {
|
||||
panic("MapIter.Key called before Next")
|
||||
}
|
||||
iterkey := mapiterkey(&iter.hiter)
|
||||
if iterkey == nil {
|
||||
panic("MapIter.Key called on exhausted iterator")
|
||||
}
|
||||
|
||||
t := (*mapType)(unsafe.Pointer(iter.m.typ()))
|
||||
ktype := t.Key
|
||||
return copyVal(ktype, iter.m.flag.ro()|flag(ktype.Kind()), iterkey)
|
||||
}
|
||||
|
||||
// SetIterKey assigns to v the key of iter's current map entry.
|
||||
// It is equivalent to v.Set(iter.Key()), but it avoids allocating a new 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() {
|
||||
panic("reflect: Value.SetIterKey called before Next")
|
||||
}
|
||||
iterkey := mapiterkey(&iter.hiter)
|
||||
if iterkey == nil {
|
||||
panic("reflect: Value.SetIterKey called on exhausted iterator")
|
||||
}
|
||||
|
||||
v.mustBeAssignable()
|
||||
var target unsafe.Pointer
|
||||
if v.kind() == Interface {
|
||||
target = v.ptr
|
||||
}
|
||||
|
||||
t := (*mapType)(unsafe.Pointer(iter.m.typ()))
|
||||
ktype := t.Key
|
||||
|
||||
iter.m.mustBeExported() // do not let unexported m leak
|
||||
key := Value{ktype, iterkey, iter.m.flag | flag(ktype.Kind()) | flagIndir}
|
||||
key = key.assignTo("reflect.MapIter.SetKey", v.typ(), target)
|
||||
typedmemmove(v.typ(), v.ptr, key.ptr)
|
||||
}
|
||||
|
||||
// Value returns the value of iter's current map entry.
|
||||
func (iter *MapIter) Value() Value {
|
||||
if !iter.hiter.initialized() {
|
||||
panic("MapIter.Value called before Next")
|
||||
}
|
||||
iterelem := mapiterelem(&iter.hiter)
|
||||
if iterelem == nil {
|
||||
panic("MapIter.Value called on exhausted iterator")
|
||||
}
|
||||
|
||||
t := (*mapType)(unsafe.Pointer(iter.m.typ()))
|
||||
vtype := t.Elem
|
||||
return copyVal(vtype, iter.m.flag.ro()|flag(vtype.Kind()), iterelem)
|
||||
}
|
||||
|
||||
// SetIterValue assigns to v the value of iter's current map entry.
|
||||
// It is equivalent to v.Set(iter.Value()), but it avoids allocating a new 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() {
|
||||
panic("reflect: Value.SetIterValue called before Next")
|
||||
}
|
||||
iterelem := mapiterelem(&iter.hiter)
|
||||
if iterelem == nil {
|
||||
panic("reflect: Value.SetIterValue called on exhausted iterator")
|
||||
}
|
||||
|
||||
v.mustBeAssignable()
|
||||
var target unsafe.Pointer
|
||||
if v.kind() == Interface {
|
||||
target = v.ptr
|
||||
}
|
||||
|
||||
t := (*mapType)(unsafe.Pointer(iter.m.typ()))
|
||||
vtype := t.Elem
|
||||
|
||||
iter.m.mustBeExported() // do not let unexported m leak
|
||||
elem := Value{vtype, iterelem, iter.m.flag | flag(vtype.Kind()) | flagIndir}
|
||||
elem = elem.assignTo("reflect.MapIter.SetValue", v.typ(), target)
|
||||
typedmemmove(v.typ(), v.ptr, elem.ptr)
|
||||
}
|
||||
|
||||
// Next advances the map iterator and reports whether there is another
|
||||
// entry. It returns false when iter is exhausted; subsequent
|
||||
// calls to [MapIter.Key], [MapIter.Value], or [MapIter.Next] will panic.
|
||||
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() {
|
||||
mapiterinit(iter.m.typ(), iter.m.pointer(), &iter.hiter)
|
||||
} else {
|
||||
if mapiterkey(&iter.hiter) == nil {
|
||||
panic("MapIter.Next called on exhausted iterator")
|
||||
}
|
||||
mapiternext(&iter.hiter)
|
||||
}
|
||||
return mapiterkey(&iter.hiter) != nil
|
||||
}
|
||||
|
||||
// Reset modifies iter to iterate over v.
|
||||
// It panics if v's Kind is not [Map] and v is not the zero Value.
|
||||
// Reset(Value{}) causes iter to not to refer to any map,
|
||||
// which may allow the previously iterated-over map to be garbage collected.
|
||||
func (iter *MapIter) Reset(v Value) {
|
||||
if v.IsValid() {
|
||||
v.mustBe(Map)
|
||||
}
|
||||
iter.m = v
|
||||
iter.hiter = hiter{}
|
||||
}
|
||||
|
||||
// MapRange returns a range iterator for a map.
|
||||
// It panics if v's Kind is not [Map].
|
||||
//
|
||||
// Call [MapIter.Next] to advance the iterator, and [MapIter.Key]/[MapIter.Value] to access each entry.
|
||||
// [MapIter.Next] returns false when the iterator is exhausted.
|
||||
// MapRange follows the same iteration semantics as a range statement.
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// iter := reflect.ValueOf(m).MapRange()
|
||||
// for iter.Next() {
|
||||
// k := iter.Key()
|
||||
// v := iter.Value()
|
||||
// ...
|
||||
// }
|
||||
func (v Value) MapRange() *MapIter {
|
||||
// This is inlinable to take advantage of "function outlining".
|
||||
// The allocation of MapIter can be stack allocated if the caller
|
||||
// does not allow it to escape.
|
||||
// See https://blog.filippo.io/efficient-go-apis-with-the-inliner/
|
||||
if v.kind() != Map {
|
||||
v.panicNotMap()
|
||||
}
|
||||
return &MapIter{m: v}
|
||||
}
|
||||
|
||||
// Force slow panicking path not inlined, so it won't add to the
|
||||
// inlining budget of the caller.
|
||||
// TODO: undo when the inliner is no longer bottom-up only.
|
||||
//
|
||||
//go:noinline
|
||||
func (f flag) panicNotMap() {
|
||||
f.mustBe(Map)
|
||||
}
|
||||
|
||||
// copyVal returns a Value containing the map key or value at ptr,
|
||||
// allocating a new variable as needed.
|
||||
func copyVal(typ *abi.Type, fl flag, ptr unsafe.Pointer) Value {
|
||||
@ -2426,58 +2167,6 @@ func (v Value) SetCap(n int) {
|
||||
s.Cap = n
|
||||
}
|
||||
|
||||
// SetMapIndex sets the element associated with key in the map v to elem.
|
||||
// It panics if v's Kind is not [Map].
|
||||
// If elem is the zero Value, SetMapIndex deletes the key from the map.
|
||||
// Otherwise if v holds a nil map, SetMapIndex will panic.
|
||||
// As in Go, key's elem must be assignable to the map's key type,
|
||||
// and elem's value must be assignable to the map's elem type.
|
||||
func (v Value) SetMapIndex(key, elem Value) {
|
||||
v.mustBe(Map)
|
||||
v.mustBeExported()
|
||||
key.mustBeExported()
|
||||
tt := (*mapType)(unsafe.Pointer(v.typ()))
|
||||
|
||||
if (tt.Key == stringType || key.kind() == String) && tt.Key == key.typ() && tt.Elem.Size() <= abi.MapMaxElemBytes {
|
||||
k := *(*string)(key.ptr)
|
||||
if elem.typ() == nil {
|
||||
mapdelete_faststr(v.typ(), v.pointer(), k)
|
||||
return
|
||||
}
|
||||
elem.mustBeExported()
|
||||
elem = elem.assignTo("reflect.Value.SetMapIndex", tt.Elem, nil)
|
||||
var e unsafe.Pointer
|
||||
if elem.flag&flagIndir != 0 {
|
||||
e = elem.ptr
|
||||
} else {
|
||||
e = unsafe.Pointer(&elem.ptr)
|
||||
}
|
||||
mapassign_faststr(v.typ(), v.pointer(), k, e)
|
||||
return
|
||||
}
|
||||
|
||||
key = key.assignTo("reflect.Value.SetMapIndex", tt.Key, nil)
|
||||
var k unsafe.Pointer
|
||||
if key.flag&flagIndir != 0 {
|
||||
k = key.ptr
|
||||
} else {
|
||||
k = unsafe.Pointer(&key.ptr)
|
||||
}
|
||||
if elem.typ() == nil {
|
||||
mapdelete(v.typ(), v.pointer(), k)
|
||||
return
|
||||
}
|
||||
elem.mustBeExported()
|
||||
elem = elem.assignTo("reflect.Value.SetMapIndex", tt.Elem, nil)
|
||||
var e unsafe.Pointer
|
||||
if elem.flag&flagIndir != 0 {
|
||||
e = elem.ptr
|
||||
} else {
|
||||
e = unsafe.Pointer(&elem.ptr)
|
||||
}
|
||||
mapassign(v.typ(), v.pointer(), k, e)
|
||||
}
|
||||
|
||||
// SetUint sets v's underlying value to x.
|
||||
// It panics if v's Kind is not [Uint], [Uintptr], [Uint8], [Uint16], [Uint32], or [Uint64], or if [Value.CanSet] returns false.
|
||||
func (v Value) SetUint(x uint64) {
|
||||
|
@ -2,6 +2,8 @@
|
||||
// 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 runtime
|
||||
|
||||
import (
|
464
src/runtime/map_fast32_swiss.go
Normal file
464
src/runtime/map_fast32_swiss.go
Normal file
@ -0,0 +1,464 @@
|
||||
// Copyright 2018 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 runtime
|
||||
|
||||
import (
|
||||
"internal/abi"
|
||||
"internal/goarch"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func mapaccess1_fast32(t *maptype, h *hmap, key uint32) unsafe.Pointer {
|
||||
if raceenabled && h != nil {
|
||||
callerpc := getcallerpc()
|
||||
racereadpc(unsafe.Pointer(h), callerpc, abi.FuncPCABIInternal(mapaccess1_fast32))
|
||||
}
|
||||
if h == nil || h.count == 0 {
|
||||
return unsafe.Pointer(&zeroVal[0])
|
||||
}
|
||||
if h.flags&hashWriting != 0 {
|
||||
fatal("concurrent map read and map write")
|
||||
}
|
||||
var b *bmap
|
||||
if h.B == 0 {
|
||||
// One-bucket table. No need to hash.
|
||||
b = (*bmap)(h.buckets)
|
||||
} else {
|
||||
hash := t.Hasher(noescape(unsafe.Pointer(&key)), uintptr(h.hash0))
|
||||
m := bucketMask(h.B)
|
||||
b = (*bmap)(add(h.buckets, (hash&m)*uintptr(t.BucketSize)))
|
||||
if c := h.oldbuckets; c != nil {
|
||||
if !h.sameSizeGrow() {
|
||||
// There used to be half as many buckets; mask down one more power of two.
|
||||
m >>= 1
|
||||
}
|
||||
oldb := (*bmap)(add(c, (hash&m)*uintptr(t.BucketSize)))
|
||||
if !evacuated(oldb) {
|
||||
b = oldb
|
||||
}
|
||||
}
|
||||
}
|
||||
for ; b != nil; b = b.overflow(t) {
|
||||
for i, k := uintptr(0), b.keys(); i < abi.MapBucketCount; i, k = i+1, add(k, 4) {
|
||||
if *(*uint32)(k) == key && !isEmpty(b.tophash[i]) {
|
||||
return add(unsafe.Pointer(b), dataOffset+abi.MapBucketCount*4+i*uintptr(t.ValueSize))
|
||||
}
|
||||
}
|
||||
}
|
||||
return unsafe.Pointer(&zeroVal[0])
|
||||
}
|
||||
|
||||
func mapaccess2_fast32(t *maptype, h *hmap, key uint32) (unsafe.Pointer, bool) {
|
||||
if raceenabled && h != nil {
|
||||
callerpc := getcallerpc()
|
||||
racereadpc(unsafe.Pointer(h), callerpc, abi.FuncPCABIInternal(mapaccess2_fast32))
|
||||
}
|
||||
if h == nil || h.count == 0 {
|
||||
return unsafe.Pointer(&zeroVal[0]), false
|
||||
}
|
||||
if h.flags&hashWriting != 0 {
|
||||
fatal("concurrent map read and map write")
|
||||
}
|
||||
var b *bmap
|
||||
if h.B == 0 {
|
||||
// One-bucket table. No need to hash.
|
||||
b = (*bmap)(h.buckets)
|
||||
} else {
|
||||
hash := t.Hasher(noescape(unsafe.Pointer(&key)), uintptr(h.hash0))
|
||||
m := bucketMask(h.B)
|
||||
b = (*bmap)(add(h.buckets, (hash&m)*uintptr(t.BucketSize)))
|
||||
if c := h.oldbuckets; c != nil {
|
||||
if !h.sameSizeGrow() {
|
||||
// There used to be half as many buckets; mask down one more power of two.
|
||||
m >>= 1
|
||||
}
|
||||
oldb := (*bmap)(add(c, (hash&m)*uintptr(t.BucketSize)))
|
||||
if !evacuated(oldb) {
|
||||
b = oldb
|
||||
}
|
||||
}
|
||||
}
|
||||
for ; b != nil; b = b.overflow(t) {
|
||||
for i, k := uintptr(0), b.keys(); i < abi.MapBucketCount; i, k = i+1, add(k, 4) {
|
||||
if *(*uint32)(k) == key && !isEmpty(b.tophash[i]) {
|
||||
return add(unsafe.Pointer(b), dataOffset+abi.MapBucketCount*4+i*uintptr(t.ValueSize)), true
|
||||
}
|
||||
}
|
||||
}
|
||||
return unsafe.Pointer(&zeroVal[0]), false
|
||||
}
|
||||
|
||||
func mapassign_fast32(t *maptype, h *hmap, key uint32) unsafe.Pointer {
|
||||
if h == nil {
|
||||
panic(plainError("assignment to entry in nil map"))
|
||||
}
|
||||
if raceenabled {
|
||||
callerpc := getcallerpc()
|
||||
racewritepc(unsafe.Pointer(h), callerpc, abi.FuncPCABIInternal(mapassign_fast32))
|
||||
}
|
||||
if h.flags&hashWriting != 0 {
|
||||
fatal("concurrent map writes")
|
||||
}
|
||||
hash := t.Hasher(noescape(unsafe.Pointer(&key)), uintptr(h.hash0))
|
||||
|
||||
// Set hashWriting after calling t.hasher for consistency with mapassign.
|
||||
h.flags ^= hashWriting
|
||||
|
||||
if h.buckets == nil {
|
||||
h.buckets = newobject(t.Bucket) // newarray(t.bucket, 1)
|
||||
}
|
||||
|
||||
again:
|
||||
bucket := hash & bucketMask(h.B)
|
||||
if h.growing() {
|
||||
growWork_fast32(t, h, bucket)
|
||||
}
|
||||
b := (*bmap)(add(h.buckets, bucket*uintptr(t.BucketSize)))
|
||||
|
||||
var insertb *bmap
|
||||
var inserti uintptr
|
||||
var insertk unsafe.Pointer
|
||||
|
||||
bucketloop:
|
||||
for {
|
||||
for i := uintptr(0); i < abi.MapBucketCount; i++ {
|
||||
if isEmpty(b.tophash[i]) {
|
||||
if insertb == nil {
|
||||
inserti = i
|
||||
insertb = b
|
||||
}
|
||||
if b.tophash[i] == emptyRest {
|
||||
break bucketloop
|
||||
}
|
||||
continue
|
||||
}
|
||||
k := *((*uint32)(add(unsafe.Pointer(b), dataOffset+i*4)))
|
||||
if k != key {
|
||||
continue
|
||||
}
|
||||
inserti = i
|
||||
insertb = b
|
||||
goto done
|
||||
}
|
||||
ovf := b.overflow(t)
|
||||
if ovf == nil {
|
||||
break
|
||||
}
|
||||
b = ovf
|
||||
}
|
||||
|
||||
// Did not find mapping for key. Allocate new cell & add entry.
|
||||
|
||||
// If we hit the max load factor or we have too many overflow buckets,
|
||||
// and we're not already in the middle of growing, start growing.
|
||||
if !h.growing() && (overLoadFactor(h.count+1, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) {
|
||||
hashGrow(t, h)
|
||||
goto again // Growing the table invalidates everything, so try again
|
||||
}
|
||||
|
||||
if insertb == nil {
|
||||
// The current bucket and all the overflow buckets connected to it are full, allocate a new one.
|
||||
insertb = h.newoverflow(t, b)
|
||||
inserti = 0 // not necessary, but avoids needlessly spilling inserti
|
||||
}
|
||||
insertb.tophash[inserti&(abi.MapBucketCount-1)] = tophash(hash) // mask inserti to avoid bounds checks
|
||||
|
||||
insertk = add(unsafe.Pointer(insertb), dataOffset+inserti*4)
|
||||
// store new key at insert position
|
||||
*(*uint32)(insertk) = key
|
||||
|
||||
h.count++
|
||||
|
||||
done:
|
||||
elem := add(unsafe.Pointer(insertb), dataOffset+abi.MapBucketCount*4+inserti*uintptr(t.ValueSize))
|
||||
if h.flags&hashWriting == 0 {
|
||||
fatal("concurrent map writes")
|
||||
}
|
||||
h.flags &^= hashWriting
|
||||
return elem
|
||||
}
|
||||
|
||||
func mapassign_fast32ptr(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
|
||||
if h == nil {
|
||||
panic(plainError("assignment to entry in nil map"))
|
||||
}
|
||||
if raceenabled {
|
||||
callerpc := getcallerpc()
|
||||
racewritepc(unsafe.Pointer(h), callerpc, abi.FuncPCABIInternal(mapassign_fast32))
|
||||
}
|
||||
if h.flags&hashWriting != 0 {
|
||||
fatal("concurrent map writes")
|
||||
}
|
||||
hash := t.Hasher(noescape(unsafe.Pointer(&key)), uintptr(h.hash0))
|
||||
|
||||
// Set hashWriting after calling t.hasher for consistency with mapassign.
|
||||
h.flags ^= hashWriting
|
||||
|
||||
if h.buckets == nil {
|
||||
h.buckets = newobject(t.Bucket) // newarray(t.bucket, 1)
|
||||
}
|
||||
|
||||
again:
|
||||
bucket := hash & bucketMask(h.B)
|
||||
if h.growing() {
|
||||
growWork_fast32(t, h, bucket)
|
||||
}
|
||||
b := (*bmap)(add(h.buckets, bucket*uintptr(t.BucketSize)))
|
||||
|
||||
var insertb *bmap
|
||||
var inserti uintptr
|
||||
var insertk unsafe.Pointer
|
||||
|
||||
bucketloop:
|
||||
for {
|
||||
for i := uintptr(0); i < abi.MapBucketCount; i++ {
|
||||
if isEmpty(b.tophash[i]) {
|
||||
if insertb == nil {
|
||||
inserti = i
|
||||
insertb = b
|
||||
}
|
||||
if b.tophash[i] == emptyRest {
|
||||
break bucketloop
|
||||
}
|
||||
continue
|
||||
}
|
||||
k := *((*unsafe.Pointer)(add(unsafe.Pointer(b), dataOffset+i*4)))
|
||||
if k != key {
|
||||
continue
|
||||
}
|
||||
inserti = i
|
||||
insertb = b
|
||||
goto done
|
||||
}
|
||||
ovf := b.overflow(t)
|
||||
if ovf == nil {
|
||||
break
|
||||
}
|
||||
b = ovf
|
||||
}
|
||||
|
||||
// Did not find mapping for key. Allocate new cell & add entry.
|
||||
|
||||
// If we hit the max load factor or we have too many overflow buckets,
|
||||
// and we're not already in the middle of growing, start growing.
|
||||
if !h.growing() && (overLoadFactor(h.count+1, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) {
|
||||
hashGrow(t, h)
|
||||
goto again // Growing the table invalidates everything, so try again
|
||||
}
|
||||
|
||||
if insertb == nil {
|
||||
// The current bucket and all the overflow buckets connected to it are full, allocate a new one.
|
||||
insertb = h.newoverflow(t, b)
|
||||
inserti = 0 // not necessary, but avoids needlessly spilling inserti
|
||||
}
|
||||
insertb.tophash[inserti&(abi.MapBucketCount-1)] = tophash(hash) // mask inserti to avoid bounds checks
|
||||
|
||||
insertk = add(unsafe.Pointer(insertb), dataOffset+inserti*4)
|
||||
// store new key at insert position
|
||||
*(*unsafe.Pointer)(insertk) = key
|
||||
|
||||
h.count++
|
||||
|
||||
done:
|
||||
elem := add(unsafe.Pointer(insertb), dataOffset+abi.MapBucketCount*4+inserti*uintptr(t.ValueSize))
|
||||
if h.flags&hashWriting == 0 {
|
||||
fatal("concurrent map writes")
|
||||
}
|
||||
h.flags &^= hashWriting
|
||||
return elem
|
||||
}
|
||||
|
||||
func mapdelete_fast32(t *maptype, h *hmap, key uint32) {
|
||||
if raceenabled && h != nil {
|
||||
callerpc := getcallerpc()
|
||||
racewritepc(unsafe.Pointer(h), callerpc, abi.FuncPCABIInternal(mapdelete_fast32))
|
||||
}
|
||||
if h == nil || h.count == 0 {
|
||||
return
|
||||
}
|
||||
if h.flags&hashWriting != 0 {
|
||||
fatal("concurrent map writes")
|
||||
}
|
||||
|
||||
hash := t.Hasher(noescape(unsafe.Pointer(&key)), uintptr(h.hash0))
|
||||
|
||||
// Set hashWriting after calling t.hasher for consistency with mapdelete
|
||||
h.flags ^= hashWriting
|
||||
|
||||
bucket := hash & bucketMask(h.B)
|
||||
if h.growing() {
|
||||
growWork_fast32(t, h, bucket)
|
||||
}
|
||||
b := (*bmap)(add(h.buckets, bucket*uintptr(t.BucketSize)))
|
||||
bOrig := b
|
||||
search:
|
||||
for ; b != nil; b = b.overflow(t) {
|
||||
for i, k := uintptr(0), b.keys(); i < abi.MapBucketCount; i, k = i+1, add(k, 4) {
|
||||
if key != *(*uint32)(k) || isEmpty(b.tophash[i]) {
|
||||
continue
|
||||
}
|
||||
// Only clear key if there are pointers in it.
|
||||
// This can only happen if pointers are 32 bit
|
||||
// wide as 64 bit pointers do not fit into a 32 bit key.
|
||||
if goarch.PtrSize == 4 && t.Key.Pointers() {
|
||||
// The key must be a pointer as we checked pointers are
|
||||
// 32 bits wide and the key is 32 bits wide also.
|
||||
*(*unsafe.Pointer)(k) = nil
|
||||
}
|
||||
e := add(unsafe.Pointer(b), dataOffset+abi.MapBucketCount*4+i*uintptr(t.ValueSize))
|
||||
if t.Elem.Pointers() {
|
||||
memclrHasPointers(e, t.Elem.Size_)
|
||||
} else {
|
||||
memclrNoHeapPointers(e, t.Elem.Size_)
|
||||
}
|
||||
b.tophash[i] = emptyOne
|
||||
// If the bucket now ends in a bunch of emptyOne states,
|
||||
// change those to emptyRest states.
|
||||
if i == abi.MapBucketCount-1 {
|
||||
if b.overflow(t) != nil && b.overflow(t).tophash[0] != emptyRest {
|
||||
goto notLast
|
||||
}
|
||||
} else {
|
||||
if b.tophash[i+1] != emptyRest {
|
||||
goto notLast
|
||||
}
|
||||
}
|
||||
for {
|
||||
b.tophash[i] = emptyRest
|
||||
if i == 0 {
|
||||
if b == bOrig {
|
||||
break // beginning of initial bucket, we're done.
|
||||
}
|
||||
// Find previous bucket, continue at its last entry.
|
||||
c := b
|
||||
for b = bOrig; b.overflow(t) != c; b = b.overflow(t) {
|
||||
}
|
||||
i = abi.MapBucketCount - 1
|
||||
} else {
|
||||
i--
|
||||
}
|
||||
if b.tophash[i] != emptyOne {
|
||||
break
|
||||
}
|
||||
}
|
||||
notLast:
|
||||
h.count--
|
||||
// Reset the hash seed to make it more difficult for attackers to
|
||||
// repeatedly trigger hash collisions. See issue 25237.
|
||||
if h.count == 0 {
|
||||
h.hash0 = uint32(rand())
|
||||
}
|
||||
break search
|
||||
}
|
||||
}
|
||||
|
||||
if h.flags&hashWriting == 0 {
|
||||
fatal("concurrent map writes")
|
||||
}
|
||||
h.flags &^= hashWriting
|
||||
}
|
||||
|
||||
func growWork_fast32(t *maptype, h *hmap, bucket uintptr) {
|
||||
// make sure we evacuate the oldbucket corresponding
|
||||
// to the bucket we're about to use
|
||||
evacuate_fast32(t, h, bucket&h.oldbucketmask())
|
||||
|
||||
// evacuate one more oldbucket to make progress on growing
|
||||
if h.growing() {
|
||||
evacuate_fast32(t, h, h.nevacuate)
|
||||
}
|
||||
}
|
||||
|
||||
func evacuate_fast32(t *maptype, h *hmap, oldbucket uintptr) {
|
||||
b := (*bmap)(add(h.oldbuckets, oldbucket*uintptr(t.BucketSize)))
|
||||
newbit := h.noldbuckets()
|
||||
if !evacuated(b) {
|
||||
// TODO: reuse overflow buckets instead of using new ones, if there
|
||||
// is no iterator using the old buckets. (If !oldIterator.)
|
||||
|
||||
// xy contains the x and y (low and high) evacuation destinations.
|
||||
var xy [2]evacDst
|
||||
x := &xy[0]
|
||||
x.b = (*bmap)(add(h.buckets, oldbucket*uintptr(t.BucketSize)))
|
||||
x.k = add(unsafe.Pointer(x.b), dataOffset)
|
||||
x.e = add(x.k, abi.MapBucketCount*4)
|
||||
|
||||
if !h.sameSizeGrow() {
|
||||
// Only calculate y pointers if we're growing bigger.
|
||||
// Otherwise GC can see bad pointers.
|
||||
y := &xy[1]
|
||||
y.b = (*bmap)(add(h.buckets, (oldbucket+newbit)*uintptr(t.BucketSize)))
|
||||
y.k = add(unsafe.Pointer(y.b), dataOffset)
|
||||
y.e = add(y.k, abi.MapBucketCount*4)
|
||||
}
|
||||
|
||||
for ; b != nil; b = b.overflow(t) {
|
||||
k := add(unsafe.Pointer(b), dataOffset)
|
||||
e := add(k, abi.MapBucketCount*4)
|
||||
for i := 0; i < abi.MapBucketCount; i, k, e = i+1, add(k, 4), add(e, uintptr(t.ValueSize)) {
|
||||
top := b.tophash[i]
|
||||
if isEmpty(top) {
|
||||
b.tophash[i] = evacuatedEmpty
|
||||
continue
|
||||
}
|
||||
if top < minTopHash {
|
||||
throw("bad map state")
|
||||
}
|
||||
var useY uint8
|
||||
if !h.sameSizeGrow() {
|
||||
// Compute hash to make our evacuation decision (whether we need
|
||||
// to send this key/elem to bucket x or bucket y).
|
||||
hash := t.Hasher(k, uintptr(h.hash0))
|
||||
if hash&newbit != 0 {
|
||||
useY = 1
|
||||
}
|
||||
}
|
||||
|
||||
b.tophash[i] = evacuatedX + useY // evacuatedX + 1 == evacuatedY, enforced in makemap
|
||||
dst := &xy[useY] // evacuation destination
|
||||
|
||||
if dst.i == abi.MapBucketCount {
|
||||
dst.b = h.newoverflow(t, dst.b)
|
||||
dst.i = 0
|
||||
dst.k = add(unsafe.Pointer(dst.b), dataOffset)
|
||||
dst.e = add(dst.k, abi.MapBucketCount*4)
|
||||
}
|
||||
dst.b.tophash[dst.i&(abi.MapBucketCount-1)] = top // mask dst.i as an optimization, to avoid a bounds check
|
||||
|
||||
// Copy key.
|
||||
if goarch.PtrSize == 4 && t.Key.Pointers() && writeBarrier.enabled {
|
||||
// Write with a write barrier.
|
||||
*(*unsafe.Pointer)(dst.k) = *(*unsafe.Pointer)(k)
|
||||
} else {
|
||||
*(*uint32)(dst.k) = *(*uint32)(k)
|
||||
}
|
||||
|
||||
typedmemmove(t.Elem, dst.e, e)
|
||||
dst.i++
|
||||
// These updates might push these pointers past the end of the
|
||||
// key or elem arrays. That's ok, as we have the overflow pointer
|
||||
// at the end of the bucket to protect against pointing past the
|
||||
// end of the bucket.
|
||||
dst.k = add(dst.k, 4)
|
||||
dst.e = add(dst.e, uintptr(t.ValueSize))
|
||||
}
|
||||
}
|
||||
// Unlink the overflow buckets & clear key/elem to help GC.
|
||||
if h.flags&oldIterator == 0 && t.Bucket.Pointers() {
|
||||
b := add(h.oldbuckets, oldbucket*uintptr(t.BucketSize))
|
||||
// Preserve b.tophash because the evacuation
|
||||
// state is maintained there.
|
||||
ptr := add(b, dataOffset)
|
||||
n := uintptr(t.BucketSize) - dataOffset
|
||||
memclrHasPointers(ptr, n)
|
||||
}
|
||||
}
|
||||
|
||||
if oldbucket == h.nevacuate {
|
||||
advanceEvacuationMark(h, t, newbit)
|
||||
}
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// 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 runtime
|
||||
|
||||
import (
|
472
src/runtime/map_fast64_swiss.go
Normal file
472
src/runtime/map_fast64_swiss.go
Normal file
@ -0,0 +1,472 @@
|
||||
// Copyright 2018 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 runtime
|
||||
|
||||
import (
|
||||
"internal/abi"
|
||||
"internal/goarch"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func mapaccess1_fast64(t *maptype, h *hmap, key uint64) unsafe.Pointer {
|
||||
if raceenabled && h != nil {
|
||||
callerpc := getcallerpc()
|
||||
racereadpc(unsafe.Pointer(h), callerpc, abi.FuncPCABIInternal(mapaccess1_fast64))
|
||||
}
|
||||
if h == nil || h.count == 0 {
|
||||
return unsafe.Pointer(&zeroVal[0])
|
||||
}
|
||||
if h.flags&hashWriting != 0 {
|
||||
fatal("concurrent map read and map write")
|
||||
}
|
||||
var b *bmap
|
||||
if h.B == 0 {
|
||||
// One-bucket table. No need to hash.
|
||||
b = (*bmap)(h.buckets)
|
||||
} else {
|
||||
hash := t.Hasher(noescape(unsafe.Pointer(&key)), uintptr(h.hash0))
|
||||
m := bucketMask(h.B)
|
||||
b = (*bmap)(add(h.buckets, (hash&m)*uintptr(t.BucketSize)))
|
||||
if c := h.oldbuckets; c != nil {
|
||||
if !h.sameSizeGrow() {
|
||||
// There used to be half as many buckets; mask down one more power of two.
|
||||
m >>= 1
|
||||
}
|
||||
oldb := (*bmap)(add(c, (hash&m)*uintptr(t.BucketSize)))
|
||||
if !evacuated(oldb) {
|
||||
b = oldb
|
||||
}
|
||||
}
|
||||
}
|
||||
for ; b != nil; b = b.overflow(t) {
|
||||
for i, k := uintptr(0), b.keys(); i < abi.MapBucketCount; i, k = i+1, add(k, 8) {
|
||||
if *(*uint64)(k) == key && !isEmpty(b.tophash[i]) {
|
||||
return add(unsafe.Pointer(b), dataOffset+abi.MapBucketCount*8+i*uintptr(t.ValueSize))
|
||||
}
|
||||
}
|
||||
}
|
||||
return unsafe.Pointer(&zeroVal[0])
|
||||
}
|
||||
|
||||
func mapaccess2_fast64(t *maptype, h *hmap, key uint64) (unsafe.Pointer, bool) {
|
||||
if raceenabled && h != nil {
|
||||
callerpc := getcallerpc()
|
||||
racereadpc(unsafe.Pointer(h), callerpc, abi.FuncPCABIInternal(mapaccess2_fast64))
|
||||
}
|
||||
if h == nil || h.count == 0 {
|
||||
return unsafe.Pointer(&zeroVal[0]), false
|
||||
}
|
||||
if h.flags&hashWriting != 0 {
|
||||
fatal("concurrent map read and map write")
|
||||
}
|
||||
var b *bmap
|
||||
if h.B == 0 {
|
||||
// One-bucket table. No need to hash.
|
||||
b = (*bmap)(h.buckets)
|
||||
} else {
|
||||
hash := t.Hasher(noescape(unsafe.Pointer(&key)), uintptr(h.hash0))
|
||||
m := bucketMask(h.B)
|
||||
b = (*bmap)(add(h.buckets, (hash&m)*uintptr(t.BucketSize)))
|
||||
if c := h.oldbuckets; c != nil {
|
||||
if !h.sameSizeGrow() {
|
||||
// There used to be half as many buckets; mask down one more power of two.
|
||||
m >>= 1
|
||||
}
|
||||
oldb := (*bmap)(add(c, (hash&m)*uintptr(t.BucketSize)))
|
||||
if !evacuated(oldb) {
|
||||
b = oldb
|
||||
}
|
||||
}
|
||||
}
|
||||
for ; b != nil; b = b.overflow(t) {
|
||||
for i, k := uintptr(0), b.keys(); i < abi.MapBucketCount; i, k = i+1, add(k, 8) {
|
||||
if *(*uint64)(k) == key && !isEmpty(b.tophash[i]) {
|
||||
return add(unsafe.Pointer(b), dataOffset+abi.MapBucketCount*8+i*uintptr(t.ValueSize)), true
|
||||
}
|
||||
}
|
||||
}
|
||||
return unsafe.Pointer(&zeroVal[0]), false
|
||||
}
|
||||
|
||||
func mapassign_fast64(t *maptype, h *hmap, key uint64) unsafe.Pointer {
|
||||
if h == nil {
|
||||
panic(plainError("assignment to entry in nil map"))
|
||||
}
|
||||
if raceenabled {
|
||||
callerpc := getcallerpc()
|
||||
racewritepc(unsafe.Pointer(h), callerpc, abi.FuncPCABIInternal(mapassign_fast64))
|
||||
}
|
||||
if h.flags&hashWriting != 0 {
|
||||
fatal("concurrent map writes")
|
||||
}
|
||||
hash := t.Hasher(noescape(unsafe.Pointer(&key)), uintptr(h.hash0))
|
||||
|
||||
// Set hashWriting after calling t.hasher for consistency with mapassign.
|
||||
h.flags ^= hashWriting
|
||||
|
||||
if h.buckets == nil {
|
||||
h.buckets = newobject(t.Bucket) // newarray(t.bucket, 1)
|
||||
}
|
||||
|
||||
again:
|
||||
bucket := hash & bucketMask(h.B)
|
||||
if h.growing() {
|
||||
growWork_fast64(t, h, bucket)
|
||||
}
|
||||
b := (*bmap)(add(h.buckets, bucket*uintptr(t.BucketSize)))
|
||||
|
||||
var insertb *bmap
|
||||
var inserti uintptr
|
||||
var insertk unsafe.Pointer
|
||||
|
||||
bucketloop:
|
||||
for {
|
||||
for i := uintptr(0); i < abi.MapBucketCount; i++ {
|
||||
if isEmpty(b.tophash[i]) {
|
||||
if insertb == nil {
|
||||
insertb = b
|
||||
inserti = i
|
||||
}
|
||||
if b.tophash[i] == emptyRest {
|
||||
break bucketloop
|
||||
}
|
||||
continue
|
||||
}
|
||||
k := *((*uint64)(add(unsafe.Pointer(b), dataOffset+i*8)))
|
||||
if k != key {
|
||||
continue
|
||||
}
|
||||
insertb = b
|
||||
inserti = i
|
||||
goto done
|
||||
}
|
||||
ovf := b.overflow(t)
|
||||
if ovf == nil {
|
||||
break
|
||||
}
|
||||
b = ovf
|
||||
}
|
||||
|
||||
// Did not find mapping for key. Allocate new cell & add entry.
|
||||
|
||||
// If we hit the max load factor or we have too many overflow buckets,
|
||||
// and we're not already in the middle of growing, start growing.
|
||||
if !h.growing() && (overLoadFactor(h.count+1, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) {
|
||||
hashGrow(t, h)
|
||||
goto again // Growing the table invalidates everything, so try again
|
||||
}
|
||||
|
||||
if insertb == nil {
|
||||
// The current bucket and all the overflow buckets connected to it are full, allocate a new one.
|
||||
insertb = h.newoverflow(t, b)
|
||||
inserti = 0 // not necessary, but avoids needlessly spilling inserti
|
||||
}
|
||||
insertb.tophash[inserti&(abi.MapBucketCount-1)] = tophash(hash) // mask inserti to avoid bounds checks
|
||||
|
||||
insertk = add(unsafe.Pointer(insertb), dataOffset+inserti*8)
|
||||
// store new key at insert position
|
||||
*(*uint64)(insertk) = key
|
||||
|
||||
h.count++
|
||||
|
||||
done:
|
||||
elem := add(unsafe.Pointer(insertb), dataOffset+abi.MapBucketCount*8+inserti*uintptr(t.ValueSize))
|
||||
if h.flags&hashWriting == 0 {
|
||||
fatal("concurrent map writes")
|
||||
}
|
||||
h.flags &^= hashWriting
|
||||
return elem
|
||||
}
|
||||
|
||||
func mapassign_fast64ptr(t *maptype, h *hmap, key unsafe.Pointer) unsafe.Pointer {
|
||||
if h == nil {
|
||||
panic(plainError("assignment to entry in nil map"))
|
||||
}
|
||||
if raceenabled {
|
||||
callerpc := getcallerpc()
|
||||
racewritepc(unsafe.Pointer(h), callerpc, abi.FuncPCABIInternal(mapassign_fast64))
|
||||
}
|
||||
if h.flags&hashWriting != 0 {
|
||||
fatal("concurrent map writes")
|
||||
}
|
||||
hash := t.Hasher(noescape(unsafe.Pointer(&key)), uintptr(h.hash0))
|
||||
|
||||
// Set hashWriting after calling t.hasher for consistency with mapassign.
|
||||
h.flags ^= hashWriting
|
||||
|
||||
if h.buckets == nil {
|
||||
h.buckets = newobject(t.Bucket) // newarray(t.bucket, 1)
|
||||
}
|
||||
|
||||
again:
|
||||
bucket := hash & bucketMask(h.B)
|
||||
if h.growing() {
|
||||
growWork_fast64(t, h, bucket)
|
||||
}
|
||||
b := (*bmap)(add(h.buckets, bucket*uintptr(t.BucketSize)))
|
||||
|
||||
var insertb *bmap
|
||||
var inserti uintptr
|
||||
var insertk unsafe.Pointer
|
||||
|
||||
bucketloop:
|
||||
for {
|
||||
for i := uintptr(0); i < abi.MapBucketCount; i++ {
|
||||
if isEmpty(b.tophash[i]) {
|
||||
if insertb == nil {
|
||||
insertb = b
|
||||
inserti = i
|
||||
}
|
||||
if b.tophash[i] == emptyRest {
|
||||
break bucketloop
|
||||
}
|
||||
continue
|
||||
}
|
||||
k := *((*unsafe.Pointer)(add(unsafe.Pointer(b), dataOffset+i*8)))
|
||||
if k != key {
|
||||
continue
|
||||
}
|
||||
insertb = b
|
||||
inserti = i
|
||||
goto done
|
||||
}
|
||||
ovf := b.overflow(t)
|
||||
if ovf == nil {
|
||||
break
|
||||
}
|
||||
b = ovf
|
||||
}
|
||||
|
||||
// Did not find mapping for key. Allocate new cell & add entry.
|
||||
|
||||
// If we hit the max load factor or we have too many overflow buckets,
|
||||
// and we're not already in the middle of growing, start growing.
|
||||
if !h.growing() && (overLoadFactor(h.count+1, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) {
|
||||
hashGrow(t, h)
|
||||
goto again // Growing the table invalidates everything, so try again
|
||||
}
|
||||
|
||||
if insertb == nil {
|
||||
// The current bucket and all the overflow buckets connected to it are full, allocate a new one.
|
||||
insertb = h.newoverflow(t, b)
|
||||
inserti = 0 // not necessary, but avoids needlessly spilling inserti
|
||||
}
|
||||
insertb.tophash[inserti&(abi.MapBucketCount-1)] = tophash(hash) // mask inserti to avoid bounds checks
|
||||
|
||||
insertk = add(unsafe.Pointer(insertb), dataOffset+inserti*8)
|
||||
// store new key at insert position
|
||||
*(*unsafe.Pointer)(insertk) = key
|
||||
|
||||
h.count++
|
||||
|
||||
done:
|
||||
elem := add(unsafe.Pointer(insertb), dataOffset+abi.MapBucketCount*8+inserti*uintptr(t.ValueSize))
|
||||
if h.flags&hashWriting == 0 {
|
||||
fatal("concurrent map writes")
|
||||
}
|
||||
h.flags &^= hashWriting
|
||||
return elem
|
||||
}
|
||||
|
||||
func mapdelete_fast64(t *maptype, h *hmap, key uint64) {
|
||||
if raceenabled && h != nil {
|
||||
callerpc := getcallerpc()
|
||||
racewritepc(unsafe.Pointer(h), callerpc, abi.FuncPCABIInternal(mapdelete_fast64))
|
||||
}
|
||||
if h == nil || h.count == 0 {
|
||||
return
|
||||
}
|
||||
if h.flags&hashWriting != 0 {
|
||||
fatal("concurrent map writes")
|
||||
}
|
||||
|
||||
hash := t.Hasher(noescape(unsafe.Pointer(&key)), uintptr(h.hash0))
|
||||
|
||||
// Set hashWriting after calling t.hasher for consistency with mapdelete
|
||||
h.flags ^= hashWriting
|
||||
|
||||
bucket := hash & bucketMask(h.B)
|
||||
if h.growing() {
|
||||
growWork_fast64(t, h, bucket)
|
||||
}
|
||||
b := (*bmap)(add(h.buckets, bucket*uintptr(t.BucketSize)))
|
||||
bOrig := b
|
||||
search:
|
||||
for ; b != nil; b = b.overflow(t) {
|
||||
for i, k := uintptr(0), b.keys(); i < abi.MapBucketCount; i, k = i+1, add(k, 8) {
|
||||
if key != *(*uint64)(k) || isEmpty(b.tophash[i]) {
|
||||
continue
|
||||
}
|
||||
// Only clear key if there are pointers in it.
|
||||
if t.Key.Pointers() {
|
||||
if goarch.PtrSize == 8 {
|
||||
*(*unsafe.Pointer)(k) = nil
|
||||
} else {
|
||||
// There are three ways to squeeze at one or more 32 bit pointers into 64 bits.
|
||||
// Just call memclrHasPointers instead of trying to handle all cases here.
|
||||
memclrHasPointers(k, 8)
|
||||
}
|
||||
}
|
||||
e := add(unsafe.Pointer(b), dataOffset+abi.MapBucketCount*8+i*uintptr(t.ValueSize))
|
||||
if t.Elem.Pointers() {
|
||||
memclrHasPointers(e, t.Elem.Size_)
|
||||
} else {
|
||||
memclrNoHeapPointers(e, t.Elem.Size_)
|
||||
}
|
||||
b.tophash[i] = emptyOne
|
||||
// If the bucket now ends in a bunch of emptyOne states,
|
||||
// change those to emptyRest states.
|
||||
if i == abi.MapBucketCount-1 {
|
||||
if b.overflow(t) != nil && b.overflow(t).tophash[0] != emptyRest {
|
||||
goto notLast
|
||||
}
|
||||
} else {
|
||||
if b.tophash[i+1] != emptyRest {
|
||||
goto notLast
|
||||
}
|
||||
}
|
||||
for {
|
||||
b.tophash[i] = emptyRest
|
||||
if i == 0 {
|
||||
if b == bOrig {
|
||||
break // beginning of initial bucket, we're done.
|
||||
}
|
||||
// Find previous bucket, continue at its last entry.
|
||||
c := b
|
||||
for b = bOrig; b.overflow(t) != c; b = b.overflow(t) {
|
||||
}
|
||||
i = abi.MapBucketCount - 1
|
||||
} else {
|
||||
i--
|
||||
}
|
||||
if b.tophash[i] != emptyOne {
|
||||
break
|
||||
}
|
||||
}
|
||||
notLast:
|
||||
h.count--
|
||||
// Reset the hash seed to make it more difficult for attackers to
|
||||
// repeatedly trigger hash collisions. See issue 25237.
|
||||
if h.count == 0 {
|
||||
h.hash0 = uint32(rand())
|
||||
}
|
||||
break search
|
||||
}
|
||||
}
|
||||
|
||||
if h.flags&hashWriting == 0 {
|
||||
fatal("concurrent map writes")
|
||||
}
|
||||
h.flags &^= hashWriting
|
||||
}
|
||||
|
||||
func growWork_fast64(t *maptype, h *hmap, bucket uintptr) {
|
||||
// make sure we evacuate the oldbucket corresponding
|
||||
// to the bucket we're about to use
|
||||
evacuate_fast64(t, h, bucket&h.oldbucketmask())
|
||||
|
||||
// evacuate one more oldbucket to make progress on growing
|
||||
if h.growing() {
|
||||
evacuate_fast64(t, h, h.nevacuate)
|
||||
}
|
||||
}
|
||||
|
||||
func evacuate_fast64(t *maptype, h *hmap, oldbucket uintptr) {
|
||||
b := (*bmap)(add(h.oldbuckets, oldbucket*uintptr(t.BucketSize)))
|
||||
newbit := h.noldbuckets()
|
||||
if !evacuated(b) {
|
||||
// TODO: reuse overflow buckets instead of using new ones, if there
|
||||
// is no iterator using the old buckets. (If !oldIterator.)
|
||||
|
||||
// xy contains the x and y (low and high) evacuation destinations.
|
||||
var xy [2]evacDst
|
||||
x := &xy[0]
|
||||
x.b = (*bmap)(add(h.buckets, oldbucket*uintptr(t.BucketSize)))
|
||||
x.k = add(unsafe.Pointer(x.b), dataOffset)
|
||||
x.e = add(x.k, abi.MapBucketCount*8)
|
||||
|
||||
if !h.sameSizeGrow() {
|
||||
// Only calculate y pointers if we're growing bigger.
|
||||
// Otherwise GC can see bad pointers.
|
||||
y := &xy[1]
|
||||
y.b = (*bmap)(add(h.buckets, (oldbucket+newbit)*uintptr(t.BucketSize)))
|
||||
y.k = add(unsafe.Pointer(y.b), dataOffset)
|
||||
y.e = add(y.k, abi.MapBucketCount*8)
|
||||
}
|
||||
|
||||
for ; b != nil; b = b.overflow(t) {
|
||||
k := add(unsafe.Pointer(b), dataOffset)
|
||||
e := add(k, abi.MapBucketCount*8)
|
||||
for i := 0; i < abi.MapBucketCount; i, k, e = i+1, add(k, 8), add(e, uintptr(t.ValueSize)) {
|
||||
top := b.tophash[i]
|
||||
if isEmpty(top) {
|
||||
b.tophash[i] = evacuatedEmpty
|
||||
continue
|
||||
}
|
||||
if top < minTopHash {
|
||||
throw("bad map state")
|
||||
}
|
||||
var useY uint8
|
||||
if !h.sameSizeGrow() {
|
||||
// Compute hash to make our evacuation decision (whether we need
|
||||
// to send this key/elem to bucket x or bucket y).
|
||||
hash := t.Hasher(k, uintptr(h.hash0))
|
||||
if hash&newbit != 0 {
|
||||
useY = 1
|
||||
}
|
||||
}
|
||||
|
||||
b.tophash[i] = evacuatedX + useY // evacuatedX + 1 == evacuatedY, enforced in makemap
|
||||
dst := &xy[useY] // evacuation destination
|
||||
|
||||
if dst.i == abi.MapBucketCount {
|
||||
dst.b = h.newoverflow(t, dst.b)
|
||||
dst.i = 0
|
||||
dst.k = add(unsafe.Pointer(dst.b), dataOffset)
|
||||
dst.e = add(dst.k, abi.MapBucketCount*8)
|
||||
}
|
||||
dst.b.tophash[dst.i&(abi.MapBucketCount-1)] = top // mask dst.i as an optimization, to avoid a bounds check
|
||||
|
||||
// Copy key.
|
||||
if t.Key.Pointers() && writeBarrier.enabled {
|
||||
if goarch.PtrSize == 8 {
|
||||
// Write with a write barrier.
|
||||
*(*unsafe.Pointer)(dst.k) = *(*unsafe.Pointer)(k)
|
||||
} else {
|
||||
// There are three ways to squeeze at least one 32 bit pointer into 64 bits.
|
||||
// Give up and call typedmemmove.
|
||||
typedmemmove(t.Key, dst.k, k)
|
||||
}
|
||||
} else {
|
||||
*(*uint64)(dst.k) = *(*uint64)(k)
|
||||
}
|
||||
|
||||
typedmemmove(t.Elem, dst.e, e)
|
||||
dst.i++
|
||||
// These updates might push these pointers past the end of the
|
||||
// key or elem arrays. That's ok, as we have the overflow pointer
|
||||
// at the end of the bucket to protect against pointing past the
|
||||
// end of the bucket.
|
||||
dst.k = add(dst.k, 8)
|
||||
dst.e = add(dst.e, uintptr(t.ValueSize))
|
||||
}
|
||||
}
|
||||
// Unlink the overflow buckets & clear key/elem to help GC.
|
||||
if h.flags&oldIterator == 0 && t.Bucket.Pointers() {
|
||||
b := add(h.oldbuckets, oldbucket*uintptr(t.BucketSize))
|
||||
// Preserve b.tophash because the evacuation
|
||||
// state is maintained there.
|
||||
ptr := add(b, dataOffset)
|
||||
n := uintptr(t.BucketSize) - dataOffset
|
||||
memclrHasPointers(ptr, n)
|
||||
}
|
||||
}
|
||||
|
||||
if oldbucket == h.nevacuate {
|
||||
advanceEvacuationMark(h, t, newbit)
|
||||
}
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// 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 runtime
|
||||
|
||||
import (
|
487
src/runtime/map_faststr_swiss.go
Normal file
487
src/runtime/map_faststr_swiss.go
Normal file
@ -0,0 +1,487 @@
|
||||
// Copyright 2018 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 runtime
|
||||
|
||||
import (
|
||||
"internal/abi"
|
||||
"internal/goarch"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func mapaccess1_faststr(t *maptype, h *hmap, ky string) unsafe.Pointer {
|
||||
if raceenabled && h != nil {
|
||||
callerpc := getcallerpc()
|
||||
racereadpc(unsafe.Pointer(h), callerpc, abi.FuncPCABIInternal(mapaccess1_faststr))
|
||||
}
|
||||
if h == nil || h.count == 0 {
|
||||
return unsafe.Pointer(&zeroVal[0])
|
||||
}
|
||||
if h.flags&hashWriting != 0 {
|
||||
fatal("concurrent map read and map write")
|
||||
}
|
||||
key := stringStructOf(&ky)
|
||||
if h.B == 0 {
|
||||
// One-bucket table.
|
||||
b := (*bmap)(h.buckets)
|
||||
if key.len < 32 {
|
||||
// short key, doing lots of comparisons is ok
|
||||
for i, kptr := uintptr(0), b.keys(); i < abi.MapBucketCount; i, kptr = i+1, add(kptr, 2*goarch.PtrSize) {
|
||||
k := (*stringStruct)(kptr)
|
||||
if k.len != key.len || isEmpty(b.tophash[i]) {
|
||||
if b.tophash[i] == emptyRest {
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
if k.str == key.str || memequal(k.str, key.str, uintptr(key.len)) {
|
||||
return add(unsafe.Pointer(b), dataOffset+abi.MapBucketCount*2*goarch.PtrSize+i*uintptr(t.ValueSize))
|
||||
}
|
||||
}
|
||||
return unsafe.Pointer(&zeroVal[0])
|
||||
}
|
||||
// long key, try not to do more comparisons than necessary
|
||||
keymaybe := uintptr(abi.MapBucketCount)
|
||||
for i, kptr := uintptr(0), b.keys(); i < abi.MapBucketCount; i, kptr = i+1, add(kptr, 2*goarch.PtrSize) {
|
||||
k := (*stringStruct)(kptr)
|
||||
if k.len != key.len || isEmpty(b.tophash[i]) {
|
||||
if b.tophash[i] == emptyRest {
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
if k.str == key.str {
|
||||
return add(unsafe.Pointer(b), dataOffset+abi.MapBucketCount*2*goarch.PtrSize+i*uintptr(t.ValueSize))
|
||||
}
|
||||
// check first 4 bytes
|
||||
if *((*[4]byte)(key.str)) != *((*[4]byte)(k.str)) {
|
||||
continue
|
||||
}
|
||||
// check last 4 bytes
|
||||
if *((*[4]byte)(add(key.str, uintptr(key.len)-4))) != *((*[4]byte)(add(k.str, uintptr(key.len)-4))) {
|
||||
continue
|
||||
}
|
||||
if keymaybe != abi.MapBucketCount {
|
||||
// Two keys are potential matches. Use hash to distinguish them.
|
||||
goto dohash
|
||||
}
|
||||
keymaybe = i
|
||||
}
|
||||
if keymaybe != abi.MapBucketCount {
|
||||
k := (*stringStruct)(add(unsafe.Pointer(b), dataOffset+keymaybe*2*goarch.PtrSize))
|
||||
if memequal(k.str, key.str, uintptr(key.len)) {
|
||||
return add(unsafe.Pointer(b), dataOffset+abi.MapBucketCount*2*goarch.PtrSize+keymaybe*uintptr(t.ValueSize))
|
||||
}
|
||||
}
|
||||
return unsafe.Pointer(&zeroVal[0])
|
||||
}
|
||||
dohash:
|
||||
hash := t.Hasher(noescape(unsafe.Pointer(&ky)), uintptr(h.hash0))
|
||||
m := bucketMask(h.B)
|
||||
b := (*bmap)(add(h.buckets, (hash&m)*uintptr(t.BucketSize)))
|
||||
if c := h.oldbuckets; c != nil {
|
||||
if !h.sameSizeGrow() {
|
||||
// There used to be half as many buckets; mask down one more power of two.
|
||||
m >>= 1
|
||||
}
|
||||
oldb := (*bmap)(add(c, (hash&m)*uintptr(t.BucketSize)))
|
||||
if !evacuated(oldb) {
|
||||
b = oldb
|
||||
}
|
||||
}
|
||||
top := tophash(hash)
|
||||
for ; b != nil; b = b.overflow(t) {
|
||||
for i, kptr := uintptr(0), b.keys(); i < abi.MapBucketCount; i, kptr = i+1, add(kptr, 2*goarch.PtrSize) {
|
||||
k := (*stringStruct)(kptr)
|
||||
if k.len != key.len || b.tophash[i] != top {
|
||||
continue
|
||||
}
|
||||
if k.str == key.str || memequal(k.str, key.str, uintptr(key.len)) {
|
||||
return add(unsafe.Pointer(b), dataOffset+abi.MapBucketCount*2*goarch.PtrSize+i*uintptr(t.ValueSize))
|
||||
}
|
||||
}
|
||||
}
|
||||
return unsafe.Pointer(&zeroVal[0])
|
||||
}
|
||||
|
||||
func mapaccess2_faststr(t *maptype, h *hmap, ky string) (unsafe.Pointer, bool) {
|
||||
if raceenabled && h != nil {
|
||||
callerpc := getcallerpc()
|
||||
racereadpc(unsafe.Pointer(h), callerpc, abi.FuncPCABIInternal(mapaccess2_faststr))
|
||||
}
|
||||
if h == nil || h.count == 0 {
|
||||
return unsafe.Pointer(&zeroVal[0]), false
|
||||
}
|
||||
if h.flags&hashWriting != 0 {
|
||||
fatal("concurrent map read and map write")
|
||||
}
|
||||
key := stringStructOf(&ky)
|
||||
if h.B == 0 {
|
||||
// One-bucket table.
|
||||
b := (*bmap)(h.buckets)
|
||||
if key.len < 32 {
|
||||
// short key, doing lots of comparisons is ok
|
||||
for i, kptr := uintptr(0), b.keys(); i < abi.MapBucketCount; i, kptr = i+1, add(kptr, 2*goarch.PtrSize) {
|
||||
k := (*stringStruct)(kptr)
|
||||
if k.len != key.len || isEmpty(b.tophash[i]) {
|
||||
if b.tophash[i] == emptyRest {
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
if k.str == key.str || memequal(k.str, key.str, uintptr(key.len)) {
|
||||
return add(unsafe.Pointer(b), dataOffset+abi.MapBucketCount*2*goarch.PtrSize+i*uintptr(t.ValueSize)), true
|
||||
}
|
||||
}
|
||||
return unsafe.Pointer(&zeroVal[0]), false
|
||||
}
|
||||
// long key, try not to do more comparisons than necessary
|
||||
keymaybe := uintptr(abi.MapBucketCount)
|
||||
for i, kptr := uintptr(0), b.keys(); i < abi.MapBucketCount; i, kptr = i+1, add(kptr, 2*goarch.PtrSize) {
|
||||
k := (*stringStruct)(kptr)
|
||||
if k.len != key.len || isEmpty(b.tophash[i]) {
|
||||
if b.tophash[i] == emptyRest {
|
||||
break
|
||||
}
|
||||
continue
|
||||
}
|
||||
if k.str == key.str {
|
||||
return add(unsafe.Pointer(b), dataOffset+abi.MapBucketCount*2*goarch.PtrSize+i*uintptr(t.ValueSize)), true
|
||||
}
|
||||
// check first 4 bytes
|
||||
if *((*[4]byte)(key.str)) != *((*[4]byte)(k.str)) {
|
||||
continue
|
||||
}
|
||||
// check last 4 bytes
|
||||
if *((*[4]byte)(add(key.str, uintptr(key.len)-4))) != *((*[4]byte)(add(k.str, uintptr(key.len)-4))) {
|
||||
continue
|
||||
}
|
||||
if keymaybe != abi.MapBucketCount {
|
||||
// Two keys are potential matches. Use hash to distinguish them.
|
||||
goto dohash
|
||||
}
|
||||
keymaybe = i
|
||||
}
|
||||
if keymaybe != abi.MapBucketCount {
|
||||
k := (*stringStruct)(add(unsafe.Pointer(b), dataOffset+keymaybe*2*goarch.PtrSize))
|
||||
if memequal(k.str, key.str, uintptr(key.len)) {
|
||||
return add(unsafe.Pointer(b), dataOffset+abi.MapBucketCount*2*goarch.PtrSize+keymaybe*uintptr(t.ValueSize)), true
|
||||
}
|
||||
}
|
||||
return unsafe.Pointer(&zeroVal[0]), false
|
||||
}
|
||||
dohash:
|
||||
hash := t.Hasher(noescape(unsafe.Pointer(&ky)), uintptr(h.hash0))
|
||||
m := bucketMask(h.B)
|
||||
b := (*bmap)(add(h.buckets, (hash&m)*uintptr(t.BucketSize)))
|
||||
if c := h.oldbuckets; c != nil {
|
||||
if !h.sameSizeGrow() {
|
||||
// There used to be half as many buckets; mask down one more power of two.
|
||||
m >>= 1
|
||||
}
|
||||
oldb := (*bmap)(add(c, (hash&m)*uintptr(t.BucketSize)))
|
||||
if !evacuated(oldb) {
|
||||
b = oldb
|
||||
}
|
||||
}
|
||||
top := tophash(hash)
|
||||
for ; b != nil; b = b.overflow(t) {
|
||||
for i, kptr := uintptr(0), b.keys(); i < abi.MapBucketCount; i, kptr = i+1, add(kptr, 2*goarch.PtrSize) {
|
||||
k := (*stringStruct)(kptr)
|
||||
if k.len != key.len || b.tophash[i] != top {
|
||||
continue
|
||||
}
|
||||
if k.str == key.str || memequal(k.str, key.str, uintptr(key.len)) {
|
||||
return add(unsafe.Pointer(b), dataOffset+abi.MapBucketCount*2*goarch.PtrSize+i*uintptr(t.ValueSize)), true
|
||||
}
|
||||
}
|
||||
}
|
||||
return unsafe.Pointer(&zeroVal[0]), false
|
||||
}
|
||||
|
||||
func mapassign_faststr(t *maptype, h *hmap, s string) unsafe.Pointer {
|
||||
if h == nil {
|
||||
panic(plainError("assignment to entry in nil map"))
|
||||
}
|
||||
if raceenabled {
|
||||
callerpc := getcallerpc()
|
||||
racewritepc(unsafe.Pointer(h), callerpc, abi.FuncPCABIInternal(mapassign_faststr))
|
||||
}
|
||||
if h.flags&hashWriting != 0 {
|
||||
fatal("concurrent map writes")
|
||||
}
|
||||
key := stringStructOf(&s)
|
||||
hash := t.Hasher(noescape(unsafe.Pointer(&s)), uintptr(h.hash0))
|
||||
|
||||
// Set hashWriting after calling t.hasher for consistency with mapassign.
|
||||
h.flags ^= hashWriting
|
||||
|
||||
if h.buckets == nil {
|
||||
h.buckets = newobject(t.Bucket) // newarray(t.bucket, 1)
|
||||
}
|
||||
|
||||
again:
|
||||
bucket := hash & bucketMask(h.B)
|
||||
if h.growing() {
|
||||
growWork_faststr(t, h, bucket)
|
||||
}
|
||||
b := (*bmap)(add(h.buckets, bucket*uintptr(t.BucketSize)))
|
||||
top := tophash(hash)
|
||||
|
||||
var insertb *bmap
|
||||
var inserti uintptr
|
||||
var insertk unsafe.Pointer
|
||||
|
||||
bucketloop:
|
||||
for {
|
||||
for i := uintptr(0); i < abi.MapBucketCount; i++ {
|
||||
if b.tophash[i] != top {
|
||||
if isEmpty(b.tophash[i]) && insertb == nil {
|
||||
insertb = b
|
||||
inserti = i
|
||||
}
|
||||
if b.tophash[i] == emptyRest {
|
||||
break bucketloop
|
||||
}
|
||||
continue
|
||||
}
|
||||
k := (*stringStruct)(add(unsafe.Pointer(b), dataOffset+i*2*goarch.PtrSize))
|
||||
if k.len != key.len {
|
||||
continue
|
||||
}
|
||||
if k.str != key.str && !memequal(k.str, key.str, uintptr(key.len)) {
|
||||
continue
|
||||
}
|
||||
// already have a mapping for key. Update it.
|
||||
inserti = i
|
||||
insertb = b
|
||||
// Overwrite existing key, so it can be garbage collected.
|
||||
// The size is already guaranteed to be set correctly.
|
||||
k.str = key.str
|
||||
goto done
|
||||
}
|
||||
ovf := b.overflow(t)
|
||||
if ovf == nil {
|
||||
break
|
||||
}
|
||||
b = ovf
|
||||
}
|
||||
|
||||
// Did not find mapping for key. Allocate new cell & add entry.
|
||||
|
||||
// If we hit the max load factor or we have too many overflow buckets,
|
||||
// and we're not already in the middle of growing, start growing.
|
||||
if !h.growing() && (overLoadFactor(h.count+1, h.B) || tooManyOverflowBuckets(h.noverflow, h.B)) {
|
||||
hashGrow(t, h)
|
||||
goto again // Growing the table invalidates everything, so try again
|
||||
}
|
||||
|
||||
if insertb == nil {
|
||||
// The current bucket and all the overflow buckets connected to it are full, allocate a new one.
|
||||
insertb = h.newoverflow(t, b)
|
||||
inserti = 0 // not necessary, but avoids needlessly spilling inserti
|
||||
}
|
||||
insertb.tophash[inserti&(abi.MapBucketCount-1)] = top // mask inserti to avoid bounds checks
|
||||
|
||||
insertk = add(unsafe.Pointer(insertb), dataOffset+inserti*2*goarch.PtrSize)
|
||||
// store new key at insert position
|
||||
*((*stringStruct)(insertk)) = *key
|
||||
h.count++
|
||||
|
||||
done:
|
||||
elem := add(unsafe.Pointer(insertb), dataOffset+abi.MapBucketCount*2*goarch.PtrSize+inserti*uintptr(t.ValueSize))
|
||||
if h.flags&hashWriting == 0 {
|
||||
fatal("concurrent map writes")
|
||||
}
|
||||
h.flags &^= hashWriting
|
||||
return elem
|
||||
}
|
||||
|
||||
func mapdelete_faststr(t *maptype, h *hmap, ky string) {
|
||||
if raceenabled && h != nil {
|
||||
callerpc := getcallerpc()
|
||||
racewritepc(unsafe.Pointer(h), callerpc, abi.FuncPCABIInternal(mapdelete_faststr))
|
||||
}
|
||||
if h == nil || h.count == 0 {
|
||||
return
|
||||
}
|
||||
if h.flags&hashWriting != 0 {
|
||||
fatal("concurrent map writes")
|
||||
}
|
||||
|
||||
key := stringStructOf(&ky)
|
||||
hash := t.Hasher(noescape(unsafe.Pointer(&ky)), uintptr(h.hash0))
|
||||
|
||||
// Set hashWriting after calling t.hasher for consistency with mapdelete
|
||||
h.flags ^= hashWriting
|
||||
|
||||
bucket := hash & bucketMask(h.B)
|
||||
if h.growing() {
|
||||
growWork_faststr(t, h, bucket)
|
||||
}
|
||||
b := (*bmap)(add(h.buckets, bucket*uintptr(t.BucketSize)))
|
||||
bOrig := b
|
||||
top := tophash(hash)
|
||||
search:
|
||||
for ; b != nil; b = b.overflow(t) {
|
||||
for i, kptr := uintptr(0), b.keys(); i < abi.MapBucketCount; i, kptr = i+1, add(kptr, 2*goarch.PtrSize) {
|
||||
k := (*stringStruct)(kptr)
|
||||
if k.len != key.len || b.tophash[i] != top {
|
||||
continue
|
||||
}
|
||||
if k.str != key.str && !memequal(k.str, key.str, uintptr(key.len)) {
|
||||
continue
|
||||
}
|
||||
// Clear key's pointer.
|
||||
k.str = nil
|
||||
e := add(unsafe.Pointer(b), dataOffset+abi.MapBucketCount*2*goarch.PtrSize+i*uintptr(t.ValueSize))
|
||||
if t.Elem.Pointers() {
|
||||
memclrHasPointers(e, t.Elem.Size_)
|
||||
} else {
|
||||
memclrNoHeapPointers(e, t.Elem.Size_)
|
||||
}
|
||||
b.tophash[i] = emptyOne
|
||||
// If the bucket now ends in a bunch of emptyOne states,
|
||||
// change those to emptyRest states.
|
||||
if i == abi.MapBucketCount-1 {
|
||||
if b.overflow(t) != nil && b.overflow(t).tophash[0] != emptyRest {
|
||||
goto notLast
|
||||
}
|
||||
} else {
|
||||
if b.tophash[i+1] != emptyRest {
|
||||
goto notLast
|
||||
}
|
||||
}
|
||||
for {
|
||||
b.tophash[i] = emptyRest
|
||||
if i == 0 {
|
||||
if b == bOrig {
|
||||
break // beginning of initial bucket, we're done.
|
||||
}
|
||||
// Find previous bucket, continue at its last entry.
|
||||
c := b
|
||||
for b = bOrig; b.overflow(t) != c; b = b.overflow(t) {
|
||||
}
|
||||
i = abi.MapBucketCount - 1
|
||||
} else {
|
||||
i--
|
||||
}
|
||||
if b.tophash[i] != emptyOne {
|
||||
break
|
||||
}
|
||||
}
|
||||
notLast:
|
||||
h.count--
|
||||
// Reset the hash seed to make it more difficult for attackers to
|
||||
// repeatedly trigger hash collisions. See issue 25237.
|
||||
if h.count == 0 {
|
||||
h.hash0 = uint32(rand())
|
||||
}
|
||||
break search
|
||||
}
|
||||
}
|
||||
|
||||
if h.flags&hashWriting == 0 {
|
||||
fatal("concurrent map writes")
|
||||
}
|
||||
h.flags &^= hashWriting
|
||||
}
|
||||
|
||||
func growWork_faststr(t *maptype, h *hmap, bucket uintptr) {
|
||||
// make sure we evacuate the oldbucket corresponding
|
||||
// to the bucket we're about to use
|
||||
evacuate_faststr(t, h, bucket&h.oldbucketmask())
|
||||
|
||||
// evacuate one more oldbucket to make progress on growing
|
||||
if h.growing() {
|
||||
evacuate_faststr(t, h, h.nevacuate)
|
||||
}
|
||||
}
|
||||
|
||||
func evacuate_faststr(t *maptype, h *hmap, oldbucket uintptr) {
|
||||
b := (*bmap)(add(h.oldbuckets, oldbucket*uintptr(t.BucketSize)))
|
||||
newbit := h.noldbuckets()
|
||||
if !evacuated(b) {
|
||||
// TODO: reuse overflow buckets instead of using new ones, if there
|
||||
// is no iterator using the old buckets. (If !oldIterator.)
|
||||
|
||||
// xy contains the x and y (low and high) evacuation destinations.
|
||||
var xy [2]evacDst
|
||||
x := &xy[0]
|
||||
x.b = (*bmap)(add(h.buckets, oldbucket*uintptr(t.BucketSize)))
|
||||
x.k = add(unsafe.Pointer(x.b), dataOffset)
|
||||
x.e = add(x.k, abi.MapBucketCount*2*goarch.PtrSize)
|
||||
|
||||
if !h.sameSizeGrow() {
|
||||
// Only calculate y pointers if we're growing bigger.
|
||||
// Otherwise GC can see bad pointers.
|
||||
y := &xy[1]
|
||||
y.b = (*bmap)(add(h.buckets, (oldbucket+newbit)*uintptr(t.BucketSize)))
|
||||
y.k = add(unsafe.Pointer(y.b), dataOffset)
|
||||
y.e = add(y.k, abi.MapBucketCount*2*goarch.PtrSize)
|
||||
}
|
||||
|
||||
for ; b != nil; b = b.overflow(t) {
|
||||
k := add(unsafe.Pointer(b), dataOffset)
|
||||
e := add(k, abi.MapBucketCount*2*goarch.PtrSize)
|
||||
for i := 0; i < abi.MapBucketCount; i, k, e = i+1, add(k, 2*goarch.PtrSize), add(e, uintptr(t.ValueSize)) {
|
||||
top := b.tophash[i]
|
||||
if isEmpty(top) {
|
||||
b.tophash[i] = evacuatedEmpty
|
||||
continue
|
||||
}
|
||||
if top < minTopHash {
|
||||
throw("bad map state")
|
||||
}
|
||||
var useY uint8
|
||||
if !h.sameSizeGrow() {
|
||||
// Compute hash to make our evacuation decision (whether we need
|
||||
// to send this key/elem to bucket x or bucket y).
|
||||
hash := t.Hasher(k, uintptr(h.hash0))
|
||||
if hash&newbit != 0 {
|
||||
useY = 1
|
||||
}
|
||||
}
|
||||
|
||||
b.tophash[i] = evacuatedX + useY // evacuatedX + 1 == evacuatedY, enforced in makemap
|
||||
dst := &xy[useY] // evacuation destination
|
||||
|
||||
if dst.i == abi.MapBucketCount {
|
||||
dst.b = h.newoverflow(t, dst.b)
|
||||
dst.i = 0
|
||||
dst.k = add(unsafe.Pointer(dst.b), dataOffset)
|
||||
dst.e = add(dst.k, abi.MapBucketCount*2*goarch.PtrSize)
|
||||
}
|
||||
dst.b.tophash[dst.i&(abi.MapBucketCount-1)] = top // mask dst.i as an optimization, to avoid a bounds check
|
||||
|
||||
// Copy key.
|
||||
*(*string)(dst.k) = *(*string)(k)
|
||||
|
||||
typedmemmove(t.Elem, dst.e, e)
|
||||
dst.i++
|
||||
// These updates might push these pointers past the end of the
|
||||
// key or elem arrays. That's ok, as we have the overflow pointer
|
||||
// at the end of the bucket to protect against pointing past the
|
||||
// end of the bucket.
|
||||
dst.k = add(dst.k, 2*goarch.PtrSize)
|
||||
dst.e = add(dst.e, uintptr(t.ValueSize))
|
||||
}
|
||||
}
|
||||
// Unlink the overflow buckets & clear key/elem to help GC.
|
||||
if h.flags&oldIterator == 0 && t.Bucket.Pointers() {
|
||||
b := add(h.oldbuckets, oldbucket*uintptr(t.BucketSize))
|
||||
// Preserve b.tophash because the evacuation
|
||||
// state is maintained there.
|
||||
ptr := add(b, dataOffset)
|
||||
n := uintptr(t.BucketSize) - dataOffset
|
||||
memclrHasPointers(ptr, n)
|
||||
}
|
||||
}
|
||||
|
||||
if oldbucket == h.nevacuate {
|
||||
advanceEvacuationMark(h, t, newbit)
|
||||
}
|
||||
}
|
@ -2,6 +2,8 @@
|
||||
// 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 runtime
|
||||
|
||||
// This file contains the implementation of Go's map type.
|
1724
src/runtime/map_swiss.go
Normal file
1724
src/runtime/map_swiss.go
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user