Skip to content

Conversation

@svs-quic
Copy link
Contributor

@svs-quic svs-quic commented Nov 3, 2025

Add support for outlining CFI instructions if

a) the outlined function is being tail called
b) all of the CFI instructions in the function are being outlined

This is similar to what is being done on AArch64 and X86.

Add support for outlinig CFI instructions if

  a) the outlined function is being tail called
  b) all of the CFI instructions in the funtion are being outlined

This is similar to what is being done on AArch64.
@llvmbot
Copy link
Member

llvmbot commented Nov 3, 2025

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

Author: Sudharsan Veeravalli (svs-quic)

Changes

Add support for outlinig CFI instructions if

a) the outlined function is being tail called
b) all of the CFI instructions in the funtion are being outlined

This is similar to what is being done on AArch64 and X86.


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

2 Files Affected:

  • (modified) llvm/lib/Target/RISCV/RISCVInstrInfo.cpp (+31-21)
  • (modified) llvm/test/CodeGen/RISCV/machine-outliner-cfi.mir (+177-20)
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp b/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
index c9df787e0012d..195ed0200a890 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfo.cpp
@@ -31,6 +31,7 @@
 #include "llvm/CodeGen/StackMaps.h"
 #include "llvm/IR/DebugInfoMetadata.h"
 #include "llvm/IR/Module.h"
+#include "llvm/MC/MCDwarf.h"
 #include "llvm/MC/MCInstBuilder.h"
 #include "llvm/MC/TargetRegistry.h"
 #include "llvm/Support/ErrorHandling.h"
