power_play/src/scratch.h

116 lines
3.7 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 temp_arena *temp)
{
#if RTC
if (ctx->scratch_id_stack_count >= ARRAY_COUNT(ctx->scratch_id_stack)) {
sys_panic(STR("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 arena parameters in the calling function's context should be passed into this
* function as a potential `conflict`. This is to prevent conflicts when the
* context's arena is itself a scratch arena (since parameterized arenas are
* often used to allocate persistent results for the caller).
*
* Call `scratch_begin_no_conflict` instead if there is no arena in the current
* context that could potentially be a scratch arena. */
#define scratch_begin(c) _scratch_begin(c)
INLINE struct temp_arena _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 = thread_local_var_eval(&tl_scratch_ctx);
struct arena *scratch_arena = &ctx->arenas[0];
if (potential_conflict && scratch_arena->base == potential_conflict->base) {
scratch_arena = &ctx->arenas[1];
}
struct temp_arena 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 context (`scratch_begin(arena)` should be called
* instead). */
#define scratch_begin_no_conflict() \
_scratch_begin_no_conflict(); \
do { \
struct arena *arena = NULL; \
(UNUSED)arena; \
} while (0)
INLINE struct temp_arena _scratch_begin_no_conflict(void)
{
struct scratch_ctx *ctx = thread_local_var_eval(&tl_scratch_ctx);
struct arena *scratch_arena = &ctx->arenas[0];
struct temp_arena temp = arena_temp_begin(scratch_arena);
scratch_dbg_push(ctx, &temp);
return temp;
}
/* ========================== *
* Scratch end
* ========================== */
INLINE void scratch_end(struct temp_arena scratch_temp)
{
#if RTC
struct scratch_ctx *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