diff options
Diffstat (limited to 'src/common/hashtbl.c')
-rw-r--r-- | src/common/hashtbl.c | 377 |
1 files changed, 377 insertions, 0 deletions
diff --git a/src/common/hashtbl.c b/src/common/hashtbl.c new file mode 100644 index 0000000..3f49d83 --- /dev/null +++ b/src/common/hashtbl.c @@ -0,0 +1,377 @@ +/* + * Copyright (c) 2012, Intel Corporation + * + * 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 Intel Corporation nor the names of its 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. + */ + +#include <stdint.h> + +#include "murphy/common/mm.h" +#include "murphy/common/list.h" +#include "murphy/common/hashtbl.h" + +#define MIN_NBUCKET 8 +#define MAX_NBUCKET 128 + +typedef struct { /* a hash bucket */ + mrp_list_hook_t entries; /* hook to hash table entries */ + mrp_list_hook_t used; /* hook to list of buckets in use */ +} bucket_t; + +typedef struct { /* a hash table entry */ + mrp_list_hook_t hook; /* hook to bucket chain */ + void *key; /* key for this entry */ + void *obj; /* object for this entry */ +} entry_t; + +typedef struct { /* iterator state */ + mrp_list_hook_t *bp, *bn; /* current bucket hook pointers */ + mrp_list_hook_t *ep, *en; /* current entry hook pointers */ + entry_t *entry; /* current entry */ + int verdict; /* remove-from-cb verdict */ +} iter_t; + +struct mrp_htbl_s { + bucket_t *buckets; /* hash table buckets */ + size_t nbucket; /* this many of them */ + mrp_list_hook_t used; /* buckets in use */ + mrp_htbl_comp_fn_t comp; /* key comparison function */ + mrp_htbl_hash_fn_t hash; /* key hash function */ + mrp_htbl_free_fn_t free; /* function to free an entry */ + iter_t *iter; /* active iterator state */ +}; + + +static size_t calc_buckets(size_t nbucket) +{ + size_t n; + + if (nbucket < MIN_NBUCKET) + nbucket = MIN_NBUCKET; + if (nbucket > MAX_NBUCKET) + nbucket = MAX_NBUCKET; + + for (n = MIN_NBUCKET; n < nbucket; n <<= 1) + ; + + return n; +} + + +mrp_htbl_t *mrp_htbl_create(mrp_htbl_config_t *cfg) +{ + mrp_htbl_t *ht; + size_t i, nbucket; + + if (cfg->comp && cfg->hash) { + if ((ht = mrp_allocz(sizeof(*ht))) != NULL) { + if (cfg->nbucket != 0) + nbucket = cfg->nbucket; + else { + if (cfg->nentry != 0) + nbucket = cfg->nentry / 4; + else + nbucket = 4 * MIN_NBUCKET; + } + + ht->nbucket = calc_buckets(nbucket); + ht->comp = cfg->comp; + ht->hash = cfg->hash; + ht->free = cfg->free; + + mrp_list_init(&ht->used); + + ht->buckets = mrp_allocz(sizeof(*ht->buckets) * ht->nbucket); + if (ht->buckets != NULL) { + for (i = 0; i < ht->nbucket; i++) { + mrp_list_init(&ht->buckets[i].entries); + mrp_list_init(&ht->buckets[i].used); + } + + return ht; + } + else { + mrp_free(ht); + ht = NULL; + } + } + } + + return NULL; +} + + +void mrp_htbl_destroy(mrp_htbl_t *ht, int free) +{ + if (ht != NULL) { + if (free) + mrp_htbl_reset(ht, free); + + mrp_free(ht->buckets); + mrp_free(ht); + } +} + + +static inline void free_entry(mrp_htbl_t *ht, entry_t *entry, int free) +{ + if (free && ht->free) + ht->free(entry->key, entry->obj); + mrp_free(entry); +} + + +void mrp_htbl_reset(mrp_htbl_t *ht, int free) +{ + mrp_list_hook_t *bp, *bn, *ep, *en; + bucket_t *bucket; + entry_t *entry; + + mrp_list_foreach(&ht->used, bp, bn) { + bucket = mrp_list_entry(bp, bucket_t, used); + + mrp_list_foreach(&bucket->entries, ep, en) { + entry = mrp_list_entry(ep, entry_t, hook); + mrp_list_delete(ep); + free_entry(ht, entry, free); + } + + mrp_list_delete(&bucket->used); + } +} + + +int mrp_htbl_insert(mrp_htbl_t *ht, void *key, void *object) +{ + uint32_t idx = ht->hash(key) & (ht->nbucket - 1); + bucket_t *bucket = ht->buckets + idx; + int first = mrp_list_empty(&bucket->entries); + entry_t *entry; + + if ((entry = mrp_allocz(sizeof(*entry))) != NULL) { + entry->key = key; + entry->obj = object; + mrp_list_append(&bucket->entries, &entry->hook); + if (first) + mrp_list_append(&ht->used, &bucket->used); + + return TRUE; + } + else + return FALSE; +} + + +static inline entry_t *lookup(mrp_htbl_t *ht, void *key, bucket_t **bucketp) +{ + uint32_t idx = ht->hash(key) & (ht->nbucket - 1); + bucket_t *bucket = ht->buckets + idx; + mrp_list_hook_t *p, *n; + entry_t *entry; + + mrp_list_foreach(&bucket->entries, p, n) { + entry = mrp_list_entry(p, entry_t, hook); + + if (!ht->comp(entry->key, key)) { + if (bucketp != NULL) + *bucketp = bucket; + return entry; + } + } + + return NULL; +} + + +void *mrp_htbl_lookup(mrp_htbl_t *ht, void *key) +{ + entry_t *entry; + + entry = lookup(ht, key, NULL); + if (entry != NULL) + return entry->obj; + else + return NULL; +} + + +static void delete_from_bucket(mrp_htbl_t *ht, bucket_t *bucket, entry_t *entry) +{ + mrp_list_hook_t *eh = &entry->hook; + + + /* + * If there is an iterator active and this entry would + * have been the next one to iterate over, we need to + * update the iterator to skip to the next entry instead + * as this one will be removed. Failing to update the + * iterator could crash mrp_htbl_foreach or drive it into + * an infinite loop. + */ + + if (ht->iter != NULL && ht->iter->en == eh) + ht->iter->en = eh->next; + + mrp_list_delete(eh); + + + /* + * If the bucket became empty, unlink it from the used list. + * If also there is an iterator active and this bucket would + * have been the next one to iterate over, we need to + * update the iterator to skip to the next bucket instead + * as this one just became empty and will be removed from + * the used bucket list. Failing to update the iterator + * could drive mrp_htbl_foreach into an infinite loop + * because of the unexpected hop from the used bucket list + * (to a single empty bucket). + */ + + if (mrp_list_empty(&bucket->entries)) { + if (ht->iter != NULL && ht->iter->bn == &bucket->used) + ht->iter->bn = bucket->used.next; + + mrp_list_delete(&bucket->used); + } +} + + +void *mrp_htbl_remove(mrp_htbl_t *ht, void *key, int free) +{ + bucket_t *bucket; + entry_t *entry; + void *object; + + /* + * We need to check the found entry and its hash-bucket + * against any potentially active iterator. Special care + * needs to be taken if the entry is being iterated over + * or if the bucket becomes empty and it would be the next + * bucket to iterate over. The former is taken care of + * here while the latter is handled in delete_from_bucket. + */ + if ((entry = lookup(ht, key, &bucket)) != NULL) { + delete_from_bucket(ht, bucket, entry); + object = entry->obj; + + if (ht->iter != NULL && entry == ht->iter->entry) /* being iterated */ + ht->iter->verdict = free ? MRP_HTBL_ITER_DELETE : 0; + else { + free_entry(ht, entry, free); + } + } + else + object = NULL; + + return object; +} + + +int mrp_htbl_foreach(mrp_htbl_t *ht, mrp_htbl_iter_cb_t cb, void *user_data) +{ + iter_t iter; + bucket_t *bucket; + entry_t *entry; + int cb_verdict, ht_verdict; + + /* + * Now we can only handle a single callback-based iterator. + * If there is already one we're busy so just bail out. + */ + if (ht->iter != NULL) + return FALSE; + + mrp_clear(&iter); + ht->iter = &iter; + + mrp_list_foreach(&ht->used, iter.bp, iter.bn) { + bucket = mrp_list_entry(iter.bp, bucket_t, used); + + mrp_list_foreach(&bucket->entries, iter.ep, iter.en) { + iter.entry = entry = mrp_list_entry(iter.ep, entry_t, hook); + cb_verdict = cb(entry->key, entry->obj, user_data); + ht_verdict = iter.verdict; + + /* delete was called from cb (unhashed entry and marked it) */ + if (ht_verdict & MRP_HTBL_ITER_DELETE) { + free_entry(ht, entry, TRUE); + } + else { + /* cb wants us to unhash (safe even if unhashed in remove) */ + if (cb_verdict & MRP_HTBL_ITER_UNHASH) + mrp_list_delete(iter.ep); + /* cb want us to free entry (and remove was not called) */ + if (cb_verdict & MRP_HTBL_ITER_DELETE) + free_entry(ht, entry, TRUE); + + /* cb wants to stop iterating */ + if (!(cb_verdict & MRP_HTBL_ITER_MORE)) + goto out; + } + } + } + + out: + ht->iter = NULL; + + return TRUE; +} + + +void *mrp_htbl_find(mrp_htbl_t *ht, mrp_htbl_find_cb_t cb, void *user_data) +{ + iter_t iter; + bucket_t *bucket; + entry_t *entry, *found; + + /* + * Bail out if there is also an iterator active... + */ + if (ht->iter != NULL) + return FALSE; + + mrp_clear(&iter); + ht->iter = &iter; + found = NULL; + + mrp_list_foreach(&ht->used, iter.bp, iter.bn) { + bucket = mrp_list_entry(iter.bp, bucket_t, used); + + mrp_list_foreach(&bucket->entries, iter.ep, iter.en) { + entry = mrp_list_entry(iter.ep, entry_t, hook); + + if (cb(entry->key, entry->obj, user_data)) { + found = entry->obj; + goto out; + } + } + } + + out: + ht->iter = NULL; + + return found; +} |