power_play/src/scratch.h

117 lines
3.8 KiB
C

#ifndef SCRATCH_H
#define SCRATCH_H
#include "arena.h"
#include "sys.h"
#include "thread_local.h"
#define SCRATCH_ARENAS_PER_THREAD 2
#define SCRATCH_ARENA_RESERVE (GIGABYTE(64))
/* ========================== *
* Thread local state
* ========================== */
struct scratch_ctx {
struct arena *arenas[SCRATCH_ARENAS_PER_THREAD];
#if RTC
u64 next_scratch_id;
u64 scratch_id_stack[16384];
u64 scratch_id_stack_count;
#endif
};
THREAD_LOCAL_VAR_DECL_EXTERN(tl_scratch_ctx, struct scratch_ctx);
/* ========================== *
* Scratch begin
* ========================== */
INLINE void scratch_dbg_push(struct scratch_ctx *ctx, struct arena_temp *temp)
{
#if RTC
if (ctx->scratch_id_stack_count >= ARRAY_COUNT(ctx->scratch_id_stack)) {
sys_panic(LIT("Max debug scratch depth reached"));
}
temp->scratch_id = ctx->next_scratch_id++;
ctx->scratch_id_stack[ctx->scratch_id_stack_count++] = temp->scratch_id;
#else
(UNUSED)ctx;
(UNUSED)temp;
#endif
}
/* Any parameterized arenas in the caller's scope should be passed into this
* function as a potential "conflict". This is to prevent friction in case the
* passed arena is itself a scratch arena from another scope (since
* parameterized arenas are often used to allocate persistent results for the
* caller).
*
* Use `scratch_begin_no_conflict` instead if there is no arena in the current
* scope that could potentially be a scratch arena from another scope. */
#define scratch_begin(potential_conflict) _scratch_begin(potential_conflict)
INLINE struct arena_temp _scratch_begin(struct arena *potential_conflict)
{
/* This function is currently hard-coded to support 2 scratch arenas */
CT_ASSERT(SCRATCH_ARENAS_PER_THREAD == 2);
/* Use `scratch_begin_no_conflict` if no conflicts are present */
ASSERT(potential_conflict != NULL);
struct scratch_ctx *ctx = (struct scratch_ctx *)thread_local_var_eval(&tl_scratch_ctx);
struct arena *scratch_arena = ctx->arenas[0];
if (potential_conflict && scratch_arena == potential_conflict) {
scratch_arena = ctx->arenas[1];
}
struct arena_temp temp = arena_temp_begin(scratch_arena);
scratch_dbg_push(ctx, &temp);
return temp;
}
/* This macro declares an unused "arena" variable that will error if an existing "arena"
* variable is present (due to shadowing). This is for catching obvious cases of
* `scratch_begin_no_conflict` getting called when an `arena` variable already
* exists in the caller's scope (`scratch_begin(arena)` should be called
* instead). */
#define scratch_begin_no_conflict() \
_scratch_begin_no_conflict(); \
do { \
u8 arena = 0; \
(UNUSED)arena; \
} while (0)
INLINE struct arena_temp _scratch_begin_no_conflict(void)
{
struct scratch_ctx *ctx = (struct scratch_ctx *)thread_local_var_eval(&tl_scratch_ctx);
struct arena *scratch_arena = ctx->arenas[0];
struct arena_temp temp = arena_temp_begin(scratch_arena);
scratch_dbg_push(ctx, &temp);
return temp;
}
/* ========================== *
* Scratch end
* ========================== */
INLINE void scratch_end(struct arena_temp scratch_temp)
{
#if RTC
struct scratch_ctx *ctx = (struct scratch_ctx *)thread_local_var_eval(&tl_scratch_ctx);
if (ctx->scratch_id_stack_count > 0) {
u64 scratch_id = scratch_temp.scratch_id;
u64 expected_id = ctx->scratch_id_stack[--ctx->scratch_id_stack_count];
/* This assertion exists to catch cases where a scratch_end was forgotten.
* It will fail if a scratch arena is reset out of order.
* E.g. there is a missing scratch_end somewhere on a different scratch
* arena (one that was created between the scratch_begin & the
* scratch_end of the arena being reset here). */
ASSERT(scratch_id == expected_id);
}
#endif
arena_temp_end(scratch_temp);
}
#endif