knight/shared/KeCodeAllocator.cpp
author David Anderson <dvander@alliedmods.net>
Thu Aug 08 20:21:37 2013 -0700 (2013-08-08)
changeset 3978 5bdc0f106c05
parent 3889 1724fb233ad6
permissions -rw-r--r--
Fix Windows build (require stdint.h i.e. MSVC 2010+).
dvander@2226
     1
#include <KePlatform.h>
dvander@2226
     2
#include <assert.h>
dvander@2226
     3
#include <string.h>
dvander@2226
     4
dvander@2226
     5
#if defined KE_PLATFORM_WINDOWS
dvander@2226
     6
#include <windows.h>
dvander@2226
     7
#elif defined KE_PLATFORM_POSIX
dvander@2226
     8
#include <unistd.h>
dvander@2226
     9
#include <stdlib.h>
dvander@2226
    10
#include <sys/mman.h>
dvander@2226
    11
#else
dvander@2226
    12
#error "TODO"
dvander@2226
    13
#endif
dvander@2226
    14
dvander@2226
    15
#include "KeCodeAllocator.h"
dvander@2226
    16
dvander@2226
    17
#define ALIGNMENT	16
dvander@2226
    18
dvander@2226
    19
using namespace Knight;
dvander@2226
    20
dvander@2226
    21
struct KeFreedCode;
dvander@2226
    22
dvander@2226
    23
/**
dvander@2226
    24
 * Defines a region of memory that is made of pages. 
dvander@2226
    25
 */
dvander@2226
    26
struct KeCodeRegion
dvander@2226
    27
{
dvander@2226
    28
	KeCodeRegion *next;
dvander@2226
    29
	unsigned char *block_start;
dvander@2226
    30
	unsigned char *block_pos;
dvander@2226
    31
	KeFreedCode *free_list;
dvander@2226
    32
	size_t total_size;
dvander@2226
    33
	size_t end_free;
dvander@2226
    34
	size_t total_free;
dvander@2226
    35
};
dvander@2226
    36
dvander@2226
    37
/**
dvander@2226
    38
 * Defines freed code.  We keep the size here because 
dvander@2226
    39
 * when we touch the linked list we don't want to dirty pages.
dvander@2226
    40
 */
dvander@2226
    41
struct KeFreedCode
dvander@2226
    42
{
dvander@2226
    43
	KeCodeRegion *region;
dvander@2226
    44
	unsigned char *block_start;
dvander@2226
    45
	size_t size;
dvander@2226
    46
	KeFreedCode *next;
dvander@2226
    47
};
dvander@2226
    48
dvander@2226
    49
struct KeSecret
dvander@2226
    50
{
dvander@2226
    51
	KeCodeRegion *region;
dvander@2226
    52
	size_t size;
dvander@2226
    53
};
dvander@2226
    54
dvander@2226
    55
class Knight::KeCodeCache
dvander@2226
    56
{
dvander@2226
    57
public:
dvander@2226
    58
	/**
dvander@2226
    59
	 * First region that is live for use.
dvander@2226
    60
	 */
dvander@2226
    61
	KeCodeRegion *first_live;
dvander@2226
    62
dvander@2226
    63
	/**
dvander@2226
    64
	 * First region that is full but has free entries.
dvander@2226
    65
	 */
dvander@2226
    66
	KeCodeRegion *first_partial;
dvander@2226
    67
dvander@2226
    68
	/**
dvander@2226
    69
	 * First region that is full.
dvander@2226
    70
	 */
dvander@2226
    71
	KeCodeRegion *first_full;
dvander@2226
    72
dvander@2226
    73
	/**
dvander@2226
    74
	 * Page granularity and size.
dvander@2226
    75
	 */
dvander@2226
    76
	unsigned int page_size;
dvander@2226
    77
	unsigned int page_granularity;
dvander@2226
    78
dvander@2226
    79
	/**
dvander@2226
    80
	 * This isn't actually for code, this is the node cache.
dvander@2226
    81
	 */
dvander@2226
    82
	KeCodeRegion *node_cache;
dvander@2226
    83
	KeFreedCode *free_node_list;
dvander@2226
    84
};
dvander@2226
    85
dvander@2226
    86
KeCodeCache *Knight::KE_CreateCodeCache()
dvander@2226
    87
{
dvander@2226
    88
	KeCodeCache *cache;
dvander@2226
    89
dvander@2226
    90
	cache = new KeCodeCache;
dvander@2226
    91
dvander@2226
    92
	memset(cache, 0, sizeof(KeCodeCache));
dvander@2226
    93
dvander@2226
    94
#if defined KE_PLATFORM_WINDOWS
dvander@2226
    95
	SYSTEM_INFO info;
dvander@2226
    96
dvander@2226
    97
	GetSystemInfo(&info);
dvander@2226
    98
	cache->page_size = info.dwPageSize;
dvander@2226
    99
	cache->page_granularity = info.dwAllocationGranularity;
dvander@2226
   100
#else
dvander@2226
   101
	cache->page_size = cache->page_granularity = sysconf(_SC_PAGESIZE);
dvander@2226
   102
#endif
dvander@2226
   103
dvander@2226
   104
	return cache;
dvander@2226
   105
}
dvander@2226
   106
dvander@2226
   107
inline size_t MinAllocSize()
dvander@2226
   108
{
dvander@2226
   109
	size_t size;
dvander@2226
   110
dvander@2226
   111
	size = sizeof(KeSecret);
dvander@2226
   112
	size += ALIGNMENT;
dvander@2226
   113
	size -= size % ALIGNMENT;
dvander@2226
   114
dvander@2226
   115
	return size;
dvander@2226
   116
}
dvander@2226
   117
dvander@2226
   118
inline size_t ke_GetAllocSize(size_t size)
dvander@2226
   119
{
dvander@2226
   120
	size += sizeof(KeSecret);
dvander@2226
   121
	size += ALIGNMENT;
dvander@2226
   122
	size -= size % ALIGNMENT;
dvander@2226
   123
dvander@2226
   124
	return size;
dvander@2226
   125
}
dvander@2226
   126
dvander@2226
   127
void *ke_AllocInRegion(KeCodeCache *cache,
dvander@2226
   128
					   KeCodeRegion **prev,
dvander@2226
   129
					   KeCodeRegion *region,
dvander@2226
   130
					   unsigned char *ptr,
dvander@2226
   131
					   size_t alloc_size,
dvander@2226
   132
					   bool is_live)
dvander@2226
   133
{
dvander@2226
   134
	KeSecret *secret;
dvander@2226
   135
dvander@2226
   136
	/* Squirrel some info in the alloc. */
dvander@2226
   137
	secret = (KeSecret *)ptr;
dvander@2226
   138
	secret->region = region;
dvander@2226
   139
	secret->size = alloc_size;
dvander@2226
   140
	ptr += sizeof(KeSecret);
dvander@2226
   141
dvander@2226
   142
	region->total_free -= alloc_size;
dvander@2226
   143
dvander@2226
   144
	/* Check if we can't use the fast-path anymore. */
dvander@2226
   145
	if ((is_live && region->end_free < MinAllocSize())
dvander@2226
   146
		|| (!is_live && region->total_free < MinAllocSize()))
dvander@2226
   147
	{
dvander@2226
   148
		KeCodeRegion **start;
dvander@2226
   149
dvander@2226
   150
		*prev = region->next;
dvander@2226
   151
dvander@2226
   152
		/* Select the appropriate arena. */
dvander@2226
   153
		if (is_live)
dvander@2226
   154
		{
dvander@2226
   155
			if (region->total_free < MinAllocSize())
dvander@2226
   156
			{
dvander@2226
   157
				start = &cache->first_full;
dvander@2226
   158
			}
dvander@2226
   159
			else
dvander@2226
   160
			{
dvander@2226
   161
				start = &cache->first_partial;
dvander@2226
   162
			}
dvander@2226
   163
		}
dvander@2226
   164
		else
dvander@2226
   165
		{
dvander@2226
   166
			start = &cache->first_full;
dvander@2226
   167
		}
dvander@2226
   168
		
dvander@2226
   169
		region->next = *start;
dvander@2226
   170
		*start = region;
dvander@2226
   171
	}
dvander@2226
   172
dvander@2226
   173
	return ptr;
dvander@2226
   174
}
dvander@2226
   175
dvander@2226
   176
