kit

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

commit 4ee57ded3d787ea29d4fa7528ae452c9a2ddc814
parent 3835e9d2503c407c83fc7f3195f01bd6ae033c92
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Wed, 10 Jun 2026 07:17:03 -0700

cleanup: I.5 — iterative order_dfs + drop dead CSemAssignContext

order_dfs: convert the recursive post-order block DFS (pass_analysis.c)
to an explicit-stack iterative walk so a deeply linear CFG cannot overflow
the C stack. Visit/mark/emit order is identical (successors first, then
label-addr targets in inst order, emit when children exhausted), so the
post-order and the dominance/RPO numbering derived from it are unchanged.

CSemAssignContext: the enum was threaded through c_sem_check_assignment's
7 call sites but the body just did (void)ctx — pure dead weight. Drop the
parameter and the enum.

test-opt, test-cg-api (211), test-parse (3880+128), test-pp, test-toy
(1394/0/33, identical to baseline) all green.

Diffstat:
Mdoc/plan/TODO.md | 3---
Mlang/c/parse/parse.c | 2+-
Mlang/c/parse/parse_expr.c | 6+++---
Mlang/c/parse/parse_init.c | 4++--
Mlang/c/parse/parse_stmt.c | 3+--
Mlang/c/sem/sem.c | 4+---
Mlang/c/sem/sem.h | 9+--------
Msrc/opt/pass_analysis.c | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------
8 files changed, 80 insertions(+), 42 deletions(-)

