commit aa58c8dc227553adba1fef5fc3553e1c37a1fb09
parent 56198fafd1ed82cecb144b87171f9b88a6ad66b0
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Fri, 8 May 2026 17:23:42 -0700
DESIGN.md update
Diffstat:
1 file changed, 27 insertions(+), 9 deletions(-)
diff --git a/doc/DESIGN.md b/doc/DESIGN.md
@@ -359,6 +359,18 @@ or already-built `ObjBuilder*` (`link_add_obj`). Path-shaped inputs are a
driver-level concern: the driver calls `c->env->file_io->read_all`, then feeds
the bytes APIs.
+**Incremental-linking forward compat.** The single-shot `link_resolve`
+implementation must not destroy or consume input-side state that a future
+incremental re-resolve would need. `LinkRelocApply` records stay as data
+(they are not burned into segment bytes destructively without preserving the
+originals); `LinkInputId → ObjBuilder*` mappings stay stable for the
+lifetime of the `Linker`; resolution is a function from inputs to a fresh
+`LinkImage`, not in-place mutation of the `Linker`. Incremental linking is
+the single most likely future addition, and the existing surface
+(`LinkInputId` stable handles, separable `LinkImage`, byte/`ObjBuilder`
+inputs) is already amenable — this discipline keeps it amenable without
+adding a speculative API.
+
### 5.6 `MemAccess` — explicit memory semantics
`MemAccess` is attached to every typed memory operation (`load`, `store`,
@@ -572,9 +584,10 @@ operation (signed overflow, shift-by-≥-width, division by zero, null deref)
is unreachable. WASM traps deterministically on the first three and faults on
the fourth — the program terminates rather than time-traveling. Real-target
behavior is also more predictable this way. The "70% of -O2" goal is
-achievable without these rules; reserved bits in `Inst.flags` can host
-`nsw`/`nuw`-style annotations later if a specific non-UB-exploiting pass
-needs them.
+achievable without these rules. `Inst.flags` is general-purpose; no specific
+bit allocations are reserved. If a non-UB-exploiting pass that benefits from
+operation-level annotations arrives later, the path is to thread a flags
+argument through `CGTarget.binop` and into `IR_*` then — not before.
### 9.1 Lifecycle
@@ -617,7 +630,8 @@ not in the IR arena.
```
build_cfg
block_cloning (hot path duplication; skipped if it would block addr_xform)
-build_ssa
+build_ssa (incl. promotion of non-address-taken FrameSlots —
+ mem2reg is folded in, not a separate pass)
addr_xform (fold GEP-equivalent address insns into uses)
gvn (incl. constprop, redundant-load elimination)
copy_prop (incl. redundant-extension elimination)
@@ -760,11 +774,15 @@ equal types thanks to `Pool global`).
diagnostics, DWARF, and dependency generation.
- **Locals and parameters always start frame-resident.** `cg_local` and
`cg_param` allocate stable `FrameSlot`s through `CGTarget.frame_slot` and
- `CGTarget.param`. A mem2reg-style pass during opt's lowering pipeline
- promotes non-address-taken slots to virtual registers (and to WASM-locals on
- that target). At -O0 every slot stays on the frame, which is the same shape
- `Debug` wants for `DVL_FRAME` (§11) — full debuggability for free, no parser
- pre-scan needed.
+ `CGTarget.param`. Promotion to virtual registers (and to WASM-locals on
+ that target) happens *inside* SSA construction: `build_ssa` (§9.2) promotes
+ any slot whose `FrameSlotFlag` never had `FSF_ADDR_TAKEN` set. Address-
+ taken slots remain as memory ops and are reasoned about through `MemAccess`
+ alias roots. There is no separate mem2reg pass — SSA construction already
+ has to decide which `FrameSlot` accesses become Phi chains vs which stay
+ loads/stores, and a second pass would re-walk the same decisions. At -O0
+ every slot stays on the frame, which is the same shape `Debug` wants for
+ `DVL_FRAME` (§11) — full debuggability for free, no parser pre-scan needed.
- **Function-pointer ABI is a linker concern.** A function symbol's address
taken via `&f` lowers to a normal `ObjSymId`-relative `Operand`.
ELF/COFF/Mach-O resolve this directly. WASM file emitters and the JIT linker