1108 lines
29 KiB
C++
1108 lines
29 KiB
C++
#define NOMINMAX
|
|
#include <windows.h>
|
|
#include <stdint.h>
|
|
typedef int8_t S8;
|
|
typedef int16_t S16;
|
|
typedef int32_t S32;
|
|
typedef int64_t S64;
|
|
typedef uint8_t U8;
|
|
typedef uint16_t U16;
|
|
typedef uint32_t U32;
|
|
typedef uint64_t U64;
|
|
typedef S8 B8;
|
|
typedef S16 B16;
|
|
typedef S32 B32;
|
|
typedef S64 B64;
|
|
typedef U64 SizeU;
|
|
typedef S64 SizeI;
|
|
typedef float F32;
|
|
typedef double F64;
|
|
#define function static
|
|
#define global static
|
|
#define force_inline __forceinline
|
|
#define assert(x) do{if(!(x))__debugbreak();}while(0)
|
|
#define assert_msg(x,...) assert(x)
|
|
#define invalid_codepath assert_msg(0, "Invalid codepath")
|
|
#define not_implemented assert_msg(0, "Not implemented")
|
|
#define buff_cap(x) (sizeof(x)/sizeof((x)[0]))
|
|
#define kib(x) ((x)*1024llu)
|
|
#define mib(x) (kib(x)*1024llu)
|
|
#define gib(x) (mib(x)*1024llu)
|
|
struct String{U8 *str;S64 len;};
|
|
union Intern_String{
|
|
String s;
|
|
struct{ U8 *str; S64 len; };
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Utilities
|
|
//-----------------------------------------------------------------------------
|
|
function SizeU
|
|
get_align_offset(SizeU size, SizeU align){
|
|
SizeU mask = align - 1;
|
|
SizeU val = size & mask;
|
|
if(val){
|
|
val = align - val;
|
|
}
|
|
return val;
|
|
}
|
|
|
|
function SizeU
|
|
align_up(SizeU size, SizeU align){
|
|
SizeU result = size + get_align_offset(size, align);
|
|
return result;
|
|
}
|
|
|
|
function SizeU
|
|
align_down(SizeU size, SizeU align){
|
|
size += 1; // Make sure 8 when align is 8 doesn't get rounded down to 0
|
|
SizeU result = size - (align - get_align_offset(size, align));
|
|
return result;
|
|
}
|
|
|
|
function void
|
|
memory_copy(void *dst, void *src, SizeU size){
|
|
U8 *d = (U8*)dst;
|
|
U8 *s = (U8*)src;
|
|
for(SizeU i = 0; i < size; i++){
|
|
d[i] = s[i];
|
|
}
|
|
}
|
|
|
|
function void
|
|
memory_zero(void *p, SizeU size){
|
|
U8 *pp = (U8 *)p;
|
|
for(SizeU i = 0; i < size; i++)
|
|
pp[i] = 0;
|
|
}
|
|
|
|
template<class T>
|
|
T max(T a, T b){
|
|
if(a > b) return a;
|
|
return b;
|
|
}
|
|
|
|
template<class T>
|
|
T min(T a, T b){
|
|
if(a > b) return b;
|
|
return a;
|
|
}
|
|
|
|
template<class T>
|
|
T clamp_top(T val, T max){
|
|
if(val > max) val = max;
|
|
return val;
|
|
}
|
|
|
|
template<class T>
|
|
T clamp_bot(T bot, T val){
|
|
if(val < bot) val = bot;
|
|
return val;
|
|
}
|
|
|
|
template<class T>
|
|
T clamp(T min, T val, T max){
|
|
if(val > max) val = max;
|
|
if(val < min) val = min;
|
|
return val;
|
|
}
|
|
|
|
function U64
|
|
hash_string(String string) {
|
|
U64 hash = (U64)14695981039346656037ULL;
|
|
for (U64 i = 0; i < string.len; i++) {
|
|
hash = hash ^ (U64)(string.str[i]);
|
|
hash = hash * (U64)1099511628211ULL;
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
function U64
|
|
hash_u64(U64 x) {
|
|
x *= 0xff51afd7ed558ccd;
|
|
x ^= x >> 32;
|
|
return x;
|
|
}
|
|
|
|
function U64
|
|
hash_ptr(const void *ptr) {
|
|
return hash_u64((uintptr_t)ptr);
|
|
}
|
|
|
|
function U64
|
|
hash_mix(U64 x, U64 y) {
|
|
x ^= y;
|
|
x *= 0xff51afd7ed558ccd;
|
|
x ^= x >> 32;
|
|
return x;
|
|
}
|
|
|
|
function U64
|
|
is_pow2(U64 x) {
|
|
assert(x != 0);
|
|
B32 result = (x & (x - 1llu)) == 0;
|
|
return result;
|
|
}
|
|
|
|
function U64
|
|
wrap_around_pow2(U64 x, U64 power_of_2) {
|
|
assert(is_pow2(power_of_2));
|
|
U64 r = (((x)&((power_of_2)-1llu)));
|
|
return r;
|
|
}
|
|
|
|
function B32
|
|
string_compare(String a, String b){
|
|
if(a.len != b.len)
|
|
return false;
|
|
for(S64 i = 0; i < a.len; i++){
|
|
if(a.str[i] != b.str[i])
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function U8
|
|
char_to_lower(U8 c){
|
|
if(c >= 'A' && c <= 'Z')
|
|
c += 32;
|
|
return c;
|
|
}
|
|
|
|
function U8
|
|
char_to_upper(U8 c){
|
|
if(c >= 'a' && c <= 'z')
|
|
c -= 32;
|
|
return c;
|
|
}
|
|
|
|
force_inline String
|
|
operator""_s(const char *str, size_t size){
|
|
return String{(U8 *)str, (S64)size};
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// OS Memory
|
|
//-----------------------------------------------------------------------------
|
|
const SizeU os_page_size = 4096;
|
|
struct OS_Memory{
|
|
SizeU commit, reserve;
|
|
U8 *data;
|
|
};
|
|
|
|
function OS_Memory
|
|
os_reserve(SizeU size){
|
|
OS_Memory result = {};
|
|
SizeU adjusted_size = align_up(size, os_page_size);
|
|
result.data = (U8*)VirtualAlloc(0, adjusted_size, MEM_RESERVE, PAGE_READWRITE);
|
|
assert_msg(result.data, "Failed to reserve virtual memory");
|
|
result.reserve = adjusted_size;
|
|
return result;
|
|
}
|
|
|
|
function B32
|
|
os_commit(OS_Memory *m, SizeU size){
|
|
SizeU commit = align_up(size, os_page_size);
|
|
SizeU total_commit = m->commit + commit;
|
|
total_commit = clamp_top(total_commit, m->reserve);
|
|
SizeU adjusted_commit = total_commit - m->commit;
|
|
if(adjusted_commit != 0){
|
|
void *result = VirtualAlloc((U8*)m->data + m->commit, adjusted_commit, MEM_COMMIT, PAGE_READWRITE);
|
|
assert_msg(result, "Failed to commit more memory");
|
|
m->commit += adjusted_commit;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function void
|
|
os_release(OS_Memory *m){
|
|
BOOL result = VirtualFree(m->data, 0, MEM_RELEASE);
|
|
assert_msg(result != 0, "Failed to release OS_Memory");
|
|
if(result){
|
|
m->data = 0;
|
|
m->commit = 0;
|
|
m->reserve = 0;
|
|
}
|
|
}
|
|
|
|
function B32
|
|
os_decommit_pos(OS_Memory *m, SizeU pos){
|
|
SizeU aligned = align_down(pos, os_page_size);
|
|
SizeU adjusted_pos = clamp_top(aligned, m->commit);
|
|
SizeU size_to_decommit = m->commit - adjusted_pos;
|
|
if(size_to_decommit){
|
|
U8 *base_address = m->data + adjusted_pos;
|
|
BOOL result = VirtualFree(base_address, size_to_decommit, MEM_DECOMMIT);
|
|
if(result){
|
|
m->commit -= size_to_decommit;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function void
|
|
test_os_memory(){
|
|
assert(align_down(4096, 4096) == 4096);
|
|
assert(align_down(4095, 4096) == 0);
|
|
|
|
OS_Memory memory = os_reserve(9000);
|
|
assert(memory.reserve == 4096*3 && memory.data && memory.commit == 0);
|
|
os_commit(&memory, 100);
|
|
assert(memory.commit == 4096);
|
|
os_commit(&memory, 100);
|
|
assert(memory.commit == 4096*2);
|
|
os_commit(&memory, 9000);
|
|
assert(memory.commit == 4096*3);
|
|
os_commit(&memory, 9000);
|
|
assert(memory.commit == 4096*3);
|
|
|
|
os_decommit_pos(&memory, 4096);
|
|
assert(memory.commit == 4096);
|
|
os_decommit_pos(&memory, 4096);
|
|
assert(memory.commit == 4096);
|
|
os_decommit_pos(&memory, 0);
|
|
assert(memory.commit == 0);
|
|
|
|
os_release(&memory);
|
|
assert(memory.data == 0);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Base Allocator stuff
|
|
//-----------------------------------------------------------------------------
|
|
enum Allocation_Kind{Allocation_Alloc,Allocation_Resize,Allocation_FreeAll,Allocation_Free,Allocation_Destroy};
|
|
struct Allocator;
|
|
typedef void *Allocator_Proc(Allocator*, Allocation_Kind, void *, SizeU);
|
|
struct Allocator{Allocator_Proc *proc; String debug_name;};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Memory arenas
|
|
//-----------------------------------------------------------------------------
|
|
global const SizeU default_reserve_size = gib(4);
|
|
global const SizeU default_alignment = 8;
|
|
global const SizeU additional_commit_size = mib(1);
|
|
struct Arena:Allocator{
|
|
OS_Memory memory;
|
|
SizeU alignment;
|
|
SizeU len;
|
|
};
|
|
|
|
function void arena_init(Arena *arena, String debug_name);
|
|
|
|
function void
|
|
arena_pop_pos(Arena *arena, SizeU pos){
|
|
pos = clamp_top(pos, arena->len);
|
|
arena->len = pos;
|
|
}
|
|
|
|
function void
|
|
arena_release(Arena *arena){
|
|
os_release(&arena->memory);
|
|
}
|
|
|
|
function void
|
|
arena_clear(Arena *arena){
|
|
arena_pop_pos(arena, 0);
|
|
}
|
|
|
|
function void *
|
|
arena_push_size(Arena *a, SizeU size){
|
|
SizeU generous_size = size + a->alignment;
|
|
if(a->len+generous_size>a->memory.commit){
|
|
if(a->memory.reserve == 0){
|
|
arena_init(a, "Zero initialized arena"_s);
|
|
}
|
|
B32 result = os_commit(&a->memory, generous_size+additional_commit_size);
|
|
assert(result);
|
|
}
|
|
|
|
a->len = align_up(a->len, a->alignment);
|
|
assert(a->memory.reserve > a->len + a->memory.commit);
|
|
void *result = (U8*)a->memory.data + a->len;
|
|
a->len += size;
|
|
return result;
|
|
}
|
|
|
|
force_inline void *
|
|
arena_allocator_proc(Allocator *a, Allocation_Kind kind, void *old_pointer, SizeU size){
|
|
Arena *arena = (Arena *)a;
|
|
switch(kind){
|
|
case Allocation_Alloc: return arena_push_size(arena, size);
|
|
case Allocation_Resize:{
|
|
void *result = arena_push_size(arena, size);
|
|
memory_copy(result, old_pointer, size);
|
|
return result;
|
|
}
|
|
case Allocation_Free : return 0;
|
|
case Allocation_FreeAll: arena_clear(arena); return 0;
|
|
case Allocation_Destroy: arena_release(arena); return 0;
|
|
}
|
|
invalid_codepath;
|
|
return 0;
|
|
}
|
|
|
|
force_inline void *
|
|
personal_arena_allocator_proc(Allocator *a, Allocation_Kind kind, void *old_pointer, SizeU size){
|
|
Arena *arena = (Arena *)a;
|
|
arena->alignment = 1;
|
|
return arena_allocator_proc(a, kind, old_pointer, size);
|
|
}
|
|
|
|
function void
|
|
arena_init(Arena *a, String debug_name){
|
|
a->memory = os_reserve(default_reserve_size);
|
|
a->alignment = default_alignment;
|
|
a->debug_name = debug_name;
|
|
if(!a->proc) a->proc = arena_allocator_proc;
|
|
}
|
|
|
|
function Arena
|
|
arena_make_personal(String debug_name){
|
|
Arena arena = {};
|
|
arena.proc = personal_arena_allocator_proc;
|
|
arena_init(&arena, debug_name);
|
|
return arena;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// OS Heap allocator
|
|
//-----------------------------------------------------------------------------
|
|
struct OS_Heap:Allocator{
|
|
HANDLE handle;
|
|
};
|
|
|
|
function void *
|
|
os_heap_allocator_proc(Allocator *a, Allocation_Kind kind, void *old_pointer, SizeU size){
|
|
OS_Heap *heap = (OS_Heap *)a;
|
|
switch(kind){
|
|
case Allocation_FreeAll:{
|
|
invalid_codepath;
|
|
return 0;
|
|
}
|
|
case Allocation_Destroy:{
|
|
BOOL result = HeapDestroy(heap->handle);
|
|
assert(result != 0);
|
|
heap->handle = 0;
|
|
heap->proc = 0;
|
|
return 0;
|
|
}
|
|
case Allocation_Free:{
|
|
BOOL result = HeapFree(heap->handle, 0, old_pointer);
|
|
assert(result != 0);
|
|
return 0;
|
|
}
|
|
case Allocation_Alloc:{
|
|
void *result = HeapAlloc(heap->handle, 0, size);
|
|
assert(result);
|
|
return result;
|
|
}
|
|
case Allocation_Resize:{
|
|
void *result = HeapReAlloc(heap->handle, 0, old_pointer, size);
|
|
assert(result);
|
|
return result;
|
|
}
|
|
default: invalid_codepath;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
function OS_Heap // max_size == 0 == growing heap
|
|
win32_os_heap_create(B32 multithreaded, SizeU initial_size, SizeU max_size){
|
|
OS_Heap result = {};
|
|
result.proc = os_heap_allocator_proc;
|
|
result.handle = HeapCreate(multithreaded ? 0 : HEAP_NO_SERIALIZE, initial_size, max_size);
|
|
assert(result.handle);
|
|
return result;
|
|
}
|
|
|
|
enum Log_Kind{Log_Kind_Normal, Log_Kind_Error};
|
|
typedef void Log_Proc(Log_Kind kind, String string, char *file, int line);
|
|
//-----------------------------------------------------------------------------
|
|
// Thread Context
|
|
//-----------------------------------------------------------------------------
|
|
struct Thread_Ctx{
|
|
Arena scratch[2];
|
|
Allocator *implicit_allocator;
|
|
void *ctx;
|
|
U64 ctx_id;
|
|
Log_Proc *log_proc;
|
|
|
|
int line;
|
|
char *file;
|
|
};
|
|
thread_local Thread_Ctx thread_ctx;
|
|
global Arena pernament_arena;
|
|
global OS_Heap os_process_heap;
|
|
|
|
#define report_file_and_line() report__file_and_line(__FILE__, __LINE__)
|
|
#define DEBUG_TRACE(x) (report_file_and_line(), (x))
|
|
function void
|
|
report__file_and_line(const char *file, int line){
|
|
thread_ctx.file = (char *)file;
|
|
thread_ctx.line = line;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Implicit scratch stack
|
|
//-----------------------------------------------------------------------------
|
|
#define Set_Scratch() Scoped_Scratch scratch_##__LINE__
|
|
#define Set_Backup_Scratch() Scoped_Scratch scratch_##__LINE__(true)
|
|
struct Scoped_Scratch{
|
|
SizeU saved_pos;
|
|
Allocator *saved_allocator;
|
|
Arena *arena;
|
|
|
|
Scoped_Scratch(B32 backup_scratch=false){
|
|
if(!backup_scratch) arena = thread_ctx.scratch;
|
|
else arena = thread_ctx.scratch + 1;
|
|
saved_allocator = thread_ctx.implicit_allocator;
|
|
saved_pos = arena->len;
|
|
thread_ctx.implicit_allocator = arena;
|
|
}
|
|
~Scoped_Scratch(){
|
|
arena_pop_pos(arena, saved_pos);
|
|
thread_ctx.implicit_allocator = saved_allocator;
|
|
}
|
|
};
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Implicit allocator stack
|
|
//-----------------------------------------------------------------------------
|
|
#define Set_Allocator(a) Scoped_Allocator scoped_##__LINE__(a)
|
|
struct Scoped_Allocator{
|
|
Allocator *allocator;
|
|
Scoped_Allocator(Allocator *a){
|
|
allocator = thread_ctx.implicit_allocator;
|
|
thread_ctx.implicit_allocator = a;
|
|
}
|
|
~Scoped_Allocator(){
|
|
thread_ctx.implicit_allocator = allocator;
|
|
}
|
|
};
|
|
|
|
function void *
|
|
thread_ctx_get_user_ctx(U64 id){
|
|
assert(id == thread_ctx.ctx_id);
|
|
assert(id != 0);
|
|
assert(thread_ctx.ctx_id != 0);
|
|
assert(thread_ctx.ctx != 0);
|
|
return thread_ctx.ctx;
|
|
}
|
|
|
|
#define Get_Ctx(T) T *ctx = (T *)thread_ctx_get_user_ctx(T##_ID)
|
|
#define Set_Ctx(ctx, id) Scoped_Ctx scoped_ctx_##__LINE__((void *)ctx, id)
|
|
struct Scoped_Ctx{
|
|
void *prev_ctx;
|
|
U64 prev_id;
|
|
Scoped_Ctx(void *in_ctx, U64 id){
|
|
prev_ctx = thread_ctx.ctx;
|
|
prev_id = thread_ctx.ctx_id;
|
|
thread_ctx.ctx = in_ctx;
|
|
thread_ctx.ctx_id = id;
|
|
}
|
|
~Scoped_Ctx(){thread_ctx.ctx = prev_ctx; thread_ctx.ctx_id = prev_id;}
|
|
};
|
|
|
|
|
|
enum Alloc_Flag{AF_None,AF_ZeroMemory};
|
|
//-----------------------------------------------------------------------------
|
|
// Explicit allocator
|
|
//-----------------------------------------------------------------------------
|
|
#define exp_alloc_array(a, T, size,...) (T *)exp_alloc(a, sizeof(T)*(size), ## __VA_ARGS__)
|
|
#define exp_alloc_type(a, T, ...) exp_alloc_array(a, T, 1, ## __VA_ARGS__)
|
|
#define exp_alloc(a, size, ...) DEBUG_TRACE(exp__alloc(a, size, ## __VA_ARGS__))
|
|
#define exp_resize(a,p,size) DEBUG_TRACE(exp__resize(a, p, size))
|
|
#define exp_resize_array(a, p, T, size) DEBUG_TRACE((T *)exp_resize(a, p, sizeof(T)*(size)))
|
|
#define exp_free(a, p) DEBUG_TRACE(exp__free(a, p))
|
|
#define exp_free_all(a) DEBUG_TRACE(exp__free_all(a))
|
|
#define exp_destroy(a) DEBUG_TRACE(exp__destroy(a))
|
|
|
|
#include <stdio.h>
|
|
force_inline void *
|
|
exp__alloc(Allocator *a, SizeU size, Alloc_Flag flag = AF_None){
|
|
printf("Alloc(%s) %s:%d %u\n", a->debug_name.str, thread_ctx.file, thread_ctx.line, (U32)size);
|
|
void *result = a->proc(a, Allocation_Alloc, 0, size);
|
|
if(flag & AF_ZeroMemory) memory_zero(result, size);
|
|
return result;
|
|
}
|
|
force_inline void *
|
|
exp__resize(Allocator *a, void *pointer, SizeU size){
|
|
printf("Resize(%s) %s:%d %u\n", a->debug_name.str, thread_ctx.file, thread_ctx.line, (U32)size);
|
|
return a->proc(a, Allocation_Resize, pointer, size);
|
|
}
|
|
force_inline void
|
|
exp__free(Allocator *a, void *pointer){
|
|
printf("Free(%s) %s:%d\n", a->debug_name.str, thread_ctx.file, thread_ctx.line);
|
|
a->proc(a, Allocation_Free, pointer, 0);
|
|
}
|
|
force_inline void
|
|
exp__free_all(Allocator *a){
|
|
printf("FreeAll(%s) %s:%d\n", a->debug_name.str, thread_ctx.file, thread_ctx.line);
|
|
a->proc(a, Allocation_FreeAll, 0, 0);
|
|
}
|
|
force_inline void
|
|
exp__destroy(Allocator *a){
|
|
printf("Destroy(%s) %s:%d\n", a->debug_name.str, thread_ctx.file, thread_ctx.line);
|
|
a->proc(a, Allocation_Destroy, 0, 0);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Implicit allocator
|
|
//-----------------------------------------------------------------------------
|
|
#define imp_alloc_array(T,size,...) (T *)imp_alloc(sizeof(T) * (size), ##__VA_ARGS__)
|
|
#define imp_alloc_type (T,...) imp_alloc_array(T,1, ## __VA_ARGS__)
|
|
#define imp_alloc(size,...) DEBUG_TRACE(imp__alloc(size, ##__VA_ARGS__))
|
|
#define imp_resize_array(p, T,size, ...) (T *)imp_resize(p, sizeof(T) * (size), ##__VA_ARGS__)
|
|
#define imp_resize(p,size) DEBUG_TRACE(imp_resize(p, size))
|
|
#define imp_free(p) DEBUG_TRACE(imp__free(p))
|
|
#define imp_free_all() DEBUG_TRACE(imp__free_all())
|
|
#define imp_destroy() DEBUG_TRACE(imp__destroy())
|
|
|
|
force_inline void *
|
|
imp__alloc(SizeU size, Alloc_Flag flag=AF_None){
|
|
return exp__alloc(thread_ctx.implicit_allocator, size, flag);
|
|
}
|
|
force_inline void *
|
|
imp__resize(void *pointer, SizeU size){
|
|
return exp__resize(thread_ctx.implicit_allocator, pointer, size);
|
|
}
|
|
force_inline void
|
|
imp__free(void *pointer){
|
|
exp__free(thread_ctx.implicit_allocator, pointer);
|
|
}
|
|
force_inline void
|
|
imp__free_all(){
|
|
exp__free_all(thread_ctx.implicit_allocator);
|
|
}
|
|
force_inline void
|
|
imp__destroy(){
|
|
exp__destroy(thread_ctx.implicit_allocator);
|
|
}
|
|
|
|
force_inline Allocator *
|
|
imp_get(){
|
|
assert(thread_ctx.implicit_allocator);
|
|
return thread_ctx.implicit_allocator;
|
|
}
|
|
|
|
function void
|
|
thread_ctx_init(){
|
|
arena_init(thread_ctx.scratch, "Scratch1"_s);
|
|
arena_init(thread_ctx.scratch+1, "Scratch2"_s);
|
|
arena_init(&pernament_arena, "Pernament Arena"_s);
|
|
os_process_heap.proc = os_heap_allocator_proc;
|
|
os_process_heap.handle = GetProcessHeap();
|
|
os_process_heap.debug_name = "Win32 Process Heap"_s;
|
|
|
|
thread_ctx.implicit_allocator = &os_process_heap;
|
|
}
|
|
|
|
function String
|
|
string_copy(Allocator *a, String string){
|
|
U8 *copy = exp_alloc_array(a, U8, string.len+1);
|
|
memory_copy(copy, string.str, string.len);
|
|
copy[string.len] = 0;
|
|
return (String){copy, string.len};
|
|
}
|
|
|
|
#include <stdio.h>
|
|
function String
|
|
string_fmtv(Allocator *a, const char *str, va_list args1) {
|
|
va_list args2;
|
|
va_copy(args2, args1);
|
|
S64 len = vsnprintf(0, 0, str, args2);
|
|
va_end(args2);
|
|
|
|
char *result = exp_alloc_array(a, char, len + 1);
|
|
vsnprintf(result, len + 1, str, args1);
|
|
|
|
String res = {(U8 *)result, len};
|
|
return res;
|
|
}
|
|
|
|
#define STRING_FMT(alloc, str, result) \
|
|
va_list args1; \
|
|
va_start(args1, str); \
|
|
String result = string_fmtv(alloc, str, args1); \
|
|
va_end(args1)
|
|
|
|
function String
|
|
string_fmt(Allocator *a, const char *str, ...) {
|
|
STRING_FMT(a, str, result);
|
|
return result;
|
|
}
|
|
|
|
#define log(...) handle_log_message(Log_Kind_Normal, __LINE__, __FILE__, ## __VA_ARGS__)
|
|
#define log_error(...) handle_log_message(Log_Kind_Error, __LINE__, __FILE__, ## __VA_ARGS__)
|
|
function void
|
|
handle_log_message(Log_Kind kind, int line, const char *file, const char *str, ...){
|
|
Set_Backup_Scratch();
|
|
STRING_FMT(imp_get(), str, message);
|
|
if(thread_ctx.log_proc) thread_ctx.log_proc(kind, message, (char *)file, line);
|
|
else{
|
|
printf("%s", message.str);
|
|
}
|
|
}
|
|
|
|
function void
|
|
test_heap_allocator(){
|
|
OS_Heap heap = win32_os_heap_create(false, mib(1), 0);
|
|
heap.debug_name = "Test heap"_s;
|
|
Set_Allocator(&heap);
|
|
assert(thread_ctx.implicit_allocator == &heap);
|
|
|
|
U8 *result = imp_alloc_array(U8,1024);
|
|
result[1023] = 1;
|
|
result = exp_alloc_type(&heap, U8);
|
|
*result = 0;
|
|
imp_destroy();
|
|
|
|
assert(thread_ctx.implicit_allocator == &heap);
|
|
{
|
|
Set_Scratch();
|
|
assert(thread_ctx.implicit_allocator != &heap);
|
|
assert(thread_ctx.implicit_allocator == thread_ctx.scratch);
|
|
}
|
|
assert(thread_ctx.implicit_allocator == &heap);
|
|
}
|
|
|
|
const U64 Test_Context_ID = 14242;
|
|
struct Test_Context{
|
|
int value;
|
|
};
|
|
|
|
function void
|
|
test_custom_context_2(){
|
|
Get_Ctx(Test_Context);
|
|
ctx->value += 10;
|
|
}
|
|
|
|
function void
|
|
test_custom_context_1(){
|
|
Test_Context context = {};
|
|
Set_Ctx(&context, Test_Context_ID);
|
|
Get_Ctx(Test_Context);
|
|
ctx->value = 10;
|
|
test_custom_context_2();
|
|
test_custom_context_2();
|
|
test_custom_context_2();
|
|
assert(ctx->value == 40);
|
|
assert(thread_ctx.ctx == &context);
|
|
}
|
|
|
|
function void
|
|
test_custom_context(){
|
|
assert(thread_ctx.ctx == 0);
|
|
test_custom_context_1();
|
|
assert(thread_ctx.ctx == 0);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Defer
|
|
// http://www.gingerbill.org/article/2015/08/19/defer-in-cpp/
|
|
//-----------------------------------------------------------------------------
|
|
template <typename F>
|
|
struct Defer_Scope {
|
|
F f;
|
|
Defer_Scope(F f) : f(f) {}
|
|
~Defer_Scope() { f(); }
|
|
};
|
|
|
|
template <typename F>
|
|
Defer_Scope<F> defer_func(F f) {
|
|
return Defer_Scope<F>(f);
|
|
}
|
|
#define DEFER_1(x, y) x##y
|
|
#define DEFER_2(x, y) DEFER_1(x, y)
|
|
#define DEFER_3(x) DEFER_2(x, __COUNTER__)
|
|
#define defer(code) auto DEFER_3(_defer_) = defer_func([&](){code;})
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Array
|
|
//-----------------------------------------------------------------------------
|
|
template<class T>
|
|
struct Array{
|
|
T *data;
|
|
S64 cap;
|
|
S64 len;
|
|
Allocator *allocator;
|
|
|
|
void init(S64 size){
|
|
if(!allocator) allocator = imp_get();
|
|
data = exp_alloc_array(allocator, T, size);
|
|
cap = size;
|
|
}
|
|
|
|
void grow(S64 required_size){
|
|
if(cap == 0){
|
|
S64 cap = max(required_size*2, (S64)16);
|
|
init(cap);
|
|
}
|
|
else if(len + required_size > cap){
|
|
U64 new_cap = max(cap * 2, len+required_size+1);
|
|
data = exp_resize_array(allocator, data, T, new_cap);
|
|
cap = new_cap;
|
|
}
|
|
}
|
|
|
|
void add(T item){
|
|
grow(1);
|
|
data[len++] = item;
|
|
}
|
|
|
|
void clear(){
|
|
len = 0;
|
|
}
|
|
|
|
T *begin(){ return data; }
|
|
T *end (){ return data + len; }
|
|
T &operator[](S64 i){ return data[i]; }
|
|
};
|
|
#define For(array,it,i) for(SizeU i = 0; i < array.len; i++) for(auto *it = &array[i]; it; it = 0)
|
|
#define IFor(array) for(auto *it = array.begin(); it != array.end(); it++)
|
|
#define IterList(list,it) for(auto *it = list->first; it; it=it->next)
|
|
|
|
template<class T>
|
|
Array<T> array_make(S64 size){
|
|
Array<T> result = {};
|
|
result.init(size);
|
|
return result;
|
|
}
|
|
|
|
function void
|
|
test_array(){
|
|
Set_Scratch();
|
|
Array<int> array = {};
|
|
int size = 1000;
|
|
for(int i = 0; i < size; i++){
|
|
array.add(i);
|
|
}
|
|
For(array, it, i){
|
|
assert(*it == i);
|
|
}
|
|
|
|
Arena arena = arena_make_personal("Test personal arena"_s);
|
|
Array<int> array2 = {};
|
|
array2.allocator = &arena;
|
|
for(int i = 0; i < size; i++){
|
|
array2.add(i);
|
|
}
|
|
For(array2, iterator, count){
|
|
assert(*iterator == count);
|
|
}
|
|
exp_destroy(&arena);
|
|
assert(arena.memory.data == 0);
|
|
assert(thread_ctx.scratch->memory.data != 0);
|
|
assert(thread_ctx.scratch == thread_ctx.implicit_allocator);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Map
|
|
//-----------------------------------------------------------------------------
|
|
struct Map_Key_Val{
|
|
U64 key;
|
|
void *value;
|
|
};
|
|
|
|
struct Map{
|
|
Map_Key_Val *data;
|
|
S64 len;
|
|
S64 cap;
|
|
Allocator *allocator;
|
|
};
|
|
function void map_insert_u64(Map *map, U64 key, void *val);
|
|
|
|
function void
|
|
map_grow(Map *map, S64 new_size){
|
|
new_size = max((S64)16, new_size);
|
|
assert(new_size > map->cap);
|
|
assert(is_pow2(new_size));
|
|
if(!map->allocator) map->allocator = imp_get();
|
|
|
|
Map new_map = {};
|
|
new_map.data = exp_alloc_array(map->allocator, Map_Key_Val, new_size, AF_ZeroMemory),
|
|
new_map.cap = new_size,
|
|
new_map.allocator = map->allocator;
|
|
|
|
for(S64 i = 0; i < map->cap; i++){
|
|
if(map->data[i].key){
|
|
map_insert_u64(&new_map, map->data[i].key, map->data[i].value);
|
|
}
|
|
}
|
|
if(map->data) exp_free(map->allocator, map->data);
|
|
*map = new_map;
|
|
}
|
|
|
|
function Map
|
|
map_make(S64 size){
|
|
Map result = {};
|
|
map_grow(&result, size);
|
|
return result;
|
|
}
|
|
|
|
function void
|
|
map_insert_u64(Map *map, U64 key, void *val){
|
|
assert(val);
|
|
if(key == 0) key++;
|
|
if((2*map->len) + 1 > map->cap){
|
|
map_grow(map, 2*map->cap);
|
|
}
|
|
U64 hash = hash_u64(key);
|
|
U64 index = wrap_around_pow2(hash, map->cap);
|
|
U64 i = index;
|
|
for(;;){
|
|
if(map->data[i].key == 0){
|
|
map->len++;
|
|
map->data[i].key = key;
|
|
map->data[i].value = val;
|
|
return;
|
|
}
|
|
else if(map->data[i].key == key){
|
|
map->data[i].value = val;
|
|
return;
|
|
}
|
|
|
|
i = wrap_around_pow2(i+1, map->cap);
|
|
if(i == map->cap){
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
function void *
|
|
map_get_u64(Map *map, U64 key){
|
|
if(map->len == 0) return 0;
|
|
if(key == 0) key++;
|
|
U64 hash = hash_u64(key);
|
|
U64 index = wrap_around_pow2(hash, map->cap);
|
|
U64 i = index;
|
|
for(;;){
|
|
if(map->data[i].key == key){
|
|
return map->data[i].value;
|
|
}
|
|
else if(map->data[i].key == 0){
|
|
return 0;
|
|
}
|
|
|
|
i = wrap_around_pow2(i+1, map->cap);
|
|
if(i == map->cap){
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
function void *
|
|
map_get(Map *map, void *pointer){
|
|
return map_get_u64(map, (U64)pointer);
|
|
}
|
|
|
|
function void
|
|
map_insert(Map *map, void *key, void *value){
|
|
map_insert_u64(map, (U64)key, value);
|
|
}
|
|
|
|
function void
|
|
map_test(){
|
|
Set_Scratch();
|
|
Map map = {0};
|
|
const SizeU size = 1025;
|
|
for(SizeU i = 1; i < size; i++){
|
|
map_insert_u64(&map, i, (void *)i);
|
|
}
|
|
for(SizeU i = 1; i < size; i++){
|
|
SizeU val = (SizeU)map_get_u64(&map, i);
|
|
assert(val == i);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Linked lists
|
|
//-----------------------------------------------------------------------------
|
|
#define SLLQueuePushMod(f,l,n,next) do{\
|
|
if((f)==0){\
|
|
(f)=(l)=(n);\
|
|
}\
|
|
else{\
|
|
(l)=(l)->next=(n);\
|
|
} \
|
|
}while(0)
|
|
#define SLLQueuePush(f,l,n) SLLQueuePushMod(f,l,n,next)
|
|
|
|
#define SLLStackPush(l,n) do{\
|
|
(n)->next = (l);\
|
|
(l) = (n);\
|
|
}while(0)
|
|
|
|
#define SLLStackPop(l,n) do{\
|
|
if(l){\
|
|
(n) = (l);\
|
|
(l) = (l)->next;\
|
|
(n)->next = 0;\
|
|
}\
|
|
}while(0)
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// String builder
|
|
//-----------------------------------------------------------------------------
|
|
struct String_Builder_Block{
|
|
String_Builder_Block *next;
|
|
S64 cap;
|
|
S64 len;
|
|
U8 data[0];
|
|
};
|
|
|
|
struct String_Builder{
|
|
String_Builder_Block *first;
|
|
String_Builder_Block *last;
|
|
Allocator *allocator;
|
|
};
|
|
|
|
function void
|
|
string_builder_push_block(String_Builder *b, SizeU size){
|
|
String_Builder_Block *block = (String_Builder_Block *)imp_alloc(sizeof(String_Builder_Block) + size);
|
|
memory_zero(block, sizeof(String_Builder_Block)+1); // Also clear first byte of character data
|
|
block->cap = size;
|
|
SLLQueuePush(b->first, b->last, block);
|
|
}
|
|
|
|
function void
|
|
string_builder_init(String_Builder *b, SizeU size = 4096){
|
|
if(!b->allocator) b->allocator = imp_get();
|
|
string_builder_push_block(b, size);
|
|
}
|
|
|
|
function void
|
|
appendf(String_Builder *b, const char *str, ...){
|
|
if(b->first == 0){
|
|
string_builder_init(b);
|
|
}
|
|
va_list args, args2;
|
|
va_start(args, str); defer(va_end(args));
|
|
retry:{
|
|
String_Builder_Block *block = b->last;
|
|
int block_size = block->cap - block->len;
|
|
char *write_address = (char *)block->data + block->len;
|
|
|
|
va_copy(args2, args); defer(va_end(args2));
|
|
int written = vsnprintf(write_address, block_size, str, args2);
|
|
|
|
if(written > block_size){
|
|
int new_block_size = max(4096, (written+1)*2);
|
|
string_builder_push_block(b, new_block_size);
|
|
goto retry;
|
|
}
|
|
block->len += written;
|
|
}
|
|
}
|
|
|
|
function String
|
|
string_flatten(String_Builder *b){
|
|
// @Note(Krzosa): Only single block, no need to flatten, vsnprintf null terminates too
|
|
if(b->first == b->last){
|
|
String result = {b->first->data, b->first->len};
|
|
return result;
|
|
}
|
|
|
|
// @Note(Krzosa): Compute size to allocate
|
|
S64 size = 1;
|
|
IterList(b, it){
|
|
size += it->len;
|
|
}
|
|
String result = {};
|
|
result.str = (U8 *)exp_alloc(b->allocator, size);
|
|
|
|
// @Note(Krzosa): Copy the content of each block into the string
|
|
IterList(b, it){
|
|
memory_copy(result.str + result.len, it->data, it->len);
|
|
result.len += it->len;
|
|
}
|
|
result.str[result.len] = 0;
|
|
return result;
|
|
}
|
|
|
|
function void
|
|
test_string_builder(){
|
|
Set_Scratch();
|
|
String_Builder sb = {};
|
|
string_builder_init(&sb, 4);
|
|
appendf(&sb, "Thing, %d", 242252);
|
|
String f = string_flatten(&sb);
|
|
assert(string_compare(f, "Thing, 242252"_s));
|
|
appendf(&sb, "-%f %f %f", 23.0, 42.29, 2925.2);
|
|
f = string_flatten(&sb);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// String intern
|
|
//-----------------------------------------------------------------------------
|
|
struct Intern_Table{
|
|
Allocator *string_allocator;
|
|
Map map;
|
|
U8 *first_keyword;
|
|
U8 *last_keyword;
|
|
};
|
|
|
|
function Intern_Table
|
|
intern_table_make(S64 initial_size){
|
|
Intern_Table result = {};
|
|
result.map = map_make(initial_size);
|
|
result.string_allocator = imp_get();
|
|
return result;
|
|
}
|
|
|
|
function Intern_String
|
|
intern_string(Intern_Table *t, String string){
|
|
if(!t->string_allocator) t->string_allocator = imp_get();
|
|
U64 hash = hash_string(string);
|
|
U8 *slot = (U8 *)map_get_u64(&t->map, hash);
|
|
if(slot){
|
|
Intern_String result = {{slot, *(slot-sizeof(S64))}};
|
|
return result;
|
|
}
|
|
|
|
S64 *len_address = (S64 *)exp_alloc(t->string_allocator, string.len+1+sizeof(S64));
|
|
*len_address = string.len;
|
|
|
|
U8 *string_address = (U8 *)(len_address + 1);
|
|
memory_copy(string_address, string.str, string.len);
|
|
string_address[string.len] = 0;
|
|
|
|
map_insert_u64(&t->map, hash, string_address);
|
|
Intern_String result = {{string_address, *len_address}};
|
|
|
|
return result;
|
|
}
|
|
|
|
function void
|
|
test_intern_table(){ Set_Scratch();
|
|
Intern_Table table = {};
|
|
Intern_String intern1 = intern_string(&table, "Thing"_s);
|
|
Intern_String intern2 = intern_string(&table, "Thing"_s);
|
|
Intern_String intern3 = intern_string(&table, "Not Thing"_s);
|
|
assert(intern1.str == intern2.str);
|
|
assert(intern3.str != intern2.str);
|
|
}
|
|
|
|
#include "new_lex.cpp"
|
|
#include "new_ast.cpp"
|
|
#include "new_parse.cpp"
|
|
int main(){
|
|
test_custom_context();
|
|
test_heap_allocator();
|
|
test_os_memory();
|
|
|
|
thread_ctx_init();
|
|
map_test();
|
|
|
|
test_parse_decl();
|
|
test_parse_expr();
|
|
|
|
test_array();
|
|
test_string_builder();
|
|
test_intern_table();
|
|
lex_test();
|
|
}
|