Revert "src/runtime: first darft of map growth code"

This reverts commit 7e05efa274.
Этот коммит содержится в:
Ron Evans 2022-04-04 23:45:16 +02:00
родитель dcd8ee63a8
коммит 1dcdd5f2d2
2 изменённых файлов: 28 добавлений и 156 удалений

Просмотреть файл

@ -10,13 +10,6 @@ import (
"tinygo.org/x/go-llvm"
)
// constants for hashmap algorithms; must match src/runtime/hashmap.go
const (
hashmapAlgorithmBinary = iota
hashmapAlgorithmString
hashmapAlgorithmInterface
)
// createMakeMap creates a new map object (runtime.hashmap) by allocating and
// initializing an appropriately sized object.
func (b *builder) createMakeMap(expr *ssa.MakeMap) (llvm.Value, error) {
@ -24,27 +17,22 @@ func (b *builder) createMakeMap(expr *ssa.MakeMap) (llvm.Value, error) {
keyType := mapType.Key().Underlying()
llvmValueType := b.getLLVMType(mapType.Elem().Underlying())
var llvmKeyType llvm.Type
var alg uint64 // must match values in src/runtime/hashmap.go
if t, ok := keyType.(*types.Basic); ok && t.Info()&types.IsString != 0 {
// String keys.
llvmKeyType = b.getLLVMType(keyType)
alg = hashmapAlgorithmString
} else if hashmapIsBinaryKey(keyType) {
// Trivially comparable keys.
llvmKeyType = b.getLLVMType(keyType)
alg = hashmapAlgorithmBinary
} else {
// All other keys. Implemented as map[interface{}]valueType for ease of
// implementation.
llvmKeyType = b.getLLVMRuntimeType("_interface")
alg = hashmapAlgorithmInterface
}
keySize := b.targetData.TypeAllocSize(llvmKeyType)
valueSize := b.targetData.TypeAllocSize(llvmValueType)
llvmKeySize := llvm.ConstInt(b.ctx.Int8Type(), keySize, false)
llvmValueSize := llvm.ConstInt(b.ctx.Int8Type(), valueSize, false)
sizeHint := llvm.ConstInt(b.uintptrType, 8, false)
algEnum := llvm.ConstInt(b.ctx.Int8Type(), alg, false)
if expr.Reserve != nil {
sizeHint = b.getValue(expr.Reserve)
var err error
@ -53,7 +41,7 @@ func (b *builder) createMakeMap(expr *ssa.MakeMap) (llvm.Value, error) {
return llvm.Value{}, err
}
}
hashmap := b.createRuntimeCall("hashmapMake", []llvm.Value{llvmKeySize, llvmValueSize, sizeHint, algEnum}, "")
hashmap := b.createRuntimeCall("hashmapMake", []llvm.Value{llvmKeySize, llvmValueSize, sizeHint}, "")
return hashmap, nil
}

Просмотреть файл

@ -12,23 +12,14 @@ import (
// The underlying hashmap structure for Go.
type hashmap struct {
next *hashmap // hashmap after evacuate (for iterators)
buckets unsafe.Pointer // pointer to array of buckets
count uintptr
keySize uint8 // maybe this can store the key type as well? E.g. keysize == 5 means string?
valueSize uint8
bucketBits uint8
keyEqual func(x, y unsafe.Pointer, n uintptr) bool
keyHash func(key unsafe.Pointer, size uintptr) uint32
}
type hashmapAlgorithm uint8
const (
hashmapAlgorithmBinary hashmapAlgorithm = iota
hashmapAlgorithmString
hashmapAlgorithmInterface
)
// A hashmap bucket. A bucket is a container of 8 key/value pairs: first the
// following two entries, then the 8 keys, then the 8 values. This somewhat odd
// ordering is to make sure the keys and values are well aligned when one of
@ -41,8 +32,6 @@ type hashmapBucket struct {
}
type hashmapIterator struct {
buckets unsafe.Pointer
numBuckets uintptr
bucketNumber uintptr
bucket *hashmapBucket
bucketIndex uint8
@ -59,7 +48,7 @@ func hashmapTopHash(hash uint32) uint8 {
}
// Create a new hashmap with the given keySize and valueSize.
func hashmapMake(keySize, valueSize uint8, sizeHint uintptr, alg uint8) *hashmap {
func hashmapMake(keySize, valueSize uint8, sizeHint uintptr) *hashmap {
numBuckets := sizeHint / 8
bucketBits := uint8(0)
for numBuckets != 0 {
@ -68,58 +57,14 @@ func hashmapMake(keySize, valueSize uint8, sizeHint uintptr, alg uint8) *hashmap
}
bucketBufSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(keySize)*8 + uintptr(valueSize)*8
buckets := alloc(bucketBufSize*(1<<bucketBits), nil)
keyHash := hashmapKeyHashAlg(hashmapAlgorithm(alg))
keyEqual := hashmapKeyEqualAlg(hashmapAlgorithm(alg))
return &hashmap{
buckets: buckets,
keySize: keySize,
valueSize: valueSize,
bucketBits: bucketBits,
keyEqual: keyEqual,
keyHash: keyHash,
}
}
func hashmapKeyEqualAlg(alg hashmapAlgorithm) func(x, y unsafe.Pointer, n uintptr) bool {
switch alg {
case hashmapAlgorithmBinary:
return memequal
case hashmapAlgorithmString:
return hashmapStringEqual
case hashmapAlgorithmInterface:
return hashmapInterfaceEqual
default:
panic("unknown hashmap equal algorithm")
}
}
func hashmapKeyHashAlg(alg hashmapAlgorithm) func(key unsafe.Pointer, n uintptr) uint32 {
switch alg {
case hashmapAlgorithmBinary:
return hash32
case hashmapAlgorithmString:
return hashmapStringPtrHash
case hashmapAlgorithmInterface:
return hashmapInterfacePtrHash
default:
panic("unknown hashmap hash algorithm")
}
}
func hashmapShouldGrow(m *hashmap) bool {
if m.bucketBits == 32 {
// can't grow any more
return false
}
// "maximum" number of elements is 0.75 * buckets * elements per bucket
max := uintptr((3 * ((1 << m.bucketBits) * 8)) / 4)
return m.count > max
}
// Return the number of entries in this hashmap, called from the len builtin.
// A nil hashmap is defined as having length 0.
//go:inline
@ -138,7 +83,7 @@ func hashmapLenUnsafePointer(p unsafe.Pointer) int {
// Set a specified key to a given value. Grow the map if necessary.
//go:nobounds
func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint32) {
func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint32, keyEqual func(x, y unsafe.Pointer, n uintptr) bool) {
tophash := hashmapTopHash(hash)
if m.buckets == nil {
@ -147,10 +92,6 @@ func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint3
return
}
if hashmapShouldGrow(m) {
hashmapGrow(m)
}
numBuckets := uintptr(1) << m.bucketBits
bucketNumber := (uintptr(hash) & (numBuckets - 1))
bucketSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8
@ -177,7 +118,7 @@ func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint3
}
if bucket.tophash[i] == tophash {
// Could be an existing key that's the same.
if m.keyEqual(key, slotKey, uintptr(m.keySize)) {
if keyEqual(key, slotKey, uintptr(m.keySize)) {
// found same key, replace it
memcpy(slotValue, value, uintptr(m.valueSize))
return
@ -217,35 +158,9 @@ func hashmapInsertIntoNewBucket(m *hashmap, key, value unsafe.Pointer, tophash u
return bucket
}
func hashmapGrow(m *hashmap) {
// clone map as empty
n := *m
n.count = 0
// allocate our new buckets twice as big
n.bucketBits = m.bucketBits + 1
numBuckets := uintptr(1) << n.bucketBits
bucketBufSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8
n.buckets = alloc(bucketBufSize*numBuckets, nil)
// use a hashmap iterator to go through the old map
var it hashmapIterator
var key = alloc(uintptr(m.keySize), nil)
var value = alloc(uintptr(m.valueSize), nil)
for hashmapNext(m, &it, key, value) {
h := m.keyHash(key, uintptr(m.keySize))
hashmapSet(&n, key, value, h)
}
*m = n
}
// Get the value of a specified key, or zero the value if not found.
//go:nobounds
func hashmapGet(m *hashmap, key, value unsafe.Pointer, valueSize uintptr, hash uint32) bool {
func hashmapGet(m *hashmap, key, value unsafe.Pointer, valueSize uintptr, hash uint32, keyEqual func(x, y unsafe.Pointer, n uintptr) bool) bool {
if m == nil {
// Getting a value out of a nil map is valid. From the spec:
// > if the map is nil or does not contain such an entry, a[x] is the
@ -274,7 +189,7 @@ func hashmapGet(m *hashmap, key, value unsafe.Pointer, valueSize uintptr, hash u
slotValue := unsafe.Pointer(uintptr(unsafe.Pointer(bucket)) + slotValueOffset)
if bucket.tophash[i] == tophash {
// This could be the key we're looking for.
if m.keyEqual(key, slotKey, uintptr(m.keySize)) {
if keyEqual(key, slotKey, uintptr(m.keySize)) {
// Found the key, copy it.
memcpy(value, slotValue, uintptr(m.valueSize))
return true
@ -292,7 +207,7 @@ func hashmapGet(m *hashmap, key, value unsafe.Pointer, valueSize uintptr, hash u
// Delete a given key from the map. No-op when the key does not exist in the
// map.
//go:nobounds
func hashmapDelete(m *hashmap, key unsafe.Pointer, hash uint32) {
func hashmapDelete(m *hashmap, key unsafe.Pointer, hash uint32, keyEqual func(x, y unsafe.Pointer, n uintptr) bool) {
if m == nil {
// The delete builtin is defined even when the map is nil. From the spec:
// > If the map m is nil or the element m[k] does not exist, delete is a
@ -318,7 +233,7 @@ func hashmapDelete(m *hashmap, key unsafe.Pointer, hash uint32) {
slotKey := unsafe.Pointer(uintptr(unsafe.Pointer(bucket)) + slotKeyOffset)
if bucket.tophash[i] == tophash {
// This could be the key we're looking for.
if m.keyEqual(key, slotKey, uintptr(m.keySize)) {
if keyEqual(key, slotKey, uintptr(m.keySize)) {
// Found the key, delete it.
bucket.tophash[i] = 0
m.count--
@ -334,16 +249,13 @@ func hashmapDelete(m *hashmap, key unsafe.Pointer, hash uint32) {
//go:nobounds
func hashmapNext(m *hashmap, it *hashmapIterator, key, value unsafe.Pointer) bool {
if m == nil {
// From the spec: If the map is nil, the number of iterations is 0.
// Iterating over a nil slice appears to be allowed by the Go spec:
// https://groups.google.com/g/golang-nuts/c/gVgVLQU1FFE?pli=1
// https://play.golang.org/p/S8jxAMytKDB
return false
}
if it.buckets == nil {
// initialize iterator
it.buckets = m.buckets
it.numBuckets = uintptr(1) << m.bucketBits
}
numBuckets := uintptr(1) << m.bucketBits
for {
if it.bucketIndex >= 8 {
// end of bucket, move to the next in the chain
@ -351,12 +263,12 @@ func hashmapNext(m *hashmap, it *hashmapIterator, key, value unsafe.Pointer) boo
it.bucket = it.bucket.next
}
if it.bucket == nil {
if it.bucketNumber >= it.numBuckets {
if it.bucketNumber >= numBuckets {
// went through all buckets
return false
}
bucketSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8
bucketAddr := uintptr(it.buckets) + bucketSize*it.bucketNumber
bucketAddr := uintptr(m.buckets) + bucketSize*it.bucketNumber
it.bucket = (*hashmapBucket)(unsafe.Pointer(bucketAddr))
it.bucketNumber++ // next bucket
}
@ -369,29 +281,11 @@ func hashmapNext(m *hashmap, it *hashmapIterator, key, value unsafe.Pointer) boo
bucketAddr := uintptr(unsafe.Pointer(it.bucket))
slotKeyOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*uintptr(it.bucketIndex)
slotKey := unsafe.Pointer(bucketAddr + slotKeyOffset)
slotValueOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*uintptr(it.bucketIndex)
slotValue := unsafe.Pointer(bucketAddr + slotValueOffset)
memcpy(key, slotKey, uintptr(m.keySize))
if it.buckets == m.buckets {
// Our view of the buckets is the same as the parent map.
// Just copy the value we have
slotValueOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*uintptr(it.bucketIndex)
slotValue := unsafe.Pointer(bucketAddr + slotValueOffset)
memcpy(value, slotValue, uintptr(m.valueSize))
it.bucketIndex++
} else {
it.bucketIndex++
// Our view of the buckets doesn't match the parent map.
// Look up the key in the new buckets and return that value if it exists
hash := m.keyHash(key, uintptr(m.keySize))
ok := hashmapGet(m, key, value, uintptr(m.valueSize), hash)
if !ok {
// doesn't exist in parent map; try next key
continue
}
// All good.
}
memcpy(value, slotValue, uintptr(m.valueSize))
it.bucketIndex++
return true
}
@ -402,7 +296,7 @@ func hashmapNext(m *hashmap, it *hashmapIterator, key, value unsafe.Pointer) boo
func hashmapBinarySet(m *hashmap, key, value unsafe.Pointer) {
// TODO: detect nil map here and throw a better panic message?
hash := hash32(key, uintptr(m.keySize))
hashmapSet(m, key, value, hash)
hashmapSet(m, key, value, hash, memequal)
}
func hashmapBinaryGet(m *hashmap, key, value unsafe.Pointer, valueSize uintptr) bool {
@ -411,7 +305,7 @@ func hashmapBinaryGet(m *hashmap, key, value unsafe.Pointer, valueSize uintptr)
return false
}
hash := hash32(key, uintptr(m.keySize))
return hashmapGet(m, key, value, valueSize, hash)
return hashmapGet(m, key, value, valueSize, hash, memequal)
}
func hashmapBinaryDelete(m *hashmap, key unsafe.Pointer) {
@ -419,7 +313,7 @@ func hashmapBinaryDelete(m *hashmap, key unsafe.Pointer) {
return
}
hash := hash32(key, uintptr(m.keySize))
hashmapDelete(m, key, hash)
hashmapDelete(m, key, hash, memequal)
}
// Hashmap with string keys (a common case).
@ -433,24 +327,19 @@ func hashmapStringHash(s string) uint32 {
return hash32(unsafe.Pointer(_s.ptr), uintptr(_s.length))
}
func hashmapStringPtrHash(sptr unsafe.Pointer, size uintptr) uint32 {
_s := *(*_string)(sptr)
return hash32(unsafe.Pointer(_s.ptr), uintptr(_s.length))
}
func hashmapStringSet(m *hashmap, key string, value unsafe.Pointer) {
hash := hashmapStringHash(key)
hashmapSet(m, unsafe.Pointer(&key), value, hash)
hashmapSet(m, unsafe.Pointer(&key), value, hash, hashmapStringEqual)
}
func hashmapStringGet(m *hashmap, key string, value unsafe.Pointer, valueSize uintptr) bool {
hash := hashmapStringHash(key)
return hashmapGet(m, unsafe.Pointer(&key), value, valueSize, hash)
return hashmapGet(m, unsafe.Pointer(&key), value, valueSize, hash, hashmapStringEqual)
}
func hashmapStringDelete(m *hashmap, key string) {
hash := hashmapStringHash(key)
hashmapDelete(m, unsafe.Pointer(&key), hash)
hashmapDelete(m, unsafe.Pointer(&key), hash, hashmapStringEqual)
}
// Hashmap with interface keys (for everything else).
@ -538,26 +427,21 @@ func hashmapInterfaceHash(itf interface{}) uint32 {
}
}
func hashmapInterfacePtrHash(iptr unsafe.Pointer, size uintptr) uint32 {
_i := *(*_interface)(iptr)
return hashmapInterfaceHash(_i)
}
func hashmapInterfaceEqual(x, y unsafe.Pointer, n uintptr) bool {
return *(*interface{})(x) == *(*interface{})(y)
}
func hashmapInterfaceSet(m *hashmap, key interface{}, value unsafe.Pointer) {
hash := hashmapInterfaceHash(key)
hashmapSet(m, unsafe.Pointer(&key), value, hash)
hashmapSet(m, unsafe.Pointer(&key), value, hash, hashmapInterfaceEqual)
}
func hashmapInterfaceGet(m *hashmap, key interface{}, value unsafe.Pointer, valueSize uintptr) bool {
hash := hashmapInterfaceHash(key)
return hashmapGet(m, unsafe.Pointer(&key), value, valueSize, hash)
return hashmapGet(m, unsafe.Pointer(&key), value, valueSize, hash, hashmapInterfaceEqual)
}
func hashmapInterfaceDelete(m *hashmap, key interface{}) {
hash := hashmapInterfaceHash(key)
hashmapDelete(m, unsafe.Pointer(&key), hash)
hashmapDelete(m, unsafe.Pointer(&key), hash, hashmapInterfaceEqual)
}