/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; -*- * * The contents of this file are subject to the Netscape Public * License Version 1.1 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.mozilla.org/NPL/ * * Software distributed under the License is distributed on an "AS * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or * implied. See the License for the specific language governing * rights and limitations under the License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All * Rights Reserved. * * Contributor(s): */ /******************************************************************************* * Hash Tables: Based on Larson's in-memory adaption of linear hashing * [CACM, vol 31, #4, 1988 pp 446-457] ******************************************************************************/ #include "dynahash.h" #include "prlog.h" #define MUL(x, y) ((x) << (y##_SHIFT)) #define DIV(x, y) ((x) >> (y##_SHIFT)) #define MOD(x, y) ((x) & ((y) - 1)) PR_IMPLEMENT(PLDynahash*) PL_NewDynahash(PRUint32 initialSize, PLHashFun hash, PLEqualFun equal, PLDestroyFun del, void* context) { PRStatus status; PLDynahash* self = (PLDynahash*)malloc(sizeof(PLDynahash)); if (self == NULL) return self; status = PL_DynahashInit(self, initialSize, hash, equal, del, context); if (status != PR_SUCCESS) { PL_DynahashDestroy(self); return NULL; } return self; } PR_IMPLEMENT(void) PL_DynahashDestroy(PLDynahash* self) { PL_DynahashEmpty(self); free(self); } PR_IMPLEMENT(PRStatus) PL_DynahashInit(PLDynahash* self, PRUint32 initialSize, PLHashFun hash, PLEqualFun equal, PLDestroyFun del, void* context) { PRUint32 count = DIV(initialSize, PL_DYNAHASH_BUCKETSIZE) + 1; self->directory = (PLHashDir*)count; self->hash = hash; self->equal = equal; self->destroy = del; self->keyCount = 0; self->nextBucketToSplit = 0; self->maxSize = (PRUint16)MUL(count, PL_DYNAHASH_BUCKETSIZE); self->bucketCount = 0; self->context = context; #ifdef PL_DYNAHASH_STATISTICS self->accesses = 0; self->collisions = 0; self->expansions = 0; #endif /* PL_DYNAHASH_STATISTICS */ return PR_SUCCESS; } static PRStatus pl_DynahashAllocBucket(PLDynahash* self) { PLHashDir* newDir; PRUint32 i, size; PRUint16 count = 1; if (self->bucketCount == 0) { count = (PRUint16)(PRInt32)self->directory; } /* realloc directory */ size = self->bucketCount + count; newDir = (PLHashDir*)calloc(sizeof(PLHashDir*), size); if (newDir == NULL) return PR_FAILURE; // memset((void*)newDir, 0, size * sizeof(PLHashBucket*)); if (self->bucketCount) { memcpy((void*)newDir, self->directory, self->bucketCount * sizeof(PLHashBucket*)); } /* add new bucket(s) */ for (i = self->bucketCount; i < size; i++) { PLHashBucket* newBucket; newBucket = (PLHashBucket*)calloc(sizeof(PLHashBucket*), PL_DYNAHASH_BUCKETSIZE); if (newBucket == NULL) return PR_FAILURE; // memset((void*)newBucket, 0, sizeof(PLDynahashElement*) // * PL_DYNAHASH_BUCKETSIZE); newDir[i] = newBucket; } if (self->bucketCount != 0) { free(self->directory); } self->directory = newDir; self->bucketCount += count; return PR_SUCCESS; } PR_IMPLEMENT(void) PL_DynahashCleanup(PLDynahash* self) { PL_DynahashEmpty(self); } #define PL_DYNAHASH_HASH(resultVar, self, key) \ { \ PRUword _h_ = self->hash(self->context, key); \ PRUword _a_ = MOD(_h_, self->maxSize); \ if (_a_ < self->nextBucketToSplit) { \ _a_ = MOD(_h_, self->maxSize << 1); /* h % (2 * maxSize) */ \ } \ resultVar = _a_; \ } \ PR_IMPLEMENT(PLDynahashElement*) PL_DynahashLookup(PLDynahash* self, PLDynahashElement* element) { PRUword h; PRUint32 bucketIndex, binIndex; PLHashBucket* bin; PLDynahashElement* result = NULL; PLHashDir* dir; PLDynahashElement* e; if (self->bucketCount == 0) return NULL; PL_DYNAHASH_HASH(h, self, element); bucketIndex = DIV(h, PL_DYNAHASH_BUCKETSIZE); binIndex = MOD(h, PL_DYNAHASH_BUCKETSIZE); /* hash ensures valid segment */ dir = self->directory; bin = dir[bucketIndex]; PR_ASSERT(bin); for (e = bin[binIndex]; e != NULL; e = e->next) { if (self->equal(self->context, e, element)) { return e; } } return result; } static PRStatus pl_DynahashExpand(PLDynahash* self) { PRStatus err; if (self->maxSize + self->nextBucketToSplit < MUL(PL_DYNAHASH_MAX_DIR_SIZE, PL_DYNAHASH_BUCKETSIZE)) { PRUint32 oldDirIndex, oldBinIndex, newDirIndex, newBinIndex; PLHashBucket* oldSegment; PLHashBucket* newSegment; PRUword newAddress; PLDynahashElement** previous; PLDynahashElement* current; PLDynahashElement** lastOfNew; /* locate the bucket to split */ oldDirIndex = DIV(self->nextBucketToSplit, PL_DYNAHASH_BUCKETSIZE); oldSegment = self->directory[oldDirIndex]; oldBinIndex = MOD(self->nextBucketToSplit, PL_DYNAHASH_BUCKETSIZE); /* expand address space */ newAddress = self->maxSize + self->nextBucketToSplit; newDirIndex = DIV(newAddress, PL_DYNAHASH_BUCKETSIZE); newBinIndex = MOD(newAddress, PL_DYNAHASH_BUCKETSIZE); /* we need to unlock the directory handle here in case AllocBucket grows the directory */ if (newBinIndex == 0) { err = pl_DynahashAllocBucket(self); if (err != PR_SUCCESS) return err; } PR_ASSERT(newDirIndex < self->bucketCount); newSegment = self->directory[newDirIndex]; /* adjust state */ self->nextBucketToSplit++; if (self->nextBucketToSplit == self->maxSize) { self->maxSize <<= 1; /* maxSize *= 2 */ self->nextBucketToSplit = 0; } /* relocate records to the new bucket */ previous = &oldSegment[oldBinIndex]; current = *previous; lastOfNew = &newSegment[newBinIndex]; *lastOfNew = NULL; while (current != NULL) { PRUword hash; PL_DYNAHASH_HASH(hash, self, current); if (hash == newAddress) { /* attach to the end of the new chain */ *lastOfNew = current; /* remove from old chain */ *previous = current->next; lastOfNew = ¤t->next; current = current->next; *lastOfNew = NULL; } else { /* leave it on the old chain */ previous = ¤t->next; current = current->next; } } } return PR_SUCCESS; } PR_IMPLEMENT(PRStatus) PL_DynahashAdd(PLDynahash* self, PLDynahashElement* element, PRBool replace, PLDynahashElement* *elementToDestroy) { /* Returns a pointer to the unused thing (either the key or the replaced key). */ PRStatus err = PR_SUCCESS; PRUword h; PRUint32 bucketIndex, binIndex; PLHashBucket* b; PLDynahashElement** p; PLDynahashElement* q; PLDynahashElement* n; *elementToDestroy = NULL; if (self->bucketCount == 0) { err = pl_DynahashAllocBucket(self); if (err != PR_SUCCESS) return err; } PL_DYNAHASH_HASH(h, self, element); bucketIndex = DIV(h, PL_DYNAHASH_BUCKETSIZE); binIndex = MOD(h, PL_DYNAHASH_BUCKETSIZE); /* hash ensures valid segment */ b = self->directory[bucketIndex]; PR_ASSERT(b); /* follow collision chain */ p = &b[binIndex]; q = *p; while (q && !self->equal(self->context, q, element)) { p = &q->next; q = *p; } if (q && !replace) { *elementToDestroy = element; goto done; } n = element; *p = n; if (q) { /* replace q with n */ n->next = q->next; } else { /* table over full? expand it: */ if (++self->keyCount / MUL(self->bucketCount, PL_DYNAHASH_BUCKETSIZE) > PL_DYNAHASH_LOAD_FACTOR) { err = pl_DynahashExpand(self); if (err != PR_SUCCESS) return err; } } *elementToDestroy = q; done:; return err; } PR_IMPLEMENT(PLDynahashElement*) PL_DynahashRemove(PLDynahash* self, PLDynahashElement* element) { PRUword h; PRUint32 bucketIndex, binIndex; PLHashBucket* b; PLDynahashElement** p; PLDynahashElement* q; if (self->bucketCount == 0) return NULL; PL_DYNAHASH_HASH(h, self, element); bucketIndex = DIV(h, PL_DYNAHASH_BUCKETSIZE); binIndex = MOD(h, PL_DYNAHASH_BUCKETSIZE); b = self->directory[bucketIndex]; PR_ASSERT(b); p = &b[binIndex]; q = *p; while (q && !self->equal(self->context, q, element)) { p = &q->next; q = *p; } if (q) { /* remove q */ *p = q->next; --self->keyCount; /* XXX table can be shrunk? shrink it */ } return q; } static PLDynahashElement* PL_DynahashRemove1(PLDynahash* self, PRUint32 bucketIndex, PRInt32 binIndex, int elemIndex) { /* only used by PLDynahashIterator, below */ PLHashBucket* b; PLDynahashElement** p; PLDynahashElement* q; if (self->bucketCount == 0) return NULL; b = self->directory[bucketIndex]; p = &b[binIndex]; q = *p; while (elemIndex--) { p = &q->next; q = *p; } if (q) { *p = q->next; --self->keyCount; } return q; } PR_IMPLEMENT(void) PL_DynahashEmpty(PLDynahash* self) { PRUint32 i, j; for (i = 0; i < self->bucketCount; i++) { PLHashBucket* b = self->directory[i]; for (j = 0; j < PL_DYNAHASH_BUCKETSIZE; j++) { PLDynahashElement* q; PLDynahashElement* p = b[j]; while (p) { q = p->next; self->destroy(self->context, p); p = q; } } free(b); } if (self->bucketCount != 0) { free(self->directory); } self->keyCount = 0; self->nextBucketToSplit = 0; self->bucketCount = 0; self->directory = (PLHashDir*)1; self->maxSize = MUL(1, PL_DYNAHASH_BUCKETSIZE); } #ifdef DEBUG PR_IMPLEMENT(PRBool) PL_DynahashIsValid(PLDynahash* self) { PLDynahashElement* hep; PLDynahashIterator iter; PL_DynahashIteratorInit(&iter, self); while ((hep = PL_DynahashIteratorNext(&iter)) != NULL) { if (PL_DynahashLookup(self, hep) != hep) { /* something else matches */ return PR_FALSE; } } return PR_TRUE; } #endif /* DEBUG */ /******************************************************************************* * Hash Table Iterator ******************************************************************************/ PR_IMPLEMENT(void) PL_DynahashIteratorInit(PLDynahashIterator* self, PLDynahash* ht) { self->table = ht; self->bucket = 0; self->bin = -1; self->curno = 0; self->lbucket = 0; self->lbin = 0; self->lcurno = 0; self->nextp = NULL; self->curp = NULL; if (ht->bucketCount == 0) { self->done = PR_TRUE; self->b = NULL; } else { self->done = PR_FALSE; self->b = ht->directory[self->bucket]; PL_DynahashIteratorNext(self); /* setup nextp */ } } static void pl_DynahashIteratorNext1(PLDynahashIterator* self) { if (!self->done) { /* save last position in case we have to reset */ self->lbucket = self->bucket; self->lbin = self->bin; self->lcurno = self->curno; if (self->nextp) { self->nextp = self->nextp->next; self->curno++; } while (!self->nextp) { if (++self->bin >= PL_DYNAHASH_BUCKETSIZE) { self->bin = 0; if (++self->bucket < self->table->bucketCount) { self->b = self->table->directory[self->bucket]; } else { self->done = PR_TRUE; break; } } self->nextp = self->b[self->bin]; self->curno = 0; } } } PR_IMPLEMENT(PLDynahashElement*) PL_DynahashIteratorNext(PLDynahashIterator* self) { /* This iterator precomputes one next element ahead so that we can detect when we dropped the current element and not skip elements. */ if (self->curp != self->nextp) { self->done = PR_TRUE; } else { pl_DynahashIteratorNext1(self); } return self->curp; } PR_IMPLEMENT(PRUword) PL_DynahashIteratorCurrentAddress(PLDynahashIterator* self) { return (PRUword)(MUL(self->lbucket, PL_DYNAHASH_BUCKETSIZE) + self->lbin); } static PLDynahashElement* PL_DynahashIteratorRemoveCurrent(PLDynahashIterator* self) { PLDynahashElement* dp = PL_DynahashRemove1(self->table, self->lbucket, self->lbin, (int) self->lcurno); if (dp != NULL) { if ((self->bin == self->lbin) && (self->bucket == self->lbucket)) { self->curno = self->lcurno; } } return dp; } PR_IMPLEMENT(void) PL_DynahashIteratorReset(PLDynahashIterator* self, PLDynahash* ht) { PRInt32 i; self->table = ht; if (self->done) return; if (self->lbin == -1) { /* a single next has occurred, will emit first element twice */ self->lbin = 0; } /* recompute curp from lbucket/lbin/lcurno */ if (self->lbucket >= self->table->bucketCount) { /* we're done */ self->curp = self->nextp = 0; self->done = PR_TRUE; return; } self->b = self->table->directory[self->lbucket]; self->curp = self->b[self->lbin]; for (i = 0; i < self->lcurno && self->curp; i++) { self->curp = self->curp->next; } /* nextp also cannot be trusted */ self->bucket = self->lbucket; self->bin = self->lbin; self->curno = self->lcurno; if (self->curp == self->nextp) { /* special case where curp has been destroyed and nextp was the next one in the bin */ return; } /* curp mibht be zero, it doesn't matter */ self->nextp = self->curp; pl_DynahashIteratorNext1(self); } /******************************************************************************* * Test ******************************************************************************/ #ifdef DEBUG typedef struct MyHashElement { PLDynahashElement hashElement; /* must be first */ PRUint32 key; PRUint32 value; } MyHashElement; PRUint32 Dynahash_Test_Hash(void* context, MyHashElement* x) { #ifdef XP_MAC #pragma unused(context) #endif return x->key; } PRBool Dynahash_Test_equal(void* context, MyHashElement* x, MyHashElement* y) { #ifdef XP_MAC #pragma unused(context) #endif return (PRBool)(x->key == y->key); } void Dynahash_Test_destroy(void* context, MyHashElement* x) { #ifdef XP_MAC #pragma unused(context) #endif free(x); } #define kDynahash_Test_Iterations 1000 #include PR_IMPLEMENT(PRBool) PL_DynahashTest(void) { PRStatus err; PRUint32 i; PRBool result = PR_FALSE; PLDynahash* ht = PL_NewDynahash(14, (PLHashFun)Dynahash_Test_Hash, (PLEqualFun)Dynahash_Test_equal, (PLDestroyFun)Dynahash_Test_destroy, NULL); for (i = 0; i < kDynahash_Test_Iterations; i++) { MyHashElement* oldElem; MyHashElement* elem; printf("seeding %ld\n", i); elem = (MyHashElement*)malloc(sizeof(MyHashElement)); if (elem == NULL) goto done; elem->hashElement.next = NULL; elem->key = i; elem->value = i; err = PL_DynahashAdd(ht, (PLDynahashElement*)elem, PR_FALSE, (PLDynahashElement**)&oldElem); if (err != PR_SUCCESS) goto done; free(oldElem); } for (i = 0; i < kDynahash_Test_Iterations; i += 2) { MyHashElement elem; MyHashElement* found; PRBool wasFound = PR_FALSE; printf("deleting %ld\n", i); elem.hashElement.next = NULL; elem.key = i; found = (MyHashElement*)PL_DynahashRemove(ht, (PLDynahashElement*)&elem); if (found == NULL) goto done; wasFound = (PRBool)(found->value == i); if (!wasFound) goto done; } for (i = 0; i < kDynahash_Test_Iterations; i++) { MyHashElement elem; MyHashElement* found; printf("getting %ld\n", i); elem.hashElement.next = NULL; elem.key = i; found = (MyHashElement*)PL_DynahashLookup(ht, (PLDynahashElement*)&elem); if (found) { if (i & 1 == 1) { PRBool wasFound = (PRBool)(found->value == i); if (!wasFound) goto done; } else { goto done; } } else { if (i & 1 == 1) { goto done; } /* else ok */ } } result = PR_TRUE; done:; PL_DynahashDestroy(ht); PR_ASSERT(result); return result; } #endif /* DEBUG */ /******************************************************************************/