knight/shared/KeHashTable.cpp
author David Anderson <dvander@alliedmods.net>
Sun Oct 26 23:50:54 2008 -0500 (2008-10-26)
changeset 2377 6ee3bbd6fbdf
parent 2226 d10b38524be3
child 2541 0352326a1f3f
permissions -rw-r--r--
Fixed leaking relocation table memory in JIT.
dvander@2226
     1
#include <KeHashTable.h>
dvander@2226
     2
dvander@2226
     3
using namespace Knight;
dvander@2226
     4
dvander@2226
     5
struct KeHashNode
dvander@2226
     6
{
dvander@2226
     7
	KeHashNode *next;
dvander@2226
     8
	uint32_t key_hash;
dvander@2226
     9
	const void *key;
dvander@2226
    10
	void *value;
dvander@2226
    11
};
dvander@2226
    12
dvander@2226
    13
namespace Knight
dvander@2226
    14
{
dvander@2226
    15
	class KeHashTable
dvander@2226
    16
	{
dvander@2226
    17
	public:
dvander@2226
    18
		KeHashNode **buckets;
dvander@2226
    19
		uint32_t num_buckets;
dvander@2226
    20
		uint32_t shift;
dvander@2226
    21
		uint32_t num_entries;
dvander@2226
    22
		KeHashGenerator key_gen;
dvander@2226
    23
		KeHashMarshal key_m;
dvander@2226
    24
		KeHashMarshal val_m;
dvander@2226
    25
		ke_allocator_t *node_alloc;
dvander@2226
    26
		size_t key_offs;
dvander@2226
    27
		size_t val_offs;
dvander@2226
    28
		size_t node_size;
dvander@2226
    29
		uint32_t grow_limit;
dvander@2226
    30
		KeHashNode *free_list;
dvander@2226
    31
		bool keep_free_list;
dvander@2226
    32
	};
dvander@2226
    33
}
dvander@2226
    34
dvander@2226
    35
void *ke_DefHashMalloc(ke_allocator_t *alloc, size_t amt)
dvander@2226
    36
{
dvander@2226
    37
	return malloc(amt);
dvander@2226
    38
}
dvander@2226
    39
dvander@2226
    40
void ke_DefHashFree(ke_allocator_t *alloc, void *addr)
dvander@2226
    41
{
dvander@2226
    42
	free(addr);
dvander@2226
    43
}
dvander@2226
    44
dvander@2226
    45
ke_allocator_t s_DefHashAllocator = 
dvander@2226
    46
{
dvander@2226
    47
	ke_DefHashMalloc,
dvander@2226
    48
	ke_DefHashFree,
dvander@2226
    49
	NULL
dvander@2226
    50
};
dvander@2226
    51
dvander@2226
    52
KeHashTable *Knight::KE_CreateHashTable(
dvander@2226
    53
	uint32_t bits,
dvander@2226
    54
	KeHashGenerator key_gen,
dvander@2226
    55
	const KeHashMarshal *key_marshal,
dvander@2226
    56
	const KeHashMarshal *val_marshal,
dvander@2226
    57
	ke_allocator_t *nodeAlloc,
dvander@2226
    58
	bool keep_free_list)
