kit

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

commit fe7b3634e9edffdaffc357413b74ccd933045dc8
parent f8c7eacf57f036647a13db18ba687a4160515635
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sat, 13 Jun 2026 09:27:11 -0700

perf(abi): memoize c_abi_record_layout — kill the O(M×N) per-access rebuild

c_abi_record_layout rebuilt a fresh ABIRecordLayout on every member access:
arena_znew + a per-field loop calling kit_cg_type_record_field +
kit_cg_type_size. Mirror cg_id by caching the computed layout pointer on the
Type node (new const ABIRecordLayout* abi_layout, NULL = not built). The layout
is a pure function of the complete record + fixed target, immutable and
arena-lived, so sharing the pointer (read-only callers) is safe. Gated on
complete records only (abi_info_cacheable); incomplete records never cache.

Byte-identical: perf_identity_gate.sh PASS across all categories; test-cg-api
0 failures.

Diffstat:
Mlang/c/abi/c_abi.c | 12+++++++++++-
Mlang/c/type/type.h | 10++++++++++
2 files changed, 21 insertions(+), 1 deletion(-)

diff --git a/lang/c/abi/c_abi.c b/lang/c/abi/c_abi.c @@ -84,10 +84,16 @@ u32 c_abi_alignof(KitCompiler* a, Pool* p, const Type* t) { const ABIRecordLayout* c_abi_record_layout(KitCompiler* a, Pool* p, const Type* t) { - KitCgTypeId id = type_cg_id_in_pool(a, p, t); + KitCgTypeId id; ABIRecordLayout* L; ABIFieldLayout* fl = NULL; u32 nfields; + /* Memoized on the Type node (mirrors cg_id). The layout is a pure function of + * the complete record + the fixed target, so once built it never changes; an + * incomplete record (no stable layout) is never cached. Collapses the former + * O(M member-accesses x N fields) rebuild to one build per distinct record. */ + if (abi_info_cacheable(t) && t->abi_layout) return t->abi_layout; + id = type_cg_id_in_pool(a, p, t); if (kit_cg_type_kind(a, id) != KIT_CG_TYPE_RECORD) return NULL; nfields = kit_cg_type_record_nfields(a, id); L = arena_znew(p->arena, ABIRecordLayout); @@ -114,6 +120,10 @@ const ABIRecordLayout* c_abi_record_layout(KitCompiler* a, Pool* p, L->align = kit_cg_type_align(a, id); L->nfields = nfields; L->fields = fl; + /* Cache for subsequent accesses. Only a complete record is cacheable; the + * RECORD-kind check above already implies the id lowered, but guard anyway so + * an incomplete record (which can't reach here via a RECORD id) never sticks. */ + if (abi_info_cacheable(t)) ((Type*)t)->abi_layout = L; return L; } diff --git a/lang/c/type/type.h b/lang/c/type/type.h @@ -62,6 +62,10 @@ typedef enum TypeQual { typedef struct Type Type; +/* Defined in abi/c_abi.h (which includes this header), forward-declared here so + * Type can cache a computed record layout pointer without a header cycle. */ +struct ABIRecordLayout; + typedef enum FieldFlag { FIELD_NONE = 0, FIELD_BITFIELD = 1u << 0, @@ -92,6 +96,12 @@ struct Type { * set compares named fields and type_tagged_eq compares the rec/enm payload, * so this mutable cache never participates in interning. See type_cg_lower. */ KitCgTypeId cg_id; + /* Memoized record layout for TY_STRUCT/TY_UNION (NULL = not yet built). Like + * cg_id this is a mutable cache, NOT part of type identity — the interning + * comparisons never read it. Only c_abi_record_layout writes it, and only for + * a complete record, whose layout is immutable + arena-lived for the pool's + * lifetime, so sharing the pointer across qualified variants is safe. */ + const struct ABIRecordLayout* abi_layout; union { struct { const Type* pointee;