kit

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

commit 93aafda346d271d17ce2ea95abb2083e4ff896b2
parent 94a15adbf27f18e030686e70efaa88605dc9d9bd
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sat, 13 Jun 2026 06:31:47 -0700

perf(cg): -O0 register-resident return — drop the ret home round-trip

nd_ret flushed the whole value cache and then plan_ret read the return
value back from its frame home, emitting `stur <result>,[home]; ldur
x0,[home]` (plus dead writebacks) in every function. At a return every
cached local is dead — the frame is about to be torn down — so source the
return value straight from its live register when cached and discard the
cache without writing it back (nd_drop_all). A non-cached value (multi-part
/ aggregate / memory-required) stays authoritative in its home, and the
emit_ret ops path (no production arch installs it) keeps the legacy
flush-then-read behavior.

Return path: `stur w8,[home]; ldur w0,[home]` -> `mov w0, w8`.

sqlite3.c -c -O0 (arm64-macOS): emitted insns 526,681 -> 520,854 (-5,827);
object 2,501,224 -> 2,477,912 B (-0.93%); compile instrs 2,153.1M ->
2,150.9M. Byte-deterministic (compile x2 identical).

Green: test-toy 1392/0, test-parse-ok 3920/0, test-parse-err 129/0,
test-smoke-x64 3/0, test-smoke-rv64 3/0; sqlite end-to-end 84|2.

Diffstat:
Msrc/cg/native_direct_target.c | 26++++++++++++++++++++++++--
1 file changed, 24 insertions(+), 2 deletions(-)

diff --git a/src/cg/native_direct_target.c b/src/cg/native_direct_target.c @@ -666,6 +666,16 @@ static void nd_flush_all(NativeDirectTarget* d) { while (d->cache_head >= 0) nd_flush_local(d, (CGLocal)(d->cache_head + 1)); } +/* Discard the whole cache WITHOUT writing entries back. Only sound where every + * cached value is provably dead — at a return the frame is about to be + * destroyed, so no home is ever read again. Lets nd_ret source the return value + * from its live register and skip the round-trip + the dead writebacks a + * flush-all would emit. */ +static void nd_drop_all(NativeDirectTarget* d) { + while (d->cache_head >= 0) + nd_invalidate_local(d, (CGLocal)(d->cache_head + 1)); +} + static NativeAddr nd_addr_materialize(NativeDirectTarget* d, NativeAddr in, NdAddrTemps* temps, MemAccess mem) { NativeAddr out = in; @@ -1776,15 +1786,27 @@ static void nd_ret(CgTarget* t, CGLocal value) { const NativeLoc* locp = NULL; NativeCallPlanRet* rets = NULL; u32 nrets = 0; - nd_flush_all(d); + /* The emit_ret ops path reads `value` from its home, so it needs the cache + * spilled first (no production arch installs it today). */ if (d->ops && d->ops->emit_ret) { + nd_flush_all(d); d->ops->emit_ret(d, value); return; } + /* Default path: at a return every cached value is dead (the frame is about to + * be torn down), so source the return value straight from its live register + * when cached and drop the cache without spilling. This removes the + * per-function `stur <result>,[home]; ldur x0,[home]` round-trip and the dead + * writebacks the old flush-all emitted. A non-cached value (multi-part / + * aggregate / memory-required) is already authoritative in its home. */ if (value != CG_LOCAL_NONE) { - loc = nd_loc_frame(d, value, 0); + NativeDirectLocal* l = nd_local(d, value); + loc = l->reg != REG_NONE + ? native_loc_reg(l->type, (NativeAllocClass)l->cls, l->reg) + : nd_loc_frame(d, value, 0); locp = &loc; } + nd_drop_all(d); ND_REQUIRE_NATIVE(d, plan_ret, "target does not plan returns"); d->native->plan_ret(d->native, d->func, locp, &rets, &nrets); for (u32 i = 0; i < nrets; ++i)