dvander@2226
    59
{
dvander@2226
    60
	KeHashTable *table;
dvander@2226
    61
dvander@2226
    62
	if (bits >= 27)
dvander@2226
    63
	{
dvander@2226
    64
		bits = 26;
dvander@2226
    65
	}
dvander@2226
    66
	else if (bits < 4)
dvander@2226
    67
	{
dvander@2226
    68
		bits = 4;
dvander@2226
    69
	}
dvander@2226
    70
dvander@2226
    71
	/* Validate marshals. */
dvander@2226
    72
	if ((key_marshal->bytes != 0
dvander@2226
    73
		 && key_marshal->ctor == NULL)
dvander@2226
    74
		|| (val_marshal->bytes != 0
dvander@2226
    75
			&& val_marshal->ctor == NULL))
dvander@2226
    76
	{
dvander@2226
    77
		return NULL;
dvander@2226
    78
	}
dvander@2226
    79
dvander@2226
    80
	table = new KeHashTable;
dvander@2226
    81
	table->key_gen = key_gen;
dvander@2226
    82
	table->key_m = *key_marshal;
dvander@2226
    83
	table->val_m = *val_marshal;
dvander@2226
    84
	table->num_entries = 0;
dvander@2226
    85
	table->shift = bits;
dvander@2226
    86
	table->node_alloc = nodeAlloc == NULL ? &s_DefHashAllocator : nodeAlloc;
dvander@2226
    87
	table->num_buckets = (1 << bits);
dvander@2226
    88
	table->grow_limit = (uint32_t)(0.9f * table->num_buckets);
dvander@2226
    89
	table->keep_free_list = keep_free_list;
dvander@2226
    90
	table->free_list = NULL;
dvander@2226
    91
	table->buckets = (KeHashNode **)malloc(sizeof(KeHashNode *) * table->num_buckets);
dvander@2226
    92
	memset(table->buckets, 0, sizeof(KeHashNode *) * table->num_buckets);
dvander@2226
    93
dvander@2226
    94
	table->key_offs = sizeof(KeHashNode);
dvander@2226
    95
	if (table->key_m.bytes != 0 && table->key_m.bytes % 8 != 0)
dvander@2226
    96
	{
dvander@2226
    97
		table->key_m.bytes += 8;
dvander@2226
    98
		table->key_m.bytes -= (table->key_m.bytes % 8);
dvander@2226
    99
	}
dvander@2226
   100
	
dvander@2226
   101
	table->val_offs = table->key_offs + table->key_m.bytes;
dvander@2226
   102
	table->node_size = table->val_offs + table->val_m.bytes;
dvander@2226
   103
dvander@2226
   104
	return table;
dvander@2226
   105
}
dvander@2226
   106
dvander@2226
   107
#define KE_GET_BUCKET(tbl, hsh) (&(tbl)->buckets[((hsh) * 0x9E3779B9) >> (tbl)->shift])
dvander@2226
   108
dvander@2226
   109
KeHashNode **ke_HashInternalFind(KeHashTable *table, uint32_t key_hash, const void *key)
dvander@2226
   110
{
dvander@2226
   111
	KeHashNode *node;
dvander@2226
   112
	KeHashNode **bucket;
dvander@2226
   113
dvander@2226
   114
	bucket = KE_GET_BUCKET(table, key_hash);
dvander@2226
   115
dvander@2226
   116
	/* :TODO: move to the front once found? */
dvander@2226
   117
dvander@2226
   118
	while ((node = *bucket) != NULL)
dvander@2226
   119
	{
dvander@2226
   120
		if (node->key_hash == key_hash
dvander@2226
   121
			&& ((table->key_m.cmp != NULL && table->key_m.cmp(node->key, key))
dvander@2226
   122
				 || node->key == key))
dvander@2226
   123
		{
dvander@2226
   124
			return bucket;
dvander@2226
   125
		}
dvander@2226
   126
		bucket = &node->next;
dvander@2226
   127
	}
dvander@2226
   128
dvander@2226
   129
	return bucket;
dvander@2226
   130
}
dvander@2226
   131
dvander@2226
   132
