aboutsummaryrefslogtreecommitdiffstats
path: root/tools/libxutil/hash_table.c
diff options
context:
space:
mode:
Diffstat (limited to 'tools/libxutil/hash_table.c')
-rw-r--r--tools/libxutil/hash_table.c640
1 files changed, 640 insertions, 0 deletions
diff --git a/tools/libxutil/hash_table.c b/tools/libxutil/hash_table.c
new file mode 100644
index 0000000000..13da946e77
--- /dev/null
+++ b/tools/libxutil/hash_table.c
@@ -0,0 +1,640 @@
+/*
+ * Copyright (C) 2001 - 2004 Mike Wray <mike.wray@hp.com>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifdef __KERNEL__
+# include <linux/config.h>
+# include <linux/module.h>
+# include <linux/kernel.h>
+# include <linux/errno.h>
+#else
+# include <errno.h>
+# include <stddef.h>
+#endif
+
+//#include <limits.h>
+
+#include "allocate.h"
+#include "hash_table.h"
+
+/** @file
+ * Base support for hashtables.
+ *
+ * Hash codes are reduced modulo the number of buckets to index tables,
+ * so there is no need for hash functions to limit the range of hashcodes.
+ * In fact it is assumed that hashcodes do not change when the number of
+ * buckets in the table changes.
+ */
+
+/*==========================================================================*/
+/** Number of bits in half a word. */
+//#if __WORDSIZE == 64
+//#define HALF_WORD_BITS 32
+//#else
+#define HALF_WORD_BITS 16
+//#endif
+
+/** Mask for lo half of a word. On 32-bit this is
+ * (1<<16) - 1 = 65535 = 0xffff
+ * It's 4294967295 = 0xffffffff on 64-bit.
+ */
+#define LO_HALF_MASK ((1 << HALF_WORD_BITS) - 1)
+
+/** Get the lo half of a word. */
+#define LO_HALF(x) ((x) & LO_HALF_MASK)
+
+/** Get the hi half of a word. */
+#define HI_HALF(x) ((x) >> HALF_WORD_BITS)
+
+/** Do a full hash on both inputs, using DES-style non-linear scrambling.
+ * Both inputs are replaced with the results of the hash.
+ *
+ * @param pleft input/output word
+ * @param pright input/output word
+ */
+void pseudo_des(unsigned long *pleft, unsigned long *pright){
+ // Bit-rich mixing constant.
+ static const unsigned long a_mixer[] = {
+ 0xbaa96887L, 0x1e17d32cL, 0x03bcdc3cL, 0x0f33d1b2L, };
+
+ // Bit-rich mixing constant.
+ static const unsigned long b_mixer[] = {
+ 0x4b0f3b58L, 0xe874f0c3L, 0x6955c5a6L, 0x55a7ca46L, };
+
+ // Number of iterations - must be 2 or 4.
+ static const int ncycle = 4;
+ //static const int ncycle = 2;
+
+ unsigned long left = *pleft, right = *pright;
+ unsigned long v, v_hi, v_lo;
+ int i;
+
+ for(i=0; i<ncycle; i++){
+ // Flip some bits in right to get v.
+ v = right;
+ v ^= a_mixer[i];
+ // Get lo and hi halves of v.
+ v_lo = LO_HALF(v);
+ v_hi = HI_HALF(v);
+ // Non-linear mix of the halves of v.
+ v = ((v_lo * v_lo) + ~(v_hi * v_hi));
+ // Swap the halves of v.
+ v = (HI_HALF(v) | (LO_HALF(v) << HALF_WORD_BITS));
+ // Flip some bits.
+ v ^= b_mixer[i];
+ // More non-linear mixing.
+ v += (v_lo * v_hi);
+ v ^= left;
+ left = right;
+ right = v;
+ }
+ *pleft = left;
+ *pright = right;
+}
+
+/** Hash a string.
+ *
+ * @param s input to hash
+ * @return hashcode
+ */
+Hashcode hash_string(char *s){
+ Hashcode h = 0;
+ if(s){
+ for( ; *s; s++){
+ h = hash_2ul(h, *s);
+ }
+ }
+ return h;
+}
+
+/** Get the bucket for a hashcode in a hash table.
+ *
+ * @param table to get bucket from
+ * @param hashcode to get bucket for
+ * @return bucket
+ */
+inline HTBucket * get_bucket(HashTable *table, Hashcode hashcode){
+ return table->buckets + (hashcode % table->buckets_n);
+}
+
+/** Initialize a hash table.
+ * Can be safely called more than once.
+ *
+ * @param table to initialize
+ */
+void HashTable_init(HashTable *table){
+ int i;
+
+ if(!table->init_done){
+ table->init_done = 1;
+ table->next_id = 0;
+ for(i=0; i<table->buckets_n; i++){
+ HTBucket *bucket = get_bucket(table, i);
+ bucket->head = 0;
+ bucket->count = 0;
+ }
+ table->entry_count = 0;
+ }
+}
+
+/** Allocate a new hashtable.
+ * If the number of buckets is not positive the default is used.
+ * The number of buckets should usually be prime.
+ *
+ * @param buckets_n number of buckets
+ * @return new hashtable or null
+ */
+HashTable *HashTable_new(int buckets_n){
+ HashTable *z = ALLOCATE(HashTable);
+ if(!z) goto exit;
+ if(buckets_n <= 0){
+ buckets_n = HT_BUCKETS_N;
+ }
+ z->buckets = (HTBucket*)allocate(buckets_n * sizeof(HTBucket));
+ if(!z->buckets){
+ deallocate(z);
+ z = 0;
+ goto exit;
+ }
+ z->buckets_n = buckets_n;
+ HashTable_init(z);
+ exit:
+ return z;
+}
+
+/** Free a hashtable.
+ * Any entries are removed and freed.
+ *
+ * @param h hashtable (ignored if null)
+ */
+void HashTable_free(HashTable *h){
+ if(h){
+ HashTable_clear(h);
+ deallocate(h->buckets);
+ deallocate(h);
+ }
+}
+
+/** Push an entry on the list in the bucket for a given hashcode.
+ *
+ * @param table to add entry to
+ * @param hashcode for the entry
+ * @param entry to add
+ */
+static inline void push_on_bucket(HashTable *table, Hashcode hashcode,
+ HTEntry *entry){
+ HTBucket *bucket;
+ HTEntry *old_head;
+
+ bucket = get_bucket(table, hashcode);
+ old_head = bucket->head;
+ bucket->count++;
+ bucket->head = entry;
+ entry->next = old_head;
+}
+
+/** Change the number of buckets in a hashtable.
+ * No-op if the number of buckets is not positive.
+ * Existing entries are reallocated to buckets based on their hashcodes.
+ * The table is unmodified if the number of buckets cannot be changed.
+ *
+ * @param table hashtable
+ * @param buckets_n new number of buckets
+ * @return 0 on success, error code otherwise
+ */
+int HashTable_set_buckets_n(HashTable *table, int buckets_n){
+ int err = 0;
+ HTBucket *old_buckets = table->buckets;
+ int old_buckets_n = table->buckets_n;
+ int i;
+
+ if(buckets_n <= 0){
+ err = -EINVAL;
+ goto exit;
+ }
+ table->buckets = (HTBucket*)allocate(buckets_n * sizeof(HTBucket));
+ if(!table->buckets){
+ err = -ENOMEM;
+ table->buckets = old_buckets;
+ goto exit;
+ }
+ table->buckets_n = buckets_n;
+ for(i=0; i<old_buckets_n; i++){
+ HTBucket *bucket = old_buckets + i;
+ HTEntry *entry, *next;
+ for(entry = bucket->head; entry; entry = next){
+ next = entry->next;
+ push_on_bucket(table, entry->hashcode, entry);
+ }
+ }
+ deallocate(old_buckets);
+ exit:
+ return err;
+}
+
+/** Adjust the number of buckets so the table is neither too full nor too empty.
+ * The table is unmodified if adjusting fails.
+ *
+ * @param table hash table
+ * @param buckets_min minimum number of buckets (use default if 0 or negative)
+ * @return 0 on success, error code otherwise
+ */
+int HashTable_adjust(HashTable *table, int buckets_min){
+ int buckets_n = 0;
+ int err = 0;
+ if(buckets_min <= 0) buckets_min = HT_BUCKETS_N;
+ if(table->entry_count >= table->buckets_n){
+ // The table is dense - expand it.
+ buckets_n = 2 * table->buckets_n;
+ } else if((table->buckets_n > buckets_min) &&
+ (4 * table->entry_count < table->buckets_n)){
+ // The table is more than minimum size and sparse - shrink it.
+ buckets_n = 2 * table->entry_count;
+ if(buckets_n < buckets_min) buckets_n = buckets_min;
+ }
+ if(buckets_n){
+ err = HashTable_set_buckets_n(table, buckets_n);
+ }
+ return err;
+}
+
+/** Allocate a new entry for a given value.
+ *
+ * @param value to put in the entry
+ * @return entry, or 0 on failure
+ */
+HTEntry * HTEntry_new(Hashcode hashcode, void *key, void *value){
+ HTEntry *z = ALLOCATE(HTEntry);
+ if(z){
+ z->hashcode = hashcode;
+ z->key = key;
+ z->value = value;
+ }
+ return z;
+}
+
+/** Free an entry.
+ *
+ * @param z entry to free
+ */
+inline void HTEntry_free(HTEntry *z){
+ if(z){
+ deallocate(z);
+ }
+}
+
+/** Free an entry in a hashtable.
+ * The table's entry_free_fn is used is defined, otherwise
+ * the HTEntry itself is freed.
+ *
+ * @param table hashtable
+ * @param entry to free
+ */
+inline void HashTable_free_entry(HashTable *table, HTEntry *entry){
+ if(!entry)return;
+ if(table && table->entry_free_fn){
+ table->entry_free_fn(table, entry);
+ } else {
+ HTEntry_free(entry);
+ }
+}
+
+/** Get the first entry satisfying a test from the bucket for the
+ * given hashcode.
+ *
+ * @param table to look in
+ * @param hashcode indicates the bucket
+ * @param test_fn test to apply to elements
+ * @param arg first argument to calls to test_fn
+ * @return entry found, or 0
+ */
+inline HTEntry * HashTable_find_entry(HashTable *table, Hashcode hashcode,
+ TableTestFn *test_fn, TableArg arg){
+ HTBucket *bucket;
+ HTEntry *entry = 0;
+ HTEntry *next;
+
+ bucket = get_bucket(table, hashcode);
+ for(entry = bucket->head; entry; entry = next){
+ next = entry->next;
+ if(test_fn(arg, table, entry)){
+ break;
+ }
+ }
+ return entry;
+}
+
+/** Test hashtable keys for equality.
+ * Uses the table's key_equal_fn if defined, otherwise pointer equality.
+ *
+ * @param key1 key to compare
+ * @param key2 key to compare
+ * @return 1 if equal, 0 otherwise
+ */
+inline int HashTable_key_equal(HashTable *table, void *key1, void *key2){
+ return (table->key_equal_fn ? table->key_equal_fn(key1, key2) : key1==key2);
+}
+
+/** Compute the hashcode of a hashtable key.
+ * The table's key_hash_fn is used if defined, otherwise the address of
+ * the key is hashed.
+ *
+ * @param table hashtable
+ * @param key to hash
+ * @return hashcode
+ */
+inline Hashcode HashTable_key_hash(HashTable *table, void *key){
+ return (table->key_hash_fn ? table->key_hash_fn(key) : hash_ul((unsigned long)key));
+}
+
+/** Test if an entry has a given key.
+ *
+ * @param arg containing key to test for
+ * @param table the entry is in
+ * @param entry to test
+ * @return 1 if the entry has the key, 0 otherwise
+ */
+static inline int has_key(TableArg arg, HashTable *table, HTEntry *entry){
+ return HashTable_key_equal(table, arg.ptr, entry->key);
+}
+
+/** Get an entry with a given key.
+ *
+ * @param table to search
+ * @param key to look for
+ * @return entry if found, null otherwise
+ */
+#if 0
+inline HTEntry * HashTable_get_entry(HashTable *table, void *key){
+ TableArg arg = { ptr: key };
+ return HashTable_find_entry(table, HashTable_key_hash(table, key), has_key, arg);
+}
+#else
+inline HTEntry * HashTable_get_entry(HashTable *table, void *key){
+ Hashcode hashcode;
+ HTBucket *bucket;
+ HTEntry *entry = 0;
+ HTEntry *next;
+
+ hashcode = HashTable_key_hash(table, key);
+ bucket = get_bucket(table, hashcode);
+ for(entry = bucket->head; entry; entry = next){
+ next = entry->next;
+ if(HashTable_key_equal(table, key, entry->key)){
+ break;
+ }
+ }
+ return entry;
+}
+#endif
+
+/** Get the value of an entry with a given key.
+ *
+ * @param table to search
+ * @param key to look for
+ * @return value if an entry was found, null otherwise
+ */
+inline void * HashTable_get(HashTable *table, void *key){
+ HTEntry *entry = HashTable_get_entry(table, key);
+ return (entry ? entry->value : 0);
+}
+
+/** Print the buckets in a table.
+ *
+ * @param table to print
+ */
+void show_buckets(HashTable *table, IOStream *io){
+ int i,j ;
+ IOStream_print(io, "entry_count=%d buckets_n=%d\n", table->entry_count, table->buckets_n);
+ for(i=0; i<table->buckets_n; i++){
+ if(0 || table->buckets[i].count>0){
+ IOStream_print(io, "bucket %3d %3d %10p ", i,
+ table->buckets[i].count,
+ table->buckets[i].head);
+ for(j = table->buckets[i].count; j>0; j--){
+ IOStream_print(io, "+");
+ }
+ IOStream_print(io, "\n");
+ }
+ }
+ HashTable_print(table, io);
+}
+
+/** Print an entry in a table.
+ *
+ * @param entry to print
+ * @param arg a pointer to an IOStream to print to
+ * @return 0
+ */
+static int print_entry(TableArg arg, HashTable *table, HTEntry *entry){
+ IOStream *io = (IOStream*)arg.ptr;
+ IOStream_print(io, " b=%4lx h=%08lx i=%08lx |-> e=%8p k=%8p v=%8p\n",
+ entry->hashcode % table->buckets_n,
+ entry->hashcode,
+ entry->index,
+ entry, entry->key, entry->value);
+ return 0;
+}
+
+/** Print a hash table.
+ *
+ * @param table to print
+ */
+void HashTable_print(HashTable *table, IOStream *io){
+ IOStream_print(io, "{\n");
+ HashTable_map(table, print_entry, (TableArg){ ptr: io });
+ IOStream_print(io, "}\n");
+}
+/*==========================================================================*/
+
+/** Get the next entry id to use for a table.
+ *
+ * @param table hash table
+ * @return non-zero entry id
+ */
+static inline unsigned long get_next_id(HashTable *table){
+ unsigned long id;
+
+ if(table->next_id == 0){
+ table->next_id = 1;
+ }
+ id = table->next_id++;
+ return id;
+}
+
+/** Add an entry to the bucket for the
+ * given hashcode.
+ *
+ * @param table to insert in
+ * @param hashcode indicates the bucket
+ * @param key to add an entry for
+ * @param value to add an entry for
+ * @return entry on success, 0 on failure
+ */
+inline HTEntry * HashTable_add_entry(HashTable *table, Hashcode hashcode, void *key, void *value){
+ HTEntry *entry = HTEntry_new(hashcode, key, value);
+ if(entry){
+ entry->index = get_next_id(table);
+ push_on_bucket(table, hashcode, entry);
+ table->entry_count++;
+ }
+ return entry;
+}
+
+/** Move the front entry for a bucket to the correct point in the bucket order as
+ * defined by the order function. If this is called every time a new entry is added
+ * the bucket will be maintained in sorted order.
+ *
+ * @param table to modify
+ * @param hashcode indicates the bucket
+ * @param order entry comparison function
+ * @return 0 if an entry was moved, 1 if not
+ */
+int HashTable_order_bucket(HashTable *table, Hashcode hashcode, TableOrderFn *order){
+ HTEntry *new_entry = NULL, *prev = NULL, *entry = NULL;
+ HTBucket *bucket;
+ int err = 1;
+
+ bucket = get_bucket(table, hashcode);
+ new_entry = bucket->head;
+ if(!new_entry || !new_entry->next) goto exit;
+ for(entry = new_entry->next; entry; prev = entry, entry = entry->next){
+ if(order(new_entry, entry) <= 0) break;
+ }
+ if(prev){
+ err = 0;
+ bucket->head = new_entry->next;
+ new_entry->next = entry;
+ prev->next = new_entry;
+ }
+ exit:
+ return err;
+}
+
+/** Add an entry to a hashtable.
+ * The entry is added to the bucket for its key's hashcode.
+ *
+ * @param table to insert in
+ * @param key to add an entry for
+ * @param value to add an entry for
+ * @return entry on success, 0 on failure
+ */
+inline HTEntry * HashTable_add(HashTable *table, void *key, void *value){
+ return HashTable_add_entry(table, HashTable_key_hash(table, key), key, value);
+}
+
+
+/** Remove entries satisfying a test from the bucket for the
+ * given hashcode.
+ *
+ * @param table to remove from
+ * @param hashcode indicates the bucket
+ * @param test_fn test to apply to elements
+ * @param arg first argument to calls to test_fn
+ * @return number of entries removed
+ */
+inline int HashTable_remove_entry(HashTable *table, Hashcode hashcode,
+ TableTestFn *test_fn, TableArg arg){
+ HTBucket *bucket;
+ HTEntry *entry, *prev = 0, *next;
+ int removed_count = 0;
+
+ bucket = get_bucket(table, hashcode);
+ for(entry = bucket->head; entry; entry = next){
+ next = entry->next;
+ if(test_fn(arg, table, entry)){
+ if(prev){
+ prev->next = next;
+ } else {
+ bucket->head = next;
+ }
+ bucket->count--;
+ table->entry_count--;
+ removed_count++;
+ HashTable_free_entry(table, entry);
+ entry = 0;
+ }
+ prev = entry;
+ }
+ return removed_count;
+}
+
+/** Remove entries with a given key.
+ *
+ * @param table to remove from
+ * @param key of entries to remove
+ * @return number of entries removed
+ */
+inline int HashTable_remove(HashTable *table, void *key){
+#if 1
+ Hashcode hashcode;
+ HTBucket *bucket;
+ HTEntry *entry, *prev = 0, *next;
+ int removed_count = 0;
+
+ hashcode = HashTable_key_hash(table, key);
+ bucket = get_bucket(table, hashcode);
+ for(entry = bucket->head; entry; entry = next){
+ next = entry->next;
+ if(HashTable_key_equal(table, key, entry->key)){
+ if(prev){
+ prev->next = next;
+ } else {
+ bucket->head = next;
+ }
+ bucket->count--;
+ table->entry_count--;
+ removed_count++;
+ HashTable_free_entry(table, entry);
+ entry = 0;
+ }
+ prev = entry;
+ }
+ return removed_count;
+#else
+ return HashTable_remove_entry(table, HashTable_key_hash(table, key),
+ has_key, (TableArg){ ptr: key});
+#endif
+}
+
+/** Remove (and free) all the entries in a bucket.
+ *
+ * @param bucket to clear
+ */
+static inline void bucket_clear(HashTable *table, HTBucket *bucket){
+ HTEntry *entry, *next;
+
+ for(entry = bucket->head; entry; entry = next){
+ next = entry->next;
+ HashTable_free_entry(table, entry);
+ }
+ bucket->head = 0;
+ table->entry_count -= bucket->count;
+ bucket->count = 0;
+}
+
+/** Remove (and free) all the entries in a table.
+ *
+ * @param table to clear
+ */
+void HashTable_clear(HashTable *table){
+ int i, n = table->buckets_n;
+
+ for(i=0; i<n; i++){
+ bucket_clear(table, table->buckets + i);
+ }
+}