Skip to content

Commit

Permalink
Outline the Music samples polling hack
Browse files Browse the repository at this point in the history
Incorporate some ideas from #104

Co-authored-by: Weypare <andreyavr@gmail.com>
  • Loading branch information
rexim and Weypare committed Jun 10, 2024
1 parent 97c4965 commit 3c10cfd
Show file tree
Hide file tree
Showing 2 changed files with 453 additions and 33 deletions.
302 changes: 302 additions & 0 deletions src/arena.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
// Copyright 2022 Alexey Kutepov <reximkut@gmail.com>

// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:

// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

#ifndef ARENA_H_
#define ARENA_H_

#include <stddef.h>
#include <stdint.h>
#include <string.h>

#ifndef ARENA_NOSTDIO
#include <stdarg.h>
#include <stdio.h>
#endif // ARENA_NOSTDIO

#ifndef ARENA_ASSERT
#include <assert.h>
#define ARENA_ASSERT assert
#endif

#define ARENA_BACKEND_LIBC_MALLOC 0
#define ARENA_BACKEND_LINUX_MMAP 1
#define ARENA_BACKEND_WIN32_VIRTUALALLOC 2
#define ARENA_BACKEND_WASM_HEAPBASE 3

#ifndef ARENA_BACKEND
#define ARENA_BACKEND ARENA_BACKEND_LIBC_MALLOC
#endif // ARENA_BACKEND

typedef struct Region Region;

struct Region {
Region *next;
size_t count;
size_t capacity;
uintptr_t data[];
};

typedef struct {
Region *begin, *end;
} Arena;

#define REGION_DEFAULT_CAPACITY (8*1024)

Region *new_region(size_t capacity);
void free_region(Region *r);

// TODO: snapshot/rewind capability for the arena
// - Snapshot should be combination of a->end and a->end->count.
// - Rewinding should be restoring a->end and a->end->count from the snapshot and
// setting count-s of all the Region-s after the remembered a->end to 0.
void *arena_alloc(Arena *a, size_t size_bytes);
void *arena_realloc(Arena *a, void *oldptr, size_t oldsz, size_t newsz);
char *arena_strdup(Arena *a, const char *cstr);
void *arena_memdup(Arena *a, void *data, size_t size);
#ifndef ARENA_NOSTDIO
char *arena_sprintf(Arena *a, const char *format, ...);
#endif // ARENA_NOSTDIO

void arena_reset(Arena *a);
void arena_free(Arena *a);

#define ARENA_DA_INIT_CAP 256

#ifdef __cplusplus
#define cast_ptr(ptr) (decltype(ptr))
#else
#define cast_ptr(...)
#endif

#define arena_da_append(a, da, item) \
do { \
if ((da)->count >= (da)->capacity) { \
size_t new_capacity = (da)->capacity == 0 ? ARENA_DA_INIT_CAP : (da)->capacity*2; \
(da)->items = cast_ptr((da)->items)arena_realloc( \
(a), (da)->items, \
(da)->capacity*sizeof(*(da)->items), \
new_capacity*sizeof(*(da)->items)); \
(da)->capacity = new_capacity; \
} \
\
(da)->items[(da)->count++] = (item); \
} while (0)

#endif // ARENA_H_

#ifdef ARENA_IMPLEMENTATION

#if ARENA_BACKEND == ARENA_BACKEND_LIBC_MALLOC
#include <stdlib.h>

// TODO: instead of accepting specific capacity new_region() should accept the size of the object we want to fit into the region
// It should be up to new_region() to decide the actual capacity to allocate
Region *new_region(size_t capacity)
{
size_t size_bytes = sizeof(Region) + sizeof(uintptr_t)*capacity;
// TODO: it would be nice if we could guarantee that the regions are allocated by ARENA_BACKEND_LIBC_MALLOC are page aligned
Region *r = (Region*)malloc(size_bytes);
ARENA_ASSERT(r);
r->next = NULL;
r->count = 0;
r->capacity = capacity;
return r;
}

void free_region(Region *r)
{
free(r);
}
#elif ARENA_BACKEND == ARENA_BACKEND_LINUX_MMAP
#include <unistd.h>
#include <sys/mman.h>

