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:
| M | src/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,