kit

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

commit 3f68896d566b03a54d6e2ef0887f8c6c38a8fce0
parent 9670b86965fba2c727db42255967f7c64b27e0f0
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sat, 13 Jun 2026 00:57:08 -0700

perf(abi,cg): memoize abi_cg_type_info per type id (F1)

abi_cg_type_info recursed through alias/enum/array chains and was called
several times per statement (often as a sizeof+alignof pair that ran it
twice). Cache the resolved {size,align,scalar_kind,signed,atomic} on the
CgApiType entry — types are immutable once built, so no invalidation is
needed — and split the function into a compute helper behind a caching
wrapper so every recursion level memoizes too. Collapse the per-temp and
per-memop sizeof+alignof pairs (api_alloc_temp_local, api_mem_for_lvalue)
to a single info call. Builtins stay uncached (their compute is already a
single indexed load).

Measured (sqlite3.c -c, best-of-5, byte-identical objects): 2160.8M ->
2153.1M instr (-7.7M, -0.36%); -fsyntax-only 1827.0M -> 1821.6M. This is
well below PERF-TCC-SLIM.md's F1 estimate: cg_type_get already stores
size/align on the CgType, so the recursion+double-call collapse is the
only real saving here, not a full type-query elimination. Kept because it
is free and byte-identical and composes with the rest.

Gate: perf_identity_gate.sh 60/60 byte-identical.

Diffstat:
Msrc/abi/abi.c | 18++++++++++++++++--
Msrc/cg/type.c | 39++++++++++++++++++++++++++++++++++++++-
Msrc/cg/type.h | 12++++++++++++
Msrc/cg/value.c | 15+++++++++++----
4 files changed, 77 insertions(+), 7 deletions(-)

diff --git a/src/abi/abi.c b/src/abi/abi.c @@ -24,10 +24,9 @@ * aarch64 and x86_64). When a Windows-x64 (LLP64) or 32-bit ABI lands, * promote prim_info into the vtable. */ -ABITypeInfo abi_cg_type_info(TargetABI* a, KitCgTypeId id) { +static ABITypeInfo abi_cg_type_info_compute(TargetABI* a, KitCgTypeId id) { ABITypeInfo r = {0, 0, ABI_SC_VOID, 0, 0, 0}; const CgType* t; - if (!id) return r; t = cg_type_get(a->c, id); if (!t) return r; switch (t->kind) { @@ -87,6 +86,21 @@ ABITypeInfo abi_cg_type_info(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; + 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); + return r; +} + u32 abi_cg_sizeof(TargetABI* a, KitCgTypeId id) { return abi_cg_type_info(a, id).size; } diff --git a/src/cg/type.c b/src/cg/type.c @@ -31,7 +31,14 @@ typedef struct CgApiType { * VALID bit distinguishes a computed all-zero class from unfilled). Reuses * the former pad, so the entry size is unchanged. */ u8 cached_class; - u8 pad[1]; + /* Lazily-filled abi_cg_type_info memo (see api_type_layout_get/put). */ + u8 abi_cached; + u8 abi_scalar_kind; + u8 abi_signed; + u8 abi_atomic; + u8 pad[3]; + u32 abi_size; + u32 abi_align; /* 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; @@ -306,6 +313,36 @@ 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) { + CgApiType* e; + if (ty == KIT_CG_TYPE_NONE) return 0; + if ((ty >> CG_API_TYPE_SEG_SHIFT) == CG_API_TYPE_BUILTIN_SEG) return 0; + 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; +} + +void api_type_layout_put(Compiler* c, KitCgTypeId ty, u32 size, u32 align, + u8 scalar_kind, u8 signed_, u8 atomic) { + CgApiType* e; + if (ty == KIT_CG_TYPE_NONE) return; + if ((ty >> CG_API_TYPE_SEG_SHIFT) == CG_API_TYPE_BUILTIN_SEG) 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_cached = 1; +} + static CgApiState* cg_api_get(Compiler* c) { Heap* h; CgApiState* s; diff --git a/src/cg/type.h b/src/cg/type.h @@ -121,6 +121,18 @@ typedef enum ApiWideKind { #define API_TYPE_CLASS_AGGREGATE 0x08u u8 api_type_class(Compiler*, KitCgTypeId); +/* Per-type-id memo for the ABI layout triple (size/align/scalar_kind + the + * signed/atomic riders). abi_cg_type_info recurses through alias/enum/array + * 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); + KitCgTypeId cg_type_ptr_to(Compiler*, KitCgTypeId); KitCgTypeId cg_type_pointee(Compiler*, KitCgTypeId); /* The function's result type, or the VOID builtin when it returns nothing. */ diff --git a/src/cg/value.c b/src/cg/value.c @@ -222,8 +222,9 @@ CGLocal api_alloc_temp_local(KitCg* g, KitCgTypeId ty) { * bounded by max-simultaneous-live temps rather than a per-subexpression sum. */ d.flags |= CG_LOCAL_TRANSIENT; if (ty) { - d.size = abi_cg_sizeof(g->c->abi, ty); - d.align = abi_cg_alignof(g->c->abi, ty); + ABITypeInfo ti = abi_cg_type_info(g->c->abi, ty); + d.size = ti.size; + d.align = ti.align; } /* A split-lane 8-byte scalar temp must live in memory so its two words are * addressable for lane ops and the multi-part ABI path; the allocator gives @@ -243,8 +244,14 @@ MemAccess api_mem_for_lvalue(KitCg* g, const Operand* lv, KitCgTypeId ty) { MemAccess m; memset(&m, 0, sizeof m); m.type = ty; - m.size = ty ? abi_cg_sizeof(g->c->abi, ty) : 0; - m.align = ty ? abi_cg_alignof(g->c->abi, ty) : 0; + if (ty) { + ABITypeInfo ti = abi_cg_type_info(g->c->abi, ty); + m.size = ti.size; + m.align = ti.align; + } else { + m.size = 0; + m.align = 0; + } m.flags = MF_NONE; if (lv->kind == OPK_LOCAL) { m.alias.kind = (u8)ALIAS_LOCAL;