| /* Copyright (C) 2002, 2004 Christopher Clark <firstname.lastname@cl.cam.ac.uk> |
| * |
| * Modified 2009 by Barret Rhoden <brho@cs.berkeley.edu> |
| * Changes include: |
| * - No longer frees keys or values. It's up to the client to do that. |
| * - Uses the slab allocator for hash entry allocation. |
| * - Merges the iterator code with the main hash table code, mostly to avoid |
| * externing the hentry cache. */ |
| |
| #include <arch/types.h> |
| #include <assert.h> |
| #include <hash.h> |
| #include <hashtable.h> |
| #include <kmalloc.h> |
| #include <ros/common.h> |
| #include <slab.h> |
| #include <stdio.h> |
| #include <string.h> |
| |
| /* |
| Credit for primes table: Aaron Krowne |
| http://br.endernet.org/~akrowne/ |
| http://planetmath.org/encyclopedia/GoodHashTablePrimes.html |
| */ |
| static const unsigned int primes[] = { |
| 53, 97, 193, 389, 769, 1543, 3079, |
| 6151, 12289, 24593, 49157, 98317, 196613, 393241, |
| 786433, 1572869, 3145739, 6291469, 12582917, 25165843, 50331653, |
| 100663319, 201326611, 402653189, 805306457, 1610612741}; |
| const unsigned int prime_table_length = sizeof(primes) / sizeof(primes[0]); |
| #define APPLY_MAX_LOAD_FACTOR(size) ((size * 13) / 20) |
| // const float max_load_factor = 0.65; |
| |
| struct kmem_cache *hentry_cache; |
| |
| /* Call this once on bootup, after initializing the slab allocator. */ |
| void hashtable_init(void) |
| { |
| hentry_cache = kmem_cache_create("hash_entry", |
| sizeof(struct hash_entry), |
| __alignof__(struct hash_entry), 0, |
| NULL, 0, 0, NULL); |
| } |
| |
| /* Common hash/equals functions. Don't call these directly. */ |
| size_t __generic_hash(void *k) |
| { |
| return hash_long((unsigned long)k, BITS_PER_LONG); |
| } |
| |
| ssize_t __generic_eq(void *k1, void *k2) |
| { |
| return k1 == k2; |
| } |
| |
| /*****************************************************************************/ |
| hashtable_t *create_hashtable(size_t minsize, size_t (*hashf)(void *), |
| ssize_t (*eqf)(void *, void *)) |
| { |
| hashtable_t *h; |
| size_t pindex, size = primes[0]; |
| |
| /* Check requested hashtable isn't too large */ |
| if (minsize > (1u << 30)) |
| return NULL; |
| /* Enforce size as prime */ |
| for (pindex = 0; pindex < prime_table_length; pindex++) { |
| if (primes[pindex] > minsize) { |
| size = primes[pindex]; |
| break; |
| } |
| } |
| h = (hashtable_t *)kmalloc(sizeof(hashtable_t), 0); |
| if (NULL == h) |
| return NULL; /*oom*/ |
| h->table = (hash_entry_t **)kmalloc(sizeof(hash_entry_t *) * size, 0); |
| if (NULL == h->table) { |
| kfree(h); |
| return NULL; |
| } /*oom*/ |
| memset(h->table, 0, size * sizeof(hash_entry_t *)); |
| h->tablelength = size; |
| h->primeindex = pindex; |
| h->entrycount = 0; |
| h->hashfn = hashf; |
| h->eqfn = eqf; |
| h->loadlimit = APPLY_MAX_LOAD_FACTOR(size); |
| return h; |
| } |
| |
| /*****************************************************************************/ |
| static size_t hash(hashtable_t *h, void *k) |
| { |
| /* Aim to protect against poor hash functions by adding logic here |
| * - logic taken from java 1.4 hashtable source */ |
| size_t i = h->hashfn(k); |
| |
| i += ~(i << 9); |
| i ^= ((i >> 14) | (i << 18)); /* >>> */ |
| i += (i << 4); |
| i ^= ((i >> 10) | (i << 22)); /* >>> */ |
| return i; |
| } |
| |
| /*****************************************************************************/ |
| static ssize_t hashtable_expand(hashtable_t *h) |
| { |
| /* Double the size of the table to accomodate more entries */ |
| hash_entry_t **newtable; |
| hash_entry_t *e; |
| hash_entry_t **pE; |
| size_t newsize, i, index; |
| |
| /* Check we're not hitting max capacity */ |
| if (h->primeindex == (prime_table_length - 1)) |
| return 0; |
| newsize = primes[++(h->primeindex)]; |
| |
| newtable = |
| (hash_entry_t **)kmalloc(sizeof(hash_entry_t *) * newsize, 0); |
| if (NULL != newtable) { |
| memset(newtable, 0, newsize * sizeof(hash_entry_t *)); |
| /* This algorithm is not 'stable'. ie. it reverses the list |
| * when it transfers entries between the tables */ |
| for (i = 0; i < h->tablelength; i++) { |
| while (NULL != (e = h->table[i])) { |
| h->table[i] = e->next; |
| index = indexFor(newsize, e->h); |
| e->next = newtable[index]; |
| newtable[index] = e; |
| } |
| } |
| kfree(h->table); |
| h->table = newtable; |
| } |
| /* Plan B: realloc instead */ |
| else { |
| newtable = (hash_entry_t **)krealloc( |
| h->table, newsize * sizeof(hash_entry_t *), 0); |
| if (NULL == newtable) { |
| (h->primeindex)--; |
| return 0; |
| } |
| h->table = newtable; |
| memset(newtable[h->tablelength], 0, newsize - h->tablelength); |
| for (i = 0; i < h->tablelength; i++) { |
| for (pE = &(newtable[i]), e = *pE; e != NULL; e = *pE) { |
| index = indexFor(newsize, e->h); |
| if (index == i) { |
| pE = &(e->next); |
| } else { |
| *pE = e->next; |
| e->next = newtable[index]; |
| newtable[index] = e; |
| } |
| } |
| } |
| } |
| h->tablelength = newsize; |
| h->loadlimit = APPLY_MAX_LOAD_FACTOR(newsize); |
| return -1; |
| } |
| |
| /*****************************************************************************/ |
| size_t hashtable_count(hashtable_t *h) |
| { |
| return h->entrycount; |
| } |
| |
| /*****************************************************************************/ |
| ssize_t hashtable_insert(hashtable_t *h, void *k, void *v) |
| { |
| /* This method allows duplicate keys - but they shouldn't be used */ |
| size_t index; |
| hash_entry_t *e; |
| |
| if (++(h->entrycount) > h->loadlimit) { |
| /* Ignore the return value. If expand fails, we should |
| * still try cramming just this value into the existing table |
| * -- we may not have memory for a larger table, but one more |
| * element may be ok. Next time we insert, we'll try expanding |
| * again.*/ |
| hashtable_expand(h); |
| } |
| e = (hash_entry_t *)kmem_cache_alloc(hentry_cache, 0); |
| if (NULL == e) { |
| --(h->entrycount); |
| return 0; |
| } /*oom*/ |
| e->h = hash(h, k); |
| index = indexFor(h->tablelength, e->h); |
| e->k = k; |
| e->v = v; |
| e->next = h->table[index]; |
| h->table[index] = e; |
| return -1; |
| } |
| |
| /*****************************************************************************/ |
| /* returns value associated with key */ |
| void *hashtable_search(hashtable_t *h, void *k) |
| { |
| hash_entry_t *e; |
| size_t hashvalue, index; |
| |
| hashvalue = hash(h, k); |
| index = indexFor(h->tablelength, hashvalue); |
| e = h->table[index]; |
| while (NULL != e) { |
| /* Check hash value to short circuit heavier comparison */ |
| if ((hashvalue == e->h) && (h->eqfn(k, e->k))) |
| return e->v; |
| e = e->next; |
| } |
| return NULL; |
| } |
| |
| /*****************************************************************************/ |
| /* returns value associated with key */ |
| void *hashtable_remove(hashtable_t *h, void *k) |
| { |
| /* TODO: consider compacting the table when the load factor drops |
| * enough, or provide a 'compact' method. */ |
| |
| hash_entry_t *e; |
| hash_entry_t **pE; |
| void *v; |
| size_t hashvalue, index; |
| |
| hashvalue = hash(h, k); |
| index = indexFor(h->tablelength, hash(h, k)); |
| pE = &(h->table[index]); |
| e = *pE; |
| while (NULL != e) { |
| /* Check hash value to short circuit heavier comparison */ |
| if ((hashvalue == e->h) && (h->eqfn(k, e->k))) { |
| *pE = e->next; |
| h->entrycount--; |
| v = e->v; |
| kmem_cache_free(hentry_cache, e); |
| return v; |
| } |
| pE = &(e->next); |
| e = e->next; |
| } |
| return NULL; |
| } |
| |
| /*****************************************************************************/ |
| /* destroy */ |
| void hashtable_destroy(hashtable_t *h) |
| { |
| if (hashtable_count(h)) |
| warn("Destroying a non-empty hashtable, clean up after " |
| "yourself!\n"); |
| |
| size_t i; |
| hash_entry_t *e, *f; |
| hash_entry_t **table = h->table; |
| |
| for (i = 0; i < h->tablelength; i++) { |
| e = table[i]; |
| while (NULL != e) { |
| f = e; |
| e = e->next; |
| kmem_cache_free(hentry_cache, f); |
| } |
| } |
| kfree(h->table); |
| kfree(h); |
| } |
| |
| /*****************************************************************************/ |
| /* hashtable_iterator - iterator constructor, be sure to kfree this when |
| * you're done. |
| * |
| * If the htable isn't empty, e and index will refer to the first entry. */ |
| |
| hashtable_itr_t *hashtable_iterator(hashtable_t *h) |
| { |
| size_t i, tablelength; |
| hashtable_itr_t *itr = |
| (hashtable_itr_t *)kmalloc(sizeof(hashtable_itr_t), 0); |
| |
| if (NULL == itr) |
| return NULL; |
| itr->h = h; |
| itr->e = NULL; |
| itr->parent = NULL; |
| tablelength = h->tablelength; |
| itr->index = tablelength; |
| if (0 == h->entrycount) |
| return itr; |
| |
| for (i = 0; i < tablelength; i++) { |
| if (NULL != h->table[i]) { |
| itr->e = h->table[i]; |
| itr->index = i; |
| break; |
| } |
| } |
| return itr; |
| } |
| |
| /*****************************************************************************/ |
| /* key - return the key of the (key,value) pair at the current position */ |
| /* value - return the value of the (key,value) pair at the current position |
| */ |
| |
| void *hashtable_iterator_key(hashtable_itr_t *i) |
| { |
| return i->e->k; |
| } |
| |
| void *hashtable_iterator_value(hashtable_itr_t *i) |
| { |
| return i->e->v; |
| } |
| |
| /*****************************************************************************/ |
| /* advance - advance the iterator to the next element |
| * returns zero if advanced to end of table */ |
| |
| ssize_t hashtable_iterator_advance(hashtable_itr_t *itr) |
| { |
| size_t j, tablelength; |
| hash_entry_t **table; |
| hash_entry_t *next; |
| |
| if (NULL == itr->e) |
| return 0; /* stupidity check */ |
| |
| next = itr->e->next; |
| if (NULL != next) { |
| itr->parent = itr->e; |
| itr->e = next; |
| return -1; |
| } |
| tablelength = itr->h->tablelength; |
| itr->parent = NULL; |
| if (tablelength <= (j = ++(itr->index))) { |
| itr->e = NULL; |
| return 0; |
| } |
| table = itr->h->table; |
| while (NULL == (next = table[j])) { |
| if (++j >= tablelength) { |
| itr->index = tablelength; |
| itr->e = NULL; |
| return 0; |
| } |
| } |
| itr->index = j; |
| itr->e = next; |
| return -1; |
| } |
| |
| /*****************************************************************************/ |
| /* remove - remove the entry at the current iterator position |
| * and advance the iterator, if there is a successive |
| * element. |
| * If you want the value, read it before you remove: |
| * beware memory leaks if you don't. |
| * Returns zero if end of iteration. */ |
| |
| ssize_t hashtable_iterator_remove(hashtable_itr_t *itr) |
| { |
| hash_entry_t *remember_e, *remember_parent; |
| ssize_t ret; |
| |
| /* Do the removal */ |
| if (NULL == (itr->parent)) { |
| /* element is head of a chain */ |
| itr->h->table[itr->index] = itr->e->next; |
| } else { |
| /* element is mid-chain */ |
| itr->parent->next = itr->e->next; |
| } |
| /* itr->e is now outside the hashtable */ |
| remember_e = itr->e; |
| itr->h->entrycount--; |
| |
| /* Advance the iterator, correcting the parent */ |
| remember_parent = itr->parent; |
| ret = hashtable_iterator_advance(itr); |
| if (itr->parent == remember_e) { |
| itr->parent = remember_parent; |
| } |
| kmem_cache_free(hentry_cache, remember_e); |
| return ret; |
| } |
| |
| /*****************************************************************************/ |
| /* returns zero if not found */ |
| ssize_t hashtable_iterator_search(hashtable_itr_t *itr, hashtable_t *h, void *k) |
| { |
| hash_entry_t *e, *parent; |
| size_t hashvalue, index; |
| |
| hashvalue = hash(h, k); |
| index = indexFor(h->tablelength, hashvalue); |
| |
| e = h->table[index]; |
| parent = NULL; |
| while (NULL != e) { |
| /* Check hash value to short circuit heavier comparison */ |
| if ((hashvalue == e->h) && (h->eqfn(k, e->k))) { |
| itr->index = index; |
| itr->e = e; |
| itr->parent = parent; |
| itr->h = h; |
| return -1; |
| } |
| parent = e; |
| e = e->next; |
| } |
| return 0; |
| } |
| |
| /* Runs func on each member of the hash table */ |
| void hash_for_each(struct hashtable *hash, void func(void *, void *), |
| void *opaque) |
| { |
| if (hashtable_count(hash)) { |
| struct hashtable_itr *iter = hashtable_iterator(hash); |
| do { |
| void *item = hashtable_iterator_value(iter); |
| func(item, opaque); |
| } while (hashtable_iterator_advance(iter)); |
| kfree(iter); |
| } |
| } |
| |
| /* Runs func on each member of the hash table, removing the item after |
| * processing it. Make sure func frees the item, o/w you'll leak. */ |
| void hash_for_each_remove(struct hashtable *hash, void func(void *, void *), |
| void *opaque) |
| { |
| if (hashtable_count(hash)) { |
| struct hashtable_itr *iter = hashtable_iterator(hash); |
| do { |
| void *item = hashtable_iterator_value(iter); |
| func(item, opaque); |
| } while (hashtable_iterator_remove(iter)); |
| kfree(iter); |
| } |
| } |
| |
| /* |
| * Copyright (c) 2002, 2004, Christopher Clark |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * * Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * * Neither the name of the original author; nor the names of any contributors |
| * may be used to endorse or promote products derived from this software |
| * without specific prior written permission. |
| * |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER |
| * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
| * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
| * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |