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