[llvm] [RISCV] Implement Relaxation for Xqcilb Jumps (PR #142702)

Sam Elliott via llvm-commits llvm-commits at lists.llvm.org
Tue Jun 3 17:23:26 PDT 2025


https://github.com/lenary updated https://github.com/llvm/llvm-project/pull/142702

>From d22549665a13ce838b8703d0cd5c7b542bb96a7b Mon Sep 17 00:00:00 2001
From: Sam Elliott <quic_aelliott at quicinc.com>
Date: Tue, 3 Jun 2025 16:54:09 -0700
Subject: [PATCH 1/2] [RISCV] Implement Relaxation for Xqcilb Jumps

Until this patch was implemented, `qc.e.j undef` (where `undef` is not
resolvable) was emitted into binaries as `jal x0, undef`, which has a
shorter range.

This happens because the compress patterns use the same predicates as
the instructions, which allow instructions referencing symbols to be
compressed. Instructions are compressed as they are added to fragments,
and then (during layout) branch relaxation may turn the relaxable
fragments back into the longer version of these instructions if the
compressed variants are out of range.

This fixes the issue, for Xqcilb - by implementing the branch relaxation
functions to support turning JAL into QC.E.J or QC.E.JAL if Xqcilb is
present, and either rd=x0 or rd=x1. This is done manually, because the
compress patterns that turn QC.E.J and QC.E.JAL into smaller variants
are compression-only, because the disassembler uses the decompresser
when printing instructions, and we don't want JAL printed as QC.E.J or
QC.E.JAL.

QC.E.J and QC.E.JAL will be compressed into C.J and C.JAL respectively
if they contain a symbol, and then branch relaxation will now
incrementally turn these into JAL and then maybe QC.E.JAL or QC.E.J if
it deems this necessary for branch ranges. This should mean that the
smallest instruction that fits the required offset is always used, and
that unresolved symbols always use the largest jump valid in the
subtarget.

Branch relaxation is disabled for exact mode, but so is compression, so
this is not an issue in that configuration.
---
 .../RISCV/MCTargetDesc/RISCVAsmBackend.cpp    |  51 +++++-
 llvm/test/MC/RISCV/rv32-relaxation-xqci.s     | 170 ++++++++++++++++++
 2 files changed, 214 insertions(+), 7 deletions(-)
 create mode 100644 llvm/test/MC/RISCV/rv32-relaxation-xqci.s

diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVAsmBackend.cpp b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVAsmBackend.cpp
index 963501db118e7..42d45ab527476 100644
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVAsmBackend.cpp
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVAsmBackend.cpp
@@ -135,22 +135,42 @@ bool RISCVAsmBackend::fixupNeedsRelaxationAdvanced(const MCFixup &Fixup,
     // For conditional branch instructions the immediate must be
     // in the range [-4096, 4095].
     return !isInt<13>(Offset);
+  case RISCV::fixup_riscv_jal:
+    // For jump instructions the immediate must be in the range
+    // [-1048576, 1048574]
+    return Offset > 1048574 || Offset < -1048576;
   }
 }
 
 // Given a compressed control flow instruction this function returns
