Skip to content

[RISCV] Implement Relaxation for Xqcilb Jumps #142702

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

lenary
Copy link
Member

@lenary lenary commented Jun 4, 2025

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 implemented manually as 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.

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.
@llvmbot llvmbot added backend:RISC-V mc Machine (object) code labels Jun 4, 2025
@llvmbot
Copy link
Member

llvmbot commented Jun 4, 2025

@llvm/pr-subscribers-backend-risc-v

Author: Sam Elliott (lenary)

Changes

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 implemented manually as 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.


Full diff: https://github.com/llvm/llvm-project/pull/142702.diff

2 Files Affected:

  • (modified) llvm/lib/Target/RISCV/MCTargetDesc/RISCVAsmBackend.cpp (+44-7)
  • (added) llvm/test/MC/RISCV/rv32-relaxation-xqci.s (+170)
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

@llvmbot
Copy link
Member

llvmbot commented Jun 4, 2025

@llvm/pr-subscribers-mc

Author: Sam Elliott (lenary)

Changes

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 implemented manually as 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.


Full diff: https://github.com/llvm/llvm-project/pull/142702.diff

2 Files Affected:

  • (modified) llvm/lib/Target/RISCV/MCTargetDesc/RISCVAsmBackend.cpp (+44-7)
  • (added) llvm/test/MC/RISCV/rv32-relaxation-xqci.s (+170)
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

@topperc
Copy link
Collaborator

topperc commented Jun 4, 2025

Do you plan to make the BranchRelaxation pass aware of these jumps?

@lenary
Copy link
Member Author

lenary commented Jun 4, 2025

Do you plan to make the BranchRelaxation pass aware of these jumps?

I don't think QC.E.J and QC.E.JAL can be turned into other branches/sequences with a longer range, so I don't think there's anything to do? Note that the pass was not aware it could relax JAL until now because there was no larger instruction to relax JAL into, by the same logic.

I am thinking of refactoring how the branch pseudos work, so that they can use JAL or QC.E.J (right now they only ever use JAL), but I'm still working through how we can implement that nicely, and I have other short-term priorities.

@topperc
Copy link
Collaborator

topperc commented Jun 4, 2025

Do you plan to make the BranchRelaxation pass aware of these jumps?

I don't think QC.E.J and QC.E.JAL can be turned into other branches/sequences with a longer range, so I don't think there's anything to do? Note that the pass was not aware it could relax JAL until now because there was no larger instruction to relax JAL into, by the same logic.

The BranchRelaxcation pass doesn't use any of the MC layer logic so I'm not sure I understand.

@lenary
Copy link
Member Author

lenary commented Jun 4, 2025

Yeah, sorry, I forgot there are two sorts of "branch relaxations", one at the MC layer and one earlier.

Working on the branch relaxation pass is only necessary when we start generating Xacilb branches in the middle end or when we change how PseudoBR is expanded (to use QC.E.J). I'll make a note but it's not on our critical path right now.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backend:RISC-V mc Machine (object) code
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants