Landed sourcepawn-1.2. The big changes:
1) JIT compilation/optimization now occurs per-function, and only when functions are first used. We're now officially a whole-method JIT rather than an AOT compiler (albiet, still a simple JIT). This has two implications: Functions are now much better abstracted internally, and loading a plugin is now much less expensive. If a function contains calls to other functions, THOSE functions are only compiled when they're invoked as well. 2) I've removed debug mode. We always show full backtraces now, as there was a very cheap way to implement this which really cleaned up everything. This is great for a number of reasons -- there's less code, the JIT is better designed, we don't need to relocate debug tables, and best of all we no longer have to tell users to enable debug mode at their own expense. --HG-- extra : convert_revision : svn%3A39bc706e-5318-0410-9160-8a85361fbb7c/trunk%402459
This commit is contained in:
@@ -0,0 +1,428 @@
|
||||
#include <KePlatform.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
|
||||
#if defined KE_PLATFORM_WINDOWS
|
||||
#include <windows.h>
|
||||
#elif defined KE_PLATFORM_POSIX
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/mman.h>
|
||||
#else
|
||||
#error "TODO"
|
||||
#endif
|
||||
|
||||
#include "KeCodeAllocator.h"
|
||||
|
||||
#define ALIGNMENT 16
|
||||
|
||||
using namespace Knight;
|
||||
|
||||
struct KeFreedCode;
|
||||
|
||||
/**
|
||||
* Defines a region of memory that is made of pages.
|
||||
*/
|
||||
struct KeCodeRegion
|
||||
{
|
||||
KeCodeRegion *next;
|
||||
unsigned char *block_start;
|
||||
unsigned char *block_pos;
|
||||
KeFreedCode *free_list;
|
||||
size_t total_size;
|
||||
size_t end_free;
|
||||
size_t total_free;
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines freed code. We keep the size here because
|
||||
* when we touch the linked list we don't want to dirty pages.
|
||||
*/
|
||||
struct KeFreedCode
|
||||
{
|
||||
KeCodeRegion *region;
|
||||
unsigned char *block_start;
|
||||
size_t size;
|
||||
KeFreedCode *next;
|
||||
};
|
||||
|
||||
struct KeSecret
|
||||
{
|
||||
KeCodeRegion *region;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
class Knight::KeCodeCache
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* First region that is live for use.
|
||||
*/
|
||||
KeCodeRegion *first_live;
|
||||
|
||||
/**
|
||||
* First region that is full but has free entries.
|
||||
*/
|
||||
KeCodeRegion *first_partial;
|
||||
|
||||
/**
|
||||
* First region that is full.
|
||||
*/
|
||||
KeCodeRegion *first_full;
|
||||
|
||||
/**
|
||||
* Page granularity and size.
|
||||
*/
|
||||
unsigned int page_size;
|
||||
unsigned int page_granularity;
|
||||
|
||||
/**
|
||||
* This isn't actually for code, this is the node cache.
|
||||
*/
|
||||
KeCodeRegion *node_cache;
|
||||
KeFreedCode *free_node_list;
|
||||
};
|
||||
|
||||
KeCodeCache *Knight::KE_CreateCodeCache()
|
||||
{
|
||||
KeCodeCache *cache;
|
||||
|
||||
cache = new KeCodeCache;
|
||||
|
||||
memset(cache, 0, sizeof(KeCodeCache));
|
||||
|
||||
#if defined KE_PLATFORM_WINDOWS
|
||||
SYSTEM_INFO info;
|
||||
|
||||
GetSystemInfo(&info);
|
||||
cache->page_size = info.dwPageSize;
|
||||
cache->page_granularity = info.dwAllocationGranularity;
|
||||
#else
|
||||
cache->page_size = cache->page_granularity = sysconf(_SC_PAGESIZE);
|
||||
#endif
|
||||
|
||||
return cache;
|
||||
}
|
||||
|
||||
inline size_t MinAllocSize()
|
||||
{
|
||||
size_t size;
|
||||
|
||||
size = sizeof(KeSecret);
|
||||
size += ALIGNMENT;
|
||||
size -= size % ALIGNMENT;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
inline size_t ke_GetAllocSize(size_t size)
|
||||
{
|
||||
size += sizeof(KeSecret);
|
||||
size += ALIGNMENT;
|
||||
size -= size % ALIGNMENT;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
void *ke_AllocInRegion(KeCodeCache *cache,
|
||||
KeCodeRegion **prev,
|
||||
KeCodeRegion *region,
|
||||
unsigned char *ptr,
|
||||
size_t alloc_size,
|
||||
bool is_live)
|
||||
{
|
||||
KeSecret *secret;
|
||||
|
||||
/* Squirrel some info in the alloc. */
|
||||
secret = (KeSecret *)ptr;
|
||||
secret->region = region;
|
||||
secret->size = alloc_size;
|
||||
ptr += sizeof(KeSecret);
|
||||
|
||||
region->total_free -= alloc_size;
|
||||
|
||||
/* Check if we can't use the fast-path anymore. */
|
||||
if ((is_live && region->end_free < MinAllocSize())
|
||||
|| (!is_live && region->total_free < MinAllocSize()))
|
||||
{
|
||||
KeCodeRegion **start;
|
||||
|
||||
*prev = region->next;
|
||||
|
||||
/* Select the appropriate arena. */
|
||||
if (is_live)
|
||||
{
|
||||
if (region->total_free < MinAllocSize())
|
||||
{
|
||||
start = &cache->first_full;
|
||||
}
|
||||
else
|
||||
{
|
||||
start = &cache->first_partial;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
start = &cache->first_full;
|
||||
}
|
||||
|
||||
region->next = *start;
|
||||
*start = region;
|
||||
}
|
||||
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void *ke_AllocFromLive(KeCodeCache *cache, size_t size)
|
||||
{
|
||||
void *ptr;
|
||||
size_t alloc_size;
|
||||
KeCodeRegion *region, **prev;
|
||||
|
||||
region = cache->first_live;
|
||||
prev = &cache->first_live;
|
||||
alloc_size = ke_GetAllocSize(size);
|
||||
|
||||
while (region != NULL)
|
||||
{
|
||||
if (region->end_free >= alloc_size)
|
||||
{
|
||||
/* Yay! We can do a simple alloc here. */
|
||||
ptr = ke_AllocInRegion(cache, prev, region, region->block_pos, alloc_size, true);
|
||||
|
||||
/* Update our counters. */
|
||||
region->block_pos += alloc_size;
|
||||
region->end_free -= alloc_size;
|
||||
|
||||
return ptr;
|
||||
}
|
||||
prev = ®ion->next;
|
||||
region = region->next;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void *ke_AllocFromPartial(KeCodeCache *cache, size_t size)
|
||||
{
|
||||
void *ptr;
|
||||
size_t alloc_size;
|
||||
KeCodeRegion *region, **prev;
|
||||
|
||||
region = cache->first_partial;
|
||||
prev = &cache->first_partial;
|
||||
alloc_size = ke_GetAllocSize(size);
|
||||
|
||||
while (region != NULL)
|
||||
{
|
||||
if (region->total_free >= alloc_size)
|
||||
{
|
||||
KeFreedCode *node, **last;
|
||||
|
||||
assert(region->free_list != NULL);
|
||||
|
||||
last = ®ion->free_list;
|
||||
node = region->free_list;
|
||||
while (node != NULL)
|
||||
{
|
||||
if (node->size >= alloc_size)
|
||||
{
|
||||
/* Use this node */
|
||||
ptr = ke_AllocInRegion(cache, prev, region, node->block_start, alloc_size, false);
|
||||
|
||||
region->total_free -= node->size;
|
||||
*last = node->next;
|
||||
|
||||
/* Make sure bookkeepping is correct. */
|
||||
assert((region->free_list == NULL && region->total_free == 0)
|
||||
|| (region->free_list != NULL && region->total_free != 0));
|
||||
|
||||
/* Link us back into the free node list. */
|
||||
node->next = cache->free_node_list;
|
||||
cache->free_node_list = node->next;
|
||||
|
||||
return ptr;
|
||||
}
|
||||
last = &node->next;
|
||||
node = node->next;
|
||||
}
|
||||
}
|
||||
prev = ®ion->next;
|
||||
region = region->next;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
KeCodeRegion *ke_AddRegionForSize(KeCodeCache *cache, size_t size)
|
||||
{
|
||||
KeCodeRegion *region;
|
||||
|
||||
region = new KeCodeRegion;
|
||||
|
||||
size = ke_GetAllocSize(size);
|
||||
size += cache->page_granularity * 2;
|
||||
size -= size % cache->page_granularity;
|
||||
|
||||
#if defined KE_PLATFORM_WINDOWS
|
||||
region->block_start = (unsigned char *)VirtualAlloc(NULL, size, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);
|
||||
#elif defined KE_PLATFORM_POSIX
|
||||
region->block_start = (unsigned char *)valloc(size);
|
||||
if (mprotect(region->block_start, size, PROT_READ|PROT_WRITE|PROT_EXEC) == -1)
|
||||
{
|
||||
free(region->block_start);
|
||||
delete region;
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
#error "TODO"
|
||||
#endif
|
||||
|
||||
if (region->block_start == NULL)
|
||||
{
|
||||
delete region;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
region->block_pos = region->block_start;
|
||||
region->end_free = region->total_free = region->total_size = size;
|
||||
region->next = cache->first_live;
|
||||
cache->first_live = region;
|
||||
region->free_list = NULL;
|
||||
|
||||
return region;
|
||||
}
|
||||
|
||||
void *Knight::KE_AllocCode(KeCodeCache *cache, size_t size)
|
||||
{
|
||||
void *ptr;
|
||||
|
||||
/* Check live easy-adds */
|
||||
if (cache->first_live != NULL)
|
||||
{
|
||||
if ((ptr = ke_AllocFromLive(cache, size)) != NULL)
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Try looking in the free lists */
|
||||
if (cache->first_partial != NULL)
|
||||
{
|
||||
if ((ptr = ke_AllocFromPartial(cache, size)) != NULL)
|
||||
{
|
||||
return ptr;
|
||||
}
|
||||
}
|
||||
|
||||
/* Create a new region */
|
||||
if (ke_AddRegionForSize(cache, size) == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return ke_AllocFromLive(cache, size);
|
||||
}
|
||||
|
||||
KeFreedCode *ke_GetFreeNode(KeCodeCache *cache)
|
||||
{
|
||||
KeFreedCode *ret;
|
||||
|
||||
if (cache->free_node_list != NULL)
|
||||
{
|
||||
ret = cache->free_node_list;
|
||||
cache->free_node_list = ret->next;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* See if the current free node region has space. */
|
||||
if (cache->node_cache != NULL
|
||||
&& cache->node_cache->end_free >= sizeof(KeFreedCode))
|
||||
{
|
||||
ret = (KeFreedCode *)cache->node_cache->block_pos;
|
||||
cache->node_cache->block_pos += sizeof(KeFreedCode);
|
||||
cache->node_cache->total_free -= sizeof(KeFreedCode);
|
||||
cache->node_cache->end_free -= sizeof(KeFreedCode);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Otherwise, we need to alloc a new region. */
|
||||
KeCodeRegion *region = new KeCodeRegion;
|
||||
|
||||
region->block_start = new unsigned char[cache->page_size / sizeof(KeFreedCode)];
|
||||
region->block_pos = region->block_start + sizeof(KeFreedCode);
|
||||
region->total_size = cache->page_size / sizeof(KeFreedCode);
|
||||
region->total_free = region->end_free = (region->total_size - sizeof(KeFreedCode));
|
||||
region->free_list = NULL;
|
||||
region->next = cache->node_cache;
|
||||
cache->node_cache = region;
|
||||
|
||||
return (KeFreedCode *)region->block_start;
|
||||
}
|
||||
|
||||
void Knight::KE_FreeCode(KeCodeCache *cache, void *code)
|
||||
{
|
||||
KeSecret *secret;
|
||||
KeFreedCode *node;
|
||||
unsigned char *ptr;
|
||||
KeCodeRegion *region;
|
||||
|
||||
ptr = (unsigned char *)code;
|
||||
secret = (KeSecret *)(ptr - sizeof(KeSecret));
|
||||
region = secret->region;
|
||||
node = ke_GetFreeNode(cache);
|
||||
node->block_start = (unsigned char *)code;
|
||||
node->next = region->free_list;
|
||||
region->free_list = node;
|
||||
node->region = region;
|
||||
node->size = secret->size;
|
||||
}
|
||||
|
||||
KeCodeRegion *ke_DestroyRegion(KeCodeRegion *region)
|
||||
{
|
||||
KeCodeRegion *next;
|
||||
|
||||
next = region->next;
|
||||
|
||||
#if defined KE_PLATFORM_WINDOWS
|
||||
VirtualFree(region->block_start, 0, MEM_RELEASE);
|
||||
#else
|
||||
free(region->block_start);
|
||||
#endif
|
||||
|
||||
delete region;
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
void ke_DestroyRegionChain(KeCodeRegion *first)
|
||||
{
|
||||
while (first != NULL)
|
||||
{
|
||||
first = ke_DestroyRegion(first);
|
||||
}
|
||||
}
|
||||
|
||||
void Knight::KE_DestroyCodeCache(KeCodeCache *cache)
|
||||
{
|
||||
/* Destroy every region and call it a day. */
|
||||
ke_DestroyRegionChain(cache->first_full);
|
||||
ke_DestroyRegionChain(cache->first_live);
|
||||
ke_DestroyRegionChain(cache->first_partial);
|
||||
|
||||
/* We use normal malloc for node cache regions */
|
||||
KeCodeRegion *region, *next;
|
||||
|
||||
region = cache->node_cache;
|
||||
while (region != NULL)
|
||||
{
|
||||
next = region->next;
|
||||
delete [] region->block_start;
|
||||
delete region;
|
||||
region = next;
|
||||
}
|
||||
|
||||
delete cache;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
#ifndef _INCLUDE_KNIGHT_KE_CODE_ALLOCATOR_H_
|
||||
#define _INCLUDE_KNIGHT_KE_CODE_ALLOCATOR_H_
|
||||
|
||||
#include <KeLinking.h>
|
||||
#include <KeStdTypes.h>
|
||||
|
||||
namespace Knight
|
||||
{
|
||||
class KeCodeCache;
|
||||
|
||||
/**
|
||||
* @brief Creates a new code cache/allocator.
|
||||
*
|
||||
* @return New code cache allocator.
|
||||
*/
|
||||
extern KeCodeCache *KE_CreateCodeCache();
|
||||
|
||||
/**
|
||||
* @brief Destroys a code cache allocator.
|
||||
*
|
||||
* @param cache Code cache object.
|
||||
*/
|
||||
extern void KE_DestroyCodeCache(KeCodeCache *cache);
|
||||
|
||||
/**
|
||||
* @brief Allocates code memory that is readable, writable,
|
||||
* and executable.
|
||||
*
|
||||
* The address returned wlil be aligned, minimally, on a 16-byte
|
||||
* boundary.
|
||||
*
|
||||
* @param cache Code cache object.
|
||||
* @param size Amount of memory needed.
|
||||
* @return Address pointing to the memory.
|
||||
*/
|
||||
extern void *KE_AllocCode(KeCodeCache *cache, size_t size);
|
||||
|
||||
/**
|
||||
* @brief Frees code memory.
|
||||
*
|
||||
* @param cache Code cache object.
|
||||
* @param code Address of code memory.
|
||||
*/
|
||||
extern void KE_FreeCode(KeCodeCache *cache, void *code);
|
||||
}
|
||||
|
||||
#endif //_INCLUDE_KNIGHT_KE_CODE_ALLOCATOR_H_
|
||||
@@ -0,0 +1,33 @@
|
||||
#include <stdio.h>
|
||||
#include "KeCommon.h"
|
||||
|
||||
using namespace Knight;
|
||||
|
||||
size_t Knight::KE_PFormat(char *buffer, size_t maxlength, const char *fmt, ...)
|
||||
{
|
||||
size_t len;
|
||||
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
len = KE_PFormatArgs(buffer, maxlength, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
size_t Knight::KE_PFormatArgs(char *buffer, size_t maxlength, const char *fmt, va_list ap)
|
||||
{
|
||||
size_t len;
|
||||
|
||||
len = vsnprintf(buffer, maxlength, fmt, ap);
|
||||
|
||||
if (len >= maxlength)
|
||||
{
|
||||
buffer[maxlength - 1] = '\0';
|
||||
return (maxlength - 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
return len;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#ifndef _INCLUDE_KNIGHT_KE_COMMON_UTILS_H_
|
||||
#define _INCLUDE_KNIGHT_KE_COMMON_UTILS_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
namespace Knight
|
||||
{
|
||||
/**
|
||||
* @brief Formats a buffer with C platform rules.
|
||||
*
|
||||
* Unlink platform snprintf, this will never return nonsense values like -1.
|
||||
*
|
||||
* @param buffer Buffer to store to.
|
||||
* @param maxlength Maximum length of buffer (including null terminator).
|
||||
* @param fmt printf() format string.
|
||||
* @param ... Formatting arguments.
|
||||
* @return Number of characters written.
|
||||
*/
|
||||
extern size_t KE_PFormat(char *buffer, size_t maxlength, const char *fmt, ...);
|
||||
|
||||
/**
|
||||
* @brief Formats a buffer with C platform rules.
|
||||
*
|
||||
* Unlink platform snprintf, this will never return nonsense values like -1.
|
||||
*
|
||||
* @param buffer Buffer to store to.
|
||||
* @param maxlength Maximum length of buffer (including null terminator).
|
||||
* @param fmt printf() format string.
|
||||
* @param args Formatting arguments.
|
||||
* @return Number of characters written.
|
||||
*/
|
||||
extern size_t KE_PFormatArgs(char *buffer, size_t maxlength, const char *fmt, va_list args);
|
||||
}
|
||||
|
||||
#endif //_INCLUDE_KNIGHT_KE_COMMON_UTILS_H_
|
||||
@@ -0,0 +1,409 @@
|
||||
#include <KeHashTable.h>
|
||||
|
||||
using namespace Knight;
|
||||
|
||||
struct KeHashNode
|
||||
{
|
||||
KeHashNode *next;
|
||||
uint32_t key_hash;
|
||||
const void *key;
|
||||
void *value;
|
||||
};
|
||||
|
||||
namespace Knight
|
||||
{
|
||||
class KeHashTable
|
||||
{
|
||||
public:
|
||||
KeHashNode **buckets;
|
||||
uint32_t num_buckets;
|
||||
uint32_t shift;
|
||||
uint32_t num_entries;
|
||||
KeHashGenerator key_gen;
|
||||
KeHashMarshal key_m;
|
||||
KeHashMarshal val_m;
|
||||
ke_allocator_t *node_alloc;
|
||||
size_t key_offs;
|
||||
size_t val_offs;
|
||||
size_t node_size;
|
||||
uint32_t grow_limit;
|
||||
KeHashNode *free_list;
|
||||
bool keep_free_list;
|
||||
};
|
||||
}
|
||||
|
||||
void *ke_DefHashMalloc(ke_allocator_t *alloc, size_t amt)
|
||||
{
|
||||
return malloc(amt);
|
||||
}
|
||||
|
||||
void ke_DefHashFree(ke_allocator_t *alloc, void *addr)
|
||||
{
|
||||
free(addr);
|
||||
}
|
||||
|
||||
ke_allocator_t s_DefHashAllocator =
|
||||
{
|
||||
ke_DefHashMalloc,
|
||||
ke_DefHashFree,
|
||||
NULL
|
||||
};
|
||||
|
||||
KeHashTable *Knight::KE_CreateHashTable(
|
||||
uint32_t bits,
|
||||
KeHashGenerator key_gen,
|
||||
const KeHashMarshal *key_marshal,
|
||||
const KeHashMarshal *val_marshal,
|
||||
ke_allocator_t *nodeAlloc,
|
||||
bool keep_free_list)
|
||||
{
|
||||
KeHashTable *table;
|
||||
|
||||
if (bits >= 27)
|
||||
{
|
||||
bits = 26;
|
||||
}
|
||||
else if (bits < 4)
|
||||
{
|
||||
bits = 4;
|
||||
}
|
||||
|
||||
/* Validate marshals. */
|
||||
if ((key_marshal->bytes != 0
|
||||
&& key_marshal->ctor == NULL)
|
||||
|| (val_marshal->bytes != 0
|
||||
&& val_marshal->ctor == NULL))
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
table = new KeHashTable;
|
||||
table->key_gen = key_gen;
|
||||
table->key_m = *key_marshal;
|
||||
table->val_m = *val_marshal;
|
||||
table->num_entries = 0;
|
||||
table->shift = bits;
|
||||
table->node_alloc = nodeAlloc == NULL ? &s_DefHashAllocator : nodeAlloc;
|
||||
table->num_buckets = (1 << bits);
|
||||
table->grow_limit = (uint32_t)(0.9f * table->num_buckets);
|
||||
table->keep_free_list = keep_free_list;
|
||||
table->free_list = NULL;
|
||||
table->buckets = (KeHashNode **)malloc(sizeof(KeHashNode *) * table->num_buckets);
|
||||
memset(table->buckets, 0, sizeof(KeHashNode *) * table->num_buckets);
|
||||
|
||||
table->key_offs = sizeof(KeHashNode);
|
||||
if (table->key_m.bytes != 0 && table->key_m.bytes % 8 != 0)
|
||||
{
|
||||
table->key_m.bytes += 8;
|
||||
table->key_m.bytes -= (table->key_m.bytes % 8);
|
||||
}
|
||||
|
||||
table->val_offs = table->key_offs + table->key_m.bytes;
|
||||
table->node_size = table->val_offs + table->val_m.bytes;
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
#define KE_GET_BUCKET(tbl, hsh) (&(tbl)->buckets[((hsh) * 0x9E3779B9) >> (tbl)->shift])
|
||||
|
||||
KeHashNode **ke_HashInternalFind(KeHashTable *table, uint32_t key_hash, const void *key)
|
||||
{
|
||||
KeHashNode *node;
|
||||
KeHashNode **bucket;
|
||||
|
||||
bucket = KE_GET_BUCKET(table, key_hash);
|
||||
|
||||
/* :TODO: move to the front once found? */
|
||||
|
||||
while ((node = *bucket) != NULL)
|
||||
{
|
||||
if (node->key_hash == key_hash
|
||||
&& ((table->key_m.cmp != NULL && table->key_m.cmp(node->key, key))
|
||||
|| node->key == key))
|
||||
{
|
||||
return bucket;
|
||||
}
|
||||
bucket = &node->next;
|
||||
}
|
||||
|
||||
return bucket;
|
||||
}
|
||||
|
||||
void ke_ResizeHashTable(KeHashTable *table, uint32_t new_shift)
|
||||
{
|
||||
uint32_t entries;
|
||||
KeHashNode *next;
|
||||
KeHashNode *node;
|
||||
KeHashNode **rbucket;
|
||||
KeHashNode **old_buckets;
|
||||
uint32_t old_num_buckets;
|
||||
|
||||
/* Save old data */
|
||||
old_num_buckets = table->num_buckets;
|
||||
old_buckets = table->buckets;
|
||||
entries = table->num_entries;
|
||||
|
||||
/* Save new data */
|
||||
table->num_buckets = (1 << new_shift);
|
||||
table->shift = new_shift;
|
||||
table->grow_limit = (uint32_t)(0.9f * table->num_buckets);
|
||||
|
||||
table->buckets = (KeHashNode **)malloc(sizeof(KeHashNode *) * table->num_buckets);
|
||||
memset(table->buckets, 0, sizeof(KeHashNode *) * table->num_buckets);
|
||||
|
||||
/* For each old bucket... */
|
||||
for (uint32_t i = 0;
|
||||
i < old_num_buckets && entries != 0;
|
||||
i++)
|
||||
{
|
||||
node = old_buckets[i];
|
||||
/* Get each item in its list... */
|
||||
while (node != NULL)
|
||||
{
|
||||
next = node->next;
|
||||
|
||||
/* Find the new replacement bucket it needs to go in. */
|
||||
rbucket = KE_GET_BUCKET(table, node->key_hash);
|
||||
|
||||
/* Link this node to the next node in the new bucket. */
|
||||
if (*rbucket == NULL)
|
||||
{
|
||||
node->next = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
node->next = *rbucket;
|
||||
}
|
||||
|
||||
/* Add us to the front of that bucket's list. */
|
||||
*rbucket = node;
|
||||
|
||||
node = next;
|
||||
}
|
||||
}
|
||||
|
||||
free(old_buckets);
|
||||
}
|
||||
|
||||
void Knight::KE_AddToHashTable(KeHashTable *table, const void *key, void *val)
|
||||
{
|
||||
KeHashNode *node;
|
||||
uint32_t key_hash;
|
||||
KeHashNode **bucket;
|
||||
|
||||
key_hash = table->key_gen(key);
|
||||
bucket = ke_HashInternalFind(table, key_hash, key);
|
||||
|
||||
if ((node = *bucket) != NULL)
|
||||
{
|
||||
/* Already in the table */
|
||||
if ((table->val_m.cmp != NULL && table->val_m.cmp(node->value, val))
|
||||
|| node->value == val)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* Destroy old value if it's set. */
|
||||
if (node->value != NULL && table->val_m.dtor != NULL)
|
||||
{
|
||||
table->val_m.dtor(node->value);
|
||||
}
|
||||
|
||||
/* Construct or set the new value. */
|
||||
if (table->val_m.bytes != 0)
|
||||
{
|
||||
table->val_m.ctor(node->value, val);
|
||||
}
|
||||
else
|
||||
{
|
||||
node->value = val;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/* If we're overloaded, we may need to resize.
|
||||
* Right now, we do this if we hit a .9 entry:buckets ratio.
|
||||
*/
|
||||
if (table->num_entries >= table->grow_limit)
|
||||
{
|
||||
ke_ResizeHashTable(table, table->shift << 1);
|
||||
bucket = ke_HashInternalFind(table, key_hash, key);
|
||||
}
|
||||
|
||||
if (table->free_list != NULL)
|
||||
{
|
||||
node = table->free_list;
|
||||
table->free_list = node->next;
|
||||
}
|
||||
else
|
||||
{
|
||||
node = (KeHashNode *)table->node_alloc->alloc(table->node_alloc, table->node_size);
|
||||
}
|
||||
|
||||
if (table->key_m.bytes == 0)
|
||||
{
|
||||
node->key = key;
|
||||
}
|
||||
else
|
||||
{
|
||||
node->key = (char *)node + table->key_offs;
|
||||
table->key_m.ctor((void *)node->key, key);
|
||||
}
|
||||
|
||||
if (table->val_m.bytes == 0)
|
||||
{
|
||||
node->value = val;
|
||||
}
|
||||
else
|
||||
{
|
||||
node->value = (char *)node + table->val_offs;
|
||||
table->val_m.ctor(node->value, val);
|
||||
}
|
||||
|
||||
node->next = *bucket;
|
||||
node->key_hash = key_hash;
|
||||
*bucket = node;
|
||||
}
|
||||
|
||||
inline void ke_CleanUpHashNode(KeHashTable *table, KeHashNode *node)
|
||||
{
|
||||
/* Destroy old value if it's set. */
|
||||
if (node->value != NULL && table->val_m.dtor != NULL)
|
||||
{
|
||||
table->val_m.dtor(node->value);
|
||||
}
|
||||
|
||||
/* Destroy the key. */
|
||||
if (table->key_m.dtor != NULL)
|
||||
{
|
||||
table->key_m.dtor(node->key);
|
||||
}
|
||||
|
||||
/* Deallocate us as appropriate. */
|
||||
if (table->keep_free_list)
|
||||
{
|
||||
node->next = table->free_list;
|
||||
table->free_list = node;
|
||||
}
|
||||
else
|
||||
{
|
||||
table->node_alloc->dealloc(table->node_alloc, node);
|
||||
}
|
||||
}
|
||||
|
||||
void Knight::KE_RemoveFromHashTable(KeHashTable *table, const void *key)
|
||||
{
|
||||
KeHashNode *node;
|
||||
uint32_t key_hash;
|
||||
KeHashNode **bucket;
|
||||
|
||||
key_hash = table->key_gen(key);
|
||||
bucket = ke_HashInternalFind(table, key_hash, key);
|
||||
|
||||
if ((node = *bucket) == NULL)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
/* Link the bucket to its next (removing us). */
|
||||
*bucket = node->next;
|
||||
|
||||
ke_CleanUpHashNode(table, node);
|
||||
}
|
||||
|
||||
bool Knight::KE_FindInHashTable(KeHashTable *table, const void *key, void **value)
|
||||
{
|
||||
KeHashNode *node;
|
||||
uint32_t key_hash;
|
||||
KeHashNode **bucket;
|
||||
|
||||
key_hash = table->key_gen(key);
|
||||
bucket = ke_HashInternalFind(table, key_hash, key);
|
||||
|
||||
if ((node = *bucket) == NULL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value != NULL)
|
||||
{
|
||||
*value = node->value;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Knight::KE_DestroyHashTable(KeHashTable *table)
|
||||
{
|
||||
KeHashNode *node, *next;
|
||||
|
||||
/* Turn off this caching! */
|
||||
table->keep_free_list = false;
|
||||
|
||||
/* Find entries in buckets that need to be freed. */
|
||||
for (uint32_t i = 0; i < table->num_buckets; i++)
|
||||
{
|
||||
node = table->buckets[i];
|
||||
|
||||
while (node != NULL)
|
||||
{
|
||||
next = node->next;
|
||||
ke_CleanUpHashNode(table, node);
|
||||
node = next;
|
||||
}
|
||||
}
|
||||
|
||||
/* Free the free list */
|
||||
while (table->free_list != NULL)
|
||||
{
|
||||
next = table->free_list->next;
|
||||
ke_CleanUpHashNode(table, table->free_list);
|
||||
table->free_list = next;
|
||||
}
|
||||
|
||||
/* Destroy everything now. */
|
||||
free(table->buckets);
|
||||
delete table;
|
||||
}
|
||||
|
||||
void Knight::KE_ClearHashTable(KeHashTable *table)
|
||||
{
|
||||
KeHashNode *node, *next;
|
||||
|
||||
/* Free every entry in the table. */
|
||||
for (uint32_t i = 0; i < table->num_buckets; i++)
|
||||
{
|
||||
node = table->buckets[i];
|
||||
|
||||
while (node != NULL)
|
||||
{
|
||||
next = node->next;
|
||||
ke_CleanUpHashNode(table, node);
|
||||
node = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#if defined _MSC_VER && (defined _M_IX86 || defined _M_AMD64 || defined _M_X64)
|
||||
#pragma intrinsic(_rotl)
|
||||
#endif
|
||||
|
||||
uint32_t Knight::KE_HashString(const void *str)
|
||||
{
|
||||
uint32_t h;
|
||||
const unsigned char *us;
|
||||
|
||||
h = 0;
|
||||
|
||||
for (us = (const unsigned char *)str; *us != 0; us++)
|
||||
{
|
||||
#if defined _MSC_VER && (defined _M_IX86 || defined _M_AMD64 || defined _M_X64)
|
||||
h = _rotl(h, 4) ^ *us;
|
||||
#else
|
||||
h = ((h << 4) | (h >> 28)) ^ *us;
|
||||
#endif
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
#ifndef _INCLUDE_KNIGHT_KE_HASHTABLE_H_
|
||||
#define _INCLUDE_KNIGHT_KE_HASHTABLE_H_
|
||||
|
||||
#include <KeStdTypes.h>
|
||||
#include <KnightAllocator.h>
|
||||
|
||||
namespace Knight
|
||||
{
|
||||
class KeHashTable;
|
||||
|
||||
/**
|
||||
* @brief Must generate a hash function given a key.
|
||||
*
|
||||
* @param key Pointer to the key.
|
||||
* @return Hash value.
|
||||
*/
|
||||
typedef uint32_t (*KeHashGenerator)(const void *key);
|
||||
|
||||
/**
|
||||
* @brief Must compare two values.
|
||||
*
|
||||
* @param val1 First value.
|
||||
* @param val2 Second value.
|
||||
* @return True if equal, false if not.
|
||||
*/
|
||||
typedef bool (*KeHashComparator)(const void *val1, const void *val2);
|
||||
|
||||
/**
|
||||
* @brief Must call the destructor of the given data, and free if necessary.
|
||||
*
|
||||
* @param val Pointer to data.
|
||||
*/
|
||||
typedef void (*KeHashDestructor)(const void *val);
|
||||
|
||||
/**
|
||||
* @brief Must transfer the contents of an object from the source to the destination.
|
||||
*
|
||||
* @param dest Destination address.
|
||||
* @param source Source address.
|
||||
*/
|
||||
typedef void (*KeHashCopyCtor)(void *dest, const void *source);
|
||||
|
||||
/**
|
||||
* @brief Contains information about how to process keys and values in a hash table.
|
||||
*/
|
||||
struct KeHashMarshal
|
||||
{
|
||||
size_t bytes; /**< Bytes of storage needed (0 to use pointers). */
|
||||
KeHashComparator cmp; /**< Comparator (if NULL, void * comparison used) */
|
||||
KeHashDestructor dtor; /**< Optional function for performing dtor cleanup. */
|
||||
KeHashCopyCtor ctor; /**< If bytes != 0, must be a valid function
|
||||
(ignored otherwise). */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Creates a new hash table structure.
|
||||
*
|
||||
* @param bits Dictates starting number of buckets as a power of two.
|
||||
* Pass 0 for the default (which is 4).
|
||||
* @param key_gen Key generation function.
|
||||
* @param key_marshal Structure detailing how to marshal keys.
|
||||
* @param vak_marshal Structure detailing how to marshal values.
|
||||
* @param nodeAlloc Node allocator (can be NULL for malloc/free).
|
||||
* @param keep_free_list True to keep a free list of nodes, false otherwise.
|
||||
* @return New hash table container.
|
||||
*/
|
||||
extern KeHashTable *KE_CreateHashTable(
|
||||
uint32_t bits,
|
||||
KeHashGenerator key_gen,
|
||||
const KeHashMarshal *key_marshal,
|
||||
const KeHashMarshal *val_marshal,
|
||||
ke_allocator_t *nodeAlloc,
|
||||
bool keep_free_list
|
||||
);
|
||||
|
||||
/**
|
||||
* @brief Destroys a hash table.
|
||||
*
|
||||
* @param table Hash table.
|
||||
*/
|
||||
extern void KE_DestroyHashTable(KeHashTable *table);
|
||||
|
||||
/**
|
||||
* @brief Adds a key/value to the hash table. If the pair already exists, the old value
|
||||
* is overwritten (calling any destructors as necessary).
|
||||
*
|
||||
* @param table Hash table.
|
||||
* @param key Key pointer.
|
||||
* @param val Value pointer.
|
||||
*/
|
||||
extern void KE_AddToHashTable(KeHashTable *table, const void *key, void *val);
|
||||
|
||||
/**
|
||||
* @brief Removes a key entry from the hash table.
|
||||
*
|
||||
* @param table Hash table.
|
||||
* @param key Key pointer.
|
||||
*/
|
||||
extern void KE_RemoveFromHashTable(KeHashTable *table, const void *key);
|
||||
|
||||
/**
|
||||
* @brief Finds an entry in the hash table.
|
||||
*
|
||||
* @param table Hash table.
|
||||
* @param key Key pointer.
|
||||
* @param value Pointer to store the value (optional).
|
||||
* @return On success, true is returned and value is filled if given.
|
||||
* On failure, false is failed and outputs are undefined.
|
||||
*/
|
||||
extern bool KE_FindInHashTable(KeHashTable *table, const void *key, void **value);
|
||||
|
||||
/**
|
||||
* @brief Clears all entries in the hash table (caching free entries when possible).
|
||||
*
|
||||
* @param table Hash table.
|
||||
*/
|
||||
extern void KE_ClearHashTable(KeHashTable *table);
|
||||
|
||||
/**
|
||||
* @brief Generic function for hasing strings.
|
||||
*
|
||||
* @param str Key string.
|
||||
* @return Hash value.
|
||||
*/
|
||||
extern uint32_t KE_HashString(const void *str);
|
||||
}
|
||||
|
||||
#endif //_INCLUDE_KNIGHT_KE_HASHTABLE_H_
|
||||
@@ -0,0 +1,12 @@
|
||||
#ifndef _INCLUDE_KNIGHT_LINKING_H_
|
||||
#define _INCLUDE_KNIGHT_LINKING_H_
|
||||
|
||||
#if defined KE_LINK_IMPORT
|
||||
#error "Not yet supported"
|
||||
#elif defined KE_LINK_EXPORT
|
||||
#error "Not yet supported"
|
||||
#else
|
||||
#define KE_LINK
|
||||
#endif
|
||||
|
||||
#endif //_INCLUDE_KNIGHT_LINKING_H_
|
||||
@@ -0,0 +1,258 @@
|
||||
#include "KePlatform.h"
|
||||
#include <KeLumpAllocator.h>
|
||||
#include <KeVector.h>
|
||||
|
||||
using namespace Knight;
|
||||
|
||||
/**
|
||||
* :TODO: don't make this part of the page, because touching it means
|
||||
* dirtying a page. Instead, we should have a separate linked list.
|
||||
* Maybe that linked list itself could be marshalled from one page.
|
||||
*/
|
||||
struct KeLumpRegion
|
||||
{
|
||||
char *base;
|
||||
char *cur;
|
||||
size_t size;
|
||||
size_t avail;
|
||||
KeLumpRegion *next;
|
||||
KeLumpRegion *prev;
|
||||
};
|
||||
|
||||
class KeLumpAllocator
|
||||
{
|
||||
public:
|
||||
KeLumpAllocator() : m_pUsableRegions(NULL), m_pUnusableRegions(NULL)
|
||||
{
|
||||
m_DefLumpSize = 65536;
|
||||
|
||||
#if defined KE_PLATFORM_WINDOWS
|
||||
SYSTEM_INFO info;
|
||||
|
||||
GetSystemInfo(&info);
|
||||
if (info.dwAllocationGranularity > m_DefLumpSize)
|
||||
{
|
||||
m_DefLumpSize = info.dwAllocationGranularity;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
~KeLumpAllocator()
|
||||
{
|
||||
FreeRegionChain(m_pUsableRegions);
|
||||
FreeRegionChain(m_pUnusableRegions);
|
||||
}
|
||||
|
||||
void Reset()
|
||||
{
|
||||
KeLumpRegion *region;
|
||||
|
||||
/* Find the tail of the usable regions. */
|
||||
region = m_pUsableRegions;
|
||||
while (region != NULL)
|
||||
{
|
||||
if (region->next == NULL)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Link the unusable chain into the usable chain. */
|
||||
if (region == NULL)
|
||||
{
|
||||
m_pUsableRegions = m_pUnusableRegions;
|
||||
}
|
||||
else
|
||||
{
|
||||
region->next = m_pUnusableRegions;
|
||||
m_pUnusableRegions->prev = region;
|
||||
}
|
||||
m_pUnusableRegions = NULL;
|
||||
|
||||
region = m_pUsableRegions;
|
||||
while (region != NULL)
|
||||
{
|
||||
region->avail = region->size;
|
||||
region->cur = region->base;
|
||||
region = region->next;
|
||||
}
|
||||
}
|
||||
|
||||
void FreeRegionChain(KeLumpRegion *region)
|
||||
{
|
||||
KeLumpRegion *next;
|
||||
|
||||
while (region != NULL)
|
||||
{
|
||||
next = region->next;
|
||||
|
||||
#if defined KE_PLATFORM_WINDOWS
|
||||
VirtualFree(region, 0, MEM_RELEASE);
|
||||
#endif
|
||||
region = next;
|
||||
}
|
||||
}
|
||||
|
||||
void *Alloc(size_t size)
|
||||
{
|
||||
char *blob;
|
||||
KeLumpRegion *region;
|
||||
|
||||
if (size % 8 != 0)
|
||||
{
|
||||
size += 8;
|
||||
size -= size % 8;
|
||||
}
|
||||
|
||||
region = FindRegionForSize(size);
|
||||
|
||||
blob = region->cur;
|
||||
region->avail -= size;
|
||||
region->cur += size;
|
||||
|
||||
/**
|
||||
* Technically we could make one last small allocation, but
|
||||
* this edge case is not worth the extra work.
|
||||
*/
|
||||
if (region->avail < 8)
|
||||
{
|
||||
/* Unlink us from the usable list */
|
||||
if (region == m_pUsableRegions)
|
||||
{
|
||||
m_pUsableRegions = m_pUsableRegions->next;
|
||||
m_pUsableRegions->prev = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
region->prev->next = region->next;
|
||||
if (region->next != NULL)
|
||||
{
|
||||
region->next->prev = region->prev;
|
||||
}
|
||||
}
|
||||
|
||||
/* Link us into the unusable list */
|
||||
region->prev = NULL;
|
||||
region->next = m_pUnusableRegions;
|
||||
|
||||
if (m_pUnusableRegions != NULL)
|
||||
{
|
||||
m_pUnusableRegions->prev = region;
|
||||
}
|
||||
|
||||
m_pUnusableRegions = region;
|
||||
}
|
||||
|
||||
return blob;
|
||||
}
|
||||
|
||||
private:
|
||||
KeLumpRegion *FindRegionForSize(size_t size)
|
||||
{
|
||||
char *base;
|
||||
KeLumpRegion *region;
|
||||
size_t size_of_region;
|
||||
|
||||
/**
|
||||
* :TODO: replace this with a priority queue or something
|
||||
* that's actually fast. Even worse is we dirty pages by
|
||||
* doing this. Ouch!
|
||||
*/
|
||||
region = m_pUsableRegions;
|
||||
while (region != NULL)
|
||||
{
|
||||
if (region->avail >= size)
|
||||
{
|
||||
return region;
|
||||
}
|
||||
region = region->next;
|
||||
}
|
||||
|
||||
/* Make sure regions end at 8-byte alignment. */
|
||||
size_of_region = sizeof(KeLumpRegion);
|
||||
if (size_of_region % 8 != 0)
|
||||
{
|
||||
size_of_region += 8;
|
||||
size_of_region -= size_of_region % 8;
|
||||
}
|
||||
|
||||
/* If the size is too big, fix that. */
|
||||
if (size > m_DefLumpSize - size_of_region)
|
||||
{
|
||||
size += m_DefLumpSize;
|
||||
size -= size % m_DefLumpSize;
|
||||
}
|
||||
else
|
||||
{
|
||||
size = m_DefLumpSize;
|
||||
}
|
||||
|
||||
#if defined KE_PLATFORM_WINDOWS
|
||||
base = (char *)VirtualAlloc(
|
||||
NULL,
|
||||
m_DefLumpSize,
|
||||
MEM_COMMIT|MEM_RESERVE,
|
||||
PAGE_READWRITE);
|
||||
#endif
|
||||
|
||||
/* Initialize the region */
|
||||
region = (KeLumpRegion *)base;
|
||||
region->base = &base[size_of_region];
|
||||
region->size = size - size_of_region;
|
||||
region->cur = region->base;
|
||||
region->avail = region->size;
|
||||
region->prev = NULL;
|
||||
region->next = m_pUsableRegions;
|
||||
|
||||
if (m_pUsableRegions != NULL)
|
||||
{
|
||||
m_pUsableRegions->prev = region;
|
||||
}
|
||||
|
||||
m_pUsableRegions = region;
|
||||
|
||||
return region;
|
||||
}
|
||||
|
||||
private:
|
||||
KeLumpRegion *m_pUsableRegions;
|
||||
KeLumpRegion *m_pUnusableRegions;
|
||||
size_t m_DefLumpSize;
|
||||
};
|
||||
|
||||
inline KeLumpAllocator *ke_LumpFromAllocator(ke_allocator_t *arena)
|
||||
{
|
||||
return (KeLumpAllocator *)arena->user;
|
||||
}
|
||||
|
||||
void *ke_LumpAlloc(ke_allocator_t *arena, size_t size)
|
||||
{
|
||||
return ke_LumpFromAllocator(arena)->Alloc(size);
|
||||
}
|
||||
|
||||
void ke_LumpFree(ke_allocator_t *arena, void *ptr)
|
||||
{
|
||||
}
|
||||
|
||||
ke_allocator_t * KE_LINK Knight::KE_CreateLumpAllocator()
|
||||
{
|
||||
ke_allocator_t *alloc;
|
||||
|
||||
alloc = new ke_allocator_t;
|
||||
alloc->alloc = ke_LumpAlloc;
|
||||
alloc->dealloc = ke_LumpFree;
|
||||
alloc->user = new KeLumpAllocator();
|
||||
|
||||
return alloc;
|
||||
}
|
||||
|
||||
void KE_LINK Knight::KE_DestroyLumpAllocator(ke_allocator_t *alloc)
|
||||
{
|
||||
delete ke_LumpFromAllocator(alloc);
|
||||
delete alloc;
|
||||
}
|
||||
|
||||
void KE_LINK Knight::KE_ResetLumpAllocator(ke_allocator_t *alloc)
|
||||
{
|
||||
ke_LumpFromAllocator(alloc)->Reset();
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#ifndef _INCLUDE_KNIGHT_KE_LUMP_ALLOCATOR_H_
|
||||
#define _INCLUDE_KNIGHT_KE_LUMP_ALLOCATOR_H_
|
||||
|
||||
#include <KeLinking.h>
|
||||
#include <KnightAllocator.h>
|
||||
|
||||
namespace Knight
|
||||
{
|
||||
/**
|
||||
* @brief Creates a new lump allocator.
|
||||
*
|
||||
* The lump allocator is intended for cases where there are many allocations
|
||||
* and none need to be freed. There is memory wastage, and the lump allocator
|
||||
* is typically thrown away after use.
|
||||
*
|
||||
* @return New lump allocator.
|
||||
*/
|
||||
extern ke_allocator_t * KE_CreateLumpAllocator();
|
||||
|
||||
/**
|
||||
* @brief Destroys a lump allocator, freeing all of its resources.
|
||||
*
|
||||
* @param lump Lump allocator.
|
||||
*/
|
||||
extern void KE_DestroyLumpAllocator(ke_allocator_t *alloc);
|
||||
|
||||
/**
|
||||
* @brief Clears a lump allocator, so its memory can be re-used from
|
||||
* the start.
|
||||
*
|
||||
* @param lump Lump allocator.
|
||||
*/
|
||||
extern void KE_ResetLumpAllocator(ke_allocator_t *alloc);
|
||||
}
|
||||
|
||||
#endif //_INCLUDE_KNIGHT_KE_LUMP_ALLOCATOR_H_
|
||||
@@ -0,0 +1,148 @@
|
||||
#include "KePageAllocator.h"
|
||||
|
||||
using namespace Knight;
|
||||
|
||||
struct PageInfo
|
||||
{
|
||||
PageInfo *next;
|
||||
void *base;
|
||||
};
|
||||
|
||||
class Knight::KePageAllocator
|
||||
{
|
||||
public:
|
||||
size_t page_size;
|
||||
size_t page_granularity;
|
||||
PageInfo *free_pages;
|
||||
PageInfo *page_blocks;
|
||||
};
|
||||
|
||||
static void *ke_LumpPageAlloc(KePageAllocator *alloc)
|
||||
{
|
||||
void *base;
|
||||
char *page;
|
||||
PageInfo *lump;
|
||||
size_t pagesInBlock;
|
||||
|
||||
#if defined KE_PLATFORM_WINDOWS
|
||||
base = VirtualAlloc(
|
||||
NULL,
|
||||
alloc->page_granularity,
|
||||
MEM_COMMIT|MEM_RESERVE,
|
||||
PAGE_READWRITE);
|
||||
#elif defined KE_PLATFORM_POSIX
|
||||
base = valloc(alloc->page_granularity);
|
||||
#else
|
||||
#error "Unsupported platform"
|
||||
#endif
|
||||
|
||||
if (base == NULL)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
|
||||
lump = new PageInfo;
|
||||
lump->base = base;
|
||||
lump->next = alloc->page_blocks;
|
||||
alloc->page_blocks = lump->next;
|
||||
|
||||
page = (char *)base + alloc->page_size;
|
||||
pagesInBlock = alloc->page_granularity / alloc->page_size;
|
||||
|
||||
for (size_t i = 1; i < pagesInBlock; i++)
|
||||
{
|
||||
lump = new PageInfo;
|
||||
lump->base = page;
|
||||
lump->next = alloc->free_pages;
|
||||
alloc->free_pages = lump;
|
||||
page += alloc->page_size;
|
||||
}
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
KePageAllocator *Knight::KE_CreatePageAllocator()
|
||||
{
|
||||
KePageAllocator *alloc;
|
||||
|
||||
alloc = new KePageAllocator;
|
||||
|
||||
#if defined KE_PLATFORM_WINDOWS
|
||||
SYSTEM_INFO info;
|
||||
|
||||
GetSystemInfo(&info);
|
||||
alloc->page_size = info.dwPageSize;
|
||||
alloc->page_granularity = info.dwAllocationGranularity;
|
||||
#elif defined KE_PLATFORM_POSIX
|
||||
alloc->page_size = sysconf(_SC_PAGESIZE);
|
||||
alloc->page_granularity = alloc->page_size * 16;
|
||||
#else
|
||||
#error "Unsupported platform"
|
||||
#endif
|
||||
|
||||
alloc->free_pages = NULL;
|
||||
alloc->page_blocks = NULL;
|
||||
|
||||
return alloc;
|
||||
}
|
||||
|
||||
void Knight::KE_DestroyPageAllocator(KePageAllocator *alloc)
|
||||
{
|
||||
PageInfo *info, *next;
|
||||
|
||||
info = alloc->page_blocks;
|
||||
while (info != NULL)
|
||||
{
|
||||
next = info->next;
|
||||
#if defined KE_PLATFORM_WINDOWS
|
||||
VirtualFree(info->base, 0, MEM_RELEASE);
|
||||
#elif defined KE_PLATFORM_WINDOWS
|
||||
free(info->base);
|
||||
#else
|
||||
#error "Unsupported platform"
|
||||
#endif
|
||||
delete info;
|
||||
next = info;
|
||||
}
|
||||
|
||||
info = alloc->free_pages;
|
||||
while (info != NULL)
|
||||
{
|
||||
next = info->next;
|
||||
delete info;
|
||||
info = next;
|
||||
}
|
||||
}
|
||||
|
||||
void *Knight::KE_PageAlloc(KePageAllocator *alloc)
|
||||
{
|
||||
if (alloc->free_pages != NULL)
|
||||
{
|
||||
void *base;
|
||||
PageInfo *info;
|
||||
|
||||
info = alloc->free_pages;
|
||||
alloc->free_pages = info->next;
|
||||
base = info->base;
|
||||
delete info;
|
||||
|
||||
return base;
|
||||
}
|
||||
|
||||
return ke_LumpPageAlloc(alloc);
|
||||
}
|
||||
|
||||
void Knight::KE_PageFree(KePageAllocator *alloc, void *page)
|
||||
{
|
||||
PageInfo *info;
|
||||
|
||||
info = new PageInfo;
|
||||
info->base = page;
|
||||
info->next = alloc->free_pages;
|
||||
alloc->free_pages = info->next;
|
||||
}
|
||||
|
||||
size_t Knight::KE_PageSize(KePageAllocator *alloc)
|
||||
{
|
||||
return alloc->page_size;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
#ifndef _INCLUDE_KNIGHT_KE_PAGE_ALLOCATOR_H_
|
||||
#define _INCLUDE_KNIGHT_KE_PAGE_ALLOCATOR_H_
|
||||
|
||||
#include <KeStdTypes.h>
|
||||
|
||||
namespace Knight
|
||||
{
|
||||
class KePageAllocator;
|
||||
|
||||
/**
|
||||
* @brief Creates a page allocator.
|
||||
*
|
||||
* @return New page allocator.
|
||||
*/
|
||||
extern KePageAllocator *KE_CreatePageAllocator();
|
||||
|
||||
/**
|
||||
* @brief Destroys a page allocator, freeing all live pages it owns.
|
||||
*
|
||||
* @param Page allocator.
|
||||
*/
|
||||
extern void KE_DestroyPageAllocator(KePageAllocator *alloc);
|
||||
|
||||
/**
|
||||
* @brief Allocates a page of memory.
|
||||
*
|
||||
* @param alloc Page allocator.
|
||||
* @return Page of memory.
|
||||
*/
|
||||
extern void *KE_PageAlloc(KePageAllocator *alloc);
|
||||
|
||||
/**
|
||||
* @brief Frees a page of memory.
|
||||
*
|
||||
* @param alloc Page allocator.
|
||||
* @param page Page of memory.
|
||||
*/
|
||||
extern void KE_PageFree(KePageAllocator *alloc, void *page);
|
||||
|
||||
/**
|
||||
* @brief Returns the size of a page.
|
||||
*
|
||||
* @param alloc Page allocator.
|
||||
* @return Page size.
|
||||
*/
|
||||
extern size_t KE_PageSize(KePageAllocator *alloc);
|
||||
}
|
||||
|
||||
#endif //_INCLUDE_KNIGHT_KE_PAGE_ALLOCATOR_H_
|
||||
@@ -0,0 +1,27 @@
|
||||
#ifndef _INCLUDE_KNIGHT_KE_PLATFORM_H_
|
||||
#define _INCLUDE_KNIGHT_KE_PLATFORM_H_
|
||||
|
||||
#if defined WIN32
|
||||
|
||||
#define KE_PLATFORM_WINDOWS
|
||||
#define WINDOWS_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <malloc.h>
|
||||
|
||||
#if !defined alloca
|
||||
#define alloca _alloca
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
#define KE_PLATFORM_POSIX
|
||||
|
||||
#if defined linux
|
||||
#define KE_PLATFORM_LINUX
|
||||
#else
|
||||
#error "TODO"
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
#endif //_INCLUDE_KNIGHT_KE_PLATFORM_H_
|
||||
@@ -0,0 +1,162 @@
|
||||
#ifndef _INCLUDE_KNIGHT_KE_SECTOR_STACK_H_
|
||||
#define _INCLUDE_KNIGHT_KE_SECTOR_STACK_H_
|
||||
|
||||
#include <KnightAllocator.h>
|
||||
#include <KeVector.h>
|
||||
|
||||
namespace Knight
|
||||
{
|
||||
template <class T>
|
||||
class KeSectorStack
|
||||
{
|
||||
public:
|
||||
static const size_t DEFAULT_SECTOR_SIZE = 64;
|
||||
|
||||
KeSectorStack() : m_SectorSize(DEFAULT_SECTOR_SIZE), m_UsedSize(0), m_MaxUsedSize(0)
|
||||
{
|
||||
m_pAlloc = NULL;
|
||||
}
|
||||
|
||||
KeSectorStack(size_t sectorSize) : m_SectorSize(sectorSize), m_UsedSize(0), m_MaxUsedSize(0)
|
||||
{
|
||||
m_pAlloc = NULL;
|
||||
}
|
||||
|
||||
KeSectorStack(size_t sectorSize, ke_allocator_t *alloc) :
|
||||
m_SectorSize(sectorSize), m_UsedSize(0), m_pAlloc(alloc), m_MaxUsedSize(0)
|
||||
{
|
||||
}
|
||||
|
||||
~KeSectorStack()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
T *sector;
|
||||
size_t last_sector;
|
||||
size_t last_sector_item;
|
||||
|
||||
if (m_MaxUsedSize == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
last_sector = (m_MaxUsedSize - 1) / m_SectorSize;
|
||||
last_sector_item = (m_MaxUsedSize - 1) % m_SectorSize;
|
||||
|
||||
for (size_t i = 0; i < last_sector; i++)
|
||||
{
|
||||
sector = m_Sectors[i];
|
||||
|
||||
for (size_t j = 0; j < m_SectorSize; j++)
|
||||
{
|
||||
sector[j].~T();
|
||||
}
|
||||
}
|
||||
|
||||
sector = m_Sectors[last_sector];
|
||||
for (size_t i = 0; i <= last_sector_item; i++)
|
||||
{
|
||||
sector[i].~T();
|
||||
}
|
||||
|
||||
clear_no_dtors();
|
||||
}
|
||||
|
||||
void clear_no_dtors()
|
||||
{
|
||||
for (size_t i = 0; i < m_Sectors.size(); i++)
|
||||
{
|
||||
free_sector(m_Sectors[i]);
|
||||
}
|
||||
|
||||
m_Sectors.clear();
|
||||
}
|
||||
|
||||
bool empty()
|
||||
{
|
||||
return (m_UsedSize == 0) ? true : false;
|
||||
}
|
||||
|
||||
void push(const T & val)
|
||||
{
|
||||
if ((m_UsedSize / m_SectorSize) >= m_Sectors.size())
|
||||
{
|
||||
/* Create a new sector */
|
||||
T * sector;
|
||||
|
||||
if (m_pAlloc == NULL)
|
||||
{
|
||||
sector = (T *)malloc(sizeof(T) * m_SectorSize);
|
||||
}
|
||||
else
|
||||
{
|
||||
sector = (T *)m_pAlloc->alloc(m_pAlloc, sizeof(T) * m_SectorSize);
|
||||
}
|
||||
|
||||
m_Sectors.push_back(sector);
|
||||
}
|
||||
|
||||
at(m_UsedSize) = val;
|
||||
|
||||
m_UsedSize++;
|
||||
|
||||
/* Keep track of the maximum used size so we can defer the
|
||||
* massive destruction job until the end.
|
||||
*/
|
||||
if (m_UsedSize > m_MaxUsedSize)
|
||||
{
|
||||
m_MaxUsedSize = m_UsedSize;
|
||||
}
|
||||
}
|
||||
|
||||
void pop()
|
||||
{
|
||||
m_UsedSize--;
|
||||
}
|
||||
|
||||
void pop_all()
|
||||
{
|
||||
m_UsedSize = 0;
|
||||
}
|
||||
|
||||
T & front()
|
||||
{
|
||||
return at(m_UsedSize - 1);
|
||||
}
|
||||
|
||||
size_t size()
|
||||
{
|
||||
return m_UsedSize;
|
||||
}
|
||||
|
||||
private:
|
||||
T & at(size_t x)
|
||||
{
|
||||
return m_Sectors[x / m_SectorSize][x % m_SectorSize];
|
||||
}
|
||||
|
||||
void free_sector(T * sector)
|
||||
{
|
||||
if (m_pAlloc == NULL)
|
||||
{
|
||||
free(sector);
|
||||
}
|
||||
else if (m_pAlloc->dealloc != NULL)
|
||||
{
|
||||
m_pAlloc->dealloc(m_pAlloc, sector);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
KeVector<T *> m_Sectors;
|
||||
size_t m_SectorSize;
|
||||
size_t m_UsedSize;
|
||||
ke_allocator_t *m_pAlloc;
|
||||
size_t m_MaxUsedSize;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //_INCLUDE_KNIGHT_KE_SECTOR_STACK_H_
|
||||
@@ -0,0 +1,24 @@
|
||||
#ifndef _INCLUDE_KNIGHT_KE_STANDARD_TYPES_H_
|
||||
#define _INCLUDE_KNIGHT_KE_STANDARD_TYPES_H_
|
||||
|
||||
#include <KePlatform.h>
|
||||
#include <stddef.h>
|
||||
|
||||
#if defined KE_PLATFORM_WINDOWS
|
||||
|
||||
typedef __int8 int8_t;
|
||||
typedef unsigned __int8 uint8_t;
|
||||
typedef __int16 int16_t;
|
||||
typedef unsigned __int16 uint16_t;
|
||||
typedef __int32 int32_t;
|
||||
typedef unsigned __int32 uint32_t;
|
||||
typedef __int64 int64_t;
|
||||
typedef unsigned __int64 uint64_t;
|
||||
|
||||
#else
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#endif
|
||||
|
||||
#endif //_INCLUDE_KNIGHT_KE_STANDARD_TYPES_H_
|
||||
@@ -0,0 +1,158 @@
|
||||
#ifndef _INCLUDE_KNIGHT_KE_VECTOR_H_
|
||||
#define _INCLUDE_KNIGHT_KE_VECTOR_H_
|
||||
|
||||
#include <new>
|
||||
|
||||
namespace Knight
|
||||
{
|
||||
template <class T>
|
||||
class KeVector
|
||||
{
|
||||
public:
|
||||
KeVector<T>() : m_Data(NULL), m_Size(0), m_CurrentUsedSize(0)
|
||||
{
|
||||
}
|
||||
|
||||
KeVector<T>(const KeVector<T> & other)
|
||||
{
|
||||
m_Size = other.m_CurrentUsedSize;
|
||||
m_CurrentUsedSize = other.m_CurrentUsedSize;
|
||||
|
||||
if (m_Size > 0)
|
||||
{
|
||||
m_Data = new T[other.m_CurrentUsedSize];
|
||||
|
||||
for (size_t i = 0; i < m_Size; i++)
|
||||
{
|
||||
m_Data[i] = other.m_Data[i];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_Data = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
~KeVector<T>()
|
||||
{
|
||||
clear();
|
||||
}
|
||||
|
||||
KeVector & operator =(const KeVector<T> & other)
|
||||
{
|
||||
clear();
|
||||
|
||||
if (other.m_CurrentUsedSize)
|
||||
{
|
||||
m_Data = new T[other.m_CurrentUsedSize];
|
||||
m_Size = other.m_CurrentUsedSize;
|
||||
m_CurrentUsedSize = other.m_CurrentUsedSize;
|
||||
|
||||
for (size_t i = 0; i < m_Size; i++)
|
||||
{
|
||||
m_Data[i] = other.m_Data[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
size_t size() const
|
||||
{
|
||||
return m_CurrentUsedSize;
|
||||
}
|
||||
|
||||
void push_back(const T & elem)
|
||||
{
|
||||
GrowIfNeeded(1);
|
||||
|
||||
new (&m_Data[m_CurrentUsedSize]) T(elem);
|
||||
|
||||
m_CurrentUsedSize++;
|
||||
}
|
||||
|
||||
void pop_back(const T & elem)
|
||||
{
|
||||
if (m_CurrentUsedSize == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_CurrentUsedSize--;
|
||||
m_Data[m_CurrentUsedSize].~T();
|
||||
}
|
||||
|
||||
bool is_empty()
|
||||
{
|
||||
return (m_CurrentUsedSize == 0);
|
||||
}
|
||||
|
||||
T & operator [](size_t pos)
|
||||
{
|
||||
return m_Data[pos];
|
||||
}
|
||||
|
||||
const T & operator [](size_t pos) const
|
||||
{
|
||||
return m_Data[pos];
|
||||
}
|
||||
|
||||
void clear()
|
||||
{
|
||||
for (size_t i = 0; i < m_CurrentUsedSize; i++)
|
||||
{
|
||||
m_Data[i].~T();
|
||||
}
|
||||
|
||||
free(m_Data);
|
||||
|
||||
m_Data = NULL;
|
||||
m_Size = 0;
|
||||
m_CurrentUsedSize = 0;
|
||||
}
|
||||
private:
|
||||
void Grow(size_t amount)
|
||||
{
|
||||
T *new_data;
|
||||
size_t new_size;
|
||||
|
||||
if (m_Size == 0)
|
||||
{
|
||||
new_size = 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
new_size = m_Size * 2;
|
||||
}
|
||||
|
||||
while (m_CurrentUsedSize + amount > new_size)
|
||||
{
|
||||
new_size *= 2;
|
||||
}
|
||||
|
||||
new_data = (T *)malloc(sizeof(T) * new_size);
|
||||
|
||||
for (size_t i = 0; i < m_CurrentUsedSize; i++)
|
||||
{
|
||||
new (&new_data[i]) T(m_Data[i]);
|
||||
m_Data[i].~T();
|
||||
}
|
||||
|
||||
free(m_Data);
|
||||
m_Data = new_data;
|
||||
m_Size = new_size;
|
||||
}
|
||||
|
||||
void GrowIfNeeded(size_t amount)
|
||||
{
|
||||
if (m_CurrentUsedSize + amount >= m_Size)
|
||||
{
|
||||
Grow(amount);
|
||||
}
|
||||
}
|
||||
private:
|
||||
T *m_Data;
|
||||
size_t m_Size;
|
||||
size_t m_CurrentUsedSize;
|
||||
};
|
||||
}
|
||||
|
||||
#endif //_INCLUDE_KNIGHT_KE_VECTOR_H_
|
||||
@@ -0,0 +1,37 @@
|
||||
#ifndef _INCLUDE_KNIGHT_ALLOCATOR_H_
|
||||
#define _INCLUDE_KNIGHT_ALLOCATOR_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
struct ke_allocator_s;
|
||||
typedef struct ke_allocator_s ke_allocator_t;
|
||||
|
||||
typedef void *(*KEFN_ALLOCATOR)(ke_allocator_t *, size_t);
|
||||
typedef void (*KEFN_DEALLOCATOR)(ke_allocator_t *, void *);
|
||||
|
||||
struct ke_allocator_s
|
||||
{
|
||||
KEFN_ALLOCATOR alloc;
|
||||
KEFN_DEALLOCATOR dealloc;
|
||||
void *user;
|
||||
};
|
||||
|
||||
inline void *operator new(size_t size, ke_allocator_t *alloc)
|
||||
{
|
||||
return alloc->alloc(alloc, size);
|
||||
}
|
||||
|
||||
inline void *operator new [](size_t size, ke_allocator_t *alloc)
|
||||
{
|
||||
return alloc->alloc(alloc, size);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void ke_destroy(ke_allocator_t *alloc, T * data)
|
||||
{
|
||||
data->~T();
|
||||
alloc->dealloc(alloc, data);
|
||||
}
|
||||
|
||||
#endif //_INCLUDE_KNIGHT_ALLOCATOR_H_
|
||||
Reference in New Issue
Block a user