void *ke_AllocFromLive(KeCodeCache *cache, size_t size)
dvander@2226
   177
{
dvander@2226
   178
	void *ptr;
dvander@2226
   179
	size_t alloc_size;
dvander@2226
   180
	KeCodeRegion *region, **prev;
dvander@2226
   181
dvander@2226
   182
	region = cache->first_live;
dvander@2226
   183
	prev = &cache->first_live;
dvander@2226
   184
	alloc_size = ke_GetAllocSize(size);
dvander@2226
   185
dvander@2226
   186
	while (region != NULL)
dvander@2226
   187
	{
dvander@2226
   188
		if (region->end_free >= alloc_size)
dvander@2226
   189
		{
dvander@2226
   190
			/* Yay! We can do a simple alloc here. */
dvander@2226
   191
			ptr = ke_AllocInRegion(cache, prev, region, region->block_pos, alloc_size, true);
dvander@2226
   192
dvander@2226
   193
			/* Update our counters. */
dvander@2226
   194
			region->block_pos += alloc_size;
dvander@2226
   195
			region->end_free -= alloc_size;
dvander@2226
   196
dvander@2226
   197
			return ptr;
dvander@2226
   198
		}
dvander@2226
   199
		prev = &region->next;
dvander@2226
   200
		region = region->next;
dvander@2226
   201
	}
dvander@2226
   202
dvander@2226
   203
	return NULL;
dvander@2226
   204
}
dvander@2226
   205
dvander@2226
   206
void *ke_AllocFromPartial(KeCodeCache *cache, size_t size)
dvander@2226
   207
{
dvander@2226
   208
	void *ptr;
dvander@2226
   209
	size_t alloc_size;
dvander@2226
   210
	KeCodeRegion *region, **prev;
dvander@2226
   211
dvander@2226
   212
	region = cache->first_partial;
dvander@2226
   213
	prev = &cache->first_partial;
dvander@2226
   214
	alloc_size = ke_GetAllocSize(size);
dvander@2226
   215
dvander@2226
   216
	while (region != NULL)
dvander@2226
   217
	{
dvander@2226
   218
		if (region->total_free >= alloc_size)
dvander@2226
   219
		{
dvander@2226
   220
			KeFreedCode *node, **last;
dvander@2226
   221
dvander@2226
   222
			assert(region->free_list != NULL);
dvander@2226
   223
dvander@2226
   224
			last = &region->free_list;
dvander@2226
   225
			node = region->free_list;
dvander@2226
   226
			while (node != NULL)
dvander@2226
   227
			{
dvander@2226
   228
				if (node->size >= alloc_size)
dvander@2226
   229
				{
dvander@2226
   230
					/* Use this node */
dvander@2226
   231
					ptr = ke_AllocInRegion(cache, prev, region, node->block_start, alloc_size, false);
dvander@2226
   232
dvander@2226
   233
					region->total_free -= node->size;
dvander@2226
   234
					*last = node->next;
dvander@2226
   235
dvander@2226
   236
					/* Make sure bookkeepping is correct. */
dvander@2226
   237
					assert((region->free_list == NULL && region->total_free == 0)
dvander@2226
   238
						   || (region->free_list != NULL && region->total_free != 0));
dvander@2226
   239
dvander@2226
   240
					/* Link us back into the free node list. */
dvander@2226
   241
					node->next = cache->free_node_list;
dvander@2226
   242
					cache->free_node_list = node->next;
dvander@2226
   243
dvander@2226
   244
					return ptr;
dvander@2226
   245
				}
dvander@2226
   246
				last = &node->next;
dvander@2226
   247
				node = node->next;
dvander@2226
   248
			}
dvander@2226
   249
		}
dvander@2226
   250
		prev = &region->next;
dvander@2226
   251
		region = region->next;
dvander@2226
   252
	}
dvander@2226
   253
dvander@2226
   254
	return NULL;
dvander@2226
   255
}
dvander@2226
   256
dvander@2226
   257