Region *new_region(size_t capacity)
{
size_t size_bytes = sizeof(Region) + sizeof(uintptr_t) * capacity;
Region *r = mmap(NULL, size_bytes, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
ARENA_ASSERT(r != MAP_FAILED);
r->next = NULL;
r->count = 0;
r->capacity = capacity;
return r;
}

void free_region(Region *r)
{
size_t size_bytes = sizeof(Region) + sizeof(uintptr_t) * r->capacity;
int ret = munmap(r, size_bytes);
ARENA_ASSERT(ret == 0);
}

#elif ARENA_BACKEND == ARENA_BACKEND_WIN32_VIRTUALALLOC

#if !defined(_WIN32)
# error "Current platform is not Windows"
#endif

#define WIN32_LEAN_AND_MEAN
#include <windows.h>

#define INV_HANDLE(x) (((x) == NULL) || ((x) == INVALID_HANDLE_VALUE))

Region *new_region(size_t capacity)
{
SIZE_T size_bytes = sizeof(Region) + sizeof(uintptr_t) * capacity;
Region *r = VirtualAllocEx(
GetCurrentProcess(), /* Allocate in current process address space */
NULL, /* Unknown position */
size_bytes, /* Bytes to allocate */
MEM_COMMIT | MEM_RESERVE, /* Reserve and commit allocated page */
PAGE_READWRITE /* Permissions ( Read/Write )*/
);
if (INV_HANDLE(r))
ARENA_ASSERT(0 && "VirtualAllocEx() failed.");

r->next = NULL;
r->count = 0;
r->capacity = capacity;
return r;
}

void free_region(Region *r)
{
if (INV_HANDLE(r))
return;

BOOL free_result = VirtualFreeEx(
GetCurrentProcess(), /* Deallocate from current process address space */
(LPVOID)r, /* Address to deallocate */
0, /* Bytes to deallocate ( Unknown, deallocate entire page ) */
MEM_RELEASE /* Release the page ( And implicitly decommit it ) */
);

if (FALSE == free_result)
ARENA_ASSERT(0 && "VirtualFreeEx() failed.");
}

#elif ARENA_BACKEND == ARENA_BACKEND_WASM_HEAPBASE
# error "TODO: WASM __heap_base backend is not implemented yet"
#else
# error "Unknown Arena backend"
#endif

// TODO: add debug statistic collection mode for arena
// Should collect things like:
// - How many times new_region was called
// - How many times existing region was skipped
// - How many times allocation exceeded REGION_DEFAULT_CAPACITY

void *arena_alloc(Arena *a, size_t size_bytes)
{
size_t size = (size_bytes + sizeof(uintptr_t) - 1)/sizeof(uintptr_t);

if (a->end == NULL) {
ARENA_ASSERT(a->begin == NULL);
size_t capacity = REGION_DEFAULT_CAPACITY;
if (capacity < size) capacity = size;
a->end = new_region(capacity);
a->begin = a->end;
}

while (a->end->count + size > a->end->capacity && a->end->next != NULL) {
a->end = a->end->next;
}

if (a->end->count + size > a->end->capacity) {
ARENA_ASSERT(a->end->next == NULL);
size_t capacity = REGION_DEFAULT_CAPACITY;
if (capacity < size) capacity = size;
a->end->next = new_region(capacity);
a->end = a->end->next;
}

void *result = &a->end->data[a->end->count];
a->end->count += size;
return result;
}

void *arena_realloc(Arena *a, void *oldptr, size_t oldsz, size_t newsz)
{
if (newsz <= oldsz) return oldptr;
void *newptr = arena_alloc(a, newsz);
char *newptr_char = (char*)newptr;
char *oldptr_char = (char*)oldptr;
for (size_t i = 0; i < oldsz; ++i) {
newptr_char[i] = oldptr_char[i];
}
return newptr;
}

char *arena_strdup(Arena *a, const char *cstr)
{
size_t n = strlen(cstr);
char *dup = (char*)arena_alloc(a, n + 1);
memcpy(dup, cstr, n);
dup[n] = '\0';
return dup;
}

void *arena_memdup(Arena *a, void *data, size_t size)
{
return memcpy(arena_alloc(a, size), data, size);
}

#ifndef ARENA_NOSTDIO
char *arena_sprintf(Arena *a, const char *format, ...)
{
va_list args;
va_start(args, format);
int n = vsnprintf(NULL, 0, format, args);
va_end(args);

ARENA_ASSERT(n >= 0);
char *result = (char*)arena_alloc(a, n + 1);
va_start(args, format);
vsnprintf(result, n + 1, format, args);
va_end(args);

return result;
}
#endif // ARENA_NOSTDIO

void arena_reset(Arena *a)
{
for (Region *r = a->begin; r != NULL; r = r->next) {
r->count = 0;
}

a->end = a->begin;
}

void arena_free(Arena *a)
{
Region *r = a->begin;
while (r) {
Region *r0 = r;
r = r->next;
free_region(r0);
}
a->begin = NULL;
a->end = NULL;
}

#endif // ARENA_IMPLEMENTATION
Loading

0 comments on commit 3c10cfd

Please sign in to comment.