[BOLT][RISCV] Handle long tail calls (#67098)

Long tail calls use the following instruction sequence on RISC-V:

```
1: auipc xi, %pcrel_hi(sym)
jalr zero, %pcrel_lo(1b)(xi)
```

Since the second instruction in isolation looks like an indirect branch,
this confused BOLT and most functions containing a long tail call got
marked with "unknown control flow" and didn't get optimized as a
consequence.

This patch fixes this by detecting long tail call sequence in
`analyzeIndirectBranch`. `FixRISCVCallsPass` also had to be updated to
expand long tail calls to `PseudoTAIL` instead of `PseudoCALL`.

Besides this, this patch also fixes a minor issue with compressed tail
calls (`c.jr`) not being detected.

Note that I had to change `BinaryFunction::postProcessIndirectBranches`
slightly: the documentation of `MCPlusBuilder::analyzeIndirectBranch`
mentions that the [`Begin`, `End`) range contains the instructions
immediately preceding `Instruction`. However, in
`postProcessIndirectBranches`, *all* the instructions in the BB where
passed in the range. This made it difficult to find the preceding
instruction so I made sure *only* the preceding instructions are passed.
This commit is contained in:
Job Noorman 2023-10-05 08:55:30 +00:00 committed by GitHub
parent d20fbc9007
commit 7fa33773e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 53 additions and 4 deletions

View File

@ -1761,7 +1761,8 @@ bool BinaryFunction::postProcessIndirectBranches(
uint64_t LastJT = 0;
uint16_t LastJTIndexReg = BC.MIB->getNoRegister();
for (BinaryBasicBlock &BB : blocks()) {
for (MCInst &Instr : BB) {
for (BinaryBasicBlock::iterator II = BB.begin(); II != BB.end(); ++II) {
MCInst &Instr = *II;
if (!BC.MIB->isIndirectBranch(Instr))
continue;
@ -1789,7 +1790,7 @@ bool BinaryFunction::postProcessIndirectBranches(
const MCExpr *DispExpr;
MCInst *PCRelBaseInstr;
IndirectBranchType Type = BC.MIB->analyzeIndirectBranch(
Instr, BB.begin(), BB.end(), PtrSize, MemLocInstr, BaseRegNum,
Instr, BB.begin(), II, PtrSize, MemLocInstr, BaseRegNum,
IndexRegNum, DispValue, DispExpr, PCRelBaseInstr);
if (Type != IndirectBranchType::UNKNOWN || MemLocInstr != nullptr)
continue;

View File

@ -43,7 +43,12 @@ void FixRISCVCallsPass::runOnFunction(BinaryFunction &BF) {
MCInst OldCall = *NextII;
auto L = BC.scopeLock();
MIB->createCall(*II, Target, Ctx);
if (MIB->isTailCall(*NextII))
MIB->createTailCall(*II, Target, Ctx);
else
MIB->createCall(*II, Target, Ctx);
MIB->moveAnnotations(std::move(OldCall), *II);
// The original offset was set on the jalr of the auipc+jalr pair. Since

View File

@ -87,6 +87,7 @@ public:
return false;
case RISCV::JALR:
case RISCV::C_JALR:
case RISCV::C_JR:
return true;
}
}
@ -158,6 +159,17 @@ public:
DispValue = 0;
DispExpr = nullptr;
PCRelBaseOut = nullptr;
// Check for the following long tail call sequence:
// 1: auipc xi, %pcrel_hi(sym)
// jalr zero, %pcrel_lo(1b)(xi)
if (Instruction.getOpcode() == RISCV::JALR && Begin != End) {
MCInst &PrevInst = *std::prev(End);
if (isRISCVCall(PrevInst, Instruction) &&
Instruction.getOperand(0).getReg() == RISCV::X0)
return IndirectBranchType::POSSIBLE_TAIL_CALL;
}
return IndirectBranchType::UNKNOWN;
}

View File

@ -4,7 +4,7 @@
// RUN: llvm-mc -triple riscv64 -filetype obj -o %t.o %s
// RUN: ld.lld --emit-relocs -o %t %t.o
// RUN: llvm-bolt --enable-bat --print-cfg --print-fix-riscv-calls \
// RUN: --print-only=_start -o /dev/null %t | FileCheck %s
// RUN: -o /dev/null %t | FileCheck %s
.text
.global f
@ -19,11 +19,24 @@ f:
// CHECK-NEXT: jal ra, f # Offset: 8
// CHECK-NEXT: jal zero, f # TAILCALL # Offset: 12
// CHECK-LABEL: Binary Function "long_tail" after building cfg {
// CHECK: auipc t1, f
// CHECK-NEXT: jalr zero, -24(t1) # TAILCALL # Offset: 8
// CHECK-LABEL: Binary Function "compressed_tail" after building cfg {
// CHECK: jr a0 # TAILCALL # Offset: 0
// CHECK-LABEL: Binary Function "_start" after fix-riscv-calls {
// CHECK: call f # Offset: 0
// CHECK-NEXT: call f # Offset: 8
// CHECK-NEXT: tail f # TAILCALL # Offset: 12
// CHECK-LABEL: Binary Function "long_tail" after fix-riscv-calls {
// CHECK: tail f # TAILCALL # Offset: 4
// CHECK-LABEL: Binary Function "compressed_tail" after fix-riscv-calls {
// CHECK: jr a0 # TAILCALL # Offset: 0
.globl _start
.p2align 1
_start:
@ -31,3 +44,21 @@ _start:
jal f
jal zero, f
.size _start, .-_start
.globl long_tail
.p2align 1
long_tail:
// NOTE: BOLT assumes indirect calls in single-BB functions are tail calls
// so artificially introduce a second BB to force RISC-V-specific analysis
// to get triggered.
beq a0, a1, 1f
1:
tail f
.size long_tail, .-long_tail
.globl compressed_tail
.p2align 1
.option rvc
compressed_tail:
c.jr a0
.size compressed_tail, .-compressed_tail