kit

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

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:
Msrc/arch/riscv/native.c | 4++++
Msrc/arch/x64/emit.h | 10+++++++++-
Msrc/arch/x64/native.c | 8++++++--
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);