summaryrefslogtreecommitdiff
path: root/src/pkg/runtime/hashmap.goc
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkg/runtime/hashmap.goc')
-rw-r--r--src/pkg/runtime/hashmap.goc1078
1 files changed, 1078 insertions, 0 deletions
diff --git a/src/pkg/runtime/hashmap.goc b/src/pkg/runtime/hashmap.goc
new file mode 100644
index 000000000..3327bed65
--- /dev/null
+++ b/src/pkg/runtime/hashmap.goc
@@ -0,0 +1,1078 @@
+// 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.
+
+package runtime
+#include "runtime.h"
+#include "arch_GOARCH.h"
+#include "malloc.h"
+#include "type.h"
+#include "race.h"
+#include "hashmap.h"
+#include "typekind.h"
+#include "../../cmd/ld/textflag.h"
+
+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++) {
+ for(b = (Bucket*)(h->buckets + bucket * h->bucketsize); b != nil; b = b->overflow) {
+ for(i = 0, k = (byte*)b->data, v = k + h->keysize * BUCKETSIZE; i < BUCKETSIZE; i++, k += h->keysize, v += h->valuesize) {
+ if(b->tophash[i] == Empty)
+ continue;
+ if(b->tophash[i] > Empty && b->tophash[i] < MinTopHash)
+ runtime·throw("evacuated cell in buckets");
+ 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 < MinTopHash)
+ top += MinTopHash;
+ 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);
+ for(; b != nil; b = b->overflow) {
+ for(i = 0, k = (byte*)b->data, v = k + h->keysize * BUCKETSIZE; i < BUCKETSIZE; i++, k += h->keysize, v += h->valuesize) {
+ if(b->tophash[i] < MinTopHash)
+ continue;
+ if(oldbucket < h->nevacuate)
+ runtime·throw("unevacuated entry in an evacuated bucket");
+ 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 < MinTopHash)
+ top += MinTopHash;
+ 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 keysize, valuesize, bucketsize;
+ uint8 flags;
+
+ flags = 0;
+
+ // figure out how big we have to make everything
+ keysize = t->key->size;
+ if(keysize > MAXKEYSIZE) {
+ flags |= IndirectKey;
+ keysize = sizeof(byte*);
+ }
+ valuesize = t->elem->size;
+ if(valuesize > MAXVALUESIZE) {
+ flags |= IndirectValue;
+ valuesize = sizeof(byte*);
+ }
+ bucketsize = offsetof(Bucket, data[0]) + (keysize + valuesize) * BUCKETSIZE;
+ if(bucketsize != t->bucket->size) {
+ runtime·printf("runtime: bucketsize=%p but t->bucket->size=%p; t=%S\n", bucketsize, t->bucket->size, *t->string);
+ runtime·throw("bucketsize wrong");
+ }
+
+ // 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((offsetof(Bucket, data[0]) & (t->key->align-1)) != 0)
+ runtime·throw("need padding in bucket (key)");
+ if((offsetof(Bucket, data[0]) & (t->elem->align-1)) != 0)
+ 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·cnewarray(t->bucket, (uintptr)1 << B);
+ }
+
+ // initialize Hmap
+ 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 marking the topbits
+// entries as evacuated, so that iterators can still iterate through the old buckets.
+static void
+evacuate(MapType *t, Hmap *h, uintptr oldbucket)
+{
+ Bucket *b;
+ Bucket *x, *y;
+ Bucket *newx, *newy;
+ uintptr xi, yi;
+ uintptr newbit;
+ uintptr hash;
+ uintptr i;
+ byte *k, *v;
+ byte *xk, *yk, *xv, *yv;
+ uint8 top;
+ bool eq;
+
+ 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 !OldIterator.)
+
+ x = (Bucket*)(h->buckets + oldbucket * h->bucketsize);
+ y = (Bucket*)(h->buckets + (oldbucket + newbit) * h->bucketsize);
+ xi = 0;
+ yi = 0;
+ xk = (byte*)x->data;
+ yk = (byte*)y->data;
+ xv = xk + h->keysize * BUCKETSIZE;
+ yv = yk + h->keysize * BUCKETSIZE;
+ for(; b != nil; b = b->overflow) {
+ for(i = 0, k = (byte*)b->data, v = k + h->keysize * BUCKETSIZE; i < BUCKETSIZE; i++, k += h->keysize, v += h->valuesize) {
+ top = b->tophash[i];
+ if(top == Empty) {
+ b->tophash[i] = EvacuatedEmpty;
+ continue;
+ }
+ if(top < MinTopHash)
+ runtime·throw("bad state");
+
+ // Compute hash to make our evacuation decision (whether we need
+ // to send this key/value to bucket x or bucket y).
+ hash = h->hash0;
+ t->key->alg->hash(&hash, t->key->size, IK(h, k));
+ if((h->flags & Iterator) != 0) {
+ t->key->alg->equal(&eq, t->key->size, IK(h, k), IK(h, k));
+ if(!eq) {
+ // If key != key (NaNs), then the hash could be (and probably
+ // will be) entirely different from the old hash. Moreover,
+ // it isn't reproducible. Reproducibility is required in the
+ // presence of iterators, as our evacuation decision must
+ // match whatever decision the iterator made.
+ // Fortunately, we have the freedom to send these keys either
+ // way. Also, tophash is meaningless for these kinds of keys.
+ // We let the low bit of tophash drive the evacuation decision.
+ // We recompute a new random tophash for the next level so
+ // these keys will get evenly distributed across all buckets
+ // after multiple grows.
+ if((top & 1) != 0)
+ hash |= newbit;
+ else
+ hash &= ~newbit;
+ top = hash >> (8*sizeof(uintptr)-8);
+ if(top < MinTopHash)
+ top += MinTopHash;
+ }
+ }
+
+ if((hash & newbit) == 0) {
+ b->tophash[i] = EvacuatedX;
+ if(xi == BUCKETSIZE) {
+ if(checkgc) mstats.next_gc = mstats.heap_alloc;
+ newx = runtime·cnew(t->bucket);
+ x->overflow = newx;
+ x = newx;
+ xi = 0;
+ xk = (byte*)x->data;
+ xv = xk + h->keysize * BUCKETSIZE;
+ }
+ x->tophash[xi] = top;
+ 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 {
+ b->tophash[i] = EvacuatedY;
+ if(yi == BUCKETSIZE) {
+ if(checkgc) mstats.next_gc = mstats.heap_alloc;
+ newy = runtime·cnew(t->bucket);
+ y->overflow = newy;
+ y = newy;
+ yi = 0;
+ yk = (byte*)y->data;
+ yv = yk + h->keysize * BUCKETSIZE;
+ }
+ y->tophash[yi] = top;
+ 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;
+ }
+ }
+ }
+
+ // Unlink the overflow buckets & clear key/value to help GC.
+ if((h->flags & OldIterator) == 0) {
+ b = (Bucket*)(h->oldbuckets + oldbucket * h->bucketsize);
+ b->overflow = nil;
+ runtime·memclr((byte*)b->data, h->bucketsize - offsetof(Bucket, data[0]));
+ }
+ }
+
+ // Advance evacuation mark
+ if(oldbucket == h->nevacuate) {
+ h->nevacuate = oldbucket + 1;
+ if(oldbucket + 1 == newbit) // newbit == # of oldbuckets
+ // Growing is all done. Free old main bucket array.
+ 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;
+ if(checkgc) mstats.next_gc = mstats.heap_alloc;
+ new_buckets = runtime·cnewarray(t->bucket, (uintptr)1 << (h->B + 1));
+ flags = (h->flags & ~(Iterator | OldIterator));
+ if((h->flags & Iterator) != 0)
+ flags |= OldIterator;
+
+ // 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, oldbucket;
+ 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) {
+ oldbucket = bucket & (((uintptr)1 << (h->B - 1)) - 1);
+ b = (Bucket*)(h->oldbuckets + oldbucket * h->bucketsize);
+ if(evacuated(b)) {
+ b = (Bucket*)(h->buckets + bucket * h->bucketsize);
+ }
+ } else {
+ b = (Bucket*)(h->buckets + bucket * h->bucketsize);
+ }
+ top = hash >> (sizeof(uintptr)*8 - 8);
+ if(top < MinTopHash)
+ top += MinTopHash;
+ do {
+ for(i = 0, k = (byte*)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;
+}
+
+// Specialized versions of mapaccess1 for specific types.
+// See ./hashmap_fast.c 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 FASTKEY(x) true
+#define QUICK_NE(x,y) ((x) != (y))
+#define QUICK_EQ(x,y) true
+#define SLOW_EQ(x,y) true
+#define MAYBE_EQ(x,y) true
+#include "hashmap_fast.c"
+
+#undef HASH_LOOKUP1
+#undef HASH_LOOKUP2
+#undef KEYTYPE
+#undef HASHFUNC
+#undef FASTKEY
+#undef QUICK_NE
+#undef QUICK_EQ
+#undef SLOW_EQ
+#undef MAYBE_EQ
+
+#define HASH_LOOKUP1 runtime·mapaccess1_fast64
+#define HASH_LOOKUP2 runtime·mapaccess2_fast64
+#define KEYTYPE uint64
+#define HASHFUNC runtime·algarray[AMEM64].hash
+#define FASTKEY(x) true
+#define QUICK_NE(x,y) ((x) != (y))
+#define QUICK_EQ(x,y) true
+#define SLOW_EQ(x,y) true
+#define MAYBE_EQ(x,y) true
+#include "hashmap_fast.c"
+
+#undef HASH_LOOKUP1
+#undef HASH_LOOKUP2
+#undef KEYTYPE
+#undef HASHFUNC
+#undef FASTKEY
+#undef QUICK_NE
+#undef QUICK_EQ
+#undef SLOW_EQ
+#undef MAYBE_EQ
+
+#ifdef GOARCH_amd64
+#define CHECKTYPE uint64
+#endif
+#ifdef GOARCH_amd64p32
+#define CHECKTYPE uint32
+#endif
+#ifdef GOARCH_386
+#define CHECKTYPE uint32
+#endif
+#ifdef GOARCH_arm
+// can't use uint32 on arm because our loads aren't aligned.
+// TODO: use uint32 for arm v6+?
+#define CHECKTYPE uint8
+#endif
+
+#define HASH_LOOKUP1 runtime·mapaccess1_faststr
+#define HASH_LOOKUP2 runtime·mapaccess2_faststr
+#define KEYTYPE String
+#define HASHFUNC runtime·algarray[ASTRING].hash
+#define FASTKEY(x) ((x).len < 32)
+#define QUICK_NE(x,y) ((x).len != (y).len)
+#define QUICK_EQ(x,y) ((x).str == (y).str)
+#define SLOW_EQ(x,y) runtime·memeq((x).str, (y).str, (x).len)
+#define MAYBE_EQ(x,y) (*(CHECKTYPE*)(x).str == *(CHECKTYPE*)(y).str && *(CHECKTYPE*)((x).str + (x).len - sizeof(CHECKTYPE)) == *(CHECKTYPE*)((y).str + (x).len - sizeof(CHECKTYPE)))
+#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·cnewarray(t->bucket, 1);
+
+ 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 < MinTopHash)
+ top += MinTopHash;
+ inserti = nil;
+ insertk = nil;
+ insertv = nil;
+ while(true) {
+ for(i = 0, k = (byte*)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] == Empty && 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·cnew(t->bucket);
+ b->overflow = newb;
+ inserti = newb->tophash;
+ insertk = (byte*)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·cnew(t->key);
+ *(byte**)insertk = kmem;
+ insertk = kmem;
+ }
+ if((h->flags & IndirectValue) != 0) {
+ if(checkgc) mstats.next_gc = mstats.heap_alloc;
+ vmem = runtime·cnew(t->elem);
+ *(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 < MinTopHash)
+ top += MinTopHash;
+ do {
+ for(i = 0, k = (byte*)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 & IndirectKey) != 0) {
+ *(byte**)k = nil;
+ } else {
+ t->key->alg->copy(t->key->size, k, nil);
+ }
+ if((h->flags & IndirectValue) != 0) {
+ *(byte**)v = nil;
+ } else {
+ t->elem->alg->copy(t->elem->size, v, nil);
+ }
+
+ b->tophash[i] = Empty;
+ h->count--;
+
+ // 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.
+
+// 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, Hiter *it)
+{
+ uint32 old;
+
+ if(sizeof(Hiter) / sizeof(uintptr) != 10) {
+ runtime·throw("hash_iter size incorrect"); // see ../../cmd/gc/reflect.c
+ }
+ it->t = t;
+ it->h = h;
+
+ // grab snapshot of bucket state
+ it->B = h->B;
+ it->buckets = h->buckets;
+
+ // iterator state
+ it->bucket = 0;
+ it->offset = runtime·fastrand1() & (BUCKETSIZE - 1);
+ it->done = false;
+ it->bptr = nil;
+
+ // Remember we have an iterator.
+ // Can run concurrently with another hash_iter_init().
+ for(;;) {
+ old = h->flags;
+ if((old&(Iterator|OldIterator)) == (Iterator|OldIterator))
+ break;
+ if(runtime·cas(&h->flags, old, old|Iterator|OldIterator))
+ break;
+ }
+
+ if(h->buckets == nil) {
+ // Empty map. Force next hash_next to exit without
+ // evaluating h->bucket.
+ it->done = 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(Hiter *it)
+{
+ Hmap *h;
+ MapType *t;
+ uintptr bucket, oldbucket;
+ uintptr hash;
+ Bucket *b;
+ uintptr i, offi;
+ intptr check_bucket;
+ bool eq;
+ byte *k, *v;
+ byte *rk, *rv;
+
+ h = it->h;
+ t = it->t;
+ bucket = it->bucket;
+ b = it->bptr;
+ i = it->i;
+ check_bucket = it->check_bucket;
+
+next:
+ if(b == nil) {
+ if(it->done) {
+ // 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.
+ // If the bucket we're looking at hasn't been filled in yet (i.e. the old
+ // bucket hasn't been evacuated) then we need to iterate through the old
+ // bucket and only return the ones that will be migrated to this bucket.
+ oldbucket = bucket & (((uintptr)1 << (it->B - 1)) - 1);
+ b = (Bucket*)(h->oldbuckets + oldbucket * h->bucketsize);
+ if(!evacuated(b)) {
+ check_bucket = bucket;
+ } else {
+ b = (Bucket*)(it->buckets + bucket * h->bucketsize);
+ check_bucket = -1;
+ }
+ } else {
+ b = (Bucket*)(it->buckets + bucket * h->bucketsize);
+ check_bucket = -1;
+ }
+ bucket++;
+ if(bucket == ((uintptr)1 << it->B)) {
+ bucket = 0;
+ it->done = true;
+ }
+ i = 0;
+ }
+ for(; i < BUCKETSIZE; i++) {
+ offi = (i + it->offset) & (BUCKETSIZE - 1);
+ k = (byte*)b->data + h->keysize * offi;
+ v = (byte*)b->data + h->keysize * BUCKETSIZE + h->valuesize * offi;
+ if(b->tophash[offi] != Empty && b->tophash[offi] != EvacuatedEmpty) {
+ if(check_bucket >= 0) {
+ // Special case: iterator was started during a grow and the
+ // grow is not done yet. We're working on a bucket whose
+ // oldbucket has not been evacuated yet. Or at least, it wasn't
+ // evacuated when we started the bucket. So we're iterating
+ // through the oldbucket, skipping any keys that will go
+ // to the other new bucket (each oldbucket expands to two
+ // buckets during a grow).
+ t->key->alg->equal(&eq, t->key->size, IK(h, k), IK(h, k));
+ if(eq) {
+ // If the item in the oldbucket is not destined for
+ // the current new bucket in the iteration, skip it.
+ hash = h->hash0;
+ t->key->alg->hash(&hash, t->key->size, IK(h, k));
+ if((hash & (((uintptr)1 << it->B) - 1)) != check_bucket) {
+ continue;
+ }
+ } else {
+ // Hash isn't repeatable if k != k (NaNs). We need a
+ // repeatable and randomish choice of which direction
+ // to send NaNs during evacuation. We'll use the low
+ // bit of tophash to decide which way NaNs go.
+ // NOTE: this case is why we need two evacuate tophash
+ // values, evacuatedX and evacuatedY, that differ in
+ // their low bit.
+ if(check_bucket >> (it->B - 1) != (b->tophash[offi] & 1)) {
+ continue;
+ }
+ }
+ }
+ if(b->tophash[offi] != EvacuatedX && b->tophash[offi] != EvacuatedY) {
+ // 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;
+ it->check_bucket = check_bucket;
+ return;
+ }
+ }
+ b = b->overflow;
+ i = 0;
+ goto next;
+}
+
+//
+/// interfaces to go runtime
+//
+
+func reflect·ismapkey(typ *Type) (ret bool) {
+ ret = typ != nil && typ->alg->hash != runtime·nohash;
+}
+
+static Hmap*
+makemap_c(MapType *typ, int64 hint)
+{
+ Hmap *h;
+ Type *key;
+
+ key = typ->key;
+
+ if(sizeof(Hmap) > 48)
+ runtime·panicstring("hmap too large");
+
+ 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·cnew(typ->hmap);
+ 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;
+}
+
+func makemap(typ *MapType, hint int64) (ret *Hmap) {
+ ret = makemap_c(typ, hint);
+}
+
+func reflect·makemap(t *MapType) (ret *Hmap) {
+ ret = makemap_c(t, 0);
+}
+
+// NOTE: The returned pointer may keep the whole map live, so don't
+// hold onto it for very long.
+#pragma textflag NOSPLIT
+func mapaccess1(t *MapType, h *Hmap, key *byte) (val *byte) {
+ if(raceenabled && h != nil) {
+ runtime·racereadpc(h, runtime·getcallerpc(&t), runtime·mapaccess1);
+ runtime·racereadobjectpc(key, t->key, runtime·getcallerpc(&t), runtime·mapaccess1);
+ }
+ if(h == nil || h->count == 0) {
+ val = t->elem->zero;
+ } else {
+ val = hash_lookup(t, h, &key);
+ if(val == nil)
+ val = t->elem->zero;
+ }
+
+ if(debug) {
+ runtime·prints("runtime.mapaccess1: map=");
+ runtime·printpointer(h);
+ runtime·prints("; key=");
+ t->key->alg->print(t->key->size, key);
+ runtime·prints("; val=");
+ t->elem->alg->print(t->elem->size, val);
+ runtime·prints("\n");
+ }
+}
+
+// NOTE: The returned pointer keeps the whole map live, so don't
+// hold onto it for very long.
+#pragma textflag NOSPLIT
+func mapaccess2(t *MapType, h *Hmap, key *byte) (val *byte, pres bool) {
+ if(raceenabled && h != nil) {
+ runtime·racereadpc(h, runtime·getcallerpc(&t), runtime·mapaccess2);
+ runtime·racereadobjectpc(key, t->key, runtime·getcallerpc(&t), runtime·mapaccess2);
+ }
+
+ if(h == nil || h->count == 0) {
+ val = t->elem->zero;
+ pres = false;
+ } else {
+ val = hash_lookup(t, h, &key);
+ if(val == nil) {
+ val = t->elem->zero;
+ pres = false;
+ } else {
+ pres = true;
+ }
+ }
+
+ if(debug) {
+ runtime·prints("runtime.mapaccess2: map=");
+ runtime·printpointer(h);
+ runtime·prints("; key=");
+ t->key->alg->print(t->key->size, key);
+ runtime·prints("; val=");
+ t->elem->alg->print(t->elem->size, val);
+ runtime·prints("; pres=");
+ runtime·printbool(pres);
+ runtime·prints("\n");
+ }
+}
+
+#pragma textflag NOSPLIT
+func reflect·mapaccess(t *MapType, h *Hmap, key *byte) (val *byte) {
+ if(h == nil)
+ val = nil;
+ else {
+ if(raceenabled) {
+ runtime·racereadpc(h, runtime·getcallerpc(&t), reflect·mapaccess);
+ runtime·racereadobjectpc(key, t->key, runtime·getcallerpc(&t), reflect·mapaccess);
+ }
+ val = hash_lookup(t, h, &key);
+ }
+}
+
+#pragma textflag NOSPLIT
+func mapassign1(t *MapType, h *Hmap, key *byte, val *byte) {
+ if(h == nil)
+ runtime·panicstring("assignment to entry in nil map");
+
+ if(raceenabled) {
+ runtime·racewritepc(h, runtime·getcallerpc(&t), runtime·mapassign1);
+ runtime·racereadobjectpc(key, t->key, runtime·getcallerpc(&t), runtime·mapassign1);
+ runtime·racereadobjectpc(val, t->elem, runtime·getcallerpc(&t), runtime·mapassign1);
+ }
+
+ hash_insert(t, h, key, val);
+
+ if(debug) {
+ runtime·prints("mapassign1: map=");
+ runtime·printpointer(h);
+ runtime·prints("; key=");
+ t->key->alg->print(t->key->size, key);
+ runtime·prints("; val=");
+ t->elem->alg->print(t->elem->size, val);
+ runtime·prints("\n");
+ }
+}
+
+#pragma textflag NOSPLIT
+func mapdelete(t *MapType, h *Hmap, key *byte) {
+ if(h == nil)
+ return;
+
+ if(raceenabled) {
+ runtime·racewritepc(h, runtime·getcallerpc(&t), runtime·mapdelete);
+ runtime·racereadobjectpc(key, t->key, runtime·getcallerpc(&t), runtime·mapdelete);
+ }
+
+ hash_remove(t, h, key);
+
+ if(debug) {
+ runtime·prints("mapdelete: map=");
+ runtime·printpointer(h);
+ runtime·prints("; key=");
+ t->key->alg->print(t->key->size, key);
+ runtime·prints("\n");
+ }
+}
+
+#pragma textflag NOSPLIT
+func reflect·mapassign(t *MapType, h *Hmap, key *byte, val *byte) {
+ if(h == nil)
+ runtime·panicstring("assignment to entry in nil map");
+ if(raceenabled) {
+ runtime·racewritepc(h, runtime·getcallerpc(&t), reflect·mapassign);
+ runtime·racereadobjectpc(key, t->key, runtime·getcallerpc(&t), reflect·mapassign);
+ runtime·racereadobjectpc(val, t->elem, runtime·getcallerpc(&t), reflect·mapassign);
+ }
+
+ hash_insert(t, h, key, val);
+
+ if(debug) {
+ runtime·prints("mapassign: map=");
+ runtime·printpointer(h);
+ runtime·prints("; key=");
+ t->key->alg->print(t->key->size, key);
+ runtime·prints("; val=");
+ t->elem->alg->print(t->elem->size, val);
+ runtime·prints("\n");
+ }
+}
+
+#pragma textflag NOSPLIT
+func reflect·mapdelete(t *MapType, h *Hmap, key *byte) {
+ if(h == nil)
+ return; // see bug 8051
+ if(raceenabled) {
+ runtime·racewritepc(h, runtime·getcallerpc(&t), reflect·mapdelete);
+ runtime·racereadobjectpc(key, t->key, runtime·getcallerpc(&t), reflect·mapdelete);
+ }
+ hash_remove(t, h, key);
+
+ if(debug) {
+ runtime·prints("mapdelete: map=");
+ runtime·printpointer(h);
+ runtime·prints("; key=");
+ t->key->alg->print(t->key->size, key);
+ runtime·prints("\n");
+ }
+}
+
+#pragma textflag NOSPLIT
+func mapiterinit(t *MapType, h *Hmap, it *Hiter) {
+ // Clear pointer fields so garbage collector does not complain.
+ it->key = nil;
+ it->value = nil;
+ it->t = nil;
+ it->h = nil;
+ it->buckets = nil;
+ it->bptr = nil;
+
+ if(h == nil || h->count == 0) {
+ 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");
+ }
+}
+
+func reflect·mapiterinit(t *MapType, h *Hmap) (it *Hiter) {
+ it = runtime·mal(sizeof *it);
+ runtime·mapiterinit(t, h, it);
+}
+
+#pragma textflag NOSPLIT
+func mapiternext(it *Hiter) {
+ if(raceenabled)
+ runtime·racereadpc(it->h, runtime·getcallerpc(&it), runtime·mapiternext);
+
+ hash_next(it);
+ if(debug) {
+ runtime·prints("runtime.mapiternext: iter=");
+ runtime·printpointer(it);
+ runtime·prints("; key=");
+ runtime·printpointer(it->key);
+ runtime·prints("\n");
+ }
+}
+
+func reflect·mapiternext(it *Hiter) {
+ runtime·mapiternext(it);
+}
+
+func reflect·mapiterkey(it *Hiter) (key *byte) {
+ key = it->key;
+}
+
+#pragma textflag NOSPLIT
+func reflect·maplen(h *Hmap) (len int) {
+ if(h == nil)
+ len = 0;
+ else {
+ len = h->count;
+ if(raceenabled)
+ runtime·racereadpc(h, runtime·getcallerpc(&h), reflect·maplen);
+ }
+}
+
+// exported value for testing
+float64 runtime·hashLoad = LOAD;