kit

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

commit 51c694af85e1ba3b7eae6763b22431cd5802087b
parent e55ca1f0335e253b0d5b7c61cf5a5e34c1cc6970
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Mon, 15 Jun 2026 09:59:46 -0700

docs: update CG stack API plan

Diffstat:
Mdoc/plan/CG-STACK-API.md | 313+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
1 file changed, 196 insertions(+), 117 deletions(-)

diff --git a/doc/plan/CG-STACK-API.md b/doc/plan/CG-STACK-API.md @@ -25,19 +25,19 @@ compatibility mode and no byte-identity gate. | # | Decision | Choice | |---|---|---| -| D1 | Keep the stack API, add a stack-owned frontend slot sidecar | yes | +| D1 | Keep the stack API, add stack-owned inline frontend slot facts | yes | | D2 | Free `KitCgValue`/`KitCgPlace` handles independent of stack lifetime | **rejected** — loses the "on stack == live" invariant | -| D3 | Where the type/flags stack lives during suppression | **CG unevaluated mode** — KitCg maintains stack shape, sidecar facts, and constant facts while target emission is off | +| D3 | Where the type/flags stack lives during suppression | **CG unevaluated mode** — KitCg maintains stack shape, inline lang facts, and constant facts while target emission is off | | D4 | Effective-address folding | **Automatic behind the CG API.** The parser builds places naively (`deref`, `field_at`, `elem`); CG decides whether to fold into the fused `[base + index*scale + offset]` operand or materialize. **No deferral or folding logic in the parser.** | | D5 | Success gate | **Correctness + determinism** (full suites pass, output deterministic). Emitted bytes MAY change vs today; byte-identity is *not* a gate. | | D6 | Fused `store_void` / `store_keep` as CG ops | **in scope** | | D7 | The `cg_adapter.c` / `pcg_*` layer | **deleted entirely.** The parser drives `kit_cg_*` directly and applies C facts via `kit_cg_retag_top`/`set_top_flags`. C-semantic operations survive as plain helper *functions* (op-enum maps, the conversion lattice, atomic/inline-asm/inc-dec shaping) — not a stack-mirroring layer, and not under a `pcg_` prefix. | | D8 | Call-arg stack-mark API (old §4.4) | **dropped** — C already passes args on the CG stack (`kit_cg_call`), so a mark API adds nothing | | D9 | `KitCgSlotInfo` + new ops are public API | **forced** — `lang/` builds only against `include/kit/cg.h` (no `src/cg` includes) | -| D10 | Sidecar is a parallel array, not inlined into `ApiSValue` | **forced** — `ApiSValue` is `_Static_assert`'d `<= 64` bytes and is near the limit (`src/cg/value.c:14`) | +| D10 | Lang facts storage | **inline in `ApiSValue`; final node size is 64 bytes.** Pack `kind`/`res`/`pinned`/`lvalue` into a `uint16_t flags` word and make `lang_flags` `uint16_t`, giving room for `const void* lang_type` while preserving the `ApiSValue <= 64` invariant (`src/cg/value.c:14`). | | D11 | C integer-constant-expression evaluation | **moves onto a CG constant-value model, with parser-owned C legality tracking.** The parser evaluates an ICE by parsing it once through the normal path under unevaluated mode, checking the syntactic/semantic ICE guard, and reading the folded value from CG. The parser's duplicate `cexpr_*` grammar and `cint_*` arithmetic engine are **deleted in the final ICE phase**, not during the stack/place cutover. | -| D12 | Constant payload shape | **stack-owned side payload, not `OPK_IMM` alone.** `kit_cg_top_const_int` today only answers for <=64-bit immediate operands; the new model carries known integer bits + width + type independently of whether the emitted operand would be an immediate/local/runtime call. | -| D13 | `__int128` constant policy | **support 128-bit integer folding in CG, including div/rem.** CG already has i128/u128 types and emitted i128 ops call runtime helpers (`__divti3`, `__udivti3`, `__modti3`, `__umodti3`). Factor the runtime two-limb arithmetic into a shared word-int implementation compiled into `libkit.a`, with runtime ABI wrappers and CG constant folding both calling it through sensible `{lo,hi}` signatures. Do not include `rt/lib/int64/int64.c` wholesale into `libkit` and do not make compile-time folding depend on target runtime helper symbols. | +| D12 | Constant payload shape | **stack-parallel constant payload, not `OPK_IMM` alone.** `kit_cg_top_const_int` today only answers for <=64-bit immediate operands; the new model carries known integer bits + width + type independently of whether the emitted operand would be an immediate/local/runtime call. | +| D13 | `__int128` constant policy | **support useful 128-bit integer bit-pattern folding in CG, but do not require 128-bit div/rem.** CG already has i128/u128 types and emitted i128 div/rem lower through runtime helpers; those are dynamic code, not compile-time constants. Move the existing parser `CConstInt` add/sub/mul/bitwise/shift/compare/cast behavior into CG so current static i128 initializer coverage stays supported; i128 div/rem returns "unknown constant" until a true compile-time word-int division implementation is added. | --- @@ -86,7 +86,7 @@ shadow-stack edit. 1. **Stack lifetime stays authoritative.** A transient operand is live iff a live `ApiSValue` stack entry references it. `api_push`/`api_pop` remain the lifetime - hooks. The sidecar never owns lifetime. + hooks. Inline lang facts and the constant payload never own lifetime. 2. **No free expression handles.** Every API returns either a local/symbol/label handle with existing lifetime rules, or a short-lived stack depth — never a separately-live expression value. @@ -98,43 +98,69 @@ shadow-stack edit. 5. **Backend boundaries stay intact.** `CgTarget` / NDT / `NativeTarget` / MC receive no C frontend state. The new place ops lower through the *existing* CgTarget contract — **no backend changes**. -6. **The hot `ApiSValue` node stays `<= 64` bytes.** The sidecar is a parallel - array (D10). -7. **No global state.** The sidecar, constant payload, and unevaluated counter - hang off `KitCg`. -8. **The sidecar is opt-in.** It is allocated only for a frontend that enables - it; toy/wasm leave it off and pay nothing. +6. **The hot `ApiSValue` node stays `<= 64` bytes.** Inline lang facts are allowed + only because the packed layout keeps the node at exactly 64 bytes (D10). +7. **No global state.** Inline lang facts live on each `ApiSValue`; the constant + payload and unevaluated counter hang off `KitCg`. +8. **No separate lang side allocation.** Non-C frontends leave `lang_type=NULL` + and `lang_flags=0`; they pay the 64-byte node size but no extra side array or + enable/disable machinery. 9. **Constant facts are stack-owned CG facts, not backend facts.** The constant payload shadows the CG stack in lockstep, carries known/unknown integer bits plus width/type, and has no `CgTarget`, local, temp, or native-backend lifetime. Frontends read it only through public CG APIs; they never inspect `ApiSValue` to decide whether a value is constant. +10. **Constant tracking is always-on.** The constant payload is a CG semantic + fact, not a C frontend feature and not an optimization-level feature. It is + maintained for every `KitCg` stack slot in normal emitting mode and in + unevaluated mode; there is no `kit_cg_const_enable` and no "constants off" + mode. --- ## 4. The new architecture -### 4.1 One stack + an opaque lang sidecar +### 4.1 One stack + inline opaque lang facts -A parallel array `KitCg.lang_side`, the same length as `KitCg.stack`, allocated -lazily when the frontend calls `kit_cg_lang_enable`. Entry *i* shadows -`stack[i]`. CG moves sidecar entries in exact lockstep with the value stack -(§6). NULL `lang_side` means "no sidecar"; all sidecar ops degrade to no-ops / -zeroed reads. A separate constant payload shadows the same stack depths whenever -constant tracking is enabled; it is owned by CG and follows the same lockstep -rules as the language sidecar, but it is not a C type/flags store. +`ApiSValue` carries the frontend facts directly. There is no `KitCg.lang_side`, +no enable call, and no per-frontend side allocation. `lang_type` is an opaque +pointer copied/dropped with the stack node; `lang_flags` is a frontend-defined +16-bit field. A separate always-on constant payload still shadows the same stack +depths; it is owned by `KitCg`, follows the stack in lockstep, and is independent +of the frontend type/flags fields. + +Planned internal shape, preserving a 64-byte hot stack node: + +```c +typedef struct ApiSValue { + Operand op; /* 24 bytes: immediate/local/global/indirect operand */ + ApiDelayed* delayed; /* off-node SV_CMP/SV_ARITH payload, else NULL */ + const void* lang_type; /* opaque to CG; lang/c stores a const Type* */ + KitCgTypeId type; /* CG type id */ + KitCgLocal source_local; /* owned/fixed local tracking */ + ApiBitField bitfield; /* 12 bytes; bit_width != 0 => bit-field PLACE */ + uint16_t lang_flags; /* frontend-defined (C: LVALUE/MODIFIABLE/...) */ + uint16_t flags; /* kind:2, res:2, pinned:1, lvalue:1, spare:10 */ +} ApiSValue; /* 64 bytes, 8-byte aligned */ +``` + +`kind` is one of `SV_OPERAND`, `SV_CMP`, or `SV_ARITH`; `res` is one of +`RES_INHERENT`, `RES_LOCAL`, or `RES_FIXED_LOCAL`; `pinned` and `lvalue` are +booleans. These fit in six bits, leaving ten spare bits in the packed word. +`ApiDelayed` remains an off-node pooled extension for delayed compare/arithmetic +fusion only; it is not a language-fact or constant-value store. ```c typedef struct KitCgSlotInfo { KitCgTypeId cg_type; /* read-only echo of the node's CG type, for queries */ const void* lang_type; /* opaque to CG; lang/c stores a const Type* */ - uint32_t lang_flags; /* frontend-defined (C: LVALUE/MODIFIABLE/...) */ + uint16_t lang_flags; /* frontend-defined (C: LVALUE/MODIFIABLE/...) */ } KitCgSlotInfo; ``` -The sidecar stores only `{lang_type, lang_flags}`; `cg_type` in the returned -struct is filled from `stack[i].type` at query time. The C flags are -frontend-private bits; suggested C layout: +`kit_cg_slot_info` reads `{lang_type, lang_flags}` from `ApiSValue` and fills +`cg_type` from `stack[i].type` at query time. The C flags are frontend-private +bits and must fit in `uint16_t`; suggested C layout: ``` C_LVALUE /* a C lvalue (distinct from CG place-ness; e.g. a const lvalue) */ @@ -151,29 +177,28 @@ C_REGISTER /* `register` storage class — forbids `&` ### 4.2 Slot queries and retag ```c -void kit_cg_lang_enable(KitCg*); /* alloc + track sidecar */ KitCgSlotInfo kit_cg_slot_info(KitCg*, uint32_t depth_from_top); /* depth 0 == top */ -void kit_cg_retag_top(KitCg*, const void* lang_type, uint32_t lang_flags); +void kit_cg_retag_top(KitCg*, const void* lang_type, uint16_t lang_flags); void kit_cg_retag_at(KitCg*, uint32_t depth_from_top, - const void* lang_type, uint32_t lang_flags); -void kit_cg_set_top_flags(KitCg*, uint32_t set, uint32_t clear); + const void* lang_type, uint16_t lang_flags); +void kit_cg_set_top_flags(KitCg*, uint16_t set, uint16_t clear); ``` `kit_cg_stack_depth` already exists (`include/kit/cg.h:706`). Retag touches only -the sidecar — never the node's CG type (the producing op sets that). C-type -changes that carry no CG op (qualifier strip on a struct lvalue; the deref -retype) become a retag. Today's C queries — `pcg_top_type`, `pcg_top2_type`, +the inline lang fields — never the node's CG type (the producing op sets that). +C-type changes that carry no CG op (qualifier strip on a struct lvalue; the +deref retype) become a retag. Today's C queries — `pcg_top_type`, `pcg_top2_type`, `pcg_top_is_lvalue`/`_modifiable_lvalue`/`_null_ptr_const`/`_bitfield`/ `_register`, and the `pcg_retag_*` family — are replaced by direct `kit_cg_slot_info` reads and `kit_cg_retag_*` calls at the parser sites (small static inline accessors over the flag bits are fine; there is no `pcg` layer). **Producer rule:** an op that produces a *fresh* result slot (load, call result, -binop/cmp result, convert, addr, push_*) clears that slot's sidecar +binop/cmp result, convert, addr, push_*) clears that slot's inline lang facts (`lang_type=NULL, lang_flags=0`). The parser stamps the C facts immediately after via `kit_cg_retag_top`. Structural ops (`dup`/`dup2`/`swap`/`rot3`/`drop`) -move/copy sidecar entries so `lang_side[i]` always tracks `stack[i]`. This is -exactly today's "emit op, then `pcg_retag_top`" idiom, with the structural half +move/copy whole `ApiSValue` nodes, so lang facts move with the value. This is +exactly today's "emit op, then `pcg_retag_top`" idiom, with structural movement done automatically inside CG. ### 4.3 Eager places; folding behind the CG API (D4) @@ -269,10 +294,10 @@ void kit_cg_unevaluated_pop(KitCg*); Contract while `unevaluated > 0`: -- **Maintained:** stack depth, `KitCgSlotInfo` sidecar facts, and the constant - payload. Every producing operation leaves a stack slot of the requested CG - type when a type is available; structural ops move/copy both side payloads in - lockstep with the value stack. +- **Maintained:** stack depth, inline lang facts, and the constant payload. + Every producing operation leaves a stack slot of the requested CG type when a + type is available; structural ops move/copy `ApiSValue` nodes and update the + constant payload in lockstep with the value stack. - **Folded:** integer `push`/`cast`/`unop`/`binop`/`cmp` update the constant payload using the CG fold core. A producing op that cannot fold pushes an unknown constant payload, not an emitted value. Unknown is the value-level @@ -283,6 +308,15 @@ Contract while `unevaluated > 0`: that the parser reaches while suppressed return benign dummy handles; those handles exist only to keep one parse path shaped correctly and cannot be used to recover emitted values later. +- **Early op rule:** every stack-mutating op must test unevaluated mode before + it forces operands into locals, allocates temps/locals, materializes delayed + values, calls target hooks, or updates emitted-code-only analyses. In this + path the op performs only its declared stack effect, inline-lang preservation + or clearing, constant-payload movement, and constant-payload folding/unknown + marking. + This matters for ops that are "structural" in API shape but not in the current + implementation: `dup` can emit a copy today, `deref`/`addr` can allocate a + pointer temp, and place/field/elem ops can materialize address arithmetic. - **File-scope safe:** the mode touches no function state, so it works before a function is open. This is required for file-scope array bounds, enum values, bit-field widths, and static initializers. @@ -294,7 +328,7 @@ Contract while `unevaluated > 0`: labels and branches no-op. In emitting mode they keep their normal lowering through labels, branches, and temporaries. In unevaluated mode the parser's control-shape helpers parse each syntactic operand once, compute the C result -type/flags from the sidecar, and push a known or unknown result constant: +type/flags from `KitCgSlotInfo`, and push a known or unknown result constant: - `&&` and `||` can fold from the known left operand without requiring the short-circuited operand to be value-known, but the skipped operand is still @@ -331,11 +365,12 @@ All of it is **deleted in the ICE cutover phase**. "A real constant-value model" means CG no longer treats "constant" as a property of `ApiSValue.operand == OPK_IMM`. The current `kit_cg_top_const_int` can only -answer for <=64-bit immediate operands. The new model is a parallel stack-owned -payload: every CG stack slot has known/unknown state plus integer bits, width, -signedness, and result CG type. It can represent a folded value even when normal -emission would have produced a local, a wide value lowered through a runtime -helper, or no emitted value at all because unevaluated mode is active. +answer for <=64-bit immediate operands. The new model is a stack-parallel +constant payload owned by `KitCg`: every CG stack slot has known/unknown state +plus integer bits, width, signedness, and result CG type. It can represent a +folded value even when normal emission would have produced a local, a wide value +lowered through a runtime helper, or no emitted value at all because unevaluated +mode is active. Proposed public shape: @@ -356,34 +391,40 @@ void kit_cg_push_const_int(KitCg*, KitCgTypeId type, const KitCgConstInt* v); The exact names can change, but the surface must be width-complete and must not require frontends to inspect `ApiSValue` or know which operands are immediates. +Constant payload policy: + +- **Always-on:** every `KitCg` maintains the payload for every value-stack slot, + regardless of frontend, optimization level, or unevaluated depth. This matches + CG's existing design pressure: the semantic layer already folds immediate + arithmetic, delayed arithmetic/compare, and local-constant loads for `-O0` + code quality, and C already queries constant-ness during normal expression + parsing. +- **Stack fact, not operand fact:** `kit_cg_top_const_int_ex` reads the payload, + not `ApiSValue.op.kind`. `OPK_IMM` becomes only one way to seed a known + payload. Values materialized as locals, delayed arith/cmp nodes, or wide + constants may still carry known payload bits. +- **Unknown is explicit:** fresh runtime values (`load` from unknown memory, + call results, atomics, volatile access, inline asm, address values, and + unsupported arithmetic such as i128 div/rem) produce an unknown payload. +- **Local-constant forwarding is separate:** `ApiSourceLocal.const_*` tracking + may seed the payload for an actual emitted load from a tracked local, but it is + still an emitting-mode optimization. The parser's `CConstGuard` decides whether + a local identifier is a legal C constant-expression operand; ordinary locals + do not become ICE operands just because CG can forward their current value. + Fold coverage required before the ICE cutover: - **<=64-bit integers:** add/sub/mul, div/rem, bitwise ops, shifts, comparisons, integer casts, and booleanization. Div/rem by zero must fail cleanly with a diagnostic path the parser can report; it must not silently produce an arbitrary constant. -- **128-bit integers:** CG already has i128/u128 types and emitted i128 - arithmetic uses `libkit_rt` helpers. For compile-time folding, create a small - shared word-int implementation compiled into `libkit.a` and used by CG through - signatures like: - - ```c - typedef struct KitInt128Words { uint64_t lo, hi; } KitInt128Words; - - int kit_int128_udivmod(KitInt128Words n, KitInt128Words d, - KitInt128Words* q, KitInt128Words* r); - int kit_int128_sdivmod(KitInt128Words n, KitInt128Words d, - KitInt128Words* q, KitInt128Words* r); - ``` - - The implementation should factor the same `ut_add`/`ut_sub`/`ut_mul`/ - `ut_udivmod` algorithms currently in `rt/lib/int64/int64.c`. The runtime - `__*ti3` ABI functions become thin wrappers around that shared implementation - (or include the same shared `.inc`), so CG and runtime stay bit-for-bit - aligned without duplicating logic. Keep the shared surface over explicit - `{lo,hi}` words so the compiler does not depend on being linked against - `libkit_rt`, on target ABI helper names, or on host support for TI-mode - integers. +- **128-bit integers:** move the existing parser `CConstInt` two-limb behavior + for add/sub/mul, bitwise ops, shifts, comparisons, and casts into CG so + existing static i128 initializer coverage remains supported. Compile-time + i128 div/rem is not required in this plan: emitted i128 div/rem lowers through + runtime helpers, and helper calls are dynamic code. CG marks i128 div/rem + payloads unknown until a true compile-time word-int division implementation is + added. The replacement parser helper is one pass over the normal grammar plus a guard: @@ -430,6 +471,36 @@ both checks must pass: the guard says the syntax is legal for the requested C constant category, and CG says the stack top has a known value of the required type. +Minimum guard event list for the ICE cutover: + +- evaluated comma operator; +- evaluated assignment and compound assignment; +- evaluated pre/post increment and decrement; +- evaluated function call, including builtin calls that are not specifically + permitted by the active constant category; +- identifier/reference classification: enum constants are legal ICE operands, + ordinary objects/functions are not, regardless of any CG local-const fact; +- floating constant use, including the narrow case where a floating constant is + immediately cast to an integer type and the broader cases where it is illegal + for ICE; +- pointer/address expressions, string-literal addresses, label addresses, and + relocation-like constants, which belong to the static-address category rather + than C ICE; +- `sizeof` / `_Alignof`: type-name forms are immediate constants; expression + forms enter a not-evaluated submode unless the operand is a VLA, where the VLA + size expression is evaluated and must be guarded normally; +- `_Generic`: the controlling expression is not evaluated; selected and + unselected associations follow the standard's type/diagnostic rules, and only + the selected expression contributes the resulting value category; +- `offsetof`: accepted through the parser's existing builtin path, but member + designator syntax must stay constrained to the builtin's rules; +- short-circuit `&&`/`||` and `?:`: skipped operands are parsed under the + correct not-evaluated submode; selected/evaluated operands are guarded + normally; +- unsupported arithmetic that CG marks unknown, including i128 div/rem in this + plan, reports "constant expression required" only after the guard has also + accepted the syntax. + Keep the categories separate: - **C ICE:** integer result plus the C ICE guard; used by case labels, enum @@ -448,13 +519,13 @@ Keep the categories separate: | Today in `pcg` / `Parser` | Target owner | |---|---| -| `PcgSlot.type` (`const Type*`) | CG sidecar `lang_type` (opaque) | +| `PcgSlot.type` (`const Type*`) | inline `ApiSValue.lang_type` (opaque) | | `PcgSlot.cg_id` | gone — read the node `type` via `kit_cg_slot_info` | -| `PcgSlot.flags` (C value flags) | CG sidecar `lang_flags` | +| `PcgSlot.flags` (C value flags) | inline `ApiSValue.lang_flags` | | `Parser.cg_slot_stack` / `cg_type_sp` / `cg_type_cap` | gone — the CG stack is the typed stack | | `PcgLvAux` (offset/scale/base_kind/bit_*) + `pcg_materialize_lv_to_ptr` / `pcg_lv_to_memop_place` / `pcg_lv_member` / `pcg_lv_subscript` / `pcg_decay_array` | gone — CG place ops (`deref`/`field_at`/`elem`/`addr`/`field_bits`) fold automatically | | `Parser.suppress_codegen` + `pcg_emit_enabled` forks | CG unevaluated mode; parser keeps only semantic forks required by C control-shape constructs | -| `pcg_dup`/`swap`/`drop` (mirror onto shadow stack) | direct `kit_cg_dup`/`swap`/`drop` (sidecar moves automatically) | +| `pcg_dup`/`swap`/`drop` (mirror onto shadow stack) | direct `kit_cg_dup`/`swap`/`drop` (inline lang facts move with `ApiSValue`; CG updates the constant payload) | | assignment `dup`/`rot3`/`swap` sequences | `kit_cg_store` / `kit_cg_store_keep` | | `cg_adapter.c` / `cg_adapter.h` / the `pcg_*` layer | **deleted** — parser drives `kit_cg_*` directly | | `cexpr_*` grammar + `cint_*` constant-arith engine (`parse_expr.c`) | **deleted in the ICE cutover** — parser owns the legality guard; CG owns constant-value folding (§4.7) | @@ -469,12 +540,13 @@ directly, with no parallel stack and no EA state. --- -## 6. Op-by-op: the sidecar + constant lockstep set +## 6. Op-by-op: inline lang + constant lockstep -Every stack-mutating CG op maintains both `lang_side` and the constant payload so -depths stay in sync. Grouped by effect: +Every stack-mutating CG op maintains inline lang facts and the constant payload +so depths stay in sync. Grouped by effect: -- **Producers (push a fresh slot, clear sidecar, set known/unknown const):** +- **Producers (push a fresh slot, clear inline lang facts, set known/unknown + const):** `push_int`, `push_float`, `push_null`, `push_local`, `push_local_addr`, `push_symbol_addr`, `push_label_addr`, `load`, `addr`, `deref`, `field`/`field_at`/`elem`/ @@ -482,30 +554,31 @@ depths stay in sync. Grouped by effect: call result, `atomic_load`/`atomic_rmw`/`atomic_cmpxchg` result, `intrinsic` result, overflow-builtin result, `inline_asm` outputs. - **Retypers (1→1, keep depth):** `trunc`/`sext`/`zext`/`bitcast`/`fpext`/ - `fptrunc`/`int<->float`/`ptr<->int`, `int_unop`/`fp_unop`. Result sidecar - cleared; parser retags. The constant payload is converted/folded when CG can - do so, otherwise marked unknown. + `fptrunc`/`int<->float`/`ptr<->int`, `int_unop`/`fp_unop`. Result inline lang + facts are cleared; parser retags. The constant payload is converted/folded + when CG can do so, otherwise marked unknown. - **Combiners (N→1):** `int_binop`/`fp_binop`/`int_cmp`/`fp_cmp` (2→1), `field`/`elem` (consume base/index), `store_keep` (2→1), `call`/`call_symbol` (N→0/1), `va_copy` (2→0), `atomic_store` (2→0), `atomic_cmpxchg` (3→…). Integer combiners fold the constant payload when operands are known and the op is supported; otherwise the result payload is unknown. -- **Pure structural:** `dup` (copy top sidecar + constant payload), `dup2`, - `swap`, `rot3`, `drop`, `store`/`store_void` (2→0). These move side payloads - with no semantic change. +- **Pure structural:** `dup` (copy top `ApiSValue` + constant payload), `dup2`, + `swap`, `rot3`, `drop`, `store`/`store_void` (2→0). These move inline lang + facts and constant payloads with no semantic change. - **Consumers (→0):** `branch_true`/`branch_false`/`switch`/`computed_goto`, `ret`. -- **Scope edges:** `api_scope_store_results`/`api_scope_push_results` move both - side payloads with carried results when active. (C uses only void scopes, so - this is inert for C, but must be correct for any future result-carrying - frontend that enables the sidecar.) +- **Scope edges:** `api_scope_store_results`/`api_scope_push_results` move + carried `ApiSValue` results and their constant payloads when active. (C uses + only void scopes, so this is inert for C, but must be correct for any future + result-carrying frontend.) Implementation choke points: `api_push`/`api_pop` (`src/cg/value.c:161/187`) and the structural ops `kit_cg_dup`/`dup2`/`swap`/`drop`/`rot3` -(`src/cg/memory.c:598-735`). Routing side-payload movement through -`api_push`/`api_pop` (which every producer/consumer already calls) covers most -ops automatically; the structural ops and scope-edge movers need explicit -handling. +(`src/cg/memory.c:598-735`). `ApiSValue` copies carry inline lang facts +automatically, while producers must clear them explicitly. Routing +constant-payload movement through `api_push`/`api_pop` (which every +producer/consumer already calls) covers most ops automatically; the structural +ops and scope-edge movers need explicit handling. --- @@ -514,13 +587,15 @@ handling. No dual-stack interim, no byte-identity gate. Each phase is either CG infrastructure with no C frontend cutover, or a one-time frontend cutover that removes the old path for that subsystem. Do not land a phase where the parser's -shadow stack and the CG sidecar both authoritatively model live expressions. - -1. **CG sidecar + slot queries.** Add `KitCgSlotInfo`, `lang_side`, - `kit_cg_lang_enable`, `kit_cg_slot_info`, `kit_cg_retag_top`/`_at`, and - `kit_cg_set_top_flags`; wire lockstep through `api_push`/`api_pop`, - structural ops, and scope-edge movers. Add focused CG API tests for - producer/consumer/structural/scope lockstep. No C frontend change yet. +shadow stack and inline CG lang facts both authoritatively model live +expressions. + +1. **CG inline lang fields + slot queries.** Add the packed 64-byte `ApiSValue` + layout, `KitCgSlotInfo`, `kit_cg_slot_info`, `kit_cg_retag_top`/`_at`, and + `kit_cg_set_top_flags`; wire producer clearing, structural copies, and + scope-edge movers. Add focused CG API tests for producer clearing, + structural/scope movement, and query/retag behavior. No C frontend change + yet. 2. **CG place + store ops.** Add `kit_cg_field_at` and `kit_cg_store_keep`; move the SEXT-load hint decision into `kit_cg_load`; confirm `deref`/`elem`/ `field_at`/`field_bits` compose to fused operands without parser help. Add @@ -531,13 +606,11 @@ shadow stack and the CG sidecar both authoritatively model live expressions. 3. **CG unevaluated + constant payload substrate.** Add `kit_cg_unevaluated_push`/`pop`, the stack-side constant payload, the width-complete constant query/push API, <=64 div/rem folding, and the required - 128-bit folds including div/rem via the shared word-int unit compiled into - `libkit.a`. Add runtime wrapper tests or existing runtime tests to confirm the - `__*ti3` ABI helpers still use the same implementation. No C frontend cutover - yet. Add CG API tests for - file-scope/no-function use, unknown constants, dummy handles, no target/local - calls, nested unevaluated mode, and side-payload lockstep under suppression. -4. **C frontend one-stack cutover.** Enable the lang sidecar for C; replace + 128-bit bit-pattern folds excluding div/rem. No C frontend cutover yet. Add CG + API tests for file-scope/no-function use, unknown constants including i128 + div/rem, dummy handles, no target/local calls, nested unevaluated mode, and + inline-lang/constant lockstep under suppression. +4. **C frontend one-stack cutover.** Use inline lang fields for C; replace parser stack reads/writes with `kit_cg_slot_info` + retag; replace EA folding with eager places; replace assignment choreography with `store`/`store_keep`; move `Parser.suppress_codegen` uses to CG unevaluated mode for type-only @@ -555,12 +628,13 @@ shadow stack and the CG sidecar both authoritatively model live expressions. `cexpr_*` grammar and `cint_*` engine. Re-test every ICE context: case labels, array sizes (block + file scope), enum values, bit-field widths, `alignas`, `_Static_assert`, designated-init indices, invalid ICE syntax that still - folds, `sizeof` not-evaluated exceptions, short-circuit exceptions, and i128 - constant expressions including div/rem and divide-by-zero diagnostics. + folds, `sizeof` not-evaluated exceptions, short-circuit exceptions, i128 + constant expressions, i128 div/rem rejection/unknown behavior, and + divide-by-zero diagnostics for supported div/rem widths. Use targeted gates and redirect output to files per `AGENTS.md`: -- Sidecar/constant substrate: `make test-cg-api test-toy`. +- Inline-lang/constant substrate: `make test-cg-api test-toy`. - C frontend cutover: `make test-cg-api test-parse test-pp test-toy`. - Codegen-sensitive place/store changes: `make test-cg-api test-isa test-aa64-inline` plus targeted native smoke cases for x64/aa64/rv64 as @@ -582,20 +656,24 @@ Use targeted gates and redirect output to files per `AGENTS.md`: - R4 CG never dereferences the lang payload; it is `const void*`. (§3.4) - R5 No backend (`CgTarget`/NDT/NT/MC) change; place ops lower through existing contracts. (§3.5) -- R6 `ApiSValue` stays `<= 64` bytes; sidecar is a parallel array. (§3.6, D10) -- R7 No global state; sidecar, constant payload, and unevaluated counter hang off - `KitCg`. (§3.7) -- R8 Sidecar opt-in; toy/wasm pay nothing. (§3.8) +- R6 `ApiSValue` is 64 bytes with inline `const void* lang_type`, + `uint16_t lang_flags`, and a packed `uint16_t flags` word. (§3.6, D10) +- R7 No global state; inline lang facts live in `ApiSValue`, while the constant + payload and unevaluated counter hang off `KitCg`. (§3.7) +- R8 No language side allocation or enable call; constant payload is always-on + for every `KitCg`. (§3.8, §3.10) - R9 Suppression: type+flags+constant facts maintained, zero emission, zero temp/local alloc, no forced CG-type lowering for type-only paths; parser loses emission-bookkeeping forks, not semantic forks required by C control-shape - constructs. (§4.5) + constructs. Every op checks unevaluated mode before local/temp allocation, + delayed materialization, target calls, or emitted-code-only analysis updates. + (§4.5) - R10 Eager places; all EA folding (const offset, dynamic index, displacement overflow, SEXT-load hint) decided inside CG; `PcgLvAux` deleted. (§4.3, D4) - R11 Bit-field geometry rides the CG place (`field_bits`); C keeps a semantic `C_BITFIELD` flag for `sizeof`/`&` rejection. (§4.1, §4.3) -- R12 C value flags (MODIFIABLE / NULL_PTR_CONST / REGISTER / LVALUE) in - `lang_flags`. (§4.1) +- R12 C value flags (MODIFIABLE / NULL_PTR_CONST / REGISTER / LVALUE) fit in + `uint16_t lang_flags`. (§4.1) - R13 `store` / `store_keep` replace assignment dup/rot3/swap. (§4.4) - R14 No call-mark API; C args stay on the CG stack. (§4.6, D8) - R15 ICE evaluation moves to CG's constant-value model plus a parser-owned @@ -610,12 +688,13 @@ Use targeted gates and redirect output to files per `AGENTS.md`: - R20 `eval_const_int` is reimplemented over the normal parse path with `CConstGuard`; the `cexpr_*` grammar and `cint_*` engine are deleted; all ICE contexts and invalid foldable-but-not-ICE cases are re-tested. (§4.7, §7.6) -- R21 CG exposes a width-complete integer constant payload API; frontends do not - inspect `ApiSValue` or `OPK_IMM` to determine constant-ness. (§4.7, D12) -- R22 <=64-bit and 128-bit div/rem folding move into CG with clean - divide-by-zero failure; the 128-bit path uses a shared `{lo,hi}` word-int - implementation compiled into `libkit.a`, with runtime `__*ti3` helpers as - wrappers or include-site users of the same implementation. (§4.7, D13) +- R21 CG exposes an always-on, width-complete integer constant payload API; + frontends do not inspect `ApiSValue` or `OPK_IMM` to determine constant-ness, + and local-const forwarding is not C ICE legality. (§4.7, D12) +- R22 <=64-bit div/rem folding moves into CG with clean divide-by-zero failure; + 128-bit add/sub/mul/bitwise/shift/compare/cast folding moves into CG, while + i128 div/rem returns unknown until a true compile-time word-int division + implementation exists. (§4.7, D13) - R23 Cutovers are one-time subsystem moves with old paths removed; no dual-stack or backcompat phase is introduced. (§7)