blob: 73782c41eb927c4dabcef113f0901eb1979c9f42 [file] [log] [blame]
/* 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.
*/