kit

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

commit 486575cfb77911b5f89ce8f54eb8f3263ff32c96
parent fb17391cb69f024c19807c67bda922243b50d655
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sat, 13 Jun 2026 10:09:20 -0700

perf(cg): -O0 carry lowered CgId on the parser value-stack slot (D.3b)

The parser's PcgSlot carried only const Type*, so pcg_mem / pcg_load re-derived
the KitCgTypeId via type_cg_id_in_pool (>1.2M calls) on every operand, feeding
the predicate/decode traffic api_type_pred sits on.

Carry the lowered cg_id ON the slot, stamped together with slot->type through a
single pcg_slot_set_type helper (every type-write site routed through it; whole-
slot dup/swap/rot3 copies carry it along). The id is lowered lazily on first
read (pcg_slot_cg_id) so an incomplete record stamped at push still lowers to
its real layout once completed — identical to a direct type_cg_id_in_pool call.
pcg_load reads the cached id via pcg_mem_id, skipping the bridge re-crossing
(and the double-lowering pcg_mem did for type + align).

Gate: full byte-identity gate PASS (the gate caught one missed type-restore
site mid-development — fixed); test-cg-api 211/0.

Diffstat:
Mlang/c/parse/cg_adapter.c | 64++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Mlang/c/parse/cg_adapter.h | 11++++++++++-
2 files changed, 60 insertions(+), 15 deletions(-)

