kit

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

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:
Msrc/cg/arith.c | 21+++++++++++++--------
Msrc/cg/memory.c | 58+++++++++++++++++++++++++++++++++-------------------------
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