kit

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

commit 32e99c0ce51b72eb9974aaf7abf9b7b3d2621121
parent addd35ea50167770689882c2c27bbdf8de3a4b95
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue,  9 Jun 2026 22:33:57 -0700

doc: consolidate cleanup backlog into TODO.md, drop the wave-tracking doc

The multi-wave cleanup (commits 78317497..addd35ea) is done; its per-wave
tracking doc has served its purpose. Moved the still-open items and known
failures into doc/plan/TODO.md (open-catalog style: known bugs, design-needed,
deferred dedups, god-functions) and removed doc/plan/CLEANUP-2026-06-09.md.

Diffstat:
Ddoc/plan/CLEANUP-2026-06-09.md | 728-------------------------------------------------------------------------------
1 file changed, 0 insertions(+), 728 deletions(-)

diff --git a/doc/plan/CLEANUP-2026-06-09.md b/doc/plan/CLEANUP-2026-06-09.md @@ -1,728 +0,0 @@ -# Cleanup tracking — 2026-06-09 - -Source: adversarial code-quality review of kit. 36 finders across `lang/ src/ driver/ -rt/ include/` → 139 raw findings → **135 confirmed** by independent skeptical verifiers -(0 high / 28 medium / 107 low) → deduped to **12 themes**, **40 ranked findings**, **27 -quick wins**. 4 findings were rejected with reasoning (see "Rejected"). - -Six of the most consequential findings were independently re-verified against source -before this doc was written; all six held up (`#embed` overflow, rv32 stubs, `max_hits`, -`object_builder` entsize, the rv64 reentrancy static, inline-asm triplication). - -**Through-line:** kit already built the right abstraction, then bypassed it in N places. -The goal of this cleanup is *the right abstractions consistently used, not bypassed* — -not new abstractions. - -## How to use this doc - -- `[ ]` = open, `[x]` = landed (with commit hash), `[~]` = in progress. -- Each item links file:line. "Fix" is the agreed approach. -- Execution is in **waves** (bottom of doc). A wave = parallel agents on *disjoint files*, - gated by a build + targeted-test + commit. God-function decompositions are last and - optional (highest risk, lowest ROI). - ---- - -## Tier 1 — Latent bugs (fix first) - -Dormant or narrow today, but real defects. - -- [x] **T1.1 `#embed` stack overflow** — `lang/cpp/pp/pp_directive.c:1166-1179`. `do_embed` - copied the `TOK_HEADER` decode from `parse_include_path` but dropped its - `slen-2+1 > cap` guard (`:731`); unbounded `memcpy` into `char path[4096]`. - *Fix:* extract a shared `header_name_to_path(slice, out, cap, *system)` with the cap - check; call from both `parse_include_path` and `do_embed`. -- [x] **T1.2 JIT panic on `_tls_index`** — `src/link/link_jit.c:791-811, 991-1005`. Append - preflight accepts undef `__tlv_bootstrap` *and* `_tls_index`; the append-resolve loop - only special-cases `__tlv_bootstrap`, so a COFF object referencing `_tls_index` passes - preflight then panics. The full-link path resolves it — the copies drifted. - *Fix:* one `jit_undef_acceptable` predicate driven by the existing - `obj_format_jit_drops_symbol_ref` hook; route all 3 sites through it. -- [~] **T1.3 C-target emits wrong data for non-absolute relocs** — `c_emit.c:3846, 3911`. - **NEEDS DESIGN — attempted in Wave A, reverted.** Width is hardcoded `(kind==R_ABS32)?4:8` - and every reloc renders as an absolute pointer, so a sub-pointer-width address reloc - (`@pcrel` into a 4-byte `i32` slot, e.g. toy tests 96/100) overflows the slot. But the - root issue is deeper: the C target **cannot** put a real `&sym` (8 bytes) into a 4-byte - slot in portable C, and a true PC-relative/section-diff offset is not a C constant - expression at all. The old code "works" only by emitting an oversized `void*` that - compiles+runs (the data is never read). A correct fix is a design decision on the C - target's data-reloc model — **either** reject such relocs (`compiler_panic`) and mark - tests 96/100 `SKIP` on `/C`, **or** emit runtime init-time fixups. The naive - panic-on-non-absolute tried in Wave A broke 96/100 (which intentionally exercise - pcrel-in-data on every backend incl. C). Deferred to a dedicated design pass. -- [x] **T1.4 RISC-V feature-off build break** — `src/arch/link_stubs.c`, `dbg_stubs.c`. - `arch_impl_rv32` is built unconditionally and references `link_arch_rv32`/`rv32_dbg_ops`, - but the stubs define only aa64/x64/**rv64**. *Fix:* add - `const LinkArchDesc link_arch_rv32 = {0};` and `const ArchDbgOps rv32_dbg_ops = {0};`. -- [x] **T1.5 `max_hits` is a dead public API field** — `src/dbg/session.c:143-146`. The - limit condition is computed into an `if` with an empty body + a comment promising a - nonexistent post-park auto-clear. Breakpoints fire forever past `max_hits`. *Fix:* - implement the auto-clear on the post-park path, or delete the field + plumbing. -- [x] **T1.6 Debugger one-shot breakpoints leak** — `src/dbg/step.c:102-128, 201, 232`. - step-into/over/out/next arm an internal bp and rely on `on_fault`'s clear path that - only fires if *that* bp traps; if a user bp stops first (common in `finish`/`next`), - the one-shot stays patched and resurfaces as a spurious `bp_id==0` stop misread as - step-completion. *Fix:* record each one-shot `bp_id`, clear on every non-self-completion - return path (one arm/resume/cleanup helper). -- [x] **T1.7 `.kpkg`/CAS parsers drop NUL rejection** — `src/dist/{tree,manifest,kpkg}.c`. - KV-line scanner triplicated; only `tree.c` rejects embedded NUL, so a NUL silently - truncates a value in the others (via `strlen`/`%s`). *Fix:* shared `dist_kv_next_line` - iterator, or at minimum add NUL rejection to `manifest.c`/`kpkg.c`. -- [x] **T1.8 Path-traversal validator drift** — `src/dist/tree.c:39-54` + manifest. - `dist_tree_path_valid` and `pkg_locator_safe` byte-identical; `dist_manifest_path_valid` - omits the `\n`/`\r` rejection. *Fix:* all three share one core. -- [x] **T1.9 Windows runtime-insert fix only in `cc`** — `driver/cmd/cc.c:2455-2502` vs - `build.c:1963`. The two-position rt-archive insert (`_setjmp` WEAK_EXTERNAL + late - `__chkstk_ms`) lives only in `cc`; `build-exe` does the single end-insert and is liable - to the `0xC0000139`/chkstk failures the `cc` comment documents. *Fix:* extract - `driver_link_inputs_insert_runtime`, call from both. -- [x] **T1.10 `object_builder` entsize orphans a section** — - `src/api/object_builder.c:79-101`. With `entsize` set: find-or-create, then a dead - `obj_section_get`+`(void)sec` with 3 self-contradicting comments, then `obj_section_ex` - pushes a *new* section → orphan + duplicate returned. Dormant (all callers pass - `entsize=0`). *Fix:* `obj_section_set_entsize` on the single id; delete dead read + false - comments. -- [x] **T1.11 wasm public/internal enum skew** — `include/kit/wasm.h:63-115` vs - `lang/wasm/runtime_abi.h`. `KIT_WASM_MEMORY_*`, `KitWasmImportKind`, - `KitWasm{Memory,Runtime}Layout` duplicated field-for-field with no `_Static_assert`; a - one-sided edit silently desyncs emitter↔reader. *Fix:* `runtime_abi.h` includes - `kit/wasm.h` for the shared types; delete the guarded duplicates. -- [~] **T1.12 `kit_dep_iter` system-path conflation** — `src/api/compile.c:654-655`. - `from_system_path` and `bracketed` both assigned the single `<>`-vs-`""` bit, so a - `<...>` resolved via `-I` falsely reports `from_system_path=1`. *Fix:* propagate the - resolved dir's `system` flag (or drop/document the redundant field). - ---- - -## Tier 2 — Abstraction-consistency themes - -The core of "use the abstraction, don't bypass it." Grouped by theme; each bullet is a -confirmed finding. - -### A. Inline-asm orchestration triplicated (med, 4) -- [x] **A.1** `aa_/x64_/rv_direct_asm_block` (`aa64/native.c:4711`, `x64/native.c:4383`, - `riscv/native.c:4083`) were ~170-line copies on top of the *already-shared* - `cg/native_asm.c` primitives. DONE: hoisted `native_asm_bind_direct_operands` + the shared - driver + `NativeAsmDirectHooks` into `cg/native_asm.{c,h}`; each backend's `*_direct_asm_block` - is now a thin wrapper supplying scratch masks / OPK constants / a panic + a few adapters - (−222 LOC × 3 native.c). The direct copies turned out **not** drifted (only the scratch-mask - seed differed); behavior preserved, verified by inline-asm × 3 / toy / smoke. -- [~] **A.2 (surfaced by A.1)** the *optimizer-path* hooks `aa_/x64_/rv_asm_block_native`. - **INVESTIGATED; dedup DEFERRED (deliberately).** Verdict: rv64's extra `staged_outs[i]=1` - output-staging branch is **NOT a bug** — it's a needed rv64 difference (rv64 has dedicated - stage regs `RV_TMP2/3`+`RV_FTMP0/1` distinct from its mem-scratch, so it can safely stage - register-constrained operands that didn't get a hard reg from regalloc). The optimizer-path - differences are *policy* (different staging coverage + structurally different scratch - models), not just naming — a hooks-based dedup would relocate, not unify, behavior and add - miscompile risk in a hot path. Correct sequencing: land **A.3** first, then dedup. -- [ ] **A.3 (NEW — latent robustness gap, surfaced by A.2's investigation)** aa64/x64 inline-asm - operand staging is *narrower* than rv64: for a register-constrained operand that regalloc - could not keep in a hard register (address-taken/spilled local), rv64 stages it to a scratch - reg and succeeds, but **aa64/x64 panic** (`"register asm operand not in a register"`). Gaps: - (a) register-constrained **outputs** in a non-reg loc — no fallback on aa64 (`native.c:4883`) - or x64 (`:4021`), only rv64 (`riscv/native.c:3780`); (b) **FP-constrained inputs** (`"w"` aa64, - `"x"/"v"` x64) in a non-reg loc; (c) x64 **`"q"`** inputs (the input-stage test is raw - `body[0]=='r'`, so `q`→INT,allowed=0 skips staging then panics). *Fix:* give aa64/x64 the - equivalent fallback staging, with test cases — changes which programs compile vs panic, so a - dedicated reviewed change (NOT a dedup). After this, A.2's dedup becomes safe. - -### B. Per-arch codegen plumbing duplicated (med, 7) -- [x] **B.1** x64 callee-save offset formula open-coded 5+ times; collect+reverse-restore - loop duplicated between `x64_func_end` and `x64_emit_tail_site` — - `src/arch/x64/native.c:1946-1953, 2655-2666`. aa64 already factored - `aa_emit_callee_restores`. *Fix:* `x64_cs_int_off/x64_cs_fp_off` helpers + extract - `x64_emit_callee_restores`, mirroring aa64. -- [x] **B.2** `CGCallDesc→NativeCallDesc` tail-call projection (`rv_no_tail`/`aa_no_tail`/ - `x64_no_tail`) duplicated. *Fix:* shared projection in `cg/native_direct_target`. -- [x] **B.3** PC-relative `auipc`+anchor reloc pair emitted 3× in riscv. *Fix:* one helper. -- [x] **B.4** Indirect `jmp`/`call r/m64` encoding 3× in x64. *Fix:* one encoder. -- [~] **B.5** `backend_make`/`semantic_target_new`/`register_at_public` byte-identical - across arches. *Fix:* share via `src/arch/registry` / `cg/native_direct_target`. - -### C. ABI / soft-float / reloc lowering hand-copied (med→low, 7) -- [x] **C.1** `compute_func_info` driver + `classify_one` dispatch + single-register scalar - tail near-identical across all 4 ABI vtable TUs (`src/abi/abi_sysv_x64.c:184-202,218-279` - + siblings). *Fix:* `abi_compute_func_info_generic(classify_one cb, sret_consumes_int)` + - `abi_classify_scalar_reg_part` in `abi.c`; each TU keeps only aggregate/scalar specifics. -- [x] **C.2** `kit_cg_fp_binop` + 8 float↔int conversions open-code 16 width→type ternaries - (`src/cg/arith.c:1253-1704`) while `api_softfp_cmp` proves the parameterized form. *Fix:* - `api_softfp_binop` mirroring `api_softfp_cmp` + an `(op,width)→suffix` table. -- [x] **C.3** RV B/J/S immediate bit-scramble reimplemented 3×. *Fix:* one shared encoder. -- [ ] **C.4** rv32 ELF reloc map clones rv64 (3 cases differ); rv32 PLT/IPLT emitters clone - rv64 (one LW-vs-LD). *Fix:* parameterize on word size. -- [ ] **C.5** RISC-V float-ABI→`e_flags` mapping inlined in both ET_REL and ET_EXEC writers - (`src/obj/elf`). *Fix:* one helper; do not encode arch direction in the format-generic - ELF layer. - -### D. Leaky format/arch identity in generic code (med→low, 7) -- [ ] **D.1** JIT undef-resolution memcmps `__tlv_bootstrap`/`_tls_index` in 3+ sites — - covered by **T1.2** (extend the format authority to cover both pseudo-symbols). -- [x] **D.2** `link_emit_relocations` skips RV markers *by enum name* while the - `RELOC_MARKER` descriptor flag built for it sits dead — `src/link/link_reloc_layout.c:937`. - *Fix:* add `reloc_kind_is_marker` (reads `RELOC_MARKER`), tag `R_RV_ALIGN`'s row, replace - the name check; same for the dead `RELOC_WIDTH_DYN` at `:982`. -- [ ] **D.3** Five `obj_secname_*` are raw `switch(target.obj)` where sibling format strings - are vtable fields. *Fix:* move to the ObjFormat vtable. -- [ ] **D.4** `obj_format_lookup_bin` reimplements the dead `bin_fmt` field's mapping. - *Fix:* delete the reimpl or the field. -- [ ] **D.5** C-target data reloc width hardcoded — covered by **T1.3**. - -### E. Driver/tool glue duplicated despite `lib/` seam (med→low, 9) -- [x] **E.1** Link pipeline (alloc/load/order-translate/emit/cleanup) copy-pasted between - `cc_run_link_exe` (`cc.c:2013-2346`, 333 lines) and `build_run_link` (240 lines). *Fix:* - `driver_link_inputs_build_order` + shared load/fill/emit/release helper in `link_inputs.c`. -- [x] **E.2** Windows two-position rt insert only in `cc` — covered by **T1.9** (lands via - the E.1 shared helper). -- [~] **E.3** OS-neutral env code (stdio writer + thunks, `env→{context,jit_host,dbg_host}` - trio, dir-handle structs + read/close, `read_stdin` grow/shrink) byte-identical between - `driver/env/posix.c` and `windows.c`. *Fix:* move to `driver/env/common.c` + a shared - header; per-host keeps only a read-chunk callback. -- [x] **E.4** `dbg_jit_language_for_tag` hardcodes the tag→language map + `<dbg-jit.EXT>` - literals (`driver/cmd/dbg.c:1957-1981`), re-encoding frontend identity the P3 refactor - routed through `kit_language_for_name`. *Fix:* resolve via `kit_language_for_name` + - `dbg_jit_default_name`; delete the literal table. -- [~] **E.5** basename-stem+extension synthesis triplicated; codegen/link flag-parse blocks - byte-identical across cc/build; `link_action` predicate computed 3 inconsistent ways. - *Fix:* shared driver helpers. - -### F. Hand-rolled utils reimplement shared ones (low, 14) -- [x] **F.1** `VEC_GROW`/`vec_grow_` bypassed: ~6× in `c_target` (`c_emit.c:133-144,374-392, - 1006-1017,1541-1553,1704-1721,1723-1739,2364-2375`, incl. 2 byte-identical `sym_forwarded` - bitmaps), 5× in emu/interp, 4-6× in cpp. *Fix:* route through `VEC_GROW` (+ a zeroing - wrapper for the u8 bitmaps). -- [ ] **F.2** `objbb_append_str` dedup strtab builder reimplemented as `StrBuilder`/ - `strtab_add` 4× in the ELF linker. *Fix:* use the shared builder. -- [ ] **F.3** LEB128 encoders forked in `debug_emit`; `dw_skip_die_attrs` loop inlined 6×. - *Fix:* one encoder + one skip helper. -- [x] **F.4** path-join triplicated (2 copies in one dist file); `ctx->diag` varargs wrapper - copied across 4 API files; `run.c` reimplements `driver_parse_u64`/`driver_record_mcmodel`. - *Fix:* use the shared helpers. -- [x] **F.5** TLS storage emission byte-identical between `define_tls_elf`/`define_tls_macho` - (`src/obj/obj_tls.c:78-191`). *Fix:* extract `tls_emit_storage(...)`. - -### G. ISA/encoding tables duplicated outside their authority (low, 4) -- [x] **G.1** aa64 condition-code table duplicated 4× (`disasm.c:38-41`, `isa.c`); no - exported `aa64_cond_*`. *Fix:* one canonical `aa64_cond_name`/`aa64_cond_from_name` in - `isa.{h,c}`; route disasm/emit/parse through it. -- [ ] **G.2** aa64 `dbg.c` re-hardcodes B/B.cond/CBZ/ADR masks `isa.h` exports, inconsistently - within one function (`dbg.c:94-179`). *Fix:* use the named `AA64_*_FAMILY_MASK/MATCH`. -- [x] **G.3** 54 byte-identical wasm memarg decode arms maintain a private byte→kind map - (`src/wasm/decode.c:196-991`). *Fix:* reverse lookup into `wasm_insn_table`; collapse the - contiguous ranges. -- [ ] **G.4** RV disassembler dispatches sub-format by `strcmp` on the display string. - *Fix:* dispatch on an encoding tag. - -### H. Function-local mutable statics (med, 2) — invariant violation -- [x] **H.1** `rv64_disasm_find_c` synthesizes into a `static Rv64InsnDesc dyn;` and returns - its address (`src/arch/riscv/isa.c:1230-1338`) — the *only* mutable function-local static - in `src/`; violates the documented no-global-state/reentrant invariant; latent race. - *Fix:* thread a caller-owned scratch slot (on the `Rv64InsnFormatter` that already hangs - off the Compiler); collapse the ~30 synthesis blocks into one `mk(name,fmt,flags)` helper. - -### I. Local parser/pass duplication & confusion (low, 13) -- [ ] **I.1** C parser shadow stack is a 3-array struct-of-arrays whose dup/swap/rot3/grow - shuffle 3 lanes in lockstep (`lang/c/parse/cg_adapter.c:53-141`). *Fix:* collapse into one - `PcgSlot{type;flags;aux;}` array. (`PcgLvAux` already has 5 bytes pad.) -- [ ] **I.2** Two parallel C-6.7.9 initializer walkers re-encode the same traversal grammar - (`lang/c/parse/parse_init.c:648-777`). *Fix:* one grammar driver + a leaf vtable. -- [ ] **I.3** `cg_adapter` callers poke the shadow-stack array directly because no - retag-keep-flags op exists. *Fix:* add the op. -- [ ] **I.4** opt passes re-hand-roll the centralized operand-walk and a `ranges_overlap` - trampoline. *Fix:* direct calls to `opt_ranges_overlap_kind`. -- [ ] **I.5** misc confusing constructs: obfuscated `max()` (`pass_live.c:178-187`), - `kit_dep_iter` bracketed/system conflation (**T1.12**), recursive `order_dfs` stack hazard, - threaded-but-ignored `CSemAssignContext`. *Fix:* per-site. - ---- - -## Tier 3 — Quick wins (surgical deletions / one-liners) - -- [x] **Q1** Delete dead `aa_ldr_lit64` (`aa64/native.c:495`) + strip the false - `__attribute__((unused))` from the 13 *live* helpers → re-enables `-Werror=unused-function`. -- [x] **Q2** Add the two rv32 stub symbols (**T1.4**). -- [x] **Q3** Delete the unreachable second `if (op == 0x88u)` (`x64/asm.c:900-910`). -- [x] **Q4** Delete the four unused `X64_PROLOGUE_*_BYTES`/`CHKSTK_DELTA` macros (`x64/emit.h:22-25`). -- [x] **Q5** `riscv/native.c:1656` → `RV_MAX_CALLEE_SAVES` instead of bare `16`. -- [x] **Q6** Delete abandoned `aa64_parse_operands` stub + `struct AA64AsmTok` fwd decl + - obsolete phase-2/3 `isa.h` comment. -- [x] **Q7** Delete dead `AA64Mn.arg` discriminator (`aa64/asm.c:1829`). -- [x] **Q8** Replace the four bare-hex family masks in `aa64/dbg.c` with named constants (**G.2**). -- [x] **Q9** Delete dead `NativeTarget.emit_prologue` hook + `emit_minimal_prologue` flag + - comment (`native_target.h:406-416`). -- [x] **Q10** Delete the four never-called CFI vtable methods + enum/impl/encode cases in `mc.c`. -- [x] **Q11** Delete dead `CoffOutHdr` struct + lying comment (`coff/link.c:1419-1426`). -- [x] **Q12** Delete Mach-O `PageChain` typedef, `MachImp.internal_vaddr` + false comment, - 6 write-only `MCtx` layout fields. -- [x] **Q13** Delete no-op first-pass loop in `cu_read_root_attrs` (`debug/dwarf_open.c:351-362`). -- [x] **Q14** Delete dead `obj_format_elf_tls_tp_bias` (or have `tls_tcb_bias` call it). -- [x] **Q15** Delete unused write-only `KitJitSession` fields - `interrupt_pending`/`entry_ret`/`regs_scratch` + false "used by signal handler" comment. -- [x] **Q16** Delete unreachable `OPK_LOCAL` store-into-temp block (`cg/wide.c:277-282`) + - collapse the two redundant `OPK_LOCAL` guards. -- [x] **Q17** Delete unreachable `defined_skip == 1` branch (`pp_expand.c:939`) + fix stale - 2-state-machine comment in `pp_priv.h`. -- [x] **Q18** Delete empty-body no-op `if` + stale `pending_space` scaffold in - `read_invocation_args` (`pp_expand.c`). -- [x] **Q19** Delete the `#if 0` wasm switch-island zombie (`arch/wasm/emit.c:3241-3370`) + - fix stale `internal.h:415` / `emit.c:4086` comments. -- [x] **Q20** Replace the `ranges_overlap_kind` trampoline + local proto with direct calls - (`pass_coalesce.c:54-58, 315`). -- [x] **Q21** Simplify `realign_phi_preds`'s no-op guard to `if (!aux) continue;` - (`pass_ssa.c:892-894`). -- [x] **Q22** Rewrite the obfuscated `max()` in `live_metric_copy` (`pass_live.c:178-187`). -- [x] **Q23** Replace `run.c`'s `run_record_mcmodel`/`run_parse_u64` with shared - `driver_record_mcmodel`/`driver_parse_u64`. -- [x] **Q24** Replace inline itoa (`lang/wasm/cg.c:2106-2117`) with `wasm_indexed_name` - (regains the bounds check). -- [x] **Q25** Delete the 3 bogus `(void)rt`/`(void)ptr_mem` casts + no-op `after_dir` - jump/place in `lang/wasm/cg.c` bulk-copy emitters. -- [x] **Q26** Fix stale `emit_dynamic_body`/`link_elf.c` comment (`obj/elf/link_dyn.c:1202`). -- [x] **Q27** Rewrite the bogus `obj_symbol_find` SymNameIndex comment (`obj.c:27-28`). - ---- - -## Rejected (do NOT "fix" — verified non-issues) - -- **Mach-O `sections[link_section_id-1]` unguarded deref** (`macho/link.c:1397`) — - unreachable (`link_reloc_layout.c` skips NONE-section relocs); matches the deliberate - cross-format invariant (ELF does the same); `patch_ptr`'s guard is general-helper defense. -- **GVN/DSE two address-root walkers** (`pass_o2.c:947, 1555`) — legitimately divergent by - pipeline phase (the raw walker runs pre-union/pre-simplify and *can't* use `gvn_find`). -- **`toy_parse_let_stmt` "god-function"** — mostly inherent local-initializer grammar - dispatch; the real emit logic is already shared. -- **`KitUnwindFrame.regs[32]` "undersized ABI"** — sized for the GP bank by design; PC is a - separate field; SIMD intentionally absent from the best-effort CFI unwinder. - ---- - -## Execution plan (waves) - -Constraint: no parallel `make` in the shared tree. Each wave = parallel agents that **edit -disjoint files only** (no build); the orchestrator then does one authoritative -build + targeted tests + commit. Baseline captured in `build/baseline_summary.txt`. - -- **Wave A — Quick wins + localized Tier-1 bugs** (Q1-Q27 + T1.1, T1.3, T1.5, T1.6, T1.10, - T1.11, T1.12). Low risk, maximally parallel (disjoint files). Re-enabling - `-Werror=unused-function` (Q1) is the leverage move. -- **Wave B — Driver/dist consolidation** (E.1-E.5, T1.7, T1.8, T1.9, F.4). Disjoint from src - backends. **LANDED:** E.1 (cc/build link pipeline → shared `driver_link_inputs_build_order` - + `driver_link_inputs_insert_runtime_archives`), T1.9/E.2 (build-exe now gets the Windows - two-position rt insert), E.4 (dbg lang-tag via `kit_language_for_name`/`dbg_jit_default_name`), - T1.7 (NUL rejection in manifest/kpkg KV scanners), T1.8 (`dist_manifest_path_valid` + - `pkg_locator_safe` routed through canonical `dist_tree_path_valid`). Also added the - **`test-dist`** target + read-side adversarial CAS coverage (untrusted-tree traversal / - absolute / NUL rejection). - **DEFERRED:** E.3 (OS-neutral env dedup posix.c↔windows.c→common.c) — needs a Windows - cross-build to verify `windows.c`, so split to its own step. E.5 (basename-stem + flag-parse - dedup) — the natural home is the driver util layer, not link_inputs.c; flag-parse blocks - aren't byte-identical (different option structs). F.4 diag-wrapper (the identical `*_diagf` - in core.c/cas.c/compress.c/package.c) — shared home is `src/core/diag.{c,h}`; do in Wave D. -- **Wave C — Cross-cutting backend extractions** (A.1, B.1-B.5, C.1-C.5, H.1). The "right - abstraction" core; agents partitioned by disjoint file sets; arch `native.c` files done - after Wave A. -- **Wave D — Format/identity hooks + util consolidation** (D.1-D.5/T1.2, F.1-F.5, G.1-G.4). -- **Wave E — Parser/pass cleanups** (I.1-I.5). -- **Wave F (optional, deferred) — God-function decompositions.** Highest risk, lowest ROI; - decompose when next touching those files: `link_emit_elf` (940), `plan_layout` (574), - `jit_append_obj_inner` (482), `wasm_emit_cg_into` (~1960), `c_emit_data_symbol` (306). - ---- - -## Pre-existing issues surfaced during cleanup (NOT regressions) - -`make test-cbackend` (the `--emit=c` C-source backend suite) was **already red at the -pre-cleanup baseline `de9bface`** — verified by building that commit in a throwaway -worktree. Two failing cases, neither caused by this cleanup: - -- **`cg_native_inline_asm_machine_constraints/O0/C`** — inherent: the C backend replays IR - as portable C with no arch register model, so `cg/asm.c` correctly rejects machine register - constraints (aa64 `"x"`/`"y"`, rv64 `"cr"`/`"cf"`). A native-only feature test that lacked a - /C skip. **FIXED** by adding the matching /C skip in `test/parse/run.sh`. -- **`memory_grow_large/C`** — `expected 42 got 139` (SIGSEGV): a grown-range access - (`memory 1 300`, grow to 300 pages, store/load at ~18.7 MB) segfaults under the C backend — - a genuine pre-existing c_target `memory.grow` bug (backing store likely not realloc'd to the - grown page count). **NOT fixed and NOT skipped** — hiding a real bug is wrong; left failing - and flagged for a dedicated c_target memory-model investigation. (Confirmed failing - identically at `de9bface`.) - ---- - -## Appendix A — all 135 confirmed findings by subsystem - -### aa64-asm-isa — 4 - -- **[low/dead-code]** `src/arch/aa64/isa.c:1474-1480` — Abandoned 'phase 3' aa64_parse_operands stub ships as documented dead code - - *Fix:* Delete aa64_parse_operands (isa.c + isa.h declaration), the `struct AA64AsmTok;` forward decl, and the obsolete 'phase 2/3' paragraphs in the isa.h header comment. Keep only aa64_print_operands, which is real. -- **[low/duplication]** `src/arch/aa64/disasm.c:38-41` — AArch64 condition-code table duplicated four times across the arch module - - *Fix:* Put one canonical table in isa.{h,c}: an aa64_cond_name(u32)->const char* and an aa64_cond_from_name(slice,u32*)->int (handling hs/lo/al). Have disasm.c emit_mnemonic, isa.c emit_cond, and asm.c parse_cond_from_ident all call it. This also kills the disasm.c table and the isa.c static table outright. -- **[low/dead-code]** `src/arch/aa64/asm.c:1826-1830` — AA64Mn.arg discriminator field is dead; ~80 zero-arg wrapper fns exist because of it - - *Fix:* Either: (a) make the dispatcher pass row->arg to a `void(*)(AsmDriver*,u32)` fn and delete the wrapper thunks, or (b) drop the unused `arg` field. Independently, collapse the 17 p_b_* wrappers + 17 b.<cc> rows: in aa64_asm_insn, detect a 'b.' prefix and resolve the suffix via the shared aa64_cond_from_name helper. -- **[low/inconsistency]** `src/arch/aa64/dbg.c:94-179` — dbg.c re-hardcodes instruction family hex masks that isa.h already exports (and is internally inconsistent) - - *Fix:* Replace the four bare-hex family checks with the named AA64_*_FAMILY_MASK/MATCH constants from isa.h, matching the BR_REG/BR_IMM checks already present at lines 230 and 318. (TBZ/TBNZ at 158-176 and LDR-literal at 198-227 genuinely have no ISA format, so leaving those hand-rolled is fine.) - -### aa64-native — 3 - -- **[medium/dead-code]** `src/arch/aa64/native.c:364-614, 495-497` — Stale __attribute__((unused)) markers defeat -Werror=unused-function and hid genuinely dead code - - *Fix:* Delete the dead `aa_ldr_lit64` outright. Then strip `__attribute__((unused))` from every helper that actually has a caller (the large majority) so -Werror=unused-function can again police them. If a couple of genuinely-want-to-keep encoder wrappers remain callerless, that is the signal to delete them too rather than annotate them away. The 503-506 comment ('thin wrappers ... preserved') is not a license to suppress the dead-code warning the build relies on. -- **[low/error-handling]** `src/arch/aa64/native.c:3382-3655` — aa_intrinsic silently emits nothing on malformed-operand intrinsics, unlike x64/rv64 which panic - - *Fix:* Match x64/rv64: move the panic out of `default:` to the end of the function so every `break` (default or guard-fail) terminates in `aa_panic(aa_of(t), "unsupported compiler intrinsic")`, turning silent mis-emission into a diagnosable backend assertion. -- **[medium/duplication]** `src/arch/aa64/native.c:4711-4880` — aa_direct_asm_block duplicates a ~170-line inline-asm orchestration verbatim across aa64/x64/riscv - - *Fix:* Hoist the generic orchestration into src/cg/native_asm.c (it already owns native_asm_constraint_reg_info / match_index / pin helpers): a `native_asm_bind_direct_operands(d, dir, constraints, n, ops, masks*, &bound[])` helper plus one shared driver that runs out-loop, in-loop, save/load/run/store/restore. Parameterize the arch-specific bits via the existing NativeOps/NativeRegInfo (scratch masks, bound-operand encode/decode). Each backend then keeps only a thin asm_block that supplies arch constants, eliminating ~340 lines of triplicated logic and the in-file out/in copy-paste. - -### api — 4 - -- **[medium/confusing]** `src/api/object_builder.c:79-101` — kit_obj_builder_section entsize path: dead read, self-contradicting comments, and a duplicate orphan section - - *Fix:* Either call obj_section_ex() once up front when desc->entsize != 0 (skipping the obj_section() call entirely so no orphan is created), or add an entsize argument / setter to obj_section so the single find-or-create path sets it. Delete the dead obj_section_get/(void)sec read and the three contradictory comments. If round-trip semantic (NOBITS for BSS) must be preserved, do not hardcode SSEM_PROGBITS. -- **[low/duplication]** `src/api/compress.c:20-30` — Identical ctx->diag varargs wrapper copy-pasted across four API files - - *Fix:* Add one shared helper, e.g. void kit_ctx_diagf(const KitContext* ctx, const char* fmt, ...) (and/or kit_ctx_diagv) in core/diag.{h,c}, and have package.c/cas.c/compress.c/core.c call it. Removes ~40 lines and one source of drift in the error-reporting path. -- **[low/duplication]** `src/api/object_file.c:29-93` — kit_obj_open and kit_objfile_internal_new duplicate ~40 lines of alloc/target/compiler/setjmp/cleanup boilerplate - - *Fix:* Extract a static KitObjFile* objfile_alloc_and_init(ctx, name, target, fmt, builder_fn) (or pass a small enum/callback selecting read-vs-new) that owns the alloc + target + compiler + setjmp scaffolding, and have both public entries call it; map its NULL/status at the two call sites. -- **[low/naming]** `src/api/compile.c:654-655` — kit_dep_iter_next reports two distinct dependency attributes from one internal bit, conflating 'system path' with '<>-bracketed' - - *Fix:* Either track the bracketing separately in SourceInclude (a distinct u8 set by the preprocessor's include handler) and surface it through bracketed, or drop/deprecate the bracketed field from the public KitDepEdge so the API stops promising information it does not have. - -### arch-shared — 3 - -- **[medium/inconsistency]** `src/arch/link_stubs.c:1-5` — Feature stubs omit rv32 link/dbg symbols, breaking a supported build config - - *Fix:* Add `const LinkArchDesc link_arch_rv32 = {0};` to link_stubs.c and `const ArchDbgOps rv32_dbg_ops = {0};` to dbg_stubs.c so the stub set covers every ArchImpl symbol the riscv backend references. (Alternatively gate the rv32 ArchImpl's `.link`/`.dbg` fields, but matching the stubs is simpler and consistent with the existing aa64/x64/rv64 entries.) -- **[low/dead-code]** `src/arch/native_target.h:406-416` — Dead emit_prologue hook + emit_minimal_prologue flag in the NativeTarget vtable - - *Fix:* Delete the `emit_prologue` hook, the `emit_minimal_prologue` field, and the 11-line comment (lines 406-416). If the minimal-prologue idea is still desired as a roadmap item, move it to doc/plan/ rather than leaving an inert hook in the live vtable. -- **[low/dead-code]** `src/arch/mc.c:482-506` — Four CFI vtable methods (def_cfa_offset/def_cfa_register/rel_offset/restore) are never called - - *Fix:* Drop cfi_def_cfa_offset, cfi_def_cfa_register, cfi_rel_offset, cfi_restore: remove the four m_cfi_* impls, their base-> assignments, their mc.h vtable decls, the CFI_OP_DEF_CFA_OFFSET/_DEF_CFA_REGISTER/_REL_OFFSET/_RESTORE enum members, and their encode_cfi_directive cases. Re-add only with a real producer (e.g. when the assembler grows .cfi_* directive parsing). - -### c_target — 4 - -- **[medium/boundary-violation]** `src/arch/c_target/c_emit.c:3846, 3911` — Data-symbol reloc width is hardcoded `R_ABS32 ? 4 : 8`, mis-sizing and mis-rendering pcrel/diff/sub relocs - - *Fix:* Replace both `(r->kind == R_ABS32) ? 4 : 8` with `reloc_kind_width(t->c, r->kind)` (include link/link_reloc_desc.h). For non-absolute kinds (pcrel/diff/sub), either lower them faithfully or compiler_panic with a clear 'unsupported reloc kind in data for C target' message instead of coercing them to an absolute pointer. At minimum, assert the kind is R_ABS32/R_ABS64 before taking the absolute-pointer path. -- **[low/duplication]** `src/arch/c_target/c_emit.c:133-144, 374-392, 1006-1017, 1541-1553, 1704-1721, 1723-1739, 2364-2375` — Six hand-rolled grow-array routines reimplement core's VEC_GROW - - *Fix:* Route the scalar/struct growers (scopes, local_static_entries/syms, local_type) through VEC_GROW. For the two u8 bitmaps (type_state, sym_forwarded) wrap VEC_GROW in a tiny helper that zeroes only the new tail. Collapse the two duplicated sym_forwarded blocks into a single c_sym_forwarded_test_and_set(t, sym) helper used by both c_ensure_forward_decl and c_emit_alias. -- **[low/god-function]** `src/arch/c_target/c_emit.c:3639-3945` — c_emit_data_symbol is a 306-line god function with a doubled reloc-collection loop and a duplicated chunk-layout walk - - *Fix:* Split into focused helpers: c_emit_extern_data_decl, c_emit_macho_tls_data, c_emit_common_data, c_emit_reloc_data. Collect the in-range relocs once into a sorted array (single pass, count via the array length). Factor the chunk/pointer walk into one routine parameterized by an 'emit declaration vs emit initializer' callback (or two trivial inline lambdas) so the layout is computed once. -- **[low/duplication]** `src/arch/c_target/c_emit.c:3016-3025, 3050-3058, 3068-3076` — Verbatim-duplicated u64-mask-to-hex-literal emission loop across bitfield load/store - - *Fix:* Add a `static void cbuf_put_hex_u64(CBuf* b, u64 v)` (or c_emit_hex_mask) helper and call it from all three sites. The byte-pair hex emitters at c_emit_load_const (3301) and c_emit_data_bytes (3585) can share the same hex table too. - -### cg-core — 3 - -- **[low/duplication]** `src/cg/arith.c:1253-1704` — Soft-float / f128 lowering in arith.c is copy-pasted across binop + 8 conversion entry points instead of table-driven - - *Fix:* Factor a `KitCgTypeId api_int_builtin_for_size(u32 sz)` helper (one of the 16 inline conditionals) and an `api_softfp_binop(KitCg*, KitCgFpBinOp, const char* suffix, KitCgTypeId opty)` mirroring the existing api_softfp_cmp, so kit_cg_fp_binop reduces to a suffix/opty selection plus one call. For the conversions, build the libcall name from a small (op, dst-width) -> name table or `snprintf("__float%s%s", int_suffix, fp_suffix)` rather than three nested ternaries per function. -- **[low/dead-code]** `src/cg/wide.c:245-291` — Unreachable duplicate OPK_LOCAL branch in api_wide16_materialize_lvalue (dead code missed by the sweep) - - *Fix:* Delete the unreachable block at 277-282. Collapse the two OPK_LOCAL guards (245 and 257) into the single `if (v->op.kind == OPK_LOCAL) { v->lvalue = 1; return *v; }` since both arms are identical; drop the now-pointless type-equality special-case. If the store-into-temp behavior was actually intended for some local case, hoist it before the catch-all guard with a real predicate. -- **[low/duplication]** `src/cg/arith.c:434-435` — Byte-identical wide8 lane-offset helpers duplicated between arith.c and wide.c - - *Fix:* Promote one pair (e.g. api_wide8_lo_off/api_wide8_hi_off, declared in internal.h alongside the other api_wide8_* lane helpers) and have arith.c call it, deleting the local wide8_lo_off/wide8_hi_off copies. - -### core-abi — 3 - -- **[medium/duplication]** `src/abi/abi_sysv_x64.c:184-202, 218-279` — ABI argument-classification scaffolding is copy-pasted across all four per-ABI vtable TUs - - *Fix:* Hoist the shared driver into abi.c next to abi_classify_void/abi_classify_int128_pair: `ABIFuncInfo* abi_compute_func_info_generic(TargetABI*, KitCgTypeId fn, void (*classify_one)(TargetABI*, KitCgTypeId, ABIArgInfo*, int is_return), int sret_consumes_int_arg)`. Each TU keeps only its classify_aggregate/classify_scalar specifics and passes its classify_one in; sysv layers its vararg-offset pass on top of the returned info. Add a shared `abi_classify_scalar_reg_part(TargetABI*, ABITypeInfo, ABIArgInfo*)` for the one-register-scalar tail, and a shared `abi_classify_one_dispatch(...)` so the identical record/alias/scalar switch lives once. -- **[low/confusing]** `src/core/strbuf.h:33-39` — strbuf_reset: dead/confusing guard that also writes a NUL into a zero-capacity buffer - - *Fix:* Match strbuf_init's contract: only write the terminator when capacity was reserved. Replace the block with `if (sb->base && sb->p != sb->base) sb->p = sb->base; if (sb->base && sb->end != sb->base) *sb->base = '\0';` — i.e. gate the NUL write on `sb->end != sb->base` (capacity > 0), not on the always-true compound condition. -- **[low/error-handling]** `src/abi/abi.c:110-128` — compute_record_layout NULL-checks L but not the sibling arena_array(fl) - - *Fix:* After the arena_array, add `if (!fl) return NULL;` (the abi_cg_record_layout caller already tolerates a NULL layout). Or, if the project's accepted convention is that arena OOM is fatal, drop the now-pointless `if (!L) return NULL;` above so the two allocations are handled consistently. - -### debug-dwarf — 5 - -- **[low/dead-code]** `src/debug/dwarf_open.c:350-365` — Dead no-op first-pass loop in cu_read_root_attrs - - *Fix:* Delete lines 351-365 (the entire first for-loop and its comment block). The two real passes that follow are self-contained and correct; the function's behaviour is unchanged. -- **[low/duplication]** `src/debug/dwarf_die.c:192-204, 219-225, 322-330, 346-352, 374-380` — dwarf_die.c reimplements dw_skip_die_attrs inline 6 times - - *Fix:* Replace each inline loop with `dw_skip_die_attrs(d, cu, &die, off)` (the local DwDie is in scope at every site). Removes ~30 lines and centralizes attribute-stream skipping. -- **[low/complexity]** `src/debug/debug_emit.c:994-1010, 1166-1174` — Buf-append 'alloc temp, flatten, write, free' dance duplicated 3x; reinvents buf_reserve - - *Fix:* Add a small static helper `buf_append(Buf* dst, const Buf* src)` { u32 n=buf_pos(src); if(!n) return; u8* p=buf_reserve(dst,n); if(p) buf_flatten(src,p); } in debug_emit.c (or core/buf.c) and replace all three blocks with `buf_append(&out, &hdr_body)` etc. -- **[low/duplication]** `src/debug/debug_emit.c:529-578` — Hand-rolled LEB128 encoders in emit_var_loc_exprloc duplicate form_uleb/form_sleb - - *Fix:* Stage the exprloc into a small local Buf and reuse form_u8/form_uleb/form_sleb, then emit its length + bytes; or have form_uleb/form_sleb share an inner encode-into-byte-ptr helper that both the Buf wrappers and this site call. Either eliminates the forked LEB code. -- **[low/duplication]** `src/debug/dwarf_type.c:65-167` — Three near-identical DIE-child walkers in dwarf_type.c - - *Fix:* Extract a `walk_children_of_tag(d, cu, off, tag, void(*on_match)(...), ctx)` helper that owns the loop + nested skip-children + dw_skip_die_subtree, and pass three small per-tag callbacks for member/enumerator/subrange extraction. - -### dist — 4 - -- **[low/duplication]** `src/dist/cas.c:18-32, 193-207` — Path-join helper triplicated (two copies in one file) - - *Fix:* Hoist a single dist_path_join(char* out, size_t cap, const char* dir, const char* rel) into dist_parse.h (or dist.h alongside dist_hex_encode) and have all three callers use it. Delete join_tree_path entirely (dist_cas_join_path already exists in the same file). The parent_dir/pkg_parent_dir pair (cas.c:94 / package.c:142) shares the same find-last-slash logic and could fold into the same path helper module, parameterizing the no-slash/overflow behavior. -- **[medium/duplication]** `src/dist/tree.c:39-54` — Security-relevant path-safety validator copy-pasted three times with subtle divergence - - *Fix:* Make pkg_locator_safe in package.c just call the already-exported dist_tree_path_valid (delete the body). Express dist_manifest_path_valid in terms of the same shared core (or document precisely why manifest paths are allowed newline bytes that tree paths are not — currently it appears accidental, since field_text_valid separately rejects newlines for manifest fields). -- **[medium/duplication]** `src/dist/tree.c:175-197` — KV-line scanner skeleton copy-pasted across three parsers, with a diverged NUL-byte check - - *Fix:* Extract a shared line iterator into dist_parse.h, e.g. dist_kv_next_line(const uint8_t* data, size_t len, size_t* pos, char* buf, size_t bufcap, char** out, ...) that performs the read+length-check+NUL-reject+trim uniformly, and have all three parsers loop over it. At minimum, add the NUL-byte rejection to manifest.c and kpkg.c so all three reject the same malformed input identically. -- **[low/error-handling]** `src/dist/blob.c:56-94` — Undocumented 16 MiB blob ceiling surfaced as a misleading "failed to hash" diagnostic - - *Fix:* Either (a) emit a specific diagnostic distinguishing the size-cap case (e.g. return a distinct sentinel from dist_blob_root so callers can say "file exceeds NN MiB blob limit"), or (b) document the DIST_BLOB_MAX_CHUNKS-derived ceiling in doc/DISTRIBUTE.md and dist.h. Long-term, the in-memory merkle could be folded incrementally (a small running stack of subtree hashes) to lift the cap, since the leaves are already produced in order. - -### driver-compilers — 5 - -- **[medium/duplication]** `driver/cmd/cc.c:2013-2346` — Link pipeline (alloc/load/order-translate/emit/cleanup) copy-pasted between cc and build - - *Fix:* Add a driver_link_inputs_build_order(set, source_obj_index, source_order_keep, KitLinkInputOrder* out, uint32_t* nout) helper in link_inputs.c that performs the DriverLinkKind->KitLinkInputOrder translation once (SOURCE_MEMORY is naturally handled by source_obj_index/source_order_keep, so the cc/build paths converge). Further, factor the load-all-inputs + fill-DriverLinkInputs + emit + release scaffolding into a single driver_link_engine helper taking the DriverLinkInputSet and the compiled objs, so cc_run_link_exe and build_run_link shrink to option setup plus one call. -- **[low/duplication]** `driver/cmd/cc.c:1488-1521` — Basename-stem + extension synthesis loop copy-pasted three times - - *Fix:* Add a shared driver_replace_ext(DriverEnv*, const char* src, const char* ext, size_t ext_len, size_t* out_size) (or driver_basename_stem) in driver/lib/target.c next to driver_default_obj_ext, and have all three callers pass the extension they want (.o/.obj via driver_default_obj_ext, or .s/.ir/.c/.d). cc_dep_default_target, cc_default_obj_path_for_name and build_default_obj_name then become a few lines of extension selection plus one call. -- **[medium/inconsistency]** `driver/cmd/cc.c:2455-2502` — Windows two-position runtime-archive insert lives only in cc, build-exe got the single-insert copy - - *Fix:* Extract the runtime-archive placement (including the Windows two-position logic) into a shared helper, e.g. driver_link_inputs_insert_runtime(set, &rt_archive, &hosted_plan, target), and call it from both driver_cc_main and build_main. That removes the duplicated single-insert and makes build-exe inherit the Windows workaround automatically. If build-exe Windows hosted linking is genuinely out of scope, that should be an explicit guard, not an accidental omission. -- **[low/duplication]** `driver/cmd/cc.c:533-562` — cc link_action predicate computed three ways; the dedicated helper is bypassed - - *Fix:* Make cc_has_link_action the single source of truth, parameterize the syntax_only consideration if it really differs by call site (e.g. cc_has_link_action treating syntax_only as non-link), and replace both inline copies with calls. Document why syntax_only is or isn't part of the predicate. -- **[low/duplication]** `driver/cmd/cc.c:698-758` — Codegen/link flag blocks (PIC, visibility, sections, LTO, nostd*) byte-identical across cc and build parsers - - *Fix:* Add a driver_codegen_flags_try_consume(target*, &visibility, &function_sections, &data_sections, &lto, &nostd*, tool, argc, argv, &i) shared consumer (mirroring driver_cflags_try_consume's return contract of 1/0/-1) and call it from both parsers before their tool-specific flag handling. Keep the genuinely divergent flags (-M family, -x stdin, --group/-X, -dynamic vs -shared) in each parser. - -### driver-main-env — 3 - -- **[medium/duplication]** `driver/env/posix.c:221-279, 387-518, 675-717, 1345-1365` — OS-neutral env code is copy-pasted between posix.c and windows.c instead of living in common.c - - *Fix:* Move the stdio writer (struct + 5 thunks + driver_stdio_writer/stdout/stderr) and the driver_env_to_context/jit_host/dbg_host trio into common.c -- they have zero OS dependency. Promote DriverDirEntryRec/DriverDirHandle to a shared header (env_internal.h) and move driver_read_dir_entry/driver_close_dir to common.c, leaving only the OS-specific driver_open_dir per host. Factor driver_read_stdin so the grow/shrink loop is shared and only a tiny host read-chunk callback differs. Optionally collapse the exec_dual registry into env_internal.h with a thin lock abstraction. -- **[low/global-state]** `driver/env/posix.c:1292-1324` — Cache-dir path stored in a file-scope mutable global, violating the no-global-state invariant - - *Fix:* Give DriverEnv ownership of the cache-dir string: either an inline `char cache_dir_buf[...]` filled in driver_env_init (e->cache_dir = e->cache_dir_buf), or a heap-allocated copy via driver_path_join freed in driver_env_fini (which is currently a no-op). Delete the `extern char g_cache_dir[4096]` from env_posix.h and the file-scope buffers from both posix.c and windows.c. -- **[low/error-handling]** `driver/main.c:217-221, 291-323` — Multi-call dispatch overloads -1 as both "no such tool" and a forwarded tool exit code - - *Fix:* Separate the not-found signal from the exit code: have dispatch take an `int* found` out-param (or return the matched DriverToolDesc* and let the caller invoke ->main), so a tool that legitimately returns -1 is forwarded faithfully. Alternatively use a sentinel outside the exit-code range (e.g. INT_MIN) and assert tools never return it. - -### driver-tools — 4 - -- **[medium/duplication]** `driver/cmd/dbg.c:1957-1981` — dbg `jit` language-tag handling hardcodes the frontend set and re-derives names the dynamic helpers already build - - *Fix:* Replace the if-chain with `KitLanguage lang = kit_language_for_name(s->compiler, tag);` (falling back to the default lang on KIT_LANG_UNKNOWN), and synthesize the name via the existing dbg_jit_default_name(s->compiler, lang, buf, cap) instead of the hardcoded literals. This deletes the entire literal table and makes the tag list track the registered frontend set. (Also note the trailing fallback uses kit_language_for_path on what is a language *name*, not a path — kit_language_for_name is the correct resolver.) -- **[low/duplication]** `driver/cmd/run.c:423-447` — run.c reimplements the shared `-mcmodel=` parser (driver_record_mcmodel) - - *Fix:* Delete run_record_mcmodel and call driver_record_mcmodel(&o->target, RUN_TOOL, a + 9) at the -mcmodel= site (run.c:673), matching cc.c and build.c. -- **[low/duplication]** `driver/cmd/run.c:405-421` — Third copy of the same u64 decimal parser (run_parse_u64 / wasm_parse_u64_dec vs driver_parse_u64) - - *Fix:* Delete run_parse_u64 and use the shared driver_parse_u64 at run.c:647 (it even adds 0x-hex support for free, matching cc/build). If the overflow rejection is genuinely wanted toolchain-wide, add it once to driver_parse_u64 rather than maintaining per-tool copies; then wasm_parse_u64_dec can collapse onto it too. -- **[low/duplication]** `driver/cmd/objcopy.c:125-141` — strip.c and objcopy.c carry copy-pasted argv/array helpers despite already sharing objedit.c - - *Fix:* Move a single driver_strlist_push(env, arr, n, cap, s) and a driver_take_flag_value(i, argc, argv, flag, &out) into objedit.c (or a small shared cmd-args helper) and have both strip.c and objcopy.c call them; drop the four local copies. - -### emu-interp-os — 3 - -- **[low/duplication]** `src/emu/image.c:181-265` — emu_addr_space_unmap and emu_addr_space_protect are copy-pasted carve-out loops - - *Fix:* Extract one helper, e.g. `static KitStatus carve_range(EmuAddrSpace* as, u64 start, u64 end, int set_mid, u8 mid_perms)` that owns the overlap loop, removal, left/right remainder re-append, and resync; have it conditionally re-append the middle piece. unmap calls carve_range(as,start,end, /*set_mid=*/0, 0); protect first checks range_is_mapped then calls carve_range(as,start,end, /*set_mid=*/1, perms). -- **[low/duplication]** `src/emu/tls.c:10-47` — Hand-rolled doubling-realloc grow helpers duplicate the existing VEC_GROW/vec_grow_ utility - - *Fix:* Replace each ensure_*_cap body with a VEC_GROW call (e.g. `if (VEC_GROW(heap, st->modules, st->modules_cap, need)) return KIT_NOMEM;`). The one behavioral delta is that vec_grow_ does not zero the grown tail while these helpers memset it; since each caller immediately initializes the single appended slot (memset(b,0,...) / field-by-field), drop the memset or zero only the appended element. Fold emu_keep_jit's inline copy in too. -- **[low/inconsistency]** `src/os/linux/linux.c:705-709` — linux.c reinvents little-endian reads inline instead of using its own linux_rd64 - - *Fix:* In rt_sigaction use `handler = linux_rd64(p); flags = linux_rd64(p + 8u); restorer = linux_rd64(p + 16u);`; in writev's inner loop use `linux_rd64(p + i*16u + 8u)`. For the two runtime.c loops over a runtime nbytes (1..8), either keep a single shared `emu_load_le(const u8*, u32)` helper in runtime.c and call it from both load paths, or note that emu_mem_load_checked's extra `access` parameter is dead (every caller passes EMU_MEM_READ) and can be dropped while consolidating. - -### include-api — 3 - -- **[low/duplication]** `include/kit/wasm.h:63-115` — Public wasm enums/structs duplicated verbatim in an internal header with manual cross-guards and no sync assert - - *Fix:* Make lang/wasm/runtime_abi.h `#include <kit/wasm.h>` for the shared public types and keep only the genuinely-internal types (KitWasmMemory, KitWasmFuncImport, ...) there, deleting the guarded duplicates. The public header is the single source of truth; the cross-guard macros then become unnecessary. -- **[low/inconsistency]** `include/kit/disasm.h:24-39` — KitDisasmContext embeds KitContext by value while the rest of the public API borrows const KitContext* - - *Fix:* Change the field to `const KitContext* context;` to match the rest of the API, or drop KitDisasmContext entirely and pass `(const KitContext*, const KitTarget*)` to kit_disasm_iter_new like the sibling kit_disasm_obj already does. -- **[low/inconsistency]** `include/kit/cg.h:113-143` — Boolean convention is inconsistent across (and within) public headers: int vs bool - - *Fix:* Pick one convention and apply it: either C11 `bool` for all true/false struct fields and parameters (predicates still returning int/KitStatus is fine if documented), or commit to `int` everywhere for ABI-stability reasons and document why. The intra-struct mix in cg.h (bool next to int booleans) is the most egregious and should be unified first. - -### jit-dbg — 5 - -- **[medium/duplication]** `src/link/link_jit.c:791-810, 991-1005` — JIT TLS pseudo-symbol special-case (__tlv_bootstrap / _tls_index) is triplicated by hand and the JIT-append path silently lost the _tls_index case - - *Fix:* Promote the authority to cover both pseudo-symbols, e.g. obj_format_jit_resolves_undef_to_zero(const Compiler*, Sym) returning 1 for __tlv_bootstrap (Mach-O TLV) and _tls_index (COFF TEB) per the active TLS model, and call it from all three sites (link_resolve.c resolve_undefs, both link_jit.c append loops). That removes the spelled-out names from src/link entirely and makes the append-vs-full-link divergence impossible by construction; it also immediately fixes the missing _tls_index case in the append resolve loop. -- **[medium/dead-code]** `src/dbg/session.c:143-146` — max_hits is an unimplemented public breakpoint feature: condition computed, if-body empty, never enforced - - *Fix:* Implement it: on the post-park path (after the REPL inspects, near session.c:164) drop the bp via bp_remove_patch when the max_hits condition held — or set a deferred-clear flag the resume path acts on. If the feature is not wanted, delete max_hits from the public struct, the bp table, and the driver plumbing rather than shipping a dead conditional that reads as functional. -- **[low/dead-code]** `src/dbg/dbg.h:146,156,163` — Three never-used fields in KitJitSession survived the dead-code sweep; one carries a misleading 'used by signal handler' comment - - *Fix:* Delete all three fields (and the misleading comment). If interrupt handling is meant to set a pending flag the worker checks, wire it; otherwise remove it. entry_ret is fully superseded by entry_u64_ret / stop.exit_code. -- **[low/duplication]** `src/dbg/session.c:103-123, 124-139` — Identical 18-line 'silent resume or surface' block duplicated verbatim in the fault handler - - *Fix:* Extract a helper, e.g. `static int bp_silent_resume(KitJitSession* s, DbgBp* bp, KitUnwindFrame* regs)` returning 1 if it armed a silent step (caller returns KIT_OK) or 0 if it had to surface (caller falls through to the park path). Both guards then call it; the body lives once. -- **[medium/error-handling]** `src/dbg/step.c:102-128, 201, 232` — One-shot internal breakpoints set by the step engine leak (stay patched) when a different stop intervenes before they fire - - *Fix:* Track these one-shots the way the displaced sentinel is tracked, or clear them defensively: have the step routines record the bp_id and call dbg_bp_clear on every return path where the stop was not their own internal completion (i.e., when a user/other stop intervened). A small helper that arms-resumes-waits-and-cleans-up-on-non-completion would cover all four sites uniformly. - -### lang-c-parse — 5 - -- **[low/poor-abstraction]** `lang/c/parse/cg_adapter.c:53-141` — Typed shadow stack is a struct-of-arrays whose dup/swap/rot3/grow each triplicate the same shuffle - - *Fix:* Collapse the three arrays into one `typedef struct PcgSlot { const Type* type; u8 flags; PcgLvAux aux; } PcgSlot;` and store a single `PcgSlot* cg_stack`. dup/swap/rot3 become single struct assignments, grow becomes one arena_array + one memcpy, and adding a future per-slot field can no longer desync the lanes. Keep pcg_top_type/pcg_top_lv_aux/etc. as thin accessors over slot fields. -- **[low/duplication]** `lang/c/parse/parse_expr.c:2660-2724` — Balanced bracket/paren/brace token-skip loop copy-pasted three times across two files - - *Fix:* Extract one helper, e.g. `skip_balanced_until(Parser*, const u32* stop_puncts, u32 nstop, TokBuf* capture_or_null)`, that walks the depth machine and optionally appends tokens to a small growable Tok buffer. Have both _Generic arms and record_initializer_expr_for_replay call it with the appropriate stop set and capture flag, eliminating the three copies and the duplicated buffer-grow boilerplate. -- **[low/duplication]** `lang/c/parse/parse_expr.c:1386-1394` — Two byte-identical temp-slot helpers, and several call sites still hand-roll the same FrameSlotDesc boilerplate - - *Fix:* Delete one of the two helpers (keep a single `parse_tmp_local(Parser*, const Type*)`), drop the redundant FSF_NONE, and route the cas_n eslot/okslot/pslot allocations, the compound-literal local, and the cg_adapter temp slots through it. -- **[low/boundary-violation]** `lang/c/parse/parse_expr.c:1036` — cg_adapter seam leaks: callers poke cg_type_stack[] directly because no retag-without-clearing op exists - - *Fix:* Add `pcg_retag_top_keep_flags(Parser*, const Type*)` (and a depth variant for the [sp-2] case) to cg_adapter that rewrites only the slot's type and route both call sites through it, so no caller touches cg_type_stack directly. -- **[low/duplication]** `lang/c/parse/parse_expr.c:3137-3183` — parse_band/parse_bxor/parse_bor are near-verbatim copies differing only in token and BO_ constant - - *Fix:* Factor a single helper `parse_bitwise_level(Parser* p, u32 punct, BinOp op, void (*next)(Parser*))` (or a small table of {punct, op, next}) and define the three levels as one-line wrappers, leaving the shared operand check and coercion in one place. - -### lang-c-sem-abi — 3 - -- **[low/dead-code]** `lang/c/abi/c_abi.c:80-95` — Frontend ABI info (c_abi_func_info, CGFuncDesc.abi, CGParamDesc.abi) is computed on every function then never read — dead stub - - *Fix:* Delete c_abi_func_info, the frontend ABIArgInfo/ABIFuncInfo/ABIArgInfo stub types, c_abi.h:51, and the CGFuncDesc.abi / CGParamDesc.abi fields (cg_adapter.h:209,225), plus the parse.c:1287/1146/1166 plumbing. If those void* fields are kept as forward-looking carriers, at minimum drop the c_abi_func_info call so the parser stops allocating a zeroed dummy array per function. Renaming the stub types away from the real ABIArgInfo/ABIFuncInfo would also remove the name collision. -- **[low/duplication]** `lang/c/parse/parse_init.c:648-777` — Two parallel C §6.7.9 initializer walkers (runtime vs static) duplicate the same traversal grammar and can diverge - - *Fix:* Factor the shared traversal into a single grammar driver parameterized by a leaf vtable/callback pair (emit-scalar, emit-string, zero-fill) so the runtime path supplies CG-store leaves and the static path supplies buffer-write leaves. At minimum, port the designator-continuation logic (designator_continues_inside/remainder) into the static path so the two cannot silently diverge on nested designators. -- **[low/confusing]** `lang/c/sem/sem.c:46-50` — c_sem_check_assignment takes a CSemAssignContext that it immediately discards - - *Fix:* Either use ctx to differentiate diagnostics (e.g. 'incompatible types when initializing' vs 'when assigning' vs 'when returning'), or remove the parameter and the CSemAssignContext enum entirely and let callers prefix their own context in the perr() message. Do not keep a threaded-but-ignored discriminator. - -### lang-cpp — 4 - -- **[low/confusing]** `lang/cpp/pp/pp_expand.c:938-965` — `defined_skip` is an undocumented 5-state machine with a dead branch and a header comment that describes a different machine - - *Fix:* Delete the unreachable `defined_skip == 1` branch (lines 939-941). Rewrite the pp_priv.h comment to describe the real states actually used (0 idle, 2 saw `defined`, 3 saw `defined (`, 4 saw operand inside parens) or, better, replace the magic integers with a named enum (DSK_IDLE/DSK_AFTER_DEFINED/DSK_IN_PAREN/DSK_AFTER_OPERAND) so the states are self-documenting and the dead state cannot silently reappear. -- **[medium/duplication]** `lang/cpp/pp/pp_directive.c:1166-1179` — `do_embed` header-name parsing is copy-pasted from `parse_include_path` and dropped the bounds check, leaving a stack overflow - - *Fix:* Extract a shared helper `static int header_name_to_path(Pp*, const Tok* hdr, char* out, size_t cap, int* system_out, SrcLoc loc)` that decodes the `<...>`/`"..."` form, validates `slen < 2`, sets the system flag, and copies with the cap check. Call it from both parse_include_path and do_embed. This both removes the duplication and closes the overflow. -- **[low/dead-code]** `lang/cpp/pp/pp_expand.c:485-505, 575-577` — `read_invocation_args` carries abandoned editing debris: an empty no-op `if`, a body-less `if`, and a block of stale stream-of-consciousness comments referencing a nonexistent variable - - *Fix:* Delete the dead `if (raw.n && depth >= 0) {}` block and the entire stale comment scaffold at 491-504, leaving a single one-line comment explaining that newlines are dropped as intra-invocation whitespace. Delete the empty `if` at 575-577 (it is pure noise; the panic below already handles the too-few-args case). -- **[low/duplication]** `lang/cpp/pp/pp_expand.c:524-530, 549-555, 584-592` — Identical inline buffer-growth boilerplate is copy-pasted four times in one function (and twice more in do_define) - - *Fix:* Add a small `U32Vec`/generic grow helper alongside the existing tv_grow/hsv_grow (or a `static inline u32* u32v_reserve(Pp*, u32** p, u32* cap, u32 want)`), and replace the four `starts` blocks and two `params` blocks with single calls. Reuses the established vector-helper pattern already in pp_priv.h. - -### lang-toy — 2 - -- **[medium/duplication]** `lang/toy/expr.c:1212-1373` — Address/access navigation chain (index / deref / field) is copy-pasted three times - - *Fix:* Extract a single address-chain walker, e.g. `static KitCgTypeId toy_parse_addr_chain(ToyParser* p, KitCgTypeId base_ptr_ty, ToyTypeId base_toy, ToyTypeId* out_toy)` that consumes a pointer-rvalue TOS and folds `[idx]`/`.*`/`.field` steps, used by the `&` operator, the assignment lvalue, and (via a final load) the postfix value path. Have toy_parse_expr_postfix call toy_record_field_index instead of its inline loop. This collapses ~400 duplicated lines into one place. -- **[low/complexity]** `lang/toy/builtins.c:392-785` — Builtin dispatch is a linear chain of toy_sym_is calls that re-intern constant strings on every comparison - - *Fix:* Intern the builtin/keyword name set once at parser init into a small table (or a precomputed KitSym -> handler/enum map), then dispatch on the interned KitSym. At minimum, within a matched group resolve the operation from a local table keyed by the already-interned `name` instead of re-calling toy_sym_is per branch; and have toy_lookup_const intern row names once (lazily cached) rather than every lookup. - -### lang-wasm — 4 - -- **[medium/god-function]** `lang/wasm/cg.c:2019-3978` — wasm_emit_cg_into is a ~1960-line god function fusing module setup with per-opcode lowering - - *Fix:* Split into a thin driver plus phase functions that all take (KitCompiler*, KitCg*, const WasmModule*, const WasmCgRuntime*, KitArena*): wasm_cg_declare_traps, wasm_cg_declare_funcs (returns the syms[]/func_types[] arrays), and a wasm_cg_emit_init that itself delegates to wasm_cg_init_memories / _passive_data / _import_funcs / _tables / _elems / _globals / _start. Pull the per-function body lowering into wasm_cg_emit_func_body(c, cg, m, rt, i, syms, func_types) so the 174-case switch lives in its own ~1400-line function with its own control stack — and hoist WasmCgControl to file scope. Each phase becomes independently testable and the driver reads as a table of contents. -- **[low/duplication]** `src/wasm/decode.c:196-991` — 54 byte-identical memarg decode arms copy-pasted instead of table-driven - - *Fix:* For the contiguous memarg opcode ranges, look the kind up via the existing table instead of hardcoding: collapse the 54 arms into a default/range branch that does `const WasmInsnInfo* info = wasm_insn_info_for_byte(prefix, op); if (info && info->operand_class == WASM_OC_MEMARG) { bin_memarg(...); wasm_func_add_mem_insn(c,out,f,info->kind,ma,mo,mi); break; }` (add a byte->row reverse lookup to wasm_insn_table.c, which already owns the kind/byte/operand_class columns). The genuinely irregular arms stay hand-written, honoring the documented design while killing the copy-paste and the second byte<->kind map. -- **[low/duplication]** `lang/wasm/cg.c:2106-2117` — Function-name builder reimplements wasm_indexed_name inline, minus its bounds guard - - *Fix:* Replace the whole else-branch body with `wasm_indexed_name(local_name, sizeof local_name, "__kit_wasm_func_", i);` then `source_name = kit_sym_intern(c, kit_slice_cstr(local_name));`. Identical result, one fewer hand-rolled itoa, and you regain the bounds check. -- **[low/dead-code]** `lang/wasm/cg.c:1652-1653` — Bogus (void) casts on used variables and a no-op jump in the bulk-copy loop emitters - - *Fix:* Delete the three (void) casts (1652, 1653, 1779) — the variables are used, so they generate no warning. Delete the after_dir label, its kit_cg_label_new, and the redundant jump/place pair at 1727-1728, letting the forward path fall straight through into loop_start (place loop_start directly after the forward label's body). - -### link — 3 - -- **[medium/boundary-violation]** `src/link/link_jit.c:797-811, 991-1004` — JIT-mode undef resolution hardcodes format/OS pseudo-symbol names by memcmp, and the two copies in link_jit.c have drifted - - *Fix:* Promote both names to format authorities and call them everywhere. `_tls_index` already has `obj_format_jit_drops_symbol_ref(c, name)` — use it instead of the inline memcmp in link_jit.c and link_resolve.c. Add a sibling authority (e.g. `obj_format_jit_weak_undef_sym(c, name)` keyed on OBJ_TLS_MACHO_DESCRIPTOR) for `__tlv_bootstrap`. Then funnel all JIT-mode undef acceptance through one shared predicate `jit_undef_acceptable(c, linker, sym)` so the preflight and resolve loops cannot diverge, which both removes the format leak and fixes the `_tls_index` drift bug. -- **[medium/god-function]** `src/link/link_jit.c:714-1195` — jit_append_obj_inner is a ~480-line god-function reimplementing the AOT link pipeline inline - - *Fix:* Decompose into named helpers mirroring the AOT pass names: jit_append_preflight(), jit_append_plan_sections(), jit_append_register_syms(), jit_append_resolve_undefs(), jit_append_grow_tables(), jit_append_materialize(), jit_append_relocs(). Better, factor the symbol-merge/duplicate-global decision and the undef-acceptance decision into shared helpers callable by both link_resolve.c and link_jit.c so the append path and the bulk path share one implementation. At minimum, collapse the two in-function copies of the duplicate-global check into a single helper. -- **[low/boundary-violation]** `src/link/link_reloc_layout.c:937-939` — link_emit_relocations skips RISC-V marker relocs by hardcoded enum name instead of the RELOC_MARKER descriptor flag built for it - - *Fix:* Add `static inline int reloc_kind_is_marker(const Compiler* c, RelocKind k)` to link_reloc_desc.h returning `reloc_desc(c,k) && (reloc_desc(c,k)->flags & RELOC_MARKER)`, and replace the name check with it. Give `R_RV_ALIGN` a descriptor row tagged `RELOC_MARKER` too (it currently has none, which is why it must be named explicitly) so the predicate covers all three. Optionally use the existing `RELOC_WIDTH_DYN` flag at line 982 in the same spirit. - -### obj-coff — 3 - -- **[low/dead-code]** `src/obj/coff/link.c:1416-1426` — Dead struct CoffOutHdr with a lying comment claiming it's used by two passes - - *Fix:* Delete the struct and its comment outright. If a shared per-section view is ever wanted, introduce it at the point of use, not as an orphan type. -- **[low/duplication]** `src/obj/coff/link.c:1702-1714` — Final-VA-of-a-defined-symbol formula triplicated, with the entry path using a weaker section lookup - - *Fix:* Reuse the existing helper: `u32 entry_rva = (u32)(coff_symbol_final_va(img, out, map, img->entry_sym, "entry") - PE_IMAGE_BASE);`. That removes the third copy, fixes the inconsistent/weaker section lookup, and folds the defined/SK_ABS guard into one place. Consider also routing apply_all_relocs' defined-symbol branch through the same helper (returning RVA, with callers adding ImageBase) so there is exactly one formula. -- **[low/confusing]** `src/obj/coff/link.c:1074-1077` — size_raw (a wire-format field) is overloaded as a scratch allocation-cap stash - - *Fix:* Add a dedicated `u32 cap;` field to CoffSection (or pass the bucket_cap[COFF_NBUCKETS] array back to link_emit_coff by reference) and drop the overloading plus all three explanatory comments. The field then means exactly one thing throughout its lifetime. - -### obj-core — 4 - -- **[low/duplication]** `src/obj/obj_tls.c:78-191` — TLS storage emission copy-pasted between define_tls_elf and define_tls_macho - - *Fix:* Extract a `static void tls_emit_storage(ObjBuilder* ob, Compiler* c, ObjSymId target, const u8* data, u32 size, int has_nonzero_init, u32 align, const ObjTlsReloc* relocs, u32 nrelocs)` that emits the tbss/tdata section and defines `target`. define_tls_elf calls it with `sym`; define_tls_macho calls it with `data_sym` and then appends only the __thread_vars descriptor + two ABS64 relocs. -- **[low/duplication]** `src/obj/registry.c:446-459` — obj_format_lookup_bin reimplements a mapping the unused ObjFormatImpl.bin_fmt field already encodes - - *Fix:* Replace the switch with a scan over obj_format_impls matching `impl->bin_fmt == fmt` (the field finally gets read, the switch disappears). Handle the KIT_BIN_PE alias the way obj_format_dso_reader_for_bytes already does (PE shares KIT_OBJ_COFF). If the field is to stay unused instead, delete it from ObjFormatImpl and all four rows. -- **[low/confusing]** `src/obj/obj.c:23-28` — Stale comment claims obj_symbol_find does a re-check + linear-scan fallback it does not - - *Fix:* Rewrite lines 27-28 to describe the real invariant: the index is the sole source of truth for find, and obj_symbol_rename keeps it exact by re-pointing/deleting the entry on rename — there is no re-check or fallback scan in find. -- **[low/inconsistency]** `src/obj/obj_secnames.c:141-229` — Five parallel switch(c->target.obj) blocks for synthetic section names bypass the format vtable - - *Fix:* Add a small per-format synthetic-section-name table to ObjFormatImpl (e.g. a `const char* synth_secname[OBJ_SYNTH_SEC_COUNT]` indexed by an enum {INIT_ARRAY, FINI_ARRAY, PREINIT_ARRAY, TDATA, TBSS}) and collapse the five functions into one lookup-and-intern helper, matching how default_entry_name/c_label_prefix already work. NULL entries map to the existing panic-unimpl path. - -### obj-elf — 6 - -- **[medium/god-function]** `src/obj/elf/link.c:891-1830` — link_emit_elf is a 940-line god function spanning 14 phases; its largest sub-block is misplaced dynamic-link emit - - *Fix:* Extract the PIE dynamic-emit block (link.c 979-1149: DT_* table build, .rela.dyn/.rela.plt/.got.plt re-serialization, refresh_dynsym_exports) into the promised emit_dynamic_body(LinkImage*, u64 img_base) declared in link_dyn.h and defined in link_dyn.c next to layout_dyn, so the produce/consume halves of dyn state are colocated. Also factor the symtab/strtab/shstrtab build (1225-1334) and phdr build (1377-1521) into named statics. Fix the stale 'link_elf.c'/'emit_dynamic_body' comment either way. -- **[low/duplication]** `src/obj/elf/link.c:645-696` — The same linear-dedup string-table builder is reimplemented four times - - *Fix:* Use ObjByteBuf + objbb_append_str everywhere a deduped strtab/shstrtab is built. Delete StrBuilder/strb_* from link.c and strtab_add from emit.c (or, if emit.c's Buf segmentation truly precludes it, keep one shared helper). Drop the inline DT_NEEDED scan per the next finding. -- **[low/confusing]** `src/obj/elf/link.c:1034-1060` — DT_NEEDED soname offsets are computed at append time, discarded, then fragilely re-scanned at emit time with a confused panic path - - *Fix:* Store the per-soname dynstr offsets in LinkDynState at append time (e.g. add `u32* needed_stroff` alongside `needed`/`nneeded` in link_dyn.h and fill it from the objbb_append_str return), then emit DT_NEEDED directly from that array. This deletes the entire re-scan loop, the self-doubting comment, and the panic. -- **[low/duplication]** `src/obj/elf/link.c:785-794` — Byte-identical DynSymRec and DynRela wire serialization duplicated across link.c and link_dyn.c - - *Fix:* Add two static-inline helpers next to the struct definitions in link_dyn.h: `dynsym_rec_write(u8* p, const DynSymRec*)` and `dynrela_write(u8* p, const DynRela*)`, and call them from all five sites. -- **[low/duplication]** `src/obj/elf/link.c:1555-1564` — RISC-V float-ABI to e_flags derivation duplicated between the ET_REL and ET_EXEC writers (surviving per-arch identity) - - *Fix:* Add `u32 elf_riscv_float_abi_to_e_flags(KitFloatAbi)` to the riscv reloc TU (paired with the existing elf_riscv_float_abi_from_e_flags) or hang it off ObjElfArchOps, and have both emit.c and link.c call it instead of inlining the policy. -- **[low/dead-code]** `src/obj/obj_secnames.c:392-399` — Dead public helper obj_format_elf_tls_tp_bias; link.c re-implements the same arch lookup inline - - *Fix:* Either delete obj_format_elf_tls_tp_bias (and its obj.h decl) as dead, or have tls_tcb_bias() call it for the base lookup and keep only the RV-hosted override local — don't keep both the dead accessor and an inline copy of its body. - -### obj-macho — 4 - -- **[medium/god-function]** `src/obj/macho/link.c:626-1199` — plan_layout is a 574-line god function doing ~7 distinct layout phases - - *Fix:* Extract the obvious phases into named helpers taking MCtx*: plan_enumerate_text/_data_const/_data/_dwarf, plan_make_stubs/got/tlv_ptrs, plan_group_secs_by_name, plan_count_outsecs, plan_predict_sizeofcmds (or better, derive sizeofcmds from the same emit path used by link_emit_macho so they can't drift), plan_place_vaddrs, plan_build_outsecs. plan_layout then becomes a readable sequence of calls. -- **[low/dead-code]** `src/obj/macho/link.c:1570-1574` — Dead struct, dead struct field, and six write-only MCtx layout fields survived the dead-code sweep - - *Fix:* Delete the PageChain struct, the MachImp.internal_vaddr field (and its misleading comment), and the six write-only MCtx fields plus their assignments in plan_layout (lines 1052,1060-1061,1079-1081). -- **[low/dead-code]** `src/obj/macho/link.c:2441-2452` — LC_LOAD_DYLINKER emission contains a no-op loop and dead variable (leftover scratch code) - - *Fix:* Replace the whole block with the same pattern used for LC_LOAD_DYLIB: record `u32 cmd_start = lc.len;` before writing the command, then `while (lc.len - cmd_start < cmd_size) objbb_u8(&lc, 0);`. Delete the no-op while, `want`, and the manual cmd_start_back/pad_needed arithmetic. Also drop the dead first `sis_size` assignment at line 1705 and the scratch comment blocks at 1706-1708 and 1344-1352. -- **[low/duplication]** `src/obj/macho/link.c:593-624` — Mach-O "__SEG,__sect" comma-name split duplicated between pick_macho_names and the __DWARF planning block - - *Fix:* Add one helper, e.g. `static int msec_split_comma_name(MSec* m, Slice nm)` returning whether a comma was found and filling segname_buf/sectname_buf, and call it from both sites. The __DWARF block keeps its else-branches (obj_macho_debug_sectname / raw fallback) on top of the shared splitter. - -### obj-wasm — 3 - -- **[low/duplication]** `src/obj/wasm/read.c:24-48` — read.c reimplements decode.c's byte-cursor + ULEB128 reader verbatim - - *Fix:* Expose a small shared bounds-checked cursor + uleb reader from src/wasm (e.g. wasm_cursor_u8 / wasm_cursor_uleb over a public WasmCursor, or just make decode.c's BinReader primitives non-static and reuse them) and have read.c's add_code_symbols / section walk call it, deleting WasmCur/cur_u8/cur_uleb. The error-routing difference (compiler_panic vs wasm_error) can be unified since both are fatal. -- **[low/complexity]** `src/obj/wasm/read.c:132-211` — read_wasm fully decodes+validates the whole module just to recover names, then re-walks the raw framing - - *Fix:* Either (a) fold the two passes into one: walk the framing once and, when hitting the export/name custom sections, decode just those to populate names — avoiding the full body decode/validate; or (b) if the full decode must stay, at least drop the redundant wasm_is_binary pre-check (decode already validates magic) and document why the second raw walk is required (the model intentionally discards section offsets/raw bytes), so the cost is a deliberate, explained choice rather than apparent accident. -- **[low/boundary-violation]** `src/obj/wasm/emit.c:14-32` — Public API function kit_obj_builder_wasm_add_custom lives in a format-internal emit file, not the api layer - - *Fix:* Move kit_obj_builder_wasm_add_custom into src/api/object_builder.c (or a dedicated src/api/object_builder_wasm.c) alongside the rest of the public ObjBuilder API, leaving src/obj/wasm/emit.c to hold only the internal emit_wasm hook. The body can call a small internal helper if it still needs WasmModule access. - -### opt-passes — 3 - -- **[low/duplication]** `src/opt/pass_lower.c:923-966, 1022-1034` — Coalesce-group / live-range / point-loop scaffold copy-pasted three times in the O1 allocator - - *Fix:* Extract a single `alloc_for_each_group_point(Func*, OptAllocator*, const OptLiveRangeSet*, PReg root, void (*cb)(OptAllocator*, u32 point, void*), void* arg)` (or an inline-able macro over the point) and have the three sites pass only their per-point body. The metrics counters (hard_point_visits / hard_mark_points / stack_mark_points) can be a parameter or incremented in the callback. -- **[low/duplication]** `src/opt/pass_o2.c:587-621, 2578-2602` — Two passes fold IR_ADDR_OF(local) zero-EA uses with a copy-pasted fold body - - *Fix:* Factor the per-use rewrite into a shared helper, e.g. `static void addr_fold_zero_ea_use(Func* f, OptUse* use, Operand local_op)`, and call it from both loops. The differing pass-level policy (all-uses gate + def removal vs. opportunistic) stays in each caller; only the load/store-aware fold body is shared. -- **[low/complexity]** `src/opt/pass_combine.c:1339-1356` — try_ret_retarget rebuilds combine ctx via the 128-iteration (cls,reg) bulk probe ctx_record is documented to avoid - - *Fix:* try_ret_retarget already knows the producer's old destination operand (the ret value's prior reg before it was rewritten to ret_reg). Return that operand (or the old reg) and call ctx_restore_removed_def on just that one (cls,reg), mirroring try_sink's ctx_restore_removed_def(&ctx, &src, prod_idx) usage, instead of scanning all 128 slots. - -### opt-ssa-regalloc — 5 - -- **[low/confusing]** `src/opt/pass_ssa.c:892-894` — realign_phi_preds has a no-op guard clause that defeats its own already-aligned fast path - - *Fix:* Decide the intent. If the fast path is wanted: `if (!aux) continue; if (aux->npreds == bl->npreds) continue;` (the realign is then a no-op for aligned phis). If realignment must always run (e.g. pred order may have changed even at equal count), drop the dead clause entirely: `if (!aux) continue;`. -- **[low/duplication]** `src/opt/pass_ssa.c:345-459` — SSA reg-renaming re-hand-rolls the IR_CALL/RET/ASM/INTRINSIC aux walk that opt_walk_inst_operands already centralizes - - *Fix:* Drive reg-renaming through opt_walk_inst_operands with a single callback that branches on is_def (replace-use when !is_def, allocate-new-Val/push when is_def), the same way replace_use/live_collect_use_def already do. The one wrinkle (def-index/ordinal tracking in reg_define_operand) can be recovered from the operand pointer or a small per-walk counter, eliminating two of the four hand-rolled aux switches in this file. -- **[low/complexity]** `src/opt/pass_analysis.c:238-265` — order_dfs recurses over the CFG while its sibling reachability walk uses an explicit stack — inconsistent and a deep-CFG stack hazard - - *Fix:* Convert order_dfs to an explicit-stack iterative DFS (post-order can be produced with a two-phase or color-marking stack), matching mark_reachable, and ideally share the successor+label-target enumeration between the two so the reachability/order walks cannot drift. This also removes the stack-overflow risk on pathologically deep CFGs. -- **[low/dead-code]** `src/opt/pass_coalesce.c:54-58` — opt_ranges_overlap_kind: redundant local prototype plus a static trampoline that just forwards to it - - *Fix:* Delete the local forward declaration (line 54) and the static ranges_overlap_kind wrapper (lines 55-57); change the call site at line 315 to call opt_ranges_overlap_kind directly, matching pass_lower.c. -- **[low/confusing]** `src/opt/pass_live.c:178-187` — Obfuscated max() inside the liveness dataflow metric wrappers - - *Fix:* Write the intent directly: `u32 n = (dst && src) ? (src->active_words > dst->active_words ? src->active_words : dst->active_words) : 0;` (or a small max helper). Optionally fold the four near-identical live_metric_* wrappers behind a single helper that records active_words, since they differ only in which bitset op they delegate to. - -### riscv-asm-isa — 5 - -- **[medium/global-state]** `src/arch/riscv/isa.c:1222-1488` — Compressed-instruction decoder synthesizes descriptors into a function-local static (global mutable state, non-reentrant) - - *Fix:* Decode compressed instructions into a caller-owned Rv64InsnDesc (pass `Rv64InsnDesc* out`), or have the caller (rv64_decode_one / rv64_format_insn) own a stack/struct-resident scratch desc and pass its address in. Better: return a small (mnemonic-slice, Rv64Format, flags) value struct by value and let the printer dispatch on the format tag, eliminating the synthesized descriptor entirely. Collapse the ~40 near-identical synthesis blocks into a table or one helper `mk(name, fmt, flags)`. -- **[low/duplication]** `src/arch/riscv/asm.c:309-328` — RISC-V B/J/S immediate bit-scramble reimplemented three times across files - - *Fix:* Add immediate-only helpers in isa.h (e.g. rv_b_imm/rv_j_imm/rv_s_imm returning just the scattered immediate bits) and define both the field-packers (rv_b = rv_b_imm|funct/op) and the assembler enc_* (enc_b = match|rv_b_imm) and the reloc patcher in terms of them, so the scramble exists once. -- **[low/confusing]** `src/arch/riscv/isa.c:1554-1591` — Disassembler dispatches instruction subclass by string-comparing the mnemonic - - *Fix:* Give Rv64InsnDesc a small numeric sub-op/alias tag (or a per-row print-kind field) and dispatch on that instead of on the printed mnemonic string, so the mnemonic stays purely a display label and mis-spellings can't change decode behavior. -- **[low/duplication]** `src/arch/riscv/link.c:78-106` — rv32 PLT/IPLT emitters are copy-paste of the rv64 ones, differing only by LW vs LD - - *Fix:* Parameterize one emitter on the GOT-slot load width (pass a `bool xlen32` or the load-builder, or read it from the variant) and have both link_arch_rv32/rv64 descriptors point at the shared implementation; drop the duplicated rv32_* bodies. -- **[low/dead-code]** `src/arch/riscv/disasm.c:73-80` — Per-decode O(table) re-scan to compute an encoding_id that nothing reads - - *Fix:* Either drop the encoding_id computation entirely (set it to a fixed sentinel) or, if a consumer is genuinely planned, have rv64_disasm_find return the index directly so the second scan is removed. At minimum stop recomputing it for the compressed path where it is unconditionally UNKNOWN. - -### riscv-native — 4 - -- **[medium/duplication]** `src/arch/riscv/native.c:3752-3850, 4083-4251` — Inline-asm orchestration loops triplicated across rv64/aa64/x64 backends - - *Fix:* Hoist the two orchestration loops into cg/native_asm.c as e.g. native_asm_bind_operands(NativeDirectTarget*/NativeTarget*, outs/ins, bound[], &used_masks, vtable). Parameterize the three arch-specific bits via a tiny callback/descriptor: bound_reg/bound_mem encoders (or an OPK_REG constant + class-tag offset), the reserved-scratch register list, and a panic hook. Each backend then keeps only its register table and its ~5-line vtable, deleting ~250 lines. -- **[low/duplication]** `src/arch/riscv/native.c:3953-3986` — rv_no_tail duplicates the arch-neutral CGCallDesc->NativeCallDesc projection - - *Fix:* Add a shared helper in native_direct_target.c that builds the NativeCallDesc from a CGCallDesc and computes the stack size via t->call_stack_bytes, comparing against d->native's incoming_stack_size; let each backend's op be a thin predicate that adds only its arch-specific preconditions (rv64's callee-save guard). -- **[low/duplication]** `src/arch/riscv/native.c:639-663, 1400-1420` — PC-relative auipc+anchor pair idiom hand-duplicated three times in the file - - *Fix:* Factor a single static helper, e.g. rv_emit_pcrel_pair(a, dst, hi_reloc, sym, addend, low_is_load), that emits the auipc+HI reloc, sets up the anchor, and emits either ld (load==1, for GOT) or addi (load==0). Call it from both rv_emit_global_addr branches and rv_load_label_addr. -- **[low/inconsistency]** `src/arch/riscv/native.c:1656` — rv_func_end hardcodes callee-save array size 16 instead of RV_MAX_CALLEE_SAVES - - *Fix:* Change line 1656 to `u32 int_regs[RV_MAX_CALLEE_SAVES], fp_regs[RV_MAX_CALLEE_SAVES];` to match the other two collect sites. - -### rt — 4 - -- **[medium/duplication]** `rt/lib/fp_tf/fp_tf.c:40-274` — fp_tf.c hand-rolls __multf3/__divtf3 instead of the shared QUAD-capable templates - - *Fix:* Replace the bespoke __multf3 with the shared template exactly as fp.c does: `#define QUAD_PRECISION` then `#include "fp_mul_impl.inc"` and `COMPILER_RT_ABI fp_t __multf3(fp_t a, fp_t b){return __mulXf3__(a,b);}`. Likewise replace __divtf3 with `#define NUMBER_OF_HALF_ITERATIONS 3 / NUMBER_OF_FULL_ITERATIONS 2`, `#include "fp_div_impl.inc"`, `return __divXf3__(a,b);`. Delete kit_tf_u256* and kit_clz_u32/u64. If a specific QUAD bug originally motivated the hand-roll, fix it in the shared template (so sf/df benefit too) rather than forking a parallel implementation. -- **[low/dead-code]** `rt/lib/fp_tf/fp_tf.c:407-450` — __fixtfsi includes fp_fixint_impl.inc but ignores it, leaving dead generated code + scaffolding - - *Fix:* Drop the `#define fixint_t/fixuint_t/FP_FIX_SUFFIX`, the `#include "fp_fixint_impl.inc"`, and the matching `#undef`s from the fixtfsi block (keep only the hand-written __fixtfsi). Better: fix the overflow case inside the shared fp_fixint_impl.inc template's saturation check so __fixtfsi can go back to `return __fixint(a);` like its di/ti siblings, eliminating the special case entirely. -- **[low/dead-code]** `rt/lib/int64/int64.c:18-81, 325-342` — Dead 128/64 division and dword-multiply helpers in int64.c - - *Fix:* Delete udiv128by64to64default, udiv128by64to64, and __mulddi3. If the intent was for __udivmodti4 to use the fast 128/64 long-division path (it is materially faster than the 128-iteration ut_udivmod loop), then wire udiv128by64to64 into __udivmodti4 and delete the bit-loop instead — but pick one; shipping both is the worst outcome. -- **[low/duplication]** `rt/lib/int/si_div.c:8-45` — si_div.c hand-rolls slow 32-bit division and duplicates its own divide loop - - *Fix:* Generate the 32-bit helpers from int_div_impl.inc the same way int.c generates the 64-bit ones (`#define fixint_t si_int; #define fixuint_t su_int; #define INT_DIV_SUFFIX udivsi3; #include "int_div_impl.inc"`), and define `__udivsi3(n,d){return __udivmodsi4(n,d,0);}` so the divide loop exists once. This removes the duplicated loop and the slow algorithm in one move. - -### wasm-backend — 4 - -- **[low/dead-code]** `src/arch/wasm/emit.c:3239-3370` — ~130 lines of dead #if 0 code referencing symbols that no longer exist - - *Fix:* Delete lines 3241-3370 (the `#if 0`/`#endif` block) entirely. Remove the now-unnecessary forward declaration at 3239 if `linearize_range` is the next definition (it is, at 3372). Fix the stale comment at emit.c:4083-4086 and the reference at internal.h:415 to describe the actual structurizer flow. -- **[low/confusing]** `src/arch/wasm/internal.h:16-21` — Module header doc claims a dozen features 'all panic' that are in fact fully implemented - - *Fix:* Rewrite the 'Scope' paragraph to reflect reality: list what is actually still unsupported (TLS, indirect_branch, load_label_addr, address-taken params, aggregate variadic args, 64-bit checked-mul overflow, setjmp/longjmp) and move the now-implemented features into the supported list. Keep it in sync with the capability hooks in arch.c. -- **[low/duplication]** `src/arch/wasm/emit.c:3556-3570` — Natural-width load/store opcode selection re-open-coded instead of reusing load_kind_for/store_kind_for - - *Fix:* Add tiny helpers like `natural_store_op(WasmValType)` / `natural_load_op(WasmValType)` (returning the full-width opcode) and a `wasm_mem_width(opcode)` lookup (already exists), then call them from both the variadic-pack loop and va_arg. Or synthesize a full-width MemAccess and route through the existing store_kind_for/load_kind_for. -- **[low/inconsistency]** `src/arch/wasm/emit.c:1177-1211` — Operand-kind tag written as raw 0/1 in one branch and WOP_REG/WOP_IMM in the next within the same function - - *Fix:* Use `WOP_REG`/`WOP_IMM` consistently in the fixed-arg branch (1177/1181). Introduce a named constant (e.g. `WRET_SRET_COPY = 1`) for the WIR_RET cgop tag and use it at both the write (1246) and read (3626) sites. - -### x64-asm-isa — 4 - -- **[low/dead-code]** `src/arch/x64/asm.c:896-910` — Unreachable duplicate `if (op == 0x88u)` block in parse_alu_rr (dead code that survived the sweep) - - *Fix:* Delete lines 900-910 entirely; the first block already covers op==0x88u via emit_movb_rr_operand. -- **[low/duplication]** `src/arch/x64/asm.c:1480-1546` — `mov` dispatch forked into a bespoke inline block that shadows the table-driven parsers, leaving dead branches - - *Fix:* Drop the inline mov block and route mov through the same find_mnemonic_row + parse_and_emit_for_format path as every other mnemonic. The MOV_RI peek (the only genuinely needed disambiguation) can be done inside parse_alu_rr/parse_mov_rm_load (check first operand kind), as the comment at 1477 already contemplates. Then delete the now-redundant reg/mem branches from parse_mov_rm_load and the 0x89/0x88 branches from parse_alu_rr. -- **[low/duplication]** `src/arch/x64/isa.c:823-1062` — Six byte-identical rm/reg operand printers that collapse to two - - *Fix:* Replace with two shared helpers, e.g. print_rm_reg (rm,reg) and print_reg_rm (reg,rm), and point the X64_FMT_* cases at them in x64_print_operands (as already done for POPCNT->print_bs). -- **[low/duplication]** `src/arch/x64/disasm.c:79-114` — Triplicated KitInsn fallback-fill block in x64_decode - - *Fix:* Factor a small `finish_byte_fallback(d, out, bytes, vaddr)` that calls render_byte_fallback and fills the one-byte KitInsn, and a `finish(out, ...total)` for the success path; the three fallback sites become one-liners. - -### x64-native — 4 - -- **[medium/duplication]** `src/arch/x64/native.c:4410-4499` — Inline-asm operand-binding loop is copy-pasted between x64 and aa64 backends - - *Fix:* Hoist the out/in binding loops into a shared cg/native_asm.c routine (e.g. native_asm_bind_direct_operands) taking a small vtable of {resolve_pin, alloc_reg, bound_reg, bound_mem, panic} plus the initial used_int/used_fp reservation. The arch files keep only their reservation seed (the reserved-scratch masks at x64 4404-4408 / aa64 4729-4731) and the helper implementations. This also collapses the in-file out-loop/in-loop near-duplication into one parameterized pass. -- **[medium/duplication]** `src/arch/x64/native.c:1946-1953, 2655-2666` — Callee-save offset formula and restore loop open-coded in 5+ sites; tail-site epilogue duplicates func_end - - *Fix:* Add x64_cs_int_off(xmm_base, n_fp, i) / x64_cs_fp_off(xmm_base, i) inline helpers and use them in all five sites, then extract x64_emit_callee_restores(a) (the collect + reverse-restore loops) and call it from both x64_func_end and x64_emit_tail_site, mirroring aa_emit_callee_restores. -- **[low/dead-code]** `src/arch/x64/emit.h:22-25` — Four prologue byte-budget macros defined but never referenced - - *Fix:* Delete the four unused macros. If a sized prologue budget is desired, derive X64_PROLOGUE_BYTES from the components in one place; otherwise drop them and keep the hardcoded budgets that x64_build_prologue's per-step `if (wi + N > cap)` guards already validate. -- **[low/duplication]** `src/arch/x64/native.c:1511-1518, 2675-2685, 2708-2717` — Indirect jmp/call r/m64 encoding triplicated - - *Fix:* Add a small emit helper, e.g. emit_jmp_call_rm64(MCEmitter* mc, u32 reg, u32 sub /* 4=jmp, 2=call */), and call it from all three sites. - -### xc-duplication — 3 - -- **[low/duplication]** `src/obj/elf/reloc_riscv32.c:17-175` — RV32 ELF reloc map is a near-verbatim clone of the RV64 map (only 3 cases differ) - - *Fix:* Delete the two duplicated switches. Make elf_riscv32_reloc_to a thin wrapper: `if (kind==R_ABS64||kind==R_ADD64||kind==R_SUB64) return ELF_R_RISCV_NONE; return elf_riscv64_reloc_to(kind);` and elf_riscv32_reloc_from likewise filter ELF_R_RISCV_64/ADD64/SUB64 to (u32)-1 then delegate to elf_riscv64_reloc_from. ~6 lines each, single source of truth for the shared arms. -- **[low/duplication]** `src/arch/aa64/arch.c:114-143` — Per-arch backend_make / semantic_target_new are identical boilerplate copy-pasted across all 3 arches - - *Fix:* Add two function-pointer fields to ArchImpl (e.g. native_target_new + native_direct_ops) or pass them in, and provide one shared cg_native_backend_make()/cg_native_semantic_target_new() in src/cg/native_direct_target.c that takes them. Each arch.c then just registers its two hooks instead of duplicating 30 lines. -- **[low/duplication]** `src/arch/aa64/arch.c:20-27` — register_at_public wrapper is byte-identical across all three arch backends - - *Fix:* Make `register_iter_get` (idx->dwarf_idx,name) the per-arch ArchImpl hook and provide one shared arch_register_at_public adapter in arch/registry.c or a shared arch helper, so .register_at points at the single shared function for every backend. - -### xc-invariants — 2 - -- **[medium/global-state]** `src/arch/riscv/isa.c:1230-1338` — RISC-V disassembler returns a pointer to a file-scope-equivalent mutable static (global mutable state, non-reentrant) - - *Fix:* Thread a caller-owned scratch slot through the function instead of a file-scope static: add a `Rv64InsnDesc* scratch` (or `Rv64InsnDesc out`) parameter to rv64_disasm_find_c(), populated from a slot living on the Rv64InsnFormatter (which already hangs off the Compiler) or on the decode call frame. Both call sites in disasm.c already have a per-call object to host it. This removes the global mutable state and makes the disassembler reentrant without changing the decode logic. -- **[low/dead-code]** `src/arch/aa64/native.c:364-614` — Blanket __attribute__((unused)) on a block of aa64 helpers masks one genuinely dead function (aa_ldr_lit64) and mislabels 13 live ones - - *Fix:* Delete aa_ldr_lit64 (truly dead). Then drop `__attribute__((unused))` from the 13 helpers that are actually called — they need no suppression and the attribute is actively misleading. Keeping the attribute off lets -Werror=unused-function catch the next dead helper automatically, which is exactly the check this block is currently defeating.