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:
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,