KeCodeRegion *ke_AddRegionForSize(KeCodeCache *cache, size_t size)
dvander@2226
   258
{
dvander@2226
   259
	KeCodeRegion *region;
dvander@2226
   260
dvander@2226
   261
	region = new KeCodeRegion;
dvander@2226
   262
dvander@2226
   263
	size = ke_GetAllocSize(size);
dvander@2226
   264
	size += cache->page_granularity * 2;
dvander@2226
   265
	size -= size % cache->page_granularity;
dvander@2226
   266
dvander@2226
   267
#if defined KE_PLATFORM_WINDOWS
dvander@2226
   268
	region->block_start = (unsigned char *)VirtualAlloc(NULL, size, MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);
dvander@2226
   269
#elif defined KE_PLATFORM_POSIX
asherkin@3889
   270
	region->block_start = (unsigned char *)mmap(NULL, size, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANON, -1, 0);
asherkin@3889
   271
	region->block_start = (region->block_start == MAP_FAILED) ? NULL : region->block_start;
dvander@2226
   272
#else
dvander@2226
   273
#error "TODO"
dvander@2226
   274
#endif
dvander@2226
   275
dvander@2226
   276
	if (region->block_start == NULL)
dvander@2226
   277
	{
dvander@2226
   278
		delete region;
dvander@2226
   279
		return NULL;
dvander@2226
   280
	}
dvander@2226
   281
dvander@2226
   282
	region->block_pos = region->block_start;
dvander@2226
   283
	region->end_free = region->total_free = region->total_size = size;
dvander@2226
   284
	region->next = cache->first_live;
dvander@2226
   285
	cache->first_live = region;
dvander@2226
   286
	region->free_list = NULL;
dvander@2226
   287
dvander@2226
   288
	return region;
dvander@2226
   289
}
dvander@2226
   290
dvander@2226
   291
void *Knight::KE_AllocCode(KeCodeCache *cache, size_t size)
dvander@2226
   292
{
dvander@2226
   293
	void *ptr;
dvander@2226
   294
dvander@2226
   295
	/* Check live easy-adds */
dvander@2226
   296
	if (cache->first_live != NULL)
dvander@2226
   297
	{
dvander@2226
   298
		if ((ptr = ke_AllocFromLive(cache, size)) != NULL)
dvander@2226
   299
		{
dvander@2226
   300
			return ptr;
dvander@2226
   301
		}
dvander@2226
   302
	}
dvander@2226
   303
	
dvander@2226
   304
	/* Try looking in the free lists */
dvander@2226
   305
	if (cache->first_partial != NULL)
dvander@2226
   306
	{
dvander@2226
   307
		if ((ptr = ke_AllocFromPartial(cache, size)) != NULL)
dvander@2226
   308
		{
dvander@2226
   309
			return ptr;
dvander@2226
   310
		}
dvander@2226
   311
	}
dvander@2226
   312
dvander@2226
   313
	/* Create a new region */
dvander@2226
   314
	if (ke_AddRegionForSize(cache, size) == NULL)
dvander@2226
   315
	{
dvander@2226
   316
		return NULL;
dvander@2226
   317
	}
dvander@2226
   318
dvander@2226
   319
	return ke_AllocFromLive(cache, size);
dvander@2226
   320
}
dvander@2226
   321
dvander@2226
   322
KeFreedCode *ke_GetFreeNode(KeCodeCache *cache)
dvander@2226
   323
{
dvander@2226
   324
	KeFreedCode *ret;
dvander@2226
   325
dvander@2226
   326
	if (cache->free_node_list != NULL)
dvander@2226
   327
	{
dvander@2226
   328
		ret = cache->free_node_list;
dvander@2226
   329
		cache->free_node_list = ret->next;
dvander@2226
   330
dvander@2226
   331
		return ret;
dvander@2226
   332
	}
dvander@2226
   333
dvander@2226
   334
	/* See if the current free node region has space. */
dvander@2226
   335
	if (cache->node_cache != NULL
dvander@2226
   336
		&& cache->node_cache->end_free >= sizeof(KeFreedCode))
dvander@2226
   337
	{
dvander@2226
   338
		ret = (KeFreedCode *)cache->node_cache->block_pos;
dvander@2226
   339
		cache->node_cache->block_pos += sizeof(KeFreedCode);
dvander@2226
   340
		cache->node_cache->total_free -= sizeof(KeFreedCode);
dvander@2226
   341
		cache->node_cache->end_free -= sizeof(KeFreedCode);
dvander@2226
   342
dvander@2226
   343
		return ret;
dvander@2226
   344
	}
dvander@2226
   345
dvander@2226
   346
	/* Otherwise, we need to alloc a new region. */
dvander@2226
   347
	KeCodeRegion *region = new KeCodeRegion;
dvander@2226
   348
	
dvander@2226
   349
	region->block_start = new unsigned char[cache->page_size / sizeof(KeFreedCode)];
dvander@2226
   350
	region->block_pos = region->block_start + sizeof(KeFreedCode);
dvander@2226
   351
	region->total_size = cache->page_size / sizeof(KeFreedCode);
dvander@2226
   352
	region->total_free = region->end_free = (region->total_size - sizeof(KeFreedCode));
dvander@2226
   353
	region->free_list = NULL;
dvander@2226
   354
	region->next = cache->node_cache;
dvander@2226
   355
	cache->node_cache = region;
dvander@2226
   356
dvander@2226
   357
	return (KeFreedCode *)region->block_start;
dvander@2226
   358
}
dvander@2226
   359