@@ -3522,6 +3523,27 @@ RISCVInstrInfo::getOutliningCandidateInfo(
       Candidate.getMF()->getSubtarget<RISCVSubtarget>().hasStdExtZca() ? 2 : 4;
   unsigned CallOverhead = 0, FrameOverhead = 0;
 
+  // Count the number of CFI instructions in the candidate, if present.
+  unsigned CFICount = 0;
+  for (auto &I : Candidate) {
+    if (I.isCFIInstruction())
+      CFICount++;
+  }
+
+  // Ensure CFI coverage matches: comparing the number of CFIs in the candidate
+  // with the total number of CFIs in the parent function for each candidate.
+  // Outlining only a subset of a function’s CFIs would split the unwind state
+  // across two code regions and lead to incorrect address offsets between the
+  // outlined body and the remaining code. To preserve correct unwind info, we
+  // only outline when all CFIs in the function can be outlined together.
+  for (outliner::Candidate &C : RepeatedSequenceLocs) {
+    std::vector<MCCFIInstruction> CFIInstructions =
+        C.getMF()->getFrameInstructions();
+
+    if (CFICount > 0 && CFICount != CFIInstructions.size())
+      return std::nullopt;
+  }
+
   MachineOutlinerConstructionID MOCI = MachineOutlinerDefault;
   if (Candidate.back().isReturn()) {
     MOCI = MachineOutlinerTailCall;
@@ -3537,6 +3559,11 @@ RISCVInstrInfo::getOutliningCandidateInfo(
     FrameOverhead = InstrSizeCExt;
   }
 
+  // If we have CFI instructions, we can only outline if the outlined section
+  // can be a tail call.
+  if (MOCI != MachineOutlinerTailCall && CFICount > 0)
+    return std::nullopt;
+
   for (auto &C : RepeatedSequenceLocs)
     C.setCallInfo(MOCI, CallOverhead);
 
@@ -3558,13 +3585,11 @@ RISCVInstrInfo::getOutliningTypeImpl(const MachineModuleInfo &MMI,
       MBB->getParent()->getSubtarget().getRegisterInfo();
   const auto &F = MI.getMF()->getFunction();
 
-  // We can manually strip out CFI instructions later.
+  // We can only outline CFI instructions if we will tail call the outlined
+  // function, or fix up the CFI offsets. Currently, CFI instructions are
+  // outlined only if in a tail call.
   if (MI.isCFIInstruction())
-    // If current function has exception handling code, we can't outline &
-    // strip these CFI instructions since it may break .eh_frame section
-    // needed in unwinding.
-    return F.needsUnwindTableEntry() ? outliner::InstrType::Illegal
-                                     : outliner::InstrType::Invisible;
+    return outliner::InstrType::Legal;
 
   if (cannotInsertTailCall(*MBB) &&
       (MI.isReturn() || isMIModifiesReg(MI, TRI, RISCV::X5)))
@@ -3591,21 +3616,6 @@ void RISCVInstrInfo::buildOutlinedFrame(
     MachineBasicBlock &MBB, MachineFunction &MF,
     const outliner::OutlinedFunction &OF) const {
 
-  // Strip out any CFI instructions
-  bool Changed = true;
-  while (Changed) {
-    Changed = false;
-    auto I = MBB.begin();
-    auto E = MBB.end();
-    for (; I != E; ++I) {
-      if (I->isCFIInstruction()) {
-        I->removeFromParent();
-        Changed = true;
-        break;
-      }
-    }
-  }
-
   if (OF.FrameConstructionID == MachineOutlinerTailCall)
     return;
 
diff --git a/llvm/test/CodeGen/RISCV/machine-outliner-cfi.mir b/llvm/test/CodeGen/RISCV/machine-outliner-cfi.mir
index 2acb1d43e01ea..e33f4a80113c1 100644
--- a/llvm/test/CodeGen/RISCV/machine-outliner-cfi.mir
+++ b/llvm/test/CodeGen/RISCV/machine-outliner-cfi.mir
@@ -3,27 +3,33 @@
 # RUN: llc -mtriple=riscv64 -x mir -run-pass=machine-outliner -simplify-mir -verify-machineinstrs < %s \
 # RUN: | FileCheck -check-prefixes=OUTLINED,RV64I-MO %s
 
-# CFIs are invisible (they can be outlined, but won't actually impact the outlining result) if there
-# is no need to unwind. CFIs will be stripped when we build outlined functions.
+# Combined tests for outlining with CFI instructions on RISCV:
+# 1) All CFIs present in candidate: outline as tail-call and keep CFIs.
+# 2) Partial CFIs in function (extra outside candidate): do not outline.
+# 3) CFIs present but candidate is not a tail-call: do not outline.
 
 --- |
-  define void @func1(i32 %a, i32 %b) nounwind { ret void }
-
-  define void @func2(i32 %a, i32 %b) nounwind { ret void }
-
-  define void @func3(i32 %a, i32 %b) nounwind { ret void }
+  define void @funcA(i32 %a, i32 %b) nounwind { ret void }
+  define void @funcB(i32 %a, i32 %b) nounwind { ret void }
+  define void @funcC(i32 %a, i32 %b) nounwind { ret void }
+  define void @funcD(i32 %a, i32 %b) nounwind { ret void }
+  define void @funcE(i32 %a, i32 %b) nounwind { ret void }
+  define void @funcF(i32 %a, i32 %b) nounwind { ret void }
 ...
+
+# Case 1: All CFIs present; expect outlining and CFIs retained in outlined body.
 ---
-name:            func1
+name:            funcA
 tracksRegLiveness: true
 body:             |
   bb.0:
     liveins: $x10, $x11
-    ; RV32I-MO-LABEL: name: func1
+    ; RV32I-MO-LABEL: name: funcA
     ; RV32I-MO: liveins: $x10, $x11
     ; RV32I-MO-NEXT: {{  $}}
     ; RV32I-MO-NEXT: PseudoTAIL target-flags(riscv-call) @OUTLINED_FUNCTION_0, implicit $x2, implicit-def $x10, implicit-def $x11, implicit-def $x12, implicit $x2, implicit $x10, implicit $x11
-    ; RV64I-MO-LABEL: name: func1
+    ;
+    ; RV64I-MO-LABEL: name: funcA
     ; RV64I-MO: liveins: $x10, $x11
     ; RV64I-MO-NEXT: {{  $}}
     ; RV64I-MO-NEXT: PseudoTAIL target-flags(riscv-call) @OUTLINED_FUNCTION_0, implicit $x2, implicit-def $x10, implicit-def $x11, implicit-def $x12, implicit $x2, implicit $x10, implicit $x11
@@ -39,62 +45,213 @@ body:             |
     PseudoRET
 ...
 ---
-name:            func2
+name:            funcB
 tracksRegLiveness: true
 body:             |
   bb.0:
     liveins: $x10, $x11
-    ; RV32I-MO-LABEL: name: func2
+    ; RV32I-MO-LABEL: name: funcB
     ; RV32I-MO: liveins: $x10, $x11
     ; RV32I-MO-NEXT: {{  $}}
     ; RV32I-MO-NEXT: PseudoTAIL target-flags(riscv-call) @OUTLINED_FUNCTION_0, implicit $x2, implicit-def $x10, implicit-def $x11, implicit-def $x12, implicit $x2, implicit $x10, implicit $x11
-    ; RV64I-MO-LABEL: name: func2
+    ;
+    ; RV64I-MO-LABEL: name: funcB
     ; RV64I-MO: liveins: $x10, $x11
     ; RV64I-MO-NEXT: {{  $}}
     ; RV64I-MO-NEXT: PseudoTAIL target-flags(riscv-call) @OUTLINED_FUNCTION_0, implicit $x2, implicit-def $x10, implicit-def $x11, implicit-def $x12, implicit $x2, implicit $x10, implicit $x11
     $x10 = ORI $x10, 1023
     CFI_INSTRUCTION offset $x1, 0
     $x11 = ORI $x11, 1023
-    CFI_INSTRUCTION offset $x1, -8
-    $x12 = ADDI $x10, 17
     CFI_INSTRUCTION offset $x1, -4
+    $x12 = ADDI $x10, 17
+    CFI_INSTRUCTION offset $x1, -8
     $x11 = AND $x12, $x11
     CFI_INSTRUCTION offset $x1, -12
     $x10 = SUB $x10, $x11
     PseudoRET
 ...
+
+# Case 2: Partial CFIs (extra CFI outside candidate in funcD); expect no outlining.
 ---
-name:            func3
+name:            funcC
 tracksRegLiveness: true
 body:             |
   bb.0:
     liveins: $x10, $x11
-    ; RV32I-MO-LABEL: name: func3
+    ; RV32I-MO-LABEL: name: funcC
     ; RV32I-MO: liveins: $x10, $x11
     ; RV32I-MO-NEXT: {{  $}}
     ; RV32I-MO-NEXT: PseudoTAIL target-flags(riscv-call) @OUTLINED_FUNCTION_0, implicit $x2, implicit-def $x10, implicit-def $x11, implicit-def $x12, implicit $x2, implicit $x10, implicit $x11
-    ; RV64I-MO-LABEL: name: func3
+    ;
+    ; RV64I-MO-LABEL: name: funcC
     ; RV64I-MO: liveins: $x10, $x11
     ; RV64I-MO-NEXT: {{  $}}
     ; RV64I-MO-NEXT: PseudoTAIL target-flags(riscv-call) @OUTLINED_FUNCTION_0, implicit $x2, implicit-def $x10, implicit-def $x11, implicit-def $x12, implicit $x2, implicit $x10, implicit $x11
     $x10 = ORI $x10, 1023
-    CFI_INSTRUCTION offset $x1, -12
+    CFI_INSTRUCTION offset $x1, 0
     $x11 = ORI $x11, 1023
+    CFI_INSTRUCTION offset $x1, -4
+    $x12 = ADDI $x10, 17
     CFI_INSTRUCTION offset $x1, -8
+    $x11 = AND $x12, $x11
+    CFI_INSTRUCTION offset $x1, -12
+    $x10 = SUB $x10, $x11
+    PseudoRET
+...
+---
+name:            funcD
+tracksRegLiveness: true
+body:             |
+  bb.0:
+    liveins: $x10, $x11
+    ; RV32I-MO-LABEL: name: funcD
+    ; RV32I-MO: liveins: $x10, $x11
+    ; RV32I-MO-NEXT: {{  $}}
+    ; RV32I-MO-NEXT: CFI_INSTRUCTION offset $x1, -16
+    ; RV32I-MO-NEXT: $x10 = ORI $x10, 1023
+    ; RV32I-MO-NEXT: CFI_INSTRUCTION offset $x1, 0
+    ; RV32I-MO-NEXT: $x11 = ORI $x11, 1023
+    ; RV32I-MO-NEXT: CFI_INSTRUCTION offset $x1, -4
+    ; RV32I-MO-NEXT: $x12 = ADDI $x10, 17
+    ; RV32I-MO-NEXT: CFI_INSTRUCTION offset $x1, -8
+    ; RV32I-MO-NEXT: $x11 = AND $x12, $x11
+    ; RV32I-MO-NEXT: CFI_INSTRUCTION offset $x1, -12
+    ; RV32I-MO-NEXT: $x10 = SUB $x10, $x11
+    ; RV32I-MO-NEXT: PseudoRET
+    ;
+    ; RV64I-MO-LABEL: name: funcD
+    ; RV64I-MO: liveins: $x10, $x11
+    ; RV64I-MO-NEXT: {{  $}}
+    ; RV64I-MO-NEXT: CFI_INSTRUCTION offset $x1, -16
+    ; RV64I-MO-NEXT: $x10 = ORI $x10, 1023
+    ; RV64I-MO-NEXT: CFI_INSTRUCTION offset $x1, 0
+    ; RV64I-MO-NEXT: $x11 = ORI $x11, 1023
+    ; RV64I-MO-NEXT: CFI_INSTRUCTION offset $x1, -4
+    ; RV64I-MO-NEXT: $x12 = ADDI $x10, 17
+    ; RV64I-MO-NEXT: CFI_INSTRUCTION offset $x1, -8
+    ; RV64I-MO-NEXT: $x11 = AND $x12, $x11
+    ; RV64I-MO-NEXT: CFI_INSTRUCTION offset $x1, -12
+    ; RV64I-MO-NEXT: $x10 = SUB $x10, $x11
+    ; RV64I-MO-NEXT: PseudoRET
+    CFI_INSTRUCTION offset $x1, -16
+    $x10 = ORI $x10, 1023
+    CFI_INSTRUCTION offset $x1, 0
+    $x11 = ORI $x11, 1023
+    CFI_INSTRUCTION offset $x1, -4
     $x12 = ADDI $x10, 17
+    CFI_INSTRUCTION offset $x1, -8
+    $x11 = AND $x12, $x11
+    CFI_INSTRUCTION offset $x1, -12
+    $x10 = SUB $x10, $x11
+    PseudoRET
+...
+
+# Case 3: CFIs present but candidate is not a tail-call; expect no outlining.
+---
+name:            funcE
+tracksRegLiveness: true
+body:             |
+  bb.0:
+    liveins: $x10, $x11
+    ; RV32I-MO-LABEL: name: funcE
+    ; RV32I-MO: liveins: $x10, $x11
+    ; RV32I-MO-NEXT: {{  $}}
+    ; RV32I-MO-NEXT: $x10 = ORI $x10, 1023
+    ; RV32I-MO-NEXT: CFI_INSTRUCTION offset $x1, 0
+    ; RV32I-MO-NEXT: $x11 = ORI $x11, 1023
+    ; RV32I-MO-NEXT: CFI_INSTRUCTION offset $x1, -4
+    ; RV32I-MO-NEXT: $x12 = ADDI $x10, 17
+    ; RV32I-MO-NEXT: CFI_INSTRUCTION offset $x1, -8
+    ; RV32I-MO-NEXT: $x11 = AND $x12, $x11
+    ; RV32I-MO-NEXT: CFI_INSTRUCTION offset $x1, -12
+    ; RV32I-MO-NEXT: $x10 = SUB $x10, $x11
+    ; RV32I-MO-NEXT: $x10 = ADDI $x10, 1
+    ; RV32I-MO-NEXT: PseudoRET
+    ;
+    ; RV64I-MO-LABEL: name: funcE
+    ; RV64I-MO: liveins: $x10, $x11
+    ; RV64I-MO-NEXT: {{  $}}
+    ; RV64I-MO-NEXT: $x10 = ORI $x10, 1023
+    ; RV64I-MO-NEXT: CFI_INSTRUCTION offset $x1, 0
+    ; RV64I-MO-NEXT: $x11 = ORI $x11, 1023
+    ; RV64I-MO-NEXT: CFI_INSTRUCTION offset $x1, -4
+    ; RV64I-MO-NEXT: $x12 = ADDI $x10, 17
+    ; RV64I-MO-NEXT: CFI_INSTRUCTION offset $x1, -8
+    ; RV64I-MO-NEXT: $x11 = AND $x12, $x11
+    ; RV64I-MO-NEXT: CFI_INSTRUCTION offset $x1, -12
+    ; RV64I-MO-NEXT: $x10 = SUB $x10, $x11
+    ; RV64I-MO-NEXT: $x10 = ADDI $x10, 1
+    ; RV64I-MO-NEXT: PseudoRET
+    $x10 = ORI $x10, 1023
+    CFI_INSTRUCTION offset $x1, 0
+    $x11 = ORI $x11, 1023
     CFI_INSTRUCTION offset $x1, -4
+    $x12 = ADDI $x10, 17
+    CFI_INSTRUCTION offset $x1, -8
     $x11 = AND $x12, $x11
+    CFI_INSTRUCTION offset $x1, -12
+    $x10 = SUB $x10, $x11
+    $x10 = ADDI $x10, 1
+    PseudoRET
+...
+---
+name:            funcF
+tracksRegLiveness: true
+body:             |
+  bb.0:
+    liveins: $x10, $x11
+    ; RV32I-MO-LABEL: name: funcF
+    ; RV32I-MO: liveins: $x10, $x11
+    ; RV32I-MO-NEXT: {{  $}}
+    ; RV32I-MO-NEXT: $x10 = ORI $x10, 1023
+    ; RV32I-MO-NEXT: CFI_INSTRUCTION offset $x1, 0
+    ; RV32I-MO-NEXT: $x11 = ORI $x11, 1023
+    ; RV32I-MO-NEXT: CFI_INSTRUCTION offset $x1, -4
+    ; RV32I-MO-NEXT: $x12 = ADDI $x10, 17
+    ; RV32I-MO-NEXT: CFI_INSTRUCTION offset $x1, -8
+    ; RV32I-MO-NEXT: $x11 = AND $x12, $x11
+    ; RV32I-MO-NEXT: CFI_INSTRUCTION offset $x1, -12
+    ; RV32I-MO-NEXT: $x10 = SUB $x10, $x11
+    ; RV32I-MO-NEXT: $x10 = ADDI $x10, 2
+    ; RV32I-MO-NEXT: PseudoRET
+    ;
+    ; RV64I-MO-LABEL: name: funcF
+    ; RV64I-MO: liveins: $x10, $x11
+    ; RV64I-MO-NEXT: {{  $}}
+    ; RV64I-MO-NEXT: $x10 = ORI $x10, 1023
+    ; RV64I-MO-NEXT: CFI_INSTRUCTION offset $x1, 0
+    ; RV64I-MO-NEXT: $x11 = ORI $x11, 1023
+    ; RV64I-MO-NEXT: CFI_INSTRUCTION offset $x1, -4
+    ; RV64I-MO-NEXT: $x12 = ADDI $x10, 17
+    ; RV64I-MO-NEXT: CFI_INSTRUCTION offset $x1, -8
+    ; RV64I-MO-NEXT: $x11 = AND $x12, $x11
+    ; RV64I-MO-NEXT: CFI_INSTRUCTION offset $x1, -12
+    ; RV64I-MO-NEXT: $x10 = SUB $x10, $x11
+    ; RV64I-MO-NEXT: $x10 = ADDI $x10, 2
+    ; RV64I-MO-NEXT: PseudoRET
+    $x10 = ORI $x10, 1023
     CFI_INSTRUCTION offset $x1, 0
+    $x11 = ORI $x11, 1023
+    CFI_INSTRUCTION offset $x1, -4
+    $x12 = ADDI $x10, 17
+    CFI_INSTRUCTION offset $x1, -8
+    $x11 = AND $x12, $x11
+    CFI_INSTRUCTION offset $x1, -12
     $x10 = SUB $x10, $x11
+    $x10 = ADDI $x10, 2
     PseudoRET
-
+...
 
 # OUTLINED-LABEL: name: OUTLINED_FUNCTION_0
 # OUTLINED: liveins: $x11, $x10
 # OUTLINED-NEXT: {{  $}}
 # OUTLINED-NEXT: $x10 = ORI $x10, 1023
+# OUTLINED-NEXT: CFI_INSTRUCTION offset $x1, 0
 # OUTLINED-NEXT: $x11 = ORI $x11, 1023
+# OUTLINED-NEXT: CFI_INSTRUCTION offset $x1, -4
 # OUTLINED-NEXT: $x12 = ADDI $x10, 17
+# OUTLINED-NEXT: CFI_INSTRUCTION offset $x1, -8
 # OUTLINED-NEXT: $x11 = AND $x12, $x11
+# OUTLINED-NEXT: CFI_INSTRUCTION offset $x1, -12
 # OUTLINED-NEXT: $x10 = SUB $x10, $x11
 # OUTLINED-NEXT: PseudoRET

Copy link
Member

@lenary lenary left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

What this means in principle is that either you get all instructions from your prolog (which might not be at the start of the function due to shrink-wrapping) to your function return outlined, or outlining won't touch the function prolog. These seem reasonable to me, and certainly easier than trying to patch up CFI directives on outlined sequences.

@svs-quic svs-quic requested a review from topperc November 5, 2025 04:02
@svs-quic
Copy link
Contributor Author

svs-quic commented Nov 7, 2025

@topperc do you have any other comments?

Copy link
Collaborator

@topperc topperc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@svs-quic svs-quic merged commit 6145b9d into llvm:main Nov 7, 2025
10 checks passed
@svs-quic svs-quic deleted the outliner-cfi branch November 7, 2025 07:20
vinay-deshmukh pushed a commit to vinay-deshmukh/llvm-project that referenced this pull request Nov 8, 2025
…llvm#166149)

Add support for outlining CFI instructions if

  a) the outlined function is being tail called
  b) all of the CFI instructions in the function are being outlined

This is similar to what is being done on AArch64 and X86.

---------

Co-authored-by: Craig Topper <craig.topper@sifive.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants