kit

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

commit f409e1485107d1a9127abf984f5df548417b68b3
parent cac0d499663f47820d827dba6a68084912dc4b38
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sat, 13 Jun 2026 13:03:25 -0700

perf(cg): -O0 materialize copy source straight into the destination register (L4 Phase 2)

nd_copy materialized the source into a fresh scratch and then moved it to the
destination (ldur scratch,[home] ; mov dst,scratch). Acquire the destination
register first and materialize the source directly into it via a new
nd_materialize_operand_into: a memory / immediate / global / indirect source
loads straight into dst (one insn, no mov); only a cache-resident source still
costs the one unavoidable reg-reg move. This is the general form of the lazy
'compute into place' destination hint applied to the assignment path.

sqlite3.c -O0 .text: -16,294 mov / -16,151 insns (1.2201x -> 1.1730x tcc).
Gate: determinism + sqlite e2e O0/O1 (golden+vs-clang) + toy/parse/smoke-x64/
smoke-rv64/dwarf/debug all green; alloca + far-slot probes match clang.

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

diff --git a/src/cg/native_direct_target.c b/src/cg/native_direct_target.c @@ -969,6 +969,28 @@ static NativeLoc nd_materialize_operand(NativeDirectTarget* d, Operand op) { return nd_materialize_loc(d, nd_loc_operand(d, op), cls, op.type); } +/* Materialize OP straight into the already-acquired register WANT, skipping the + * scratch round-trip nd_materialize_operand + a separate move would make for a + * memory / immediate / global / indirect source: nd_copy_to_reg loads each of + * those forms directly into its destination. A cache-resident local still costs + * one reg-reg move (its value lives in another register); everything else lands + * in WANT with a single load. WANT must already be pinned by the caller. */ +static void nd_materialize_operand_into(NativeDirectTarget* d, NativeLoc want, + Operand op) { + if (op.kind == OPK_LOCAL) { + NativeDirectLocal* l = nd_local(d, op.v.local); + if (l->reg != REG_NONE && op.type == l->type && nd_local_cacheable(d, l)) { + nd_touch_local(d, l); + nd_copy_to_reg(d, want, + nd_loc_reg(d, op.type, (NativeAllocClass)l->cls, l->reg)); + return; + } + if (l->reg != REG_NONE) nd_flush_local(d, op.v.local); + } + nd_flush_operand_addr_locals(d, op); + nd_copy_to_reg(d, want, nd_loc_operand(d, op)); +} + static NativeLoc nd_dst_scratch(NativeDirectTarget* d, Operand dst) { NativeAllocClass cls = nd_class_for_type(d, dst.type); Reg r = nd_scratch_acquire(d, cls); @@ -1520,11 +1542,12 @@ static void nd_copy(CgTarget* t, Operand dst, Operand src) { return; } } - NativeLoc val = nd_materialize_operand(d, src); + /* Acquire the destination register first, then materialize the source + * straight into it: a memory/immediate/global source loads directly into dr + * (no scratch + mov), a cached source costs the one unavoidable reg-reg move. */ NativeLoc dr = nd_dst_reg(d, dst); - nd_copy_to_reg(d, dr, val); + nd_materialize_operand_into(d, dr, src); nd_dst_writeback(d, dst, dr); - nd_release_materialized(d, val); } /* Bit-fields ride the generic load/store (mem.bf_width != 0); this impl