commit 73e128a2a4859f2f82f39f3cf956ba61bb8a2917
parent ecbd00dc995aefbe600af5bfd5494412f7a59245
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Sat, 13 Jun 2026 01:45:18 -0700
perf(cg): reuse a dead-transient index instead of copying it (kit_cg_elem)
kit_cg_elem copied the dynamic index into a fresh owned local "for unambiguous
ownership" before building the [base + index<<scale + ofs] place. When the index
is already a dead transient (provably referenced by nothing else — the value
stack confirms via api_temp_dead), take it over as the index slot directly and
skip the copy. Falls back to the copy for declared/shared locals and to the
pre-multiply for non-power-of-two strides. Guarded so the reused index can't
alias the base.
sqlite3.c -c, arm64-macOS: 543,470 -> 539,549 insns; reg-reg mov 45,770 -> 44,000.
Compile is net faster with the coalescing on (2.154B vs 2.177B with it disabled),
so the incremental refcount pays for itself.
test-toy 1392/0, test-parse-ok 3920/0 (O0/O1 x D/R/J/E), test-parse-err 129/0,
test-smoke-x64/rv64 3/0; subscript edge cases (struct-field, 2D, non-pow2) verified.
Diffstat:
1 file changed, 16 insertions(+), 7 deletions(-)
diff --git a/src/cg/control.c b/src/cg/control.c
@@ -1109,18 +1109,27 @@ static void api_cg_elem(KitCg* g, u32 elem_size, int64_t offset) {
* index is copied into a fresh local for unambiguous ownership. */
{
int lg2 = cg_scale_to_log2(elemsz);
- CGLocal ir = api_alloc_temp_local(g, idx_ty);
- Operand iro = api_op_local(ir, idx_ty);
+ CGLocal ir;
u8 log2_scale;
- /* Refresh idx_op: allocating `ir` may have materialized a delayed index. */
+ /* Force the index to a local first so we can tell whether it is a dead
+ * transient (provably not referenced elsewhere) that we can take over as the
+ * index slot directly, skipping the ownership copy. */
idx_op = api_force_local_unless_imm(g, &idx, idx_ty);
if (idx.op.kind == OPK_LOCAL) idx_op = idx.op;
- if (lg2 >= 0) {
- T->copy(T, iro, idx_op);
+ if (lg2 >= 0 && idx_op.kind == OPK_LOCAL && idx_op.v.local != base_local &&
+ api_coalesce_on(g) && api_temp_dead(g, idx_op.v.local)) {
+ ir = idx_op.v.local; /* reuse the dead transient; no copy */
log2_scale = (u8)lg2;
} else {
- T->binop(T, BO_IMUL, iro, idx_op, api_op_imm((i64)elemsz, idx_ty));
- log2_scale = 0;
+ ir = api_alloc_temp_local(g, idx_ty);
+ if (lg2 >= 0) {
+ T->copy(T, api_op_local(ir, idx_ty), idx_op);
+ log2_scale = (u8)lg2;
+ } else {
+ T->binop(T, BO_IMUL, api_op_local(ir, idx_ty), idx_op,
+ api_op_imm((i64)elemsz, idx_ty));
+ log2_scale = 0;
+ }
}
api_release(g, &base);
api_release(g, &idx);