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:
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);
}
}