commit af44b31a755fcb5fe512d1554b5157163a38fb48
parent 9fb2baf5d5280fc66fafcff63cf83bf9fca0eaec
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Sat, 13 Jun 2026 07:55:31 -0700
perf(cg): -O0 copy coalescing — `x = <dead transient>` renames, no mov
The scalar-local plain-store path (`x = <value>` / `int x = <value>`) emitted
`T->copy(dst, src)` -> a routing `mov` whenever the RHS landed in a transient
register that the store then copied into x's storage.
Flag that source dead (OPK_FLAG_KILL) when it is a transient with no other live
value-stack reference (api_temp_dead; rv, its last reference, was popped at the
top of kit_cg_store). The nd_copy hook added with convert coalescing then
renames the transient's live register to x instead of emitting the mov — the
producer's register simply becomes x's. A live or shared source stays clear in
the flag and is copied as before (correctness over a missed mov).
sqlite3.c -c -O0 (arm64-macOS): emitted insns 462,967 -> 457,115 (-5,852);
object 2,246,368 -> 2,222,960 B (-1.0%); compile instrs ~2,133.4M -> ~2,131.9M.
Cumulative with convert coalescing: emitted 474,752 -> 457,115 (-3.7%); object
2,293,504 -> 2,222,960 B (-3.1%). Byte-deterministic (compile x2 identical).
Green: test-toy 1392/0, test-parse 3920+129/0, test-smoke-x64/rv64 3/0,
test-cg-api 0 fail, test-opt 0 fail, test-libc musl 18 + glibc 9; sqlite e2e
84|2 at -O0 and -O1; clang-differential probe matches (value-live-after-copy,
multi-copy chains, self-referential) at -O0/-O1/clang.
Diffstat:
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git a/src/cg/memory.c b/src/cg/memory.c
@@ -524,7 +524,15 @@ void kit_cg_store(KitCg* g, KitCgMemAccess access) {
api_local_const_store(g, base.source_local, access, src.v.imm);
} else {
if (src.kind != OPK_LOCAL) src = api_force_local(g, &rv, ty);
- if (src.v.local != dst.v.local) T->copy(T, dst, src);
+ if (src.v.local != dst.v.local) {
+ /* `x = <value in a dead transient>`: flag the source dead so the -O0
+ * backend renames its register to x instead of emitting a routing mov
+ * (rv, its last reference, was popped above). */
+ if (api_coalesce_on(g) && src.kind == OPK_LOCAL &&
+ api_temp_dead(g, src.v.local))
+ src.flags |= OPK_FLAG_KILL;
+ T->copy(T, dst, src);
+ }
if (base.source_local != KIT_CG_LOCAL_NONE)
api_local_const_clear(api_local_from_handle(g, base.source_local));
}