-// the expanded instruction.
-static unsigned getRelaxedOpcode(unsigned Op) {
-  switch (Op) {
-  default:
-    return Op;
+// the expanded instruction, or the original instruction code if no
+// expansion is available.
+static unsigned getRelaxedOpcode(const MCInst &Inst,
+                                 const MCSubtargetInfo &STI) {
+  switch (Inst.getOpcode()) {
   case RISCV::C_BEQZ:
     return RISCV::BEQ;
   case RISCV::C_BNEZ:
     return RISCV::BNE;
   case RISCV::C_J:
   case RISCV::C_JAL: // fall through.
+    // This only relaxes one "step" - i.e. from C.J to JAL, not from C.J to
+    // QC.E.J, because we can always relax again if needed.
     return RISCV::JAL;
+  case RISCV::JAL: {
+    // We can only relax JAL if we have Xqcilb
+    if (!STI.hasFeature(RISCV::FeatureVendorXqcilb))
+      break;
+
+    // And only if it is using X0 or X1 for rd.
+    const MCRegister &Reg = Inst.getOperand(0).getReg();
+    if (Reg == RISCV::X0)
+      return RISCV::QC_E_J;
+    if (Reg == RISCV::X1)
+      return RISCV::QC_E_JAL;
+
+    break;
+  }
   case RISCV::BEQ:
     return RISCV::PseudoLongBEQ;
   case RISCV::BNE:
@@ -188,6 +208,9 @@ static unsigned getRelaxedOpcode(unsigned Op) {
   case RISCV::QC_E_BGEUI:
     return RISCV::PseudoLongQC_E_BGEUI;
   }
+
+  // Returning the original opcode means we cannot relax the instruction.
+  return Inst.getOpcode();
 }
 
 void RISCVAsmBackend::relaxInstruction(MCInst &Inst,
@@ -205,6 +228,20 @@ void RISCVAsmBackend::relaxInstruction(MCInst &Inst,
   case RISCV::C_JAL: {
     [[maybe_unused]] bool Success = RISCVRVC::uncompress(Res, Inst, STI);
     assert(Success && "Can't uncompress instruction");
+    assert(Res.getOpcode() == getRelaxedOpcode(Inst, STI) &&
+           "Branch Relaxation Error");
+    break;
+  }
+  case RISCV::JAL: {
+    // This has to be written manually because the QC.E.J -> JAL is
+    // compression-only, so that it is not used when printing disassembly.
+    assert(STI.hasFeature(RISCV::FeatureVendorXqcilb) &&
+           "JAL is only relaxable with Xqcilb");
+    assert((Inst.getOperand(0).getReg() == RISCV::X0 ||
+            Inst.getOperand(0).getReg() == RISCV::X1) &&
+           "JAL only relaxable with rd=x0 or rd=x1");
+    Res.setOpcode(getRelaxedOpcode(Inst, STI));
+    Res.addOperand(Inst.getOperand(1));
     break;
   }
   case RISCV::BEQ:
@@ -225,7 +262,7 @@ void RISCVAsmBackend::relaxInstruction(MCInst &Inst,
   case RISCV::QC_E_BGEI:
   case RISCV::QC_E_BLTUI:
   case RISCV::QC_E_BGEUI:
-    Res.setOpcode(getRelaxedOpcode(Inst.getOpcode()));
+    Res.setOpcode(getRelaxedOpcode(Inst, STI));
     Res.addOperand(Inst.getOperand(0));
     Res.addOperand(Inst.getOperand(1));
     Res.addOperand(Inst.getOperand(2));
@@ -380,7 +417,7 @@ bool RISCVAsmBackend::mayNeedRelaxation(const MCInst &Inst,
   if (STI.hasFeature(RISCV::FeatureExactAssembly))
     return false;
 
-  return getRelaxedOpcode(Inst.getOpcode()) != Inst.getOpcode();
+  return getRelaxedOpcode(Inst, STI) != Inst.getOpcode();
 }
 
 bool RISCVAsmBackend::writeNopData(raw_ostream &OS, uint64_t Count,
diff --git a/llvm/test/MC/RISCV/rv32-relaxation-xqci.s b/llvm/test/MC/RISCV/rv32-relaxation-xqci.s
new file mode 100644
index 0000000000000..03c9914bb37d2
--- /dev/null
+++ b/llvm/test/MC/RISCV/rv32-relaxation-xqci.s
@@ -0,0 +1,170 @@
+# RUN: split-file %s %t
+# RUN: llvm-mc -filetype=obj -triple riscv32 -mattr=+experimental-xqcilb %t/pass.s -o - \
+# RUN:   | llvm-objdump -dr -M no-aliases - --mattr=+experimental-xqcilb | FileCheck %s
+# RUN: not llvm-mc -filetype=obj -triple riscv32 -mattr=+experimental-xqcilb %t/fail.s \
+# RUN:   2>&1 | FileCheck %t/fail.s --check-prefix=ERROR
+
+## This testcase shows how `c.j`, `c.jal` and `jal` can be relaxed to `qc.e.j` and `qc.e.jal`
+## with Xqcilb, when the branches are out of range, but also that these can be compressed
+## when referencing close labels.
+
+## The only problem we have here is when `jal` references an out-of-range label, and `rd` is
+## not `x0` or `x1` - for which we have no equivalent sequence, so we just fail to relax, and
+## emit a fixup-value-out-of-range error.
+
+#--- pass.s
+
+EXT_JUMP_NEGATIVE:
+  c.nop
+.space 0x100000
+
+FAR_JUMP_NEGATIVE:
+  c.nop
+.space 0x1000
+
+NEAR_NEGATIVE:
+  c.nop
+
+start:
+  c.j NEAR
+# CHECK: c.j {{0x[0-9a-f]+}} <NEAR>
+  c.j NEAR_NEGATIVE
+# CHECK: c.j {{0x[0-9a-f]+}} <NEAR_NEGATIVE>
+  c.j FAR_JUMP
+# CHECK: jal zero, {{0x[0-9a-f]+}} <FAR_JUMP>
+  c.j FAR_JUMP_NEGATIVE
+# CHECK: jal zero, {{0x[0-9a-f]+}} <FAR_JUMP_NEGATIVE>
+  c.j EXT_JUMP
+# CHECK: qc.e.j {{0x[0-9a-f]+}} <EXT_JUMP>
+  c.j EXT_JUMP_NEGATIVE
+# CHECK: qc.e.j {{0x[0-9a-f]+}} <EXT_JUMP_NEGATIVE>
+  c.j undef
+# CHECK: qc.e.j {{0x[0-9a-f]+}} <start+{{0x[0-9a-f]+}}>
+# CHECK: R_RISCV_CUSTOM195 undef
+
+  c.jal NEAR
+# CHECK: c.jal {{0x[0-9a-f]+}} <NEAR>
+  c.jal NEAR_NEGATIVE
+# CHECK: c.jal {{0x[0-9a-f]+}} <NEAR_NEGATIVE>
+  c.jal FAR_JUMP
+# CHECK: jal ra, {{0x[0-9a-f]+}} <FAR_JUMP>
+  c.jal FAR_JUMP_NEGATIVE
+# CHECK: jal ra, {{0x[0-9a-f]+}} <FAR_JUMP_NEGATIVE>
+  c.jal EXT_JUMP
+# CHECK: qc.e.jal {{0x[0-9a-f]+}} <EXT_JUMP>
+  c.jal EXT_JUMP_NEGATIVE
+# CHECK: qc.e.jal {{0x[0-9a-f]+}} <EXT_JUMP_NEGATIVE>
+  c.jal undef
+# CHECK: qc.e.jal {{0x[0-9a-f]+}} <start+{{0x[0-9a-f]+}}>
+# CHECK: R_RISCV_CUSTOM195 undef
+
+  jal zero, NEAR
+# CHECK: c.j {{0x[0-9a-f]+}} <NEAR>
+  jal zero, NEAR_NEGATIVE
+# CHECK: c.j {{0x[0-9a-f]+}} <NEAR_NEGATIVE>
+  jal zero, FAR_JUMP
+# CHECK: jal zero, {{0x[0-9a-f]+}} <FAR_JUMP>
+  jal zero, FAR_JUMP_NEGATIVE
+# CHECK: jal zero, {{0x[0-9a-f]+}} <FAR_JUMP_NEGATIVE>
+  jal zero, EXT_JUMP
+# CHECK: qc.e.j {{0x[0-9a-f]+}} <EXT_JUMP>
+  jal zero, EXT_JUMP_NEGATIVE
+# CHECK: qc.e.j {{0x[0-9a-f]+}} <EXT_JUMP_NEGATIVE>
+  jal zero, undef
+# CHECK: qc.e.j {{0x[0-9a-f]+}} <start+{{0x[0-9a-f]+}}>
+# CHECK: R_RISCV_CUSTOM195 undef
+
+  jal ra, NEAR
+# CHECK: c.jal {{0x[0-9a-f]+}} <NEAR>
+  jal ra, NEAR_NEGATIVE
+# CHECK: c.jal {{0x[0-9a-f]+}} <NEAR_NEGATIVE>
+  jal ra, FAR_JUMP
+# CHECK: jal ra, {{0x[0-9a-f]+}} <FAR_JUMP>
+  jal ra, FAR_JUMP_NEGATIVE
+# CHECK: jal ra, {{0x[0-9a-f]+}} <FAR_JUMP_NEGATIVE>
+  jal ra, EXT_JUMP
+# CHECK: qc.e.jal {{0x[0-9a-f]+}} <EXT_JUMP>
+  jal ra, EXT_JUMP_NEGATIVE
+# CHECK: qc.e.jal {{0x[0-9a-f]+}} <EXT_JUMP_NEGATIVE>
+  jal ra, undef
+# CHECK: qc.e.jal {{0x[0-9a-f]+}} <start+{{0x[0-9a-f]+}}>
+# CHECK: R_RISCV_CUSTOM195 undef
+
+  qc.e.j NEAR
+# CHECK: c.j {{0x[0-9a-f]+}} <NEAR>
+  qc.e.j NEAR_NEGATIVE
+# CHECK: c.j {{0x[0-9a-f]+}} <NEAR_NEGATIVE>
+  qc.e.j FAR_JUMP
+# CHECK: jal zero, {{0x[0-9a-f]+}} <FAR_JUMP>
+  qc.e.j FAR_JUMP_NEGATIVE
+# CHECK: jal zero, {{0x[0-9a-f]+}} <FAR_JUMP_NEGATIVE>
+  qc.e.j EXT_JUMP
+# CHECK: qc.e.j {{0x[0-9a-f]+}} <EXT_JUMP>
+  qc.e.j EXT_JUMP_NEGATIVE
+# CHECK: qc.e.j {{0x[0-9a-f]+}} <EXT_JUMP_NEGATIVE>
+  qc.e.j undef
+# CHECK: qc.e.j {{0x[0-9a-f]+}} <start+{{0x[0-9a-f]+}}>
+# CHECK: R_RISCV_CUSTOM195 undef
+
+  qc.e.jal NEAR
+# CHECK: c.jal {{0x[0-9a-f]+}} <NEAR>
+  qc.e.jal NEAR_NEGATIVE
+# CHECK: c.jal {{0x[0-9a-f]+}} <NEAR_NEGATIVE>
+  qc.e.jal FAR_JUMP
+# CHECK: jal ra, {{0x[0-9a-f]+}} <FAR_JUMP>
+  qc.e.jal FAR_JUMP_NEGATIVE
+# CHECK: jal ra, {{0x[0-9a-f]+}} <FAR_JUMP_NEGATIVE>
+  qc.e.jal EXT_JUMP
+# CHECK: qc.e.jal {{0x[0-9a-f]+}} <EXT_JUMP>
+  qc.e.jal EXT_JUMP_NEGATIVE
+# CHECK: qc.e.jal {{0x[0-9a-f]+}} <EXT_JUMP_NEGATIVE>
+  qc.e.jal undef
+# CHECK: qc.e.jal {{0x[0-9a-f]+}} <start+{{0x[0-9a-f]+}}>
+# CHECK: R_RISCV_CUSTOM195 undef
+
+
+
+  jal t1, NEAR
+# CHECK: jal t1, {{0x[0-9a-f]+}} <NEAR>
+  jal t1, NEAR_NEGATIVE
+# CHECK: jal t1, {{0x[0-9a-f]+}} <NEAR_NEGATIVE>
+  jal t1, FAR_JUMP
+# CHECK: jal t1, {{0x[0-9a-f]+}} <FAR_JUMP>
+  jal t1, FAR_JUMP_NEGATIVE
+# CHECK: jal t1, {{0x[0-9a-f]+}} <FAR_JUMP_NEGATIVE>
+
+## The two cases with EXT_JUMP and EXT_JUMP_NEGATIVE are
+## in fail.s, below.
+
+  jal t1, undef
+# CHECK: jal t1, {{0x[0-9a-f]+}} <start+{{0x[0-9a-f]+}}>
+# CHECK: R_RISCV_JAL undef
+
+
+NEAR:
+  c.nop
+.space 0x1000
+FAR_JUMP:
+  c.nop
+.space 0x100000
+EXT_JUMP:
+  c.nop
+
+
+#--- fail.s
+
+
+EXT_JUMP_NEGATIVE:
+  c.nop
+.space 0x100000
+.space 0x1000
+
+  jal t1, EXT_JUMP
+# ERROR: [[@LINE-1]]:3: error: fixup value out of range
+  jal t1, EXT_JUMP_NEGATIVE
+# ERROR: [[@LINE-1]]:3: error: fixup value out of range
+
+.space 0x1000
+.space 0x100000
+EXT_JUMP:
+  c.nop

>From 70f2f4ca2c115c7cac18b0a4fee54c5a3cd02258 Mon Sep 17 00:00:00 2001
From: Sam Elliott <quic_aelliott at quicinc.com>
Date: Tue, 3 Jun 2025 17:23:15 -0700
Subject: [PATCH 2/2] Copy MCRegister

---
 llvm/lib/Target/RISCV/MCTargetDesc/RISCVAsmBackend.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVAsmBackend.cpp b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVAsmBackend.cpp
index 42d45ab527476..6da0c3e25b877 100644
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVAsmBackend.cpp
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVAsmBackend.cpp
@@ -163,7 +163,7 @@ static unsigned getRelaxedOpcode(const MCInst &Inst,
       break;
 
     // And only if it is using X0 or X1 for rd.
-    const MCRegister &Reg = Inst.getOperand(0).getReg();
+    MCRegister Reg = Inst.getOperand(0).getReg();
     if (Reg == RISCV::X0)
       return RISCV::QC_E_J;
     if (Reg == RISCV::X1)



More information about the llvm-commits mailing list