knight/shared/KeCodeAllocator.cpp
author Nicholas Hastings <skamonkey@gmail.com>
Sat Aug 10 12:22:09 2013 -0400 (2013-08-10)
changeset 3981 af3681eade3f
parent 3889 1724fb233ad6
permissions -rw-r--r--
Debug build fixes.
     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 *)mmap(NULL, size, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANON, -1, 0);
   271 	region->block_start = (region->block_start == MAP_FAILED) ? NULL : region->block_start;
   272 #else
   273 #error "TODO"
   274 #endif
   275 
   276 	if (region->block_start == NULL)
   277 	{
   278 		delete region;
   279 		return NULL;
   280 	}
   281 
   282 	region->block_pos = region->block_start;
   283 	region->end_free = region->total_free = region->total_size = size;
   284 	region->next = cache->first_live;
   285 	cache->first_live = region;
   286 	region->free_list = NULL;
   287 
   288 	return region;
   289 }
   290 
   291 void *Knight::KE_AllocCode(KeCodeCache *cache, size_t size)
   292 {
   293 	void *ptr;
   294 
   295 	/* Check live easy-adds */
   296 	if (cache->first_live != NULL)
   297 	{
   298 		if ((ptr = ke_AllocFromLive(cache, size)) != NULL)
   299 		{
   300 			return ptr;
   301 		}
   302 	}
   303 	
   304 	/* Try looking in the free lists */
   305 	if (cache->first_partial != NULL)
   306 	{
   307 		if ((ptr = ke_AllocFromPartial(cache, size)) != NULL)
   308 		{
   309 			return ptr;
   310 		}
   311 	}
   312 
   313 	/* Create a new region */
   314 	if (ke_AddRegionForSize(cache, size) == NULL)
   315 	{
   316 		return NULL;
   317 	}
   318 
   319 	return ke_AllocFromLive(cache, size);
   320 }
   321 
   322 KeFreedCode *ke_GetFreeNode(KeCodeCache *cache)
   323 {
   324 	KeFreedCode *ret;
   325 
   326 	if (cache->free_node_list != NULL)
   327 	{
   328 		ret = cache->free_node_list;
   329 		cache->free_node_list = ret->next;
   330 
   331 		return ret;
   332 	}
   333 
   334 	/* See if the current free node region has space. */
   335 	if (cache->node_cache != NULL
   336 		&& cache->node_cache->end_free >= sizeof(KeFreedCode))
   337 	{
   338 		ret = (KeFreedCode *)cache->node_cache->block_pos;
   339 		cache->node_cache->block_pos += sizeof(KeFreedCode);
   340 		cache->node_cache->total_free -= sizeof(KeFreedCode);
   341 		cache->node_cache->end_free -= sizeof(KeFreedCode);
   342 
   343 		return ret;
   344 	}
   345 
   346 	/* Otherwise, we need to alloc a new region. */
   347 	KeCodeRegion *region = new KeCodeRegion;
   348 	
   349 	region->block_start = new unsigned char[cache->page_size / sizeof(KeFreedCode)];
   350 	region->block_pos = region->block_start + sizeof(KeFreedCode);
   351 	region->total_size = cache->page_size / sizeof(KeFreedCode);
   352 	region->total_free = region->end_free = (region->total_size - sizeof(KeFreedCode));
   353 	region->free_list = NULL;
   354 	region->next = cache->node_cache;
   355 	cache->node_cache = region;
   356 
   357 	return (KeFreedCode *)region->block_start;
   358 }
   359 
   360 void Knight::KE_FreeCode(KeCodeCache *cache, void *code)
   361 {
   362 	KeSecret *secret;
   363 	KeFreedCode *node;
   364 	unsigned char *ptr;
   365 	KeCodeRegion *region;
   366 
   367 	ptr = (unsigned char *)code;
   368 	secret = (KeSecret *)(ptr - sizeof(KeSecret));
   369 	region = secret->region;
   370 	node = ke_GetFreeNode(cache);
   371 	node->block_start = (unsigned char *)code;
   372 	node->next = region->free_list;
   373 	region->free_list = node;
   374 	node->region = region;
   375 	node->size = secret->size;
   376 }
   377 
   378 KeCodeRegion *ke_DestroyRegion(KeCodeRegion *region)
   379 {
   380 	KeCodeRegion *next;
   381 
   382 	next = region->next;
   383 
   384 #if defined KE_PLATFORM_WINDOWS
   385 	VirtualFree(region->block_start, 0, MEM_RELEASE);
   386 #else
   387 	munmap(region->block_start, region->total_size);
   388 #endif
   389 
   390 	delete region;
   391 
   392 	return next;
   393 }
   394 
   395 void ke_DestroyRegionChain(KeCodeRegion *first)
   396 {
   397 	while (first != NULL)
   398 	{
   399 		first = ke_DestroyRegion(first);
   400 	}
   401 }
   402 
   403 void Knight::KE_DestroyCodeCache(KeCodeCache *cache)
   404 {
   405 	/* Destroy every region and call it a day. */
   406 	ke_DestroyRegionChain(cache->first_full);
   407 	ke_DestroyRegionChain(cache->first_live);
   408 	ke_DestroyRegionChain(cache->first_partial);
   409 
   410 	/* We use normal malloc for node cache regions */
   411 	KeCodeRegion *region, *next;
   412 	
   413 	region = cache->node_cache;
   414 	while (region != NULL)
   415 	{
   416 		next = region->next;
   417 		delete [] region->block_start;
   418 		delete region;
   419 		region = next;
   420 	}
   421 	
   422 	delete cache;
   423 }