kit

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

commit 704f1a09fbf4c1342cfcdc75a1e41468bd1e4bc6
parent 537cb4cc23c3e08565bb8bd04a6bf6ca9d04c399
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sat, 13 Jun 2026 09:31:14 -0700

perf(cg): flat per-id type predicate descriptor — one load, no alias re-walk

Extend the proven cached_class memo: pack a {pred_bits, pred_valid, unalias_filled,
unaliased} descriptor onto CgApiType (reusing the former pad[3] + one u32). The
cg_type_is_{int,float,ptr,void,record,aggregate} predicates and api_unalias_type
re-derived recursively through cg_type_get<->alias-walk on every operand push
(cg_type_is_aggregate alone was 31M self / 550K calls). Now:
  - predicates: builtin fast path, else one api_type_from_id load + a byte test;
    pred_bits is classified ONCE on the unaliased terminal and cached.
  - api_unalias_type: returns the cached terminal id after a one-time walk.

Membership reproduces the legacy predicates EXACTLY (IS_INT covers INT|BOOL|ENUM;
IS_AGGREGATE == IS_RECORD). Builtins are never aliases (no entry, classified
directly); user-type entries are zero-initialized by segvec push so the valid
bits start clear; ids are immutable so the cache never goes stale.

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

Diffstat:
Msrc/cg/type.c | 121++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
1 file changed, 92 insertions(+), 29 deletions(-)