dvander@2226
   360
void Knight::KE_FreeCode(KeCodeCache *cache, void *code)
dvander@2226
   361
{
dvander@2226
   362
	KeSecret *secret;
dvander@2226
   363
	KeFreedCode *node;
dvander@2226
   364
	unsigned char *ptr;
dvander@2226
   365
	KeCodeRegion *region;
dvander@2226
   366
dvander@2226
   367
	ptr = (unsigned char *)code;
dvander@2226
   368
	secret = (KeSecret *)(ptr - sizeof(KeSecret));
dvander@2226
   369
	region = secret->region;
dvander@2226
   370
	node = ke_GetFreeNode(cache);
dvander@2226
   371
	node->block_start = (unsigned char *)code;
dvander@2226
   372
	node->next = region->free_list;
dvander@2226
   373
	region->free_list = node;
dvander@2226
   374
	node->region = region;
dvander@2226
   375
	node->size = secret->size;
dvander@2226
   376
}
dvander@2226
   377
dvander@2226
   378
KeCodeRegion *ke_DestroyRegion(KeCodeRegion *region)
dvander@2226
   379
{
dvander@2226
   380
	KeCodeRegion *next;
dvander@2226
   381
dvander@2226
   382
	next = region->next;
dvander@2226
   383
dvander@2226
   384
#if defined KE_PLATFORM_WINDOWS
dvander@2226
   385
	VirtualFree(region->block_start, 0, MEM_RELEASE);
dvander@2226
   386
#else
asherkin@3889
   387
	munmap(region->block_start, region->total_size);
dvander@2226
   388
#endif
dvander@2226
   389
dvander@2226
   390
	delete region;
dvander@2226
   391
dvander@2226
   392
	return next;
dvander@2226
   393
}
dvander@2226
   394
dvander@2226
   395
void ke_DestroyRegionChain(KeCodeRegion *first)
dvander@2226
   396
{
dvander@2226
   397
	while (first != NULL)
dvander@2226
   398
	{
dvander@2226
   399
		first = ke_DestroyRegion(first);
dvander@2226
   400
	}
dvander@2226
   401
}
dvander@2226
   402
dvander@2226
   403
void Knight::KE_DestroyCodeCache(KeCodeCache *cache)
dvander@2226
   404
{
dvander@2226
   405
	/* Destroy every region and call it a day. */
dvander@2226
   406
	ke_DestroyRegionChain(cache->first_full);
dvander@2226
   407
	ke_DestroyRegionChain(cache->first_live);
dvander@2226
   408
	ke_DestroyRegionChain(cache->first_partial);
dvander@2226
   409
dvander@2226
   410
	/* We use normal malloc for node cache regions */
dvander@2226
   411
	KeCodeRegion *region, *next;
dvander@2226
   412
	
dvander@2226
   413
	region = cache->node_cache;
dvander@2226
   414
	while (region != NULL)
dvander@2226
   415
	{
dvander@2226
   416
		next = region->next;
dvander@2226
   417
		delete [] region->block_start;
dvander@2226
   418
		delete region;
dvander@2226
   419
		region = next;
dvander@2226
   420
	}
dvander@2226
   421
	
dvander@2226
   422
	delete cache;
dvander@2226
   423
}