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

Merged
merged 3 commits into from
Jun 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 44 additions & 7 deletions llvm/lib/Target/RISCV/MCTargetDesc/RISCVAsmBackend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,22 +136,42 @@ bool RISCVAsmBackend::fixupNeedsRelaxationAdvanced(const MCFixup &Fixup,
// For conditional branch instructions the immediate must be
// in the range [-4096, 4094].
return Offset > 4094 || Offset < -4096;
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.
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:
Expand Down Expand Up @@ -189,6 +209,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,
Expand All @@ -206,6 +229,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:
Expand All @@ -226,7 +263,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));
Expand Down Expand Up @@ -381,7 +418,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,
Expand Down
170 changes: 170 additions & 0 deletions llvm/test/MC/RISCV/rv32-relaxation-xqci.s
Original file line number Diff line number Diff line change
@@ -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
Loading