kit

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

commit dfc5f2358d70e838ab8574941d0d61c4d6bbbb6f
parent 7f49849e76a96d275a93c7bf26b9700cbb8bb7bf
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Fri, 29 May 2026 12:32:38 -0700

rv64: route conditional branches through a jal for ±1MiB reach

RISC-V B-type branches reach only ±4 KiB, which a single function (especially at
-O0) can exceed between a branch and its target. Emit the *inverse* condition as
a short branch that skips a following unconditional jal (±1 MiB) to the real
target, so the conditional leg always carries a tiny in-range displacement and
the jal carries the long reach. Apply the same to the FP-compare path.

Add loud range-check panics in the R_RV_BRANCH / R_RV_JAL fixups: a violation is
now a backend bug to surface, not silently-truncated code.

Diffstat:
Msrc/arch/rv64/arch.c | 14++++++++++++++
Msrc/arch/rv64/native.c | 35++++++++++++++++++++++-------------
2 files changed, 36 insertions(+), 13 deletions(-)

diff --git a/src/arch/rv64/arch.c b/src/arch/rv64/arch.c @@ -23,6 +23,11 @@ static int rv64_register_at_public(uint32_t idx, CfreeArchReg* out) { return rc; } +static SrcLoc rv64_no_loc(void) { + SrcLoc l = {0, 0, 0}; + return l; +} + static int rv64_apply_label_fixup(Compiler* c, const ArchLabelFixup* fx) { const Section* s; u8 cur[4]; @@ -48,6 +53,11 @@ static int rv64_apply_label_fixup(Compiler* c, const ArchLabelFixup* fx) { switch (fx->kind) { case R_RV_BRANCH: + /* B-type reaches ±4 KiB. Conditional branches are emitted over a jal + * (see rv_cmp_branch) so this only carries small fixed displacements; + * a violation is a backend bug, not silently-truncated code. */ + if ((i64)fx->disp < -(i64)(1 << 12) || (i64)fx->disp >= (i64)(1 << 12)) + compiler_panic(c, rv64_no_loc(), "rv64: BRANCH out of range (±4KiB)"); word &= 0x01fff07fu; word |= ((b >> 12) & 1u) << 31; word |= ((b >> 5) & 0x3fu) << 25; @@ -55,6 +65,10 @@ static int rv64_apply_label_fixup(Compiler* c, const ArchLabelFixup* fx) { word |= ((b >> 11) & 1u) << 7; break; case R_RV_JAL: + /* J-type reaches ±1 MiB — ample for intra-function jumps (including the + * long leg of a conditional branch). Fail loudly rather than wrap. */ + if ((i64)fx->disp < -(i64)(1 << 20) || (i64)fx->disp >= (i64)(1 << 20)) + compiler_panic(c, rv64_no_loc(), "rv64: JAL out of range (±1MiB)"); word &= 0x00000fffu; word |= ((b >> 20) & 1u) << 31; word |= ((b >> 1) & 0x3ffu) << 21; diff --git a/src/arch/rv64/native.c b/src/arch/rv64/native.c @@ -1126,6 +1126,13 @@ static void rv_jump(NativeTarget* t, MCLabel l) { static void rv_cmp_branch(NativeTarget* t, CmpOp op, NativeLoc aop, NativeLoc bop, MCLabel l) { MCEmitter* mc = t->mc; + /* RISC-V B-type branches reach only ±4 KiB, which a single (especially + * -O0) function can exceed between a branch and its target. Rather than a + * lone conditional branch to the label, emit a short *inverted* branch + * that skips an unconditional `jal` (±1 MiB) to the target. The inverted + * branch's displacement is the constant SKIP_JAL (skip just the jal) and + * so is always in range; the jal carries the long reach. See rv_jump. */ + enum { SKIP_JAL = 8 }; /* branch over the 4-byte jal that follows it */ /* FP compares (incl. EQ/NE on FP operands) have no register-register branch * form: materialize the 0/1 into TMP0 via rv_cmp, then branch on nonzero. */ if (op >= CMP_LT_F || @@ -1133,8 +1140,9 @@ static void rv_cmp_branch(NativeTarget* t, CmpOp op, NativeLoc aop, NativeLoc tmp = rv_reg_loc(builtin_id(CFREE_CG_BUILTIN_I64), NATIVE_REG_INT, RV_TMP0); rv_cmp(t, op, tmp, aop, bop); - rv64_emit32(mc, rv_bne(RV_TMP0, RV_ZERO, 0)); - mc->emit_label_ref(mc, l, R_RV_BRANCH, 4, 0); + /* Skip the jal when the result is 0 (condition false). */ + rv64_emit32(mc, rv_beq(RV_TMP0, RV_ZERO, SKIP_JAL)); + rv_jump(t, l); return; } { @@ -1142,21 +1150,22 @@ static void rv_cmp_branch(NativeTarget* t, CmpOp op, NativeLoc aop, u32 ra = rv_cmp_ext(t, sg, aop, RV_TMP0); u32 rb = rv_cmp_ext(t, sg, bop, RV_TMP1); u32 word; + /* Encode the *inverse* of `op`, skipping the jal when NOT taken. */ switch (op) { - case CMP_EQ: word = rv_beq(ra, rb, 0); break; - case CMP_NE: word = rv_bne(ra, rb, 0); break; - case CMP_LT_S: word = rv_blt(ra, rb, 0); break; - case CMP_GE_S: word = rv_bge(ra, rb, 0); break; - case CMP_LT_U: word = rv_bltu(ra, rb, 0); break; - case CMP_GE_U: word = rv_bgeu(ra, rb, 0); break; - case CMP_GT_S: word = rv_blt(rb, ra, 0); break; - case CMP_LE_S: word = rv_bge(rb, ra, 0); break; - case CMP_GT_U: word = rv_bltu(rb, ra, 0); break; - case CMP_LE_U: word = rv_bgeu(rb, ra, 0); break; + case CMP_EQ: word = rv_bne(ra, rb, SKIP_JAL); break; + case CMP_NE: word = rv_beq(ra, rb, SKIP_JAL); break; + case CMP_LT_S: word = rv_bge(ra, rb, SKIP_JAL); break; + case CMP_GE_S: word = rv_blt(ra, rb, SKIP_JAL); break; + case CMP_LT_U: word = rv_bgeu(ra, rb, SKIP_JAL); break; + case CMP_GE_U: word = rv_bltu(ra, rb, SKIP_JAL); break; + case CMP_GT_S: word = rv_bge(rb, ra, SKIP_JAL); break; + case CMP_LE_S: word = rv_blt(rb, ra, SKIP_JAL); break; + case CMP_GT_U: word = rv_bgeu(rb, ra, SKIP_JAL); break; + case CMP_LE_U: word = rv_bltu(rb, ra, SKIP_JAL); break; default: rv_panic(rv_of(t), "unsupported cmp_branch"); } rv64_emit32(mc, word); - mc->emit_label_ref(mc, l, R_RV_BRANCH, 4, 0); + rv_jump(t, l); } }