From 1dcdd5f2d2bc8e96b4ad2705dabd4f7c3533b00b Mon Sep 17 00:00:00 2001 From: Ron Evans Date: Mon, 4 Apr 2022 23:45:16 +0200 Subject: [PATCH] Revert "src/runtime: first darft of map growth code" This reverts commit 7e05efa274765463859820f8ad3b0bde27927bc9. --- compiler/map.go | 14 +--- src/runtime/hashmap.go | 170 +++++++---------------------------------- 2 files changed, 28 insertions(+), 156 deletions(-) diff --git a/compiler/map.go b/compiler/map.go index 57a7033a..62162c0c 100644 --- a/compiler/map.go +++ b/compiler/map.go @@ -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 } diff --git a/src/runtime/hashmap.go b/src/runtime/hashmap.go index c8c12419..c050dae9 100644 --- a/src/runtime/hashmap.go +++ b/src/runtime/hashmap.go @@ -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< 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) }