/* vim: set expandtab ts=4 sw=4: */
/*
* You may redistribute this program and/or modify it under the terms of
* the GNU General Public License as published by the Free Software Foundation,
* either version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include
#include "exception/Except.h"
#include "memory/BufferAllocator.h"
#include "util/Bits.h"
#include "util/Identity.h"
/**
* TODO: addOnFreeJob adds a job which is only run when the root allocator is freed
* and it needs to be run when the allocator which called it, or any of that allocator's
* ancestors is freed, not just the root.
*/
/* Define alignment as the size of a pointer which is usually 4 or 8 bytes. */
#define ALIGNMENT sizeof(char*)
struct Job {
struct Allocator_OnFreeJob generic;
struct Allocator* alloc;
struct Job* next;
Identity
};
/** Internal state for Allocator. */
struct BufferAllocator {
struct Allocator generic;
/** Pointer to the beginning of the buffer. */
char* basePointer;
/** Pointer to a pointer to the place in the buffer to allocate the next block of memory. */
char** pPointer;
/** Pointer to the end of the buffer. */
char* const endPointer;
struct Job* onFree;
/** Number of onfree jobs which are not yet complete. */
int outstandingJobs;
struct Except* onOOM;
const char* file;
int line;
Identity
};
/**
* Get a pointer which is aligned on memory boundries.
*
* @param pointer the location where the pointer should be.
* @param alignedOn how big the word is that the boundry should be aligned on.
*/
#define getAligned(pointer, alignedOn) \
((char*) ((uintptr_t)( ((char*)(pointer)) + (alignedOn) - 1) & ~ ((alignedOn) - 1)))
/** @see Allocator_malloc() */
static void* allocatorMalloc(unsigned long length,
struct Allocator* allocator,
const char* identFile,
int identLine)
{
struct BufferAllocator* context = Identity_cast((struct BufferAllocator*) allocator);
char* pointer = getAligned((*context->pPointer), ALIGNMENT);
char* endOfAlloc = pointer + length;
if (endOfAlloc >= context->endPointer) {
Except_raise(context->onOOM, -1, "BufferAllocator ran out of memory [%s:%d]",
identFile, identLine);
}
if (endOfAlloc < *(context->pPointer)) {
Except_raise(context->onOOM, -2, "BufferAllocator integer overflow [%s:%d]",
identFile, identLine);
}
(*context->pPointer) = endOfAlloc;
return (void*) pointer;
}
/** @see Allocator->calloc() */
static void* allocatorCalloc(unsigned long length,
unsigned long count,
struct Allocator* allocator,
const char* identFile,
int identLine)
{
void* pointer = allocatorMalloc(length * count, allocator, identFile, identLine);
Bits_memset(pointer, 0, length * count);
return pointer;
}
/** @see Allocator->clone() */
static void* allocatorClone(unsigned long length,
struct Allocator* allocator,
const void* toClone,
const char* identFile,
int identLine)
{
void* pointer = allocatorMalloc(length, allocator, identFile, identLine);
Bits_memcpy(pointer, toClone, length);
return pointer;
}
/** @see Allocator->realloc() */
static void* allocatorRealloc(const void* original,
unsigned long length,
struct Allocator* allocator,
const char* identFile,
int identLine)
{
if (original == NULL) {
return allocatorMalloc(length, allocator, identFile, identLine);
}
// Need to pointer to make sure we dont copy too much.
struct BufferAllocator* context = Identity_cast((struct BufferAllocator*) allocator);
char* pointer = *context->pPointer;
uint32_t amountToClone = (length < (uint32_t)(pointer - (char*)original))
? length
: (uint32_t)(pointer - (char*)original);
// The likelyhood of nothing having been allocated since is
// almost 0 so we will always create a new
// allocation and copy into it.
void* newAlloc = allocatorMalloc(length, allocator, identFile, identLine);
Bits_memcpy(newAlloc, original, amountToClone);
return newAlloc;
}
/** @see Allocator->free() */
static void freeAllocator(struct Allocator* allocator, const char* identFile, int identLine)
{
struct BufferAllocator* context = Identity_cast((struct BufferAllocator*) allocator);
struct Job* job = context->onFree;
while (job != NULL) {
if (job->generic.callback) {
job->generic.callback(&job->generic);
context->outstandingJobs++;
}
job = job->next;
}
}
static int removeOnFreeJob(struct Allocator_OnFreeJob* toRemove)
{
struct Job* j = Identity_cast((struct Job*) toRemove);
struct BufferAllocator* context = Identity_cast((struct BufferAllocator*) j->alloc);
struct Job** jobPtr = &(context->onFree);
while (*jobPtr != NULL) {
if (*jobPtr == j) {
*jobPtr = (*jobPtr)->next;
return 0;
}
jobPtr = &(*jobPtr)->next;
}
return -1;
}
static int onFreeComplete(struct Allocator_OnFreeJob* job)
{
struct BufferAllocator* context =
Identity_cast((struct BufferAllocator*) ((struct Job*)job)->alloc);
if (!--context->outstandingJobs) {
if ((uintptr_t) context > (uintptr_t) context->pPointer) {
// pPointer points to a destination which is > context unless this is a child alloc.
return 0;
}
// complete
(*context->pPointer) = context->basePointer;
}
return 0;
}
/** @see Allocator->onFree() */
static struct Allocator_OnFreeJob* onFree(struct Allocator* alloc,
const char* file,
int line)
{
struct BufferAllocator* context = Identity_cast((struct BufferAllocator*) alloc);
struct Job* newJob = Allocator_clone(alloc, (&(struct Job) {
.generic = {
.cancel = removeOnFreeJob,
.complete = onFreeComplete
},
.alloc = alloc,
}));
Identity_set(&newJob->generic);
struct Job* job = context->onFree;
if (job == NULL) {
context->onFree = newJob;
} else {
while (job->next != NULL) {
job = job->next;
}
job->next = newJob;
}
return &newJob->generic;
}
/** @see Allocator_child() */
static struct Allocator* childAllocator(struct Allocator* alloc,
const char* file,
int line)
{
struct BufferAllocator* context = Identity_cast((struct BufferAllocator*) alloc);
struct BufferAllocator* child =
allocatorClone(sizeof(struct BufferAllocator), alloc, context, file, line);
child->file = file;
child->line = line;
return &child->generic;
}
static void adopt(struct Allocator* alloc, struct Allocator* allocB, const char* file, int line)
{
Assert_always(!"Unimplemented");
}
/** @see BufferAllocator.h */
struct Allocator* BufferAllocator_newWithIdentity(void* buffer,
unsigned long length,
char* file,
int line)
{
struct FirstAlloc {
struct BufferAllocator alloc;
char* pointer;
};
struct FirstAlloc tempAlloc = {
.alloc = {
.generic = {
.free = freeAllocator,
.malloc = allocatorMalloc,
.calloc = allocatorCalloc,
.clone = allocatorClone,
.realloc = allocatorRealloc,
.child = childAllocator,
.onFree = onFree,
.adopt = adopt
},
// Align the pointer to do the first write manually.
.pPointer = NULL,
.basePointer = getAligned(buffer, sizeof(char*)),
.endPointer = ((char*)buffer) + length,
.file = file,
.line = line
},
.pointer = getAligned(buffer, sizeof(char*))
};
tempAlloc.alloc.pPointer = &tempAlloc.pointer;
Identity_set(&tempAlloc.alloc);
if (tempAlloc.alloc.endPointer < (*tempAlloc.alloc.pPointer)) {
// int64_t overflow.
return NULL;
}
if (length + (char*) buffer < (*tempAlloc.alloc.pPointer) + sizeof(struct BufferAllocator)) {
// Not enough space to allocate the context.
return NULL;
}
struct FirstAlloc* alloc = (struct FirstAlloc*) (*tempAlloc.alloc.pPointer);
Bits_memcpyConst(alloc, &tempAlloc, sizeof(struct FirstAlloc));
alloc->pointer += sizeof(struct FirstAlloc);
alloc->alloc.pPointer = &alloc->pointer;
return &alloc->alloc.generic;
}
void BufferAllocator_onOOM(struct Allocator* alloc,
struct Except* exceptionHandler)
{
struct BufferAllocator* context = Identity_cast((struct BufferAllocator*) alloc);
context->onOOM = exceptionHandler;
}