kit

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

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:
Mdoc/plan/PERF.md | 172++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------
Minclude/kit/cg.h | 10+++++++++-
Mlang/c/parse/cg.c | 42+++++++++++++++++++-----------------------
Mlang/c/parse/cg.h | 2+-
Msrc/abi/abi.c | 28++++++++++++++++++----------
Msrc/api/config_stubs.c | 29++++++++++++++++++++++++-----
Msrc/cg/atomic.c | 26++++++++++++++------------
Msrc/cg/data.c | 29+++++++++++++++++++++++++++++
Msrc/cg/debug.c | 120++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
Msrc/cg/internal.h | 15+++++++++++++++
Msrc/cg/local.c | 8++++----
Msrc/cg/memory.c | 132+++++++++++++++++++++++++++++++++++++++++--------------------------------------
Msrc/cg/session.c | 11+++++++++++
Msrc/cg/type.c | 92++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
Msrc/cg/type.h | 15+++++++++------
Msrc/cg/value.c | 145++++++++++++++++++++++++++++++++++++++++---------------------------------------
Msrc/core/core.h | 7+++++++
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