480 lines
13 KiB
C
480 lines
13 KiB
C
/*
|
|
* Copyright (c) 2007-2008 Apple Inc. All rights reserved.
|
|
*
|
|
* @APPLE_OSREFERENCE_LICENSE_HEADER_START@
|
|
*
|
|
* This file contains Original Code and/or Modifications of Original Code
|
|
* as defined in and that are subject to the Apple Public Source License
|
|
* Version 2.0 (the 'License'). You may not use this file except in
|
|
* compliance with the License. The rights granted to you under the License
|
|
* may not be used to create, or enable the creation or redistribution of,
|
|
* unlawful or unlicensed copies of an Apple operating system, or to
|
|
* circumvent, violate, or enable the circumvention or violation of, any
|
|
* terms of an Apple operating system software license agreement.
|
|
*
|
|
* Please obtain a copy of the License at
|
|
* http://www.opensource.apple.com/apsl/ and read it before using this file.
|
|
*
|
|
* The Original Code and all software distributed under the License are
|
|
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
|
|
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
|
|
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
|
|
* Please see the License for the specific language governing rights and
|
|
* limitations under the License.
|
|
*
|
|
* @APPLE_OSREFERENCE_LICENSE_HEADER_END@
|
|
*/
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
|
|
#define DEBUG_ASSERT_COMPONENT_NAME_STRING "kxld"
|
|
#include <AssertMacros.h>
|
|
|
|
#include "kxld_dict.h"
|
|
#include "kxld_util.h"
|
|
|
|
/*******************************************************************************
|
|
* Types and macros
|
|
*******************************************************************************/
|
|
|
|
/* Ratio of num_entries:num_buckets that will cause a resize */
|
|
#define RESIZE_NUMER 7
|
|
#define RESIZE_DENOM 10
|
|
#define RESIZE_THRESHOLD(x) (((x)*RESIZE_NUMER) / RESIZE_DENOM)
|
|
#define MIN_BUCKETS(x) (((x)*RESIZE_DENOM) / RESIZE_NUMER)
|
|
|
|
/* Selected for good scaling qualities when resizing dictionary
|
|
* ... see: http://www.concentric.net/~ttwang/tech/hashsize.htm
|
|
*/
|
|
#define DEFAULT_DICT_SIZE 89
|
|
|
|
typedef struct dict_entry DictEntry;
|
|
|
|
typedef enum {
|
|
EMPTY = 0,
|
|
USED = 1,
|
|
DELETED = 2
|
|
} DictEntryState;
|
|
|
|
struct dict_entry {
|
|
const void *key;
|
|
void *value;
|
|
DictEntryState state;
|
|
};
|
|
|
|
/*******************************************************************************
|
|
* Function prototypes
|
|
*******************************************************************************/
|
|
|
|
static kern_return_t get_locate_index(const KXLDDict *dict, const void *key,
|
|
u_int *idx);
|
|
static kern_return_t get_insert_index(const KXLDDict *dict, const void *key,
|
|
u_int *idx);
|
|
static kern_return_t resize_dict(KXLDDict *dict);
|
|
|
|
/*******************************************************************************
|
|
*******************************************************************************/
|
|
kern_return_t
|
|
kxld_dict_init(KXLDDict * dict, kxld_dict_hash hash, kxld_dict_cmp cmp,
|
|
u_int num_entries)
|
|
{
|
|
kern_return_t rval = KERN_FAILURE;
|
|
u_int min_buckets = MIN_BUCKETS(num_entries);
|
|
u_int num_buckets = DEFAULT_DICT_SIZE;
|
|
|
|
check(dict);
|
|
check(hash);
|
|
check(cmp);
|
|
|
|
/* We want the number of allocated buckets to be at least twice that of the
|
|
* number to be inserted.
|
|
*/
|
|
while (min_buckets > num_buckets) {
|
|
num_buckets *= 2;
|
|
num_buckets++;
|
|
}
|
|
|
|
/* Allocate enough buckets for the anticipated number of entries */
|
|
rval = kxld_array_init(&dict->buckets, sizeof(DictEntry), num_buckets);
|
|
require_noerr(rval, finish);
|
|
|
|
/* Initialize */
|
|
dict->hash = hash;
|
|
dict->cmp = cmp;
|
|
dict->num_entries = 0;
|
|
dict->resize_threshold = RESIZE_THRESHOLD(num_buckets);
|
|
|
|
rval = KERN_SUCCESS;
|
|
|
|
finish:
|
|
return rval;
|
|
}
|
|
|
|
/*******************************************************************************
|
|
*******************************************************************************/
|
|
void
|
|
kxld_dict_clear(KXLDDict *dict)
|
|
{
|
|
check(dict);
|
|
|
|
dict->hash = NULL;
|
|
dict->cmp = NULL;
|
|
dict->num_entries = 0;
|
|
dict->resize_threshold = 0;
|
|
kxld_array_clear(&dict->buckets);
|
|
kxld_array_clear(&dict->resize_buckets);
|
|
}
|
|
|
|
/*******************************************************************************
|
|
*******************************************************************************/
|
|
void
|
|
kxld_dict_iterator_init(KXLDDictIterator *iter, const KXLDDict *dict)
|
|
{
|
|
check(iter);
|
|
check(dict);
|
|
|
|
iter->idx = 0;
|
|
iter->dict = dict;
|
|
}
|
|
|
|
/*******************************************************************************
|
|
*******************************************************************************/
|
|
void
|
|
kxld_dict_deinit(KXLDDict *dict)
|
|
{
|
|
check(dict);
|
|
|
|
kxld_array_deinit(&dict->buckets);
|
|
kxld_array_deinit(&dict->resize_buckets);
|
|
}
|
|
|
|
/*******************************************************************************
|
|
*******************************************************************************/
|
|
u_int
|
|
kxld_dict_get_num_entries(const KXLDDict *dict)
|
|
{
|
|
check(dict);
|
|
|
|
return dict->num_entries;
|
|
}
|
|
|
|
/*******************************************************************************
|
|
*******************************************************************************/
|
|
void *
|
|
kxld_dict_find(const KXLDDict *dict, const void *key)
|
|
{
|
|
kern_return_t rval = KERN_FAILURE;
|
|
DictEntry *entry = NULL;
|
|
u_int idx = 0;
|
|
|
|
check(dict);
|
|
check(key);
|
|
|
|
rval = get_locate_index(dict, key, &idx);
|
|
if (rval) {
|
|
return NULL;
|
|
}
|
|
|
|
entry = kxld_array_get_item(&dict->buckets, idx);
|
|
|
|
return entry->value;
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* This dictionary uses linear probing, which means that when there is a
|
|
* collision, we just walk along the buckets until a free bucket shows up.
|
|
* A consequence of this is that when looking up an item, items that lie between
|
|
* its hash value and its actual bucket may have been deleted since it was
|
|
* inserted. Thus, we should only stop a lookup when we've wrapped around the
|
|
* dictionary or encountered an EMPTY bucket.
|
|
********************************************************************************/
|
|
static kern_return_t
|
|
get_locate_index(const KXLDDict *dict, const void *key, u_int *_idx)
|
|
{
|
|
kern_return_t rval = KERN_FAILURE;
|
|
DictEntry *entry = NULL;
|
|
u_int base, idx;
|
|
|
|
base = idx = dict->hash(dict, key);
|
|
|
|
/* Iterate until we match the key, wrap, or hit an empty bucket */
|
|
entry = kxld_array_get_item(&dict->buckets, idx);
|
|
while (!dict->cmp(entry->key, key)) {
|
|
if (entry->state == EMPTY) {
|
|
goto finish;
|
|
}
|
|
|
|
idx = (idx + 1) % dict->buckets.nitems;
|
|
if (idx == base) {
|
|
goto finish;
|
|
}
|
|
|
|
entry = kxld_array_get_item(&dict->buckets, idx);
|
|
}
|
|
|
|
check(idx < dict->buckets.nitems);
|
|
|
|
*_idx = idx;
|
|
rval = KERN_SUCCESS;
|
|
|
|
finish:
|
|
return rval;
|
|
}
|
|
|
|
/*******************************************************************************
|
|
*******************************************************************************/
|
|
kern_return_t
|
|
kxld_dict_insert(KXLDDict *dict, const void *key, void *value)
|
|
{
|
|
kern_return_t rval = KERN_FAILURE;
|
|
DictEntry *entry = NULL;
|
|
u_int idx = 0;
|
|
|
|
check(dict);
|
|
check(key);
|
|
check(value);
|
|
|
|
/* Resize if we are greater than the capacity threshold.
|
|
* Note: this is expensive, but the dictionary can be sized correctly at
|
|
* construction to avoid ever having to do this.
|
|
*/
|
|
while (dict->num_entries > dict->resize_threshold) {
|
|
rval = resize_dict(dict);
|
|
require_noerr(rval, finish);
|
|
}
|
|
|
|
/* If this function returns FULL after we've already resized appropriately
|
|
* something is very wrong and we should return an error.
|
|
*/
|
|
rval = get_insert_index(dict, key, &idx);
|
|
require_noerr(rval, finish);
|
|
|
|
/* Insert the new key-value pair into the bucket, but only count it as a
|
|
* new entry if we are not overwriting an existing entry.
|
|
*/
|
|
entry = kxld_array_get_item(&dict->buckets, idx);
|
|
if (entry->state != USED) {
|
|
dict->num_entries++;
|
|
entry->key = key;
|
|
entry->state = USED;
|
|
}
|
|
entry->value = value;
|
|
|
|
rval = KERN_SUCCESS;
|
|
|
|
finish:
|
|
return rval;
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* Increases the hash table's capacity by 2N+1. Uses dictionary API. Not
|
|
* fast; just correct.
|
|
*******************************************************************************/
|
|
static kern_return_t
|
|
resize_dict(KXLDDict *dict)
|
|
{
|
|
kern_return_t rval = KERN_FAILURE;
|
|
KXLDArray tmparray;
|
|
DictEntry *entry = NULL;
|
|
u_int nbuckets = (dict->buckets.nitems * 2 + 1);
|
|
u_int i = 0;
|
|
|
|
check(dict);
|
|
|
|
/* Initialize a new set of buckets to hold more entries */
|
|
rval = kxld_array_init(&dict->resize_buckets, sizeof(DictEntry), nbuckets);
|
|
require_noerr(rval, finish);
|
|
|
|
/* Swap the new buckets with the old buckets */
|
|
tmparray = dict->buckets;
|
|
dict->buckets = dict->resize_buckets;
|
|
dict->resize_buckets = tmparray;
|
|
|
|
/* Reset dictionary parameters */
|
|
dict->num_entries = 0;
|
|
dict->resize_threshold = RESIZE_THRESHOLD(dict->buckets.nitems);
|
|
|
|
/* Rehash all of the entries */
|
|
for (i = 0; i < dict->resize_buckets.nitems; ++i) {
|
|
entry = kxld_array_get_item(&dict->resize_buckets, i);
|
|
if (entry->state == USED) {
|
|
rval = kxld_dict_insert(dict, entry->key, entry->value);
|
|
require_noerr(rval, finish);
|
|
}
|
|
}
|
|
|
|
/* Clear the old buckets */
|
|
kxld_array_clear(&dict->resize_buckets);
|
|
|
|
rval = KERN_SUCCESS;
|
|
|
|
finish:
|
|
return rval;
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* Simple function to find the first empty cell
|
|
*******************************************************************************/
|
|
static kern_return_t
|
|
get_insert_index(const KXLDDict *dict, const void *key, u_int *r_index)
|
|
{
|
|
kern_return_t rval = KERN_FAILURE;
|
|
DictEntry *entry = NULL;
|
|
u_int base, idx;
|
|
|
|
base = idx = dict->hash(dict, key);
|
|
|
|
/* Iterate through the buckets until we find an EMPTY bucket, a DELETED
|
|
* bucket, or a key match.
|
|
*/
|
|
entry = kxld_array_get_item(&dict->buckets, idx);
|
|
while (entry->state == USED && !dict->cmp(entry->key, key)) {
|
|
idx = (idx + 1) % dict->buckets.nitems;
|
|
require_action(base != idx, finish, rval = KERN_FAILURE);
|
|
entry = kxld_array_get_item(&dict->buckets, idx);
|
|
}
|
|
|
|
*r_index = idx;
|
|
rval = KERN_SUCCESS;
|
|
|
|
finish:
|
|
return rval;
|
|
}
|
|
|
|
/*******************************************************************************
|
|
*******************************************************************************/
|
|
void
|
|
kxld_dict_remove(KXLDDict *dict, const void *key, void **value)
|
|
{
|
|
kern_return_t rval = KERN_FAILURE;
|
|
DictEntry *entry = NULL;
|
|
u_int idx = 0;
|
|
|
|
check(dict);
|
|
check(key);
|
|
|
|
/* Find the item */
|
|
rval = get_locate_index(dict, key, &idx);
|
|
if (rval) {
|
|
if (value) {
|
|
*value = NULL;
|
|
}
|
|
return;
|
|
}
|
|
|
|
entry = kxld_array_get_item(&dict->buckets, idx);
|
|
|
|
/* Save the value if requested */
|
|
if (value) {
|
|
*value = entry->value;
|
|
}
|
|
|
|
/* Delete the item from the dictionary */
|
|
entry->key = NULL;
|
|
entry->value = NULL;
|
|
entry->state = DELETED;
|
|
dict->num_entries--;
|
|
}
|
|
|
|
/*******************************************************************************
|
|
*******************************************************************************/
|
|
void
|
|
kxld_dict_iterator_get_next(KXLDDictIterator *iter, const void **key,
|
|
void **value)
|
|
{
|
|
DictEntry *entry = NULL;
|
|
|
|
check(iter);
|
|
check(key);
|
|
check(value);
|
|
|
|
*key = NULL;
|
|
*value = NULL;
|
|
|
|
/* Walk over the dictionary looking for USED buckets */
|
|
for (; iter->idx < iter->dict->buckets.nitems; ++(iter->idx)) {
|
|
entry = kxld_array_get_item(&iter->dict->buckets, iter->idx);
|
|
if (entry->state == USED) {
|
|
*key = entry->key;
|
|
*value = entry->value;
|
|
++(iter->idx);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*******************************************************************************
|
|
*******************************************************************************/
|
|
void
|
|
kxld_dict_iterator_reset(KXLDDictIterator *iter)
|
|
{
|
|
iter->idx = 0;
|
|
}
|
|
|
|
/*******************************************************************************
|
|
* This is Daniel Bernstein's hash algorithm from comp.lang.c
|
|
* It's fast and distributes well. Returns an idx into the symbol hash table.
|
|
* NOTE: Will not check for a valid pointer - performance
|
|
*******************************************************************************/
|
|
u_int
|
|
kxld_dict_string_hash(const KXLDDict *dict, const void *_key)
|
|
{
|
|
const char *key = _key;
|
|
u_int c = 0;
|
|
u_int hash_val = 5381;
|
|
|
|
check(dict);
|
|
check(_key);
|
|
|
|
while ((c = *key++)) {
|
|
/* hash(i) = hash(i-1) *33 ^ name[i] */
|
|
hash_val = ((hash_val << 5) + hash_val) ^ c;
|
|
}
|
|
|
|
return hash_val % dict->buckets.nitems;
|
|
}
|
|
|
|
u_int
|
|
kxld_dict_uint32_hash(const KXLDDict *dict, const void *_key)
|
|
{
|
|
uint32_t key = *(const uint32_t *) _key;
|
|
|
|
check(_key);
|
|
|
|
return (u_int) (key % dict->buckets.nitems);
|
|
}
|
|
|
|
u_int
|
|
kxld_dict_kxldaddr_hash(const KXLDDict *dict, const void *_key)
|
|
{
|
|
kxld_addr_t key = *(const kxld_addr_t *) _key;
|
|
|
|
check(_key);
|
|
|
|
return (u_int) (key % dict->buckets.nitems);
|
|
}
|
|
|
|
u_int
|
|
kxld_dict_string_cmp(const void *key1, const void *key2)
|
|
{
|
|
return streq(key1, key2);
|
|
}
|
|
|
|
u_int
|
|
kxld_dict_uint32_cmp(const void *key1, const void *key2)
|
|
{
|
|
const uint32_t *a = key1;
|
|
const uint32_t *b = key2;
|
|
|
|
return a && b && (*a == *b);
|
|
}
|
|
|
|
u_int
|
|
kxld_dict_kxldaddr_cmp(const void *key1, const void *key2)
|
|
{
|
|
const kxld_addr_t *a = key1;
|
|
const kxld_addr_t *b = key2;
|
|
|
|
return a && b && (*a == *b);
|
|
}
|