gems-kernel/source/THIRDPARTY/xnu/libkern/kxld/kxld_dict.c
2024-06-03 11:29:39 -05:00

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