// Copyright 2009 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. #include "runtime.h" #include "arch_GOARCH.h" #include "malloc.h" #include "hashmap.h" #include "type.h" #include "race.h" // This file contains the implementation of Go's map type. // // The map is just a hash table. The data is arranged // into an array of buckets. Each bucket contains up to // 8 key/value pairs. The low-order bits of the hash are // used to select a bucket. Each bucket contains a few // high-order bits of each hash to distinguish the entries // within a single bucket. // // If more than 8 keys hash to a bucket, we chain on // extra buckets. // // When the hashtable grows, we allocate a new array // of buckets twice as big. Buckets are incrementally // copied from the old bucket array to the new bucket array. // // Map iterators walk through the array of buckets and // return the keys in walk order (bucket #, then overflow // chain order, then bucket index). To maintain iteration // semantics, we never move keys within their bucket (if // we did, keys might be returned 0 or 2 times). When // growing the table, iterators remain iterating through the // old table and must check the new table if the bucket // they are iterating through has been moved ("evacuated") // to the new table. // Maximum number of key/value pairs a bucket can hold. #define BUCKETSIZE 8 // Maximum average load of a bucket that triggers growth. #define LOAD 6.5 // Picking LOAD: too large and we have lots of overflow // buckets, too small and we waste a lot of space. I wrote // a simple program to check some stats for different loads: // (64-bit, 8 byte keys and values) // LOAD %overflow bytes/entry hitprobe missprobe // 4.00 2.13 20.77 3.00 4.00 // 4.50 4.05 17.30 3.25 4.50 // 5.00 6.85 14.77 3.50 5.00 // 5.50 10.55 12.94 3.75 5.50 // 6.00 15.27 11.67 4.00 6.00 // 6.50 20.90 10.79 4.25 6.50 // 7.00 27.14 10.15 4.50 7.00 // 7.50 34.03 9.73 4.75 7.50 // 8.00 41.10 9.40 5.00 8.00 // // %overflow = percentage of buckets which have an overflow bucket // bytes/entry = overhead bytes used per key/value pair // hitprobe = # of entries to check when looking up a present key // missprobe = # of entries to check when looking up an absent key // // Keep in mind this data is for maximally loaded tables, i.e. just // before the table grows. Typical tables will be somewhat less loaded. // Maximum key or value size to keep inline (instead of mallocing per element). // Must fit in a uint8. // Fast versions cannot handle big values - the cutoff size for // fast versions in ../../cmd/gc/walk.c must be at most this value. #define MAXKEYSIZE 128 #define MAXVALUESIZE 128 typedef struct Bucket Bucket; struct Bucket { uint8 tophash[BUCKETSIZE]; // top 8 bits of hash of each entry (0 = empty) Bucket *overflow; // overflow bucket, if any byte data[1]; // BUCKETSIZE keys followed by BUCKETSIZE values }; // NOTE: packing all the keys together and then all the values together makes the // code a bit more complicated than alternating key/value/key/value/... but it allows // us to eliminate padding which would be needed for, e.g., map[int64]int8. // Low-order bit of overflow field is used to mark a bucket as already evacuated // without destroying the overflow pointer. // Only buckets in oldbuckets will be marked as evacuated. // Evacuated bit will be set identically on the base bucket and any overflow buckets. #define evacuated(b) (((uintptr)(b)->overflow & 1) != 0) #define overflowptr(b) ((Bucket*)((uintptr)(b)->overflow & ~(uintptr)1)) // Initialize bucket to the empty state. This only works if BUCKETSIZE==8! #define clearbucket(b) { *(uint64*)((b)->tophash) = 0; (b)->overflow = nil; } struct Hmap { uintgo count; // # live cells == size of map. Must be first (used by len() builtin) uint8 B; // log_2 of # of buckets (can hold up to LOAD * 2^B items) uint8 flags; uint8 keysize; // key size in bytes uint8 valuesize; // value size in bytes uint16 bucketsize; // bucket size in bytes uintptr hash0; // hash seed byte *buckets; // array of 2^B Buckets byte *oldbuckets; // previous bucket array of half the size, non-nil only when growing uintptr nevacuate; // progress counter for evacuation (buckets less than this have been evacuated) }; // possible flags enum { IndirectKey = 1, // storing pointers to keys IndirectValue = 2, // storing pointers to values Iterator = 4, // there may be an iterator using buckets OldIterator = 8, // there may be an iterator using oldbuckets CanFreeBucket = 16, // ok to free buckets CanFreeKey = 32, // keys are indirect and ok to free keys }; // Macros for dereferencing indirect keys #define IK(h, p) (((h)->flags & IndirectKey) != 0 ? *(byte**)(p) : (p)) #define IV(h, p) (((h)->flags & IndirectValue) != 0 ? *(byte**)(p) : (p)) enum { docheck = 0, // check invariants before and after every op. Slow!!! debug = 0, // print every operation checkgc = 0 || docheck, // check interaction of mallocgc() with the garbage collector }; static void check(MapType *t, Hmap *h) { uintptr bucket, oldbucket; Bucket *b; uintptr i; uintptr hash; uintgo cnt; uint8 top; bool eq; byte *k, *v; cnt = 0; // check buckets for(bucket = 0; bucket < (uintptr)1 << h->B; bucket++) { if(h->oldbuckets != nil) { oldbucket = bucket & (((uintptr)1 << (h->B - 1)) - 1); b = (Bucket*)(h->oldbuckets + oldbucket * h->bucketsize); if(!evacuated(b)) continue; // b is still uninitialized } for(b = (Bucket*)(h->buckets + bucket * h->bucketsize); b != nil; b = b->overflow) { for(i = 0, k = b->data, v = k + h->keysize * BUCKETSIZE; i < BUCKETSIZE; i++, k += h->keysize, v += h->valuesize) { if(b->tophash[i] == 0) continue; cnt++; t->key->alg->equal(&eq, t->key->size, IK(h, k), IK(h, k)); if(!eq) continue; // NaN! hash = h->hash0; t->key->alg->hash(&hash, t->key->size, IK(h, k)); top = hash >> (8*sizeof(uintptr) - 8); if(top == 0) top = 1; if(top != b->tophash[i]) runtime·throw("bad hash"); } } } // check oldbuckets if(h->oldbuckets != nil) { for(oldbucket = 0; oldbucket < (uintptr)1 << (h->B - 1); oldbucket++) { b = (Bucket*)(h->oldbuckets + oldbucket * h->bucketsize); if(evacuated(b)) continue; if(oldbucket < h->nevacuate) runtime·throw("bucket became unevacuated"); for(; b != nil; b = overflowptr(b)) { for(i = 0, k = b->data, v = k + h->keysize * BUCKETSIZE; i < BUCKETSIZE; i++, k += h->keysize, v += h->valuesize) { if(b->tophash[i] == 0) continue; cnt++; t->key->alg->equal(&eq, t->key->size, IK(h, k), IK(h, k)); if(!eq) continue; // NaN! hash = h->hash0; t->key->alg->hash(&hash, t->key->size, IK(h, k)); top = hash >> (8*sizeof(uintptr) - 8); if(top == 0) top = 1; if(top != b->tophash[i]) runtime·throw("bad hash (old)"); } } } } if(cnt != h->count) { runtime·printf("%D %D\n", (uint64)cnt, (uint64)h->count); runtime·throw("entries missing"); } } static void hash_init(MapType *t, Hmap *h, uint32 hint) { uint8 B; byte *buckets; uintptr i; uintptr keysize, valuesize, bucketsize; uint8 flags; Bucket *b; flags = CanFreeBucket; // figure out how big we have to make everything keysize = t->key->size; if(keysize > MAXKEYSIZE) { flags |= IndirectKey | CanFreeKey; keysize = sizeof(byte*); } valuesize = t->elem->size; if(valuesize >= MAXVALUESIZE) { flags |= IndirectValue; valuesize = sizeof(byte*); } bucketsize = offsetof(Bucket, data[0]) + (keysize + valuesize) * BUCKETSIZE; // invariants we depend on. We should probably check these at compile time // somewhere, but for now we'll do it here. if(t->key->align > BUCKETSIZE) runtime·throw("key align too big"); if(t->elem->align > BUCKETSIZE) runtime·throw("value align too big"); if(t->key->size % t->key->align != 0) runtime·throw("key size not a multiple of key align"); if(t->elem->size % t->elem->align != 0) runtime·throw("value size not a multiple of value align"); if(BUCKETSIZE < 8) runtime·throw("bucketsize too small for proper alignment"); if(BUCKETSIZE != 8) runtime·throw("must redo clearbucket"); if(sizeof(void*) == 4 && t->key->align > 4) runtime·throw("need padding in bucket (key)"); if(sizeof(void*) == 4 && t->elem->align > 4) runtime·throw("need padding in bucket (value)"); // find size parameter which will hold the requested # of elements B = 0; while(hint > BUCKETSIZE && hint > LOAD * ((uintptr)1 << B)) B++; // allocate initial hash table // If hint is large zeroing this memory could take a while. if(checkgc) mstats.next_gc = mstats.heap_alloc; if(B == 0) { // done lazily later. buckets = nil; } else { buckets = runtime·mallocgc(bucketsize << B, 0, 1, 0); for(i = 0; i < (uintptr)1 << B; i++) { b = (Bucket*)(buckets + i * bucketsize); clearbucket(b); } } // initialize Hmap // Note: we save all these stores to the end so gciter doesn't see // a partially initialized map. h->count = 0; h->B = B; h->flags = flags; h->keysize = keysize; h->valuesize = valuesize; h->bucketsize = bucketsize; h->hash0 = runtime·fastrand1(); h->buckets = buckets; h->oldbuckets = nil; h->nevacuate = 0; if(docheck) check(t, h); } // Moves entries in oldbuckets[i] to buckets[i] and buckets[i+2^k]. // We leave the original bucket intact, except for the evacuated marks, so that // iterators can still iterate through the old buckets. static void evacuate(MapType *t, Hmap *h, uintptr oldbucket) { Bucket *b; Bucket *nextb; Bucket *x, *y; Bucket *newx, *newy; uintptr xi, yi; uintptr newbit; uintptr hash; uintptr i; byte *k, *v; byte *xk, *yk, *xv, *yv; byte *ob; b = (Bucket*)(h->oldbuckets + oldbucket * h->bucketsize); newbit = (uintptr)1 << (h->B - 1); if(!evacuated(b)) { // TODO: reuse overflow buckets instead of using new ones, if there // is no iterator using the old buckets. (If CanFreeBuckets and !OldIterator.) x = (Bucket*)(h->buckets + oldbucket * h->bucketsize); y = (Bucket*)(h->buckets + (oldbucket + newbit) * h->bucketsize); clearbucket(x); clearbucket(y); xi = 0; yi = 0; xk = x->data; yk = y->data; xv = xk + h->keysize * BUCKETSIZE; yv = yk + h->keysize * BUCKETSIZE; do { for(i = 0, k = b->data, v = k + h->keysize * BUCKETSIZE; i < BUCKETSIZE; i++, k += h->keysize, v += h->valuesize) { if(b->tophash[i] == 0) continue; hash = h->hash0; t->key->alg->hash(&hash, t->key->size, IK(h, k)); // NOTE: if key != key, then this hash could be (and probably will be) // entirely different from the old hash. We effectively only update // the B'th bit of the hash in this case. if((hash & newbit) == 0) { if(xi == BUCKETSIZE) { if(checkgc) mstats.next_gc = mstats.heap_alloc; newx = runtime·mallocgc(h->bucketsize, 0, 1, 0); clearbucket(newx); x->overflow = newx; x = newx; xi = 0; xk = x->data; xv = xk + h->keysize * BUCKETSIZE; } x->tophash[xi] = b->tophash[i]; if((h->flags & IndirectKey) != 0) { *(byte**)xk = *(byte**)k; // copy pointer } else { t->key->alg->copy(t->key->size, xk, k); // copy value } if((h->flags & IndirectValue) != 0) { *(byte**)xv = *(byte**)v; } else { t->elem->alg->copy(t->elem->size, xv, v); } xi++; xk += h->keysize; xv += h->valuesize; } else { if(yi == BUCKETSIZE) { if(checkgc) mstats.next_gc = mstats.heap_alloc; newy = runtime·mallocgc(h->bucketsize, 0, 1, 0); clearbucket(newy); y->overflow = newy; y = newy; yi = 0; yk = y->data; yv = yk + h->keysize * BUCKETSIZE; } y->tophash[yi] = b->tophash[i]; if((h->flags & IndirectKey) != 0) { *(byte**)yk = *(byte**)k; } else { t->key->alg->copy(t->key->size, yk, k); } if((h->flags & IndirectValue) != 0) { *(byte**)yv = *(byte**)v; } else { t->elem->alg->copy(t->elem->size, yv, v); } yi++; yk += h->keysize; yv += h->valuesize; } } // mark as evacuated so we don't do it again. // this also tells any iterators that this data isn't golden anymore. nextb = b->overflow; b->overflow = (Bucket*)((uintptr)nextb + 1); b = nextb; } while(b != nil); // Free old overflow buckets as much as we can. if((h->flags & OldIterator) == 0) { b = (Bucket*)(h->oldbuckets + oldbucket * h->bucketsize); if((h->flags & CanFreeBucket) != 0) { while((nextb = overflowptr(b)) != nil) { b->overflow = nextb->overflow; runtime·free(nextb); } } else { // can't explicitly free overflow buckets, but at least // we can unlink them. b->overflow = (Bucket*)1; } } } // advance evacuation mark if(oldbucket == h->nevacuate) { h->nevacuate = oldbucket + 1; if(oldbucket + 1 == newbit) { // newbit == # of oldbuckets // free main bucket array if((h->flags & (OldIterator | CanFreeBucket)) == CanFreeBucket) { ob = h->oldbuckets; h->oldbuckets = nil; runtime·free(ob); } else { h->oldbuckets = nil; } } } if(docheck) check(t, h); } static void grow_work(MapType *t, Hmap *h, uintptr bucket) { uintptr noldbuckets; noldbuckets = (uintptr)1 << (h->B - 1); // make sure we evacuate the oldbucket corresponding // to the bucket we're about to use evacuate(t, h, bucket & (noldbuckets - 1)); // evacuate one more oldbucket to make progress on growing if(h->oldbuckets != nil) evacuate(t, h, h->nevacuate); } static void hash_grow(MapType *t, Hmap *h) { byte *old_buckets; byte *new_buckets; uint8 flags; // allocate a bigger hash table if(h->oldbuckets != nil) runtime·throw("evacuation not done in time"); old_buckets = h->buckets; // NOTE: this could be a big malloc, but since we don't need zeroing it is probably fast. if(checkgc) mstats.next_gc = mstats.heap_alloc; new_buckets = runtime·mallocgc(h->bucketsize << (h->B + 1), 0, 1, 0); flags = (h->flags & ~(Iterator | OldIterator)); if((h->flags & Iterator) != 0) { flags |= OldIterator; // We can't free indirect keys any more, as // they are potentially aliased across buckets. flags &= ~CanFreeKey; } // commit the grow (atomic wrt gc) h->B++; h->flags = flags; h->oldbuckets = old_buckets; h->buckets = new_buckets; h->nevacuate = 0; // the actual copying of the hash table data is done incrementally // by grow_work() and evacuate(). if(docheck) check(t, h); } // returns ptr to value associated with key *keyp, or nil if none. // if it returns non-nil, updates *keyp to point to the currently stored key. static byte* hash_lookup(MapType *t, Hmap *h, byte **keyp) { void *key; uintptr hash; uintptr bucket; Bucket *b; uint8 top; uintptr i; bool eq; byte *k, *k2, *v; key = *keyp; if(docheck) check(t, h); if(h->count == 0) return nil; hash = h->hash0; t->key->alg->hash(&hash, t->key->size, key); bucket = hash & (((uintptr)1 << h->B) - 1); if(h->oldbuckets != nil) grow_work(t, h, bucket); b = (Bucket*)(h->buckets + bucket * h->bucketsize); top = hash >> (sizeof(uintptr)*8 - 8); if(top == 0) top = 1; do { for(i = 0, k = b->data, v = k + h->keysize * BUCKETSIZE; i < BUCKETSIZE; i++, k += h->keysize, v += h->valuesize) { if(b->tophash[i] == top) { k2 = IK(h, k); t->key->alg->equal(&eq, t->key->size, key, k2); if(eq) { *keyp = k2; return IV(h, v); } } } b = b->overflow; } while(b != nil); return nil; } // When an item is not found, fast versions return a pointer to this zeroed memory. static uint8 empty_value[MAXVALUESIZE]; // Specialized versions of mapaccess1 for specific types. // See ./hashmap_fast and ../../cmd/gc/walk.c. #define HASH_LOOKUP1 runtime·mapaccess1_fast32 #define HASH_LOOKUP2 runtime·mapaccess2_fast32 #define KEYTYPE uint32 #define HASHFUNC runtime·algarray[AMEM32].hash #define EQFUNC(x,y) ((x) == (y)) #define QUICKEQ(x) true #include "hashmap_fast.c" #undef HASH_LOOKUP1 #undef HASH_LOOKUP2 #undef KEYTYPE #undef HASHFUNC #undef EQFUNC #undef QUICKEQ #define HASH_LOOKUP1 runtime·mapaccess1_fast64 #define HASH_LOOKUP2 runtime·mapaccess2_fast64 #define KEYTYPE uint64 #define HASHFUNC runtime·algarray[AMEM64].hash #define EQFUNC(x,y) ((x) == (y)) #define QUICKEQ(x) true #include "hashmap_fast.c" #undef HASH_LOOKUP1 #undef HASH_LOOKUP2 #undef KEYTYPE #undef HASHFUNC #undef EQFUNC #undef QUICKEQ #define HASH_LOOKUP1 runtime·mapaccess1_faststr #define HASH_LOOKUP2 runtime·mapaccess2_faststr #define KEYTYPE String #define HASHFUNC runtime·algarray[ASTRING].hash #define EQFUNC(x,y) ((x).len == (y).len && ((x).str == (y).str || runtime·mcmp((x).str, (y).str, (x).len) == 0)) #define QUICKEQ(x) ((x).len < 32) #include "hashmap_fast.c" static void hash_insert(MapType *t, Hmap *h, void *key, void *value) { uintptr hash; uintptr bucket; uintptr i; bool eq; Bucket *b; Bucket *newb; uint8 *inserti; byte *insertk, *insertv; uint8 top; byte *k, *v; byte *kmem, *vmem; if(docheck) check(t, h); hash = h->hash0; t->key->alg->hash(&hash, t->key->size, key); if(h->buckets == nil) { h->buckets = runtime·mallocgc(h->bucketsize, 0, 1, 0); b = (Bucket*)(h->buckets); clearbucket(b); } again: bucket = hash & (((uintptr)1 << h->B) - 1); if(h->oldbuckets != nil) grow_work(t, h, bucket); b = (Bucket*)(h->buckets + bucket * h->bucketsize); top = hash >> (sizeof(uintptr)*8 - 8); if(top == 0) top = 1; inserti = 0; insertk = nil; insertv = nil; while(true) { for(i = 0, k = b->data, v = k + h->keysize * BUCKETSIZE; i < BUCKETSIZE; i++, k += h->keysize, v += h->valuesize) { if(b->tophash[i] != top) { if(b->tophash[i] == 0 && inserti == nil) { inserti = &b->tophash[i]; insertk = k; insertv = v; } continue; } t->key->alg->equal(&eq, t->key->size, key, IK(h, k)); if(!eq) continue; // already have a mapping for key. Update it. t->key->alg->copy(t->key->size, IK(h, k), key); // Need to update key for keys which are distinct but equal (e.g. +0.0 and -0.0) t->elem->alg->copy(t->elem->size, IV(h, v), value); if(docheck) check(t, h); return; } if(b->overflow == nil) break; b = b->overflow; } // did not find mapping for key. Allocate new cell & add entry. if(h->count >= LOAD * ((uintptr)1 << h->B) && h->count >= BUCKETSIZE) { hash_grow(t, h); goto again; // Growing the table invalidates everything, so try again } if(inserti == nil) { // all current buckets are full, allocate a new one. if(checkgc) mstats.next_gc = mstats.heap_alloc; newb = runtime·mallocgc(h->bucketsize, 0, 1, 0); clearbucket(newb); b->overflow = newb; inserti = newb->tophash; insertk = newb->data; insertv = insertk + h->keysize * BUCKETSIZE; } // store new key/value at insert position if((h->flags & IndirectKey) != 0) { if(checkgc) mstats.next_gc = mstats.heap_alloc; kmem = runtime·mallocgc(t->key->size, 0, 1, 0); *(byte**)insertk = kmem; insertk = kmem; } if((h->flags & IndirectValue) != 0) { if(checkgc) mstats.next_gc = mstats.heap_alloc; vmem = runtime·mallocgc(t->elem->size, 0, 1, 0); *(byte**)insertv = vmem; insertv = vmem; } t->key->alg->copy(t->key->size, insertk, key); t->elem->alg->copy(t->elem->size, insertv, value); *inserti = top; h->count++; if(docheck) check(t, h); } static void hash_remove(MapType *t, Hmap *h, void *key) { uintptr hash; uintptr bucket; Bucket *b; uint8 top; uintptr i; byte *k, *v; bool eq; if(docheck) check(t, h); if(h->count == 0) return; hash = h->hash0; t->key->alg->hash(&hash, t->key->size, key); bucket = hash & (((uintptr)1 << h->B) - 1); if(h->oldbuckets != nil) grow_work(t, h, bucket); b = (Bucket*)(h->buckets + bucket * h->bucketsize); top = hash >> (sizeof(uintptr)*8 - 8); if(top == 0) top = 1; do { for(i = 0, k = b->data, v = k + h->keysize * BUCKETSIZE; i < BUCKETSIZE; i++, k += h->keysize, v += h->valuesize) { if(b->tophash[i] != top) continue; t->key->alg->equal(&eq, t->key->size, key, IK(h, k)); if(!eq) continue; if((h->flags & CanFreeKey) != 0) { k = *(byte**)k; } if((h->flags & IndirectValue) != 0) { v = *(byte**)v; } b->tophash[i] = 0; h->count--; if((h->flags & CanFreeKey) != 0) { runtime·free(k); } if((h->flags & IndirectValue) != 0) { runtime·free(v); } // TODO: consolidate buckets if they are mostly empty // can only consolidate if there are no live iterators at this size. if(docheck) check(t, h); return; } b = b->overflow; } while(b != nil); } // TODO: shrink the map, the same way we grow it. // If you modify hash_iter, also change cmd/gc/range.c to indicate // the size of this structure. struct hash_iter { uint8* key; // Must be in first position. Write nil to indicate iteration end (see cmd/gc/range.c). uint8* value; MapType *t; Hmap *h; // end point for iteration uintptr endbucket; bool wrapped; // state of table at time iterator is initialized uint8 B; byte *buckets; // iter state uintptr bucket; struct Bucket *bptr; uintptr i; }; // iterator state: // bucket: the current bucket ID // b: the current Bucket in the chain // i: the next offset to check in the current bucket static void hash_iter_init(MapType *t, Hmap *h, struct hash_iter *it) { it->t = t; it->h = h; // grab snapshot of bucket state it->B = h->B; it->buckets = h->buckets; // iterator state it->bucket = it->endbucket = runtime·fastrand1() & (((uintptr)1 << h->B) - 1); it->wrapped = false; it->bptr = nil; // Remember we have an iterator at this level. h->flags |= Iterator; if(h->buckets == nil) { // Empty map. Force next hash_next to exit without // evalulating h->bucket. it->wrapped = true; } } // initializes it->key and it->value to the next key/value pair // in the iteration, or nil if we've reached the end. static void hash_next(struct hash_iter *it) { Hmap *h; MapType *t; uintptr bucket; Bucket *b; uintptr i; bool eq; byte *k, *v; byte *rk, *rv; h = it->h; t = it->t; bucket = it->bucket; b = it->bptr; i = it->i; next: if(b == nil) { if(bucket == it->endbucket && it->wrapped) { // end of iteration it->key = nil; it->value = nil; return; } if(h->oldbuckets != nil && it->B == h->B) { // Iterator was started in the middle of a grow, and the grow isn't done yet. // Make sure the bucket we're about to read is valid. grow_work(t, h, bucket); } b = (Bucket*)(it->buckets + bucket * h->bucketsize); bucket++; if(bucket == ((uintptr)1 << it->B)) { bucket = 0; it->wrapped = true; } i = 0; } k = b->data + h->keysize * i; v = b->data + h->keysize * BUCKETSIZE + h->valuesize * i; for(; i < BUCKETSIZE; i++, k += h->keysize, v += h->valuesize) { if(b->tophash[i] != 0) { if(!evacuated(b)) { // this is the golden data, we can return it. it->key = IK(h, k); it->value = IV(h, v); } else { // The hash table has grown since the iterator was started. // The golden data for this key is now somewhere else. t->key->alg->equal(&eq, t->key->size, IK(h, k), IK(h, k)); if(eq) { // Check the current hash table for the data. // This code handles the case where the key // has been deleted, updated, or deleted and reinserted. // NOTE: we need to regrab the key as it has potentially been // updated to an equal() but not identical key (e.g. +0.0 vs -0.0). rk = IK(h, k); rv = hash_lookup(t, it->h, &rk); if(rv == nil) continue; // key has been deleted it->key = rk; it->value = rv; } else { // if key!=key then the entry can't be deleted or // updated, so we can just return it. That's lucky for // us because when key!=key we can't look it up // successfully in the current table. it->key = IK(h, k); it->value = IV(h, v); } } it->bucket = bucket; it->bptr = b; it->i = i + 1; return; } } b = overflowptr(b); i = 0; goto next; } #define PHASE_BUCKETS 0 #define PHASE_OLD_BUCKETS 1 #define PHASE_TABLE 2 #define PHASE_OLD_TABLE 3 #define PHASE_DONE 4 // Initialize the iterator. // Returns false if Hmap contains no pointers (in which case the iterator is not initialized). bool hash_gciter_init (Hmap *h, struct hash_gciter *it) { // GC during map initialization or on an empty map. if(h->buckets == nil) return false; it->h = h; it->phase = PHASE_BUCKETS; it->bucket = 0; it->b = nil; // TODO: finish evacuating oldbuckets so that we can collect // oldbuckets? We don't want to keep a partially evacuated // table around forever, so each gc could make at least some // evacuation progress. Need to be careful about concurrent // access if we do concurrent gc. Even if not, we don't want // to make the gc pause any longer than it has to be. return true; } // Returns true and fills *data with internal structure/key/value data, // or returns false if the iterator has terminated. // Ugh, this interface is really annoying. I want a callback fn! bool hash_gciter_next(struct hash_gciter *it, struct hash_gciter_data *data) { Hmap *h; uintptr bucket, oldbucket; Bucket *b, *oldb; uintptr i; byte *k, *v; h = it->h; bucket = it->bucket; b = it->b; i = it->i; data->st = nil; data->key_data = nil; data->val_data = nil; data->indirectkey = (h->flags & IndirectKey) != 0; data->indirectval = (h->flags & IndirectValue) != 0; next: switch (it->phase) { case PHASE_BUCKETS: if(b != nil) { k = b->data + h->keysize * i; v = b->data + h->keysize * BUCKETSIZE + h->valuesize * i; for(; i < BUCKETSIZE; i++, k += h->keysize, v += h->valuesize) { if(b->tophash[i] != 0) { data->key_data = k; data->val_data = v; it->bucket = bucket; it->b = b; it->i = i + 1; return true; } } b = b->overflow; if(b != nil) { data->st = (byte*)b; it->bucket = bucket; it->b = b; it->i = 0; return true; } } while(bucket < ((uintptr)1 << h->B)) { if(h->oldbuckets != nil) { oldbucket = bucket & (((uintptr)1 << (h->B - 1)) - 1); oldb = (Bucket*)(h->oldbuckets + oldbucket * h->bucketsize); if(!evacuated(oldb)) { // new bucket isn't valid yet bucket++; continue; } } b = (Bucket*)(h->buckets + bucket * h->bucketsize); i = 0; bucket++; goto next; } it->phase = PHASE_OLD_BUCKETS; bucket = 0; b = nil; goto next; case PHASE_OLD_BUCKETS: if(h->oldbuckets == nil) { it->phase = PHASE_TABLE; goto next; } if(b != nil) { k = b->data + h->keysize * i; v = b->data + h->keysize * BUCKETSIZE + h->valuesize * i; for(; i < BUCKETSIZE; i++, k += h->keysize, v += h->valuesize) { if(b->tophash[i] != 0) { data->key_data = k; data->val_data = v; it->bucket = bucket; it->b = b; it->i = i + 1; return true; } } b = overflowptr(b); if(b != nil) { data->st = (byte*)b; it->bucket = bucket; it->b = b; it->i = 0; return true; } } if(bucket < ((uintptr)1 << (h->B - 1))) { b = (Bucket*)(h->oldbuckets + bucket * h->bucketsize); bucket++; i = 0; goto next; } it->phase = PHASE_TABLE; goto next; case PHASE_TABLE: it->phase = PHASE_OLD_TABLE; data->st = h->buckets; return true; case PHASE_OLD_TABLE: it->phase = PHASE_DONE; if(h->oldbuckets != nil) { data->st = h->oldbuckets; return true; } else { goto next; } } if(it->phase != PHASE_DONE) runtime·throw("bad phase at done"); return false; } // /// interfaces to go runtime // void reflect·ismapkey(Type *typ, bool ret) { ret = typ != nil && typ->alg->hash != runtime·nohash; FLUSH(&ret); } Hmap* runtime·makemap_c(MapType *typ, int64 hint) { Hmap *h; Type *key; key = typ->key; if(hint < 0 || (int32)hint != hint) runtime·panicstring("makemap: size out of range"); if(key->alg->hash == runtime·nohash) runtime·throw("runtime.makemap: unsupported map key type"); h = runtime·mal(sizeof(*h)); if(UseSpanType) { if(false) { runtime·printf("makemap %S: %p\n", *typ->string, h); } runtime·settype(h, (uintptr)typ | TypeInfo_Map); } hash_init(typ, h, hint); // these calculations are compiler dependent. // figure out offsets of map call arguments. if(debug) { runtime·printf("makemap: map=%p; keysize=%p; valsize=%p; keyalg=%p; valalg=%p\n", h, key->size, typ->elem->size, key->alg, typ->elem->alg); } return h; } // makemap(key, val *Type, hint int64) (hmap *map[any]any); void runtime·makemap(MapType *typ, int64 hint, Hmap *ret) { ret = runtime·makemap_c(typ, hint); FLUSH(&ret); } // For reflect: // func makemap(Type *mapType) (hmap *map) void reflect·makemap(MapType *t, Hmap *ret) { ret = runtime·makemap_c(t, 0); FLUSH(&ret); } void runtime·mapaccess(MapType *t, Hmap *h, byte *ak, byte *av, bool *pres) { byte *res; Type *elem; elem = t->elem; if(h == nil || h->count == 0) { elem->alg->copy(elem->size, av, nil); *pres = false; return; } if(runtime·gcwaiting) runtime·gosched(); res = hash_lookup(t, h, &ak); if(res != nil) { *pres = true; elem->alg->copy(elem->size, av, res); } else { *pres = false; elem->alg->copy(elem->size, av, nil); } } // mapaccess1(hmap *map[any]any, key any) (val any); #pragma textflag 7 void runtime·mapaccess1(MapType *t, Hmap *h, ...) { byte *ak, *av; byte *res; if(raceenabled && h != nil) runtime·racereadpc(h, runtime·getcallerpc(&t), runtime·mapaccess1); ak = (byte*)(&h + 1); av = ak + ROUND(t->key->size, Structrnd); if(h == nil || h->count == 0) { t->elem->alg->copy(t->elem->size, av, nil); } else { res = hash_lookup(t, h, &ak); t->elem->alg->copy(t->elem->size, av, res); } if(debug) { runtime·prints("runtime.mapaccess1: map="); runtime·printpointer(h); runtime·prints("; key="); t->key->alg->print(t->key->size, ak); runtime·prints("; val="); t->elem->alg->print(t->elem->size, av); runtime·prints("\n"); } } // mapaccess2(hmap *map[any]any, key any) (val any, pres bool); #pragma textflag 7 void runtime·mapaccess2(MapType *t, Hmap *h, ...) { byte *ak, *av, *ap; if(raceenabled && h != nil) runtime·racereadpc(h, runtime·getcallerpc(&t), runtime·mapaccess2); ak = (byte*)(&h + 1); av = ak + ROUND(t->key->size, Structrnd); ap = av + t->elem->size; runtime·mapaccess(t, h, ak, av, ap); if(debug) { runtime·prints("runtime.mapaccess2: map="); runtime·printpointer(h); runtime·prints("; key="); t->key->alg->print(t->key->size, ak); runtime·prints("; val="); t->elem->alg->print(t->elem->size, av); runtime·prints("; pres="); runtime·printbool(*ap); runtime·prints("\n"); } } // For reflect: // func mapaccess(t type, h map, key iword) (val iword, pres bool) // where an iword is the same word an interface value would use: // the actual data if it fits, or else a pointer to the data. void reflect·mapaccess(MapType *t, Hmap *h, uintptr key, uintptr val, bool pres) { byte *ak, *av; if(raceenabled && h != nil) runtime·racereadpc(h, runtime·getcallerpc(&t), reflect·mapaccess); if(t->key->size <= sizeof(key)) ak = (byte*)&key; else ak = (byte*)key; val = 0; pres = false; if(t->elem->size <= sizeof(val)) av = (byte*)&val; else { av = runtime·mal(t->elem->size); val = (uintptr)av; } runtime·mapaccess(t, h, ak, av, &pres); FLUSH(&val); FLUSH(&pres); } void runtime·mapassign(MapType *t, Hmap *h, byte *ak, byte *av) { if(h == nil) runtime·panicstring("assignment to entry in nil map"); if(runtime·gcwaiting) runtime·gosched(); if(av == nil) { hash_remove(t, h, ak); } else { hash_insert(t, h, ak, av); } if(debug) { runtime·prints("mapassign: map="); runtime·printpointer(h); runtime·prints("; key="); t->key->alg->print(t->key->size, ak); runtime·prints("; val="); t->elem->alg->print(t->elem->size, av); runtime·prints("\n"); } } // mapassign1(mapType *type, hmap *map[any]any, key any, val any); #pragma textflag 7 void runtime·mapassign1(MapType *t, Hmap *h, ...) { byte *ak, *av; if(h == nil) runtime·panicstring("assignment to entry in nil map"); if(raceenabled) runtime·racewritepc(h, runtime·getcallerpc(&t), runtime·mapassign1); ak = (byte*)(&h + 1); av = ak + ROUND(t->key->size, t->elem->align); runtime·mapassign(t, h, ak, av); } // mapdelete(mapType *type, hmap *map[any]any, key any) #pragma textflag 7 void runtime·mapdelete(MapType *t, Hmap *h, ...) { byte *ak; if(h == nil) return; if(raceenabled) runtime·racewritepc(h, runtime·getcallerpc(&t), runtime·mapdelete); ak = (byte*)(&h + 1); runtime·mapassign(t, h, ak, nil); if(debug) { runtime·prints("mapdelete: map="); runtime·printpointer(h); runtime·prints("; key="); t->key->alg->print(t->key->size, ak); runtime·prints("\n"); } } // For reflect: // func mapassign(t type h map, key, val iword, pres bool) // where an iword is the same word an interface value would use: // the actual data if it fits, or else a pointer to the data. void reflect·mapassign(MapType *t, Hmap *h, uintptr key, uintptr val, bool pres) { byte *ak, *av; if(h == nil) runtime·panicstring("assignment to entry in nil map"); if(raceenabled) runtime·racewritepc(h, runtime·getcallerpc(&t), reflect·mapassign); if(t->key->size <= sizeof(key)) ak = (byte*)&key; else ak = (byte*)key; if(t->elem->size <= sizeof(val)) av = (byte*)&val; else av = (byte*)val; if(!pres) av = nil; runtime·mapassign(t, h, ak, av); } // mapiterinit(mapType *type, hmap *map[any]any, hiter *any); void runtime·mapiterinit(MapType *t, Hmap *h, struct hash_iter *it) { if(h == nil) { it->key = nil; return; } if(raceenabled) runtime·racereadpc(h, runtime·getcallerpc(&t), runtime·mapiterinit); hash_iter_init(t, h, it); hash_next(it); if(debug) { runtime·prints("runtime.mapiterinit: map="); runtime·printpointer(h); runtime·prints("; iter="); runtime·printpointer(it); runtime·prints("; key="); runtime·printpointer(it->key); runtime·prints("\n"); } } // For reflect: // func mapiterinit(h map) (it iter) void reflect·mapiterinit(MapType *t, Hmap *h, struct hash_iter *it) { uint8 flags; if(h != nil && t->key->size > sizeof(void*)) { // reflect·mapiterkey returns pointers to key data, // and reflect holds them, so we cannot free key data // eagerly anymore. flags = h->flags; if(flags & IndirectKey) flags &= ~CanFreeKey; else flags &= ~CanFreeBucket; h->flags = flags; } it = runtime·mal(sizeof *it); FLUSH(&it); runtime·mapiterinit(t, h, it); } // mapiternext(hiter *any); void runtime·mapiternext(struct hash_iter *it) { if(raceenabled) runtime·racereadpc(it->h, runtime·getcallerpc(&it), runtime·mapiternext); if(runtime·gcwaiting) runtime·gosched(); hash_next(it); if(debug) { runtime·prints("runtime.mapiternext: iter="); runtime·printpointer(it); runtime·prints("; key="); runtime·printpointer(it->key); runtime·prints("\n"); } } // For reflect: // func mapiternext(it iter) void reflect·mapiternext(struct hash_iter *it) { runtime·mapiternext(it); } // mapiter1(hiter *any) (key any); #pragma textflag 7 void runtime·mapiter1(struct hash_iter *it, ...) { byte *ak, *res; Type *key; ak = (byte*)(&it + 1); res = it->key; if(res == nil) runtime·throw("runtime.mapiter1: key:val nil pointer"); key = it->t->key; key->alg->copy(key->size, ak, res); if(debug) { runtime·prints("mapiter1: iter="); runtime·printpointer(it); runtime·prints("; map="); runtime·printpointer(it->h); runtime·prints("\n"); } } bool runtime·mapiterkey(struct hash_iter *it, void *ak) { byte *res; Type *key; res = it->key; if(res == nil) return false; key = it->t->key; key->alg->copy(key->size, ak, res); return true; } // For reflect: // func mapiterkey(h map) (key iword, ok bool) // where an iword is the same word an interface value would use: // the actual data if it fits, or else a pointer to the data. void reflect·mapiterkey(struct hash_iter *it, uintptr key, bool ok) { byte *res; Type *tkey; key = 0; ok = false; res = it->key; if(res == nil) { key = 0; ok = false; } else { tkey = it->t->key; key = 0; if(tkey->size <= sizeof(key)) tkey->alg->copy(tkey->size, (byte*)&key, res); else key = (uintptr)res; ok = true; } FLUSH(&key); FLUSH(&ok); } // For reflect: // func maplen(h map) (len int) // Like len(m) in the actual language, we treat the nil map as length 0. void reflect·maplen(Hmap *h, intgo len) { if(h == nil) len = 0; else { len = h->count; if(raceenabled) runtime·racereadpc(h, runtime·getcallerpc(&h), reflect·maplen); } FLUSH(&len); } // mapiter2(hiter *any) (key any, val any); #pragma textflag 7 void runtime·mapiter2(struct hash_iter *it, ...) { byte *ak, *av, *res; MapType *t; t = it->t; ak = (byte*)(&it + 1); av = ak + ROUND(t->key->size, t->elem->align); res = it->key; if(res == nil) runtime·throw("runtime.mapiter2: key:val nil pointer"); t->key->alg->copy(t->key->size, ak, res); t->elem->alg->copy(t->elem->size, av, it->value); if(debug) { runtime·prints("mapiter2: iter="); runtime·printpointer(it); runtime·prints("; map="); runtime·printpointer(it->h); runtime·prints("\n"); } }