kit

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

commit 9a060c6c4f6e3d811e3d5d0a493a73625571062d
parent 407e8ac9402c41d518db4ec5f084baea375f33ae
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sat, 13 Jun 2026 11:40:17 -0700

perf(cg): one type decode per id at multi-predicate sites + hoist non-CSE unalias (B1)

(cherry picked from commit a687d419874a87ced7dc15ff447a9cd059b55815)

Diffstat:
Msrc/cg/local.c | 5+++--
Msrc/cg/memory.c | 19++++++++++---------
Msrc/cg/type.c | 32++++++++++++++++----------------
Msrc/cg/type.h | 14++++++++++++++
Msrc/cg/value.c | 14++++++--------
5 files changed, 49 insertions(+), 35 deletions(-)

diff --git a/src/cg/local.c b/src/cg/local.c @@ -10,8 +10,9 @@ int api_local_requires_memory(KitCg* g, KitCgTypeId ty, KitCgLocalAttrs attrs) { * state, and any non-scalar type must live in memory. */ if (api_is_wide16_scalar_type(g->c, ty)) return 1; if (api_is_wide8_scalar_type(g->c, ty)) return 1; - return !(cg_type_is_int(g->c, ty) || cg_type_is_float(g->c, ty) || - cg_type_is_ptr(g->c, ty)); + /* int||float||ptr, in one type decode instead of three. */ + return !(api_type_pred_bits(g->c, ty) & + (API_PRED_INT | API_PRED_FLOAT | API_PRED_PTR)); } KitCgLocal api_local_handle(u32 index) { diff --git a/src/cg/memory.c b/src/cg/memory.c @@ -388,6 +388,7 @@ void kit_cg_store(KitCg* g, KitCgMemAccess access) { Operand mem_op; int is_lvalue; int is_bitfield; + int ty_is_agg; if (!g) return; T = g->target; if (access.flags & KIT_CG_MEM_VOLATILE) api_local_const_memory_boundary(g); @@ -407,20 +408,20 @@ void kit_cg_store(KitCg* g, KitCgMemAccess access) { ty = api_mem_access_type(g, access, api_sv_type(&base), "store"); access_ty = ty; - /* Aggregate store: memcpy through the source place. */ - if (!is_bitfield && (cg_type_is_aggregate(g->c, ty) || - cg_type_is_aggregate(g->c, api_sv_type(&rv)))) { + /* Aggregate store: memcpy through the source place. `ty` is stable here, so + * decode its aggregate predicate once and reuse it below. */ + ty_is_agg = cg_type_is_aggregate(g->c, ty); + if (!is_bitfield && + (ty_is_agg || cg_type_is_aggregate(g->c, api_sv_type(&rv)))) { KitCgTypeId ptr_ty; Operand dst_addr, src_addr; int src_ptr_rvalue; AggregateAccess agg; u32 src_size; - u32 dst_size = cg_type_is_aggregate(g->c, ty) - ? api_mem_type_size(g, ty, "store") - : api_mem_type_size(g, api_sv_type(&base), "store"); - u32 access_size = cg_type_is_aggregate(g->c, ty) - ? api_mem_type_size(g, ty, "store") - : dst_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; src_ptr_rvalue = !api_is_lvalue_sv(&rv) && cg_type_is_ptr(g->c, api_sv_type(&rv)); src_size = src_ptr_rvalue ? access_size diff --git a/src/cg/type.c b/src/cg/type.c @@ -54,15 +54,8 @@ typedef struct CgApiType { KitCgTypeId self_id; } CgApiType; -/* Flat per-id predicate bitset, computed on the UNALIASED type. The membership - * rules MUST match the legacy cg_type_is_* exactly (notably: IS_INT covers - * INT|BOOL|ENUM, and IS_AGGREGATE == IS_RECORD). */ -#define API_PRED_INT 0x01u -#define API_PRED_FLOAT 0x02u -#define API_PRED_PTR 0x04u -#define API_PRED_VOID 0x08u -#define API_PRED_RECORD 0x10u -#define API_PRED_AGGREGATE 0x20u +/* The flat per-id predicate bitset (API_PRED_*) and api_type_pred_bits live in + * cg/type.h so multi-predicate-on-same-id call sites can decode an id once. */ /* High bit of CgApiType.cached_class: set once the class has been computed, so * a genuine {WK_NARROW, non-aggregate} (packed 0) is told apart from unfilled. */ @@ -447,10 +440,11 @@ static u8 api_pred_bits_for_kind(const CgType* ty) { } } -/* One predicate query for `id`: builtin fast path (no aliasing, no entry) or a - * descriptor load off the user-type entry, filled lazily on first miss. Returns - * (bits & mask) != 0. Mirrors api_type_class's CACHED-bit memo. */ -static int api_type_pred(Compiler* c, KitCgTypeId id, u8 mask) { +/* Full API_PRED_* bitset for `id` via exactly one decode: builtin fast path (no + * aliasing, no entry) or a descriptor load off the user-type entry, filled + * lazily on first miss. Multi-predicate-on-the-same-id call sites read this once + * and then test the masks locally instead of decoding `id` per predicate. */ +u8 api_type_pred_bits(Compiler* c, KitCgTypeId id) { CgApiType* e; if (id == KIT_CG_TYPE_NONE) return 0; if ((id >> CG_API_TYPE_SEG_SHIFT) == CG_API_TYPE_BUILTIN_SEG) { @@ -458,8 +452,8 @@ static int api_type_pred(Compiler* c, KitCgTypeId id, u8 mask) { * init, so this is one indexed load (no cg_type_get + re-classify). */ u32 off = id & CG_API_TYPE_SEG_MASK; CgApiState* s = c->cg_api ? (CgApiState*)c->cg_api : cg_api_get(c); - if (s && off < KIT_CG_BUILTIN_COUNT) return (s->builtin_pred[off] & mask) != 0; - return (api_pred_bits_for_kind(cg_type_get(c, id)) & mask) != 0; + if (s && off < KIT_CG_BUILTIN_COUNT) return s->builtin_pred[off]; + return api_pred_bits_for_kind(cg_type_get(c, id)); } e = api_type_from_id(c, id); if (!e) return 0; @@ -470,7 +464,13 @@ static int api_type_pred(Compiler* c, KitCgTypeId id, u8 mask) { e->pred_bits = api_pred_bits_for_kind(cg_type_get(c, u)); e->pred_valid = 1; } - return (e->pred_bits & mask) != 0; + return e->pred_bits; +} + +/* One predicate query for `id`: returns (bits & mask) != 0 off the single-decode + * bitset. Mirrors api_type_class's CACHED-bit memo. */ +static int api_type_pred(Compiler* c, KitCgTypeId id, u8 mask) { + return (api_type_pred_bits(c, id) & mask) != 0; } int cg_type_is_int(Compiler* c, KitCgTypeId id) { diff --git a/src/cg/type.h b/src/cg/type.h @@ -88,6 +88,20 @@ KitCgTypeId api_unalias_type(Compiler*, KitCgTypeId); int cg_type_is_void(Compiler*, KitCgTypeId); int cg_type_is_aggregate(Compiler*, KitCgTypeId); +/* Flat per-id predicate bitset (computed on the UNALIASED type). The six + * cg_type_is_* predicates are (bits & API_PRED_*) tests over this set; a site + * that asks several predicates of the SAME id reads the bitset once via + * api_type_pred_bits and tests the masks locally (one decode, not N). The + * membership rules MUST match cg_type_is_* exactly: INT covers INT|BOOL|ENUM and + * AGGREGATE == RECORD. */ +#define API_PRED_INT 0x01u +#define API_PRED_FLOAT 0x02u +#define API_PRED_PTR 0x04u +#define API_PRED_VOID 0x08u +#define API_PRED_RECORD 0x10u +#define API_PRED_AGGREGATE 0x20u +u8 api_type_pred_bits(Compiler*, KitCgTypeId); + /* Codegen-relevant scalar predicates: a value of this type is a float, a * 128-bit scalar (f128 / i128), or a wide 8-byte scalar the selected ABI splits * into two 32-bit lanes (forced memory-resident; legalized as lane sequences / diff --git a/src/cg/value.c b/src/cg/value.c @@ -366,15 +366,14 @@ int api_sv_owns_operand_local(const ApiSValue* sv, const Operand* op) { void api_ensure_local(KitCg* g, ApiSValue* sv) { if (sv->kind == SV_CMP) { KitCgTypeId ty = api_sv_type(sv); + KitCgTypeId uty = api_unalias_type(g->c, ty); /* compute the dst terminal once */ Operand dst; if (sv->delayed->cmp.a_owned && sv->delayed->cmp.a.kind == OPK_LOCAL && - api_unalias_type(g->c, sv->delayed->cmp.a.type) == - api_unalias_type(g->c, ty)) { + api_unalias_type(g->c, sv->delayed->cmp.a.type) == uty) { dst = api_op_local(sv->delayed->cmp.a.v.local, ty); } else if (sv->delayed->cmp.b_owned && sv->delayed->cmp.b.kind == OPK_LOCAL && - api_unalias_type(g->c, sv->delayed->cmp.b.type) == - api_unalias_type(g->c, ty)) { + api_unalias_type(g->c, sv->delayed->cmp.b.type) == uty) { dst = api_op_local(sv->delayed->cmp.b.v.local, ty); } else { CGLocal r = @@ -386,15 +385,14 @@ void api_ensure_local(KitCg* g, ApiSValue* sv) { } if (sv->kind == SV_ARITH) { KitCgTypeId ty = api_sv_type(sv); + KitCgTypeId uty = api_unalias_type(g->c, ty); /* compute the dst terminal once */ Operand dst; if (sv->delayed->arith.a_owned && sv->delayed->arith.a.kind == OPK_LOCAL && - api_unalias_type(g->c, sv->delayed->arith.a.type) == - api_unalias_type(g->c, ty)) { + api_unalias_type(g->c, sv->delayed->arith.a.type) == uty) { dst = api_op_local(sv->delayed->arith.a.v.local, ty); } else if (api_arith_rhs_reusable(sv) && sv->delayed->arith.b_owned && sv->delayed->arith.b.kind == OPK_LOCAL && - api_unalias_type(g->c, sv->delayed->arith.b.type) == - api_unalias_type(g->c, ty)) { + api_unalias_type(g->c, sv->delayed->arith.b.type) == uty) { dst = api_op_local(sv->delayed->arith.b.v.local, ty); } else { CGLocal r =