void ke_ResizeHashTable(KeHashTable *table, uint32_t new_shift)
dvander@2226
   133
{
dvander@2226
   134
	uint32_t entries;
dvander@2226
   135
	KeHashNode *next;
dvander@2226
   136
	KeHashNode *node;
dvander@2226
   137
	KeHashNode **rbucket;
dvander@2226
   138
	KeHashNode **old_buckets;
dvander@2226
   139
	uint32_t old_num_buckets;
dvander@2226
   140
dvander@2226
   141
	/* Save old data */
dvander@2226
   142
	old_num_buckets = table->num_buckets;
dvander@2226
   143
	old_buckets = table->buckets;
dvander@2226
   144
	entries = table->num_entries;
dvander@2226
   145
dvander@2226
   146
	/* Save new data */
dvander@2226
   147
	table->num_buckets = (1 << new_shift);
dvander@2226
   148
	table->shift = new_shift;
dvander@2226
   149
	table->grow_limit = (uint32_t)(0.9f * table->num_buckets);
dvander@2226
   150
dvander@2226
   151
	table->buckets = (KeHashNode **)malloc(sizeof(KeHashNode *) * table->num_buckets);
dvander@2226
   152
	memset(table->buckets, 0, sizeof(KeHashNode *) * table->num_buckets);
dvander@2226
   153
dvander@2226
   154
	/* For each old bucket... */
dvander@2226
   155
	for (uint32_t i = 0;
dvander@2226
   156
		 i < old_num_buckets && entries != 0;
dvander@2226
   157
		 i++)
dvander@2226
   158
	{
dvander@2226
   159
		node = old_buckets[i];
dvander@2226
   160
		/* Get each item in its list... */
dvander@2226
   161
		while (node != NULL)
dvander@2226
   162
		{
dvander@2226
   163
			next = node->next;
dvander@2226
   164
			
dvander@2226
   165
			/* Find the new replacement bucket it needs to go in. */
dvander@2226
   166
			rbucket = KE_GET_BUCKET(table, node->key_hash);
dvander@2226
   167
dvander@2226
   168
			/* Link this node to the next node in the new bucket. */
dvander@2226
   169
			if (*rbucket == NULL)
dvander@2226
   170
			{
dvander@2226
   171
				node->next = NULL;
dvander@2226
   172
			}
dvander@2226
   173
			else
dvander@2226
   174
			{
dvander@2226
   175
				node->next = *rbucket;
dvander@2226
   176
			}
dvander@2226
   177
dvander@2226
   178
			/* Add us to the front of that bucket's list. */
dvander@2226
   179
			*rbucket = node;
dvander@2226
   180
dvander@2226
   181
			node = next;
dvander@2226
   182
		}
dvander@2226
   183
	}
dvander@2226
   184
dvander@2226
   185
	free(old_buckets);
dvander@2226
   186
}
dvander@2226
   187
dvander@2226
   188
void Knight::KE_AddToHashTable(KeHashTable *table, const void *key, void *val)
dvander@2226
   189
{
dvander@2226
   190
	KeHashNode *node;
dvander@2226
   191
	uint32_t key_hash;
dvander@2226
   192
	KeHashNode **bucket;
dvander@2226
   193
dvander@2226
   194
	key_hash = table->key_gen(key);
dvander@2226
   195
	bucket = ke_HashInternalFind(table, key_hash, key);
dvander@2226
   196
dvander@2226
   197
	if ((node = *bucket) != NULL)
dvander@2226
   198
	{
dvander@2226
   199
		/* Already in the table */
dvander@2226
   200
		if ((table->val_m.cmp != NULL && table->val_m.cmp(node->value, val))
dvander@2226
   201
			 || node->value == val)
dvander@2226
   202
		{
dvander@2226
   203
			return;
dvander@2226
   204
		}
dvander@2226
   205
dvander@2226
   206
		/* Destroy old value if it's set. */
dvander@2226
   207
		if (node->value != NULL && table->val_m.dtor != NULL)
dvander@2226
   208
		{
dvander@2226
   209
			table->val_m.dtor(node->value);
dvander@2226
   210
		}
dvander@2226
   211
		
dvander@2226
   212
		/* Construct or set the new value. */
dvander@2226
   213
		if (table->val_m.bytes != 0)
dvander@2226
   214
		{
dvander@2226
   215
			table->val_m.ctor(node->value, val);
dvander@2226
   216
		}
dvander@2226
   217
		else
dvander@2226
   218
		{
dvander@2226
   219
			node->value = val;
dvander@2226
   220
		}
dvander@2226
   221
dvander@2226
   222
		return;
dvander@2226
   223
	}
dvander@2226
   224
dvander@2226
   225
	/* If we're overloaded, we may need to resize. 
dvander@2226
   226
	 * Right now, we do this if we hit a .9 entry:buckets ratio.
dvander@2226
   227
	 */
dvander@2226
   228
	if (table->num_entries >= table->grow_limit)
dvander@2226
   229
	{
dvander@2226
   230
		ke_ResizeHashTable(table, table->shift << 1);
dvander@2226
   231
		bucket = ke_HashInternalFind(table, key_hash, key);
dvander@2226
   232
	}
dvander@2226
   233
dvander@2226
   234
	if (table->free_list != NULL)
dvander@2226
   235
	{
dvander@2226
   236
		node = table->free_list;
dvander@2226
   237
		table->free_list = node->next;
dvander@2226
   238
	}
dvander@2226
   239
	else
dvander@2226
   240
	{
dvander@2226
   241
		node = (KeHashNode *)table->node_alloc->alloc(table->node_alloc, table->node_size);
dvander@2226
   242
	}
dvander@2226
   243
	
dvander@2226
   244
	if (table->key_m.bytes == 0)
dvander@2226
   245
	{
dvander@2226
   246
		node->key = key;
dvander@2226
   247
	}
dvander@2226
   248
	else
dvander@2226
   249
	{
dvander@2226
   250
		node->key = (char *)node + table->key_offs;
dvander@2226
   251
		table->key_m.ctor((void *)node->key, key);
dvander@2226
   252
	}
dvander@2226
   253
dvander@2226
   254
	if (table->val_m.bytes == 0)
dvander@2226
   255
	{
dvander@2226
   256
		node->value = val;
dvander@2226
   257
	}
dvander@2226
   258
	else
dvander@2226
   259
	{
dvander@2226
   260
		node->value = (char *)node + table->val_offs;
dvander@2226
   261
		table->val_m.ctor(node->value, val);
dvander@2226
   262
	}
dvander@2226
   263
dvander@2226
   264
	node->next = *bucket;
dvander@2226
   265
	node->key_hash = key_hash;
dvander@2226
   266
	*bucket = node;
dvander@2226
   267
}
dvander@2226
   268
