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:
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;