commit f72ddf35d97b90d5c0ac2c0a6afc98397348846a
parent 549af2838a49bbafedb372906f363bda053063b2
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Mon, 15 Jun 2026 16:39:56 -0700
cg: cut type-interaction cost on the hot compile path
Slot queries, memory-access layout, builtin ABI-layout cache, and the
resolve/predicate fast paths, plus the abi_cg_type_info follow-up:
- kit_cg_slot_info by-value copy retired on the hot path; the C frontend
reads single facts through zero-copy narrow accessors
(kit_cg_slot_cg_type/lang_type/lang_flags); kit_cg_slot_info_ref + the
slot_info_scratch field removed.
- api_mem_from_access: keep natural alignment, only override on an explicit
access.align, only flag MF_UNALIGNED when the explicit align is smaller.
- Builtin ABI layout precomputed once per compiler; api_type_layout_get
returns it instead of recomputing via abi_cg_type_info_compute.
- resolve_type validates without a full cg_type_get; _resolved variants
drop redundant resolve_type/predicate work in the memory helpers.
- api_type_layout_get -> api_type_layout_ref returns a borrowed pointer to
the cached ABITypeInfo (one entry field, no 5-field out-param copy);
abi_cg_type_info resolves builtins with one inline indexed load off the
new Compiler.cg_builtin_layout table.
Byte-identical: the sqlite -O0 object is bit-for-bit unchanged; perf-identity
gate green on every non--g category. sqlite -c instructions 1.939B -> 1.760B
(-9.2%, ~2.7x tcc); callgrind 1.741B -> 1.566B (-10.1%). Also carries the
in-tree debug-for-globals support files (data/debug/session/config_stubs).
Tests: cg-api, toy (1392/0), opt, isa, smoke-x64 all green.
Diffstat:
17 files changed, 577 insertions(+), 306 deletions(-)
diff --git a/doc/plan/PERF.md b/doc/plan/PERF.md
@@ -30,23 +30,30 @@ release kit at `build/release/kit` (`make bin RELEASE=1`).
## 1. Current standings
-### Compile speed — the open frontier (~2.8× tcc)
+### Compile speed — the open frontier (~2.7× tcc)
-Apple-silicon arm64 / macOS. **`instructions` is the metric to trust** —
-load-independent (`/usr/bin/time -l`, best-of-7); cycles/wall are low-load
-readings (`sysctl -n vm.loadavg`) shown only for context.
+Apple-silicon arm64 / Darwin 25.3, refreshed 2026-06-15. **`instructions` is the
+metric to trust** — load-independent (`/usr/bin/time -l`, best-of-7);
+cycles/wall are low-load readings shown only for context. The kit row is **after
+the CG-type-interaction pass** (see §3); the object is byte-identical to the
+pre-pass build (1.70 MB unchanged), only the compiler's own instruction count moved.
| compiler | instructions | cycles † | wall † | object |
|---|--:|--:|--:|--:|
-| **tcc 0.9.28** | 0.66 B | 0.19 B | 0.06 s | 2.11 MB |
-| **kit** | 1.84 B | 0.53 B | 0.16 s | 1.83 MB |
-| clang 22 | 8.73 B | 2.57 B | 0.81 s | 1.50 MB |
-
-† low-load; instructions is the figure to trust.
-
-kit beats clang and is the fastest *general* backend, but **tcc is ~2.8×
+| **tcc 0.9.28rc** | 0.662 B | 0.193 B | 0.06 s | 2.11 MB |
+| **kit** | 1.760 B | 0.578 B | 0.18 s | 1.70 MB |
+| Apple clang 21 | 8.818 B | 2.799 B | 0.91 s | 1.50 MB |
+
+† low-load; instructions is the figure to trust. kit instructions over the
+CG-type-interaction pass: **1.939 B → 1.864 B** (slot-query dedup + memory-align
+contract + builtin ABI-layout cache + resolve/predicate fast paths) **→ 1.760 B**
+(`abi_cg_type_info` inline builtin fast path + by-pointer layout cache) =
+**−179 M total (−9.2 %)**, all byte-identical (the sqlite object is bit-for-bit
+unchanged).
+
+kit beats clang and is the fastest *general* backend, but **tcc is ~2.7×
(instructions) ahead** — this is the whole game. **The gap is instructions, not
-cache:** kit's IPC (~3.5) is on par with tcc's (~3.5), so both are compute-bound
+cache:** kit's IPC (~3.4) is on par with tcc's (~3.4), so both are compute-bound
on a wide core and there is no hidden cache-miss penalty to claw back. The payoff
mechanism is **fewer retired instructions** (fewer copies, indirect calls,
redundant recomputations) — denser structures help only because they cost fewer
@@ -64,39 +71,42 @@ load/store/move *instructions*.
> a CG loc per statement) and the first cut built the line index with a scalar
> `\n` rescan + a per-loc binary search. Fixing the *implementation* of laziness
> (build the index with memchr; cursor the materializer — §4.1) turned it into a
-> net **−24 M `-c` win** (1.865 B → 1.841 B), since the scanner no longer builds a
-> `SrcLoc` per token. The remaining ~+19 M parse+CG residual (per-statement loc
-> lookups the old eager-on-token loc got for free) is the optional Stage-2 target
-> in §4.1.
+> campaign-local **−24 M `-c` win** (1.865 B → 1.841 B), since the scanner no
+> longer builds a `SrcLoc` per token. The remaining ~+19 M parse+CG residual
+> (per-statement loc lookups the old eager-on-token loc got for free) is the
+> optional Stage-2 target in §4.1.
-### Code size — ≈ parity (1.044× tcc)
+### Code size — below tcc (0.951×)
The honest metric is **`.text` machine code** (the object file is format-skewed —
Mach-O vs tcc's ELF — and not comparable; kit's *object* is actually smaller).
| metric | kit | tcc | ratio |
|---|--:|--:|--:|
-| **`.text`** | **1,430,828 B / 357,707 insns** | **1,370,940 B / 342,735 insns** | **1.044×** (+14,972 insns) |
+| **`.text`** | **1,303,352 B / 325,838 4-byte slots** | **1,370,940 B / 342,735 4-byte slots** | **0.951×** (−16,897 slots) |
-Where the remaining `.text` excess lives (per-mnemonic, kit − tcc on sqlite):
+kit now clears the code-size bar overall. The remaining local excess is still in
+the same instruction families (per-mnemonic, kit − tcc on sqlite):
| heavier in kit | Δ | what it is |
|---|--:|---|
-| `stur`+`ldur`+`str` (spills) | **+58,567** | **#1: register pressure** — the single-pass NDT spills more than tcc keeps resident across statements / control-flow joins |
-| `mov` | **+32,487** | residual arg / value-stack copies the arg0-first placement can't reach (nested-call results already in x0, pressure spills) |
-| `sub`+`movk` | +5,048 | residual far-frame addressing (byte/half slots, `&local` in big frames) |
+| `stur`+`ldur`+`str` (spills) | **+47,095** | **#1: register pressure** — the single-pass NDT spills more than tcc keeps resident across statements / control-flow joins |
+| `mov` | **+13,666** | residual arg / value-stack copies the arg0-first placement can't reach (nested-call results already in x0, pressure spills) |
+| `sub`+`movk` | +3,474 | residual far-frame addressing (byte/half slots, `&local` in big frames) |
…offset by where **kit already beats tcc** (structural wins — do not touch):
-`add` −21,713 (folds offsets into displacements), `ldr` −21,814 (far slots fold
-into `[sp,#scaled]`), `cset` −17,226 / `cbnz` −10,817 / `cmp` −9,421 (kit fuses
-compares into `cbz`/`b.cc`; tcc materializes a bool then tests), `nop` −4,704.
-Net +14,972.
+`ldr` −24,214 (far slots fold into `[sp,#scaled]`), `add` −20,007 (folds offsets
+into displacements), `cset` −17,226 / `cbnz` −10,817 / `cmp` −9,421 (kit fuses
+compares into `cbz`/`b.cc`; tcc materializes a bool then tests), `movn` −6,720 /
+`movz` −4,644 / `stp` −3,722, and `nop` −246. The mnemonic histogram nets
+−17,309 decoded instructions; kit also has 412 4-byte padding slots, so the byte
+metric nets −16,897 slots.
---
## 2. Where the cost is (current profile)
-### Compile is frontend-bound — lex+pp ~2.1× tcc, post-PP ~3.8× tcc
+### Compile is frontend-bound — lex+pp ~2.0× tcc, post-PP ~4.3× tcc
Phase split measured directly (instructions, best-of-7), not estimated from `-E`.
`-E` is a **bad** lex+pp proxy: it re-serializes the token stream to text, work
@@ -110,17 +120,17 @@ compilers (tcc: `-bench` hook, patched to use `-c` `parse_flags`; kit:
| phase | kit `-c` | tcc `-c` | kit / tcc |
|---|--:|--:|--:|
-| **lex + pp** | **0.82 B** | **0.39 B** | **~2.1×** |
-| parse + sema + types + CG-drive | 0.76 B | ┐ 0.27 B | — |
+| **lex + pp** | **0.82 B** | **0.40 B** | **~2.0×** |
+| parse + sema + types + CG-drive | 0.86 B | ┐ 0.26 B | — |
| native emit + object write | 0.26 B | ┘ (post-PP) | — |
-| **post-PP total** | **1.02 B** | **0.27 B** | **~3.8×** |
-| **total** | **1.84 B** | **0.66 B** | **~2.8×** |
+| **post-PP total** | **1.12 B** | **0.26 B** | **~4.3×** |
+| **total** | **1.94 B** | **0.66 B** | **~2.9×** |
So **both halves are real frontiers** — lex+pp is *not* at parity (an earlier
claim from the misleading `-E` proxy was wrong). The post-PP ratio is larger, but
-lex+pp is ~44 % of kit's `-c` and a clean 2.1× behind. Note kit's lex+pp drain
+lex+pp is ~42 % of kit's `-c` and a clean 2.0× behind. Note kit's lex+pp drain
does *less* than tcc's — kit defers number/string decode to the parser (tcc decodes
-in the lexer; bare tcc scan+expand is 0.38 B, +decode 0.39 B) — yet is still ~2.1×
+in the lexer; bare tcc scan+expand is 0.38 B, +decode 0.40 B) — yet is still ~2.0×
heavier, so the gap is pure per-token engine overhead, not extra work:
- **tcc: one mutable global token** (`int tok` + `CValue tokc` + `tok_flags`) —
@@ -141,45 +151,73 @@ heavier, so the gap is pure per-token engine overhead, not extra work:
state per token. This is the [[frontend-instruction-halving-pathb]] lever: fewer
per-token ops, not less work.
-The post-PP 3.8× is the bigger slice: tcc drives a thin `SValue[]` straight into a
+The post-PP 4.3× is the bigger slice: tcc drives a thin `SValue[]` straight into a
one-pass emitter, while kit routes through the `CgTarget`→`NativeTarget`→`MCEmitter`
seam and a richer type/ABI layer (§4). Closing either is a campaign, not one hot
function.
-Net of the lean-token rewrite + its loc-materialization fix (golden→now,
+In the lean-token rewrite + loc-materialization fix A/B (golden→candidate,
best-of-7): **lex+pp −44 M** (the scanner no longer builds a `SrcLoc` per token —
`loc` is a byte offset), **parse+sema+CG +19 M** (the residual: a per-statement
cursor lookup at `pcg_set_loc` the old eager-on-token loc got for free), emit
flat → **−24 M total (−1.3 %)**.
**Linux callgrind** (inclusive, instruction-grounded — the tool that sees what
-wall-clock `sample` hides; total **1.652 B `Ir`**, glibc/ELF; self-`Ir` summed
-across callgrind's `'2` symbol splits):
+wall-clock `sample` hides; total **1.566 B `Ir`**, glibc/ELF; self-`Ir` summed
+across callgrind's `'2` symbol splits). This is **after the CG-type-interaction
+pass** (slot-query dedup + memory-align contract + builtin ABI-layout cache +
+resolve/predicate fast paths + the `api_type_layout_get` follow-up): the type
+cluster went **1.741 B → 1.608 B → 1.566 B (−175 M, −10.1 %)**, all
+byte-identical (perf-identity gate green on every non-`-g` category, and the
+sqlite object is bit-for-bit unchanged; the `-g` delta is the separate
+debug-for-globals feature).
| self % | function(s) | subsystem |
|--:|---|---|
-| **11.7** | `lex_next` | scanner |
-| 4.5 / 3.9 | `src_next_raw_into` / `pool_intern_slice` | pp + interning |
-| 3.8 | `pp_pull_into` | preprocessor |
-| 3.0 / 3.0 | `api_unalias_type` / `api_type_pred_bits` | types |
-| 2.6 | `finish_ident` | scanner |
-| 1.4 / 1.0×2 | `cg_type_get` / `resolve_type`,`api_type_class` | types |
+| **12.3** | `lex_next` | scanner |
+| 6.3 | `malloc` | hosted heap |
+| 4.7 / 4.3 | `src_next_raw_into` / `pool_intern_slice` | pp + interning |
+| 4.1 | `pp_pull_into` | preprocessor |
+| 2.7 | `finish_ident` | scanner |
+| 1.9 / 1.6 / 0.3 | `__GI_memset` / `__GI_memchr` / `__GI_memcpy` | libc memory |
+| 1.9 / 1.8 | `abi_cg_type_info` / `api_type_pred_bits` | types/ABI |
+| 1.1 / 1.1 / 1.0 | `api_const_from_sv` / `pp_materialize_loc` / `api_sv_adjust_refs` | value-stack / lazy-loc |
+| 1.1 / 1.0 / 0.97 / 0.4 | `cg_type_get` / `api_type_layout_ref` / `api_type_class` / `resolve_type` | types |
| 1.0 | `aa_emit_mem` | codegen |
-| **0.9** | `pp_materialize_loc` | lazy-loc lookup (was 2.6 % — now cursored) |
-| **0.6** | `srcinfo_build_lines` | lazy-loc index build (was 5.3 % — now memchr) |
-
-**Subsystem rollup:** scanner ~18 %, types ~13 %, preprocessor ~11 %, interning
-~4 %, lazy-loc (`pp_materialize_loc` + `srcinfo_build_lines`) ~1.5 %, codegen/emit
-~3 %. The scanner is leaner than pre-rewrite (line tracking + eager literal/punct
-interning left `lex_next`); the lazy-loc machinery that briefly dominated is now
-sub-2 %. Trust callgrind for *where*; trust macOS instructions for *how much*.
-
-### Code size is spill-bound
-
-After arg-into-arg-register placement, the `.text` excess is dominated by
-**spills** (register pressure in the single-pass cache, +58 K) then residual
-**`mov`s** (+32 K). Both concentrate in the giant functions (`_sqlite3VdbeExec`
-etc.) where per-function pressure is highest.
+| 0.75 (Σ) | `kit_cg_slot_lang_type` 0.52 / `kit_cg_slot_lang_flags` 0.15 / `kit_cg_slot_cg_type` 0.09 | CG slot queries (zero-copy accessors) |
+
+**What moved (vs the pre-pass profile above each arrow):**
+`kit_cg_slot_info` **3.2 % → gone** (the by-value struct copy is retired; the C
+frontend now reads single facts through the narrow accessors above, Σ 0.75 %),
+`cg_type_get` 3.0 → 1.1, `resolve_type` 1.8 → 0.4, `__GI_memset` 3.0 → 1.9. The
+ABI-layout lookup was first **consolidated** into `api_type_layout_get` (1.3 →
+3.9 in the intermediate profile) — one memoized indexed load per type for
+builtins *and* user types instead of the `abi_cg_type_info_compute` →
+`cg_type_get` → per-call `resolve_type` spread — then the **follow-up** retired
+that 5-field out-param marshalling: `api_type_layout_ref` returns a *borrowed
+pointer* to the cached `ABITypeInfo`, and `abi_cg_type_info` resolves a builtin
+with **one inline indexed load** off `Compiler.cg_builtin_layout` (no cross-TU
+call). Combined `abi_cg_type_info` + layout cache: **5.5 % → 2.9 % (−43 M, the
+whole program-total drop this step)**; `api_type_layout_get` 3.9 → `_ref` 1.0,
+`abi_cg_type_info` 1.6 → 1.9 (it absorbed the inline load). Net of the targeted
+leaves: **15.0 % → ~9 %**.
+
+**Subsystem rollup:** scanner ~15 % (`lex_next`+`finish_ident`), pp+interning
+~13 % (`src_next_raw_into`+`pp_pull_into`+`pool_intern_slice`), CG/types/ABI
+metadata ~9 % (down from ~15 %), libc allocation/memory helpers ~10 %, lazy-loc
+(`pp_materialize_loc`) ~1 %, direct codegen emit ~1 %. The type layer is no longer
+a top-tier frontier — the remaining leaves (`abi_cg_type_info` register-struct
+returns, `api_type_pred_bits`, `cg_type_get`) are already at/under ~2 % and were
+explicitly *not* micro-optimized. The next frontiers are the scanner (`lex_next`
+~15 %) and the hosted heap (`malloc` 6 %). Trust callgrind for *where*; trust
+macOS instructions for *how much*.
+
+### Code size is still locally spill-bound
+
+Even with aggregate `.text` now smaller than tcc, the remaining per-mnemonic
+excess is dominated by **spills** (register pressure in the single-pass cache,
++47 K) then residual **`mov`s** (+14 K). Both concentrate in the giant functions
+(`_sqlite3VdbeExec` etc.) where per-function pressure is highest.
---
@@ -391,15 +429,15 @@ optimal); `aa_emit_mem` typed-store micro-levers.
### 4.2 Code size (run-correctness-gated; multiplicative — each byte cut shrinks emit + objwrite + assemble together)
-1. **Spill reduction — the #1 remaining `.text` excess (+58 K).** The single-pass
- NDT spills more than tcc keeps resident across statements and control-flow
- joins. The no-new-analysis wins (dead-operand drop, materialize-before-flush)
- are done; deeper residency = keeping a *live* value in a register across a join,
- which is register allocation and out of simple single-pass scope. Pursue as a
- separate `-O1`-style pass over the NDT, or a cheaper "pin the hot local across a
- straight-line run" heuristic.
+1. **Spill reduction — the #1 remaining local code-size excess (+47 K).** The
+ single-pass NDT spills more than tcc keeps resident across statements and
+ control-flow joins. The no-new-analysis wins (dead-operand drop,
+ materialize-before-flush) are done; deeper residency = keeping a *live* value
+ in a register across a join, which is register allocation and out of simple
+ single-pass scope. Pursue as a separate `-O1`-style pass over the NDT, or a
+ cheaper "pin the hot local across a straight-line run" heuristic.
2. **Positive-offset frame addressing** (kills the residual `sub xN,x29`+`movk`,
- ~5 K). The machinery already exists — aa64 has a bottom-record layout
+ ~3.5 K). The machinery already exists — aa64 has a bottom-record layout
(`fp_at_bottom`, `frame_size−off`) + an `AAPatch` deferred-patch list, currently
gated to `-O1` known-frame. Extend to `-O0` by deferring per-slot offsets (emit
scaled `str`/`ldr` with an imm12 placeholder, patch `(N−off)>>scale` at
@@ -407,7 +445,7 @@ optimal); `aa_emit_mem` typed-store micro-levers.
word). alloca-safe via a stable base anchor (the fp-at-bottom / a callee-saved
frame base plays tcc's stable-x29 role). Compile-speed risk (per-access
bookkeeping) — gate it.
-3. **Residual arg/value `mov`s (+32 K).** Forward-order materialization places
+3. **Residual arg/value `mov`s (+13.7 K).** Forward-order materialization places
straightforward args directly in their ABI register; what's left is args it
can't place — a nested-call result already in x0, or a value spilled under
pressure. A per-arg **destination hint** (abstract arg-slot threaded
diff --git a/include/kit/cg.h b/include/kit/cg.h
@@ -889,8 +889,16 @@ typedef struct KitCgSlotInfo {
/* Inspect and update frontend-owned facts on value-stack slots. CG treats
* lang_type as opaque and lang_flags as frontend-defined bits; stack-producing
* operations clear them, while structural stack operations copy/move them with
- * the slot. depth_from_top 0 names TOS. */
+ * the slot. depth_from_top 0 names TOS.
+ *
+ * kit_cg_slot_info returns the whole {cg_type, lang_type, lang_flags} fact by
+ * value (a zeroed struct when the slot does not exist). The narrower accessors
+ * read one field directly with no copy and return zero/NULL when the slot does
+ * not exist; prefer them on hot paths that need a single fact. */
KIT_API KitCgSlotInfo kit_cg_slot_info(KitCg*, uint32_t depth_from_top);
+KIT_API KitCgTypeId kit_cg_slot_cg_type(KitCg*, uint32_t depth_from_top);
+KIT_API const void* kit_cg_slot_lang_type(KitCg*, uint32_t depth_from_top);
+KIT_API uint16_t kit_cg_slot_lang_flags(KitCg*, uint32_t depth_from_top);
KIT_API void kit_cg_retag_top(KitCg*, const void* lang_type,
uint16_t lang_flags);
KIT_API void kit_cg_retag_at(KitCg*, uint32_t depth_from_top,
diff --git a/lang/c/parse/cg.c b/lang/c/parse/cg.c
@@ -12,10 +12,6 @@ KitCgTypeId c_cg_tid(Parser* p, const Type* ty) {
return type_cg_id_in_pool(p->c, p->pool, ty);
}
-static KitCgSlotInfo c_cg_slot(Parser* p, u32 depth) {
- return kit_cg_slot_info(p->cg, depth);
-}
-
static uint16_t c_cg_lvalue_flags_for_type(const Type* ty) {
uint16_t flags = C_CG_VALUE_LVALUE;
if (ty && !(ty->qual & Q_CONST) && ty->kind != TY_ARRAY &&
@@ -25,24 +21,25 @@ static uint16_t c_cg_lvalue_flags_for_type(const Type* ty) {
return flags;
}
-static KitCgTypeId c_cg_slot_cg_id(Parser* p, u32 depth, const Type* fallback) {
- KitCgSlotInfo info = c_cg_slot(p, depth);
- return info.cg_type ? info.cg_type : c_cg_tid(p, fallback);
+/* Resolve the CG type id for a value-stack slot: the slot's recorded cg type
+ * when present, else map the frontend lang_type. kit_cg_slot_cg_type reads the
+ * slot's type directly (no struct copy); the lang_type fallback is taken only
+ * when the slot carries no cg type. */
+static KitCgTypeId c_cg_slot_cg_id(Parser* p, u32 depth) {
+ KitCgTypeId cg = kit_cg_slot_cg_type(p->cg, depth);
+ if (cg) return cg;
+ return c_cg_tid(p, (const Type*)kit_cg_slot_lang_type(p->cg, depth));
}
-static KitCgTypeId c_cg_top_cg_id(Parser* p) {
- return c_cg_slot_cg_id(p, 0, c_cg_top_type(p));
-}
+static KitCgTypeId c_cg_top_cg_id(Parser* p) { return c_cg_slot_cg_id(p, 0); }
-static KitCgTypeId c_cg_top2_cg_id(Parser* p) {
- return c_cg_slot_cg_id(p, 1, c_cg_top2_type(p));
-}
+static KitCgTypeId c_cg_top2_cg_id(Parser* p) { return c_cg_slot_cg_id(p, 1); }
static KitCgMemAccess c_cg_mem_id(Parser* p, KitCgTypeId id, const Type* ty) {
KitCgMemAccess m;
+ (void)p;
memset(&m, 0, sizeof m);
m.type = id;
- m.align = (u32)kit_cg_type_align(p->c, id);
if (ty && (ty->qual & Q_VOLATILE)) m.flags |= KIT_CG_MEM_VOLATILE;
if (type_is_int(ty) && c_cg_type_is_signed(ty))
m.flags |= KIT_CG_MEM_SOURCE_SIGNED;
@@ -54,11 +51,11 @@ KitCgMemAccess c_cg_mem(Parser* p, const Type* ty) {
}
const Type* c_cg_top_type(Parser* p) {
- return (const Type*)c_cg_slot(p, 0).lang_type;
+ return (const Type*)kit_cg_slot_lang_type(p->cg, 0);
}
const Type* c_cg_top2_type(Parser* p) {
- return (const Type*)c_cg_slot(p, 1).lang_type;
+ return (const Type*)kit_cg_slot_lang_type(p->cg, 1);
}
void c_cg_retag_top(Parser* p, const Type* ty) {
@@ -70,8 +67,7 @@ void c_cg_retag_at(Parser* p, u32 depth, const Type* ty, uint16_t flags) {
}
void c_cg_retag_keep_flags(Parser* p, u32 depth, const Type* ty) {
- KitCgSlotInfo info = c_cg_slot(p, depth);
- kit_cg_retag_at(p->cg, depth, ty, info.lang_flags);
+ kit_cg_retag_at(p->cg, depth, ty, kit_cg_slot_lang_flags(p->cg, depth));
}
void c_cg_dup(Parser* p) { kit_cg_dup(p->cg); }
@@ -89,7 +85,7 @@ void c_cg_drop_to_depth(Parser* p, uint32_t depth) {
}
int c_cg_top_is_bitfield(Parser* p) {
- return (c_cg_slot(p, 0).lang_flags & C_CG_VALUE_BITFIELD) != 0;
+ return (kit_cg_slot_lang_flags(p->cg, 0) & C_CG_VALUE_BITFIELD) != 0;
}
void c_cg_set_top_bitfield(Parser* p) {
@@ -97,7 +93,7 @@ void c_cg_set_top_bitfield(Parser* p) {
}
int c_cg_top_is_register(Parser* p) {
- return (c_cg_slot(p, 0).lang_flags & C_CG_VALUE_REGISTER) != 0;
+ return (kit_cg_slot_lang_flags(p->cg, 0) & C_CG_VALUE_REGISTER) != 0;
}
void c_cg_set_top_register(Parser* p) {
@@ -105,17 +101,17 @@ void c_cg_set_top_register(Parser* p) {
}
int c_cg_top_is_lvalue(Parser* p) {
- return (c_cg_slot(p, 0).lang_flags & C_CG_VALUE_LVALUE) != 0;
+ return (kit_cg_slot_lang_flags(p->cg, 0) & C_CG_VALUE_LVALUE) != 0;
}
int c_cg_top_is_modifiable_lvalue(Parser* p) {
- uint16_t flags = c_cg_slot(p, 0).lang_flags;
+ uint16_t flags = kit_cg_slot_lang_flags(p->cg, 0);
return (flags & (C_CG_VALUE_LVALUE | C_CG_VALUE_MODIFIABLE)) ==
(C_CG_VALUE_LVALUE | C_CG_VALUE_MODIFIABLE);
}
int c_cg_top_is_null_ptr_const(Parser* p) {
- return (c_cg_slot(p, 0).lang_flags & C_CG_VALUE_NULL_PTR_CONST) != 0;
+ return (kit_cg_slot_lang_flags(p->cg, 0) & C_CG_VALUE_NULL_PTR_CONST) != 0;
}
void c_cg_set_top_lvalue(Parser* p) {
diff --git a/lang/c/parse/cg.h b/lang/c/parse/cg.h
@@ -5,7 +5,7 @@
*
* Declares C-semantic helpers implemented in cg.c. The expression stack is the
* public KitCg stack; C type/category facts live in KitCg slot metadata and
- * are read back through kit_cg_slot_info. */
+ * are read back through the KitCg slot inspection helpers. */
#include <kit/cg.h>
diff --git a/src/abi/abi.c b/src/abi/abi.c
@@ -85,17 +85,25 @@ static ABITypeInfo abi_cg_type_info_compute(TargetABI* a, KitCgTypeId id) {
}
ABITypeInfo abi_cg_type_info(TargetABI* a, KitCgTypeId id) {
- ABITypeInfo r = {0, 0, ABI_SC_VOID, 0, 0, 0};
- if (!id) return r;
- /* Memoized per type id: collapses the alias/enum/array recursion and the
- * several repeats per statement to one load (builtins stay uncached — their
- * compute is already a single indexed load). See api_type_layout_get. */
- if (api_type_layout_get(a->c, id, &r.size, &r.align, &r.scalar_kind,
- &r.signed_, &r.atomic))
- return r;
+ const ABITypeInfo* p;
+ ABITypeInfo r;
+ if (!id) {
+ ABITypeInfo z = {0, 0, ABI_SC_VOID, 0, 0, 0};
+ return z;
+ }
+ /* Builtins are the overwhelming majority of type queries: index the
+ * per-compiler layout table inline (one load, no cross-TU call) once cg_api
+ * has published it. Before that, fall through to the general path, which
+ * lazily creates the table. */
+ if (id <= KIT_CG_BUILTIN_COUNT && a->c->cg_builtin_layout)
+ return ((const ABITypeInfo*)a->c->cg_builtin_layout)[id - 1u];
+ /* User types: a borrowed pointer to the cached layout (no field-by-field
+ * copy) collapses the alias/enum/array recursion to one load; a miss computes
+ * once and caches. */
+ p = api_type_layout_ref(a->c, id);
+ if (p) return *p;
r = abi_cg_type_info_compute(a, id);
- api_type_layout_put(a->c, id, r.size, r.align, r.scalar_kind, r.signed_,
- r.atomic);
+ api_type_layout_put(a->c, id, r);
return r;
}
diff --git a/src/api/config_stubs.c b/src/api/config_stubs.c
@@ -170,6 +170,11 @@ DebugTypeBuilder* debug_type_record_begin(Debug* d, Sym tag, int is_union,
return NULL;
}
+DebugTypeId debug_type_record_id(DebugTypeBuilder* b) {
+ (void)b;
+ return DEBUG_TYPE_NONE;
+}
+
void debug_type_record_field(DebugTypeBuilder* b, Sym name, DebugTypeId type,
u32 byte_offset) {
(void)b;
@@ -265,6 +270,16 @@ void debug_local(Debug* d, Sym name, DebugTypeId type, SrcLoc loc,
(void)var_loc;
}
+void debug_global(Debug* d, Sym name, DebugTypeId type, SrcLoc loc,
+ DebugVarLoc var_loc, int external) {
+ (void)d;
+ (void)name;
+ (void)type;
+ (void)loc;
+ (void)var_loc;
+ (void)external;
+}
+
void debug_line(Debug* d, ObjSecId text_section_id, u32 text_offset, SrcLoc loc,
int is_stmt) {
(void)d;
@@ -649,14 +664,16 @@ KitStatus kit_dbg_session_new(KitJit* jit, const KitDbgHost* host,
void kit_dbg_session_free(KitDebugSession* s) { (void)s; }
-KitStatus kit_dbg_session_attach_dwarf(KitDebugSession* s, KitDebugInfo* dwarf) {
+KitStatus kit_dbg_session_attach_dwarf(KitDebugSession* s,
+ KitDebugInfo* dwarf) {
(void)s;
(void)dwarf;
return KIT_UNSUPPORTED;
}
-KitStatus kit_dbg_session_call(KitDebugSession* s, void* entry, KitEntryKind kind,
- int argc, char** argv, KitStopInfo* stop_out) {
+KitStatus kit_dbg_session_call(KitDebugSession* s, void* entry,
+ KitEntryKind kind, int argc, char** argv,
+ KitStopInfo* stop_out) {
(void)s;
(void)entry;
(void)kind;
@@ -715,7 +732,8 @@ KitStatus kit_dbg_session_get_regs(KitDebugSession* s, KitUnwindFrame* out) {
return KIT_UNSUPPORTED;
}
-KitStatus kit_dbg_session_set_regs(KitDebugSession* s, const KitUnwindFrame* in) {
+KitStatus kit_dbg_session_set_regs(KitDebugSession* s,
+ const KitUnwindFrame* in) {
(void)s;
(void)in;
return KIT_UNSUPPORTED;
@@ -889,7 +907,8 @@ KitStatus kit_dbg_resolve(KitDebugSession* s, KitSlice spec, uint64_t* addr_out,
return KIT_UNSUPPORTED;
}
-KitStatus kit_dbg_break(KitDebugSession* s, KitSlice spec, uint32_t* bp_id_out) {
+KitStatus kit_dbg_break(KitDebugSession* s, KitSlice spec,
+ uint32_t* bp_id_out) {
(void)s;
(void)spec;
(void)bp_id_out;
diff --git a/src/cg/atomic.c b/src/cg/atomic.c
@@ -3,15 +3,15 @@
MemAccess api_mem_for_atomic(KitCg* g, KitCgTypeId val_ty) {
MemAccess ma;
- api_require_scalar_mem_type(g, "atomic memory access", val_ty);
- if (api_mem_type_size(g, val_ty, "atomic memory access") >
- CG_MAX_ATOMIC_SIZE) {
+ u32 size =
+ api_require_scalar_mem_type_resolved(g, "atomic memory access", val_ty);
+ if (size > CG_MAX_ATOMIC_SIZE) {
compiler_panic(g->c, g->cur_loc,
"KitCg: atomic memory access size exceeds 8 bytes");
}
memset(&ma, 0, sizeof ma);
ma.type = val_ty;
- ma.size = val_ty ? abi_cg_sizeof(g->c->abi, val_ty) : 0;
+ ma.size = size;
ma.align = val_ty ? abi_cg_alignof(g->c->abi, val_ty) : 0;
ma.flags = MF_ATOMIC;
ma.alias.kind = (u8)ALIAS_UNKNOWN;
@@ -46,7 +46,7 @@ int kit_cg_atomic_is_legal(KitCompiler* c, KitCgMemAccess access,
if (cg_type_is_aggregate(c, ty) || cg_type_is_void(c, ty)) return 0;
/* Still legal up to 8 bytes everywhere: the libcall path makes 8-byte atomics
* available even when they are not lock-free. */
- return abi_cg_sizeof(c->abi, access.type) <= CG_MAX_ATOMIC_SIZE;
+ return abi_cg_sizeof(c->abi, ty) <= CG_MAX_ATOMIC_SIZE;
}
int kit_cg_atomic_is_lock_free(KitCompiler* c, KitCgMemAccess access) {
@@ -56,7 +56,7 @@ int kit_cg_atomic_is_lock_free(KitCompiler* c, KitCgMemAccess access) {
/* Lock-free up to the native atomic width, NOT the pointer width: wasm32 has
* 4-byte pointers but lowers 8-byte (i64) atomics lock-free, while rv32 does
* not have native 64-bit atomics. */
- return abi_cg_sizeof(c->abi, access.type) <= cg_atomic_lock_free_max(c);
+ return abi_cg_sizeof(c->abi, ty) <= cg_atomic_lock_free_max(c);
}
/* True when an atomic access of `val_ty` must be lowered to a libatomic
@@ -192,7 +192,8 @@ void kit_cg_atomic_store(KitCg* g, KitCgMemAccess access, KitCgMemOrder order) {
return;
}
api_require_pointer_value(g, "atomic_store pointer", pty);
- api_validate_memory_value(g, "atomic_store", val_ty, api_sv_type(&val));
+ api_validate_memory_value_resolved(g, "atomic_store", val_ty,
+ api_sv_type(&val));
if (cg_atomic_needs_libcall(g, val_ty)) {
/* void __atomic_store_8(void* ptr, u64 val, int memorder) */
KitCgTypeId i32 = builtin_id(KIT_CG_BUILTIN_I32);
@@ -237,7 +238,8 @@ void kit_cg_atomic_rmw(KitCg* g, KitCgMemAccess access, KitCgAtomicOp op,
return;
}
api_require_pointer_value(g, "atomic_rmw pointer", pty);
- api_validate_memory_value(g, "atomic_rmw", val_ty, api_sv_type(&val));
+ api_validate_memory_value_resolved(g, "atomic_rmw", val_ty,
+ api_sv_type(&val));
if (cg_atomic_needs_libcall(g, val_ty)) {
/* u64 __atomic_{exchange,fetch_*}_8(void* ptr, u64 val, int memorder).
* All return the prior value, matching native atomic_rmw semantics. */
@@ -299,10 +301,10 @@ void kit_cg_atomic_cmpxchg(KitCg* g, KitCgMemAccess access,
return;
}
api_require_pointer_value(g, "atomic_cmpxchg pointer", pty);
- api_validate_memory_value(g, "atomic_cmpxchg expected", val_ty,
- api_sv_type(&expected));
- api_validate_memory_value(g, "atomic_cmpxchg desired", val_ty,
- api_sv_type(&desired));
+ api_validate_memory_value_resolved(g, "atomic_cmpxchg expected", val_ty,
+ api_sv_type(&expected));
+ api_validate_memory_value_resolved(g, "atomic_cmpxchg desired", val_ty,
+ api_sv_type(&desired));
if (cg_atomic_needs_libcall(g, val_ty)) {
/* bool __atomic_compare_exchange_8(void* ptr, void* expected, u64 desired,
* int succ, int fail).
diff --git a/src/cg/data.c b/src/cg/data.c
@@ -115,6 +115,30 @@ static void api_data_mark_defined_unit(KitCg* g, ObjSymId sym) {
g->sym_def_seq[sym] = g->cur_unit_seq;
}
+static void api_data_debug_object_def(KitCg* g, ObjSymId sym,
+ const KitCgDecl* decl, KitCgTypeId type) {
+ DebugVarLoc loc;
+ DebugTypeId dbg_type;
+ Sym name;
+ if (!g || !g->debug || sym == OBJ_SYM_NONE || !decl ||
+ decl->kind != KIT_CG_DECL_OBJECT) {
+ return;
+ }
+ name = (Sym)(decl->display_name ? decl->display_name : decl->linkage_name);
+ if (!name) return;
+ dbg_type = api_debug_type(g, decl->as.object.debug_type, type);
+ if (dbg_type == DEBUG_TYPE_NONE) return;
+ memset(&loc, 0, sizeof loc);
+ loc.kind = DVL_GLOBAL;
+ loc.v.global = sym;
+ if (g->fn_ret_type != KIT_CG_TYPE_NONE) {
+ debug_local(g->debug, name, dbg_type, g->cur_loc, loc);
+ } else {
+ debug_global(g->debug, name, dbg_type, g->cur_loc, loc,
+ decl->sym.bind != KIT_SB_LOCAL);
+ }
+}
+
static void api_data_tls_ensure_materialized(KitCg* g) {
if (!g || !g->data_tls_collect || !g->data_tls_zero_fill) return;
if (g->data_size) api_data_tls_write_zero(g, g->data_size);
@@ -227,6 +251,7 @@ void kit_cg_data_begin(KitCg* g, KitCgSym cg_sym, KitCgDataDefAttrs attrs) {
g->data_base = 0;
g->data_size = 0;
g->data_local_static_target = 1;
+ api_data_debug_object_def(g, sym, &decl_attrs, ty);
return;
}
}
@@ -328,6 +353,7 @@ void kit_cg_data_begin(KitCg* g, KitCgSym cg_sym, KitCgDataDefAttrs attrs) {
obj_symbol_define(ob, sym, sec, (u64)g->data_base,
(u64)abi_cg_sizeof(c->abi, decl_attrs.type));
api_data_mark_defined_unit(g, sym);
+ api_data_debug_object_def(g, sym, &decl_attrs, ty);
}
}
@@ -374,6 +400,7 @@ void kit_cg_data_common(KitCg* g, KitCgSym cg_sym, uint64_t size,
osym->size = size;
osym->common_align = align;
api_data_mark_defined_unit(g, sym);
+ api_data_debug_object_def(g, sym, &decl_attrs, decl_attrs.type);
}
void kit_cg_data_align(KitCg* g, uint32_t align) {
@@ -735,6 +762,8 @@ void kit_cg_data_end(KitCg* g) {
g->data_tls_zero_fill ? NULL : flat, (u32)g->data_size,
g->data_tls_zero_fill ? 0 : 1, g->data_tls_align,
g->data_tls_relocs, g->data_tls_nrelocs);
+ api_data_mark_defined_unit(g, g->data_sym);
+ api_data_debug_object_def(g, g->data_sym, &decl_attrs, decl_attrs.type);
if (flat) h->free(h, flat, (size_t)g->data_size);
buf_fini(&g->data_tls_bytes);
if (g->data_tls_relocs)
diff --git a/src/cg/debug.c b/src/cg/debug.c
@@ -21,6 +21,56 @@ static DebugBaseEncoding debug_base_encoding(KitCgDebugEncoding enc) {
return DEBUG_BE_SIGNED;
}
+static int api_debug_type_cache_ensure(KitCg* g, KitCgTypeId id) {
+ Heap* h;
+ DebugTypeId* nt;
+ u8* ns;
+ u32 cap;
+ if (!g || id == KIT_CG_TYPE_NONE) return 0;
+ if (id < g->debug_type_cap) return 1;
+ h = (Heap*)g->c->ctx->heap;
+ cap = g->debug_type_cap ? g->debug_type_cap : 64u;
+ while (cap <= id) cap *= 2u;
+ nt = (DebugTypeId*)h->alloc(h, sizeof(*nt) * cap, _Alignof(DebugTypeId));
+ ns = (u8*)h->alloc(h, sizeof(*ns) * cap, _Alignof(u8));
+ if (!nt || !ns) {
+ if (nt) h->free(h, nt, sizeof(*nt) * cap);
+ if (ns) h->free(h, ns, sizeof(*ns) * cap);
+ return 0;
+ }
+ memset(nt, 0, sizeof(*nt) * cap);
+ memset(ns, 0, sizeof(*ns) * cap);
+ if (g->debug_type_cache) {
+ memcpy(nt, g->debug_type_cache,
+ sizeof(*g->debug_type_cache) * g->debug_type_cap);
+ h->free(h, g->debug_type_cache,
+ sizeof(*g->debug_type_cache) * g->debug_type_cap);
+ }
+ if (g->debug_type_state) {
+ memcpy(ns, g->debug_type_state,
+ sizeof(*g->debug_type_state) * g->debug_type_cap);
+ h->free(h, g->debug_type_state,
+ sizeof(*g->debug_type_state) * g->debug_type_cap);
+ }
+ g->debug_type_cache = nt;
+ g->debug_type_state = ns;
+ g->debug_type_cap = cap;
+ return 1;
+}
+
+static DebugTypeId api_debug_type_cache_get(KitCg* g, KitCgTypeId id) {
+ if (!g || id == KIT_CG_TYPE_NONE || id >= g->debug_type_cap)
+ return DEBUG_TYPE_NONE;
+ return g->debug_type_cache[id];
+}
+
+static void api_debug_type_cache_put(KitCg* g, KitCgTypeId id, DebugTypeId dt,
+ u8 state) {
+ if (!api_debug_type_cache_ensure(g, id)) return;
+ g->debug_type_cache[id] = dt;
+ g->debug_type_state[id] = state;
+}
+
KitCgDebugType kit_cg_debug_base(KitCg* g, KitSym name, KitCgDebugEncoding enc,
uint32_t bytes) {
if (!g || !g->debug || !name) return KIT_CG_DEBUG_TYPE_NONE;
@@ -76,16 +126,22 @@ KitCgDebugType kit_cg_debug_func(KitCg* g, KitCgDebugType ret,
static DebugTypeId api_debug_of_type(KitCg* g, KitCgTypeId id) {
const CgType* ty;
+ DebugTypeId cached;
+ DebugTypeId out = DEBUG_TYPE_NONE;
if (!g || !g->debug) return DEBUG_TYPE_NONE;
+ cached = api_debug_type_cache_get(g, id);
+ if (cached != DEBUG_TYPE_NONE) return cached;
ty = cg_type_get(g->c, id);
if (!ty) return DEBUG_TYPE_NONE;
switch (ty->kind) {
case KIT_CG_TYPE_VOID:
- return debug_type_void(g->debug);
+ out = debug_type_void(g->debug);
+ break;
case KIT_CG_TYPE_BOOL:
- return debug_type_base(
- g->debug, pool_intern_slice(g->c->global, SLICE_LIT("_Bool")),
- DEBUG_BE_BOOL, 1);
+ out = debug_type_base(g->debug,
+ pool_intern_slice(g->c->global, SLICE_LIT("_Bool")),
+ DEBUG_BE_BOOL, 1);
+ break;
case KIT_CG_TYPE_INT: {
const char* name = "long long";
if (ty->integer.width <= 8)
@@ -94,26 +150,30 @@ static DebugTypeId api_debug_of_type(KitCg* g, KitCgTypeId id) {
name = "short";
else if (ty->integer.width <= 32)
name = "int";
- return debug_type_base(
+ out = debug_type_base(
g->debug, pool_intern_slice(g->c->global, slice_from_cstr(name)),
DEBUG_BE_SIGNED, (u32)((ty->integer.width + 7u) / 8u));
+ break;
}
case KIT_CG_TYPE_FLOAT: {
const char* name = ty->fp.width <= 32 ? "float" : "double";
- return debug_type_base(
+ out = debug_type_base(
g->debug, pool_intern_slice(g->c->global, slice_from_cstr(name)),
DEBUG_BE_FLOAT, (u32)((ty->fp.width + 7u) / 8u));
+ break;
}
case KIT_CG_TYPE_PTR: {
DebugTypeId pointee = api_debug_of_type(g, ty->ptr.pointee);
if (pointee == DEBUG_TYPE_NONE) pointee = debug_type_void(g->debug);
- return debug_type_ptr(g->debug, pointee);
+ out = debug_type_ptr(g->debug, pointee);
+ break;
}
case KIT_CG_TYPE_ARRAY: {
DebugTypeId elem = api_debug_of_type(g, ty->array.elem);
u32 count = ty->array.count > UINT32_MAX ? 0u : (u32)ty->array.count;
if (elem == DEBUG_TYPE_NONE) elem = debug_type_void(g->debug);
- return debug_type_array(g->debug, elem, count);
+ out = debug_type_array(g->debug, elem, count);
+ break;
}
case KIT_CG_TYPE_FUNC: {
Heap* h = (Heap*)g->c->ctx->heap;
@@ -136,14 +196,39 @@ static DebugTypeId api_debug_of_type(KitCg* g, KitCgTypeId id) {
fn = debug_type_func(g->debug, ret, params, ty->func.nparams,
ty->func.abi_variadic);
if (params) h->free(h, params, sizeof(*params) * ty->func.nparams);
- return fn;
+ out = fn;
+ break;
}
case KIT_CG_TYPE_RECORD: {
- DebugTypeBuilder* b = debug_type_record_begin(
- g->debug, (Sym)ty->record.tag, ty->record.is_union, (u32)ty->size,
- ty->align);
+ const KitCgRecordLayout* layout =
+ kit_cg_type_record_layout((KitCompiler*)g->c, id);
+ DebugTypeBuilder* b;
+ if (!layout) {
+ b = debug_type_record_begin(g->debug, (Sym)ty->record.tag,
+ ty->record.is_union, 0, 1);
+ if (!b) return DEBUG_TYPE_NONE;
+ return debug_type_record_end(b);
+ }
+ b = debug_type_record_begin(g->debug, (Sym)ty->record.tag,
+ ty->record.is_union, (u32)layout->size,
+ layout->align);
if (!b) return DEBUG_TYPE_NONE;
- return debug_type_record_end(b);
+ out = debug_type_record_id(b);
+ api_debug_type_cache_put(g, id, out, 1);
+ for (u32 i = 0; i < layout->nfields; ++i) {
+ const KitCgFieldLayout* f = &layout->fields[i];
+ DebugTypeId ft = api_debug_of_type(g, f->type);
+ if (ft == DEBUG_TYPE_NONE) ft = debug_type_void(g->debug);
+ if (f->bit_width) {
+ debug_type_record_bitfield(b, (Sym)f->name, ft, (u32)f->offset,
+ f->bit_offset, f->bit_width);
+ } else {
+ debug_type_record_field(b, (Sym)f->name, ft, (u32)f->offset);
+ }
+ }
+ out = debug_type_record_end(b);
+ api_debug_type_cache_put(g, id, out, 2);
+ break;
}
case KIT_CG_TYPE_ENUM: {
DebugTypeId base = api_debug_of_type(g, ty->enum_.base);
@@ -154,12 +239,15 @@ static DebugTypeId api_debug_of_type(KitCg* g, KitCgTypeId id) {
debug_type_enum_value(eb, (Sym)ty->enum_.values[i].name,
(i64)ty->enum_.values[i].value);
}
- return debug_type_enum_end(eb);
+ out = debug_type_enum_end(eb);
+ break;
}
case KIT_CG_TYPE_VARARG_STATE:
- return debug_type_void(g->debug);
+ out = debug_type_void(g->debug);
+ break;
}
- return DEBUG_TYPE_NONE;
+ if (out != DEBUG_TYPE_NONE) api_debug_type_cache_put(g, id, out, 2);
+ return out;
}
KitCgDebugType kit_cg_debug_enum(KitCg* g, KitCgTypeId enum_type,
diff --git a/src/cg/internal.h b/src/cg/internal.h
@@ -316,6 +316,10 @@ struct KitCg {
KitCgDecl* sym_attrs;
u32 sym_cap;
+ DebugTypeId* debug_type_cache; /* indexed by KitCgTypeId */
+ u8* debug_type_state; /* 0 empty, 1 building, 2 complete */
+ u32 debug_type_cap;
+
/* Per-ObjSymId: the cur_unit_seq of the unit that last *defined* this symbol
* (0 = not defined by any unit yet). Distinct from sym_attrs, which is reset
* on every decl; this is written only when a definition is emitted. */
@@ -551,6 +555,9 @@ void kit_cg_dup2(KitCg* g);
void kit_cg_swap(KitCg* g);
void kit_cg_drop(KitCg* g);
KitCgSlotInfo kit_cg_slot_info(KitCg* g, uint32_t depth_from_top);
+KitCgTypeId kit_cg_slot_cg_type(KitCg* g, uint32_t depth_from_top);
+const void* kit_cg_slot_lang_type(KitCg* g, uint32_t depth_from_top);
+uint16_t kit_cg_slot_lang_flags(KitCg* g, uint32_t depth_from_top);
void kit_cg_retag_top(KitCg* g, const void* lang_type, uint16_t lang_flags);
void kit_cg_retag_at(KitCg* g, uint32_t depth_from_top, const void* lang_type,
uint16_t lang_flags);
@@ -641,13 +648,21 @@ void api_set_owned_local(ApiSValue* sv, CGLocal r);
KitCgTypeId api_owned_local_type(KitCg* g, const ApiSValue* sv);
CGLocal api_alloc_temp_local(KitCg* g, KitCgTypeId ty);
MemAccess api_mem_for_lvalue(KitCg* g, const Operand* lv, KitCgTypeId ty);
+MemAccess api_mem_from_access_resolved(KitCg* g, const Operand* lv,
+ KitCgMemAccess access, KitCgTypeId ty);
MemAccess api_mem_from_access(KitCg* g, const Operand* lv,
KitCgMemAccess access);
KitCgTypeId api_mem_access_type(KitCg* g, KitCgMemAccess access,
KitCgTypeId fallback, const char* who);
+u32 api_mem_type_size_resolved(KitCg* g, KitCgTypeId ty, const char* who);
u32 api_mem_type_size(KitCg* g, KitCgTypeId ty, const char* who);
+u32 api_require_scalar_mem_type_resolved(KitCg* g, const char* who,
+ KitCgTypeId ty);
void api_require_scalar_mem_type(KitCg* g, const char* who, KitCgTypeId ty);
void api_require_pointer_value(KitCg* g, const char* who, KitCgTypeId ty);
+void api_validate_memory_value_resolved(KitCg* g, const char* who,
+ KitCgTypeId access_ty,
+ KitCgTypeId value_ty);
void api_validate_memory_value(KitCg* g, const char* who, KitCgTypeId access_ty,
KitCgTypeId value_ty);
int api_sv_owns_operand_local(const ApiSValue* sv, const Operand* op);
diff --git a/src/cg/local.c b/src/cg/local.c
@@ -71,8 +71,8 @@ KitCgLocal kit_cg_local(KitCg* g, KitCgTypeId type, KitCgLocalAttrs attrs) {
desc.type = ty;
desc.name = (Sym)attrs.name;
desc.loc = g->cur_loc;
- desc.size = abi_cg_sizeof(g->c->abi, type);
- desc.align = attrs.align ? attrs.align : abi_cg_alignof(g->c->abi, type);
+ desc.size = abi_cg_sizeof(g->c->abi, ty);
+ desc.align = attrs.align ? attrs.align : abi_cg_alignof(g->c->abi, ty);
if (api_local_requires_memory(g, ty, attrs))
desc.flags |= CG_LOCAL_MEMORY_REQUIRED;
if (api_unevaluated(g)) {
@@ -111,8 +111,8 @@ KitCgLocal kit_cg_param(KitCg* g, uint32_t index, KitCgTypeId type,
if (handle == KIT_CG_LOCAL_NONE || !api_grow_locals(g, g->nlocals + 1u))
return KIT_CG_LOCAL_NONE;
- size = abi_cg_sizeof(g->c->abi, type);
- align = attrs.align ? attrs.align : abi_cg_alignof(g->c->abi, type);
+ size = abi_cg_sizeof(g->c->abi, ty);
+ align = attrs.align ? attrs.align : abi_cg_alignof(g->c->abi, ty);
memset(&pd, 0, sizeof pd);
pd.index = index;
diff --git a/src/cg/memory.c b/src/cg/memory.c
@@ -280,7 +280,7 @@ static KitCgMemAccess api_load_access_with_hints(KitCg* g,
int is_bitfield) {
if (!is_bitfield && (access.flags & KIT_CG_MEM_SOURCE_SIGNED) &&
cg_type_is_int(g->c, access_ty) &&
- (u32)api_mem_type_size(g, access_ty, "load") < 4u) {
+ (u32)api_mem_type_size_resolved(g, access_ty, "load") < 4u) {
access.flags |= KIT_CG_MEM_SEXT_LOAD;
}
return access;
@@ -294,6 +294,7 @@ void kit_cg_load(KitCg* g, KitCgMemAccess access) {
CgTarget* T;
KitCgTypeId ty;
KitCgTypeId access_ty;
+ KitCgTypeId base_ty;
Operand mem_op;
CGLocal dst_r;
Operand dst;
@@ -303,25 +304,22 @@ void kit_cg_load(KitCg* g, KitCgMemAccess access) {
if (access.flags & KIT_CG_MEM_VOLATILE) api_local_const_memory_boundary(g);
base = api_pop(g);
- if (!api_is_lvalue_sv(&base)) {
- compiler_panic(g->c, g->cur_loc,
- "KitCg: load requires a place; deref the pointer first");
- }
+ CG_REQUIRE(g, api_is_lvalue_sv(&base),
+ "KitCg: load requires a place; deref the pointer first");
+ base_ty = api_sv_type(&base);
is_bitfield = api_sv_is_bitfield(&base);
/* Aggregate place: an aggregate-typed access returns the place itself; a
* scalar access reads a scalar sub-object and falls through. */
- if (!is_bitfield && cg_type_is_aggregate(g->c, api_sv_type(&base))) {
- ty = api_mem_access_type(g, access, api_sv_type(&base), "load");
+ if (!is_bitfield && cg_type_is_aggregate(g->c, base_ty)) {
+ ty = api_mem_access_type(g, access, base_ty, "load");
if (cg_type_is_aggregate(g->c, ty)) {
- u32 access_size = api_mem_type_size(g, ty, "load");
- u32 lvalue_size = api_mem_type_size(g, api_sv_type(&base), "load");
- if (access_size != lvalue_size) {
- compiler_panic(g->c, g->cur_loc,
- "KitCg: load aggregate type/size mismatch: access "
- "size %u, lvalue size %u",
- (unsigned)access_size, (unsigned)lvalue_size);
- }
+ u32 access_size = api_mem_type_size_resolved(g, ty, "load");
+ u32 lvalue_size = api_mem_type_size_resolved(g, base_ty, "load");
+ CG_REQUIRE(g, access_size == lvalue_size,
+ "KitCg: load aggregate type/size mismatch: access "
+ "size %u, lvalue size %u",
+ (unsigned)access_size, (unsigned)lvalue_size);
base.lang_type = NULL;
base.lang_flags = 0;
api_push(g, base);
@@ -330,10 +328,11 @@ void kit_cg_load(KitCg* g, KitCgMemAccess access) {
/* scalar access from an aggregate place: fall through */
}
- ty = api_mem_access_type(g, access, api_sv_type(&base), "load");
+ ty = api_mem_access_type(g, access, base_ty, "load");
access_ty = ty;
- if (!is_bitfield) api_require_scalar_mem_type(g, "load", access_ty);
+ if (!is_bitfield)
+ (void)api_require_scalar_mem_type_resolved(g, "load", access_ty);
access = api_load_access_with_hints(g, access, access_ty, is_bitfield);
if (api_unevaluated(g)) {
@@ -357,7 +356,6 @@ void kit_cg_load(KitCg* g, KitCgMemAccess access) {
if (!is_bitfield && base.source_local != KIT_CG_LOCAL_NONE &&
base.op.kind == OPK_LOCAL &&
!api_sv_local_storage_is_aggregate(g, &base)) {
- KitCgTypeId base_ty = api_sv_type(&base);
u8 base_bits = api_type_pred_bits(g->c, base_ty);
u8 ty_bits = api_type_pred_bits(g->c, ty);
if (!(base_bits & API_PRED_AGGREGATE) && !(ty_bits & API_PRED_AGGREGATE) &&
@@ -371,7 +369,7 @@ void kit_cg_load(KitCg* g, KitCgMemAccess access) {
/* Resolve the place into a single backend memop operand. */
if (!api_operand_can_address(&base.op)) {
- KitCgTypeId pty = cg_type_ptr_to(g->c, api_sv_type(&base));
+ KitCgTypeId pty = cg_type_ptr_to(g->c, base_ty);
Operand addr = api_lvalue_addr(g, &base, pty);
mem_op = api_op_indirect(addr.v.local, 0, access_ty);
} else {
@@ -389,7 +387,8 @@ void kit_cg_load(KitCg* g, KitCgMemAccess access) {
* CgTarget impl extracts/extends within the storage unit. */
T->load(T, dst, mem_op, api_mem_for_bitfield(g, &base, &mem_op, access_ty));
} else {
- T->load(T, dst, mem_op, api_mem_from_access(g, &mem_op, access));
+ T->load(T, dst, mem_op,
+ api_mem_from_access_resolved(g, &mem_op, access, access_ty));
}
api_release(g, &base);
@@ -408,10 +407,8 @@ void kit_cg_deref(KitCg* g, int64_t offset) {
if (!g) return;
v = api_pop(g);
pty = api_sv_type(&v);
- if (api_is_lvalue_sv(&v) || !cg_type_is_ptr(g->c, pty)) {
- compiler_panic(g->c, g->cur_loc,
- "KitCg: deref requires a pointer value, not a place");
- }
+ CG_REQUIRE(g, !api_is_lvalue_sv(&v) && cg_type_is_ptr(g->c, pty),
+ "KitCg: deref requires a pointer value, not a place");
pointee = cg_type_pointee(g->c, pty);
if (!pointee) pointee = builtin_id(KIT_CG_BUILTIN_VOID);
if (api_unevaluated(g)) {
@@ -482,6 +479,8 @@ static void api_cg_store_impl(KitCg* g, KitCgMemAccess access,
CgTarget* T;
KitCgTypeId ty;
KitCgTypeId access_ty;
+ KitCgTypeId base_ty;
+ KitCgTypeId rv_ty;
Operand src;
Operand mem_op;
int is_lvalue;
@@ -495,16 +494,15 @@ static void api_cg_store_impl(KitCg* g, KitCgMemAccess access,
rv_const = api_const_at(g, 0);
rv = api_pop(g);
base = api_pop(g);
+ rv_ty = api_sv_type(&rv);
+ base_ty = api_sv_type(&base);
is_lvalue = api_is_lvalue_sv(&base);
- if (!is_lvalue) {
- compiler_panic(g->c, g->cur_loc,
- "KitCg: store requires a place destination; deref first");
- return;
- }
+ CG_REQUIRE(g, is_lvalue,
+ "KitCg: store requires a place destination; deref first");
is_bitfield = api_sv_is_bitfield(&base);
- ty = api_mem_access_type(g, access, api_sv_type(&base), "store");
+ ty = api_mem_access_type(g, access, base_ty, "store");
access_ty = ty;
if (api_unevaluated(g)) {
api_release(g, &base);
@@ -521,31 +519,26 @@ static void api_cg_store_impl(KitCg* g, KitCgMemAccess access,
* decode its aggregate predicate once and reuse it below; likewise decode the
* source value's id once for its aggregate/ptr predicate bits. */
ty_is_agg = cg_type_is_aggregate(g->c, ty);
- u8 rv_bits = api_type_pred_bits(g->c, api_sv_type(&rv));
+ u8 rv_bits = api_type_pred_bits(g->c, rv_ty);
if (!is_bitfield && (ty_is_agg || (rv_bits & API_PRED_AGGREGATE))) {
KitCgTypeId ptr_ty;
Operand dst_addr, src_addr;
int src_ptr_rvalue;
AggregateAccess agg;
u32 src_size;
- u32 dst_size = ty_is_agg
- ? api_mem_type_size(g, ty, "store")
- : api_mem_type_size(g, api_sv_type(&base), "store");
- u32 access_size = ty_is_agg ? api_mem_type_size(g, ty, "store") : dst_size;
+ u32 dst_size = ty_is_agg ? api_mem_type_size_resolved(g, ty, "store")
+ : api_mem_type_size_resolved(g, base_ty, "store");
+ u32 access_size =
+ ty_is_agg ? api_mem_type_size_resolved(g, ty, "store") : dst_size;
src_ptr_rvalue = !api_is_lvalue_sv(&rv) && (rv_bits & API_PRED_PTR);
src_size = src_ptr_rvalue ? access_size
- : api_mem_type_size(g, api_sv_type(&rv), "store");
- if (!api_is_lvalue_sv(&rv) && !src_ptr_rvalue) {
- compiler_panic(g->c, g->cur_loc,
- "KitCg: aggregate store source is not an lvalue");
- }
- if (access_size != dst_size || access_size != src_size) {
- compiler_panic(g->c, g->cur_loc,
- "KitCg: store aggregate type/size mismatch: access "
- "size %u, destination size %u, value size %u",
- (unsigned)access_size, (unsigned)dst_size,
- (unsigned)src_size);
- }
+ : api_mem_type_size_resolved(g, rv_ty, "store");
+ CG_REQUIRE(g, api_is_lvalue_sv(&rv) || src_ptr_rvalue,
+ "KitCg: aggregate store source is not an lvalue");
+ CG_REQUIRE(g, access_size == dst_size && access_size == src_size,
+ "KitCg: store aggregate type/size mismatch: access "
+ "size %u, destination size %u, value size %u",
+ (unsigned)access_size, (unsigned)dst_size, (unsigned)src_size);
if (base.source_local != KIT_CG_LOCAL_NONE) {
api_local_const_clear(api_local_from_handle(g, base.source_local));
} else if (base.op.kind == OPK_INDIRECT || base.op.kind == OPK_GLOBAL ||
@@ -555,7 +548,7 @@ static void api_cg_store_impl(KitCg* g, KitCgMemAccess access,
ptr_ty = cg_type_ptr_to(g->c, ty);
dst_addr = api_lvalue_addr(g, &base, ptr_ty);
if (src_ptr_rvalue) {
- src_addr = api_force_local(g, &rv, api_sv_type(&rv));
+ src_addr = api_force_local(g, &rv, rv_ty);
} else {
src_addr = api_lvalue_addr(g, &rv, ptr_ty);
}
@@ -573,7 +566,7 @@ static void api_cg_store_impl(KitCg* g, KitCgMemAccess access,
return;
}
- if (!is_bitfield) api_validate_memory_value(g, "store", ty, api_sv_type(&rv));
+ if (!is_bitfield) api_validate_memory_value_resolved(g, "store", ty, rv_ty);
/* A 16-byte scalar immediate (an i128 small constant) only carries 64 bits in
* op.v.imm; materialize it into both sign-extended lanes so the general store
@@ -600,7 +593,6 @@ static void api_cg_store_impl(KitCg* g, KitCgMemAccess access,
base.op.kind == OPK_LOCAL &&
!api_sv_local_storage_is_aggregate(g, &base) && !ty_is_agg;
if (scalar_local_place) {
- KitCgTypeId base_ty = api_sv_type(&base);
u8 base_bits = api_type_pred_bits(g->c, base_ty);
scalar_local_place = !(base_bits & API_PRED_AGGREGATE) && base_ty == ty;
}
@@ -642,7 +634,7 @@ static void api_cg_store_impl(KitCg* g, KitCgMemAccess access,
if (api_sv_op_is_local_or_imm(&rv)) {
src = rv.op;
} else {
- src = api_force_local(g, &rv, api_sv_type(&rv));
+ src = api_force_local(g, &rv, rv_ty);
}
/* Scalar local-resident place, plain store: copy into the local. */
@@ -677,7 +669,7 @@ static void api_cg_store_impl(KitCg* g, KitCgMemAccess access,
/* Resolve the place into a single backend memop operand. */
if (!api_operand_can_address(&base.op)) {
- KitCgTypeId pty = cg_type_ptr_to(g->c, api_sv_type(&base));
+ KitCgTypeId pty = cg_type_ptr_to(g->c, base_ty);
Operand addr = api_lvalue_addr(g, &base, pty);
mem_op = api_op_indirect(addr.v.local, 0, access_ty);
} else {
@@ -709,7 +701,8 @@ static void api_cg_store_impl(KitCg* g, KitCgMemAccess access,
T->store(T, mem_op, src,
api_mem_for_bitfield(g, &base, &mem_op, access_ty));
} else {
- T->store(T, mem_op, src, api_mem_from_access(g, &mem_op, access));
+ T->store(T, mem_op, src,
+ api_mem_from_access_resolved(g, &mem_op, access, access_ty));
}
if (keep_result) {
@@ -851,17 +844,30 @@ void kit_cg_drop(KitCg* g) {
api_release(g, &v);
}
+static KitCgSlotInfo api_slot_info_from_sv(const ApiSValue* sv) {
+ return (KitCgSlotInfo){.cg_type = api_sv_type(sv),
+ .lang_type = sv->lang_type,
+ .lang_flags = sv->lang_flags};
+}
+
KitCgSlotInfo kit_cg_slot_info(KitCg* g, uint32_t depth_from_top) {
- KitCgSlotInfo info;
- memset(&info, 0, sizeof info);
- if (!g || depth_from_top >= g->sp) return info;
- {
- ApiSValue* sv = &g->stack[g->sp - 1u - depth_from_top];
- info.cg_type = api_sv_type(sv);
- info.lang_type = sv->lang_type;
- info.lang_flags = sv->lang_flags;
- }
- return info;
+ if (!g || depth_from_top >= g->sp) return (KitCgSlotInfo){0};
+ return api_slot_info_from_sv(&g->stack[g->sp - 1u - depth_from_top]);
+}
+
+KitCgTypeId kit_cg_slot_cg_type(KitCg* g, uint32_t depth_from_top) {
+ if (!g || depth_from_top >= g->sp) return KIT_CG_TYPE_NONE;
+ return api_sv_type(&g->stack[g->sp - 1u - depth_from_top]);
+}
+
+const void* kit_cg_slot_lang_type(KitCg* g, uint32_t depth_from_top) {
+ if (!g || depth_from_top >= g->sp) return NULL;
+ return g->stack[g->sp - 1u - depth_from_top].lang_type;
+}
+
+uint16_t kit_cg_slot_lang_flags(KitCg* g, uint32_t depth_from_top) {
+ if (!g || depth_from_top >= g->sp) return 0;
+ return g->stack[g->sp - 1u - depth_from_top].lang_flags;
}
void kit_cg_retag_at(KitCg* g, uint32_t depth_from_top, const void* lang_type,
diff --git a/src/cg/session.c b/src/cg/session.c
@@ -37,6 +37,16 @@ static void cg_free_obj_state(KitCg* g) {
h->free(h, g->sym_attrs, sizeof(*g->sym_attrs) * g->sym_cap);
g->sym_attrs = NULL;
}
+ if (g->debug_type_cache) {
+ h->free(h, g->debug_type_cache,
+ sizeof(*g->debug_type_cache) * g->debug_type_cap);
+ g->debug_type_cache = NULL;
+ }
+ if (g->debug_type_state) {
+ h->free(h, g->debug_type_state,
+ sizeof(*g->debug_type_state) * g->debug_type_cap);
+ g->debug_type_state = NULL;
+ }
if (g->sym_def_seq) {
h->free(h, g->sym_def_seq, sizeof(*g->sym_def_seq) * g->sym_def_seq_cap);
g->sym_def_seq = NULL;
@@ -64,6 +74,7 @@ static void cg_free_obj_state(KitCg* g) {
g->locals_cap = 0;
g->scopes_cap = 0;
g->sym_cap = 0;
+ g->debug_type_cap = 0;
g->fn_ret_type = 0;
memset(&g->fn_desc, 0, sizeof(g->fn_desc));
memset(g->fn_params, 0, sizeof(g->fn_params));
diff --git a/src/cg/type.c b/src/cg/type.c
@@ -16,19 +16,16 @@ typedef struct CgApiType {
/* Lazily-filled packed API_TYPE_CLASS_* memo (0 == not yet computed; the
* VALID bit distinguishes a computed all-zero class from unfilled). */
u8 cached_class;
- /* Lazily-filled abi_cg_type_info memo (see api_type_layout_get/put). */
+ /* Lazily-filled abi_cg_type_info memo (see api_type_layout_ref/put): `abi`
+ * holds the cached size/align/scalar profile, valid iff abi_cached. */
u8 abi_cached;
- u8 abi_scalar_kind;
- u8 abi_signed;
- u8 abi_atomic;
/* Lazily-filled flat predicate descriptor. Extends the cached_class
* mechanism: pred_bits is a bitset of API_PRED_* computed on the exact
* operational type, and pred_valid distinguishes a computed all-zero set from
* unfilled. */
u8 pred_bits;
u8 pred_valid;
- u32 abi_size;
- u32 abi_align;
+ ABITypeInfo abi;
/* This entry's own type id (set by type_alloc). Lets a structural hashset of
* CgApiType* recover the id without maintaining a reverse index. */
KitCgTypeId self_id;
@@ -88,6 +85,9 @@ typedef struct CgApiState {
* with a single indexed load instead of a cg_type_get + per-kind re-classify
* on every call. Builtins are never aliases, so the bitset is exact. */
u8 builtin_pred[KIT_CG_BUILTIN_COUNT];
+ /* Per-builtin ABI layout, precomputed at init so abi_cg_type_info can hit the
+ * same memo path for builtin scalars as it does for user types. */
+ ABITypeInfo builtin_layout[KIT_CG_BUILTIN_COUNT];
u8 builtins_init;
u8 pad[3];
} CgApiState;
@@ -192,6 +192,43 @@ static CgApiState* cg_api_get(Compiler* c);
static CgApiType* api_type_from_id(Compiler* c, KitCgTypeId id);
static u8 api_pred_bits_for_kind(const CgType* ty);
+static ABITypeInfo api_builtin_layout_info(Compiler* c, const CgType* t) {
+ ABITypeInfo r = {0, 0, ABI_SC_VOID, 0, 0, 0};
+ if (!t) return r;
+ switch (t->kind) {
+ case KIT_CG_TYPE_PTR:
+ r.size = c->target.ptr_size ? c->target.ptr_size : 8;
+ r.align = c->target.ptr_align ? c->target.ptr_align : 8;
+ r.scalar_kind = ABI_SC_PTR;
+ return r;
+ case KIT_CG_TYPE_VOID:
+ r.align = 1;
+ r.scalar_kind = ABI_SC_VOID;
+ return r;
+ case KIT_CG_TYPE_BOOL:
+ r.size = t->size;
+ r.align = t->align;
+ r.scalar_kind = ABI_SC_BOOL;
+ return r;
+ case KIT_CG_TYPE_INT:
+ r.size = t->size;
+ r.align = t->align;
+ r.scalar_kind = ABI_SC_INT;
+ return r;
+ case KIT_CG_TYPE_FLOAT:
+ r.size = t->size;
+ r.align = t->align;
+ r.scalar_kind = ABI_SC_FLOAT;
+ return r;
+ case KIT_CG_TYPE_VARARG_STATE:
+ r.size = t->size;
+ r.align = t->align;
+ return r;
+ default:
+ return r;
+ }
+}
+
/* ---- codegen scalar type-property predicates ----
* Thin queries over the resolved CgType / target ABI; pure functions of
* (type, target). The wide-class predicates below feed api_wide_kind_for, which
@@ -274,6 +311,12 @@ static void cg_api_init_builtins(Compiler* c, CgApiState* s) {
for (u32 i = 0; i < KIT_CG_BUILTIN_COUNT; ++i) {
builtin_cg_type_init(c, &s->builtins[i], (KitCgBuiltinType)i);
}
+ for (u32 i = 0; i < KIT_CG_BUILTIN_COUNT; ++i) {
+ s->builtin_layout[i] = api_builtin_layout_info(c, &s->builtins[i]);
+ }
+ /* Publish the filled table so abi_cg_type_info can index it inline (one load,
+ * no cross-TU call) for the common builtin case. NULLed in cg_api_fini. */
+ c->cg_builtin_layout = s->builtin_layout;
/* Precompute each builtin's class now that the builtin table and the ABI are
* both live (builtin_cg_type_init already consults c->abi for the va_list
* builtin, so it is ready). */
@@ -312,34 +355,26 @@ u8 api_type_class(Compiler* c, KitCgTypeId ty) {
return (u8)(e->cached_class & ~API_TYPE_CLASS_CACHED);
}
-int api_type_layout_get(Compiler* c, KitCgTypeId ty, u32* size, u32* align,
- u8* scalar_kind, u8* signed_, u8* atomic) {
+const ABITypeInfo* api_type_layout_ref(Compiler* c, KitCgTypeId ty) {
+ CgApiState* s;
CgApiType* e;
- if (ty == KIT_CG_TYPE_NONE) return 0;
- if (ty <= KIT_CG_BUILTIN_COUNT) return 0;
+ if (!c || ty == KIT_CG_TYPE_NONE) return NULL;
+ if (ty <= KIT_CG_BUILTIN_COUNT) {
+ s = c->cg_api ? (CgApiState*)c->cg_api : cg_api_get(c);
+ return s ? &s->builtin_layout[ty - 1u] : NULL;
+ }
e = api_type_from_id(c, ty);
- if (!e || !e->abi_cached) return 0;
- *size = e->abi_size;
- *align = e->abi_align;
- *scalar_kind = e->abi_scalar_kind;
- *signed_ = e->abi_signed;
- *atomic = e->abi_atomic;
- return 1;
+ return (e && e->abi_cached) ? &e->abi : NULL;
}
-void api_type_layout_put(Compiler* c, KitCgTypeId ty, u32 size, u32 align,
- u8 scalar_kind, u8 signed_, u8 atomic) {
+void api_type_layout_put(Compiler* c, KitCgTypeId ty, ABITypeInfo info) {
CgApiType* e;
- if (ty == KIT_CG_TYPE_NONE) return;
+ if (!c || ty == KIT_CG_TYPE_NONE) return;
if (ty <= KIT_CG_BUILTIN_COUNT) return;
- if (!align && scalar_kind == ABI_SC_VOID) return;
+ if (!info.align && info.scalar_kind == ABI_SC_VOID) return;
e = api_type_from_id(c, ty);
if (!e) return;
- e->abi_size = size;
- e->abi_align = align;
- e->abi_scalar_kind = scalar_kind;
- e->abi_signed = signed_;
- e->abi_atomic = atomic;
+ e->abi = info;
e->abi_cached = 1;
}
@@ -620,7 +655,9 @@ static CgApiType* api_type_from_id(Compiler* c, KitCgTypeId id) {
}
KitCgTypeId resolve_type(Compiler* c, KitCgTypeId id) {
- return cg_type_get(c, id) ? id : KIT_CG_TYPE_NONE;
+ if (!c || id == KIT_CG_TYPE_NONE) return KIT_CG_TYPE_NONE;
+ if (id <= KIT_CG_BUILTIN_COUNT) return id;
+ return api_type_from_id(c, id) ? id : KIT_CG_TYPE_NONE;
}
/* Fill an entry's identity prefix (the kit/cg.h KitCgTypeInfo) once. Identity
@@ -1478,6 +1515,7 @@ void cg_api_fini(Compiler* c) {
s->heap->free(s->heap, s, sizeof(*s));
c->cg_api = NULL;
c->cg_api_free = NULL;
+ c->cg_builtin_layout = NULL;
}
/* ============================================================
diff --git a/src/cg/type.h b/src/cg/type.h
@@ -3,6 +3,7 @@
#include <kit/cg.h>
+#include "abi/abi.h"
#include "core/core.h"
typedef struct CgTypeField {
@@ -132,12 +133,14 @@ u8 api_type_class(Compiler*, KitCgTypeId);
* chains and is hit several times per statement; caching the resolved result
* on the type entry collapses each repeat (and the alias chase) to one load.
* Types are immutable once built, so the cache never needs invalidation.
- * Builtins are not cached here (their abi_cg_type_info is already a single
- * indexed load) — _get returns 0 for them so the caller computes. */
-int api_type_layout_get(Compiler*, KitCgTypeId, u32* size, u32* align,
- u8* scalar_kind, u8* signed_, u8* atomic);
-void api_type_layout_put(Compiler*, KitCgTypeId, u32 size, u32 align,
- u8 scalar_kind, u8 signed_, u8 atomic);
+ * _ref returns a borrowed pointer to the cached ABITypeInfo (no field-by-field
+ * copy) or NULL on a miss; the pointer is stable for the compiler's life.
+ * Builtins resolve against a precomputed per-compiler table in CgApiState (also
+ * mirrored on Compiler.cg_builtin_layout for the inline fast path in
+ * abi_cg_type_info), so they hit this memo path without creating CgApiType
+ * entries. */
+const ABITypeInfo* api_type_layout_ref(Compiler*, KitCgTypeId);
+void api_type_layout_put(Compiler*, KitCgTypeId, ABITypeInfo);
KitCgTypeId cg_type_ptr_to(Compiler*, KitCgTypeId);
KitCgTypeId cg_type_pointee(Compiler*, KitCgTypeId);
diff --git a/src/cg/value.c b/src/cg/value.c
@@ -176,11 +176,9 @@ void api_push(KitCg* g, ApiSValue v) {
* any aggregate VALUE at the point it would enter the stack. i128/f128 are
* scalars (not aggregates), so they remain valid VALUEs and are unaffected.
*/
- if ((cls & API_TYPE_CLASS_AGGREGATE) && !api_is_lvalue_sv(&v)) {
- compiler_panic(g->c, g->cur_loc,
- "KitCg: aggregate must be a place, not a value; load the "
- "place or pass it by reference");
- }
+ CG_REQUIRE(g, !(cls & API_TYPE_CLASS_AGGREGATE) || api_is_lvalue_sv(&v),
+ "KitCg: aggregate must be a place, not a value; load the "
+ "place or pass it by reference");
/* Cache the wide/soft-float dispatch class on the node; the wide and
* soft-float predicates in arith.c read it back instead of re-deriving it per
* operand. dup/swap/rot copy the whole node (type and tag together), so it
@@ -195,9 +193,7 @@ void api_push(KitCg* g, ApiSValue v) {
ApiSValue api_pop(KitCg* g) {
ApiSValue r;
- if (g->sp == 0) {
- compiler_panic(g->c, g->cur_loc, "KitCg: stack underflow");
- }
+ CG_REQUIRE(g, g->sp != 0, "KitCg: stack underflow");
r = g->stack[--g->sp];
if (!api_unevaluated(g)) api_sv_adjust_refs(g, &r, -1);
return r;
@@ -285,90 +281,99 @@ MemAccess api_mem_for_lvalue(KitCg* g, const Operand* lv, KitCgTypeId ty) {
return m;
}
-MemAccess api_mem_from_access(KitCg* g, const Operand* lv,
- KitCgMemAccess access) {
- KitCgTypeId ty = resolve_type(g->c, access.type);
+MemAccess api_mem_from_access_resolved(KitCg* g, const Operand* lv,
+ KitCgMemAccess access, KitCgTypeId ty) {
MemAccess m = api_mem_for_lvalue(g, lv, ty);
- if (access.align) m.align = access.align;
+ u32 natural_align = m.align;
+ if (access.align) {
+ m.align = access.align;
+ if (ty && access.align < natural_align) m.flags |= MF_UNALIGNED;
+ }
m.addr_space = (u16)access.address_space;
if (access.flags & KIT_CG_MEM_VOLATILE) m.flags |= MF_VOLATILE;
if (access.flags & KIT_CG_MEM_SEXT_LOAD) m.flags |= MF_SEXT_LOAD;
- if (!access.align || (ty && access.align < abi_cg_alignof(g->c->abi, ty))) {
- m.flags |= MF_UNALIGNED;
- }
return m;
}
+MemAccess api_mem_from_access(KitCg* g, const Operand* lv,
+ KitCgMemAccess access) {
+ return api_mem_from_access_resolved(g, lv, access,
+ resolve_type(g->c, access.type));
+}
+
KitCgTypeId api_mem_access_type(KitCg* g, KitCgMemAccess access,
KitCgTypeId fallback, const char* who) {
KitCgTypeId ty = resolve_type(g->c, access.type);
if (!ty) ty = resolve_type(g->c, fallback);
- if (!ty) {
- compiler_panic(g->c, g->cur_loc, "KitCg: %.*s has no value type",
- SLICE_ARG(slice_from_cstr(who)));
- }
+ CG_REQUIRE(g, ty, "KitCg: %.*s has no value type",
+ SLICE_ARG(slice_from_cstr(who)));
return ty;
}
-u32 api_mem_type_size(KitCg* g, KitCgTypeId ty, const char* who) {
- ty = resolve_type(g->c, ty);
- if (!ty) {
- compiler_panic(g->c, g->cur_loc, "KitCg: %.*s has invalid type",
- SLICE_ARG(slice_from_cstr(who)));
- }
- if (cg_type_is_void(g->c, ty)) {
- compiler_panic(g->c, g->cur_loc, "KitCg: %.*s uses void type",
- SLICE_ARG(slice_from_cstr(who)));
- }
+u32 api_mem_type_size_resolved(KitCg* g, KitCgTypeId ty, const char* who) {
+ CG_REQUIRE(g, ty, "KitCg: %.*s has invalid type",
+ SLICE_ARG(slice_from_cstr(who)));
+ CG_REQUIRE(g, !cg_type_is_void(g->c, ty), "KitCg: %.*s uses void type",
+ SLICE_ARG(slice_from_cstr(who)));
return abi_cg_sizeof(g->c->abi, ty);
}
+u32 api_mem_type_size(KitCg* g, KitCgTypeId ty, const char* who) {
+ return api_mem_type_size_resolved(g, resolve_type(g->c, ty), who);
+}
+
+u32 api_require_scalar_mem_type_resolved(KitCg* g, const char* who,
+ KitCgTypeId ty) {
+ u8 bits = api_type_pred_bits(g->c, ty);
+ CG_REQUIRE(g, !(bits & API_PRED_AGGREGATE),
+ "KitCg: %.*s cannot use aggregate value type (size %u); "
+ "copy fields or use byte memory operations",
+ SLICE_ARG(slice_from_cstr(who)),
+ (unsigned)api_mem_type_size_resolved(g, ty, who));
+ return api_mem_type_size_resolved(g, ty, who);
+}
+
void api_require_scalar_mem_type(KitCg* g, const char* who, KitCgTypeId ty) {
- if (cg_type_is_aggregate(g->c, ty)) {
- compiler_panic(g->c, g->cur_loc,
- "KitCg: %.*s cannot use aggregate value type (size %u); "
- "copy fields or use byte memory operations",
- SLICE_ARG(slice_from_cstr(who)),
- (unsigned)api_mem_type_size(g, ty, who));
- }
- (void)api_mem_type_size(g, ty, who);
+ (void)api_require_scalar_mem_type_resolved(g, who, resolve_type(g->c, ty));
}
void api_require_pointer_value(KitCg* g, const char* who, KitCgTypeId ty) {
- if (!cg_type_pointee(g->c, ty)) {
- compiler_panic(g->c, g->cur_loc, "KitCg: %.*s operand must be a pointer",
- SLICE_ARG(slice_from_cstr(who)));
- }
+ CG_REQUIRE(g, cg_type_pointee(g->c, ty),
+ "KitCg: %.*s operand must be a pointer",
+ SLICE_ARG(slice_from_cstr(who)));
}
-void api_validate_memory_value(KitCg* g, const char* who, KitCgTypeId access_ty,
- KitCgTypeId value_ty) {
+void api_validate_memory_value_resolved(KitCg* g, const char* who,
+ KitCgTypeId access_ty,
+ KitCgTypeId value_ty) {
u32 access_size;
u32 value_size;
- access_ty = resolve_type(g->c, access_ty);
- value_ty = resolve_type(g->c, value_ty);
- api_require_scalar_mem_type(g, who, access_ty);
- if (!value_ty) {
- compiler_panic(g->c, g->cur_loc, "KitCg: %.*s value has no type",
- SLICE_ARG(slice_from_cstr(who)));
- }
- if (cg_type_is_aggregate(g->c, value_ty)) {
- compiler_panic(g->c, g->cur_loc,
- "KitCg: %.*s value is aggregate (size %u); copy fields or "
- "use byte memory operations",
- SLICE_ARG(slice_from_cstr(who)),
- (unsigned)api_mem_type_size(g, value_ty, who));
- }
- access_size = api_mem_type_size(g, access_ty, who);
- value_size = api_mem_type_size(g, value_ty, who);
- if (access_size != value_size ||
- api_type_is_float(g->c, access_ty) != api_type_is_float(g->c, value_ty)) {
- compiler_panic(g->c, g->cur_loc,
- "KitCg: %.*s value type/size mismatch: access size %u, "
- "value size %u",
- SLICE_ARG(slice_from_cstr(who)), (unsigned)access_size,
- (unsigned)value_size);
- }
+ u8 access_bits;
+ u8 value_bits;
+ access_size = api_require_scalar_mem_type_resolved(g, who, access_ty);
+ CG_REQUIRE(g, value_ty, "KitCg: %.*s value has no type",
+ SLICE_ARG(slice_from_cstr(who)));
+ access_bits = api_type_pred_bits(g->c, access_ty);
+ value_bits = api_type_pred_bits(g->c, value_ty);
+ CG_REQUIRE(g, !(value_bits & API_PRED_AGGREGATE),
+ "KitCg: %.*s value is aggregate (size %u); copy fields or "
+ "use byte memory operations",
+ SLICE_ARG(slice_from_cstr(who)),
+ (unsigned)api_mem_type_size_resolved(g, value_ty, who));
+ value_size = api_mem_type_size_resolved(g, value_ty, who);
+ CG_REQUIRE(g,
+ !(access_size != value_size ||
+ ((access_bits ^ value_bits) & API_PRED_FLOAT)),
+ "KitCg: %.*s value type/size mismatch: access size %u, "
+ "value size %u",
+ SLICE_ARG(slice_from_cstr(who)), (unsigned)access_size,
+ (unsigned)value_size);
+}
+
+void api_validate_memory_value(KitCg* g, const char* who, KitCgTypeId access_ty,
+ KitCgTypeId value_ty) {
+ api_validate_memory_value_resolved(g, who, resolve_type(g->c, access_ty),
+ resolve_type(g->c, value_ty));
}
int api_sv_owns_operand_local(const ApiSValue* sv, const Operand* op) {
@@ -435,7 +440,7 @@ Operand api_force_local(KitCg* g, ApiSValue* v, KitCgTypeId ty) {
} else if (v->op.kind == OPK_GLOBAL) {
T->addr_of(T, dst, v->op);
} else {
- compiler_panic(g->c, g->cur_loc, "KitCg: cannot force operand to local");
+ CG_BUG(g, "KitCg: cannot force operand to local");
}
v->op = dst;
api_sv_set_res(v, RES_LOCAL);
@@ -722,9 +727,7 @@ Operand api_lvalue_addr(KitCg* g, ApiSValue* v, KitCgTypeId pty) {
if (api_unevaluated(g)) return api_op_imm(0, pty);
api_local_const_address_taken(g, v->source_local);
api_ensure_local(g, v);
- if (!api_is_lvalue_sv(v)) {
- compiler_panic(g->c, g->cur_loc, "KitCg: addr operand is not an lvalue");
- }
+ CG_REQUIRE(g, api_is_lvalue_sv(v), "KitCg: addr operand is not an lvalue");
T = g->target;
r = api_alloc_temp_local(g, pty);
dst = api_op_local(r, pty);
diff --git a/src/core/core.h b/src/core/core.h
@@ -142,6 +142,13 @@ struct KitCompiler {
const KitFrontendVTable* frontends[KIT_LANG_COUNT];
void* cg_api;
void (*cg_api_free)(Compiler*);
+ /* Borrowed pointer to the per-compiler builtin
+ * ABITypeInfo[KIT_CG_BUILTIN_COUNT] layout table inside cg_api state: set
+ * when builtins initialize, NULLed on cg_api teardown. Lets abi_cg_type_info
+ * resolve a builtin's size/align/scalar profile with one inline indexed load
+ * instead of a cross-TU layout-cache call. Opaque here (const void*) so
+ * core.h need not pull abi.h; abi.c casts it to const ABITypeInfo*. */
+ const void* cg_builtin_layout;
/* Wasm frontend host-import configuration. Stashed by
* kit_wasm_set_host_imports; consumed by runners that call
* kit_wasm_bind_host_imports after the link image is produced. The