Does brind need to preserve the return address register? RISCV seems to think so

The RISCV target defines a pseudo instruction for brind as shown below (

let isCall = 1, Defs=[X1] in
let isBarrier = 1, isBranch = 1, isIndirectBranch = 1, isTerminator = 1 in
def PseudoBRIND : Pseudo<(outs), (ins GPR:$rs1, simm12:$imm12), []>,
PseudoInstExpansion<(JALR X0, GPR:$rs1, simm12:$imm12)>;

def : Pat<(brind GPR:$rs1), (PseudoBRIND GPR:$rs1, 0)>;
def : Pat<(brind (add GPR:$rs1, simm12:$imm12)),
(PseudoBRIND GPR:$rs1, simm12:$imm12)>;

Note the Defs=[X1] (the return address register) in the definition despite it not actually being used in the expansion. This results in code being generated to save the link register before performing an indirect jump (llvm/test/CodeGen/RISCV/indirectbr.ll):

define i32 @indirectbr(i8* %target) nounwind {
; RV32I-LABEL: indirectbr:
; RV32I: # %bb.0:
; RV32I-NEXT: addi sp, sp, -16
; RV32I-NEXT: sw ra, 12(sp)
; RV32I-NEXT: jr a0
; RV32I-NEXT: .LBB0_1: # %test_label
; RV32I-NEXT: mv a0, zero
; RV32I-NEXT: lw ra, 12(sp)
; RV32I-NEXT: addi sp, sp, 16
; RV32I-NEXT: ret
indirectbr i8* %target, [label %test_label]
br label %ret
ret i32 0

This seems unnecessary to me, as brind is not a call, right? Or are the semantics of brind more complicated than I understand it to be? As far as I can tell ARM doesn’t do this, but I can’t follow as well, so I’m not sure. Should I replicate this in my target? Or should I send a patch to fix this in the RISCV target?

Would appreciate any help in the matter.


You are right, x1 is not redefined by PseudoBRIND. There was recently a fix for this in the RISC-V backend:


Ah, great! Apologies, I’m using the amazingly useful patchset your organisation provides to guide the implementation of my own target and stumbled upon this bit at and then only checked that it’s still the same on the 11 branch and missed this newest commit.

Thanks and sorry for the noise!