commit e9ea681650df37ae1ab0dcd95c0d265466077836
parent d3e8b7f09626f91da77683a4c532256088448400
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Fri, 12 Jun 2026 14:02:29 -0700
perf(cg,arch): tcc -O0 prologue on riscv; shrink x64 single-pass reservation
Builds on the reworked NDT value-cache pools (caller-saved a-regs on riscv;
RSI/RDI + bounded R13-R15 on x64).
riscv: the NDT pool is entirely caller-saved (a0..a7 int, ft/fa fp), so set
ndt_caller_saved_only=1. The -O0 path now never uses a callee-saved register
(asserted in nd_func_end), enabling the tcc-style deferred-`sub` prologue that
is already in the tree: add3 drops to a 16-byte fixed entry + a 2-NOP region.
Validated: high-pressure fp.c compiles at -O0, asm-rv64 43/0, asm-rv32 16/0,
debug roundtrip + CFI unit OK, parse 3920/0, toy 1392/0.
x64: keeps a bounded callee-saved cache subset (R13-R15), so it is not
caller-saved-only; instead size the single-pass placeholder to that bounded
worst case. The optimizer path can use up to 7 int + 10 fp callee-saves
(X64_PROLOGUE_BYTES = 96/192), but the NDT only caches in its allocable pool's
callee-saved subset — 3 int (+ 6 fp on Win64) — so the single-pass reservation
needs only push+mov(4) + sub/chkstk(7/13) + sret(7) + 3*int-spill(21)
[+ 6*fp-spill(54)] = 39 (SysV) / 99 (Win64). Add X64_NDT_PROLOGUE_BYTES =
48 / 112 and use them for the -O0 placeholder. Per-function NOP pad falls from
~85 to 16-37 bytes (less when the function actually uses R13-R15). Validated:
x86_64-macos -O0 compile+run via Rosetta, rt assert.c/fp.c compile at -O0.
Note: x64 could be made caller-saved-only (4 working regs R8/R9/RSI/RDI handle
fp.c/opt.c) for an aa64/riscv-tiny prologue, at the cost of a 2-register -O0
value cache; left as a follow-up design call.
Diffstat:
3 files changed, 19 insertions(+), 3 deletions(-)
diff --git a/src/arch/riscv/native.c b/src/arch/riscv/native.c
@@ -606,6 +606,10 @@ static int rv_asm_constraint_reg(const NativeRegInfo* ri, const char* body,
static const NativeRegInfo rv_reg_info = {
.classes = rv_classes,
.nclasses = sizeof rv_classes / sizeof rv_classes[0],
+ /* The NDT value-cache / scratch pool is the caller-saved a-regs (int) and
+ * 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,
.resolve_name = rv_resolve_name,
.asm_operand_reg_ok = rv_asm_operand_reg_ok,
.asm_constraint_reg = rv_asm_constraint_reg,
diff --git a/src/arch/x64/emit.h b/src/arch/x64/emit.h
@@ -15,9 +15,17 @@
#include "core/slice.h"
#include "obj/obj.h"
-/* ---- prologue placeholder budgets / Win64 constants ---- */
+/* ---- prologue placeholder budgets / Win64 constants ----
+ * X64_PROLOGUE_BYTES / _WIN64 are the worst-case buffer sizes (optimizer path:
+ * up to 7 int + 10 fp callee-saves). X64_NDT_PROLOGUE_BYTES / _WIN64 are the
+ * smaller *single-pass (-O0)* reservation: the NDT only ever caches in its
+ * allocable pool's callee-saved subset (3 int r13-r15; on Win64 also 6 fp
+ * xmm6-11), so its prologue is bounded to push+mov(4) + sub/chkstk(7/13) +
+ * sret(7) + 3*int-spill(21) [+ 6*fp-spill(54)] = 39 (SysV) / 99 (Win64). */
#define X64_PROLOGUE_BYTES 96u
#define X64_PROLOGUE_BYTES_WIN64 192u
+#define X64_NDT_PROLOGUE_BYTES 48u
+#define X64_NDT_PROLOGUE_BYTES_WIN64 112u
#define X64_PROLOGUE_BASE_BYTES 11u
#define X64_WIN64_SHADOW_SPACE 32u
#define X64_MAX_CS_INT_REGS 7u
diff --git a/src/arch/x64/native.c b/src/arch/x64/native.c
@@ -1762,8 +1762,12 @@ static void x64_func_begin_common(NativeTarget* t, const CGFuncDesc* fd) {
a->nbind_moves = 0;
a->slim_frame = 0;
a->redzone_leaf = 0;
- a->prologue_nbytes =
- a->abi->shadow_space ? X64_PROLOGUE_BYTES_WIN64 : X64_PROLOGUE_BYTES;
+ /* Single-pass (-O0) reservation: the NDT caches only in its allocable pool's
+ * bounded callee-saved subset (3 int + 6 fp on Win64), so a far smaller
+ * placeholder than the optimizer-worst-case X64_PROLOGUE_BYTES suffices. The
+ * known-frame path overrides prologue_nbytes with its exact length. */
+ a->prologue_nbytes = a->abi->shadow_space ? X64_NDT_PROLOGUE_BYTES_WIN64
+ : X64_NDT_PROLOGUE_BYTES;
mc->set_section(mc, fd->text_section_id);
mc->emit_align(mc, 16, X64_NOP1);