commit d3493a9dc57891958726cc00c1bb57d6a76f0c58
parent ba542bfde60e045ffc887bfdcb11f8006a406eef
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Sat, 13 Jun 2026 17:54:03 -0700
perf(cg): -O0 fp args into fa-regs + result-in-a0 (Lever 1/5, riscv)
Front fa0..fa7 in the rv NDT fp value-cache pool (rv_fp_allocable) so the
-O0 forward-materialization (api_pack_call_args_in_order, A1) lands fp call
args directly in their ABI registers, mirroring the int pool a0..a7. Flip
ndt_result_reg_stable for riscv: a0/fa0 are clobbered only at call
boundaries (rv div/rem/mul are R-type with explicit operands, unlike
x86-64 implicit-RAX), so a scalar call result can stay cached in a0/fa0
across straight-line ops and feed the next consumer with no mov, as on
aa64. ft0/ft1 (emit) and ft2/ft3 (driver) stay out of the pool.
Gate (run-correctness + determinism, this is a codesize-track change):
- make bin RELEASE=1: clean (-Werror, no warnings)
- test-smoke-rv64: 3 pass / 0 fail; test-toy: 1392 pass / 0 fail / 35 skip
(skips pre-existing C-target/wasm/inline-asm, unrelated to rv64)
- functional rv64 run (computed args, shared temp, result-stable-then-div,
nested-call args, + fp variants) compiled by kit, run on real riscv64
(qemu-user in podman riscv64/debian): exit 0 = all 7 checks pass; clang
build of same test also exits 0 (expected values correct); negative
control returns 1 (harness detects an injected failure)
- determinism: object byte-identical across two compiles (two sources)
Measured (call-heavy corpus, kit -c -target riscv64-linux -O0):
.text 1644->1564 B (-4.9%), object 3512->3432 B, total insns 411->391
(-4.9%); fsgnj.d (fp reg-reg mov / fmv.d pseudo) 37->21 (-43%, fp-front),
mv (int reg-reg) 34->30 (-12%, result-in-a0).
Diffstat:
1 file changed, 17 insertions(+), 7 deletions(-)
diff --git a/src/arch/riscv/native.c b/src/arch/riscv/native.c
@@ -495,15 +495,18 @@ static const NativePhysRegInfo rv_int_phys[] = {
/* NDT (-O0) fp value-cache / scratch pool (read only by NativeDirectTarget; the
* optimizer uses the phys[] ALLOCABLE flags, which also cover the callee-saved
- * fs0..fs11). Caller-saved only — ft4..ft7, ft8..ft11, and the fa0..fa7 arg
- * registers — for the same reasons as the int pool: the -O0 cache flushes at
+ * fs0..fs11). Caller-saved only — the fa0..fa7 arg registers, then ft4..ft7 and
+ * ft8..ft11 — for the same reasons as the int pool: the -O0 cache flushes at
* every call/branch/return, so caller-saved suffices, and keeping callee-saves
* out of the pool keeps the fixed single-pass prologue placeholder from
- * overflowing. ft0/ft1 reserved as emit-internal scratch; ft2/ft3 driver
- * scratch. */
-static const Reg rv_fp_allocable[] = {4u, 5u, 6u, 7u, 28u, 29u,
- 30u, 31u, 10u, 11u, 12u, 13u,
- 14u, 15u, 16u, 17u}; /* ft4-7,ft8-11,fa0-7 */
+ * overflowing. fa0..fa7 come FIRST (mirroring the int pool's a0..a7):
+ * nd_cache_alloc scans the pool in order, so fronting the arg registers lets a
+ * producer materialize a call's fp args directly into their ABI registers
+ * (Lever 1, via api_pack_call_args_in_order). ft0/ft1 reserved as emit-internal
+ * scratch; ft2/ft3 driver scratch. */
+static const Reg rv_fp_allocable[] = {10u, 11u, 12u, 13u, 14u, 15u,
+ 16u, 17u, 4u, 5u, 6u, 7u,
+ 28u, 29u, 30u, 31u}; /* fa0-7,ft4-7,ft8-11 */
static const Reg rv_fp_scratch[] = {2u, 3u}; /* ft2, ft3 */
static const NativePhysRegInfo rv_fp_phys[] = {
@@ -632,6 +635,13 @@ static const NativeRegInfo rv_reg_info = {
* ft/fa regs (fp), so the -O0 path never needs a callee-saved register —
* enabling the tcc-style deferred-`sub` prologue (see rv_func_begin). */
.ndt_caller_saved_only = 1u,
+ /* a0/fa0 are clobbered only by calls: rv div/rem/mul are R-type with
+ * explicit operands (unlike x86-64's implicit-RAX div/mul), so a scalar
+ * call result can stay cached in a0/fa0 across the following straight-line
+ * ops and feed the next consumer with no mov — exactly as on aa64 (cf. the
+ * aa64 ndt_result_reg_stable comment; doc/plan/PERF-O0-CODESIZE.md Lever 5).
+ * The NDT places the result directly in the ABI result reg post-call. */
+ .ndt_result_reg_stable = 1u,
.resolve_name = rv_resolve_name,
.asm_operand_reg_ok = rv_asm_operand_reg_ok,
.asm_constraint_reg = rv_asm_constraint_reg,