kit

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

commit 78317497c377709d6bffd9f97385ff3923766c33
parent a877e58f06163ff693abc10716c8e269327134d3
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue,  9 Jun 2026 20:31:51 -0700

cleanup wave A: dead-code deletions + localized bug fixes (review backlog)

Adversarial code-quality review (doc/plan/CLEANUP-2026-06-09.md) -> first wave:
all 27 quick-win deletions + 6 localized Tier-1 bug fixes, plus an include-guard
fix surfaced while integrating the wasm-ABI dedup. Net ~ -360 LOC across 39 files.

Quick wins (Q1-Q27): delete dead aa_ldr_lit64 + strip the false
__attribute__((unused)) from 13 live aa64 helpers (re-enables
-Werror=unused-function); dead x64 duplicate mov branch + prologue macros; dead
CFI vtable methods + emit_prologue hook in the arch MC layer; dead CoffOutHdr;
Mach-O write-only layout fields; KitJitSession dead fields; OPK_LOCAL /
defined_skip / dwarf no-op deadcode; #if 0 wasm zombie; ranges_overlap
trampoline; obfuscated max(); run.c -> shared driver_parse_u64 /
driver_record_mcmodel; wasm inline itoa -> wasm_indexed_name; AA64Mn.arg dead
field; several stale/lying comments.

Tier-1 bug fixes:
- T1.1  #embed stack overflow: shared bounds-checked header_name_to_path
- T1.4  RISC-V feature-off build break: add rv32 link/dbg stubs
- T1.5  breakpoint max_hits: implement the post-park auto-clear (was empty if)
- T1.6  debugger one-shot bp leak: run_to_internal_bp clears on all paths
- T1.10 object_builder entsize: use obj_section_set_entsize, drop orphan path
- T1.11 wasm public/internal enum dedup: runtime_abi.h includes kit/wasm.h
- include-guard collision (latent, exposed by T1.11): src/wasm/wasm.h
  KIT_WASM_H -> KIT_WASM_WASM_H; public kit/wasm.h keeps KIT_WASM_H
- T1.12 dep-iter: documented (accurate fix needs a cross-file SourceInclude change)

T1.3 (C-target reloc width) attempted then reverted: needs a design pass since
the C target cannot represent sub-pointer-width address relocs; see the doc.

Verified: make lib bin + full targeted suite green (asm/parse/pp/toy/cg-api/opt/
isa/aa64-inline/link/elf/ar/driver-ar/debug/dwarf/libc/smoke-x64/smoke-rv64).
Pre-existing test-lib-deps allowlist drift is unchanged.

Diffstat:
Adoc/plan/CLEANUP-2026-06-09.md | 680+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mdriver/cmd/run.c | 77+++++++++++++++++++----------------------------------------------------------
Minclude/kit/wasm.h | 48++++++++++++++++++++----------------------------
Mlang/cpp/pp/pp_directive.c | 69+++++++++++++++++++++++++++++++++++----------------------------------
Mlang/cpp/pp/pp_expand.c | 38++++++++++----------------------------
Mlang/cpp/pp/pp_priv.h | 9+++++----
Mlang/wasm/cg.c | 21+++------------------
Mlang/wasm/runtime_abi.h | 62++++++++++++++++----------------------------------------------
Msrc/api/compile.c | 18++++++++++++++----
Msrc/api/object_builder.c | 13+------------
Msrc/arch/aa64/asm.c | 439+++++++++++++++++++++++++++++++++++++++----------------------------------------
Msrc/arch/aa64/dbg.c | 8++++----
Msrc/arch/aa64/isa.c | 14--------------
Msrc/arch/aa64/isa.h | 17++---------------
Msrc/arch/aa64/native.c | 86++++++++++++++++++++++++++++++++++---------------------------------------------
Msrc/arch/dbg_stubs.c | 1+
Msrc/arch/link_stubs.c | 1+
Msrc/arch/mc.c | 56+-------------------------------------------------------
Msrc/arch/mc.h | 4----
Msrc/arch/native_target.h | 31++++++++++++++-----------------
Msrc/arch/riscv/native.c | 127+++++++++++++++++++++++++++++++++++++++++++------------------------------------
Msrc/arch/wasm/emit.c | 146++++---------------------------------------------------------------------------
Msrc/arch/wasm/internal.h | 5+++--
Msrc/arch/x64/asm.c | 11-----------
Msrc/arch/x64/emit.h | 4----
Msrc/cg/wide.c | 29+++++++++++------------------
Msrc/dbg/dbg.h | 5+----
Msrc/dbg/session.c | 16++++++++++++++--
Msrc/dbg/step.c | 55++++++++++++++++++++++++++++++-------------------------
Msrc/debug/dwarf_open.c | 12------------
Msrc/obj/coff/link.c | 30++++++++++--------------------
Msrc/obj/elf/link_dyn.c | 28++++++++++++++++------------
Msrc/obj/macho/link.c | 26--------------------------
Msrc/obj/obj.c | 35++++++++++++++++++-----------------
Msrc/obj/obj.h | 7-------
Msrc/obj/obj_secnames.c | 9---------
Msrc/opt/pass_coalesce.c | 7++-----
Msrc/opt/pass_live.c | 4++--
Msrc/opt/pass_ssa.c | 4+---
Msrc/wasm/wasm.h | 4++--
40 files changed, 1268 insertions(+), 988 deletions(-)

