Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion engine/native/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ include(Modules)
add_subdirectory(thirdparty SYSTEM)

add_modules_library(core SHARED)
target_link_libraries(core PUBLIC definitions math)
target_link_libraries(core PUBLIC definitions math memory)
1 change: 1 addition & 0 deletions engine/native/core/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
add_modules_library(definitions PIC)
add_modules_library(math)
add_modules_library(memory)
target_link_libraries(math PUBLIC definitions)
1 change: 1 addition & 0 deletions engine/native/core/core.cppm
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export module core;
export import core.defs;
export import core.math;
export import core.memory;
67 changes: 67 additions & 0 deletions engine/native/core/memory/allocator.cppm
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
module;

#include <cstddef>

export module core.memory.allocator;
export import core.memory.slice;

export namespace draco::memory
{
enum class Error
{
Okay,
OutOfMemory,
NotImplemented,
Other, // This one shouldn't be needed. If you see it returned, make a
// new error.
};

struct AllocatorVTbl;

struct Allocator
{
AllocatorVTbl *vtbl;
void *allocatorData;
};

struct AllocatorVTbl
{
using AllocFn = Error (*)(
Allocator alloc,
Slice *dst,
size_t size,
size_t align
);
using FreeFn = Error (*)(Allocator alloc, Slice block);
using FreeAllFn = Error (*)(Allocator alloc);
AllocFn alloc;
FreeFn free;
FreeAllFn freeAll;
};

Error nilAlloc(
Allocator alloc,
Slice *dst,
size_t size,
size_t align
)
{
return Error::NotImplemented;
}

Error nilFree(Allocator alloc, Slice block)
{
return Error::NotImplemented;
}

Error nilFreeAll(Allocator alloc)
{
return Error::NotImplemented;
}

void asAllocatorVoid(Allocator *dst, void *alloc, AllocatorVTbl *vtbl)
{
dst->allocatorData = (void*)alloc;
dst->vtbl = vtbl;
}
}
148 changes: 148 additions & 0 deletions engine/native/core/memory/bumpAllocator.cppm
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
module;

#include <algorithm>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstring>

export module core.memory.bumpAllocator;
export import core.memory.allocator;
export import core.memory.slice;