dvander@2226
   269
inline void ke_CleanUpHashNode(KeHashTable *table, KeHashNode *node)
dvander@2226
   270
{
dvander@2226
   271
	/* Destroy old value if it's set. */
dvander@2226
   272
	if (node->value != NULL && table->val_m.dtor != NULL)
dvander@2226
   273
	{
dvander@2226
   274
		table->val_m.dtor(node->value);
dvander@2226
   275
	}
dvander@2226
   276
dvander@2226
   277
	/* Destroy the key. */
dvander@2226
   278
	if (table->key_m.dtor != NULL)
dvander@2226
   279
	{
dvander@2226
   280
		table->key_m.dtor(node->key);
dvander@2226
   281
	}
dvander@2226
   282
dvander@2226
   283
	/* Deallocate us as appropriate. */
dvander@2226
   284
	if (table->keep_free_list)
dvander@2226
   285
	{
dvander@2226
   286
		node->next = table->free_list;
dvander@2226
   287
		table->free_list = node;
dvander@2226
   288
	}
dvander@2226
   289
	else
dvander@2226
   290
	{
dvander@2226
   291
		table->node_alloc->dealloc(table->node_alloc, node);
dvander@2226
   292
	}
dvander@2226
   293
}
dvander@2226
   294
dvander@2226
   295
void Knight::KE_RemoveFromHashTable(KeHashTable *table, const void *key)
dvander@2226
   296
{
dvander@2226
   297
	KeHashNode *node;
dvander@2226
   298
	uint32_t key_hash;
dvander@2226
   299
	KeHashNode **bucket;
dvander@2226
   300
dvander@2226
   301
	key_hash = table->key_gen(key);
dvander@2226
   302
	bucket = ke_HashInternalFind(table, key_hash, key);
dvander@2226
   303
dvander@2226
   304
	if ((node = *bucket) == NULL)
dvander@2226
   305
	{
dvander@2226
   306
		return;
dvander@2226
   307
	}
dvander@2226
   308
dvander@2226
   309
	/* Link the bucket to its next (removing us). */
dvander@2226
   310
	*bucket = node->next;
dvander@2226
   311
dvander@2226
   312
	ke_CleanUpHashNode(table, node);
dvander@2226
   313
}
dvander@2226
   314
dvander@2226
   315