diff --git a/lang/c/parse/cg_adapter.c b/lang/c/parse/cg_adapter.c @@ -6,12 +6,37 @@ KitCgTypeId pcg_tid(Parser* p, const Type* ty) { return type_cg_id_in_pool(p->c, p->pool, ty); } -static u32 pcg_sizeof(Parser* p, const Type* ty) { - return (u32)kit_cg_type_size(p->c, pcg_tid(p, ty)); +/* Set both a slot's C type and its lowered CG id together so the two never + * drift. Every site that writes slot->type must go through this (or copy a + * whole slot, which carries cg_id along). The id is lowered lazily on first + * read (pcg_slot_cg_id), not here, so an incomplete record stamped at push time + * still lowers to its real layout once completed -- identical to a direct + * type_cg_id_in_pool call. */ +static void pcg_slot_set_type(PcgSlot* s, const Type* ty) { + s->type = ty; + s->cg_id = KIT_CG_TYPE_NONE; /* lazily lowered on first read */ +} + +/* The slot's lowered CG id, lowering+caching on first read. A NULL type yields + * KIT_CG_TYPE_NONE every time (the sentinel also means "unfilled", but + * type_cg_id_in_pool(NULL) is constant so re-lowering is free). For a real type + * the first read crosses the bridge once; later reads are a load. */ +static KitCgTypeId pcg_slot_cg_id(Parser* p, PcgSlot* s) { + if (s->cg_id == KIT_CG_TYPE_NONE && s->type) + s->cg_id = type_cg_id_in_pool(p->c, p->pool, s->type); + return s->cg_id; } -static u32 pcg_alignof(Parser* p, const Type* ty) { - return (u32)kit_cg_type_align(p->c, pcg_tid(p, ty)); +/* The cached CG id of the TOS slot's type, lowering lazily on first read. + * Empty-stack-safe (returns NONE), matching pcg_tid(p, pcg_top_type(p)). */ +static KitCgTypeId pcg_top_cg_id(Parser* p) { + return p->cg_type_sp + ? pcg_slot_cg_id(p, &p->cg_slot_stack[p->cg_type_sp - 1u]) + : KIT_CG_TYPE_NONE; +} + +static u32 pcg_sizeof(Parser* p, const Type* ty) { + return (u32)kit_cg_type_size(p->c, pcg_tid(p, ty)); } #define PCG_VALUE_LVALUE 1u @@ -29,15 +54,22 @@ static u8 pcg_lvalue_flags_for_type(const Type* ty) { return flags; } -KitCgMemAccess pcg_mem(Parser* p, const Type* ty) { +/* Build a MemAccess from an already-lowered CG id plus the C type (for its + * qualifiers). Lets a caller that already has the slot's cached cg_id skip the + * type_cg_id_in_pool re-crossing pcg_mem(ty) does twice (lower + align). */ +static KitCgMemAccess pcg_mem_id(Parser* p, KitCgTypeId id, const Type* ty) { KitCgMemAccess m; memset(&m, 0, sizeof m); - m.type = pcg_tid(p, ty); - m.align = pcg_alignof(p, ty); + 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; return m; } +KitCgMemAccess pcg_mem(Parser* p, const Type* ty) { + return pcg_mem_id(p, pcg_tid(p, ty), ty); +} + static void pcg_aux_clear(PcgLvAux* a) { a->offset = 0; a->scale = 0; @@ -68,7 +100,7 @@ void pcg_push_type(Parser* p, const Type* ty) { PcgSlot* s; pcg_stack_grow(p, p->cg_type_sp + 1u); s = &p->cg_slot_stack[p->cg_type_sp]; - s->type = ty; + pcg_slot_set_type(s, ty); s->flags = 0; pcg_aux_clear(&s->aux); ++p->cg_type_sp; @@ -83,11 +115,12 @@ void pcg_dup_type(Parser* p) { if (p->cg_type_sp) { top = p->cg_slot_stack[p->cg_type_sp - 1u]; } else { - top.type = NULL; + pcg_slot_set_type(&top, NULL); top.flags = 0; pcg_aux_clear(&top.aux); } - /* Copy the slot out before push: pcg_stack_grow may reallocate. */ + /* Copy the slot out before push: pcg_stack_grow may reallocate. The whole + * slot (cg_id included) is copied back at the end, so dup preserves the id. */ pcg_push_type(p, top.type); if (p->cg_type_sp) p->cg_slot_stack[p->cg_type_sp - 1u] = top; } @@ -157,7 +190,7 @@ const Type* pcg_top2_type(Parser* p) { void pcg_retag_top(Parser* p, const Type* ty) { if (p->cg_type_sp) { - p->cg_slot_stack[p->cg_type_sp - 1u].type = ty; + pcg_slot_set_type(&p->cg_slot_stack[p->cg_type_sp - 1u], ty); p->cg_slot_stack[p->cg_type_sp - 1u].flags = 0; pcg_aux_clear(&p->cg_slot_stack[p->cg_type_sp - 1u].aux); } @@ -169,7 +202,7 @@ void pcg_retag_top(Parser* p, const Type* ty) { * changes (e.g. struct unqualification, narrowing a compound-assign LHS). */ void pcg_retag_keep_flags(Parser* p, u32 depth, const Type* ty) { if (p->cg_type_sp > depth) - p->cg_slot_stack[p->cg_type_sp - 1u - depth].type = ty; + pcg_slot_set_type(&p->cg_slot_stack[p->cg_type_sp - 1u - depth], ty); } int pcg_top_is_bitfield(Parser* p) { @@ -492,7 +525,7 @@ void pcg_load(Parser* p) { int was_lvalue = pcg_top_is_lvalue(p); if (pcg_emit_enabled(p)) { PcgLvAux* lv = pcg_top_lv_aux(p); - KitCgMemAccess access = pcg_mem(p, ty); + KitCgMemAccess access = pcg_mem_id(p, pcg_top_cg_id(p), ty); /* Snapshot bit-field geometry before materialize clears the aux. */ PcgLvAux bf = lv ? *lv : (PcgLvAux){0}; /* Build the PLACE the strict load requires. A trivial local already has its @@ -510,7 +543,10 @@ void pcg_load(Parser* p) { kit_cg_load(p->cg, access); } if (was_lvalue && p->cg_type_sp) { - p->cg_slot_stack[p->cg_type_sp - 1u].type = ty; + /* The emit block above can rewrite the TOS slot's type (materialize / + * deref push/drop), so restore it to the loaded value's type `ty` here -- + * stamping cg_id alongside it. */ + pcg_slot_set_type(&p->cg_slot_stack[p->cg_type_sp - 1u], ty); p->cg_slot_stack[p->cg_type_sp - 1u].flags = 0; pcg_aux_clear(&p->cg_slot_stack[p->cg_type_sp - 1u].aux); } diff --git a/lang/c/parse/cg_adapter.h b/lang/c/parse/cg_adapter.h @@ -61,10 +61,19 @@ typedef struct PcgLvAux { /* One shadow-stack slot: the CG type of a logical C value plus its parser-side * value flags (PCG_VALUE_*) and lvalue aux, kept in lockstep with the CG value - * stack. The parser pushes/dups/swaps/rotates whole slots. */ + * stack. The parser pushes/dups/swaps/rotates whole slots. + * + * `cg_id` is the lowered KitCgTypeId of `type`, stamped together with `type` + * (see pcg_slot_set_type) so the hot pcg_mem / pcg_load queries read it directly + * instead of re-crossing the Type*->CgId bridge via type_cg_id_in_pool on every + * operand. KIT_CG_TYPE_NONE doubles as "type is NULL or not yet lowered"; the + * lazy filler (pcg_slot_cg_id) lowers on first read so it tracks any later + * record completion exactly like a direct type_cg_id_in_pool call would. cg_id + * is a pure function of `type`, so whole-slot copies (dup/swap/rot3) carry it. */ typedef struct PcgSlot { const Type* type; PcgLvAux aux; + KitCgTypeId cg_id; u8 flags; } PcgSlot;