export namespace draco::memory
{
namespace bump
{
using namespace std;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
struct Node;
Comment thread
VictorSohier marked this conversation as resolved.
Outdated

struct Node
{
Node *next;
size_t size;
uint8_t data[];
};

struct BumpAllocator
{
Allocator base;
Node *first;
size_t minAllocRequest;
size_t allocated;
};

void init(
BumpAllocator *alloc,
Allocator baseAlloc,
// one page by default on unix-like systems
size_t minAllocRequest = (1 << 12)
)
{
memset(alloc, 0, sizeof(BumpAllocator));
alloc->base = baseAlloc;
alloc->minAllocRequest = minAllocRequest;
}

void deinit(BumpAllocator *alloc)
{
Node *lastNode;
Node *node = alloc->first;
while (node != nullptr)
{
lastNode = node;
node = node->next;
alloc->base.vtbl->free(
alloc->base,
{
.data = (void*)lastNode,
.size = lastNode->size + sizeof(Node),
}
);
}
}

Error alloc(
Allocator alloc,
Slice *dst,
size_t size,
size_t align
)
{
Error err;
BumpAllocator *allocData = (BumpAllocator *)alloc.allocatorData;
uintptr_t alignMask = align - 1;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
Node **lastNode;
Node **node = &(allocData->first);
size_t pos = allocData->allocated;
size_t oldPos = pos;
size_t reqSize = size;
Slice newBlock;
uintptr_t currentPtr;
lastNode = node;
while (((*node) != nullptr) & (pos > 0))
{
oldPos = pos;
pos -= std::min((*node)->size, pos);
lastNode = node;
node = &((*node)->next);
}
assert(pos == 0); // fraudulent mark provided
currentPtr = (uintptr_t)&((*lastNode)->data[pos]);
reqSize += ((reqSize + currentPtr - 1) & alignMask);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
if (!(*lastNode) || (reqSize > ((*lastNode)->size - oldPos)))
{
reqSize = (sizeof(Node) + size) & ~alignMask;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
err = allocData->base.vtbl->alloc(
allocData->base,
&newBlock,
std::max(allocData->minAllocRequest, reqSize),
std::max(alignof(Node), align)
);
if (err != Error::Okay)
{
return Error::OutOfMemory;
}
(*node) = (Node *)newBlock.data;
(*node)->size = newBlock.size - sizeof(Node);
pos = 0;
lastNode = node;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
currentPtr = ((uintptr_t)&((*lastNode)->data[oldPos]));
reqSize = (size + ((currentPtr - 1) & alignMask)) & ~alignMask;
currentPtr = (currentPtr + alignMask) & ~alignMask;
allocData->allocated += reqSize;
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
dst->data = (void*)currentPtr;
dst->size = size;
return Error::Okay;
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated

Error freeAll(Allocator alloc)
{
BumpAllocator *allocData = (BumpAllocator *)alloc.allocatorData;
allocData->allocated = 0;
return Error::Okay;
}

AllocatorVTbl bumpAllocatorVtbl = {
.alloc = alloc,
.free = nilFree,
.freeAll = freeAll,
};

size_t saveMark(BumpAllocator *self)
{
return self->allocated;
}

void resumeMark(BumpAllocator *self, size_t mark)
{
self->allocated = mark;
}

inline void asAllocator(Allocator *dst, BumpAllocator *alloc)
{
asAllocatorVoid(dst, (void*)alloc, &bumpAllocatorVtbl);
}
}
}
101 changes: 101 additions & 0 deletions engine/native/core/memory/bumpAllocator.test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include <doctest.h>

import core.memory;

TEST_CASE("Bump allocator provides distinct pointers on allocation")
{
using namespace draco::memory;
bump::BumpAllocator bumpAlloc;
Allocator alloc;
Slice a;
Slice b;
Error err;
bump::init(&bumpAlloc, page::pageAllocator);
bump::asAllocator(&alloc, &bumpAlloc);
err = alloc.vtbl->alloc(alloc, &a, sizeof(int), alignof(int));
REQUIRE(err == Error::Okay);
REQUIRE(bumpAlloc.first != nullptr);
// REQUIRE(bumpAlloc.first->next == nullptr);
REQUIRE(bumpAlloc.first->size > bumpAlloc.allocated);
err = alloc.vtbl->alloc(alloc, &b, sizeof(int), alignof(int));
REQUIRE(err == Error::Okay);
REQUIRE((((ptrdiff_t)b.data) - ((ptrdiff_t)a.data)) >= sizeof(int));
bump::deinit(&bumpAlloc);
}

TEST_CASE("Bump allocator aligns pointers correctly")
{
using namespace draco::memory;
bump::BumpAllocator bumpAlloc;
Allocator alloc;
Slice a;
Slice b;
Error err;
bump::init(&bumpAlloc, page::pageAllocator);
bump::asAllocator(&alloc, &bumpAlloc);
err = alloc.vtbl->alloc(alloc, &a, sizeof(uint8_t), 2);
REQUIRE(err == Error::Okay);
REQUIRE(bumpAlloc.first != nullptr);
// REQUIRE(bumpAlloc.first->next == nullptr);
REQUIRE(bumpAlloc.first->size > bumpAlloc.allocated);
err = alloc.vtbl->alloc(alloc, &b, sizeof(uint8_t), 3);
REQUIRE(err == Error::Okay);
REQUIRE((((uintptr_t)a.data) & (2 - 1)) == 0);
REQUIRE((((uintptr_t)b.data) & (3 - 1)) == 0);
bump::deinit(&bumpAlloc);
}

TEST_CASE("Bump allocator data is well packed")
{
struct Foo
{
uint32_t a;
uint64_t b;
};
using namespace draco::memory;
bump::BumpAllocator bumpAlloc;
Allocator alloc;
Slice aSlice;
Slice bSlice;
uint32_t *a;
uint64_t *b;
Error err;
bump::init(&bumpAlloc, page::pageAllocator);
bump::asAllocator(&alloc, &bumpAlloc);
err = alloc.vtbl->alloc(alloc, &aSlice, sizeof(uint32_t), alignof(Foo));
REQUIRE(err == Error::Okay);
REQUIRE(bumpAlloc.first != nullptr);
// REQUIRE(bumpAlloc.first->next == nullptr);
REQUIRE(bumpAlloc.first->size > bumpAlloc.allocated);
err = alloc.vtbl->alloc(alloc, &bSlice, sizeof(uint64_t), alignof(Foo));
REQUIRE(err == Error::Okay);
a = (uint32_t*)aSlice.data;
b = (uint64_t*)bSlice.data;
*a = 69;
*b = 420;
REQUIRE(((Foo*)bumpAlloc.first->data)->a == 69);
REQUIRE(((Foo*)bumpAlloc.first->data)->b == 420);
bump::deinit(&bumpAlloc);
}

TEST_CASE("Bump allocator allocates second page when available")
{
using namespace draco::memory;
bump::BumpAllocator bumpAlloc;
Allocator alloc;
Slice aSlice;
Slice bSlice;
Error err;
bump::init(&bumpAlloc, page::pageAllocator);
bump::asAllocator(&alloc, &bumpAlloc);
err = alloc.vtbl->alloc(alloc, &aSlice, 8192, 1);
REQUIRE(err == Error::Okay);
err = alloc.vtbl->alloc(alloc, &bSlice, 8192, 1);
REQUIRE(err == Error::Okay);
REQUIRE(aSlice.data != bSlice.data);
REQUIRE(aSlice.size == 8192);
REQUIRE(bSlice.size == 8192);
REQUIRE(bumpAlloc.first->next != nullptr);
bump::deinit(&bumpAlloc);
}
Comment thread
VictorSohier marked this conversation as resolved.
Loading