You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

689 lines
20 KiB

/* -*- coding: utf-8 -*-
* ----------------------------------------------------------------------
* Copyright © 2011-2014, RedJack, LLC.
* All rights reserved.
*
* Please see the COPYING file in this distribution for license details.
* ----------------------------------------------------------------------
*/
#include <stdlib.h>
#include <string.h>
#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 <stdio.h>
#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);
}