#include "KePlatform.h" #include #include 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); #else free(region); #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); #else base = (char*)valloc(m_DefLumpSize); #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(); }