/* -*- coding: utf-8 -*- * ---------------------------------------------------------------------- * Copyright © 2011-2014, RedJack, LLC. * All rights reserved. * * Please see the COPYING file in this distribution for license details. * ---------------------------------------------------------------------- */ #include #include #include "libcork/core/callbacks.h" #include "libcork/core/hash.h" #include "libcork/core/types.h" #include "libcork/ds/dllist.h" #include "libcork/ds/hash-table.h" #include "libcork/helpers/errors.h" #ifndef CORK_HASH_TABLE_DEBUG #define CORK_HASH_TABLE_DEBUG 0 #endif #if CORK_HASH_TABLE_DEBUG #include #define DEBUG(...) \ do { \ fprintf(stderr, __VA_ARGS__); \ fprintf(stderr, "\n"); \ } while (0) #else #define DEBUG(...) /* nothing */ #endif /*----------------------------------------------------------------------- * Hash tables */ struct cork_hash_table_entry_priv { struct cork_hash_table_entry public; struct cork_dllist_item in_bucket; struct cork_dllist_item insertion_order; }; struct cork_hash_table { struct cork_dllist *bins; struct cork_dllist insertion_order; size_t bin_count; size_t bin_mask; size_t entry_count; void *user_data; cork_free_f free_user_data; cork_hash_f hash; cork_equals_f equals; cork_free_f free_key; cork_free_f free_value; }; static cork_hash cork_hash_table__default_hash(void *user_data, const void *key) { return (cork_hash) (uintptr_t) key; } static bool cork_hash_table__default_equals(void *user_data, const void *key1, const void *key2) { return key1 == key2; } /* The default initial number of bins to allocate in a new table. */ #define CORK_HASH_TABLE_DEFAULT_INITIAL_SIZE 8 /* The default number of entries per bin to allow before increasing the * number of bins. */ #define CORK_HASH_TABLE_MAX_DENSITY 5 /* Return a power-of-2 bin count that's at least as big as the given requested * size. */ static inline size_t cork_hash_table_new_size(size_t desired_count) { size_t v = desired_count; size_t r = 1; while (v >>= 1) { r <<= 1; } if (r != desired_count) { r <<= 1; } return r; } #define bin_index(table, hash) ((hash) & (table)->bin_mask) /* Allocates a new bins array in a hash table. We overwrite the old * array, so make sure to stash it away somewhere safe first. */ static void cork_hash_table_allocate_bins(struct cork_hash_table *table, size_t desired_count) { size_t i; table->bin_count = cork_hash_table_new_size(desired_count); table->bin_mask = table->bin_count - 1; DEBUG("Allocate %zu bins", table->bin_count); table->bins = cork_calloc(table->bin_count, sizeof(struct cork_dllist)); for (i = 0; i < table->bin_count; i++) { cork_dllist_init(&table->bins[i]); } } static struct cork_hash_table_entry_priv * cork_hash_table_new_entry(struct cork_hash_table *table, cork_hash hash, void *key, void *value) { struct cork_hash_table_entry_priv *entry = cork_new(struct cork_hash_table_entry_priv); cork_dllist_add(&table->insertion_order, &entry->insertion_order); entry->public.hash = hash; entry->public.key = key; entry->public.value = value; return entry; } static void cork_hash_table_free_entry(struct cork_hash_table *table, struct cork_hash_table_entry_priv *entry) { if (table->free_key != NULL) { table->free_key(entry->public.key); } if (table->free_value != NULL) { table->free_value(entry->public.value); } cork_dllist_remove(&entry->insertion_order); cork_delete(struct cork_hash_table_entry_priv, entry); } struct cork_hash_table * cork_hash_table_new(size_t initial_size, unsigned int flags) { struct cork_hash_table *table = cork_new(struct cork_hash_table); table->entry_count = 0; table->user_data = NULL; table->free_user_data = NULL; table->hash = cork_hash_table__default_hash; table->equals = cork_hash_table__default_equals; table->free_key = NULL; table->free_value = NULL; cork_dllist_init(&table->insertion_order); if (initial_size < CORK_HASH_TABLE_DEFAULT_INITIAL_SIZE) { initial_size = CORK_HASH_TABLE_DEFAULT_INITIAL_SIZE; } cork_hash_table_allocate_bins(table, initial_size); return table; } void cork_hash_table_clear(struct cork_hash_table *table) { size_t i; struct cork_dllist_item *curr; struct cork_dllist_item *next; DEBUG("(clear) Remove all entries"); for (curr = cork_dllist_start(&table->insertion_order); !cork_dllist_is_end(&table->insertion_order, curr); curr = next) { struct cork_hash_table_entry_priv *entry = cork_container_of (curr, struct cork_hash_table_entry_priv, insertion_order); next = curr->next; cork_hash_table_free_entry(table, entry); } cork_dllist_init(&table->insertion_order); DEBUG("(clear) Clear bins"); for (i = 0; i < table->bin_count; i++) { DEBUG(" Bin %zu", i); cork_dllist_init(&table->bins[i]); } table->entry_count = 0; } void cork_hash_table_free(struct cork_hash_table *table) { cork_hash_table_clear(table); cork_cfree(table->bins, table->bin_count, sizeof(struct cork_dllist)); cork_delete(struct cork_hash_table, table); } size_t cork_hash_table_size(const struct cork_hash_table *table) { return table->entry_count; } void cork_hash_table_set_user_data(struct cork_hash_table *table, void *user_data, cork_free_f free_user_data) { table->user_data = user_data; table->free_user_data = free_user_data; } void cork_hash_table_set_hash(struct cork_hash_table *table, cork_hash_f hash) { table->hash = hash; } void cork_hash_table_set_equals(struct cork_hash_table *table, cork_equals_f equals) { table->equals = equals; } void cork_hash_table_set_free_key(struct cork_hash_table *table, cork_free_f free) { table->free_key = free; } void cork_hash_table_set_free_value(struct cork_hash_table *table, cork_free_f free) { table->free_value = free; } void cork_hash_table_ensure_size(struct cork_hash_table *table, size_t desired_count) { if (desired_count > table->bin_count) { struct cork_dllist *old_bins = table->bins; size_t old_bin_count = table->bin_count; cork_hash_table_allocate_bins(table, desired_count); if (old_bins != NULL) { size_t i; for (i = 0; i < old_bin_count; i++) { struct cork_dllist *bin = &old_bins[i]; struct cork_dllist_item *curr = cork_dllist_start(bin); while (!cork_dllist_is_end(bin, curr)) { struct cork_hash_table_entry_priv *entry = cork_container_of (curr, struct cork_hash_table_entry_priv, in_bucket); struct cork_dllist_item *next = curr->next; size_t bin_index = bin_index(table, entry->public.hash); DEBUG(" Rehash %p from bin %zu to bin %zu", entry, i, bin_index); cork_dllist_add(&table->bins[bin_index], curr); curr = next; } } cork_cfree(old_bins, old_bin_count, sizeof(struct cork_dllist)); } } } static void cork_hash_table_rehash(struct cork_hash_table *table) { DEBUG(" Reached maximum density; rehash"); cork_hash_table_ensure_size(table, table->bin_count + 1); } struct cork_hash_table_entry * cork_hash_table_get_entry_hash(const struct cork_hash_table *table, cork_hash hash, const void *key) { size_t bin_index; struct cork_dllist *bin; struct cork_dllist_item *curr; if (table->bin_count == 0) { DEBUG("(get) Empty table when searching for key %p " "(hash 0x%08" PRIx32 ")", key, hash); return NULL; } bin_index = bin_index(table, hash); DEBUG("(get) Search for key %p (hash 0x%08" PRIx32 ", bin %zu)", key, hash, bin_index); bin = &table->bins[bin_index]; curr = cork_dllist_start(bin); while (!cork_dllist_is_end(bin, curr)) { struct cork_hash_table_entry_priv *entry = cork_container_of (curr, struct cork_hash_table_entry_priv, in_bucket); DEBUG(" Check entry %p", entry); if (table->equals(table->user_data, key, entry->public.key)) { DEBUG(" Match"); return &entry->public; } curr = curr->next; } DEBUG(" Entry not found"); return NULL; } struct cork_hash_table_entry * cork_hash_table_get_entry(const struct cork_hash_table *table, const void *key) { cork_hash hash = table->hash(table->user_data, key); return cork_hash_table_get_entry_hash(table, hash, key); } void * cork_hash_table_get_hash(const struct cork_hash_table *table, cork_hash hash, const void *key) { struct cork_hash_table_entry *entry = cork_hash_table_get_entry_hash(table, hash, key); if (entry == NULL) { return NULL; } else { DEBUG(" Extract value pointer %p", entry->value); return entry->value; } } void * cork_hash_table_get(const struct cork_hash_table *table, const void *key) { struct cork_hash_table_entry *entry = cork_hash_table_get_entry(table, key); if (entry == NULL) { return NULL; } else { DEBUG(" Extract value pointer %p", entry->value); return entry->value; } } struct cork_hash_table_entry * cork_hash_table_get_or_create_hash(struct cork_hash_table *table, cork_hash hash, void *key, bool *is_new) { struct cork_hash_table_entry_priv *entry; size_t bin_index; if (table->bin_count > 0) { struct cork_dllist *bin; struct cork_dllist_item *curr; bin_index = bin_index(table, hash); DEBUG("(get_or_create) Search for key %p " "(hash 0x%08" PRIx32 ", bin %zu)", key, hash, bin_index); bin = &table->bins[bin_index]; curr = cork_dllist_start(bin); while (!cork_dllist_is_end(bin, curr)) { struct cork_hash_table_entry_priv *entry = cork_container_of (curr, struct cork_hash_table_entry_priv, in_bucket); DEBUG(" Check entry %p", entry); if (table->equals(table->user_data, key, entry->public.key)) { DEBUG(" Match"); DEBUG(" Return value pointer %p", entry->public.value); *is_new = false; return &entry->public; } curr = curr->next; } /* create a new entry */ DEBUG(" Entry not found"); if ((table->entry_count / table->bin_count) > CORK_HASH_TABLE_MAX_DENSITY) { cork_hash_table_rehash(table); bin_index = bin_index(table, hash); } } else { DEBUG("(get_or_create) Search for key %p (hash 0x%08" PRIx32 ")", key, hash); DEBUG(" Empty table"); cork_hash_table_rehash(table); bin_index = bin_index(table, hash); } DEBUG(" Allocate new entry"); entry = cork_hash_table_new_entry(table, hash, key, NULL); DEBUG(" Created new entry %p", entry); DEBUG(" Add entry into bin %zu", bin_index); cork_dllist_add(&table->bins[bin_index], &entry->in_bucket); table->entry_count++; *is_new = true; return &entry->public; } struct cork_hash_table_entry * cork_hash_table_get_or_create(struct cork_hash_table *table, void *key, bool *is_new) { cork_hash hash = table->hash(table->user_data, key); return cork_hash_table_get_or_create_hash(table, hash, key, is_new); } void cork_hash_table_put_hash(struct cork_hash_table *table, cork_hash hash, void *key, void *value, bool *is_new, void **old_key, void **old_value) { struct cork_hash_table_entry_priv *entry; size_t bin_index; if (table->bin_count > 0) { struct cork_dllist *bin; struct cork_dllist_item *curr; bin_index = bin_index(table, hash); DEBUG("(put) Search for key %p (hash 0x%08" PRIx32 ", bin %zu)", key, hash, bin_index); bin = &table->bins[bin_index]; curr = cork_dllist_start(bin); while (!cork_dllist_is_end(bin, curr)) { struct cork_hash_table_entry_priv *entry = cork_container_of (curr, struct cork_hash_table_entry_priv, in_bucket); DEBUG(" Check entry %p", entry); if (table->equals(table->user_data, key, entry->public.key)) { DEBUG(" Found existing entry; overwriting"); DEBUG(" Return old key %p", entry->public.key); if (old_key != NULL) { *old_key = entry->public.key; } DEBUG(" Return old value %p", entry->public.value); if (old_value != NULL) { *old_value = entry->public.value; } DEBUG(" Copy key %p into entry", key); entry->public.key = key; DEBUG(" Copy value %p into entry", value); entry->public.value = value; if (is_new != NULL) { *is_new = false; } return; } curr = curr->next; } /* create a new entry */ DEBUG(" Entry not found"); if ((table->entry_count / table->bin_count) > CORK_HASH_TABLE_MAX_DENSITY) { cork_hash_table_rehash(table); bin_index = bin_index(table, hash); } } else { DEBUG("(put) Search for key %p (hash 0x%08" PRIx32 ")", key, hash); DEBUG(" Empty table"); cork_hash_table_rehash(table); bin_index = bin_index(table, hash); } DEBUG(" Allocate new entry"); entry = cork_hash_table_new_entry(table, hash, key, value); DEBUG(" Created new entry %p", entry); DEBUG(" Add entry into bin %zu", bin_index); cork_dllist_add(&table->bins[bin_index], &entry->in_bucket); table->entry_count++; if (old_key != NULL) { *old_key = NULL; } if (old_value != NULL) { *old_value = NULL; } if (is_new != NULL) { *is_new = true; } } void cork_hash_table_put(struct cork_hash_table *table, void *key, void *value, bool *is_new, void **old_key, void **old_value) { cork_hash hash = table->hash(table->user_data, key); cork_hash_table_put_hash (table, hash, key, value, is_new, old_key, old_value); } void cork_hash_table_delete_entry(struct cork_hash_table *table, struct cork_hash_table_entry *ventry) { struct cork_hash_table_entry_priv *entry = cork_container_of(ventry, struct cork_hash_table_entry_priv, public); cork_dllist_remove(&entry->in_bucket); table->entry_count--; cork_hash_table_free_entry(table, entry); } bool cork_hash_table_delete_hash(struct cork_hash_table *table, cork_hash hash, const void *key, void **deleted_key, void **deleted_value) { size_t bin_index; struct cork_dllist *bin; struct cork_dllist_item *curr; if (table->bin_count == 0) { DEBUG("(delete) Empty table when searching for key %p " "(hash 0x%08" PRIx32 ")", key, hash); return false; } bin_index = bin_index(table, hash); DEBUG("(delete) Search for key %p (hash 0x%08" PRIx32 ", bin %zu)", key, hash, bin_index); bin = &table->bins[bin_index]; curr = cork_dllist_start(bin); while (!cork_dllist_is_end(bin, curr)) { struct cork_hash_table_entry_priv *entry = cork_container_of (curr, struct cork_hash_table_entry_priv, in_bucket); DEBUG(" Check entry %p", entry); if (table->equals(table->user_data, key, entry->public.key)) { DEBUG(" Match"); if (deleted_key != NULL) { *deleted_key = entry->public.key; } if (deleted_value != NULL) { *deleted_value = entry->public.value; } DEBUG(" Remove entry from hash bin %zu", bin_index); cork_dllist_remove(curr); table->entry_count--; DEBUG(" Free entry %p", entry); cork_hash_table_free_entry(table, entry); return true; } curr = curr->next; } DEBUG(" Entry not found"); return false; } bool cork_hash_table_delete(struct cork_hash_table *table, const void *key, void **deleted_key, void **deleted_value) { cork_hash hash = table->hash(table->user_data, key); return cork_hash_table_delete_hash (table, hash, key, deleted_key, deleted_value); } void cork_hash_table_map(struct cork_hash_table *table, void *user_data, cork_hash_table_map_f map) { struct cork_dllist_item *curr; DEBUG("Map across hash table"); curr = cork_dllist_start(&table->insertion_order); while (!cork_dllist_is_end(&table->insertion_order, curr)) { struct cork_hash_table_entry_priv *entry = cork_container_of (curr, struct cork_hash_table_entry_priv, insertion_order); struct cork_dllist_item *next = curr->next; enum cork_hash_table_map_result result; DEBUG(" Apply function to entry %p", entry); result = map(user_data, &entry->public); if (result == CORK_HASH_TABLE_MAP_ABORT) { return; } else if (result == CORK_HASH_TABLE_MAP_DELETE) { DEBUG(" Delete requested"); cork_dllist_remove(curr); cork_dllist_remove(&entry->in_bucket); table->entry_count--; cork_hash_table_free_entry(table, entry); } curr = next; } } void cork_hash_table_iterator_init(struct cork_hash_table *table, struct cork_hash_table_iterator *iterator) { DEBUG("Iterate through hash table"); iterator->table = table; iterator->priv = cork_dllist_start(&table->insertion_order); } struct cork_hash_table_entry * cork_hash_table_iterator_next(struct cork_hash_table_iterator *iterator) { struct cork_hash_table *table = iterator->table; struct cork_dllist_item *curr = iterator->priv; struct cork_hash_table_entry_priv *entry; if (cork_dllist_is_end(&table->insertion_order, curr)) { return NULL; } entry = cork_container_of (curr, struct cork_hash_table_entry_priv, insertion_order); DEBUG(" Return entry %p", entry); iterator->priv = curr->next; return &entry->public; } /*----------------------------------------------------------------------- * Built-in key types */ static cork_hash string_hash(void *user_data, const void *vk) { const char *k = vk; size_t len = strlen(k); return cork_hash_buffer(0, k, len); } static bool string_equals(void *user_data, const void *vk1, const void *vk2) { const char *k1 = vk1; const char *k2 = vk2; return strcmp(k1, k2) == 0; } struct cork_hash_table * cork_string_hash_table_new(size_t initial_size, unsigned int flags) { struct cork_hash_table *table = cork_hash_table_new(initial_size, flags); cork_hash_table_set_hash(table, string_hash); cork_hash_table_set_equals(table, string_equals); return table; } struct cork_hash_table * cork_pointer_hash_table_new(size_t initial_size, unsigned int flags) { return cork_hash_table_new(initial_size, flags); }