diff --git a/doc/plan/CLEANUP-2026-06-09.md b/doc/plan/CLEANUP-2026-06-09.md @@ -0,0 +1,680 @@ +# 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`. +- [ ] **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). +- [ ] **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`. +- [ ] **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. +- [ ] **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) +- [ ] **A.1** `aa_/x64_/rv_direct_asm_block` (`aa64/native.c:4711`, `x64/native.c:4383`, + `riscv/native.c:4083`) are ~170-line line-for-line copies on top of the *already-shared* + `cg/native_asm.c` primitives, and already drifting (3 different frame-slot conditions; + rv64 has an extra output-staging branch). *Fix:* hoist + `native_asm_bind_direct_operands(d, dir, constraints, n, ops, masks, bound[])` + one + shared driver into `cg/native_asm.c`; each backend keeps only its register table + a + thin vtable. (Single agent owns all 4 files.) + +### B. Per-arch codegen plumbing duplicated (med, 7) +- [ ] **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. +- [ ] **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`. +- [ ] **B.3** PC-relative `auipc`+anchor reloc pair emitted 3× in riscv. *Fix:* one helper. +- [ ] **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) +- [ ] **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. +- [ ] **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. +- [ ] **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). +- [ ] **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) +- [ ] **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`. +- [ ] **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. +- [ ] **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) +- [ ] **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. +- [ ] **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. +- [ ] **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) +- [ ] **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`. +- [ ] **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 +- [ ] **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. +- **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). + +--- + +## 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. diff --git a/driver/cmd/run.c b/driver/cmd/run.c @@ -401,51 +401,6 @@ static int run_alloc_arrays(RunOptions* o, int argc) { return 0; } -/* Decimal uint64 parse for -fmax-errors=N. Mirrors cc's helper. */ -static int run_parse_u64(const char* s, uint64_t* out) { - uint64_t v = 0; - int any = 0; - if (!s) return 1; - while (*s) { - unsigned d; - if (*s < '0' || *s > '9') return 1; - d = (unsigned)(*s - '0'); - if (v > (UINT64_MAX - d) / 10u) return 1; - v = v * 10u + d; - any = 1; - s++; - } - if (!any) return 1; - *out = v; - return 0; -} - -static int run_record_mcmodel(RunOptions* o, const char* val) { - if (driver_streq(val, "small")) { - o->target.code_model = KIT_CM_SMALL; - return 0; - } - if (driver_streq(val, "medium")) { - o->target.code_model = KIT_CM_MEDIUM; - return 0; - } - if (driver_streq(val, "large")) { - o->target.code_model = KIT_CM_LARGE; - return 0; - } - if (driver_streq(val, "medlow")) { - o->target.code_model = KIT_CM_SMALL; - return 0; - } - if (driver_streq(val, "medany")) { - o->target.code_model = KIT_CM_MEDIUM; - return 0; - } - driver_errf(RUN_TOOL, "unknown -mcmodel value: %.*s", - KIT_SLICE_ARG(kit_slice_cstr(val))); - return 1; -} - static int run_classify_positional(RunOptions* o, const char* a) { int r = driver_inputs_classify(&o->inputs, a); if (r < 0) return 1; @@ -475,7 +430,8 @@ static const char* wasm_scan_custom(const uint8_t* p, size_t total, while (p < end) { uint8_t b; id = *p++; - size = 0; shift = 0; + size = 0; + shift = 0; for (;;) { if (p >= end) return NULL; b = *p++; @@ -490,12 +446,18 @@ static const char* wasm_scan_custom(const uint8_t* p, size_t total, uint32_t nlen = 0; shift = 0; for (;;) { - if (p >= sec_end) { p = sec_end; goto next_section; } + if (p >= sec_end) { + p = sec_end; + goto next_section; + } b = *p++; nlen |= (uint32_t)(b & 0x7f) << shift; if (!(b & 0x80)) break; shift += 7; - if (shift > 28) { p = sec_end; goto next_section; } + if (shift > 28) { + p = sec_end; + goto next_section; + } } if ((uint32_t)(sec_end - p) >= nlen) { size_t wlen = driver_strlen(want); @@ -506,7 +468,7 @@ static const char* wasm_scan_custom(const uint8_t* p, size_t total, } } } - next_section: + next_section: p = sec_end; } return NULL; @@ -644,7 +606,7 @@ static int run_parse(int argc, char** argv, RunOptions* o) { } if (driver_strneq(a, "-fmax-errors=", 13)) { uint64_t v; - if (run_parse_u64(a + 13, &v) != 0 || v > 0xFFFFFFFFu) { + if (driver_parse_u64(a + 13, &v) != 0 || v > 0xFFFFFFFFu) { driver_errf(RUN_TOOL, "-fmax-errors= requires a non-negative integer"); return 1; } @@ -670,7 +632,7 @@ static int run_parse(int argc, char** argv, RunOptions* o) { } if (driver_strneq(a, "-mcmodel=", 9)) { - if (run_record_mcmodel(o, a + 9) != 0) return 1; + if (driver_record_mcmodel(&o->target, RUN_TOOL, a + 9) != 0) return 1; continue; } { @@ -766,16 +728,14 @@ static int run_parse(int argc, char** argv, RunOptions* o) { return 1; } if (!o->entry && run_inputs_have_wasm_source(&o->inputs) && - !run_inputs_have_non_wasm(&o->inputs) && - o->inputs.nsources == 1) { + !run_inputs_have_non_wasm(&o->inputs) && o->inputs.nsources == 1) { DriverLoad lf = {0}; KitSlice bytes = {0}; - if (driver_load_bytes(&o->env->file_io, RUN_TOOL, - o->inputs.sources[0], &lf, &bytes) == 0) { + if (driver_load_bytes(&o->env->file_io, RUN_TOOL, o->inputs.sources[0], &lf, + &bytes) == 0) { uint32_t elen = 0; const char* ep = wasm_scan_custom((const uint8_t*)bytes.s, - (size_t)bytes.len, - "kit-entry", &elen); + (size_t)bytes.len, "kit-entry", &elen); if (ep && elen > 0 && elen < 256) { char* buf = (char*)o->env->heap->alloc(o->env->heap, elen + 1u, 1); if (buf) { @@ -1046,7 +1006,8 @@ int driver_run(int argc, char** argv) { } /* Compile/JIT succeeded; enforce -Werror / note -fmax-errors before running - * the entry. On a -Werror trip we tear the JIT image down, never executing. */ + * the entry. On a -Werror trip we tear the JIT image down, never executing. + */ if (driver_diag_finish(&env, RUN_TOOL, ro.warnings_are_errors, ro.max_errors)) { kit_interp_program_free(interp); diff --git a/include/kit/wasm.h b/include/kit/wasm.h @@ -60,23 +60,17 @@ typedef struct KitWasmImportType { enum { KIT_WASM_PAGE_SIZE = 65536u }; -#ifndef KIT_WASM_MEMORY_FLAG_TYPES -#define KIT_WASM_MEMORY_FLAG_TYPES enum { KIT_WASM_MEMORY_SHARED = 1u << 0, KIT_WASM_MEMORY_64 = 1u << 1, }; -#endif -#ifndef KIT_WASM_IMPORT_KIND_TYPES -#define KIT_WASM_IMPORT_KIND_TYPES typedef enum KitWasmImportKind { KIT_WASM_IMPORT_FUNC = 0, KIT_WASM_IMPORT_TABLE = 1, KIT_WASM_IMPORT_MEMORY = 2, KIT_WASM_IMPORT_GLOBAL = 3, } KitWasmImportKind; -#endif typedef struct KitWasmMemoryImportType { uint64_t min_pages; @@ -97,8 +91,6 @@ typedef struct KitWasmGlobalImportType { uint32_t mutable_; } KitWasmGlobalImportType; -#ifndef KIT_WASM_RUNTIME_LAYOUT_TYPES -#define KIT_WASM_RUNTIME_LAYOUT_TYPES typedef struct KitWasmMemoryLayout { uint64_t offset; /* byte offset of this KitWasmMemory in the instance */ uint64_t min_pages; /* initial page count */ @@ -112,7 +104,6 @@ typedef struct KitWasmRuntimeLayout { const KitWasmMemoryLayout* memories; /* borrowed from the JIT image */ uint32_t nmemories; } KitWasmRuntimeLayout; -#endif typedef enum KitWasmHostFlags { KIT_WASM_HOST_WASI_PREVIEW1 = 1u << 0, @@ -126,26 +117,26 @@ typedef enum KitWasmFsFlags { /* WASI filetype constants (wasi_filetype_t). */ enum { - KIT_WASM_FILETYPE_UNKNOWN = 0, - KIT_WASM_FILETYPE_BLOCK_DEVICE = 1, + KIT_WASM_FILETYPE_UNKNOWN = 0, + KIT_WASM_FILETYPE_BLOCK_DEVICE = 1, KIT_WASM_FILETYPE_CHARACTER_DEVICE = 2, - KIT_WASM_FILETYPE_DIRECTORY = 3, - KIT_WASM_FILETYPE_REGULAR_FILE = 4, - KIT_WASM_FILETYPE_SOCKET_DGRAM = 5, - KIT_WASM_FILETYPE_SOCKET_STREAM = 6, - KIT_WASM_FILETYPE_SYMBOLIC_LINK = 7, + KIT_WASM_FILETYPE_DIRECTORY = 3, + KIT_WASM_FILETYPE_REGULAR_FILE = 4, + KIT_WASM_FILETYPE_SOCKET_DGRAM = 5, + KIT_WASM_FILETYPE_SOCKET_STREAM = 6, + KIT_WASM_FILETYPE_SYMBOLIC_LINK = 7, }; /* Host-side directory entry returned by KitWasmReadDirEntryFn. * name is borrowed from callback-owned storage; valid only until the next * read_dir_entry call or close_dir on the same handle. */ typedef struct KitWasmDirEntry { - uint64_t ino; /* inode or synthetic id; 0 if unavailable */ - uint64_t size; /* file size in bytes; 0 for directories */ - uint64_t mtime_ns; /* modification time ns since Unix epoch; 0 if unknown */ - const char* name; /* borrowed; not NUL-terminated — use name_len */ - uint32_t name_len; - uint8_t filetype; /* KIT_WASM_FILETYPE_* */ + uint64_t ino; /* inode or synthetic id; 0 if unavailable */ + uint64_t size; /* file size in bytes; 0 for directories */ + uint64_t mtime_ns; /* modification time ns since Unix epoch; 0 if unknown */ + const char* name; /* borrowed; not NUL-terminated — use name_len */ + uint32_t name_len; + uint8_t filetype; /* KIT_WASM_FILETYPE_* */ } KitWasmDirEntry; typedef struct KitWasmFsMount { @@ -166,7 +157,7 @@ typedef KitStatus (*KitWasmRandomFn)(void* user, uint8_t* dst, size_t n); typedef KitStatus (*KitWasmStatPathFn)(void* user, const char* path, uint64_t* out_size, uint64_t* out_mtime_ns, - uint8_t* out_filetype); + uint8_t* out_filetype); /* Open a directory and snapshot its entries (excluding "." and ".."). * Returns KIT_OK with a non-NULL *out_handle on success. */ @@ -178,8 +169,8 @@ typedef KitStatus (*KitWasmOpenDirFn)(void* user, const char* path, * out->name is borrowed from the handle; valid until the next call or * close_dir. */ typedef KitStatus (*KitWasmReadDirEntryFn)(void* user, void* handle, - uint64_t index, - KitWasmDirEntry* out); + uint64_t index, + KitWasmDirEntry* out); /* Close and free a directory handle opened by KitWasmOpenDirFn. */ typedef void (*KitWasmCloseDirFn)(void* user, void* handle); @@ -205,10 +196,11 @@ typedef struct KitWasmHostConfig { KitWasmWriteFn write; KitWasmClockFn clock; KitWasmRandomFn random; - KitWasmStatPathFn stat_path; /* NULL: fd_filestat_get synthesizes from buffered size */ - KitWasmOpenDirFn open_dir; /* NULL: directory path_open returns ENOSYS */ + KitWasmStatPathFn + stat_path; /* NULL: fd_filestat_get synthesizes from buffered size */ + KitWasmOpenDirFn open_dir; /* NULL: directory path_open returns ENOSYS */ KitWasmReadDirEntryFn read_dir_entry; - KitWasmCloseDirFn close_dir; + KitWasmCloseDirFn close_dir; void* user; } KitWasmHostConfig; diff --git a/lang/cpp/pp/pp_directive.c b/lang/cpp/pp/pp_directive.c @@ -66,7 +66,8 @@ void read_directive_line(Pp* pp, Tok** out_toks, u32* out_n) { static i64 parse_pp_int(const char* s, size_t n) { int base = 10; size_t i = 0; - u64 val = 0; /* unsigned: #if arithmetic wraps on overflow, signed would be UB */ + u64 val = + 0; /* unsigned: #if arithmetic wraps on overflow, signed would be UB */ if (n >= 2 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) { base = 16; i = 2; @@ -336,12 +337,12 @@ typedef struct EeOp { } EeOp; static const EeOp ee_ops[] = { - {'*', 10, eb_mul}, {'/', 10, eb_div}, {'%', 10, eb_mod}, - {'+', 9, eb_add}, {'-', 9, eb_sub}, {P_SHL, 8, eb_shl}, - {P_SHR, 8, eb_shr}, {'<', 7, eb_lt}, {'>', 7, eb_gt}, - {P_LE, 7, eb_le}, {P_GE, 7, eb_ge}, {P_EQ, 6, eb_eq}, - {P_NE, 6, eb_ne}, {'&', 5, eb_band}, {'^', 4, eb_bxor}, - {'|', 3, eb_bor}, {P_AND, 2, eb_logand}, {P_OR, 1, eb_logor}, + {'*', 10, eb_mul}, {'/', 10, eb_div}, {'%', 10, eb_mod}, + {'+', 9, eb_add}, {'-', 9, eb_sub}, {P_SHL, 8, eb_shl}, + {P_SHR, 8, eb_shr}, {'<', 7, eb_lt}, {'>', 7, eb_gt}, + {P_LE, 7, eb_le}, {P_GE, 7, eb_ge}, {P_EQ, 6, eb_eq}, + {P_NE, 6, eb_ne}, {'&', 5, eb_band}, {'^', 4, eb_bxor}, + {'|', 3, eb_bor}, {P_AND, 2, eb_logand}, {P_OR, 1, eb_logor}, }; static const EeOp* ee_lookup_op(const Tok* t) { @@ -709,6 +710,26 @@ static int find_and_open_include(Pp* pp, const char* path, int system, return 0; } +/* Decode a directly-lexed TOK_HEADER name into a NUL-terminated path. + * Classifies <...> (system) vs "..." (local), enforces the destination + * capacity, and writes the unwrapped contents to out. `what` is the + * directive name used in panic messages (e.g. "#include", "#embed"). */ +static void header_name_to_path(Pp* pp, KitSlice slc, char* out, size_t cap, + int* system_out, SrcLoc loc, const char* what) { + const char* s = slc.s; + size_t slen = slc.len; + if (slen < 2) compiler_panic(pp->c, loc, "%s: malformed header name", what); + if (s[0] == '<' && s[slen - 1] == '>') + *system_out = 1; + else if (s[0] == '"' && s[slen - 1] == '"') + *system_out = 0; + else + compiler_panic(pp->c, loc, "%s: malformed header name", what); + if (slen - 2 + 1 > cap) compiler_panic(pp->c, loc, "%s: path too long", what); + memcpy(out, s + 1, slen - 2); + out[slen - 2] = 0; +} + /* Parse the directive arguments into (path, system_flag). Handles: * - directly-lexed TOK_HEADER: < ... > or " ... " * - macro-replaced form: line is macro-expanded, then expected to @@ -719,19 +740,7 @@ static void parse_include_path(Pp* pp, const Tok* line, u32 n, SrcLoc loc, if (line[0].kind == TOK_HEADER) { KitSlice sl = kit_sym_str(pp->pool->c, line[0].spelling); - const char* s = sl.s; - size_t slen = sl.len; - if (slen < 2) compiler_panic(pp->c, loc, "#include: malformed header name"); - if (s[0] == '<' && s[slen - 1] == '>') - *system_out = 1; - else if (s[0] == '"' && s[slen - 1] == '"') - *system_out = 0; - else - compiler_panic(pp->c, loc, "#include: malformed header name"); - if (slen - 2 + 1 > cap) - compiler_panic(pp->c, loc, "#include: path too long"); - memcpy(path_out, s + 1, slen - 2); - path_out[slen - 2] = 0; + header_name_to_path(pp, sl, path_out, cap, system_out, loc, "#include"); return; } @@ -984,9 +993,10 @@ static void handle_pragma_pack(Pp* pp, const Tok* line, u32 n, SrcLoc loc) { (u32)(sizeof pp->pack_stack / sizeof pp->pack_stack[0])) { pp->pack_stack[pp->pack_stack_n++] = pp->pack_align; } else { - pp_warn(pp, loc, - "#pragma pack(push): pack stack overflow (max %u); push dropped", - (u32)(sizeof pp->pack_stack / sizeof pp->pack_stack[0])); + pp_warn( + pp, loc, + "#pragma pack(push): pack stack overflow (max %u); push dropped", + (u32)(sizeof pp->pack_stack / sizeof pp->pack_stack[0])); } ++i; if (i < n && line[i].kind == TOK_PUNCT && line[i].v.punct == ',') { @@ -1165,17 +1175,8 @@ static void do_embed(Pp* pp, const Tok* line, u32 n, SrcLoc loc) { if (line[0].kind == TOK_HEADER) { KitSlice slc = kit_sym_str(pp->pool->c, line[0].spelling); - const char* s = slc.s; - size_t sl = slc.len; - if (sl < 2) compiler_panic(pp->c, loc, "#embed: malformed header name"); - if (s[0] == '<' && s[sl - 1] == '>') - system_form = 1; - else if (s[0] == '"' && s[sl - 1] == '"') - system_form = 0; - else - compiler_panic(pp->c, loc, "#embed: malformed header name"); - memcpy(path, s + 1, sl - 2); - path[sl - 2] = 0; + header_name_to_path(pp, slc, path, sizeof(path), &system_form, loc, + "#embed"); arg_start = 1; } else { compiler_panic(pp->c, loc, "#embed: header-name argument required"); diff --git a/lang/cpp/pp/pp_expand.c b/lang/cpp/pp/pp_expand.c @@ -167,10 +167,11 @@ void do_define(Pp* pp, const Tok* line, u32 n) { ++i; /* GNU named variadic: `args...` — the named parameter itself collects * the trailing arguments (the body refers to it by name rather than - * __VA_ARGS__). The variadic arg-collection below is positional on the - * last param, so we just mark the macro variadic and eat the ellipsis; - * the "'...' must be last" check still fires if a comma follows. Linux - * UAPI headers use this (e.g. <linux/stddef.h>'s __struct_group). */ + * __VA_ARGS__). The variadic arg-collection below is positional on + * the last param, so we just mark the macro variadic and eat the + * ellipsis; the "'...' must be last" check still fires if a comma + * follows. Linux UAPI headers use this (e.g. <linux/stddef.h>'s + * __struct_group). */ if (i < n && line[i].kind == TOK_PUNCT && line[i].v.punct == P_ELLIPSIS) { m->is_variadic = 1; @@ -483,25 +484,9 @@ static Tok read_invocation_args(Pp* pp, const Macro* m, SrcLoc invoke_loc, "unterminated function-like macro invocation"); } if (t.kind == TOK_NEWLINE) { - /* Whitespace within an invocation. Mark the next token as - * having space; drop the NL. */ - if (raw.n && depth >= 0) { - /* No-op token list; we'll OR onto the next pushed token. */ - } - /* Use a sentinel: track via a flag on a deferred push. We - * accumulate "has_space" by setting it on the next pushed - * token. */ - /* Simpler: just push a placeholder by OR'ing onto next via - * a flag stored in `first_token_of_arg`-style state. */ - /* Implementation: use the next read token's TF_HAS_SPACE bit, - * which the lexer already sets after a NL. Actually NOT — - * after a NL the lexer sets TF_AT_BOL on the next token, not - * HAS_SPACE necessarily. Force it: */ - /* We'll OR it manually onto the next token. */ - /* Use a small flag stash: */ - /* (handled below by setting a pending flag) */ - /* See: pending_space variable */ - /* — commit: declare a pending_space static earlier. */ + /* A newline inside an invocation is just whitespace; drop it. + * The lexer already records the surrounding-whitespace bits on + * the next token, so nothing needs to be carried across here. */ continue; } @@ -936,14 +921,11 @@ Tok pp_next_raw(Pp* pp) { * expand an already-defined X to its body (typically empty). See * the `defined_skip` field comment in pp_priv.h. */ if (pp->in_if_expansion) { - if (pp->defined_skip == 1 && t.kind == TOK_IDENT) { - t.flags |= TF_NO_EXPAND; - pp->defined_skip = 0; - } else if (pp->defined_skip == 2) { + if (pp->defined_skip == 2) { if (t.kind == TOK_PUNCT && t.v.punct == '(') { pp->defined_skip = 3; } else if (t.kind == TOK_IDENT) { - /* `defined IDENT` (no parens) — same as the skip==1 case. */ + /* `defined IDENT` (no parens): mark the operand and reset. */ t.flags |= TF_NO_EXPAND; pp->defined_skip = 0; } else { diff --git a/lang/cpp/pp/pp_priv.h b/lang/cpp/pp/pp_priv.h @@ -177,10 +177,11 @@ struct Pp { * This pair of fields tracks the state across `pp_next_raw` calls * within `expand_for_if`: * in_if_expansion: 1 inside an #if's expand_arg_to_eof call - * defined_skip: 0 normally; 1 after emitting `defined` - * (consume one IDENT before clearing); 2 after - * emitting `defined (` (waiting for IDENT then - * `)`). + * defined_skip: 0 normally; 2 after emitting `defined` (a bare + * `defined IDENT` marks the IDENT and resets, while + * a `(` advances to 3); 3 after `defined (` (waiting + * for the operand IDENT, advancing to 4); 4 after + * `defined ( IDENT` (waiting for the closing `)`). * The expander uses these to mark the operand IDENT TF_NO_EXPAND * before the macro-expansion check at the head of pp_next_raw. */ u8 in_if_expansion; diff --git a/lang/wasm/cg.c b/lang/wasm/cg.c @@ -1644,13 +1644,10 @@ static void wasm_cg_emit_byte_copy_loop( KitCgLabel done = kit_cg_label_new(cg); KitCgLabel loop_start = kit_cg_label_new(cg); KitCgLabel forward = kit_cg_label_new(cg); - KitCgLabel after_dir = kit_cg_label_new(cg); memset(&attrs, 0, sizeof attrs); attrs.flags = KIT_CG_LOCAL_COMPILER_TEMP; idx = kit_cg_local(cg, b.id[KIT_CG_BUILTIN_I64], attrs); dir = kit_cg_local(cg, b.id[KIT_CG_BUILTIN_I32], attrs); - (void)rt; - (void)ptr_mem; /* If n == 0, nothing to do. */ kit_cg_push_local(cg, n_local); kit_cg_load(cg, i64_mem); @@ -1724,8 +1721,6 @@ static void wasm_cg_emit_byte_copy_loop( kit_cg_push_local(cg, idx); kit_cg_push_int(cg, 0, b.id[KIT_CG_BUILTIN_I64]); kit_cg_store(cg, i64_mem); - kit_cg_jump(cg, after_dir); - kit_cg_label_place(cg, after_dir); kit_cg_label_place(cg, loop_start); kit_cg_push_local(cg, idx); kit_cg_load(cg, i64_mem); @@ -1776,7 +1771,6 @@ static void wasm_cg_emit_byte_fill_loop(KitCg* cg, KitCgBuiltinTypes b, KitCgLocal idx; KitCgLabel done = kit_cg_label_new(cg); KitCgLabel loop_start = kit_cg_label_new(cg); - (void)rt; memset(&attrs, 0, sizeof attrs); attrs.flags = KIT_CG_LOCAL_COMPILER_TEMP; idx = kit_cg_local(cg, b.id[KIT_CG_BUILTIN_I64], attrs); @@ -2103,17 +2097,7 @@ void wasm_emit_cg_into(KitCompiler* c, KitCg* cg, const WasmModule* m) { if (f->export_name) { source_name = kit_sym_intern(c, kit_slice_cstr(f->export_name)); } else { - size_t pos = 0; - const char prefix[] = "__kit_wasm_func_"; - uint32_t n = i, div = 1000000000u; - memcpy(local_name, prefix, sizeof(prefix) - 1u); - pos = sizeof(prefix) - 1u; - while (div > 1u && n / div == 0) div /= 10u; - while (div) { - local_name[pos++] = (char)('0' + (n / div) % 10u); - div /= 10u; - } - local_name[pos] = '\0'; + wasm_indexed_name(local_name, sizeof local_name, "__kit_wasm_func_", i); source_name = kit_sym_intern(c, kit_slice_cstr(local_name)); } memset(&decl, 0, sizeof decl); @@ -2799,7 +2783,8 @@ void wasm_emit_cg_into(KitCompiler* c, KitCg* cg, const WasmModule* m) { for (uint32_t p = 0; p < t->nparams; ++p) ref_params[p + 1u].type = wasm_cg_type(c, b, t->params[p]); memset(&ref_sig, 0, sizeof ref_sig); - if (t->nresults) ref_sig.result.type = wasm_cg_type(c, b, t->results[0]); + if (t->nresults) + ref_sig.result.type = wasm_cg_type(c, b, t->results[0]); ref_sig.params = ref_params; ref_sig.nparams = t->nparams + 1u; ref_sig.call_conv = KIT_CG_CC_TARGET_C; diff --git a/lang/wasm/runtime_abi.h b/lang/wasm/runtime_abi.h @@ -3,6 +3,12 @@ #include <stdint.h> +/* The public header is the source of truth for the shared emitter<->reader + * types: the KIT_WASM_MEMORY_* flags, KitWasmImportKind, KitWasmMemoryLayout, + * KitWasmRuntimeLayout, and the KitWasmInstance forward declaration. The + * internal-only kit-instance ABI types live below. */ +#include <kit/wasm.h> + typedef struct KitWasmMemory { uint8_t* data; uint64_t pages; @@ -10,41 +16,6 @@ typedef struct KitWasmMemory { uint32_t flags; } KitWasmMemory; -#ifndef KIT_WASM_MEMORY_FLAG_TYPES -#define KIT_WASM_MEMORY_FLAG_TYPES -enum { - KIT_WASM_MEMORY_SHARED = 1u << 0, - KIT_WASM_MEMORY_64 = 1u << 1, -}; -#endif - -#ifndef KIT_WASM_IMPORT_KIND_TYPES -#define KIT_WASM_IMPORT_KIND_TYPES -typedef enum KitWasmImportKind { - KIT_WASM_IMPORT_FUNC = 0, - KIT_WASM_IMPORT_TABLE = 1, - KIT_WASM_IMPORT_MEMORY = 2, - KIT_WASM_IMPORT_GLOBAL = 3, -} KitWasmImportKind; -#endif - -#ifndef KIT_WASM_RUNTIME_LAYOUT_TYPES -#define KIT_WASM_RUNTIME_LAYOUT_TYPES -typedef struct KitWasmMemoryLayout { - uint64_t offset; - uint64_t min_pages; - uint64_t max_pages; - uint32_t flags; - uint32_t reserved; -} KitWasmMemoryLayout; - -typedef struct KitWasmRuntimeLayout { - uint64_t instance_size; - const KitWasmMemoryLayout* memories; - uint32_t nmemories; -} KitWasmRuntimeLayout; -#endif - typedef struct KitWasmFuncImport { void* fn; } KitWasmFuncImport; @@ -82,12 +53,11 @@ typedef struct KitWasmPassiveElemSegment { uint32_t length; } KitWasmPassiveElemSegment; -/* Opaque to C callers for now. The compiler emits a module-specific instance - * layout with KitWasmMemory first, followed by import slots, runtime tables, - * and lowered global slots. Table entry storage is instance-owned for - * defined tables; embedders fill import slots before calling the init hook. */ -typedef struct KitWasmInstance KitWasmInstance; - +/* KitWasmInstance is forward-declared in kit/wasm.h. The compiler emits a + * module-specific instance layout with KitWasmMemory first, followed by import + * slots, runtime tables, and lowered global slots. Table entry storage is + * instance-owned for defined tables; embedders fill import slots before calling + * the init hook. */ typedef void (*KitWasmInitFn)(KitWasmInstance*); /* ---- Host-import metadata (subagent C) ---- @@ -118,11 +88,11 @@ typedef void (*KitWasmInitFn)(KitWasmInstance*); * the public KitWasmValType enum when invoking host resolvers. */ typedef struct KitWasmImportDesc { - const char* module; /* image-owned, NUL-terminated */ - const char* field; /* image-owned, NUL-terminated */ - uint32_t kind; /* KitWasmImportKind */ - uint32_t desc_index; /* index into the kind-specific descriptor array */ - uint32_t slot_offset; /* byte offset of the instance slot for this import */ + const char* module; /* image-owned, NUL-terminated */ + const char* field; /* image-owned, NUL-terminated */ + uint32_t kind; /* KitWasmImportKind */ + uint32_t desc_index; /* index into the kind-specific descriptor array */ + uint32_t slot_offset; /* byte offset of the instance slot for this import */ uint32_t reserved; } KitWasmImportDesc; diff --git a/src/api/compile.c b/src/api/compile.c @@ -1,8 +1,8 @@ /* libkit's top-level compile entry points. Status-returning shapes * that drive the C, asm, and registered-frontend paths. */ -#include <kit/compile.h> #include <kit/cg.h> +#include <kit/compile.h> #include <kit/core.h> #include <string.h> @@ -451,8 +451,8 @@ static KitStatus compile_session_run(KitCompileSession* s, kit_cg_free(cg); if (st == KIT_OK) st = compile_obj_finalize((Compiler*)s->c, ob); } else { - st = kit_frontend_compile_obj(s->frontend, &opts, input, - (KitObjBuilder*)ob); + st = + kit_frontend_compile_obj(s->frontend, &opts, input, (KitObjBuilder*)ob); } if (st != KIT_OK) { kit_frontend_abort(s->frontend); @@ -651,8 +651,18 @@ KitIterResult kit_dep_iter_next(KitDepIter* it, KitDepEdge* out) { out->included_name = included ? pool_slice(it->c->global, included->name) : KIT_SLICE_NULL; out->include_loc = edge->include_loc; - out->from_system_path = (uint8_t)(edge->system ? 1 : 0); + /* SourceInclude.system records only the spelling form (<...> vs "..."), not + * which configured directory ultimately resolved the header: the resolved-dir + * system flag (pp->inc_dirs[i].system in lang/cpp/pp/pp_directive.c) is never + * threaded onto the dep edge — find_and_open_include / source_add_include + * drop it. So a true from_system_path (e.g. "<...>" or "..." satisfied by an + * -isystem dir, vs. a plain -I dir) is not derivable here without widening + * the SourceInclude record + its producers. Until that plumbing exists, both + * fields carry the same spelling bit: `bracketed` is its accurate meaning, + * and `from_system_path` is reported as the spelling bit too (an + * approximation that over-reports <...> via plain -I as system). */ out->bracketed = (uint8_t)(edge->system ? 1 : 0); + out->from_system_path = out->bracketed; out->pad[0] = 0; out->pad[1] = 0; return KIT_ITER_ITEM; diff --git a/src/api/object_builder.c b/src/api/object_builder.c @@ -84,18 +84,7 @@ KitStatus kit_obj_builder_section(KitObjBuilder* b, id = obj_section(b, (Sym)desc->name, (SecKind)desc->kind, (u16)desc->flags, desc->align ? desc->align : 1u); if (id == OBJ_SEC_NONE) return KIT_ERR; - if (desc->entsize) { - /* Carry entsize through obj_section_ex if needed; obj_section path - * uses default 0. Use obj_section_ex when caller specifies entsize. */ - const Section* sec = obj_section_get(b, id); - (void)sec; - /* Re-create via the _ex path to set entsize. */ - /* obj_section dedupes by name+kind+flags+align; calling _ex with the - * same fields plus the entsize updates that section. */ - id = obj_section_ex(b, (Sym)desc->name, (SecKind)desc->kind, SSEM_PROGBITS, - (u16)desc->flags, desc->align ? desc->align : 1u, - desc->entsize, 0, 0); - } + if (desc->entsize) obj_section_set_entsize(b, id, desc->entsize); *out = intern_to_pub_sec(id); return KIT_OK; } diff --git a/src/arch/aa64/asm.c b/src/arch/aa64/asm.c @@ -1826,7 +1826,6 @@ typedef void (*P_Fn)(AsmDriver*); typedef struct AA64Mn { const char* name; P_Fn fn; - u32 arg; /* per-fn discriminator (alias parameter) */ } AA64Mn; /* Wrapper functions for the discriminator-taking parsers, since the @@ -2370,226 +2369,226 @@ DEF_LSE(p_ldsetl, 0u, 1u, 0u, AA64_LSE_OPC_LDSET, "ldsetl") DEF_LSE(p_ldsetal, 1u, 1u, 0u, AA64_LSE_OPC_LDSET, "ldsetal") static const AA64Mn kTable[] = { - {"fadd", p_fadd, 0}, - {"fsub", p_fsub, 0}, - {"fmul", p_fmul, 0}, - {"fdiv", p_fdiv, 0}, - {"fmax", p_fmax, 0}, - {"fmin", p_fmin, 0}, - {"fnmul", p_fnmul, 0}, - {"fneg", p_fneg, 0}, - {"fabs", p_fabs, 0}, - {"fsqrt", p_fsqrt, 0}, - {"fmov", p_fmov, 0}, - {"fcmp", p_fcmp, 0}, - {"fcvt", p_fcvt, 0}, - {"scvtf", p_scvtf, 0}, - {"ucvtf", p_ucvtf, 0}, - {"fcvtzs", p_fcvtzs, 0}, - {"fcvtzu", p_fcvtzu, 0}, - {"clz", p_clz, 0}, - {"rbit", p_rbit, 0}, - {"rev", p_rev, 0}, - {"rev16", p_rev16, 0}, - {"sbfm", p_sbfm, 0}, - {"ubfm", p_ubfm, 0}, - {"bfm", p_bfm, 0}, - {"sbfx", p_sbfx, 0}, - {"ubfx", p_ubfx, 0}, - {"sxtb", p_sxtb, 0}, - {"sxth", p_sxth, 0}, - {"sxtw", p_sxtw, 0}, - {"uxtb", p_uxtb, 0}, - {"uxth", p_uxth, 0}, - {"uxtw", p_uxtw, 0}, - {"nop", p_nop, 0}, - {"dmb", p_dmb, 0}, - {"dsb", p_dsb, 0}, - {"isb", p_isb, 0}, - {"clrex", p_clrex, 0}, - {"ret", p_ret, 0}, - {"br", p_br, 0}, - {"blr", p_blr, 0}, - {"mov", p_mov, 0}, - {"mvn", p_mvn, 0}, - {"movz", p_movz_, 0}, - {"movn", p_movn_, 0}, - {"movk", p_movk_, 0}, - {"add", p_addsub_add, 0}, - {"adds", p_addsub_adds, 0}, - {"sub", p_addsub_sub, 0}, - {"subs", p_addsub_subs, 0}, - {"cmp", p_cmp_w, 0}, - {"cmn", p_cmn_w, 0}, - {"csel", p_csel_, 0}, - {"csinc", p_csinc_, 0}, - {"csinv", p_csinv_, 0}, - {"csneg", p_csneg_, 0}, - {"cset", p_cset_, 0}, - {"csetm", p_csetm_, 0}, - {"neg", p_neg_w, 0}, - {"negs", p_negs_w, 0}, - {"and", p_and_w, 0}, - {"bic", p_bic_w, 0}, - {"orr", p_orr_w, 0}, - {"orn", p_orn_w, 0}, - {"eor", p_eor_w, 0}, - {"eon", p_eon_w, 0}, - {"ands", p_ands_w, 0}, - {"bics", p_bics_w, 0}, - {"madd", p_madd, 0}, - {"msub", p_msub, 0}, - {"mul", p_mul_w, 0}, - {"mneg", p_mneg_w, 0}, - {"udiv", p_udiv_w, 0}, - {"sdiv", p_sdiv_w, 0}, - {"lslv", p_lslv_w, 0}, - {"lsrv", p_lsrv_w, 0}, - {"asrv", p_asrv_w, 0}, - {"rorv", p_rorv_w, 0}, - {"lsl", p_lsl_, 0}, - {"lsr", p_lsr_, 0}, - {"asr", p_asr_, 0}, - {"b", p_b_, 0}, - {"bl", p_bl_, 0}, - {"cbz", p_cbz_, 0}, - {"cbnz", p_cbnz_, 0}, - {"svc", p_svc_, 0}, - {"brk", p_brk_, 0}, - {"hlt", p_hlt_, 0}, - {"mrs", p_mrs_, 0}, - {"msr", p_msr_, 0}, - {"ldr", p_ldr_, 0}, - {"str", p_str_, 0}, - {"ldrb", p_ldrb, 0}, - {"strb", p_strb, 0}, - {"ldrh", p_ldrh, 0}, - {"strh", p_strh, 0}, - {"ldrsb", p_ldrsb, 0}, - {"ldrsh", p_ldrsh, 0}, - {"ldrsw", p_ldrsw, 0}, - {"ldur", p_ldur_, 0}, - {"stur", p_stur_, 0}, - {"ldurb", p_ldurb, 0}, - {"sturb", p_sturb, 0}, - {"ldurh", p_ldurh, 0}, - {"sturh", p_sturh, 0}, - {"ldursb", p_ldursb, 0}, - {"ldursh", p_ldursh, 0}, - {"ldursw", p_ldursw, 0}, - {"ldp", p_ldp_, 0}, - {"stp", p_stp_, 0}, - {"adr", p_adr_, 0}, - {"adrp", p_adrp_, 0}, + {"fadd", p_fadd}, + {"fsub", p_fsub}, + {"fmul", p_fmul}, + {"fdiv", p_fdiv}, + {"fmax", p_fmax}, + {"fmin", p_fmin}, + {"fnmul", p_fnmul}, + {"fneg", p_fneg}, + {"fabs", p_fabs}, + {"fsqrt", p_fsqrt}, + {"fmov", p_fmov}, + {"fcmp", p_fcmp}, + {"fcvt", p_fcvt}, + {"scvtf", p_scvtf}, + {"ucvtf", p_ucvtf}, + {"fcvtzs", p_fcvtzs}, + {"fcvtzu", p_fcvtzu}, + {"clz", p_clz}, + {"rbit", p_rbit}, + {"rev", p_rev}, + {"rev16", p_rev16}, + {"sbfm", p_sbfm}, + {"ubfm", p_ubfm}, + {"bfm", p_bfm}, + {"sbfx", p_sbfx}, + {"ubfx", p_ubfx}, + {"sxtb", p_sxtb}, + {"sxth", p_sxth}, + {"sxtw", p_sxtw}, + {"uxtb", p_uxtb}, + {"uxth", p_uxth}, + {"uxtw", p_uxtw}, + {"nop", p_nop}, + {"dmb", p_dmb}, + {"dsb", p_dsb}, + {"isb", p_isb}, + {"clrex", p_clrex}, + {"ret", p_ret}, + {"br", p_br}, + {"blr", p_blr}, + {"mov", p_mov}, + {"mvn", p_mvn}, + {"movz", p_movz_}, + {"movn", p_movn_}, + {"movk", p_movk_}, + {"add", p_addsub_add}, + {"adds", p_addsub_adds}, + {"sub", p_addsub_sub}, + {"subs", p_addsub_subs}, + {"cmp", p_cmp_w}, + {"cmn", p_cmn_w}, + {"csel", p_csel_}, + {"csinc", p_csinc_}, + {"csinv", p_csinv_}, + {"csneg", p_csneg_}, + {"cset", p_cset_}, + {"csetm", p_csetm_}, + {"neg", p_neg_w}, + {"negs", p_negs_w}, + {"and", p_and_w}, + {"bic", p_bic_w}, + {"orr", p_orr_w}, + {"orn", p_orn_w}, + {"eor", p_eor_w}, + {"eon", p_eon_w}, + {"ands", p_ands_w}, + {"bics", p_bics_w}, + {"madd", p_madd}, + {"msub", p_msub}, + {"mul", p_mul_w}, + {"mneg", p_mneg_w}, + {"udiv", p_udiv_w}, + {"sdiv", p_sdiv_w}, + {"lslv", p_lslv_w}, + {"lsrv", p_lsrv_w}, + {"asrv", p_asrv_w}, + {"rorv", p_rorv_w}, + {"lsl", p_lsl_}, + {"lsr", p_lsr_}, + {"asr", p_asr_}, + {"b", p_b_}, + {"bl", p_bl_}, + {"cbz", p_cbz_}, + {"cbnz", p_cbnz_}, + {"svc", p_svc_}, + {"brk", p_brk_}, + {"hlt", p_hlt_}, + {"mrs", p_mrs_}, + {"msr", p_msr_}, + {"ldr", p_ldr_}, + {"str", p_str_}, + {"ldrb", p_ldrb}, + {"strb", p_strb}, + {"ldrh", p_ldrh}, + {"strh", p_strh}, + {"ldrsb", p_ldrsb}, + {"ldrsh", p_ldrsh}, + {"ldrsw", p_ldrsw}, + {"ldur", p_ldur_}, + {"stur", p_stur_}, + {"ldurb", p_ldurb}, + {"sturb", p_sturb}, + {"ldurh", p_ldurh}, + {"sturh", p_sturh}, + {"ldursb", p_ldursb}, + {"ldursh", p_ldursh}, + {"ldursw", p_ldursw}, + {"ldp", p_ldp_}, + {"stp", p_stp_}, + {"adr", p_adr_}, + {"adrp", p_adrp_}, /* ---- atomics / exclusive ---- */ - {"ldxr", p_ldxr_wx, 0}, - {"ldxrb", p_ldxrb, 0}, - {"ldxrh", p_ldxrh, 0}, - {"ldaxr", p_ldaxr_wx, 0}, - {"ldaxrb", p_ldaxrb, 0}, - {"ldaxrh", p_ldaxrh, 0}, - {"ldar", p_ldar_wx, 0}, - {"ldarb", p_ldarb, 0}, - {"ldarh", p_ldarh, 0}, - {"stxr", p_stxr_wx, 0}, - {"stxrb", p_stxrb_, 0}, - {"stxrh", p_stxrh_, 0}, - {"stlxr", p_stlxr_wx, 0}, - {"stlxrb", p_stlxrb_, 0}, - {"stlxrh", p_stlxrh_, 0}, - {"stlr", p_stlr_wx, 0}, - {"stlrb", p_stlrb_, 0}, - {"stlrh", p_stlrh_, 0}, - {"cas", p_cas_wx, 0}, - {"casb", p_casb, 0}, - {"cash", p_cash, 0}, - {"casa", p_casa_wx, 0}, - {"casab", p_casab, 0}, - {"casah", p_casah, 0}, - {"casl", p_casl_wx, 0}, - {"caslb", p_caslb, 0}, - {"caslh", p_caslh, 0}, - {"casal", p_casal_wx, 0}, - {"casalb", p_casalb, 0}, - {"casalh", p_casalh, 0}, - {"swp", p_swp_wx, 0}, - {"swpb", p_swpb, 0}, - {"swph", p_swph, 0}, - {"swpa", p_swpa_wx, 0}, - {"swpab", p_swpab, 0}, - {"swpah", p_swpah, 0}, - {"swpl", p_swpl_wx, 0}, - {"swplb", p_swplb, 0}, - {"swplh", p_swplh, 0}, - {"swpal", p_swpal_wx, 0}, - {"swpalb", p_swpalb, 0}, - {"swpalh", p_swpalh, 0}, - {"ldadd", p_ldadd_wx, 0}, - {"ldaddb", p_ldaddb, 0}, - {"ldaddh", p_ldaddh, 0}, - {"ldadda", p_ldadda_wx, 0}, - {"ldaddab", p_ldaddab, 0}, - {"ldaddah", p_ldaddah, 0}, - {"ldaddl", p_ldaddl_wx, 0}, - {"ldaddlb", p_ldaddlb, 0}, - {"ldaddlh", p_ldaddlh, 0}, - {"ldaddal", p_ldaddal_wx, 0}, - {"ldaddalb", p_ldaddalb, 0}, - {"ldaddalh", p_ldaddalh, 0}, - {"ldclr", p_ldclr_wx, 0}, - {"ldclrb", p_ldclrb, 0}, - {"ldclrh", p_ldclrh, 0}, - {"ldclra", p_ldclra_wx, 0}, - {"ldclrab", p_ldclrab, 0}, - {"ldclrah", p_ldclrah, 0}, - {"ldclrl", p_ldclrl_wx, 0}, - {"ldclrlb", p_ldclrlb, 0}, - {"ldclrlh", p_ldclrlh, 0}, - {"ldclral", p_ldclral_wx, 0}, - {"ldclralb", p_ldclralb, 0}, - {"ldclralh", p_ldclralh, 0}, - {"ldeor", p_ldeor_wx, 0}, - {"ldeorb", p_ldeorb, 0}, - {"ldeorh", p_ldeorh, 0}, - {"ldeora", p_ldeora_wx, 0}, - {"ldeorab", p_ldeorab, 0}, - {"ldeorah", p_ldeorah, 0}, - {"ldeorl", p_ldeorl_wx, 0}, - {"ldeorlb", p_ldeorlb, 0}, - {"ldeorlh", p_ldeorlh, 0}, - {"ldeoral", p_ldeoral_wx, 0}, - {"ldeoralb", p_ldeoralb, 0}, - {"ldeoralh", p_ldeoralh, 0}, - {"ldset", p_ldset_wx, 0}, - {"ldsetb", p_ldsetb, 0}, - {"ldseth", p_ldseth, 0}, - {"ldseta", p_ldseta_wx, 0}, - {"ldsetab", p_ldsetab, 0}, - {"ldsetah", p_ldsetah, 0}, - {"ldsetl", p_ldsetl_wx, 0}, - {"ldsetlb", p_ldsetlb, 0}, - {"ldsetlh", p_ldsetlh, 0}, - {"ldsetal", p_ldsetal_wx, 0}, - {"ldsetalb", p_ldsetalb, 0}, - {"ldsetalh", p_ldsetalh, 0}, - {"b.eq", p_b_eq, 0}, - {"b.ne", p_b_ne, 0}, - {"b.cs", p_b_cs, 0}, - {"b.hs", p_b_hs, 0}, - {"b.cc", p_b_cc, 0}, - {"b.lo", p_b_lo, 0}, - {"b.mi", p_b_mi, 0}, - {"b.pl", p_b_pl, 0}, - {"b.vs", p_b_vs, 0}, - {"b.vc", p_b_vc, 0}, - {"b.hi", p_b_hi, 0}, - {"b.ls", p_b_ls, 0}, - {"b.ge", p_b_ge, 0}, - {"b.lt", p_b_lt, 0}, - {"b.gt", p_b_gt, 0}, - {"b.le", p_b_le, 0}, - {"b.al", p_b_al, 0}, - {NULL, NULL, 0}, + {"ldxr", p_ldxr_wx}, + {"ldxrb", p_ldxrb}, + {"ldxrh", p_ldxrh}, + {"ldaxr", p_ldaxr_wx}, + {"ldaxrb", p_ldaxrb}, + {"ldaxrh", p_ldaxrh}, + {"ldar", p_ldar_wx}, + {"ldarb", p_ldarb}, + {"ldarh", p_ldarh}, + {"stxr", p_stxr_wx}, + {"stxrb", p_stxrb_}, + {"stxrh", p_stxrh_}, + {"stlxr", p_stlxr_wx}, + {"stlxrb", p_stlxrb_}, + {"stlxrh", p_stlxrh_}, + {"stlr", p_stlr_wx}, + {"stlrb", p_stlrb_}, + {"stlrh", p_stlrh_}, + {"cas", p_cas_wx}, + {"casb", p_casb}, + {"cash", p_cash}, + {"casa", p_casa_wx}, + {"casab", p_casab}, + {"casah", p_casah}, + {"casl", p_casl_wx}, + {"caslb", p_caslb}, + {"caslh", p_caslh}, + {"casal", p_casal_wx}, + {"casalb", p_casalb}, + {"casalh", p_casalh}, + {"swp", p_swp_wx}, + {"swpb", p_swpb}, + {"swph", p_swph}, + {"swpa", p_swpa_wx}, + {"swpab", p_swpab}, + {"swpah", p_swpah}, + {"swpl", p_swpl_wx}, + {"swplb", p_swplb}, + {"swplh", p_swplh}, + {"swpal", p_swpal_wx}, + {"swpalb", p_swpalb}, + {"swpalh", p_swpalh}, + {"ldadd", p_ldadd_wx}, + {"ldaddb", p_ldaddb}, + {"ldaddh", p_ldaddh}, + {"ldadda", p_ldadda_wx}, + {"ldaddab", p_ldaddab}, + {"ldaddah", p_ldaddah}, + {"ldaddl", p_ldaddl_wx}, + {"ldaddlb", p_ldaddlb}, + {"ldaddlh", p_ldaddlh}, + {"ldaddal", p_ldaddal_wx}, + {"ldaddalb", p_ldaddalb}, + {"ldaddalh", p_ldaddalh}, + {"ldclr", p_ldclr_wx}, + {"ldclrb", p_ldclrb}, + {"ldclrh", p_ldclrh}, + {"ldclra", p_ldclra_wx}, + {"ldclrab", p_ldclrab}, + {"ldclrah", p_ldclrah}, + {"ldclrl", p_ldclrl_wx}, + {"ldclrlb", p_ldclrlb}, + {"ldclrlh", p_ldclrlh}, + {"ldclral", p_ldclral_wx}, + {"ldclralb", p_ldclralb}, + {"ldclralh", p_ldclralh}, + {"ldeor", p_ldeor_wx}, + {"ldeorb", p_ldeorb}, + {"ldeorh", p_ldeorh}, + {"ldeora", p_ldeora_wx}, + {"ldeorab", p_ldeorab}, + {"ldeorah", p_ldeorah}, + {"ldeorl", p_ldeorl_wx}, + {"ldeorlb", p_ldeorlb}, + {"ldeorlh", p_ldeorlh}, + {"ldeoral", p_ldeoral_wx}, + {"ldeoralb", p_ldeoralb}, + {"ldeoralh", p_ldeoralh}, + {"ldset", p_ldset_wx}, + {"ldsetb", p_ldsetb}, + {"ldseth", p_ldseth}, + {"ldseta", p_ldseta_wx}, + {"ldsetab", p_ldsetab}, + {"ldsetah", p_ldsetah}, + {"ldsetl", p_ldsetl_wx}, + {"ldsetlb", p_ldsetlb}, + {"ldsetlh", p_ldsetlh}, + {"ldsetal", p_ldsetal_wx}, + {"ldsetalb", p_ldsetalb}, + {"ldsetalh", p_ldsetalh}, + {"b.eq", p_b_eq}, + {"b.ne", p_b_ne}, + {"b.cs", p_b_cs}, + {"b.hs", p_b_hs}, + {"b.cc", p_b_cc}, + {"b.lo", p_b_lo}, + {"b.mi", p_b_mi}, + {"b.pl", p_b_pl}, + {"b.vs", p_b_vs}, + {"b.vc", p_b_vc}, + {"b.hi", p_b_hi}, + {"b.ls", p_b_ls}, + {"b.ge", p_b_ge}, + {"b.lt", p_b_lt}, + {"b.gt", p_b_gt}, + {"b.le", p_b_le}, + {"b.al", p_b_al}, + {NULL, NULL}, }; void aa64_asm_insn(AA64Asm* a, AsmDriver* d, Sym mnemonic) { diff --git a/src/arch/aa64/dbg.c b/src/arch/aa64/dbg.c @@ -91,7 +91,7 @@ static int aa64_dbg_build_shim_word(uint32_t orig_insn, uint64_t orig_pc, pc_delta = (int64_t)orig_pc - (int64_t)scratch_runtime; /* ---- B / BL (imm26) ------------------------------------------------ */ - if ((orig_insn & 0x7C000000u) == 0x14000000u) { + if ((orig_insn & AA64_BR_IMM_FAMILY_MASK) == AA64_BR_IMM_FAMILY_MATCH) { AA64BrImm f = aa64_brimm_unpack(orig_insn); int64_t imm = sign_extend(f.imm26, 26); int64_t new_off = imm * 4 + pc_delta; @@ -108,7 +108,7 @@ static int aa64_dbg_build_shim_word(uint32_t orig_insn, uint64_t orig_pc, } /* ---- B.cond (imm19) ------------------------------------------------ */ - if ((orig_insn & 0xFF000010u) == 0x54000000u) { + if ((orig_insn & AA64_BR_COND_FAMILY_MASK) == AA64_BR_COND_FAMILY_MATCH) { AA64BrCond f = aa64_brcond_unpack(orig_insn); int64_t imm = sign_extend(f.imm19, 19); int64_t new_off = imm * 4 + pc_delta; @@ -137,7 +137,7 @@ static int aa64_dbg_build_shim_word(uint32_t orig_insn, uint64_t orig_pc, } /* ---- CBZ / CBNZ (imm19) — always trampoline form ------------------- */ - if ((orig_insn & 0x7E000000u) == 0x34000000u) { + if ((orig_insn & AA64_CB_FAMILY_MASK) == AA64_CB_FAMILY_MATCH) { AA64CB f = aa64_cb_unpack(orig_insn); int64_t imm = sign_extend(f.imm19, 19); uint64_t target = orig_pc + (uint64_t)(imm * 4); @@ -176,7 +176,7 @@ static int aa64_dbg_build_shim_word(uint32_t orig_insn, uint64_t orig_pc, } /* ---- ADR / ADRP ---------------------------------------------------- */ - if ((orig_insn & 0x1F000000u) == 0x10000000u) { + if ((orig_insn & AA64_PCREL_ADR_FAMILY_MASK) == AA64_PCREL_ADR_FAMILY_MATCH) { AA64PCRelAdr f = aa64_pcrel_adr_unpack(orig_insn); uint64_t imm_raw = ((uint64_t)f.immhi << 2) | (uint64_t)f.immlo; int64_t imm21 = sign_extend(imm_raw, 21); diff --git a/src/arch/aa64/isa.c b/src/arch/aa64/isa.c @@ -1464,17 +1464,3 @@ void aa64_print_operands(StrBuf* sb, const AA64InsnDesc* desc, u32 word, break; } } - -/* ===================================================================== - * Operand parse — phase-3 wires this up to the asm token stream. Phase - * 2 ships the signature so the assembler bring-up commit doesn't need to - * touch the descriptor table; the body returns 0 for every format until - * the per-format grammar is implemented. */ - -int aa64_parse_operands(struct AA64AsmTok* tok, const AA64InsnDesc* desc, - void* fields_out) { - (void)tok; - (void)desc; - (void)fields_out; - return 0; -} diff --git a/src/arch/aa64/isa.h b/src/arch/aa64/isa.h @@ -1566,21 +1566,13 @@ extern const u32 aa64_insn_table_n; const AA64InsnDesc* aa64_disasm_find(u32 word); /* ==================================================================== - * Operand print / parse — one entry per AA64Format. + * Operand print — one entry per AA64Format. * * aa64_print_operands renders the operand text (everything after the * mnemonic) for `word` into `sb`, using `desc->fmt` to dispatch. * Mnemonic itself is in `desc->mnemonic`; the caller writes it before * calling this helper. `vaddr` is the instruction's virtual address - * for PC-relative formats; pass 0 if not known. - * - * aa64_parse_operands is the dual: read the operand grammar for the - * format from `tok` (opaque to phase 2 — declared but unimplemented) - * and fill `fields_out` (a pointer to the format's field struct). - * Phase 3 wires `tok` up; for now the function is a forward declaration - * the assembler can resolve once it's in place. */ - -struct AA64AsmTok; /* opaque, defined by the phase-3 asm parser */ + * for PC-relative formats; pass 0 if not known. */ void aa64_print_operands(StrBuf* sb, const AA64InsnDesc* desc, u32 word, u64 vaddr); @@ -1599,9 +1591,4 @@ const char* aa64_bitfield_extend_alias(u32 word); * source bit and extracted width when an alias is available. */ const char* aa64_bitfield_extract_alias(u32 word, u32* lsb, u32* width); -/* Returns 1 on success, 0 on parse error. Phase 2 stub returns 0 for - * every format; phase 3 fills in the bodies. */ -int aa64_parse_operands(struct AA64AsmTok* tok, const AA64InsnDesc* desc, - void* fields_out); - #endif diff --git a/src/arch/aa64/native.c b/src/arch/aa64/native.c @@ -291,9 +291,8 @@ static inline i32 aa_fp_off_tail_out_arg(const AANativeTarget* a, * fp+frame_size bottom-record). Named so the CFI emit site stays layout-blind. */ static inline i32 aa_cfa_off(const AANativeTarget* a) { - return a->fp_at_bottom - ? (i32)a->frame_size_final - : (i32)(AA_FRAME_SAVE_SIZE + a->top_home_bytes); + return a->fp_at_bottom ? (i32)a->frame_size_final + : (i32)(AA_FRAME_SAVE_SIZE + a->top_home_bytes); } /* fp-relative offset of GP home slot `i` (Windows variadic only). The home area @@ -361,12 +360,11 @@ static int loc_is_64(NativeTarget* t, NativeLoc loc) { /* native_loc_is_fp is shared in native_target.h. */ -static __attribute__((unused)) int aa_use_got_for_sym(NativeTarget* t, - ObjSymId sym) { +static int aa_use_got_for_sym(NativeTarget* t, ObjSymId sym) { return obj_symbol_extern_via_got(t->c, t->obj, sym); } -static __attribute__((unused)) RelocKind aa_ldst_reloc_for_size(u32 size) { +static RelocKind aa_ldst_reloc_for_size(u32 size) { switch (size) { case 0: return R_AARCH64_LDST8_ABS_LO12_NC; @@ -420,8 +418,7 @@ static void aa_emit_add_imm(AANativeTarget* a, u32 rd, u32 rn, i32 off) { aa_emit32(mc, aa64_add(1, rd, rn, rd)); } -static __attribute__((unused)) void aa_emit_add_i64(AANativeTarget* a, u32 rd, - u32 rn, i64 off) { +static void aa_emit_add_i64(AANativeTarget* a, u32 rd, u32 rn, i64 off) { u32 imm12, sh; MCEmitter* mc = a->base.mc; if (off >= 0 && aa64_addsub_imm_fits(off, &imm12, &sh)) { @@ -478,27 +475,19 @@ static u32 aa_ldr_uimm(u32 size, u32 rt, u32 rn, u32 byte_off) { return aa_ldr_uimm_v(size, 0, rt, rn, byte_off); } -static __attribute__((unused)) u32 aa_str_uimm(u32 size, u32 rt, u32 rn, - u32 byte_off) { +static u32 aa_str_uimm(u32 size, u32 rt, u32 rn, u32 byte_off) { return aa_str_uimm_v(size, 0, rt, rn, byte_off); } -static __attribute__((unused)) u32 aa_ldst_regoff_v(u32 size, u32 v, u32 load, - u32 rt, u32 rn, u32 rm, - u32 scaled) { +static u32 aa_ldst_regoff_v(u32 size, u32 v, u32 load, u32 rt, u32 rn, u32 rm, + u32 scaled) { return ((size & 3u) << 30) | 0x38200800u | ((v & 1u) << 26) | ((load ? AA64_LDST_OPC_LDR : AA64_LDST_OPC_STR) << 22) | ((rm & 0x1fu) << 16) | (3u << 13) | ((scaled & 1u) << 12) | ((rn & 0x1fu) << 5) | (rt & 0x1fu); } -static __attribute__((unused)) u32 aa_ldr_lit64(u32 rt, u32 imm19) { - return 0x58000000u | ((imm19 & 0x7ffffu) << 5) | (rt & 0x1fu); -} - -static __attribute__((unused)) u32 aa_mrs_tpidr_el0(u32 rt) { - return 0xd53bd040u | (rt & 0x1fu); -} +static u32 aa_mrs_tpidr_el0(u32 rt) { return 0xd53bd040u | (rt & 0x1fu); } /* The scalar-FP / bit-packing encoders below delegate to the single-source * isa.h encoders so encode/decode stay in lockstep. ftype 0=single, 1=double; @@ -575,7 +564,8 @@ static u32 aa_rbit(u32 sf, u32 rd, u32 rn) { } /* REV reverses all bytes of the operand, so the 32-bit form is REV(32) and the - * 64-bit form is REV(64) — the opcode2 follows sf rather than being constant. */ + * 64-bit form is REV(64) — the opcode2 follows sf rather than being constant. + */ static u32 aa_rev(u32 sf, u32 rd, u32 rn) { return aa64_dp1(sf, sf ? AA64_DP1_REV64 : AA64_DP1_REV32, rd, rn); } @@ -584,34 +574,33 @@ static u32 aa_sbfm(u32 sf, u32 rd, u32 rn, u32 immr, u32 imms) { return aa64_bitfield(sf, /*SBFM*/ 0u, immr, imms, rd, rn); } -static __attribute__((unused)) u32 aa_ubfm(u32 sf, u32 rd, u32 rn, u32 immr, - u32 imms) { +static u32 aa_ubfm(u32 sf, u32 rd, u32 rn, u32 immr, u32 imms) { return aa64_bitfield(sf, /*UBFM*/ 2u, immr, imms, rd, rn); } -static __attribute__((unused)) u32 aa_ldaxr(u32 size, u32 rt, u32 rn) { +static u32 aa_ldaxr(u32 size, u32 rt, u32 rn) { return (size << 30) | 0x085ffc00u | ((rn & 0x1fu) << 5) | (rt & 0x1fu); } -static __attribute__((unused)) u32 aa_ldxr(u32 size, u32 rt, u32 rn) { +static u32 aa_ldxr(u32 size, u32 rt, u32 rn) { return (size << 30) | 0x085f7c00u | ((rn & 0x1fu) << 5) | (rt & 0x1fu); } -static __attribute__((unused)) u32 aa_stlxr(u32 size, u32 rs, u32 rt, u32 rn) { +static u32 aa_stlxr(u32 size, u32 rs, u32 rt, u32 rn) { return (size << 30) | 0x0800fc00u | ((rs & 0x1fu) << 16) | ((rn & 0x1fu) << 5) | (rt & 0x1fu); } -static __attribute__((unused)) u32 aa_stxr(u32 size, u32 rs, u32 rt, u32 rn) { +static u32 aa_stxr(u32 size, u32 rs, u32 rt, u32 rn) { return (size << 30) | 0x08007c00u | ((rs & 0x1fu) << 16) | ((rn & 0x1fu) << 5) | (rt & 0x1fu); } -static __attribute__((unused)) u32 aa_ldar(u32 size, u32 rt, u32 rn) { +static u32 aa_ldar(u32 size, u32 rt, u32 rn) { return (size << 30) | 0x08dffc00u | ((rn & 0x1fu) << 5) | (rt & 0x1fu); } -static __attribute__((unused)) u32 aa_stlr(u32 size, u32 rt, u32 rn) { +static u32 aa_stlr(u32 size, u32 rt, u32 rn) { return (size << 30) | 0x089ffc00u | ((rn & 0x1fu) << 5) | (rt & 0x1fu); } @@ -1584,8 +1573,8 @@ static void aa_apply_patches(AANativeTarget* a, const AAFrameLayout* L) { static void aa_func_end(NativeTarget* t) { AANativeTarget* a = aa_of(t); MCEmitter* mc = t->mc; - AAFrameLayout L = - aa_build_layout(a->frame.cum_off, a->frame.max_outgoing, a->top_home_bytes); + AAFrameLayout L = aa_build_layout(a->frame.cum_off, a->frame.max_outgoing, + a->top_home_bytes); /* known_frame (optimizer): prologue, allocas, and tail epilogues were emitted * final and slim eligibility was settled in aa_func_begin_known_frame — there * is nothing to patch. Single-pass (NDT): a worst-case prologue region was @@ -1758,7 +1747,8 @@ static void aa_func_begin_known_frame(NativeTarget* t, const CGFuncDesc* fd, * bottom-record (saved pair at the very bottom) leave room for. (See * aa_func_end for the single-pass path, which never takes any slim form.) */ a->slim_prologue = a->frame.ncallee_saves == 0 && !a->frame.has_alloca && - L.slot_bytes == 0 && L.out_stack == 0 && !a->top_home_bytes; + L.slot_bytes == 0 && L.out_stack == 0 && + !a->top_home_bytes; a->fp_at_bottom = !a->slim_prologue && !a->frame.has_alloca && L.out_stack == 0 && L.frame_size <= 504u && !a->top_home_bytes; @@ -2659,8 +2649,7 @@ static u32 aa_part_stack_align_min(u32 min_align, const ABIArgPart* part) { return al; } -static u32 aa_part_stack_align(const ABIFuncInfo* abi, - const ABIArgPart* part) { +static u32 aa_part_stack_align(const ABIFuncInfo* abi, const ABIArgPart* part) { return aa_part_stack_align_min(aa_stack_arg_min_align(abi), part); } @@ -2669,8 +2658,7 @@ static u32 aa_part_vararg_stack_align(const ABIFuncInfo* abi, return aa_part_stack_align_min(aa_vararg_stack_arg_min_align(abi), part); } -static u32 aa_part_stack_size(const ABIFuncInfo* abi, - const ABIArgPart* part) { +static u32 aa_part_stack_size(const ABIFuncInfo* abi, const ABIArgPart* part) { return align_up_u32(part->size ? part->size : 8u, aa_part_stack_align(abi, part)); } @@ -2981,8 +2969,8 @@ static void aa_emit_tail_site(NativeTarget* t, NativeLoc callee) { /* Frame is final: emit the tail epilogue (callee restores + frame restore + * branch) directly, exactly the words aa_apply_patches would patch in but * without the reserved NOP padding. */ - AAFrameLayout L = - aa_build_layout(a->frame.cum_off, a->frame.max_outgoing, a->top_home_bytes); + AAFrameLayout L = aa_build_layout(a->frame.cum_off, a->frame.max_outgoing, + a->top_home_bytes); u32 words[AA_TAIL_WORDS]; u32 n = 0; aa_words_callee_restores(a, words, AA_TAIL_WORDS, &n); @@ -3039,8 +3027,8 @@ static void aa_emit_call(NativeTarget* t, const NativeCallPlan* plan) { } static void aa_plan_ret(NativeTarget* t, const CGFuncDesc* fd, - const NativeLoc* value, - NativeCallPlanRet** out_rets, u32* out_nrets) { + const NativeLoc* value, NativeCallPlanRet** out_rets, + u32* out_nrets) { const ABIFuncInfo* abi = abi_cg_func_info(t->c->abi, fd->fn_type); NativeCallPlanRet* rets = NULL; u32 nr = 0; @@ -3636,7 +3624,8 @@ static void aa_intrinsic(NativeTarget* t, IntrinKind kind, /* Walk the AAPCS64 frame-record chain. Every kit prologue stores * {x29, x30} and anchors x29 at the record: [x29] = caller's x29, * [x29 + 8] = saved x30 (this frame's return address). The level is a - * compile-time constant, so the walk unrolls to `level` dependent loads. */ + * compile-time constant, so the walk unrolls to `level` dependent loads. + */ if (ndst == 1u) { u32 level = (narg >= 1u && args[0].kind == NATIVE_LOC_IMM) ? (u32)args[0].v.imm @@ -3670,8 +3659,7 @@ static int aa_machine_op_clobbers(NativeTarget* t, const NativeMachineOp* op, * the descriptor-model clobbers so a value live across a TLS access is not * left in one of these registers. */ if (!obj_format_tls_via_descriptor(t->c)) return 0; - mask[NATIVE_REG_INT] = - (1u << 0) | (1u << 16) | (1u << 17) | (1u << AA_LR); + mask[NATIVE_REG_INT] = (1u << 0) | (1u << 16) | (1u << 17) | (1u << AA_LR); return 1; } if ((NativeMachineOpKind)op->kind != NATIVE_MOP_INTRINSIC || @@ -4556,9 +4544,8 @@ static void aa_asm_clobber_masks(Compiler* c, SrcLoc loc, const Sym* clobbers, } AA_UNUSED_FN static Reg aa_asm_alloc_reg(NativeDirectTarget* d, - NativeAllocClass cls, - u32 allowed_mask, u32* used_int, - u32* used_fp) { + NativeAllocClass cls, u32 allowed_mask, + u32* used_int, u32* used_fp) { static const Reg int_pool[] = {0u, 1u, 2u, 3u, 4u, 5u, 6u, 7u, 8u, 11u, 12u, 13u, 14u, 15u}; static const Reg fp_pool[] = {0u, 1u, 2u, 3u, 4u, 5u, 6u, 7u, @@ -5020,8 +5007,8 @@ static void aa_asm_block_native(NativeTarget* t, const char* tmpl, for (u32 i = 0; i < nout; ++i) { KitCgTypeId type = outs[i].type ? outs[i].type : out_locs[i].type; NativeLoc outloc = out_locs[i]; - NativeAsmPinnedLoc pinned = - native_asm_prepare_pinned_loc(t, outs[i].reg, outs[i].str, type, outloc); + NativeAsmPinnedLoc pinned = native_asm_prepare_pinned_loc( + t, outs[i].reg, outs[i].str, type, outloc); if (pinned.has_pin) { if (pinned.pin_status != NATIVE_ASM_REG_PIN_OK) aa_asm_panic_at(c, loc, @@ -5035,7 +5022,8 @@ static void aa_asm_block_native(NativeTarget* t, const char* tmpl, aa_asm_load_loc_to_reg(a, loc, out_locs[i], outloc); } } - aa_asm_bind_native(a, loc, &bound_outs[i], outs[i].str, type, outloc, &ntmp); + aa_asm_bind_native(a, loc, &bound_outs[i], outs[i].str, type, outloc, + &ntmp); } for (u32 i = 0; i < nin; ++i) { const char* body = native_asm_constraint_body(ins[i].str); diff --git a/src/arch/dbg_stubs.c b/src/arch/dbg_stubs.c @@ -3,3 +3,4 @@ const ArchDbgOps aa64_dbg_ops = {0}; const ArchDbgOps x64_dbg_ops = {0}; const ArchDbgOps rv64_dbg_ops = {0}; +const ArchDbgOps rv32_dbg_ops = {0}; diff --git a/src/arch/link_stubs.c b/src/arch/link_stubs.c @@ -3,3 +3,4 @@ const LinkArchDesc link_arch_aa64 = {0}; const LinkArchDesc link_arch_x64 = {0}; const LinkArchDesc link_arch_rv64 = {0}; +const LinkArchDesc link_arch_rv32 = {0}; diff --git a/src/arch/mc.c b/src/arch/mc.c @@ -80,7 +80,7 @@ typedef struct MCLabelInfo { /* ---- CFI buffering (.eh_frame producer) ---- * * Each cfi_startproc opens a new FDE record; the per-arch backend then - * calls cfi_def_cfa / cfi_offset / cfi_restore as the prologue is laid + * calls cfi_def_cfa / cfi_offset as the prologue is laid * down. Each directive snapshots either the current section offset or the * override set by cfi_set_next_pc_offset — which is STICKY until cfi_endproc * (used by backends that emit the whole CFI batch in func_end, after the @@ -88,11 +88,7 @@ typedef struct MCLabelInfo { * .eh_frame section is synthesised at mc_emit_eh_frame() time. */ typedef enum CfiOpKind { CFI_OP_DEF_CFA, - CFI_OP_DEF_CFA_REGISTER, - CFI_OP_DEF_CFA_OFFSET, CFI_OP_OFFSET, - CFI_OP_REL_OFFSET, - CFI_OP_RESTORE, } CfiOpKind; typedef struct CfiDirective { @@ -479,31 +475,11 @@ static void m_cfi_def_cfa(MCEmitter* m, u32 r, i32 o) { if (mc->cur_fde < 0) return; fde_push(mc, CFI_OP_DEF_CFA, r, o); } -static void m_cfi_def_cfa_offset(MCEmitter* m, i32 o) { - MCImpl* mc = impl_of(m); - if (mc->cur_fde < 0) return; - fde_push(mc, CFI_OP_DEF_CFA_OFFSET, 0, o); -} -static void m_cfi_def_cfa_register(MCEmitter* m, u32 r) { - MCImpl* mc = impl_of(m); - if (mc->cur_fde < 0) return; - fde_push(mc, CFI_OP_DEF_CFA_REGISTER, r, 0); -} static void m_cfi_offset(MCEmitter* m, u32 r, i32 o) { MCImpl* mc = impl_of(m); if (mc->cur_fde < 0) return; fde_push(mc, CFI_OP_OFFSET, r, o); } -static void m_cfi_rel_offset(MCEmitter* m, u32 r, i32 o) { - MCImpl* mc = impl_of(m); - if (mc->cur_fde < 0) return; - fde_push(mc, CFI_OP_REL_OFFSET, r, o); -} -static void m_cfi_restore(MCEmitter* m, u32 r) { - MCImpl* mc = impl_of(m); - if (mc->cur_fde < 0) return; - fde_push(mc, CFI_OP_RESTORE, r, 0); -} static void m_cfi_set_next_pc_offset(MCEmitter* m, u32 pc_offset) { MCImpl* mc = impl_of(m); mc->has_pc_override = 1; @@ -546,11 +522,7 @@ MCEmitter* mc_new(Compiler* c, ObjBuilder* o) { base->cfi_startproc = m_cfi_startproc; base->cfi_endproc = m_cfi_endproc; base->cfi_def_cfa = m_cfi_def_cfa; - base->cfi_def_cfa_offset = m_cfi_def_cfa_offset; - base->cfi_def_cfa_register = m_cfi_def_cfa_register; base->cfi_offset = m_cfi_offset; - base->cfi_rel_offset = m_cfi_rel_offset; - base->cfi_restore = m_cfi_restore; base->cfi_set_next_pc_offset = m_cfi_set_next_pc_offset; base->destroy = m_destroy; @@ -689,14 +661,6 @@ static void encode_cfi_directive(Buf* prog, const CfiDirective* d, u32* cur_loc, buf_uleb(prog, d->reg); buf_uleb(prog, (u64)(d->imm < 0 ? 0 : d->imm)); break; - case CFI_OP_DEF_CFA_OFFSET: - buf_u8(prog, DW_CFA_def_cfa_offset); - buf_uleb(prog, (u64)(d->imm < 0 ? 0 : d->imm)); - break; - case CFI_OP_DEF_CFA_REGISTER: - buf_u8(prog, DW_CFA_def_cfa_register); - buf_uleb(prog, d->reg); - break; case CFI_OP_OFFSET: { i64 fac; if (data_align == 0) @@ -712,24 +676,6 @@ static void encode_cfi_directive(Buf* prog, const CfiDirective* d, u32* cur_loc, buf_sleb(prog, fac); } } break; - case CFI_OP_REL_OFFSET: { - i64 fac; - if (data_align == 0) - fac = d->imm; - else - fac = (i64)d->imm / (i64)data_align; - buf_u8(prog, DW_CFA_offset_extended_sf); - buf_uleb(prog, d->reg); - buf_sleb(prog, fac); - } break; - case CFI_OP_RESTORE: - if (d->reg < 0x40u) { - buf_u8(prog, DW_CFA_restore | (u8)d->reg); - } else { - buf_u8(prog, DW_CFA_restore_extended); - buf_uleb(prog, d->reg); - } - break; } } diff --git a/src/arch/mc.h b/src/arch/mc.h @@ -108,11 +108,7 @@ struct MCEmitter { void (*cfi_startproc)(MCEmitter*); void (*cfi_endproc)(MCEmitter*); void (*cfi_def_cfa)(MCEmitter*, u32 reg, i32 ofs); - void (*cfi_def_cfa_offset)(MCEmitter*, i32 ofs); - void (*cfi_def_cfa_register)(MCEmitter*, u32 reg); void (*cfi_offset)(MCEmitter*, u32 reg, i32 ofs); - void (*cfi_rel_offset)(MCEmitter*, u32 reg, i32 ofs); - void (*cfi_restore)(MCEmitter*, u32 reg); /* Override the PC offset used by the *next* cfi_* directive (one-shot). * Backends that patch the prologue in func_end (so the live pc has * moved past the prologue) call this with the post-prologue offset diff --git a/src/arch/native_target.h b/src/arch/native_target.h @@ -403,17 +403,6 @@ struct NativeTarget { * back to NativeAllocClassInfo.{caller,callee}_saved_mask. */ u32 (*caller_saved_mask)(NativeTarget*, NativeAllocClass); u32 (*callee_saved_mask)(NativeTarget*, NativeAllocClass); - /* Optional. When set, the optimizer emit path calls this once — after - * func_begin, reserve_callee_saves, and frame-slot mapping, but before the - * body — to emit a minimal, exact-size prologue in place (no reserved NOP - * region). Frame-size immediates are still patched in func_end, since the - * final frame size isn't known until body emission allocates its temporaries. - * Backends that leave this NULL fall back to the single-pass - * reserve-and-patch prologue used by NativeDirectTarget. Gated by - * `emit_minimal_prologue`, which the optimizer emit path sets before - * func_begin so func_begin can skip the reserved region. */ - void (*emit_prologue)(NativeTarget*); - u8 emit_minimal_prologue; /* Bytes of stack-passed arguments the fixed parameters of this function * signature use (the part beyond the register arg pools). Sets *variadic to * whether the signature is variadic and *nparams to the fixed parameter @@ -547,8 +536,8 @@ struct NativeTarget { void (*destroy)(NativeTarget*); }; -static inline const NativeAllocClassInfo* -native_target_class_info(const NativeTarget* t, NativeAllocClass cls) { +static inline const NativeAllocClassInfo* native_target_class_info( + const NativeTarget* t, NativeAllocClass cls) { if (!t || !t->regs) return NULL; for (u32 i = 0; i < t->regs->nclasses; ++i) { const NativeAllocClassInfo* ci = &t->regs->classes[i]; @@ -656,10 +645,18 @@ static inline NativeAllocClass native_class_for_type_fp_le8(NativeTarget* t, KitCgTypeId type) { u32 flen; switch (t->c->target.float_abi) { - case KIT_FLOAT_ABI_SINGLE: flen = 4u; break; - case KIT_FLOAT_ABI_DOUBLE: flen = 8u; break; - case KIT_FLOAT_ABI_SOFT: flen = 0u; break; - default: flen = t->c->target.ptr_size; break; /* DEFAULT: historical */ + case KIT_FLOAT_ABI_SINGLE: + flen = 4u; + break; + case KIT_FLOAT_ABI_DOUBLE: + flen = 8u; + break; + case KIT_FLOAT_ABI_SOFT: + flen = 0u; + break; + default: + flen = t->c->target.ptr_size; + break; /* DEFAULT: historical */ } if (type && flen && cg_type_is_float(t->c, type) && cg_type_size(t->c, type) <= flen) diff --git a/src/arch/riscv/native.c b/src/arch/riscv/native.c @@ -112,7 +112,8 @@ static void rv_emit_li32(const RiscvVariant* v, MCEmitter* mc, u32 rd, /* ADDIW is RV64-only; on RV32 the value fits 32 bits so plain ADDI is * exact (and identical to ADDIW's low result on RV64). */ if (lo) - rv64_emit32(mc, v->has_w_forms ? rv_addiw(rd, rd, lo) : rv_addi(rd, rd, lo)); + rv64_emit32(mc, + v->has_w_forms ? rv_addiw(rd, rd, lo) : rv_addi(rd, rd, lo)); } } @@ -124,7 +125,8 @@ static i32 sext12(u32 v) { /* Builds a full XLEN-wide value. The recursion / slli-12 chain assembles bits * above 32 and is only ever reached on rv64 (a single rv32 register cannot hold * a value wider than 32 bits — the cg layer legalizes those into pairs). */ -static void rv_emit_li64(const RiscvVariant* v, MCEmitter* mc, u32 rd, u64 imm) { +static void rv_emit_li64(const RiscvVariant* v, MCEmitter* mc, u32 rd, + u64 imm) { if (fits_i32((i64)imm)) { rv_emit_li32(v, mc, rd, (i32)(i64)imm); return; @@ -958,8 +960,8 @@ static void rv_binop(NativeTarget* t, BinOp op, NativeLoc dst, NativeLoc aop, } case BO_IADD: if (b_imm) { - rv64_emit32( - mc, w ? rv_addiw(rd, ra, (i32)imm) : rv_addi(rd, ra, (i32)imm)); + rv64_emit32(mc, + w ? rv_addiw(rd, ra, (i32)imm) : rv_addi(rd, ra, (i32)imm)); } else { rv64_emit32(mc, w ? rv_addw(rd, ra, rb) : rv_add(rd, ra, rb)); } @@ -1247,11 +1249,11 @@ static void rv_convert(NativeTarget* t, ConvKind op, NativeLoc dst, return; case CV_ITOF_S: if (native_type_size(t, dst.type) == 8u) - rv64_emit32(mc, il && src_sz == 8u ? rv_fcvt_d_l(rd, rs) - : rv_fcvt_d_w(rd, rs)); + rv64_emit32( + mc, il && src_sz == 8u ? rv_fcvt_d_l(rd, rs) : rv_fcvt_d_w(rd, rs)); else - rv64_emit32(mc, il && src_sz == 8u ? rv_fcvt_s_l(rd, rs) - : rv_fcvt_s_w(rd, rs)); + rv64_emit32( + mc, il && src_sz == 8u ? rv_fcvt_s_l(rd, rs) : rv_fcvt_s_w(rd, rs)); return; case CV_ITOF_U: if (native_type_size(t, dst.type) == 8u) @@ -1263,11 +1265,11 @@ static void rv_convert(NativeTarget* t, ConvKind op, NativeLoc dst, return; case CV_FTOI_S: if (src_sz == 8u) - rv64_emit32(mc, il && dst_sz == 8u ? rv_fcvt_l_d(rd, rs) - : rv_fcvt_w_d(rd, rs)); + rv64_emit32( + mc, il && dst_sz == 8u ? rv_fcvt_l_d(rd, rs) : rv_fcvt_w_d(rd, rs)); else - rv64_emit32(mc, il && dst_sz == 8u ? rv_fcvt_l_s(rd, rs) - : rv_fcvt_w_s(rd, rs)); + rv64_emit32( + mc, il && dst_sz == 8u ? rv_fcvt_l_s(rd, rs) : rv_fcvt_w_s(rd, rs)); return; case CV_FTOI_U: if (src_sz == 8u) @@ -1533,8 +1535,8 @@ static i32 rv_save_off(RvNativeTarget* a, u32 n_int, u32 idx) { static void rv_load_s0(const RiscvVariant* v, MCEmitter* mc, int fp, u32 reg, i32 off) { if (fits_i12(off)) { - rv64_emit32(mc, fp ? rv_fld(reg, RV_S0, off) - : rv_ld_ptr(v, reg, RV_S0, off)); + rv64_emit32(mc, + fp ? rv_fld(reg, RV_S0, off) : rv_ld_ptr(v, reg, RV_S0, off)); return; } rv_emit_load_imm(v, mc, 1, RV_TMP0, (i64)off); @@ -1548,13 +1550,14 @@ static u32 rv_build_prologue(RvNativeTarget* a, u32* words, u32 cap, const u32* int_regs, u32 n_int, const u32* fp_regs, u32 n_fp) { const RiscvVariant* v = a->variant; - u32 ptr = v->ptr_bytes; /* saved-pair / int-save stride */ - u32 gp_slot = v->gp_slot_bytes; /* vararg GP-slot stride */ - u32 fsz = v->frame_save_size; /* saved ra+s0 pair base offset */ + u32 ptr = v->ptr_bytes; /* saved-pair / int-save stride */ + u32 gp_slot = v->gp_slot_bytes; /* vararg GP-slot stride */ + u32 fsz = v->frame_save_size; /* saved ra+s0 pair base offset */ u32 wi = 0; /* lui+ADD{I,IW} materializes a 32-bit constant in TMP0; ADDIW is RV64-only so * use plain ADDI on rv32 (the value already fits 32 bits). */ -#define ADDI_LO(rd, lo) (v->has_w_forms ? rv_addiw((rd), (rd), (lo)) : rv_addi((rd), (rd), (lo))) +#define ADDI_LO(rd, lo) \ + (v->has_w_forms ? rv_addiw((rd), (rd), (lo)) : rv_addi((rd), (rd), (lo))) #define PUSH(w) \ do { \ if (wi >= cap) rv_panic(a, "prologue placeholder overflow"); \ @@ -1653,7 +1656,7 @@ static void rv_func_end(NativeTarget* t) { MCEmitter* mc = t->mc; ObjBuilder* obj = t->obj; ObjSecId sec = a->func->text_section_id; - u32 int_regs[16], fp_regs[16]; + u32 int_regs[RV_MAX_CALLEE_SAVES], fp_regs[RV_MAX_CALLEE_SAVES]; u32 n_int = rv_collect_int_saves(a, int_regs); u32 n_fp = rv_collect_fp_saves(a, fp_regs); u32 frame_size = rv_frame_size(a); @@ -1898,7 +1901,8 @@ static const ABIArgInfo* rv_param_abi(NativeTarget* t, const ABIFuncInfo* abi, * register pair, matching the named-arg classifier (abi_rv64.c). Synthesize * one INT part per GPR-word so the per-part marshaller fills both registers * (low word in the lower-numbered reg) instead of dropping the high half into - * a single register. FP-class args (hardware-float, size<=GPR) stay single. */ + * a single register. FP-class args (hardware-float, size<=GPR) stay single. + */ if (!is_fp && sz > gpr) { u32 nparts = (sz + gpr - 1u) / gpr, p; ABIArgPart* parts = arena_zarray(t->c->tu, ABIArgPart, nparts); @@ -2178,8 +2182,8 @@ static void rv_bind_native_param(NativeTarget* t, const CGParamDesc* p, Reg tmp = (cls == NATIVE_REG_FP) ? RV_FTMP0 : RV_TMP0; NativeAddr sa; src = native_loc_reg(p->type, cls, tmp); - a->next_param_stack = align_up_u32( - a->next_param_stack, rv_part_stack_align(a->variant, part)); + a->next_param_stack = align_up_u32(a->next_param_stack, + rv_part_stack_align(a->variant, part)); memset(&sa, 0, sizeof sa); sa.base_kind = NATIVE_ADDR_BASE_REG; sa.base.reg = RV_S0; @@ -2435,8 +2439,8 @@ static void rv_emit_call(NativeTarget* t, const NativeCallPlan* plan) { } static void rv_plan_ret(NativeTarget* t, const CGFuncDesc* fd, - const NativeLoc* value, - NativeCallPlanRet** out_rets, u32* out_nrets) { + const NativeLoc* value, NativeCallPlanRet** out_rets, + u32* out_nrets) { RvNativeTarget* a = rv_of(t); const ABIFuncInfo* abi = abi_cg_func_info(t->c->abi, fd->fn_type); NativeCallPlanRet* rets = NULL; @@ -2570,8 +2574,8 @@ static void rv_bitfield_load(NativeTarget* t, NativeLoc dst, NativeAddr ra, i32 off; u32 lsb = bf.bit_offset; u32 width = bf.bit_width ? bf.bit_width : 1u; - /* Shift left so the field's MSB lands at the register top (XLEN-1), then shift - * right to sign/zero extend it down. Shifts are XLEN-wide. */ + /* Shift left so the field's MSB lands at the register top (XLEN-1), then + * shift right to sign/zero extend it down. Shifts are XLEN-wide. */ u32 sh_left = v->xlen - (lsb + width); u32 sh_right = v->xlen - width; ra.offset += (i32)bf.storage_offset; @@ -2649,10 +2653,10 @@ static void rv_atomic_load(NativeTarget* t, NativeLoc dst, NativeAddr addr, rv64_emit32(mc, sf ? rv_lr_d(loc_reg(dst), base, 1, 0) : rv_lr_w(loc_reg(dst), base, 1, 0)); } else { - rv64_emit32(mc, enc_int_load(a->variant, - mem.size ? mem.size - : native_type_size(t, dst.type), - 0, loc_reg(dst), base, 0)); + rv64_emit32( + mc, enc_int_load(a->variant, + mem.size ? mem.size : native_type_size(t, dst.type), 0, + loc_reg(dst), base, 0)); } } @@ -2674,7 +2678,8 @@ static void rv_atomic_rmw(NativeTarget* t, KitCgAtomicOp op, NativeLoc dst, RvNativeTarget* a = rv_of(t); const RiscvVariant* v = a->variant; MCEmitter* mc = t->mc; - u32 sf = (mem.size ? mem.size : native_type_size(t, dst.type)) == 8u ? 1u : 0u; + u32 sf = + (mem.size ? mem.size : native_type_size(t, dst.type)) == 8u ? 1u : 0u; /* W-form add/sub apply only to a 32-bit value on rv64; on rv32 the base ops * are the 32-bit ops. */ int w = !sf && v->has_w_forms; @@ -2782,9 +2787,9 @@ static void rv_va_start_core(RvNativeTarget* a, NativeAddr ap) { rv_panic(a, "unsupported va_list layout"); if (!a->is_variadic) rv_panic(a, "va_start: function not variadic"); /* *ap = s0 + frame_save + next_param_int*gp_slot (skip named-int slots). */ - rv64_emit32(mc, rv_addi(RV_TMP1, RV_S0, - (i32)v->frame_save_size + - (i32)(a->next_param_int * slot))); + rv64_emit32( + mc, rv_addi(RV_TMP1, RV_S0, + (i32)v->frame_save_size + (i32)(a->next_param_int * slot))); rv_emit_mem(a, 0, native_loc_reg(i64t, NATIVE_REG_INT, RV_TMP1), ap, native_mem_for_type(t, i64t, v->ptr_bytes)); } @@ -2953,8 +2958,8 @@ static void rv_intrin_copy(const RiscvVariant* v, MCEmitter* mc, u32 dr, u32 sr, i += 8u; } while (i + 4u <= n) { - rv64_emit32(mc, wide ? rv_lwu(RV_TMP3, sr, (i32)i) - : rv_lw(RV_TMP3, sr, (i32)i)); + rv64_emit32( + mc, wide ? rv_lwu(RV_TMP3, sr, (i32)i) : rv_lw(RV_TMP3, sr, (i32)i)); rv64_emit32(mc, rv_sw(RV_TMP3, dr, (i32)i)); i += 4u; } @@ -2977,8 +2982,8 @@ static void rv_intrin_copy(const RiscvVariant* v, MCEmitter* mc, u32 dr, u32 sr, } while (i >= 4u) { i -= 4u; - rv64_emit32(mc, wide ? rv_lwu(RV_TMP3, sr, (i32)i) - : rv_lw(RV_TMP3, sr, (i32)i)); + rv64_emit32( + mc, wide ? rv_lwu(RV_TMP3, sr, (i32)i) : rv_lw(RV_TMP3, sr, (i32)i)); rv64_emit32(mc, rv_sw(RV_TMP3, dr, (i32)i)); } while (i >= 2u) { @@ -3035,7 +3040,8 @@ static void rv_intrinsic(NativeTarget* t, IntrinKind kind, } rv_emit_reg_arg_moves(t, moves, narg); rv64_emit32(mc, rv_ecall()); - rv_move(t, dsts[0], native_loc_reg(dsts[0].type, NATIVE_REG_INT, RV_A0)); + rv_move(t, dsts[0], + native_loc_reg(dsts[0].type, NATIVE_REG_INT, RV_A0)); } return; case INTRIN_BSWAP: { @@ -3057,13 +3063,16 @@ static void rv_intrinsic(NativeTarget* t, IntrinKind kind, u32 rd = loc_reg(dsts[0]), rs = loc_reg(args[0]); /* SRLIW is RV64-only; on rv32 SRLI on a 32-bit reg is equivalent. */ int w = v->has_w_forms; - rv64_emit32(mc, w ? rv_srliw(RV_TMP1, rs, 24) : rv_srli(RV_TMP1, rs, 24)); + rv64_emit32(mc, + w ? rv_srliw(RV_TMP1, rs, 24) : rv_srli(RV_TMP1, rs, 24)); rv64_emit32(mc, rv_andi(RV_TMP1, RV_TMP1, 0xff)); - rv64_emit32(mc, w ? rv_srliw(RV_TMP2, rs, 16) : rv_srli(RV_TMP2, rs, 16)); + rv64_emit32(mc, + w ? rv_srliw(RV_TMP2, rs, 16) : rv_srli(RV_TMP2, rs, 16)); rv64_emit32(mc, rv_andi(RV_TMP2, RV_TMP2, 0xff)); rv64_emit32(mc, rv_slli(RV_TMP2, RV_TMP2, 8)); rv64_emit32(mc, rv_or(RV_TMP1, RV_TMP1, RV_TMP2)); - rv64_emit32(mc, w ? rv_srliw(RV_TMP2, rs, 8) : rv_srli(RV_TMP2, rs, 8)); + rv64_emit32(mc, + w ? rv_srliw(RV_TMP2, rs, 8) : rv_srli(RV_TMP2, rs, 8)); rv64_emit32(mc, rv_andi(RV_TMP2, RV_TMP2, 0xff)); rv64_emit32(mc, rv_slli(RV_TMP2, RV_TMP2, 16)); rv64_emit32(mc, rv_or(RV_TMP1, RV_TMP1, RV_TMP2)); @@ -3210,8 +3219,8 @@ static void rv_intrinsic(NativeTarget* t, IntrinKind kind, } } else { rv64_emit32(mc, rv_sltu(rovf, ra, rb)); - rv64_emit32(mc, single ? rv_sub(RV_TMP2, ra, rb) - : rv_subw(RV_TMP2, ra, rb)); + rv64_emit32( + mc, single ? rv_sub(RV_TMP2, ra, rb) : rv_subw(RV_TMP2, ra, rb)); } rv64_emit32(mc, rv_addi(rd, RV_TMP2, 0)); return; @@ -3543,7 +3552,8 @@ typedef struct RvAsmSavedClobber { /* A clobber save slot is register-width: ptr_bytes for an integer reg (4 on * rv32, 8 on rv64) but always 8 for an FP reg (fsd, even on rv32d). */ -static u32 rv_asm_save_bytes(const RvNativeTarget* a, const RvAsmSavedClobber* s) { +static u32 rv_asm_save_bytes(const RvNativeTarget* a, + const RvAsmSavedClobber* s) { return s->cls == NATIVE_REG_FP ? 8u : a->variant->ptr_bytes; } static void rv_asm_save_one(RvNativeTarget* a, RvAsmSavedClobber* s) { @@ -3700,22 +3710,22 @@ static void rv_asm_load_loc_to_reg(RvNativeTarget* a, SrcLoc loc, NativeLoc src, t->load_imm(t, dst, src.v.imm); return; } - rv_emit_mem(a, 1, dst, rv_asm_loc_to_addr(a, loc, src), - native_mem_for_type(t, dst.type, - rv_asm_reg_mem_size(a, cls, dst.type))); + rv_emit_mem( + a, 1, dst, rv_asm_loc_to_addr(a, loc, src), + native_mem_for_type(t, dst.type, rv_asm_reg_mem_size(a, cls, dst.type))); } -static void rv_asm_store_reg_to_loc(RvNativeTarget* a, SrcLoc loc, NativeLoc dst, - NativeLoc src) { +static void rv_asm_store_reg_to_loc(RvNativeTarget* a, SrcLoc loc, + NativeLoc dst, NativeLoc src) { NativeTarget* t = &a->base; NativeAllocClass cls = (NativeAllocClass)src.cls; if (dst.kind == NATIVE_LOC_REG) { if (dst.v.reg != src.v.reg || dst.cls != src.cls) t->move(t, dst, src); return; } - rv_emit_mem(a, 0, src, rv_asm_loc_to_addr(a, loc, dst), - native_mem_for_type(t, src.type, - rv_asm_reg_mem_size(a, cls, src.type))); + rv_emit_mem( + a, 0, src, rv_asm_loc_to_addr(a, loc, dst), + native_mem_for_type(t, src.type, rv_asm_reg_mem_size(a, cls, src.type))); } static void rv_asm_bind_native(RvNativeTarget* a, SrcLoc loc, Operand* out, @@ -3767,8 +3777,8 @@ static void rv_asm_block_native(NativeTarget* t, const char* tmpl, KitCgTypeId type = outs[i].type ? outs[i].type : out_locs[i].type; NativeLoc outloc = out_locs[i]; NativeAsmConstraintInfo info; - NativeAsmPinnedLoc pinned = - native_asm_prepare_pinned_loc(t, outs[i].reg, outs[i].str, type, outloc); + NativeAsmPinnedLoc pinned = native_asm_prepare_pinned_loc( + t, outs[i].reg, outs[i].str, type, outloc); if (pinned.has_pin) { if (pinned.pin_status != NATIVE_ASM_REG_PIN_OK) rv_asm_panic_at(c, loc, @@ -3789,7 +3799,8 @@ static void rv_asm_block_native(NativeTarget* t, const char* tmpl, if (outs[i].dir == KIT_CG_ASM_INOUT) rv_asm_load_loc_to_reg(a, loc, out_locs[i], outloc); } - rv_asm_bind_native(a, loc, &bound_outs[i], outs[i].str, type, outloc, &ntmp); + rv_asm_bind_native(a, loc, &bound_outs[i], outs[i].str, type, outloc, + &ntmp); } for (i = 0; i < nin; ++i) { const char* body = native_asm_constraint_body(ins[i].str); @@ -4046,8 +4057,8 @@ static void rv_va_arg_(NativeDirectTarget* d, Operand dst, Operand ap_addr, /* Float-ABI-aware class: a soft (or wider-than-flen) float is INT-class so * the va_arg fetch never lands a double in an FP register on rv32. */ cls = native_class_for_type_fp_le8(d->native, type); - NativeLoc res = native_loc_reg(type, cls, - cls == NATIVE_REG_FP ? RV_FTMP0 : RV_TMP0); + NativeLoc res = + native_loc_reg(type, cls, cls == NATIVE_REG_FP ? RV_FTMP0 : RV_TMP0); NativeAddr dst_addr; rv_va_arg_core(a, res, rv_direct_va_base(d, ap_addr, RV_TMP3), type); /* Store the fetched value back into the semantic destination. */ diff --git a/src/arch/wasm/emit.c b/src/arch/wasm/emit.c @@ -1095,7 +1095,8 @@ void wasm_call(CGTarget* tg, const CGCallDesc* d) { if (is_tail) { /* Realizability is decided by CG via wasm_ir_tail_call_unrealizable_reason * (target.c) before CG_CALL_TAIL is set: variadic tails are rejected there, - * and sret tails forward the incoming sret pointer (handled in WIR emit). */ + * and sret tails forward the incoming sret pointer (handled in WIR emit). + */ ensure_module(t); t->module->features |= WASM_FEATURE_TAIL_CALLS; } @@ -3236,139 +3237,6 @@ static void emit_intrinsic(WTarget* t, const WIR* w) { } } -static void linearize_range(WTarget* t, LoweringState* L, u32 start, u32 end); - -#if 0 /* Switch-island matcher: replaced by wasm_structurize's \ - * unroll_switch_islands, which reorders the WIR in-place so case \ - * labels become forward refs handled by the general structurer. */ -static int label_in_list(Label l, const Label* labels, u32 nlabels) { - for (u32 i = 0; i < nlabels; ++i) { - if (labels[i] == l) return 1; - } - return 0; -} - -static int try_linearize_switch_island(WTarget* t, LoweringState* L, u32* ip) { - WIR* jump = &t->wir[*ip]; - WLabel* dispatch = lookup_label(t, jump->labels[0]); - u32 dispatch_i; - u32 switch_i = UINT32_MAX; - WIR* sw; - Label target_labels[64]; - Label body_labels[64]; - Label end_labels[64]; - u32 ntarget_labels = 0; - u32 nbody_labels = 0; - u32 nend_labels = 0; - u32 end_remap_mark; - u32 case_remap_mark; - Label synthetic_end = LABEL_NONE; - - if (!dispatch || dispatch->kind != WLBL_FORWARD || !dispatch->placed || - dispatch->wir_index <= *ip) - return 0; - dispatch_i = dispatch->wir_index; - if (dispatch_i >= t->nwir || t->wir[dispatch_i].op != WIR_LABEL) - return 0; - for (u32 i = dispatch_i + 1u; i < t->nwir; ++i) { - if (t->wir[i].op == WIR_SWITCH) { - switch_i = i; - break; - } - } - if (switch_i == UINT32_MAX) return 0; - - sw = &t->wir[switch_i]; - for (u32 i = 0; i < sw->switch_ncases; ++i) { - Label l = sw->switch_cases[i].label; - if (!label_in_list(l, target_labels, ntarget_labels)) { - if (ntarget_labels >= 64u) wfail(t, "wasm: too many switch targets"); - target_labels[ntarget_labels++] = l; - } - } - if (sw->labels[0] != LABEL_NONE && - !label_in_list(sw->labels[0], target_labels, ntarget_labels)) { - if (ntarget_labels >= 64u) wfail(t, "wasm: too many switch targets"); - target_labels[ntarget_labels++] = sw->labels[0]; - } - - for (u32 i = *ip + 1u; i < dispatch_i; ++i) { - if (t->wir[i].op != WIR_LABEL) continue; - Label l = t->wir[i].labels[0]; - if (label_in_list(l, target_labels, ntarget_labels) && - !label_in_list(l, body_labels, nbody_labels)) { - if (nbody_labels >= 64u) wfail(t, "wasm: too many switch body labels"); - body_labels[nbody_labels++] = l; - } - } - for (u32 i = *ip + 1u; i < dispatch_i; ++i) { - Label l = LABEL_NONE; - WLabel* lbl; - if (t->wir[i].op == WIR_JUMP || t->wir[i].op == WIR_CMP_BRANCH) { - l = t->wir[i].labels[0]; - } - if (l == LABEL_NONE || label_in_list(l, body_labels, nbody_labels)) - continue; - lbl = lookup_label(t, l); - if (!lbl || lbl->kind != WLBL_FORWARD || !lbl->placed || - lbl->wir_index <= switch_i) - continue; - if (!label_in_list(l, end_labels, nend_labels)) { - if (nend_labels >= 64u) wfail(t, "wasm: too many switch exit labels"); - end_labels[nend_labels++] = l; - } - } - if (nbody_labels == 0) return 0; - - if (!label_in_list(sw->labels[0], body_labels, nbody_labels)) - synthetic_end = sw->labels[0]; - - emit_insn(t, WASM_INSN_BLOCK, 0); - L->cur_depth++; - end_remap_mark = L->nremaps; - if (synthetic_end != LABEL_NONE) - lowering_push_remap(L, synthetic_end, L->cur_depth); - for (u32 i = 0; i < nend_labels; ++i) { - lowering_push_remap(L, end_labels[i], L->cur_depth); - } - - for (u32 ri = nbody_labels; ri > 0; --ri) { - Label l = body_labels[ri - 1u]; - emit_insn(t, WASM_INSN_BLOCK, 0); - L->cur_depth++; - lowering_push_remap(L, l, L->cur_depth); - } - - linearize_range(t, L, dispatch_i + 1u, switch_i); - case_remap_mark = end_remap_mark + nend_labels + - (synthetic_end != LABEL_NONE ? 1u : 0u); - emit_switch_br_table(t, L, sw); - lowering_pop_remaps(L, case_remap_mark); - - for (u32 bi = 0; bi < nbody_labels; ++bi) { - u32 seg_start; - u32 seg_end = dispatch_i; - WLabel* lbl = lookup_label(t, body_labels[bi]); - if (!lbl) wfail(t, "wasm: switch body label disappeared"); - emit_insn(t, WASM_INSN_END, 0); - L->cur_depth--; - seg_start = lbl->wir_index + 1u; - if (bi + 1u < nbody_labels) { - WLabel* next = lookup_label(t, body_labels[bi + 1u]); - if (!next) wfail(t, "wasm: switch body label disappeared"); - seg_end = next->wir_index; - } - linearize_range(t, L, seg_start, seg_end); - } - - emit_insn(t, WASM_INSN_END, 0); - L->cur_depth--; - lowering_pop_remaps(L, end_remap_mark); - *ip = switch_i; - return 1; -} -#endif - static void linearize_range(WTarget* t, LoweringState* L, u32 start, u32 end) { for (u32 i = start; i < end; ++i) { WIR* w = &t->wir[i]; @@ -3437,8 +3305,7 @@ static void linearize_range(WTarget* t, LoweringState* L, u32 start, u32 end) { emit_insn( t, vt == WASM_VAL_I64 ? WASM_INSN_I64_EQZ : WASM_INSN_I32_EQZ, 0); - if (vt == WASM_VAL_I64) - emit_insn(t, WASM_INSN_I64_EXTEND_I32_U, 0); + if (vt == WASM_VAL_I64) emit_insn(t, WASM_INSN_I64_EXTEND_I32_U, 0); break; } } @@ -4081,9 +3948,10 @@ static void linearize_range(WTarget* t, LoweringState* L, u32 start, u32 end) { static void linearize(WTarget* t) { LoweringState L; /* Rewrite WIR so every free label is bound to a synthetic SCOPE_BLOCK - * (forward goto) or SCOPE_LOOP (backward goto). After this, the only - * remaining free labels are switch-island participants, which the - * try_linearize_switch_island fast path inside linearize_range handles. */ + * (forward goto) or SCOPE_LOOP (backward goto). Switch islands are + * pre-reordered into selector + WIR_SWITCH + case bodies, turning their + * case labels into forward refs the same structuring covers. After this, + * br_to_label resolves every jump through scope-bound machinery. */ wasm_structurize(t); memset(&L, 0, sizeof L); L.t = t; diff --git a/src/arch/wasm/internal.h b/src/arch/wasm/internal.h @@ -411,8 +411,9 @@ void wasm_destroy(CGTarget*); * list so every free WIR_LABEL becomes the break/continue of a synthetic * SCOPE_BLOCK / SCOPE_LOOP. Called from emit.c's linearize() before the * WIR walk; after this returns, br_to_label resolves every jump through - * the existing scope-bound machinery. Labels referenced by WIR_SWITCH - * are left untouched and handled by try_linearize_switch_island. */ + * the existing scope-bound machinery. Switch islands are pre-reordered + * (selector + WIR_SWITCH + case bodies) so their case labels become + * forward refs the same synthetic-scope structuring handles. */ void wasm_structurize(WTarget* t); #endif diff --git a/src/arch/x64/asm.c b/src/arch/x64/asm.c @@ -897,17 +897,6 @@ static void parse_alu_rr(X64ParseCtx* p) { emit_movb_rr_operand(p->d, p->mc, dst, src); return; } - if (op == 0x88u) { - /* MOV r/m8, r8 — byte form (preserved from prior asm.c). */ - u8 ob = 0x88; - emit_rex(p->mc, 0, src.reg, 0, dst.reg); - p->mc->emit_bytes(p->mc, &ob, 1); - { - u8 mr = modrm(3u, src.reg, dst.reg); - p->mc->emit_bytes(p->mc, &mr, 1); - } - return; - } /* xor/test/and/... — emit_alu_rr handles the generic shape. */ emit_alu_rr(p->mc, width_to_w(p->width), op, dst.reg, src.reg); return; diff --git a/src/arch/x64/emit.h b/src/arch/x64/emit.h @@ -19,10 +19,6 @@ #define X64_PROLOGUE_BYTES 96u #define X64_PROLOGUE_BYTES_WIN64 192u #define X64_PROLOGUE_BASE_BYTES 11u -#define X64_PROLOGUE_SRET_BYTES 7u -#define X64_PROLOGUE_SAVE_BYTES 7u -#define X64_PROLOGUE_XMM_SAVE_BYTES 8u -#define X64_PROLOGUE_CHKSTK_DELTA 6u #define X64_WIN64_SHADOW_SPACE 32u #define X64_MAX_CS_INT_REGS 7u diff --git a/src/cg/wide.c b/src/cg/wide.c @@ -154,13 +154,17 @@ CGLocal api_wide8_temp_local(KitCg* g, KitCgTypeId ty) { } /* Byte offset of the low / high 32-bit lane within an 8-byte scalar. */ -static i32 api_wide8_lo_off(KitCg* g) { return g->c->target.big_endian ? 4 : 0; } -static i32 api_wide8_hi_off(KitCg* g) { return g->c->target.big_endian ? 0 : 4; } +static i32 api_wide8_lo_off(KitCg* g) { + return g->c->target.big_endian ? 4 : 0; +} +static i32 api_wide8_hi_off(KitCg* g) { + return g->c->target.big_endian ? 0 : 4; +} -/* Materialize a 64-bit constant bit pattern into a fresh memory-resident scalar, - * storing its two 32-bit lanes, and return the value backed by that local. Used - * for both i64 immediates (bits = (u64)imm) and soft-double constants (bits = - * the IEEE-754 binary64 encoding). */ +/* Materialize a 64-bit constant bit pattern into a fresh memory-resident + * scalar, storing its two 32-bit lanes, and return the value backed by that + * local. Used for both i64 immediates (bits = (u64)imm) and soft-double + * constants (bits = the IEEE-754 binary64 encoding). */ ApiSValue api_make_wide8_const_bits(KitCg* g, u64 bits, KitCgTypeId ty) { KitCgTypeId i32_ty = builtin_id(KIT_CG_BUILTIN_I32); KitCgTypeId ptr_ty = cg_type_ptr_to(g->c, ty); @@ -242,8 +246,7 @@ void api_wide8_store_lane(KitCg* g, Operand addr, i32 off, Operand val) { ApiSValue api_wide16_materialize_lvalue(KitCg* g, ApiSValue* v, KitCgTypeId ty) { - if (v->op.kind == OPK_LOCAL && - api_unalias_type(g->c, v->op.type) == api_unalias_type(g->c, ty)) { + if (v->op.kind == OPK_LOCAL) { v->lvalue = 1; return *v; } @@ -254,10 +257,6 @@ ApiSValue api_wide16_materialize_lvalue(KitCg* g, ApiSValue* v, out.lvalue = 1; return out; } - if (v->op.kind == OPK_LOCAL) { - v->lvalue = 1; - return *v; - } if (v->op.kind == OPK_GLOBAL) { CGLocal local = api_f128_temp_local(g, ty); Operand dst_lv = api_op_local(local, ty); @@ -274,12 +273,6 @@ ApiSValue api_wide16_materialize_lvalue(KitCg* g, ApiSValue* v, g->target->copy_bytes(g->target, dst_addr, src_addr, agg); return api_make_lv(dst_lv, ty); } - if (v->op.kind == OPK_LOCAL) { - CGLocal local = api_f128_temp_local(g, ty); - Operand dst = api_op_local(local, ty); - g->target->store(g->target, dst, v->op, api_mem_for_lvalue(g, &dst, ty)); - return api_make_lv(dst, ty); - } if (v->op.kind == OPK_IMM) { return api_make_wide16_int_const(g, v->op.v.imm, ty); } diff --git a/src/dbg/dbg.h b/src/dbg/dbg.h @@ -143,24 +143,21 @@ struct KitJitSession { void* ev_resume; void* ev_stop; DbgSessionState state; - u8 interrupt_pending; u8 worker_alive; u8 worker_should_exit; - u8 pad0; + u8 pad0[2]; /* entry args set by _call */ void* entry; KitEntryKind entry_kind; int entry_argc; char** entry_argv; - int entry_ret; uint64_t entry_u64_args[8]; uint32_t entry_u64_nargs; uint64_t entry_u64_ret; /* current stop slot (filled by the fault handler / worker exit path) */ KitStopInfo stop; - KitUnwindFrame regs_scratch; /* used by signal handler */ /* pending resume directive (set by REPL before signaling ev_resume) */ KitResumeMode pending_mode; diff --git a/src/dbg/session.c b/src/dbg/session.c @@ -35,6 +35,10 @@ static KitStatus on_fault(void* session_v, int signo, KitUnwindFrame* regs) { uint64_t bp_addr; u32 idx; DbgBp* bp; + /* When a user bp hits its max_hits limit we surface the stop with a valid + * bp_id and defer clearing the patch until the post-park path below, so the + * id is still resolvable while the driver inspects the stop. 0 = no clear. */ + u32 auto_clear_user_id = 0; if (!s) return KIT_INVALID; @@ -141,8 +145,10 @@ static KitStatus on_fault(void* session_v, int signo, KitUnwindFrame* regs) { s->stop.bp_id = bp->user_id; s->stop.reason = KIT_STOP_REASON_USER_BREAKPOINT; if (bp->max_hits != 0 && bp->hit_count >= bp->max_hits + bp->skip_count) { - /* Auto-clear after surfacing. Defer to post-park so the bp_id - * is still valid when the driver inspects. */ + /* Reached the hit limit: surface this stop, then auto-clear the + * patch in the post-park path so the bp_id stays valid while the + * driver inspects, and the bp does not fire again afterward. */ + auto_clear_user_id = bp->user_id; } goto park; } @@ -161,6 +167,12 @@ park: s->os->event_wait(s->os->user, s->ev_resume); s->os->event_reset(s->os->user, s->ev_resume); + /* Post-park auto-clear: a max_hits-limited bp was surfaced above; now that + * the driver has inspected the stop, drop its patch so it fires no more. */ + if (auto_clear_user_id != 0) { + dbg_bp_clear(s, auto_clear_user_id); + } + if (s->pending_mode == KIT_RESUME_ABORT) { if (s->os->thread_abort) { s->os->thread_abort(s->os->user); diff --git a/src/dbg/step.c b/src/dbg/step.c @@ -48,6 +48,32 @@ static int stop_is_internal_completion(const KitJitSession* s) { return s->stop.kind == KIT_STOP_BREAKPOINT && s->stop.bp_id == 0; } +/* Arm a one-shot internal bp at `target`, resume the worker, and wait for the + * next stop. on_fault clears the internal bp only if it is the bp that traps; + * if a user bp / signal / exit intervenes first the one-shot would linger and + * later resurface as a spurious bp_id==0 stop, so clear it here on any return + * path that is not its own completion. Mirrors the displaced-step sentinel + * cleanup. */ +static KitStatus run_to_internal_bp(KitJitSession* s, uint64_t target) { + u32 bp_id = 0; + KitStatus st = dbg_bp_set_internal(s, target, &bp_id); + if (st != KIT_OK) return st; + st = dbg_session_signal_resume(s); + if (st != KIT_OK) { + dbg_bp_clear(s, bp_id); + return st; + } + st = dbg_session_wait_stop(s); + if (st != KIT_OK) { + dbg_bp_clear(s, bp_id); + return st; + } + if (!stop_is_internal_completion(s)) { + dbg_bp_clear(s, bp_id); + } + return KIT_OK; +} + static KitStatus direct_call_target(KitJitSession* s, uint64_t* target) { ArchDbgInsn insn; if (!s->arch_dbg || !s->arch_dbg->direct_call_target) return KIT_NOT_FOUND; @@ -93,18 +119,13 @@ static int line_changed(KitSlice base_file, uint32_t base_line, static KitStatus try_step_into_direct_call(KitJitSession* s, int* did) { uint64_t target = 0; - u32 bp_id = 0; KitStatus st; *did = 0; st = direct_call_target(s, &target); if (st == KIT_NOT_FOUND || st == KIT_UNSUPPORTED) return KIT_OK; if (st != KIT_OK) return st; - st = dbg_bp_set_internal(s, target, &bp_id); - if (st != KIT_OK) return st; *did = 1; - st = dbg_session_signal_resume(s); - if (st != KIT_OK) return st; - st = dbg_session_wait_stop(s); + st = run_to_internal_bp(s, target); if (st != KIT_OK) return st; if (stop_is_internal_completion(s)) { return run_step_line_loop(s); @@ -114,18 +135,13 @@ static KitStatus try_step_into_direct_call(KitJitSession* s, int* did) { static KitStatus try_follow_direct_jump(KitJitSession* s, int* did) { uint64_t target = 0; - u32 bp_id = 0; KitStatus st; *did = 0; st = direct_jump_target(s, &target); if (st == KIT_NOT_FOUND || st == KIT_UNSUPPORTED) return KIT_OK; if (st != KIT_OK) return st; - st = dbg_bp_set_internal(s, target, &bp_id); - if (st != KIT_OK) return st; *did = 1; - st = dbg_session_signal_resume(s); - if (st != KIT_OK) return st; - return dbg_session_wait_stop(s); + return run_to_internal_bp(s, target); } static KitStatus run_step_line_loop(KitJitSession* s) { @@ -182,7 +198,6 @@ static KitStatus run_step_line_loop(KitJitSession* s) { static KitStatus run_step_out(KitJitSession* s) { KitUnwindFrame frame; - u32 bp_id = 0; KitStatus st; frame = s->stop.regs; frame.pc = step_rt_to_img(s, frame.pc); /* CFI lookup is in image space */ @@ -198,11 +213,7 @@ static KitStatus run_step_out(KitJitSession* s) { * the saved return-address register / stack slot — already a runtime PC, no * inverse translation needed before the internal bp install. */ if (frame.pc == 0) return KIT_NOT_FOUND; - st = dbg_bp_set_internal(s, frame.pc, &bp_id); - if (st != KIT_OK) return st; - st = dbg_session_signal_resume(s); - if (st != KIT_OK) return st; - return dbg_session_wait_stop(s); + return run_to_internal_bp(s, frame.pc); } static KitStatus run_next_line(KitJitSession* s) { @@ -221,7 +232,6 @@ static KitStatus run_next_line(KitJitSession* s) { * keep advancing until the source line actually changes. */ { KitUnwindFrame frame = s->stop.regs; - u32 bp_id = 0; KitStatus st; frame.pc = step_rt_to_img(s, frame.pc); if (kit_dwarf_unwind_step(s->dwarf, &frame) != KIT_OK || frame.pc == 0) { @@ -229,12 +239,7 @@ static KitStatus run_next_line(KitJitSession* s) { return run_step_line_loop(s); } /* frame.pc is now a runtime return-address (from the stack). */ - if (dbg_bp_set_internal(s, frame.pc, &bp_id) != KIT_OK) { - return run_step_line_loop(s); - } - st = dbg_session_signal_resume(s); - if (st != KIT_OK) return st; - st = dbg_session_wait_stop(s); + st = run_to_internal_bp(s, frame.pc); if (st != KIT_OK) return st; if (stop_is_internal_completion(s)) { return run_step_line_loop(s); diff --git a/src/debug/dwarf_open.c b/src/debug/dwarf_open.c @@ -348,18 +348,6 @@ static void cu_read_root_attrs(KitDebugInfo* d, DwCu* cu) { if (code == 0) return; ab = dw_abbrev_lookup(t, code); if (!ab) return; - /* First pass: pull str_offsets_base if present (so subsequent strx - * resolutions work). */ - for (i = 0; i < ab->nattrs; ++i) { - DwAbbrevAttr* aa = &ab->attrs[i]; - if (aa->attr == DW_AT_str_offsets_base) { - u32 tmp = off; - /* Skip preceding attrs to locate this attr's payload — easier - * to do a full pass and remember offsets. We re-scan instead. */ - (void)tmp; - break; - } - } /* Two-pass scan: do skipping reads, but capture base attrs. We must * be careful: dw_read_form for strx forms uses cu->str_offsets_base, * so we read in two passes. */ diff --git a/src/obj/coff/link.c b/src/obj/coff/link.c @@ -397,13 +397,14 @@ static void coff_define_tls_used(LinkImage* img, * symbol's vaddr in apply_all_relocs. */ typedef struct CoffImport { - LinkSymId sym; /* canonical LinkSymId from img->syms */ - Sym import_name; /* DLL export name override (short-import NameType); 0=use sym */ - u32 dll_idx; /* index into CoffImportTable.dlls */ - u32 stub_off; /* offset in .text bucket (functions only) */ - u32 iat_off; /* offset in .idata IAT block */ - u32 ilt_off; /* offset in .idata ILT block */ - u32 hint_off; /* offset in .idata hint/name table */ + LinkSymId sym; /* canonical LinkSymId from img->syms */ + Sym import_name; /* DLL export name override (short-import NameType); 0=use + sym */ + u32 dll_idx; /* index into CoffImportTable.dlls */ + u32 stub_off; /* offset in .text bucket (functions only) */ + u32 iat_off; /* offset in .idata IAT block */ + u32 ilt_off; /* offset in .idata ILT block */ + u32 hint_off; /* offset in .idata hint/name table */ u8 is_func; u8 pad[3]; } CoffImport; @@ -479,7 +480,8 @@ static const char* coff_import_lookup_name(Compiler* c, const LinkSymbol* s, * short-import NameType override (CoffImport.import_name, e.g. EXPORTAS's real * DLL export name) when present, else derives it from the symbol name. */ static const char* coff_import_emit_name(Compiler* c, const CoffImport* imp, - const LinkSymbol* s, size_t* nlen_out) { + const LinkSymbol* s, + size_t* nlen_out) { if (imp->import_name) { Slice nm_s = pool_slice(c->global, imp->import_name); if (nlen_out) *nlen_out = nm_s.len; @@ -1413,18 +1415,6 @@ static void coff_write_file_header(Writer* w, u16 machine, u16 nsec, coff_wr_u16(w, characteristics); } -/* Per-section meta used by both the data-directory fill and the - * IMAGE_SECTION_HEADER emit. Compactly captures everything the writer - * needs to know about the four-or-five output sections. */ -typedef struct CoffOutHdr { - const char* name; - u32 vsize; - u32 rva; - u32 size_raw; - u32 file_offset; - u32 characteristics; -} CoffOutHdr; - static void coff_write_optional_header(Writer* w, u32 entry_rva, const CoffSection out[COFF_NBUCKETS], u32 headers_size_padded, u32 image_size, diff --git a/src/obj/elf/link_dyn.c b/src/obj/elf/link_dyn.c @@ -31,6 +31,8 @@ * so downstream passes (emit_reloc_records, GC) leave them alone. */ +#include "obj/elf/link_dyn.h" + #include <string.h> #include "core/bytes.h" @@ -44,7 +46,6 @@ #include "link/link_internal.h" #include "obj/bytebuf.h" #include "obj/elf/elf.h" -#include "obj/elf/link_dyn.h" #include "obj/format.h" /* ---- small allocators (mirror layout_iplt's helpers) ---- */ @@ -518,7 +519,8 @@ static void build_versions(Linker* l, LinkImage* img, LinkDynState* dyn, } } { - u32 total = nson * (u32)ELF_VERNEED_SIZE + vb.nreq * (u32)ELF_VERNAUX_SIZE; + u32 total = + nson * (u32)ELF_VERNEED_SIZE + vb.nreq * (u32)ELF_VERNAUX_SIZE; u8* vn = (u8*)h->alloc(h, total, 4); u8* p; u32 si; @@ -539,19 +541,20 @@ static void build_versions(Linker* l, LinkImage* img, LinkDynState* dyn, if (vb.reqs[r].soname != sonames[si]) continue; ver_s = pool_slice(l->c->global, vb.reqs[r].version); name_off = objbb_append_str(dynstr, ver_s.s, (u32)ver_s.len); - wr_u32_le(p + 0, elf_sysv_hash(ver_s.s, (u32)ver_s.len)); /* vna_hash */ - wr_u16_le(p + 4, 0); /* vna_flags */ - wr_u16_le(p + 6, vb.reqs[r].index); /* vna_other */ - wr_u32_le(p + 8, name_off); /* vna_name */ + wr_u32_le(p + 0, + elf_sysv_hash(ver_s.s, (u32)ver_s.len)); /* vna_hash */ + wr_u16_le(p + 4, 0); /* vna_flags */ + wr_u16_le(p + 6, vb.reqs[r].index); /* vna_other */ + wr_u32_le(p + 8, name_off); /* vna_name */ /* vna_next: filled after we know if another aux follows. */ p += ELF_VERNAUX_SIZE; ++cnt; } /* Verneed header. vn_aux is the byte offset to the first Vernaux. */ - wr_u16_le(vn_rec + 0, 1); /* vn_version */ - wr_u16_le(vn_rec + 2, (u16)cnt); /* vn_cnt */ - wr_u32_le(vn_rec + 4, file_off); /* vn_file */ - wr_u32_le(vn_rec + 8, (u32)(aux - vn_rec)); /* vn_aux */ + wr_u16_le(vn_rec + 0, 1); /* vn_version */ + wr_u16_le(vn_rec + 2, (u16)cnt); /* vn_cnt */ + wr_u32_le(vn_rec + 4, file_off); /* vn_file */ + wr_u32_le(vn_rec + 8, (u32)(aux - vn_rec)); /* vn_aux */ wr_u32_le(vn_rec + 12, si + 1u < nson ? (u32)(p - vn_rec) : 0u); /* vn_next */ /* Link the Vernaux chain (each entry -> next, last -> 0). */ @@ -1034,7 +1037,8 @@ void layout_dyn(Linker* l, LinkImage* img) { Sym name_dynamic = pool_intern_slice(l->c->global, SLICE_LIT(".dynamic")); Sym name_plt = pool_intern_slice(l->c->global, SLICE_LIT(".plt")); Sym name_got_plt = pool_intern_slice(l->c->global, SLICE_LIT(".got.plt")); - Sym name_gnu_version = pool_intern_slice(l->c->global, SLICE_LIT(".gnu.version")); + Sym name_gnu_version = + pool_intern_slice(l->c->global, SLICE_LIT(".gnu.version")); Sym name_gnu_version_r = pool_intern_slice(l->c->global, SLICE_LIT(".gnu.version_r")); @@ -1199,7 +1203,7 @@ void layout_dyn(Linker* l, LinkImage* img) { } /* The .dynamic body is built later, after segment shifts are - * applied during emit (link_elf.c). emit_dynamic_body takes the + * applied during emit. link_emit_elf (src/obj/elf/link.c) takes the * post-shift vaddrs of every other dyn section and writes one * DT_* entry per index. */ diff --git a/src/obj/macho/link.c b/src/obj/macho/link.c @@ -107,9 +107,6 @@ typedef struct MachImp { * chained-fixup rebase entry (or no entry at all for a weak-undef * resolving to NULL). No dylib_ord / stub_idx / chained-fixup bind. */ u8 internal; - u8 pad[1]; - u64 internal_vaddr; /* image-relative target vaddr; meaningful only when - internal=1 */ } MachImp; typedef struct MachDylib { @@ -273,14 +270,8 @@ typedef struct MCtx { /* Final layout (computed during plan) */ u64 text_vaddr; - u64 text_filesz; u64 stubs_vaddr; u64 got_vaddr; - u64 data_const_vaddr; - u64 data_vaddr; - u64 data_const_filesz; - u64 data_filesz; - u64 data_memsz; u64 linkedit_vaddr; u64 linkedit_fileoff; u32 entry_offset; /* offset of entry within __TEXT segment */ @@ -456,11 +447,6 @@ static void collect_imports(MCtx* x) { mi->is_func = (t->kind == SK_FUNC || t->kind == SK_IFUNC) ? 1 : 0; mi->weak = (t->bind == SB_WEAK) ? 1 : 0; mi->internal = 1; - /* internal_vaddr is read fresh from the LinkSymbol when the slot - * gets initialized — collect_imports runs before shift_sections - * rebases section vaddrs to Mach-O layout, so capturing here would - * be stale by the time __got bytes are written. */ - mi->internal_vaddr = 0; x->sym_to_imp[canon] = x->nimports; if (canon != r->target) x->sym_to_imp[r->target] = x->nimports; } @@ -1049,7 +1035,6 @@ static void plan_layout(MCtx* x) { if (slice_eq_cstr(slice_from_cstr(m->sectname), "__stubs")) x->stubs_vaddr = m->vaddr; } - x->text_filesz = sg->filesize; } if (i == 2) { for (u32 j = 0; j < sg->nsects; ++j) { @@ -1057,8 +1042,6 @@ static void plan_layout(MCtx* x) { if (slice_eq_cstr(slice_from_cstr(m->sectname), "__got")) x->got_vaddr = m->vaddr; } - x->data_const_vaddr = sg->vmaddr; - x->data_const_filesz = sg->filesize; } if (i == 3) { for (u32 j = 0; j < sg->nsects; ++j) { @@ -1076,9 +1059,6 @@ static void plan_layout(MCtx* x) { x->has_tls_image = 1; } } - x->data_vaddr = sg->vmaddr; - x->data_filesz = sg->filesize; - x->data_memsz = sg->vmsize; } vaddr = sg->vmaddr + sg->vmsize; /* Mach-O segments are mapped in page units. If a segment's memory @@ -1567,12 +1547,6 @@ static void apply_relocs(MCtx* x, FixList* fl) { * field (4-byte units, 0 = end of chain). */ -typedef struct PageChain { - u32 first_offset_in_page; /* relative to page start */ - u32 nsites; - u32 first_site_idx; /* into a per-segment site array */ -} PageChain; - static int site_cmp_by_vaddr(const void* a, const void* b) { const FixSite* x = a; const FixSite* y = b; diff --git a/src/obj/obj.c b/src/obj/obj.c @@ -20,16 +20,17 @@ SEGVEC_DEFINE(Sections, Section, 5); /* 32 entries per segment */ SEGVEC_DEFINE(Symbols, ObjSym, 6); /* 64 entries per segment */ -/* name (interned Sym) -> first defining ObjSymId. A validated fast-path index - * for obj_symbol_find: the whole-program LTO builder holds every TU's symbols - * in one builder, so the historical linear scan is O(n^2) at decl time. The - * index stores the first id seen for a name (matching the scan's "first match" - * semantics); obj_symbol_find re-checks the hit's name and falls back to a - * linear scan if it is stale (after obj_symbol_rename), so it is always exact. */ +/* name (interned Sym) -> first defining ObjSymId. The authoritative index for + * obj_symbol_find: the whole-program LTO builder holds every TU's symbols in + * one builder, so the historical linear scan is O(n^2) at decl time. The index + * stores the first id seen for a name (matching the scan's "first match" + * semantics), and obj_symbol_find is a pure O(1) hash lookup with no fallback. + * obj_symbol_rename keeps the index exact (re-homing or dropping a renamed + * symbol's entry), so the lookup never sees a stale hit. */ HASHMAP_DEFINE(SymNameIndex, Sym, ObjSymId, hash_u32); -SEGVEC_DEFINE(Relocs, Reloc, 6); /* 64 entries per segment */ -SEGVEC_DEFINE(Groups, ObjGroup, 3); /* 8 entries per segment */ -SEGVEC_DEFINE(Atoms, ObjAtom, 5); /* 32 entries per segment */ +SEGVEC_DEFINE(Relocs, Reloc, 6); /* 64 entries per segment */ +SEGVEC_DEFINE(Groups, ObjGroup, 3); /* 8 entries per segment */ +SEGVEC_DEFINE(Atoms, ObjAtom, 5); /* 32 entries per segment */ /* COFF WEAK_EXTERNAL alias declaration: symbol `sym` is an alias for the * symbol named `target`. Rare (only import-archive members and the like @@ -51,11 +52,11 @@ typedef struct ObjExtSlot { struct KitObjBuilder { Compiler* c; Heap* heap; - Sections sections; /* index 0 reserved as "none" */ - Symbols symbols; /* index 0 reserved as "none" */ - Relocs relocs; /* flat across all sections; filtered on read */ - Groups groups; /* index 0 reserved as "none" */ - Atoms atoms; /* index 0 reserved as "none" */ + Sections sections; /* index 0 reserved as "none" */ + Symbols symbols; /* index 0 reserved as "none" */ + Relocs relocs; /* flat across all sections; filtered on read */ + Groups groups; /* index 0 reserved as "none" */ + Atoms atoms; /* index 0 reserved as "none" */ SymNameIndex sym_by_name; /* name -> first ObjSymId; accelerates find */ /* Format-specific ELF e_flags. Set by read_elf to the input's * e_flags (e.g. on RISC-V, EF_RISCV_RVC | EF_RISCV_FLOAT_ABI_DOUBLE); @@ -655,9 +656,9 @@ void obj_patch(ObjBuilder* ob, ObjSecId id, u32 ofs, const void* data, } static ObjSymId obj_symbol_make(ObjBuilder* ob, Sym name, SymBind bind, - SymVis vis, SymKind kind, - ObjSecId section_id, u64 value, u64 size, - u64 common_align, int index_name) { + SymVis vis, SymKind kind, ObjSecId section_id, + u64 value, u64 size, u64 common_align, + int index_name) { u32 id; ObjSym* s = Symbols_push(&ob->symbols, &id); if (!s) return OBJ_SYM_NONE; diff --git a/src/obj/obj.h b/src/obj/obj.h @@ -859,13 +859,6 @@ int obj_format_static_ifunc_via_rela_iplt(const Compiler*); * Returns 0 when the format has no such reloc. */ u32 obj_format_static_ifunc_irelative_type(const Compiler*); -/* Per-arch variant-I TP bias for the active target's ELF arch: distance - * from the TLS image start to where `tp` points in kit's freestanding - * layout (16 for AArch64/RISC-V, 0 for x86_64 variant-II). Returns 0 - * for a non-ELF target or an arch with no ELF descriptor. The - * hosted-vs-freestanding RISC-V split is applied by the caller. */ -u32 obj_format_elf_tls_tp_bias(const Compiler*); - /* Format boundary-symbol classifier. Asks the active object format * whether `name` is a symbol the format itself owns as a boundary / * synthetic global, and if so what SymKind it carries. Returns 1 and diff --git a/src/obj/obj_secnames.c b/src/obj/obj_secnames.c @@ -389,15 +389,6 @@ u32 obj_format_static_ifunc_irelative_type(const Compiler* c) { return ao ? ao->r_irelative : 0u; } -u32 obj_format_elf_tls_tp_bias(const Compiler* c) { - const ObjFormatImpl* fmt; - const ObjElfArchOps* arch; - if (!c || c->target.obj != KIT_OBJ_ELF) return 0u; - fmt = obj_format_lookup(KIT_OBJ_ELF); - arch = (fmt && fmt->elf_arch) ? fmt->elf_arch(c->target.arch) : NULL; - return arch ? arch->tls_tp_bias : 0u; -} - int obj_format_boundary_sym_kind(const Compiler* c, KitSlice name, int* symkind) { /* PE/COFF owns two synthetic absolute globals the linker emits: diff --git a/src/opt/pass_coalesce.c b/src/opt/pass_coalesce.c @@ -51,10 +51,6 @@ static void coalesce_add_related(CoalesceCtx* c, PReg v) { c->related[c->nrelated++] = v; } -int opt_ranges_overlap_kind(const OptLiveRangeSet* ranges, PReg a, PReg b); -static int ranges_overlap_kind(const OptLiveRangeSet* ranges, PReg a, PReg b) { - return opt_ranges_overlap_kind(ranges, a, b); -} int opt_ranges_overlap_kind(const OptLiveRangeSet* ranges, PReg a, PReg b) { /* Returns 0 (no overlap), 1 (a single unit-length overlap), or 2 (real * conflict: an overlap longer than one point, or two or more disjoint @@ -312,7 +308,8 @@ void opt_coalesce_ranges(Func* f, const OptLiveRangeSet* ranges) { arena_zarray(f->arena, u64, ctx.conflict_words ? ctx.conflict_words : 1u); for (u32 i = 0; i < ctx.nrelated; ++i) { for (u32 j = i + 1u; j < ctx.nrelated; ++j) { - int kind = ranges_overlap_kind(ranges, ctx.related[i], ctx.related[j]); + int kind = + opt_ranges_overlap_kind(ranges, ctx.related[i], ctx.related[j]); if (kind) { coalesce_set_conflict(&ctx, i, j, kind == 1); ++f->opt_coalesce_conflicts; diff --git a/src/opt/pass_live.c b/src/opt/pass_live.c @@ -179,8 +179,8 @@ static int live_metric_copy(OptLiveInfo* live, OptBitset* dst, const OptBitset* src) { u32 n = 0; if (dst && src) { - n = src->active_words; - if (dst->active_words > n) n += dst->active_words - n; + n = src->active_words > dst->active_words ? src->active_words + : dst->active_words; } if (live) live->bitset_words_touched += n; return opt_bitset_copy(dst, src); diff --git a/src/opt/pass_ssa.c b/src/opt/pass_ssa.c @@ -889,9 +889,7 @@ static void realign_phi_preds(Func* f) { Inst* phi = &bl->insts[i]; if ((IROp)phi->op != IR_PHI) break; IRPhiAux* aux = (IRPhiAux*)phi->extra.aux; - if (!aux || aux->npreds == bl->npreds) { - if (!aux) continue; - } + if (!aux) continue; u32 old_n = aux->npreds; u32* old_blocks = aux->pred_blocks; Val* old_vals = aux->pred_vals; diff --git a/src/wasm/wasm.h b/src/wasm/wasm.h @@ -1,5 +1,5 @@ -#ifndef KIT_WASM_H -#define KIT_WASM_H +#ifndef KIT_WASM_WASM_H +#define KIT_WASM_WASM_H /* Shared Wasm binary/core: in-memory module model, binary decoder, WAT * parser, validator, encoder, and small instruction-kind helpers. This