bool Knight::KE_FindInHashTable(KeHashTable *table, const void *key, void **value)
dvander@2226
   316
{
dvander@2226
   317
	KeHashNode *node;
dvander@2226
   318
	uint32_t key_hash;
dvander@2226
   319
	KeHashNode **bucket;
dvander@2226
   320
dvander@2226
   321
	key_hash = table->key_gen(key);
dvander@2226
   322
	bucket = ke_HashInternalFind(table, key_hash, key);
dvander@2226
   323
dvander@2226
   324
	if ((node = *bucket) == NULL)
dvander@2226
   325
	{
dvander@2226
   326
		return false;
dvander@2226
   327
	}
dvander@2226
   328
dvander@2226
   329
	if (value != NULL)
dvander@2226
   330
	{
dvander@2226
   331
		*value = node->value;
dvander@2226
   332
	}
dvander@2226
   333
dvander@2226
   334
	return true;
dvander@2226
   335
}
dvander@2226
   336
dvander@2226
   337
void Knight::KE_DestroyHashTable(KeHashTable *table)
dvander@2226
   338
{
dvander@2226
   339
	KeHashNode *node, *next;
dvander@2226
   340
dvander@2226
   341
	/* Turn off this caching! */
dvander@2226
   342
	table->keep_free_list = false;
dvander@2226
   343
dvander@2226
   344
	/* Find entries in buckets that need to be freed. */
dvander@2226
   345
	for (uint32_t i = 0; i < table->num_buckets; i++)
dvander@2226
   346
	{
dvander@2226
   347
		node = table->buckets[i];
dvander@2226
   348
dvander@2226
   349
		while (node != NULL)
dvander@2226
   350
		{
dvander@2226
   351
			next = node->next;
dvander@2226
   352
			ke_CleanUpHashNode(table, node);
dvander@2226
   353
			node = next;
dvander@2226
   354
		}
dvander@2226
   355
	}
dvander@2226
   356
dvander@2226
   357
	/* Free the free list */
dvander@2226
   358
	while (table->free_list != NULL)
dvander@2226
   359
	{
dvander@2226
   360
		next = table->free_list->next;
dvander@2226
   361
		ke_CleanUpHashNode(table, table->free_list);
dvander@2226
   362
		table->free_list = next;
dvander@2226
   363
	}
dvander@2226
   364
dvander@2226
   365
	/* Destroy everything now. */
dvander@2226
   366
	free(table->buckets);
dvander@2226
   367
	delete table;
dvander@2226
   368
}
dvander@2226
   369
dvander@2226
   370
void Knight::KE_ClearHashTable(KeHashTable *table)
dvander@2226
   371
{
dvander@2226
   372
	KeHashNode *node, *next;
dvander@2226
   373
dvander@2226
   374
	/* Free every entry in the table. */
dvander@2226
   375
	for (uint32_t i = 0; i < table->num_buckets; i++)
dvander@2226
   376
	{
dvander@2226
   377
		node = table->buckets[i];
dvander@2226
   378
dvander@2226
   379
		while (node != NULL)
dvander@2226
   380
		{
dvander@2226
   381
			next = node->next;
dvander@2226
   382
			ke_CleanUpHashNode(table, node);
dvander@2226
   383
			node = next;
dvander@2226
   384
		}
dvander@2226
   385
	}
dvander@2226
   386
}
dvander@2226
   387
dvander@2226
   388
#if defined _MSC_VER && (defined _M_IX86 || defined _M_AMD64 || defined _M_X64)
dvander@2226
   389
#pragma intrinsic(_rotl)
dvander@2226
   390
#endif
dvander@2226
   391
dvander@2226
   392
uint32_t Knight::KE_HashString(const void *str)
dvander@2226
   393
{
dvander@2226
   394
	uint32_t h;
dvander@2226
   395
	const unsigned char *us;
dvander@2226
   396
dvander@2226
   397
	h = 0;
dvander@2226
   398
dvander@2226
   399
	for (us = (const unsigned char *)str; *us != 0; us++)
dvander@2226
   400
	{
dvander@2226
   401
#if defined _MSC_VER && (defined _M_IX86 || defined _M_AMD64 || defined _M_X64)
dvander@2226
   402
		h = _rotl(h, 4) ^ *us;
dvander@2226
   403
#else
dvander@2226
   404
		h = ((h << 4) | (h >> 28)) ^ *us;
dvander@2226
   405
#endif
dvander@2226
   406
	}
dvander@2226
   407
dvander@2226
   408
	return h;
dvander@2226
   409
}