commit d07e43a3fbb9561a6479b2a1654698bc36cda62e
parent 8e89fac12c3e6ef815053b98ab80f1d5420baabe
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Sat, 13 Jun 2026 21:31:30 -0700
perf(cg): single-decode per-op type predicates (PERF §4.1 #2a)
The hottest CG ops re-asked several predicates of the SAME type id, each a
fresh id->entry decode + unalias-chase. Hoist one api_type_pred_bits() per
distinct id and test the API_PRED_* masks locally instead of calling the
cg_type_is_* wrappers repeatedly:
- kit_cg_load scalar-local fast path: decode base/ty once each (was base
decoded ~3x via two cg_type_is_aggregate + two api_unalias_type).
- kit_cg_store aggregate path: decode the source value's id once for its
aggregate+ptr bits (was cg_type_is_aggregate + cg_type_is_ptr).
- kit_cg_store scalar_local_place: reuse ty_is_agg, decode base once for its
aggregate bit + unalias terminal.
- api_cg_convert_kind bitcast path: sty/dty are already unaliased; decode each
once for aggregate+float bits (was 2x aggregate + 2x float = 4 fresh decodes
re-unaliasing already-unaliased ids).
The API_PRED_* mask membership matches the cg_type_is_* predicates exactly
(src/cg/type.h:91-96): FLOAT<->api_type_is_float, AGGREGATE<->cg_type_is_aggregate,
PTR<->cg_type_is_ptr, all classified on the unaliased terminal. No struct
changes; byte-identical emitted output.
Gate: perf-gate PASS (60/60 byte-identical). sqlite3.c -c object bit-identical
to golden. test-cg-api/test-toy(1392/0)/test-parse/test-opt green.
Measured sqlite -c: ~-9.1M instructions (-0.47%, best-of-9, golden-vs-cand).
Diffstat:
2 files changed, 46 insertions(+), 33 deletions(-)
diff --git a/src/cg/arith.c b/src/cg/arith.c
@@ -279,14 +279,19 @@ void api_cg_convert_kind(KitCg* g, KitCgTypeId dst_type, ConvKind ck) {
* and keep the explicit convert. OPK_IMM is excluded: a bitcast immediate
* that becomes an address base must reach a register (an INDIRECT base cannot
* be an immediate), an invariant the aggregate-arg lowering relies on. */
- if (ck == CV_BITCAST && T->untyped_values && v.op.kind != OPK_IMM &&
- !cg_type_is_aggregate(g->c, sty) && !cg_type_is_aggregate(g->c, dty) &&
- abi_cg_sizeof(g->c->abi, sty) == abi_cg_sizeof(g->c->abi, dty) &&
- api_type_is_float(g->c, sty) == api_type_is_float(g->c, dty)) {
- v.type = dty;
- v.op.type = dty;
- api_push(g, v);
- return;
+ if (ck == CV_BITCAST && T->untyped_values && v.op.kind != OPK_IMM) {
+ /* sty/dty are already unaliased; decode each id's predicate bitset once and
+ * read the aggregate/float bits locally instead of three fresh decodes. */
+ u8 sty_bits = api_type_pred_bits(g->c, sty);
+ u8 dty_bits = api_type_pred_bits(g->c, dty);
+ if (!(sty_bits & API_PRED_AGGREGATE) && !(dty_bits & API_PRED_AGGREGATE) &&
+ abi_cg_sizeof(g->c->abi, sty) == abi_cg_sizeof(g->c->abi, dty) &&
+ !(sty_bits & API_PRED_FLOAT) == !(dty_bits & API_PRED_FLOAT)) {
+ v.type = dty;
+ v.op.type = dty;
+ api_push(g, v);
+ return;
+ }
}
if (ck == CV_BITCAST && abi_cg_sizeof(g->c->abi, sty) == 16 &&
abi_cg_sizeof(g->c->abi, dty) == 16 &&
diff --git a/src/cg/memory.c b/src/cg/memory.c
@@ -282,18 +282,21 @@ void kit_cg_load(KitCg* g, KitCgMemAccess access) {
}
/* Scalar local place: the value already lives in the local; hand it back
- * directly without a memory access. */
+ * directly without a memory access. Decode each id's predicate bitset once
+ * (api_type_pred_bits also warms the entry's unalias cache), then read the
+ * aggregate bit and the unalias terminal locally instead of re-decoding. */
if (!is_bitfield && base.source_local != KIT_CG_LOCAL_NONE &&
- base.op.kind == OPK_LOCAL &&
- !api_sv_local_storage_is_aggregate(g, &base) &&
- !cg_type_is_aggregate(g->c, api_sv_type(&base)) &&
- !cg_type_is_aggregate(g->c, ty) &&
- api_unalias_type(g->c, api_sv_type(&base)) ==
- api_unalias_type(g->c, ty)) {
- base.lvalue = 0;
- base.res = RES_FIXED_LOCAL;
- api_push(g, base);
- return;
+ base.op.kind == OPK_LOCAL && !api_sv_local_storage_is_aggregate(g, &base)) {
+ KitCgTypeId base_ty = api_sv_type(&base);
+ u8 base_bits = api_type_pred_bits(g->c, base_ty);
+ u8 ty_bits = api_type_pred_bits(g->c, ty);
+ if (!(base_bits & API_PRED_AGGREGATE) && !(ty_bits & API_PRED_AGGREGATE) &&
+ api_unalias_type(g->c, base_ty) == api_unalias_type(g->c, ty)) {
+ base.lvalue = 0;
+ base.res = RES_FIXED_LOCAL;
+ api_push(g, base);
+ return;
+ }
}
/* Resolve the place into a single backend memop operand. */
@@ -409,10 +412,11 @@ void kit_cg_store(KitCg* g, KitCgMemAccess access) {
access_ty = ty;
/* Aggregate store: memcpy through the source place. `ty` is stable here, so
- * decode its aggregate predicate once and reuse it below. */
+ * decode its aggregate predicate once and reuse it below; likewise decode the
+ * source value's id once for its aggregate/ptr predicate bits. */
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)))) {
+ u8 rv_bits = api_type_pred_bits(g->c, api_sv_type(&rv));
+ if (!is_bitfield && (ty_is_agg || (rv_bits & API_PRED_AGGREGATE))) {
KitCgTypeId ptr_ty;
Operand dst_addr, src_addr;
int src_ptr_rvalue;
@@ -422,8 +426,7 @@ void kit_cg_store(KitCg* g, KitCgMemAccess access) {
: 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_ptr_rvalue = !api_is_lvalue_sv(&rv) && (rv_bits & API_PRED_PTR);
src_size = src_ptr_rvalue ? access_size
: api_mem_type_size(g, api_sv_type(&rv), "store");
if (!api_is_lvalue_sv(&rv) && !src_ptr_rvalue) {
@@ -478,15 +481,20 @@ void kit_cg_store(KitCg* g, KitCgMemAccess access) {
/* Does this store land a scalar value straight into a local's own storage
* (a plain `x = <expr>` / `int x = <expr>`, not bit-field/aggregate/indirect)?
- */
- int scalar_local_place =
- !is_bitfield && base.source_local != KIT_CG_LOCAL_NONE &&
- base.op.kind == OPK_LOCAL &&
- !api_sv_local_storage_is_aggregate(g, &base) &&
- !cg_type_is_aggregate(g->c, api_sv_type(&base)) &&
- !cg_type_is_aggregate(g->c, ty) &&
- api_unalias_type(g->c, api_sv_type(&base)) ==
- api_unalias_type(g->c, ty);
+ * Single-decode: `ty`'s aggregate bit was already taken (ty_is_agg); decode
+ * base's id once for both its aggregate bit and its unalias terminal. */
+ int scalar_local_place = !is_bitfield &&
+ base.source_local != KIT_CG_LOCAL_NONE &&
+ base.op.kind == OPK_LOCAL &&
+ !api_sv_local_storage_is_aggregate(g, &base) &&
+ !ty_is_agg;
+ if (scalar_local_place) {
+ KitCgTypeId base_ty = api_sv_type(&base);
+ u8 base_bits = api_type_pred_bits(g->c, base_ty);
+ scalar_local_place = !(base_bits & API_PRED_AGGREGATE) &&
+ api_unalias_type(g->c, base_ty) ==
+ api_unalias_type(g->c, ty);
+ }
/* A still-delayed arith/cmp value going into a scalar local: emit the op
* DIRECTLY into the local instead of materializing it into a temp and then