Files
xen/tools/libxutil/hash_table.c
T
mjw@wray-m-3.hpl.hp.com 4b64c0d7ea bitkeeper revision 1.1159.183.2 (41a216ce6YIv6IWbateVWedQylK5rA)
Restructure libxutil to prepare for adding vnets.
Move common files from xfrd into libxutil.
2004-11-22 16:41:50 +00:00

641 lines
17 KiB
C

/*
* 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);
}
}