kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit f595bf99aca8e36b7e2874f6b03cdd6174c25b1e
parent 9f6c53254dc71957f285663f22f230c6e50ff64a
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Fri, 12 Jun 2026 09:43:07 -0700

fix(cg): grow the control-scope stack instead of a fixed 64-deep cap

The KitCg control-scope stack was a fixed ApiCgScope[64]; a deeply nested else-if
chain (e.g. SQLite's shell command dispatch) overflowed it with 'too many nested
scopes'. Make scopes a growable array (mirroring locals) and widen the scope
handle's index field from 8 to 16 bits so depths past 64 stay addressable.

Diffstat:
Msrc/cg/control.c | 35++++++++++++++++++++++++++++++++---
Msrc/cg/internal.h | 12++++++++++--
Msrc/cg/session.c | 10++++++++--
3 files changed, 50 insertions(+), 7 deletions(-)

diff --git a/src/cg/control.c b/src/cg/control.c @@ -412,8 +412,31 @@ void kit_cg_unreachable(KitCg* g) { * Scopes / structured control flow * ============================================================ */ +/* Grow g->scopes to hold at least `want` entries (mirrors api_grow_locals). + * New slots are zeroed so their `active`/`generation` start clean. */ +int api_grow_scopes(KitCg* g, u32 want) { + Heap* h = g->c->ctx->heap; + ApiCgScope* nb; + u32 cap; + if (g->scopes_cap >= want) return 1; + cap = g->scopes_cap ? g->scopes_cap : 16u; + while (cap < want) cap *= 2u; + nb = (ApiCgScope*)h->alloc(h, sizeof(*nb) * cap, _Alignof(ApiCgScope)); + if (!nb) return 0; + memset(nb, 0, sizeof(*nb) * cap); + if (g->scopes) { + memcpy(nb, g->scopes, sizeof(*nb) * g->nscopes); + h->free(h, g->scopes, sizeof(*g->scopes) * g->scopes_cap); + } + g->scopes = nb; + g->scopes_cap = cap; + return 1; +} + +/* The handle packs the 1-based scope index into the low 16 bits and the + * generation into the upper 16 — see api_scope_from_handle. */ KitCgScope api_scope_handle(u32 idx, u32 generation) { - return (KitCgScope)((generation << 8) | ((idx + 1u) & 0xffu)); + return (KitCgScope)((generation << 16) | ((idx + 1u) & 0xffffu)); } ApiCgScope* api_scope_from_handle(KitCg* g, KitCgScope scope, int require_top, @@ -422,8 +445,8 @@ ApiCgScope* api_scope_from_handle(KitCg* g, KitCgScope scope, int require_top, u32 generation; ApiCgScope* s; if (!g || scope == 0) return NULL; - scope_index = ((u32)scope & 0xffu); - generation = ((u32)scope >> 8); + scope_index = ((u32)scope & 0xffffu); + generation = ((u32)scope >> 16); if (scope_index == 0 || scope_index > API_CG_MAX_SCOPES) { compiler_panic(g->c, g->cur_loc, "%.*s: invalid scope handle", SLICE_ARG(slice_from_cstr(who))); @@ -494,9 +517,15 @@ static KitCgScope api_scope_begin_kind(KitCg* g, u8 kind, if (cont_lbl != LABEL_NONE) g->target->label_place(g->target, cont_lbl); if (g->nscopes >= API_CG_MAX_SCOPES) { + /* Depth past what the 16-bit handle index can address (see + * api_scope_handle) — unreachable for any realistic source. */ compiler_panic(g->c, g->cur_loc, "KitCg: too many nested scopes"); return 0; } + if (!api_grow_scopes(g, g->nscopes + 1u)) { + compiler_panic(g->c, g->cur_loc, "KitCg: out of memory growing scopes"); + return 0; + } idx = g->nscopes; s = &g->scopes[idx]; s->break_lbl = break_lbl; diff --git a/src/cg/internal.h b/src/cg/internal.h @@ -131,7 +131,13 @@ typedef struct ApiCgScope { u8 pad[3]; } ApiCgScope; -#define API_CG_MAX_SCOPES 64 +/* Upper bound on live (LIFO-nested) control scopes. This is the limit the + * scope handle can encode, not a preallocated size: g->scopes grows on + * demand (api_grow_scopes). The handle packs the 1-based scope index into + * its low 16 bits (api_scope_handle), so a depth of 0xffff is the most that + * remains addressable. Real translation units nest a few hundred deep at + * most; this ceiling exists only to keep the handle representable. */ +#define API_CG_MAX_SCOPES 0xffffu typedef enum ApiSourceLocalKind { API_SOURCE_LOCAL_AUTO, @@ -211,8 +217,9 @@ struct KitCg { u32* sym_def_seq; u32 sym_def_seq_cap; - ApiCgScope scopes[API_CG_MAX_SCOPES]; + ApiCgScope* scopes; /* grows on demand; see api_grow_scopes */ u32 nscopes; + u32 scopes_cap; u32 scope_generation; u32 rodata_counter; @@ -373,6 +380,7 @@ DebugTypeId api_debug_type(KitCg* g, KitCgTypeId id); int api_local_requires_memory(KitCg* g, KitCgTypeId ty, KitCgLocalAttrs attrs); KitCgLocal api_local_handle(u32 index); int api_grow_locals(KitCg* g, u32 want); +int api_grow_scopes(KitCg* g, u32 want); ApiSourceLocal* api_local_from_handle(KitCg* g, KitCgLocal local); CGLocal api_frame_local_storage(KitCg* g, const CGLocalDesc* d); KitCgLocal kit_cg_local(KitCg* g, KitCgTypeId type, KitCgLocalAttrs attrs); diff --git a/src/cg/session.c b/src/cg/session.c @@ -21,6 +21,10 @@ static void cg_free_obj_state(KitCg* g) { h->free(h, g->locals, sizeof(*g->locals) * g->locals_cap); g->locals = NULL; } + if (g->scopes) { + h->free(h, g->scopes, sizeof(*g->scopes) * g->scopes_cap); + g->scopes = NULL; + } if (g->sym_types) { h->free(h, g->sym_types, sizeof(*g->sym_types) * g->sym_cap); g->sym_types = NULL; @@ -53,11 +57,11 @@ static void cg_free_obj_state(KitCg* g) { g->nlocals = 0; g->const_head = KIT_CG_LOCAL_NONE; g->locals_cap = 0; + g->scopes_cap = 0; g->sym_cap = 0; g->fn_ret_type = 0; memset(&g->fn_desc, 0, sizeof(g->fn_desc)); memset(g->fn_params, 0, sizeof(g->fn_params)); - memset(g->scopes, 0, sizeof(g->scopes)); g->nscopes = 0; g->scope_generation = 0; g->rodata_counter = 0; @@ -481,7 +485,9 @@ void kit_cg_func_end(KitCg* g) { if (g->debug) debug_func_end(g->debug); g->fn_ret_type = KIT_CG_TYPE_NONE; g->nscopes = 0; - memset(g->scopes, 0, sizeof g->scopes); + /* Clear active/generation on the now-dead scope slots so a stray handle + * from this function is caught as stale in the next one. */ + if (g->scopes) memset(g->scopes, 0, sizeof(*g->scopes) * g->scopes_cap); } void api_call_symbol_common(KitCg* g, KitCgSym sym, uint32_t nargs,