diff --git a/src/runtime/hashmap.go b/src/runtime/hashmap.go index 9390cd19..2cccebcd 100644 --- a/src/runtime/hashmap.go +++ b/src/runtime/hashmap.go @@ -89,13 +89,20 @@ func hashmapLen(m *hashmap) 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, keyEqual func(x, y unsafe.Pointer, n uintptr) bool) { + tophash := hashmapTopHash(hash) + + if m.buckets == nil { + // No bucket was allocated yet, do so now. + m.buckets = unsafe.Pointer(hashmapInsertIntoNewBucket(m, key, value, tophash)) + return + } + numBuckets := uintptr(1) << m.bucketBits bucketNumber := (uintptr(hash) & (numBuckets - 1)) bucketSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8 bucketAddr := uintptr(m.buckets) + bucketSize*bucketNumber bucket := (*hashmapBucket)(unsafe.Pointer(bucketAddr)) - - tophash := hashmapTopHash(hash) + var lastBucket *hashmapBucket // See whether the key already exists somewhere. var emptySlotKey unsafe.Pointer @@ -104,9 +111,9 @@ func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint3 for bucket != nil { for i := uintptr(0); i < 8; i++ { slotKeyOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*uintptr(i) - slotKey := unsafe.Pointer(bucketAddr + slotKeyOffset) + slotKey := unsafe.Pointer(uintptr(unsafe.Pointer(bucket)) + slotKeyOffset) slotValueOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*uintptr(i) - slotValue := unsafe.Pointer(bucketAddr + slotValueOffset) + slotValue := unsafe.Pointer(uintptr(unsafe.Pointer(bucket)) + slotValueOffset) if bucket.tophash[i] == 0 && emptySlotKey == nil { // Found an empty slot, store it for if we couldn't find an // existing slot. @@ -115,7 +122,7 @@ func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint3 emptySlotTophash = &bucket.tophash[i] } if bucket.tophash[i] == tophash { - // Could be an existing value that's the same. + // Could be an existing key that's the same. if keyEqual(key, slotKey, uintptr(m.keySize)) { // found same key, replace it memcpy(slotValue, value, uintptr(m.valueSize)) @@ -123,16 +130,37 @@ func hashmapSet(m *hashmap, key unsafe.Pointer, value unsafe.Pointer, hash uint3 } } } + lastBucket = bucket bucket = bucket.next } - if emptySlotKey != nil { - m.count++ - memcpy(emptySlotKey, key, uintptr(m.keySize)) - memcpy(emptySlotValue, value, uintptr(m.valueSize)) - *emptySlotTophash = tophash + if emptySlotKey == nil { + // Add a new bucket to the bucket chain. + // TODO: rebalance if necessary to avoid O(n) insert and lookup time. + lastBucket.next = (*hashmapBucket)(hashmapInsertIntoNewBucket(m, key, value, tophash)) return } - panic("todo: hashmap: grow bucket") + m.count++ + memcpy(emptySlotKey, key, uintptr(m.keySize)) + memcpy(emptySlotValue, value, uintptr(m.valueSize)) + *emptySlotTophash = tophash +} + +// hashmapInsertIntoNewBucket creates a new bucket, inserts the given key and +// value into the bucket, and returns a pointer to this bucket. +func hashmapInsertIntoNewBucket(m *hashmap, key, value unsafe.Pointer, tophash uint8) *hashmapBucket { + bucketBufSize := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + uintptr(m.valueSize)*8 + bucketBuf := alloc(bucketBufSize) + // Insert into the first slot, which is empty as it has just been allocated. + slotKeyOffset := unsafe.Sizeof(hashmapBucket{}) + slotKey := unsafe.Pointer(uintptr(bucketBuf) + slotKeyOffset) + slotValueOffset := unsafe.Sizeof(hashmapBucket{}) + uintptr(m.keySize)*8 + slotValue := unsafe.Pointer(uintptr(bucketBuf) + slotValueOffset) + m.count++ + memcpy(slotKey, key, uintptr(m.keySize)) + memcpy(slotValue, value, uintptr(m.valueSize)) + bucket := (*hashmapBucket)(bucketBuf) + bucket.tophash[0] = tophash + return bucket } // Get the value of a specified key, or zero the value if not found. diff --git a/testdata/map.go b/testdata/map.go index db673efa..c780c013 100644 --- a/testdata/map.go +++ b/testdata/map.go @@ -50,15 +50,15 @@ func main() { // test preallocated map squares := make(map[int]int, 200) - for i := 0; i < 100; i++ { - squares[i] = i*i - for j := 0; j <= i; j++ { - if v := squares[j]; v != j*j { - println("unexpected value read back from squares map:", j, v) - } - } - } + testBigMap(squares, 100) println("tested preallocated map") + + // test growing maps + squares = make(map[int]int, 0) + testBigMap(squares, 10) + squares = make(map[int]int, 20) + testBigMap(squares, 40) + println("tested growing of a map") } func readMap(m map[string]int, key string) { @@ -73,3 +73,22 @@ func lookup(m map[string]int, key string) { value, ok := m[key] println("lookup with comma-ok:", key, value, ok) } + +func testBigMap(squares map[int]int, n int) { + for i := 0; i < n; i++ { + if len(squares) != i { + println("unexpected length:", len(squares), "at i =", i) + } + squares[i] = i*i + for j := 0; j <= i; j++ { + if v, ok := squares[j]; !ok || v != j*j { + if !ok { + println("key not found in squares map:", j) + } else { + println("unexpected value read back from squares map:", j, v) + } + return + } + } + } +} diff --git a/testdata/map.txt b/testdata/map.txt index ad525521..66636d11 100644 --- a/testdata/map.txt +++ b/testdata/map.txt @@ -55,3 +55,4 @@ true false 0 4321 5555 tested preallocated map +tested growing of a map