diff --git a/doc/plan/TODO.md b/doc/plan/TODO.md @@ -124,9 +124,6 @@ Add new deferred fixes below as they are discovered. retag-keep-flags op exists. Add the op. (Pairs with I.1.) - **I.4 — opt passes re-hand-roll the centralized operand-walk.** (The `ranges_overlap` trampoline part is fixed.) -- **I.5 — misc confusing constructs:** the recursive `order_dfs` stack hazard, and the - threaded-but-ignored `CSemAssignContext`. (The obfuscated `max()` and the dep-iter - field conflation are tracked elsewhere — the latter is T1.12.) ## God-function decompositions (highest risk / lowest ROI — do when next touching) diff --git a/lang/c/parse/parse.c b/lang/c/parse/parse.c @@ -914,7 +914,7 @@ static void parse_init_declarator(Parser* p, const DeclSpecs* specs) { { const Type* rhs = pcg_top_type(p); CSemCheck chk = - c_sem_check_assignment(p->pool, var_ty, rhs, C_SEM_ASSIGN_INIT); + c_sem_check_assignment(p->pool, var_ty, rhs); if (!chk.ok) perr(p, "%.*s", KIT_SLICE_ARG(kit_slice_cstr(chk.message))); } diff --git a/lang/c/parse/parse_expr.c b/lang/c/parse/parse_expr.c @@ -2290,8 +2290,8 @@ static void parse_postfix(Parser* p) { parse_assign_expr(p); to_rvalue(p); if (param_ty) { - CSemCheck chk = c_sem_check_assignment( - p->pool, param_ty, pcg_top_type(p), C_SEM_ASSIGN_EXPR); + CSemCheck chk = + c_sem_check_assignment(p->pool, param_ty, pcg_top_type(p)); if (!chk.ok) perr(p, "%.*s", KIT_SLICE_ARG(kit_slice_cstr(chk.message))); coerce_top_to_type(p, param_ty); @@ -3455,7 +3455,7 @@ void parse_assign_expr(Parser* p) { { const Type* rhs = pcg_top_type(p); CSemCheck chk = - c_sem_check_assignment(p->pool, lhs, rhs, C_SEM_ASSIGN_EXPR); + c_sem_check_assignment(p->pool, lhs, rhs); if (!chk.ok) perr(p, "%.*s", KIT_SLICE_ARG(kit_slice_cstr(chk.message))); } coerce_top_to_lvalue(p); diff --git a/lang/c/parse/parse_init.c b/lang/c/parse/parse_init.c @@ -342,7 +342,7 @@ static void init_field_at(Parser* p, FrameSlot slot, const Type* arr_ty, { const Type* rhs = pcg_top_type(p); CSemCheck chk = - c_sem_check_assignment(p->pool, f->type, rhs, C_SEM_ASSIGN_INIT); + c_sem_check_assignment(p->pool, f->type, rhs); if (!chk.ok) perr(p, "%.*s", KIT_SLICE_ARG(kit_slice_cstr(chk.message))); } coerce_top_to_lvalue(p); @@ -764,7 +764,7 @@ void init_at(Parser* p, FrameSlot slot, const Type* arr_ty, u32 offset, to_rvalue(p); { const Type* rhs = pcg_top_type(p); - CSemCheck chk = c_sem_check_assignment(p->pool, ty, rhs, C_SEM_ASSIGN_INIT); + CSemCheck chk = c_sem_check_assignment(p->pool, ty, rhs); if (!chk.ok) perr(p, "%.*s", KIT_SLICE_ARG(kit_slice_cstr(chk.message))); } coerce_top_to_lvalue(p); diff --git a/lang/c/parse/parse_stmt.c b/lang/c/parse/parse_stmt.c @@ -194,8 +194,7 @@ static void parse_return_stmt(Parser* p) { to_rvalue(p); { const Type* rhs = pcg_top_type(p); - CSemCheck chk = c_sem_check_assignment(p->pool, p->cur_func_ret, rhs, - C_SEM_ASSIGN_RETURN); + CSemCheck chk = c_sem_check_assignment(p->pool, p->cur_func_ret, rhs); if (!chk.ok) perr(p, "%.*s", KIT_SLICE_ARG(kit_slice_cstr(chk.message))); } /* Convert the value to the function return type, as `return` performs the diff --git a/lang/c/sem/sem.c b/lang/c/sem/sem.c @@ -43,11 +43,9 @@ static CSemCheck sem_bad(const char* msg) { return c; } -CSemCheck c_sem_check_assignment(Pool* p, const Type* lhs, const Type* rhs, - CSemAssignContext ctx) { +CSemCheck c_sem_check_assignment(Pool* p, const Type* lhs, const Type* rhs) { const Type* lu; const Type* ru; - (void)ctx; if (!lhs || !rhs) return sem_ok(); lu = type_unqual(p, lhs); ru = type_unqual(p, rhs); diff --git a/lang/c/sem/sem.h b/lang/c/sem/sem.h @@ -3,19 +3,12 @@ #include "type/type.h" -typedef enum CSemAssignContext { - C_SEM_ASSIGN_EXPR, - C_SEM_ASSIGN_INIT, - C_SEM_ASSIGN_RETURN, -} CSemAssignContext; - typedef struct CSemCheck { u8 ok; const char* message; } CSemCheck; -CSemCheck c_sem_check_assignment(Pool*, const Type* lhs, const Type* rhs, - CSemAssignContext); +CSemCheck c_sem_check_assignment(Pool*, const Type* lhs, const Type* rhs); CSemCheck c_sem_check_compound_assignment(Pool*, const Type* lhs, const Type* rhs, int op); CSemCheck c_sem_check_redeclaration(Pool*, const Type* old_type, diff --git a/src/opt/pass_analysis.c b/src/opt/pass_analysis.c @@ -217,34 +217,85 @@ static void block_list_add_unique(Arena* arena, OptBlockList* list, u32 block) { block_list_add(arena, list, block); } -static void order_dfs(OptAnalysis* a, u32 block); - -static void order_label_addr_target(OptAnalysis* a, const Inst* in) { +/* The label-addr target block reachable from one instruction, if any + * (computed gotos / local static-data label addresses keep their target block + * live). Returns 1 and sets *out when the inst names a target. */ +static int order_inst_label_target(const Inst* in, u32* out) { switch ((IROp)in->op) { case IR_LOAD_LABEL_ADDR: - order_dfs(a, (u32)in->extra.imm); - break; + *out = (u32)in->extra.imm; + return 1; case IR_LOCAL_STATIC_DATA_LABEL_ADDR: { - CgIrLocalStaticLabelAux* aux = - (CgIrLocalStaticLabelAux*)in->extra.aux; - if (aux) order_dfs(a, (u32)aux->target); - break; + CgIrLocalStaticLabelAux* aux = (CgIrLocalStaticLabelAux*)in->extra.aux; + if (!aux) return 0; + *out = (u32)aux->target; + return 1; } default: - break; + return 0; } } -static void order_dfs(OptAnalysis* a, u32 block) { - if (block >= a->nblocks || a->reachable[block]) return; - a->reachable[block] = 1; - Block* bl = &a->f->blocks[block]; - for (u32 i = 0; i < bl->nsucc; ++i) order_dfs(a, bl->succ[i]); - for (u32 i = 0; i < bl->ninsts; ++i) - order_label_addr_target(a, &bl->insts[i]); - a->po[a->npo] = block; - a->po_index[block] = a->npo; - ++a->npo; +/* One in-progress DFS frame: the block plus cursors into its successor list + * and instruction stream (the two child sources, walked in that order). */ +typedef struct OrderFrame { + u32 block; + u32 succ_i; + u32 inst_i; +} OrderFrame; + +/* Iterative post-order DFS over the block graph: successors first, then the + * label-addr targets in instruction order, recording the block once its + * children are exhausted. An explicit stack replaces recursion so a deeply + * linear CFG cannot overflow the C stack; the visit/mark/emit sequence is + * identical to the former recursive walk, so the post-order (and the + * dominance/RPO numbering derived from it) is unchanged. */ +static void order_dfs(OptAnalysis* a, u32 entry) { + OrderFrame* stack; + u32 sp = 0; + if (entry >= a->nblocks || a->reachable[entry]) return; + stack = arena_array(a->arena, OrderFrame, a->nblocks); + a->reachable[entry] = 1; + stack[sp].block = entry; + stack[sp].succ_i = 0; + stack[sp].inst_i = 0; + ++sp; + while (sp > 0) { + OrderFrame* fr = &stack[sp - 1]; + Block* bl = &a->f->blocks[fr->block]; + int pushed = 0; + while (fr->succ_i < bl->nsucc) { + u32 c = bl->succ[fr->succ_i++]; + if (c < a->nblocks && !a->reachable[c]) { + a->reachable[c] = 1; + stack[sp].block = c; + stack[sp].succ_i = 0; + stack[sp].inst_i = 0; + ++sp; + pushed = 1; + break; + } + } + if (pushed) continue; + while (fr->inst_i < bl->ninsts) { + u32 c; + if (order_inst_label_target(&bl->insts[fr->inst_i++], &c) && + c < a->nblocks && !a->reachable[c]) { + a->reachable[c] = 1; + stack[sp].block = c; + stack[sp].succ_i = 0; + stack[sp].inst_i = 0; + ++sp; + pushed = 1; + break; + } + } + if (pushed) continue; + a->po[a->npo] = fr->block; + a->po_index[fr->block] = a->npo; + ++a->npo; + --sp; + } } void opt_analysis_build_order(Func* f, OptAnalysis* a) {