generic slice type, buddy allocator, string type, other stuff
This commit is contained in:
parent
70bbccff71
commit
52ea920ff3
13 changed files with 749 additions and 140 deletions
|
@ -29,7 +29,7 @@ data. This buffer type also acts as a **string builder**, by letting you append
|
|||
data to the buffer, then compile it to a string type.
|
||||
- [ ] A logging system built ontop of this I/O system. You can either log out to
|
||||
a file or to stdout.
|
||||
- [ ] Generic data-structures. The premier one is the
|
||||
- [x] Generic data-structures. The premier one is the
|
||||
[Dynamic Array](https://dylanfalconer.com/articles/dynamic-arrays-in-c), which
|
||||
brings the power of C++'s `std::vector` to C
|
||||
- [ ] A simple GUI initialization system that gets you an OpenGL window with
|
||||
|
@ -56,6 +56,10 @@ in C.
|
|||
|
||||
C is great. Don't let anyone tell you it's not.
|
||||
|
||||
## Examples
|
||||
You can change the build script to any of the examples. Once built, you should
|
||||
step through each example in a debugger in order to see how the programs work.
|
||||
|
||||
## Setup
|
||||
Download the [latest release](https://github.com/midnadimple/bastd/releases/latest)
|
||||
whenever you want to start a new project.
|
||||
|
|
13
bastd.c
13
bastd.c
|
@ -173,7 +173,7 @@ typedef uintptr_t UPtr;
|
|||
typedef ptrdiff_t ISize;
|
||||
typedef size_t USize;
|
||||
|
||||
typedef U32 B32;
|
||||
typedef U8 B8;
|
||||
#define TRUE 1
|
||||
#define FALSE 0
|
||||
|
||||
|
@ -194,9 +194,16 @@ typedef U32 B32;
|
|||
#define COUNT_OF(a) (ISize)(sizeof(a) / sizeof(*(a)))
|
||||
#define LENGTH_OF(s) (COUNT_OF(s) - 1)
|
||||
|
||||
#define KILO (1024)
|
||||
#define MEGA (KILO * 1024)
|
||||
#define GIGA (MEGA * 1024)
|
||||
#define TERA (GIGA * 1024)
|
||||
#define PETA (TERA * 1024)
|
||||
|
||||
// Includes
|
||||
#include "bastd/os.c"
|
||||
#include "bastd/memory.c"
|
||||
#include "bastd/builtins.c"
|
||||
#include "bastd/string.c"
|
||||
#include "bastd/os_windows.c"
|
||||
#include "bastd/mem.c"
|
||||
|
||||
#endif //BASTD_C
|
1
bastd/builtins.c
Normal file
1
bastd/builtins.c
Normal file
|
@ -0,0 +1 @@
|
|||
// Premade generics used in the codebase
|
52
bastd/examples/fib_slice.c
Normal file
52
bastd/examples/fib_slice.c
Normal file
|
@ -0,0 +1,52 @@
|
|||
#include "../../bastd.c"
|
||||
|
||||
/* The sl_TYPE macro defines the type of the generic. It is cleared after every
|
||||
include of "../slice.c"
|
||||
*/
|
||||
#define sl_TYPE I32
|
||||
#include "../slice.c"
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
m_Buddy buddy = m_Buddy_create(os_alloc(2 * GIGA), 2 * GIGA);
|
||||
m_Allocator perm = m_BUDDY_ALLOCATOR(buddy);
|
||||
|
||||
// Slices can be created with a backing array to start with.
|
||||
I32 init[] = {0, 1};
|
||||
sl_I32 fib = sl_I32_create(init, 2);
|
||||
|
||||
os_DEBUGBREAK();
|
||||
|
||||
for (;;) {
|
||||
// The data in a slice can be accessed like a normal array
|
||||
I32 a = fib.elems[fib.len - 2];
|
||||
I32 b = fib.elems[fib.len - 1];
|
||||
if (a + b > 255) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* The sl_TYPE_push function automatically doubles the capacity of the
|
||||
array if the slice is full and returns a pointer to a new element
|
||||
*/
|
||||
*sl_I32_push(&fib, &perm) = a + b;
|
||||
}
|
||||
|
||||
os_DEBUGBREAK();
|
||||
|
||||
sl_S8 msgs = sl_S8_create(NIL, 0);
|
||||
*sl_S8_push(&msgs, &perm) = S8("C standard library!");
|
||||
*sl_S8_push(&msgs, &perm) = S8("a Bravely Arranged ");
|
||||
*sl_S8_push(&msgs, &perm) = S8("This is ");
|
||||
*sl_S8_push(&msgs, &perm) = S8("World! ");
|
||||
*sl_S8_push(&msgs, &perm) = S8("Hello, ");
|
||||
|
||||
os_DEBUGBREAK();
|
||||
|
||||
for (; msgs.len > 0;) {
|
||||
S8 check_debugger = sl_S8_pop(&msgs);
|
||||
os_DEBUGBREAK();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -3,9 +3,13 @@
|
|||
int
|
||||
main(void)
|
||||
{
|
||||
ISize buffer_size = 100 * sizeof(U8);
|
||||
m_Arena arena = m_Arena_create(OS_Alloc(buffer_size), buffer_size);
|
||||
m_Allocator permanent_allocator = m_ARENA_ALLOCATOR(arena);
|
||||
ISize buffer_size = 2 * KILO;
|
||||
|
||||
/* Arena allocator allocates new values at an incrementing offset, and can
|
||||
only free by resetting the beginning pointer to a previous offset
|
||||
*/
|
||||
m_Arena arena = m_Arena_create(os_alloc(buffer_size), buffer_size);
|
||||
m_Allocator arena_allocator = m_ARENA_ALLOCATOR(arena);
|
||||
|
||||
{
|
||||
/* By cloning an arena at the start of the scope, all changes to the
|
||||
|
@ -15,29 +19,62 @@ main(void)
|
|||
m_Arena scratch_arena = arena;
|
||||
m_Allocator scratch_allocator = m_ARENA_ALLOCATOR(scratch_arena);
|
||||
|
||||
U8* array = m_MAKE(U8, 20, scratch_allocator);
|
||||
U8* array = m_MAKE(U8, 20, &scratch_allocator);
|
||||
for (int i = 0; i < 20; i++) {
|
||||
array[i] = i;
|
||||
}
|
||||
} // Auto clears here
|
||||
os_DEBUGBREAK();
|
||||
|
||||
// The same can be accomplished by saving the arena's offset, then restoring it
|
||||
m_ArenaOffset savepoint = m_Arena_getOffset(arena);
|
||||
|
||||
U8* temp_array = m_MAKE(U8, 20, &arena_allocator);
|
||||
for (int i = 0; i < 20; i++) {
|
||||
temp_array[i] = i;
|
||||
}
|
||||
|
||||
m_Arena_setOffset(&arena, savepoint); // Clears here
|
||||
|
||||
os_DEBUGBREAK();
|
||||
|
||||
U8* array = m_MAKE(U8, 10, permanent_allocator);
|
||||
U8* array = m_MAKE(U8, 10, &arena_allocator);
|
||||
for (int i = 10; i-- > 0;) {
|
||||
array[10 - i] = i;
|
||||
}
|
||||
os_DEBUGBREAK();
|
||||
|
||||
array = m_RESIZE(array, 10, 20, permanent_allocator);
|
||||
// Arenas allow for the resizing of blocks.
|
||||
os_DEBUGBREAK();
|
||||
array = m_RESIZE(array, 10, 20, &arena_allocator);
|
||||
for (int i = 20; i-- > 0;) {
|
||||
array[20 - i] = i;
|
||||
}
|
||||
|
||||
os_DEBUGBREAK();
|
||||
|
||||
m_RELEASE(array, 20, permanent_allocator);
|
||||
F64 *vector2 = m_MAKE(F32, 2, permanent_allocator);
|
||||
vector2[0] = 0x1.921fb6p+1f;
|
||||
vector2[1] = 490875;
|
||||
/* For an arena allocator, you can deallocate the last block allcoated on
|
||||
the arena.
|
||||
*/
|
||||
m_RELEASE(array, 20, &arena_allocator);
|
||||
os_DEBUGBREAK();
|
||||
|
||||
/* Buddy allocator is more general-purpose than arena allocator, but is
|
||||
slower and should preferrably be used for long-lasting allocations,
|
||||
rather than temporary ones.
|
||||
*/
|
||||
m_Buddy buddy = m_Buddy_create(os_alloc(buffer_size), buffer_size);
|
||||
m_Allocator buddy_allocator = m_BUDDY_ALLOCATOR(buddy);
|
||||
|
||||
// You can allocate and deallocate any sized object in any order
|
||||
U8 *file = m_MAKE(U8, 20, &buddy_allocator);
|
||||
F32 *vec2 = m_MAKE(F32, 2, &buddy_allocator);
|
||||
char *raw_str = m_MAKE(char, 50, &buddy_allocator);
|
||||
|
||||
os_DEBUGBREAK();
|
||||
|
||||
m_RELEASE(vec2, 2, &buddy_allocator);
|
||||
m_RELEASE(raw_str, 50, &buddy_allocator);
|
||||
m_RELEASE(file, 20, &buddy_allocator);
|
||||
|
||||
os_DEBUGBREAK();
|
||||
}
|
19
bastd/examples/string.c
Normal file
19
bastd/examples/string.c
Normal file
|
@ -0,0 +1,19 @@
|
|||
#include "../../bastd.c"
|
||||
|
||||
int
|
||||
main(void)
|
||||
{
|
||||
m_Buddy buddy = m_Buddy_create(os_alloc(2 * MEGA), 2 * MEGA);
|
||||
m_Allocator perm = m_BUDDY_ALLOCATOR(buddy);
|
||||
|
||||
S8 init[] = {S8("Hello"), S8("World")};
|
||||
sl_S8 str_list = sl_S8_create(init, 2);
|
||||
S8 greeting = s8Join(str_list, S8(", "), &perm);
|
||||
os_DEBUGBREAK();
|
||||
|
||||
S8 hello = s8Sub(greeting, 0, 5, &perm);
|
||||
os_DEBUGBREAK();
|
||||
|
||||
sl_S8 bye_bye = s8Split(S8("Goodbye! Sayonara! Ciao!"), S8(" "), &perm);
|
||||
os_DEBUGBREAK();
|
||||
}
|
116
bastd/mem.c
116
bastd/mem.c
|
@ -1,116 +0,0 @@
|
|||
#ifndef BASTD_MEM_C
|
||||
#define BASTD_MEM_C
|
||||
|
||||
FUNCTION void *
|
||||
m_memorySet(void *buffer, U8 value, ISize length)
|
||||
{
|
||||
U8* p = buffer;
|
||||
while (length-- > 0) {
|
||||
*p++ = value;
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
FUNCTION void *
|
||||
m_memoryCopy(void *dst, void *src, size_t n)
|
||||
{
|
||||
U8 *s = (U8 *)src;
|
||||
U8 *d = (U8 *)dst;
|
||||
|
||||
for (I32 i = 0; i < n; i++) {
|
||||
d[i] = s[i];
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
/* Alloc function taken from Lua.
|
||||
If ptr == NIL, old_size irrelevant
|
||||
If ptr != NIL, old_size = size of block of memory at ptr
|
||||
If new_size > 0, function like realloc and either resize or create
|
||||
If new_size <= 0, function like free and clear the memory
|
||||
*/
|
||||
typedef void *(*m_AllocFunc)(void *ctx, void *ptr, ISize old_size, ISize new_size);
|
||||
|
||||
typedef struct m_Allocator m_Allocator;
|
||||
struct m_Allocator {
|
||||
m_AllocFunc alloc;
|
||||
void* ctx;
|
||||
};
|
||||
|
||||
#define m_MAKE(T, n, a) ((T *)((a).alloc((a).ctx, NIL, 0, sizeof(T) * n)))
|
||||
#define m_RESIZE(p, o, n, a) ((a).alloc((a).ctx, p, sizeof(*(p)) * o, sizeof(*(p)) * n))
|
||||
#define m_RELEASE(p, s, a) ((a).alloc((a).ctx, p, sizeof(*(p)) * s, 0))
|
||||
|
||||
/* Linear Allocator (Arena)
|
||||
Allocates variably-sized regions from a fixed-size block of memory.
|
||||
A set of regions is freed by setting the allocator's offset to an earlier
|
||||
value.
|
||||
*/
|
||||
typedef struct m_Arena m_Arena;
|
||||
struct m_Arena {
|
||||
U8* beg;
|
||||
U8* end;
|
||||
};
|
||||
|
||||
#define DEFAULT_ALIGNMENT (2 * sizeof(void *))
|
||||
|
||||
FUNCTION void *
|
||||
m_Arena_alloc(void *ctx, void *ptr, ISize old_size, ISize new_size)
|
||||
{
|
||||
m_Arena *a = (m_Arena *)ctx;
|
||||
|
||||
if (new_size <= 0) {
|
||||
/* Arena can only free the most recent block. This allows to follow stack pattern.
|
||||
What we do is move the end pointer forward by the size of the block
|
||||
Then, because there is now more available space, the data will be
|
||||
ignored following the next allocation.
|
||||
*/
|
||||
ISize padding = -old_size & (DEFAULT_ALIGNMENT - 1);
|
||||
a->end += old_size + padding;
|
||||
return NIL;
|
||||
} else {
|
||||
// Allocate a block
|
||||
ASSERT(new_size > old_size, "Can't reallocate to a smaller block");
|
||||
|
||||
ISize padding = -(UPtr)a->beg & (DEFAULT_ALIGNMENT - 1);
|
||||
ISize available = a->end - a->beg - padding;
|
||||
|
||||
if (available < 0 || new_size > available) {
|
||||
OS_Abort(S8("Out of Memory!"));
|
||||
}
|
||||
|
||||
void *p = NIL;
|
||||
|
||||
if (a->beg == (U8 *)ptr + old_size) {
|
||||
// This was the last allocated block, can extend allocation for realloc
|
||||
p = ptr;
|
||||
a->beg += padding + new_size;
|
||||
} else {
|
||||
// New allocation;
|
||||
p = a->beg + padding;
|
||||
a->beg += padding + new_size;
|
||||
p = m_memorySet(p, 0, new_size);
|
||||
|
||||
if (ptr != NIL) {
|
||||
// Arbitrary block, copy data from old pointer
|
||||
p = m_memoryCopy(p, ptr, old_size);
|
||||
}
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
FUNCTION m_Arena
|
||||
m_Arena_create(void* buffer, ISize capacity)
|
||||
{
|
||||
m_Arena arena = {0};
|
||||
arena.beg = (U8 *)buffer;
|
||||
arena.end = arena.beg ? arena.beg + capacity : 0;
|
||||
return arena;
|
||||
}
|
||||
|
||||
#define m_ARENA_ALLOCATOR(a) (m_Allocator){m_Arena_alloc, (void *)&(a)}
|
||||
|
||||
#endif//BASTD_MEM_C
|
369
bastd/memory.c
Normal file
369
bastd/memory.c
Normal file
|
@ -0,0 +1,369 @@
|
|||
#ifndef BASTD_MEM_C
|
||||
#define BASTD_MEM_C
|
||||
|
||||
FUNCTION void *
|
||||
m_memorySet(void *buffer, U8 value, ISize length)
|
||||
{
|
||||
U8* p = buffer;
|
||||
while (length-- > 0) {
|
||||
*p++ = value;
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
FUNCTION void *
|
||||
m_memoryCopy(void *dst, void *src, ISize n)
|
||||
{
|
||||
U8 *s = (U8 *)src;
|
||||
U8 *d = (U8 *)dst;
|
||||
|
||||
for (ISize i = 0; i < n; i++) {
|
||||
d[i] = s[i];
|
||||
}
|
||||
|
||||
return dst;
|
||||
}
|
||||
|
||||
FUNCTION ISize
|
||||
m_memoryDifference(void *dst, void *src, ISize n)
|
||||
{
|
||||
U8 *s = (U8 *)src;
|
||||
U8 *d = (U8 *)dst;
|
||||
|
||||
ISize count = 0;
|
||||
for (ISize i = 0; i < n; i++) {
|
||||
if (d[i] != s[i]) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/* Alloc function taken from Lua.
|
||||
If ptr == NIL, old_size irrelevant
|
||||
If ptr != NIL, old_size = size of block of memory at ptr
|
||||
If new_size > 0, function like realloc and either resize or create
|
||||
If new_size <= 0, function like free and clear the memory
|
||||
*/
|
||||
typedef void *(*m_AllocFunc)(void *ctx, void *ptr, ISize old_size, ISize new_size);
|
||||
|
||||
typedef struct m_Allocator m_Allocator;
|
||||
struct m_Allocator {
|
||||
m_AllocFunc alloc;
|
||||
void* ctx;
|
||||
};
|
||||
|
||||
#define m_MAKE(T, n, a) ((T *)((a)->alloc((a)->ctx, NIL, 0, sizeof(T) * n)))
|
||||
#define m_RESIZE(p, o, n, a) ((a)->alloc((a)->ctx, p, sizeof(*(p)) * o, sizeof(*(p)) * n))
|
||||
#define m_RELEASE(p, s, a) ((a)->alloc((a)->ctx, p, sizeof(*(p)) * s, 0))
|
||||
|
||||
/* Linear Allocator (Arena)
|
||||
Allocates variably-sized regions from a fixed-size block of memory.
|
||||
A set of regions is freed by setting the allocator's offset to an earlier
|
||||
value. This should be used as an allocator for tasks where the lifetime of
|
||||
the data isn't important to its implementation. This would be an update loop
|
||||
in a game, or a threaded web request, for example.
|
||||
*/
|
||||
typedef struct m_Arena m_Arena;
|
||||
struct m_Arena {
|
||||
U8* beg;
|
||||
U8* end;
|
||||
};
|
||||
|
||||
#define DEFAULT_ALIGNMENT (2 * sizeof(void *))
|
||||
|
||||
FUNCTION void *
|
||||
m_Arena_alloc(void *ctx, void *ptr, ISize old_size, ISize new_size)
|
||||
{
|
||||
m_Arena *a = (m_Arena *)ctx;
|
||||
|
||||
if (new_size <= 0) {
|
||||
/* Arena can only free the most recent block. This allows to follow stack pattern.
|
||||
All we do is move the beginning pointer back the size of the allocation.
|
||||
*/
|
||||
ISize padding = -old_size & (DEFAULT_ALIGNMENT - 1);
|
||||
a->beg -= (old_size + padding);
|
||||
return NIL;
|
||||
} else {
|
||||
// Allocate a block
|
||||
ASSERT(new_size > old_size, "Can't reallocate to a smaller block");
|
||||
|
||||
ISize padding = -(UPtr)a->beg & (DEFAULT_ALIGNMENT - 1);
|
||||
ISize available = a->end - a->beg - padding;
|
||||
|
||||
if (available < 0 || new_size > available) {
|
||||
os_abort("Out of Memory!");
|
||||
}
|
||||
|
||||
void *p = NIL;
|
||||
|
||||
if (a->beg == (U8 *)ptr + old_size) {
|
||||
// This was the last allocated block, can extend allocation for realloc
|
||||
p = ptr;
|
||||
a->beg += padding + new_size;
|
||||
} else {
|
||||
// New allocation;
|
||||
p = a->beg + padding;
|
||||
a->beg += padding + new_size;
|
||||
p = m_memorySet(p, 0, new_size);
|
||||
|
||||
if (ptr != NIL) {
|
||||
// Arbitrary block, copy data from old pointer
|
||||
p = m_memoryCopy(p, ptr, old_size);
|
||||
}
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
FUNCTION m_Arena
|
||||
m_Arena_create(void* buffer, ISize capacity)
|
||||
{
|
||||
m_Arena arena = {0};
|
||||
arena.beg = (U8 *)buffer;
|
||||
arena.end = arena.beg ? arena.beg + capacity : 0;
|
||||
return arena;
|
||||
}
|
||||
|
||||
// Just to make it clear what you're saving
|
||||
typedef U8 *m_ArenaOffset;
|
||||
|
||||
FUNCTION m_ArenaOffset
|
||||
m_Arena_getOffset(m_Arena arena)
|
||||
{
|
||||
return arena.beg;
|
||||
}
|
||||
|
||||
FUNCTION void
|
||||
m_Arena_setOffset(m_Arena *arena, m_ArenaOffset offset)
|
||||
{
|
||||
arena->beg = offset;
|
||||
}
|
||||
|
||||
#define m_ARENA_ALLOCATOR(a) (m_Allocator){m_Arena_alloc, (void *)&(a)}
|
||||
|
||||
/* Buddy Allocator (General-Purpose)
|
||||
Allocates any size data in any order, by splitting a fixed block of memory
|
||||
into half-size "buddy" blocks recursively until the allocation fits in the
|
||||
smallest possible block. This is a general purpose allocator, so you have to
|
||||
keep track of individual object lifetimes. THe best use-case for this is for
|
||||
objects that live for the entirety of a program, although static global
|
||||
variables / function parameters should be preferred.
|
||||
*/
|
||||
typedef struct m_BuddyBlock m_BuddyBlock;
|
||||
struct m_BuddyBlock {
|
||||
ISize size;
|
||||
B8 is_free;
|
||||
};
|
||||
|
||||
FUNCTION m_BuddyBlock *
|
||||
m_BuddyBlock_next(m_BuddyBlock *block)
|
||||
{
|
||||
return (m_BuddyBlock *)((char *)block + block->size);
|
||||
}
|
||||
|
||||
FUNCTION m_BuddyBlock *
|
||||
m_BuddyBlock_split(m_BuddyBlock *block, ISize size)
|
||||
{
|
||||
if (block != NIL && size != 0) {
|
||||
while (size < block->size) {
|
||||
ISize sz = block->size >> 1;
|
||||
block->size = sz;
|
||||
block = m_BuddyBlock_next(block);
|
||||
block->size = sz;
|
||||
block->is_free = TRUE;
|
||||
}
|
||||
|
||||
if (size <= block->size) {
|
||||
return block;
|
||||
}
|
||||
}
|
||||
|
||||
return NIL;
|
||||
}
|
||||
|
||||
FUNCTION m_BuddyBlock *
|
||||
m_BuddyBlock_findBest(m_BuddyBlock *head, m_BuddyBlock *tail, ISize size)
|
||||
{
|
||||
m_BuddyBlock *best_block = NIL;
|
||||
m_BuddyBlock *block = head;
|
||||
m_BuddyBlock *buddy = m_BuddyBlock_next(block);
|
||||
|
||||
if (buddy == tail && block->is_free) {
|
||||
return m_BuddyBlock_split(block, size);
|
||||
}
|
||||
|
||||
while (block < tail && buddy < tail) {
|
||||
// Coalesce to reduce fragmentation
|
||||
if (block->is_free && buddy->is_free && block->size == buddy->size) {
|
||||
block->size <<= 1;
|
||||
if (size <= block->size && (best_block == NIL || block->size <= best_block->size)) {
|
||||
best_block = block;
|
||||
}
|
||||
|
||||
block = m_BuddyBlock_next(buddy);
|
||||
if (block < tail) {
|
||||
// Delay the buddy block for the next iteration
|
||||
buddy = m_BuddyBlock_next(block);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (block->is_free && size <= block->size &&
|
||||
(best_block == NIL || block->size <= best_block->size)) {
|
||||
best_block = block;
|
||||
}
|
||||
|
||||
if (buddy->is_free && size <= buddy->size &&
|
||||
(best_block == NIL || buddy->size < best_block->size)) {
|
||||
// If each buddy are the same size, then it makes more sense
|
||||
// to pick the buddy as it "bounces around" less
|
||||
best_block = buddy;
|
||||
}
|
||||
|
||||
if (block->size <= buddy->size) {
|
||||
block = m_BuddyBlock_next(buddy);
|
||||
if (block < tail) {
|
||||
// Delay the buddy block for the next iteration
|
||||
buddy = m_BuddyBlock_next(block);
|
||||
}
|
||||
} else {
|
||||
// Buddy was split into smaller blocks
|
||||
block = buddy;
|
||||
buddy = m_BuddyBlock_next(buddy);
|
||||
}
|
||||
}
|
||||
|
||||
if (best_block != NIL) {
|
||||
return m_BuddyBlock_split(best_block, size);
|
||||
}
|
||||
|
||||
os_abort("Failed to buddy allocate, possibly out of memory");
|
||||
return NIL;
|
||||
}
|
||||
|
||||
typedef struct m_Buddy m_Buddy;
|
||||
struct m_Buddy {
|
||||
m_BuddyBlock *head;
|
||||
m_BuddyBlock *tail;
|
||||
ISize alignment;
|
||||
};
|
||||
|
||||
FUNCTION m_Buddy
|
||||
m_Buddy_create(void *data, ISize size)
|
||||
{
|
||||
m_Buddy res = {0};
|
||||
res.alignment = DEFAULT_ALIGNMENT;
|
||||
if (res.alignment < sizeof(m_BuddyBlock)) {
|
||||
res.alignment = sizeof(m_BuddyBlock);
|
||||
}
|
||||
|
||||
ASSERT((UPtr)data & res.alignment == 0, "Data not aligned to minimum alignment");
|
||||
|
||||
res.head = (m_BuddyBlock *)data;
|
||||
res.head->size = size;
|
||||
res.head->is_free = TRUE;
|
||||
|
||||
res.tail = m_BuddyBlock_next(res.head);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
FUNCTION ISize
|
||||
m_Buddy_requiredSize(m_Buddy *b, ISize size) {
|
||||
ISize actual_size = b->alignment;
|
||||
|
||||
size += sizeof(m_BuddyBlock);
|
||||
ISize padding = -size & (b->alignment - 1);
|
||||
size += b->alignment + padding;
|
||||
|
||||
while (size > actual_size) {
|
||||
actual_size <<= 1;
|
||||
}
|
||||
|
||||
return actual_size;
|
||||
}
|
||||
|
||||
FUNCTION void
|
||||
m_Buddy_coalesce(m_BuddyBlock *head, m_BuddyBlock *tail) {
|
||||
for (;;) {
|
||||
// Keep looping until there are no more buddies to coalesce
|
||||
|
||||
m_BuddyBlock *block = head;
|
||||
m_BuddyBlock *buddy = m_BuddyBlock_next(block);
|
||||
|
||||
B8 no_coalescence = TRUE;
|
||||
while (block < tail && buddy < tail) { // make sure the buddies are within the range
|
||||
if (block->is_free && buddy->is_free && block->size == buddy->size) {
|
||||
// Coalesce buddies into one
|
||||
block->size <<= 1;
|
||||
block = m_BuddyBlock_next(block);
|
||||
if (block < tail) {
|
||||
buddy = m_BuddyBlock_next(block);
|
||||
no_coalescence = FALSE;
|
||||
}
|
||||
} else if (block->size < buddy->size) {
|
||||
// The buddy block is split into smaller blocks
|
||||
block = buddy;
|
||||
buddy = m_BuddyBlock_next(buddy);
|
||||
} else {
|
||||
block = m_BuddyBlock_next(buddy);
|
||||
if (block < tail) {
|
||||
// Leave the buddy block for the next iteration
|
||||
buddy = m_BuddyBlock_next(block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (no_coalescence) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FUNCTION void *
|
||||
m_Buddy_alloc(void *ctx, void *ptr, ISize old_size, ISize new_size) {
|
||||
m_Buddy *b = (m_Buddy *)ctx;
|
||||
|
||||
if (new_size <= 0 && ptr != NIL) {
|
||||
m_BuddyBlock *block;
|
||||
|
||||
ASSERT(b->head <= ptr, "Head is greater than pointer to free");
|
||||
ASSERT(ptr < b->tail, "Tail is less than pointer to free");
|
||||
|
||||
block = (m_BuddyBlock *)((char *)ptr - b->alignment);
|
||||
block->is_free = TRUE;
|
||||
|
||||
m_Buddy_coalesce(b->head, b->tail);
|
||||
|
||||
return NIL;
|
||||
} else {
|
||||
size_t actual_size = m_Buddy_requiredSize(b, new_size);
|
||||
|
||||
m_BuddyBlock *found = m_BuddyBlock_findBest(b->head, b->tail, actual_size);
|
||||
if (found == NIL) {
|
||||
// Try to coalesce all the free buddy blocks and then search again
|
||||
m_Buddy_coalesce(b->head, b->tail);
|
||||
found = m_BuddyBlock_findBest(b->head, b->tail, actual_size);
|
||||
}
|
||||
|
||||
if (found != NIL) {
|
||||
found->is_free = FALSE;
|
||||
U8 *p = (U8 *)found + b->alignment;
|
||||
if (ptr != NIL) {
|
||||
m_memoryCopy(p, ptr, old_size);
|
||||
}
|
||||
return (void *)(p);
|
||||
}
|
||||
|
||||
os_abort("Buddy Allcoator out of memory, possibly too much internal fragmentation");
|
||||
}
|
||||
|
||||
return NIL;
|
||||
}
|
||||
|
||||
#define m_BUDDY_ALLOCATOR(a) (m_Allocator){m_Buddy_alloc, (void *)&(a)}
|
||||
|
||||
#endif//BASTD_MEM_C
|
|
@ -1,8 +1,8 @@
|
|||
#ifndef BASTD_OS_C
|
||||
#define BASTD_OS_C
|
||||
|
||||
FUNCTION void OS_Abort(S8 msg);
|
||||
FUNCTION void *OS_Alloc(ISize cap);
|
||||
FUNCTION void os_abort(char *msg);
|
||||
FUNCTION void *os_alloc(ISize cap);
|
||||
|
||||
#if defined(OS_WINDOWS)
|
||||
|
||||
|
|
|
@ -6,16 +6,16 @@
|
|||
#define os_DEBUGBREAK() __debugbreak();
|
||||
|
||||
FUNCTION void
|
||||
OS_Abort(S8 msg)
|
||||
os_abort(char *msg)
|
||||
{
|
||||
HANDLE stderr = GetStdHandle(-12);
|
||||
U32 dummy;
|
||||
WriteFile(stderr, msg.raw, msg.length, &dummy, 0);
|
||||
WriteFile(stderr, msg, LENGTH_OF(msg), &dummy, 0);
|
||||
ExitProcess(101);
|
||||
}
|
||||
|
||||
FUNCTION void *
|
||||
OS_Alloc(ISize cap)
|
||||
os_alloc(ISize cap)
|
||||
{
|
||||
return VirtualAlloc(NIL, cap, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||
}
|
||||
|
|
49
bastd/slice.c
Normal file
49
bastd/slice.c
Normal file
|
@ -0,0 +1,49 @@
|
|||
// GENERIC FILE, REDEFINED ON EVERY INCLUDE
|
||||
#ifndef sl_TYPE
|
||||
#error "No sl_TYPE given"
|
||||
#endif
|
||||
|
||||
#define sl_NAME CONCAT(sl_, sl_TYPE)
|
||||
|
||||
typedef struct sl_NAME sl_NAME;
|
||||
struct sl_NAME {
|
||||
sl_TYPE *elems;
|
||||
ISize len;
|
||||
ISize cap;
|
||||
};
|
||||
|
||||
FUNCTION sl_NAME
|
||||
CONCAT(sl_NAME, _create) (sl_TYPE *initial_data, ISize initial_len)
|
||||
{
|
||||
sl_NAME res = {0};
|
||||
res.elems = initial_data;
|
||||
|
||||
res.len = initial_len;
|
||||
res.cap = initial_len;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
FUNCTION sl_TYPE*
|
||||
CONCAT(sl_NAME, _push) (sl_NAME *slice, m_Allocator *perm)
|
||||
{
|
||||
if (slice->len >= slice->cap) {
|
||||
// Slice is full, Grow slice to double its current size
|
||||
slice->cap = slice->cap ? slice->cap : 1;
|
||||
ISize current_size = sizeof(*slice->elems) * slice->cap;
|
||||
slice->elems = perm->alloc(perm->ctx, slice->elems, current_size, current_size*2);
|
||||
slice->cap *= 2;
|
||||
}
|
||||
|
||||
// Return the slot the end of the slice and increase the length
|
||||
return slice->elems + slice->len++;
|
||||
}
|
||||
|
||||
FUNCTION sl_TYPE
|
||||
CONCAT(sl_NAME, _pop) (sl_NAME *slice)
|
||||
{
|
||||
return *(slice->elems + --slice->len);
|
||||
}
|
||||
|
||||
#undef sl_NAME
|
||||
#undef sl_TYPE
|
189
bastd/string.c
189
bastd/string.c
|
@ -3,10 +3,195 @@
|
|||
|
||||
typedef struct S8 S8;
|
||||
struct S8 {
|
||||
ISize length;
|
||||
ISize len;
|
||||
U8 *raw;
|
||||
};
|
||||
|
||||
#define S8(s) (S8){(U8 *)s, LENGTH_OF(s)}
|
||||
#define sl_TYPE S8
|
||||
#include "slice.c"
|
||||
|
||||
#define S8(s) (S8){.len = LENGTH_OF(s), .raw = (U8 *)s}
|
||||
|
||||
FUNCTION S8
|
||||
s8Alloc(ISize len, m_Allocator *perm)
|
||||
{
|
||||
S8 res = {0};
|
||||
res.len = len;
|
||||
res.raw = m_MAKE(U8, len + 1, perm);
|
||||
res.raw[len] = 0;
|
||||
return res;
|
||||
}
|
||||
|
||||
FUNCTION S8
|
||||
s8Concat(S8 s1, S8 s2, m_Allocator *perm)
|
||||
{
|
||||
ISize len = s1.len + s2.len;
|
||||
S8 s = s8Alloc(len, perm);
|
||||
m_memoryCopy(s.raw, s1.raw, s1.len);
|
||||
m_memoryCopy(&s.raw[s1.len], s2.raw, s2.len);
|
||||
return s;
|
||||
}
|
||||
|
||||
FUNCTION S8
|
||||
s8Sub(S8 s, ISize start, ISize end, m_Allocator *perm)
|
||||
{
|
||||
S8 res = {0};
|
||||
if (end <= s.len && start < end) {
|
||||
res = s8Alloc(end - start, perm);
|
||||
m_memoryCopy(res.raw, &s.raw[start], res.len);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
FUNCTION B8
|
||||
s8Contains(S8 haystack, S8 needle)
|
||||
{
|
||||
B8 found = FALSE;
|
||||
for (ISize i = 0, j = 0; i < haystack.len && !found; i++) {
|
||||
while (haystack.raw[i] == needle.raw[j]) {
|
||||
j += 1;
|
||||
i += 1;
|
||||
if (j == needle.len) {
|
||||
found = TRUE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
FUNCTION ISize
|
||||
s8IndexOf(S8 haystack, S8 needle)
|
||||
{
|
||||
for (ISize i = 0; i < haystack.len; i += 1) {
|
||||
ISize j = 0;
|
||||
ISize start = i;
|
||||
while (haystack.raw[i] == needle.raw[j]) {
|
||||
j += 1;
|
||||
i += 1;
|
||||
if (j == needle.len) {
|
||||
return start;
|
||||
}
|
||||
}
|
||||
}
|
||||
return (ISize)-1;
|
||||
}
|
||||
|
||||
FUNCTION S8
|
||||
s8SubView(S8 haystack, S8 needle)
|
||||
{
|
||||
S8 r = {0};
|
||||
ISize start_index = str_index_of(haystack, needle);
|
||||
if (start_index < haystack.len) {
|
||||
r.raw = &haystack.raw[start_index];
|
||||
r.len = needle.len;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
FUNCTION B8
|
||||
s8Equal(S8 a, S8 b)
|
||||
{
|
||||
if (a.len != b.len) {
|
||||
return FALSE;
|
||||
}
|
||||
return m_memoryDifference(a.raw, b.raw, a.len) == 0;
|
||||
}
|
||||
|
||||
FUNCTION S8
|
||||
s8View(S8 s, ISize start, ISize end)
|
||||
{
|
||||
if (end < start || end - start > s.len) {
|
||||
return (S8){0};
|
||||
}
|
||||
return (S8){end - start, s.raw + start};
|
||||
}
|
||||
|
||||
FUNCTION S8
|
||||
s8Clone(S8 s, m_Allocator *perm)
|
||||
{
|
||||
S8 r = {0};
|
||||
if (s.len) {
|
||||
r.raw = perm->alloc(perm->ctx, NIL, 0, s.len);
|
||||
r.len = s.len;
|
||||
m_memoryCopy(r.raw, s.raw, s.len);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
FUNCTION sl_S8
|
||||
s8Split(S8 s, S8 delimiter, m_Allocator *perm)
|
||||
{
|
||||
sl_S8 arr = sl_S8_create(NIL, 0);
|
||||
ISize start = 0;
|
||||
for (ISize i = 0; i < s.len; i += 1) {
|
||||
if (s.raw[i] != delimiter.raw[0]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m_memoryDifference(&s.raw[i], delimiter.raw, delimiter.len) == 0) {
|
||||
// Clone the substring before the delimiter.
|
||||
ISize end = i;
|
||||
S8 cloned = s8Sub(s, start, end, perm);
|
||||
*sl_S8_push(&arr, perm) = cloned;
|
||||
start = end + delimiter.len;
|
||||
}
|
||||
}
|
||||
// Get the last segment.
|
||||
if (start + delimiter.len < s.len) {
|
||||
S8 cloned = s8Sub(s, start, s.len, perm);
|
||||
*sl_S8_push(&arr, perm) = cloned;
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
FUNCTION sl_S8
|
||||
s8SplitView(S8 s, S8 delimiter, m_Allocator *perm) {
|
||||
sl_S8 arr = sl_S8_create(NIL, 0);
|
||||
ISize start = 0;
|
||||
for (ISize i = 0; i < s.len; i += 1) {
|
||||
if (s.raw[i] != delimiter.raw[0]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m_memoryDifference(&s.raw[i], delimiter.raw, delimiter.len) == 0) {
|
||||
ISize end = i;
|
||||
S8 view = s8View(s, start, end);
|
||||
*sl_S8_push(&arr, perm) = view;
|
||||
start = end + delimiter.len;
|
||||
}
|
||||
}
|
||||
if (start + delimiter.len < s.len) {
|
||||
S8 view = s8View(s, start, s.len);
|
||||
*sl_S8_push(&arr, perm) = view;
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
FUNCTION S8
|
||||
s8Join(sl_S8 s, S8 join, m_Allocator *perm) {
|
||||
ISize total_length = s.len * join.len;
|
||||
for (ISize i = 0; i < s.len; i += 1) {
|
||||
total_length += s.elems[i].len;
|
||||
}
|
||||
|
||||
U8 *mem = m_MAKE(U8, total_length + 1, perm);
|
||||
ISize offset = 0;
|
||||
for (ISize i = 0; i < s.len; i += 1) {
|
||||
m_memoryCopy(&mem[offset], s.elems[i].raw, s.elems[i].len);
|
||||
offset += s.elems[i].len;
|
||||
|
||||
if (i == s.len - 1) {
|
||||
break;
|
||||
}
|
||||
|
||||
m_memoryCopy(&mem[offset], join.raw, join.len);
|
||||
offset += join.len;
|
||||
}
|
||||
|
||||
mem[total_length] = 0;
|
||||
|
||||
return (S8){total_length, mem};
|
||||
}
|
||||
|
||||
#endif//BASTD_STRING_C
|
|
@ -5,8 +5,10 @@ pushd build
|
|||
|
||||
REM Uncomment one of these to run an example. Try it! They're great documentation.
|
||||
REM cl /Zi ..\bastd\examples\memory.c
|
||||
REM cl /Zi ..\bastd\examples\fib_slice.c
|
||||
cl /Zi ..\bastd\examples\string.c
|
||||
|
||||
REM This builds your application with debug symbols.
|
||||
cl /Zi ..\bastd\examples\memory.c
|
||||
REM cl /Zi ..\main.c
|
||||
|
||||
popd
|
Loading…
Add table
Reference in a new issue