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:
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