diff --git a/src/cg/type.c b/src/cg/type.c @@ -36,14 +36,34 @@ typedef struct CgApiType { u8 abi_scalar_kind; u8 abi_signed; u8 abi_atomic; - u8 pad[3]; + /* Lazily-filled flat predicate descriptor (reuses the former pad[3]). Extends + * the cached_class mechanism: pred_bits is a bitset of API_PRED_* computed on + * the UNALIASED type, pred_valid distinguishes a computed all-zero set from + * unfilled, and `unaliased` caches api_unalias_type's terminal id (valid once + * unalias_filled is set). Predicates and api_unalias_type then become one + * descriptor load instead of a recursive alias walk + per-kind re-decode. */ + u8 pred_bits; + u8 pred_valid; + u8 unalias_filled; u32 abi_size; u32 abi_align; + /* Terminal id of this entry's alias chain (set when unalias_filled). */ + KitCgTypeId unaliased; /* 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; } 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 + /* 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. */ #define API_TYPE_CLASS_CACHED 0x80u @@ -395,48 +415,72 @@ uint32_t cg_type_align(Compiler* c, KitCgTypeId id) { return ty ? ty->align : 0; } -int cg_type_is_int(Compiler* c, KitCgTypeId id) { - const CgType* ty = cg_type_get(c, id); - if (ty && ty->kind == KIT_CG_TYPE_ALIAS) { - return cg_type_is_int(c, ty->alias.base); +/* Predicate bitset of a single (already-unaliased) CgType node. The kind->bit + * mapping reproduces the legacy cg_type_is_* predicates exactly. */ +static u8 api_pred_bits_for_kind(const CgType* ty) { + if (!ty) return 0; + switch (ty->kind) { + case KIT_CG_TYPE_INT: + case KIT_CG_TYPE_BOOL: + case KIT_CG_TYPE_ENUM: + return API_PRED_INT; + case KIT_CG_TYPE_FLOAT: + return API_PRED_FLOAT; + case KIT_CG_TYPE_PTR: + return API_PRED_PTR; + case KIT_CG_TYPE_VOID: + return API_PRED_VOID; + case KIT_CG_TYPE_RECORD: + return API_PRED_RECORD | API_PRED_AGGREGATE; + default: + return 0; } - return ty && (ty->kind == KIT_CG_TYPE_INT || ty->kind == KIT_CG_TYPE_BOOL || - ty->kind == KIT_CG_TYPE_ENUM); } -int cg_type_is_float(Compiler* c, KitCgTypeId id) { - const CgType* ty = cg_type_get(c, id); - if (ty && ty->kind == KIT_CG_TYPE_ALIAS) { - return cg_type_is_float(c, ty->alias.base); +/* 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) { + CgApiType* e; + if (id == KIT_CG_TYPE_NONE) return 0; + if ((id >> CG_API_TYPE_SEG_SHIFT) == CG_API_TYPE_BUILTIN_SEG) { + /* Builtins are never aliases; classify the node directly. */ + return (api_pred_bits_for_kind(cg_type_get(c, id)) & mask) != 0; + } + e = api_type_from_id(c, id); + if (!e) return 0; + if (!e->pred_valid) { + /* Classify on the unaliased terminal — the legacy predicates recurse + * through KIT_CG_TYPE_ALIAS before testing the kind. */ + KitCgTypeId u = api_unalias_type(c, id); + e->pred_bits = api_pred_bits_for_kind(cg_type_get(c, u)); + e->pred_valid = 1; } - return ty && ty->kind == KIT_CG_TYPE_FLOAT; + return (e->pred_bits & mask) != 0; +} + +int cg_type_is_int(Compiler* c, KitCgTypeId id) { + return api_type_pred(c, id, API_PRED_INT); +} + +int cg_type_is_float(Compiler* c, KitCgTypeId id) { + return api_type_pred(c, id, API_PRED_FLOAT); } int cg_type_is_ptr(Compiler* c, KitCgTypeId id) { - const CgType* ty = cg_type_get(c, id); - if (ty && ty->kind == KIT_CG_TYPE_ALIAS) { - return cg_type_is_ptr(c, ty->alias.base); - } - return ty && ty->kind == KIT_CG_TYPE_PTR; + return api_type_pred(c, id, API_PRED_PTR); } int cg_type_is_record(Compiler* c, KitCgTypeId id) { - const CgType* ty = cg_type_get(c, id); - if (ty && ty->kind == KIT_CG_TYPE_ALIAS) { - return cg_type_is_record(c, ty->alias.base); - } - return ty && ty->kind == KIT_CG_TYPE_RECORD; + return api_type_pred(c, id, API_PRED_RECORD); } int cg_type_is_void(Compiler* c, KitCgTypeId id) { - const CgType* ty = cg_type_get(c, id); - if (ty && ty->kind == KIT_CG_TYPE_ALIAS) - return cg_type_is_void(c, ty->alias.base); - return ty && ty->kind == KIT_CG_TYPE_VOID; + return api_type_pred(c, id, API_PRED_VOID); } int cg_type_is_aggregate(Compiler* c, KitCgTypeId id) { - return cg_type_is_record(c, id); + return api_type_pred(c, id, API_PRED_AGGREGATE); } KitCgTypeId cg_type_ptr_to(Compiler* c, KitCgTypeId pointee) { @@ -588,12 +632,31 @@ KitCgTypeId resolve_type(Compiler* c, KitCgTypeId id) { } KitCgTypeId api_unalias_type(Compiler* c, KitCgTypeId id) { - const CgType* ty = cg_type_get(c, id); + CgApiType* e; + const CgType* ty; + KitCgTypeId start = id; + /* Builtins are never aliases — no entry, no chain to walk or cache. */ + if (id == KIT_CG_TYPE_NONE || + (id >> CG_API_TYPE_SEG_SHIFT) == CG_API_TYPE_BUILTIN_SEG) { + ty = cg_type_get(c, id); + return ty ? id : KIT_CG_TYPE_NONE; + } + e = api_type_from_id(c, id); + if (e && e->unalias_filled) return e->unaliased; + ty = cg_type_get(c, id); while (ty && ty->kind == KIT_CG_TYPE_ALIAS) { id = ty->alias.base; ty = cg_type_get(c, id); } - return ty ? id : KIT_CG_TYPE_NONE; + id = ty ? id : KIT_CG_TYPE_NONE; + /* Cache on the entry for the start id. (start == the original id; e was + * fetched for it above. Only fill for a real entry.) */ + if (e) { + e->unaliased = id; + e->unalias_filled = 1; + } + (void)start; + return id; } static KitCgFuncParam* copy_cg_params(Compiler* c, const KitCgFuncParam* src,