[llvm] [MC][debug_frame] Fix a bug in MCDwarfFrameEmitter::emit() so that per-function CIE can be generated when CIEs are different (PR #192727)

via llvm-commits llvm-commits at lists.llvm.org
Fri Apr 17 20:16:08 PDT 2026


https://github.com/jaewookshin701 updated https://github.com/llvm/llvm-project/pull/192727

>From 3a0fe5513f19bc6bc5824255e2a2f4823d29ca57 Mon Sep 17 00:00:00 2001
From: Jaewook Shin <jaewooks at 2u1g-b650-1731.ipp3a2.colossus.nvidia.com>
Date: Fri, 17 Apr 2026 19:36:38 +0000
Subject: [PATCH 1/4] Add a support for per function CIE

---
 llvm/include/llvm/MC/MCAsmInfo.h | 9 +++++++++
 llvm/lib/MC/MCDwarf.cpp          | 3 ++-
 2 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/llvm/include/llvm/MC/MCAsmInfo.h b/llvm/include/llvm/MC/MCAsmInfo.h
index 1e55bef20f2e8..8fc99072bde61 100644
--- a/llvm/include/llvm/MC/MCAsmInfo.h
+++ b/llvm/include/llvm/MC/MCAsmInfo.h
@@ -383,6 +383,12 @@ class LLVM_ABI MCAsmInfo {
   /// names in .cfi_* directives.  Defaults to false.
   bool DwarfRegNumForCFI = false;
 
+  /// When true, emit a new CIE in .debug_frame whenever the CIE key changes
+  /// (e.g. different return-address register per function).  The default LLVM
+  /// behaviour reuses a single CIE for all .debug_frame FDEs; targets that
+  /// need per-function CIE variation must opt in.
+  bool UsePerFunctionDebugFrameCIE = false;
+
   /// True if target uses @ (expr at specifier) for relocation specifiers.
   bool UseAtForSpecifier = false;
 
@@ -674,6 +680,9 @@ class LLVM_ABI MCAsmInfo {
 
   bool doDwarfFDESymbolsUseAbsDiff() const { return DwarfFDESymbolsUseAbsDiff; }
   bool useDwarfRegNumForCFI() const { return DwarfRegNumForCFI; }
+  bool usePerFunctionDebugFrameCIE() const {
+    return UsePerFunctionDebugFrameCIE;
+  }
   bool useAtForSpecifier() const { return UseAtForSpecifier; }
   bool useParensForSpecifier() const { return UseParensForSpecifier; }
   bool supportsExtendedDwarfLocDirective() const {
diff --git a/llvm/lib/MC/MCDwarf.cpp b/llvm/lib/MC/MCDwarf.cpp
index f24e3fb03433f..9cc90f6c5dbf3 100644
--- a/llvm/lib/MC/MCDwarf.cpp
+++ b/llvm/lib/MC/MCDwarf.cpp
@@ -1979,7 +1979,8 @@ void MCDwarfFrameEmitter::emit(MCObjectStreamer &Streamer, bool IsEH) {
       continue;
 
     CIEKey Key(Frame);
-    if (!LastCIEStart || (IsEH && Key != LastKey)) {
+    if (!LastCIEStart ||
+        (Key != LastKey && (IsEH || AsmInfo->usePerFunctionDebugFrameCIE()))) {
       LastKey = Key;
       LastCIEStart = &Emitter.EmitCIE(Frame);
     }

>From 3577405722fd6f3e236d0a8fd3a405c593bf46f2 Mon Sep 17 00:00:00 2001
From: Jaewook Shin <jaewooks at 2u1g-b650-1731.ipp3a2.colossus.nvidia.com>
Date: Sat, 18 Apr 2026 00:50:17 +0000
Subject: [PATCH 2/4] Change to a bug fix in MCDwarfFrameEmitter::emit

---
 llvm/include/llvm/MC/MCAsmInfo.h | 9 ---------
 llvm/lib/MC/MCDwarf.cpp          | 3 +--
 2 files changed, 1 insertion(+), 11 deletions(-)

diff --git a/llvm/include/llvm/MC/MCAsmInfo.h b/llvm/include/llvm/MC/MCAsmInfo.h
index 8fc99072bde61..1e55bef20f2e8 100644
--- a/llvm/include/llvm/MC/MCAsmInfo.h
+++ b/llvm/include/llvm/MC/MCAsmInfo.h
@@ -383,12 +383,6 @@ class LLVM_ABI MCAsmInfo {
   /// names in .cfi_* directives.  Defaults to false.
   bool DwarfRegNumForCFI = false;
 
-  /// When true, emit a new CIE in .debug_frame whenever the CIE key changes
-  /// (e.g. different return-address register per function).  The default LLVM
-  /// behaviour reuses a single CIE for all .debug_frame FDEs; targets that
-  /// need per-function CIE variation must opt in.
-  bool UsePerFunctionDebugFrameCIE = false;
-
   /// True if target uses @ (expr at specifier) for relocation specifiers.
   bool UseAtForSpecifier = false;
 
@@ -680,9 +674,6 @@ class LLVM_ABI MCAsmInfo {
 
   bool doDwarfFDESymbolsUseAbsDiff() const { return DwarfFDESymbolsUseAbsDiff; }
   bool useDwarfRegNumForCFI() const { return DwarfRegNumForCFI; }
-  bool usePerFunctionDebugFrameCIE() const {
-    return UsePerFunctionDebugFrameCIE;
-  }
   bool useAtForSpecifier() const { return UseAtForSpecifier; }
   bool useParensForSpecifier() const { return UseParensForSpecifier; }
   bool supportsExtendedDwarfLocDirective() const {
diff --git a/llvm/lib/MC/MCDwarf.cpp b/llvm/lib/MC/MCDwarf.cpp
index 9cc90f6c5dbf3..9c313fb2930d7 100644
--- a/llvm/lib/MC/MCDwarf.cpp
+++ b/llvm/lib/MC/MCDwarf.cpp
@@ -1979,8 +1979,7 @@ void MCDwarfFrameEmitter::emit(MCObjectStreamer &Streamer, bool IsEH) {
       continue;
 
     CIEKey Key(Frame);
-    if (!LastCIEStart ||
-        (Key != LastKey && (IsEH || AsmInfo->usePerFunctionDebugFrameCIE()))) {
+    if (!LastCIEStart || Key != LastKey) {
       LastKey = Key;
       LastCIEStart = &Emitter.EmitCIE(Frame);
     }

>From 57ef40d4861727d3ec7ea683c06a2373d777ec01 Mon Sep 17 00:00:00 2001
From: Jaewook Shin <jaewooks at 2u1g-b650-1731.ipp3a2.colossus.nvidia.com>
Date: Sat, 18 Apr 2026 01:25:28 +0000
Subject: [PATCH 3/4] Add a test case for the bug fix in MCDwarf.cpp

---
 llvm/unittests/MC/CMakeLists.txt         |   2 +
 llvm/unittests/MC/DwarfDebugFrameCIE.cpp | 253 +++++++++++++++++++++++
 2 files changed, 255 insertions(+)
 create mode 100644 llvm/unittests/MC/DwarfDebugFrameCIE.cpp

diff --git a/llvm/unittests/MC/CMakeLists.txt b/llvm/unittests/MC/CMakeLists.txt
index da8e219113f46..4881888f03742 100644
--- a/llvm/unittests/MC/CMakeLists.txt
+++ b/llvm/unittests/MC/CMakeLists.txt
@@ -6,6 +6,7 @@ endforeach()
 
 set(LLVM_LINK_COMPONENTS
   ${LLVM_TARGETS_TO_BUILD}
+  DebugInfoDWARF
   MC
   MCDisassembler
   Object
@@ -15,6 +16,7 @@ set(LLVM_LINK_COMPONENTS
 
 add_llvm_unittest(MCTests
   Disassembler.cpp
+  DwarfDebugFrameCIE.cpp
   DwarfLineTables.cpp
   DwarfLineTableHeaders.cpp
   MCInstPrinter.cpp
diff --git a/llvm/unittests/MC/DwarfDebugFrameCIE.cpp b/llvm/unittests/MC/DwarfDebugFrameCIE.cpp
new file mode 100644
index 0000000000000..08446c0fc2c7d
--- /dev/null
+++ b/llvm/unittests/MC/DwarfDebugFrameCIE.cpp
@@ -0,0 +1,253 @@
+//===- llvm/unittest/MC/DwarfDebugFrameCIE.cpp ----------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// Verify that .debug_frame emits distinct CIEs when frame parameters (e.g.
+/// the return-address register) differ between functions.  This is a
+/// regression test for a bug where only the first CIE was emitted for
+/// .debug_frame, causing all FDEs to silently reuse that CIE's
+/// return-address register regardless of per-function overrides via
+/// emitCFIReturnColumn.
+///
+//===----------------------------------------------------------------------===//
+
+#include "llvm/BinaryFormat/Dwarf.h"
+#include "llvm/DebugInfo/DWARF/DWARFDataExtractor.h"
+#include "llvm/DebugInfo/DWARF/DWARFDebugFrame.h"
+#include "llvm/MC/MCAsmBackend.h"
+#include "llvm/MC/MCAsmInfo.h"
+#include "llvm/MC/MCCodeEmitter.h"
+#include "llvm/MC/MCContext.h"
+#include "llvm/MC/MCDwarf.h"
+#include "llvm/MC/MCInstrInfo.h"
+#include "llvm/MC/MCObjectFileInfo.h"
+#include "llvm/MC/MCObjectStreamer.h"
+#include "llvm/MC/MCObjectWriter.h"
+#include "llvm/MC/MCRegisterInfo.h"
+#include "llvm/MC/MCStreamer.h"
+#include "llvm/MC/MCSubtargetInfo.h"
+#include "llvm/MC/MCTargetOptions.h"
+#include "llvm/MC/TargetRegistry.h"
+#include "llvm/Object/ELFObjectFile.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/TargetSelect.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+
+namespace {
+
+class DwarfDebugFrameCIE : public ::testing::Test {
+public:
+  static constexpr const char *TripleName = "x86_64-pc-linux";
+  Triple TT;
+  std::unique_ptr<MCRegisterInfo> MRI;
+  std::unique_ptr<MCAsmInfo> MAI;
+  std::unique_ptr<const MCSubtargetInfo> STI;
+  MCTargetOptions MCOptions;
+  const Target *TheTarget = nullptr;
+
+  struct StreamerContext {
+    std::unique_ptr<MCObjectFileInfo> MOFI;
+    std::unique_ptr<MCContext> Ctx;
+    std::unique_ptr<const MCInstrInfo> MII;
+    std::unique_ptr<MCStreamer> Streamer;
+  };
+
+  DwarfDebugFrameCIE() : TT(TripleName) {
+    llvm::InitializeAllTargetInfos();
+    llvm::InitializeAllTargetMCs();
+    llvm::InitializeAllDisassemblers();
+
+    std::string Error;
+    TheTarget = TargetRegistry::lookupTarget(TT, Error);
+    if (!TheTarget)
+      return;
+
+    MRI.reset(TheTarget->createMCRegInfo(TT));
+    MAI.reset(TheTarget->createMCAsmInfo(*MRI, TT, MCOptions));
+    STI.reset(TheTarget->createMCSubtargetInfo(TT, "", ""));
+  }
+
+  StreamerContext createStreamer(raw_pwrite_stream &OS) {
+    StreamerContext Res;
+    Res.Ctx = std::make_unique<MCContext>(TT, MAI.get(), MRI.get(),
+                                          /*MSTI=*/nullptr);
+    Res.MOFI.reset(
+        TheTarget->createMCObjectFileInfo(*Res.Ctx, /*PIC=*/false));
+    Res.Ctx->setObjectFileInfo(Res.MOFI.get());
+
+    Res.MII.reset(TheTarget->createMCInstrInfo());
+    MCCodeEmitter *MCE =
+        TheTarget->createMCCodeEmitter(*Res.MII, *Res.Ctx);
+    MCAsmBackend *MAB =
+        TheTarget->createMCAsmBackend(*STI, *MRI, MCTargetOptions());
+    std::unique_ptr<MCObjectWriter> OW = MAB->createObjectWriter(OS);
+    Res.Streamer.reset(TheTarget->createMCObjectStreamer(
+        TT, *Res.Ctx, std::unique_ptr<MCAsmBackend>(MAB), std::move(OW),
+        std::unique_ptr<MCCodeEmitter>(MCE), *STI));
+    return Res;
+  }
+
+  /// Enable .debug_frame emission (instead of the default .eh_frame).
+  void enableDebugFrame(StreamerContext &C) {
+    C.Streamer->emitCFISections(/*EH=*/false, /*Debug=*/true, /*SFrame=*/false);
+  }
+
+  /// Emit a mock function with the given return-address register in its CIE.
+  void emitFunction(StreamerContext &C, StringRef Name, unsigned RAReg) {
+    MCStreamer *S = C.Streamer.get();
+    MCContext &Ctx = *C.Ctx;
+
+    MCSection *TextSec = C.MOFI->getTextSection();
+    TextSec->setHasInstructions(true);
+    S->switchSection(TextSec);
+
+    MCSymbol *FuncSym = Ctx.getOrCreateSymbol(Name);
+    S->emitLabel(FuncSym);
+
+    S->emitCFIStartProc(/*IsSimple=*/true);
+    S->emitCFIReturnColumn(RAReg);
+    S->emitNops(4, 1, SMLoc(), *STI);
+    S->emitCFIEndProc();
+  }
+};
+
+/// Test that two functions with different return-address registers produce
+/// two distinct CIEs in .debug_frame.
+TEST_F(DwarfDebugFrameCIE, DistinctReturnColumnsGetDistinctCIEs) {
+  if (!TheTarget)
+    GTEST_SKIP();
+
+  SmallString<0> ObjContents;
+  raw_svector_ostream VecOS(ObjContents);
+  StreamerContext C = createStreamer(VecOS);
+
+  C.Streamer->initSections(*STI);
+  enableDebugFrame(C);
+
+  // Function A: return-address register = 16 (x86_64 RA)
+  emitFunction(C, "funcA", /*RAReg=*/16);
+  // Function B: return-address register = 0 (different)
+  emitFunction(C, "funcB", /*RAReg=*/0);
+
+  C.Streamer->finish();
+
+  // Parse the emitted ELF and find .debug_frame.
+  std::unique_ptr<MemoryBuffer> MB =
+      MemoryBuffer::getMemBuffer(ObjContents.str(), "", /*RequiresNullTerminator=*/false);
+  auto BinOrErr = llvm::object::createBinary(MB->getMemBufferRef());
+  ASSERT_TRUE(static_cast<bool>(BinOrErr));
+  auto *ELF = dyn_cast<llvm::object::ELFObjectFileBase>(&**BinOrErr);
+  ASSERT_NE(ELF, nullptr);
+
+  // Extract .debug_frame section contents.
+  StringRef FrameContents;
+  bool Found = false;
+  for (const auto &Section : ELF->sections()) {
+    Expected<StringRef> NameOrErr = Section.getName();
+    ASSERT_TRUE(static_cast<bool>(NameOrErr));
+    if (*NameOrErr == ".debug_frame") {
+      Expected<StringRef> ContentsOrErr = Section.getContents();
+      ASSERT_TRUE(static_cast<bool>(ContentsOrErr));
+      FrameContents = *ContentsOrErr;
+      Found = true;
+      break;
+    }
+  }
+  ASSERT_TRUE(Found) << ".debug_frame section not found";
+  ASSERT_FALSE(FrameContents.empty());
+
+  // Parse the .debug_frame section using DWARFDebugFrame.
+  DWARFDataExtractor Data(FrameContents, /*isLittleEndian=*/true,
+                          /*AddressSize=*/8);
+  DWARFDebugFrame DebugFrame(Triple::x86_64, /*IsEH=*/false);
+  Error Err = DebugFrame.parse(Data);
+  ASSERT_FALSE(static_cast<bool>(Err)) << toString(std::move(Err));
+
+  // Collect CIEs and their return-address registers.
+  SmallVector<uint64_t, 4> CIEReturnRegs;
+  unsigned FDECount = 0;
+  for (const auto &Entry : DebugFrame) {
+    if (const auto *CIEp = dyn_cast<dwarf::CIE>(&Entry))
+      CIEReturnRegs.push_back(CIEp->getReturnAddressRegister());
+    else if (isa<dwarf::FDE>(Entry))
+      ++FDECount;
+  }
+
+  // We emitted two functions, so expect two FDEs.
+  EXPECT_EQ(FDECount, 2u);
+  // The two functions use different return-address registers, so there must
+  // be two distinct CIEs.
+  ASSERT_EQ(CIEReturnRegs.size(), 2u);
+  EXPECT_NE(CIEReturnRegs[0], CIEReturnRegs[1]);
+  // Verify both registers are present (order depends on CIEKey sort).
+  llvm::sort(CIEReturnRegs);
+  EXPECT_EQ(CIEReturnRegs[0], 0u);
+  EXPECT_EQ(CIEReturnRegs[1], 16u);
+}
+
+/// Test that two functions with the same return-address register share a
+/// single CIE (deduplication still works).
+TEST_F(DwarfDebugFrameCIE, SameReturnColumnsShareCIE) {
+  if (!TheTarget)
+    GTEST_SKIP();
+
+  SmallString<0> ObjContents;
+  raw_svector_ostream VecOS(ObjContents);
+  StreamerContext C = createStreamer(VecOS);
+
+  C.Streamer->initSections(*STI);
+  enableDebugFrame(C);
+
+  emitFunction(C, "funcC", /*RAReg=*/16);
+  emitFunction(C, "funcD", /*RAReg=*/16);
+
+  C.Streamer->finish();
+
+  std::unique_ptr<MemoryBuffer> MB =
+      MemoryBuffer::getMemBuffer(ObjContents.str(), "", false);
+  auto BinOrErr = llvm::object::createBinary(MB->getMemBufferRef());
+  ASSERT_TRUE(static_cast<bool>(BinOrErr));
+  auto *ELF = dyn_cast<llvm::object::ELFObjectFileBase>(&**BinOrErr);
+  ASSERT_NE(ELF, nullptr);
+
+  StringRef FrameContents;
+  bool Found = false;
+  for (const auto &Section : ELF->sections()) {
+    Expected<StringRef> NameOrErr = Section.getName();
+    ASSERT_TRUE(static_cast<bool>(NameOrErr));
+    if (*NameOrErr == ".debug_frame") {
+      Expected<StringRef> ContentsOrErr = Section.getContents();
+      ASSERT_TRUE(static_cast<bool>(ContentsOrErr));
+      FrameContents = *ContentsOrErr;
+      Found = true;
+      break;
+    }
+  }
+  ASSERT_TRUE(Found) << ".debug_frame section not found";
+
+  DWARFDataExtractor Data(FrameContents, true, 8);
+  DWARFDebugFrame DebugFrame(Triple::x86_64, /*IsEH=*/false);
+  Error Err = DebugFrame.parse(Data);
+  ASSERT_FALSE(static_cast<bool>(Err)) << toString(std::move(Err));
+
+  unsigned CIECount = 0;
+  unsigned FDECount = 0;
+  for (const auto &Entry : DebugFrame) {
+    if (isa<dwarf::CIE>(Entry))
+      ++CIECount;
+    else if (isa<dwarf::FDE>(Entry))
+      ++FDECount;
+  }
+
+  EXPECT_EQ(FDECount, 2u);
+  // Same return-address register → single shared CIE.
+  EXPECT_EQ(CIECount, 1u);
+}
+
+} // namespace

>From 10703f2cad19c7126dc172038bd14f689c5d5f35 Mon Sep 17 00:00:00 2001
From: Jaewook Shin <jaewooks at 2u1g-b650-1731.ipp3a2.colossus.nvidia.com>
Date: Sat, 18 Apr 2026 03:13:45 +0000
Subject: [PATCH 4/4] changing type from auto to dwarf::FrameEntry

---
 llvm/unittests/MC/DwarfDebugFrameCIE.cpp | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/llvm/unittests/MC/DwarfDebugFrameCIE.cpp b/llvm/unittests/MC/DwarfDebugFrameCIE.cpp
index 08446c0fc2c7d..d57e336ec77b5 100644
--- a/llvm/unittests/MC/DwarfDebugFrameCIE.cpp
+++ b/llvm/unittests/MC/DwarfDebugFrameCIE.cpp
@@ -1,4 +1,4 @@
-//===- llvm/unittest/MC/DwarfDebugFrameCIE.cpp ----------------------------===//
+//===----------------------------------------------------------------------===//
 //
 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 // See https://llvm.org/LICENSE.txt for license information.
@@ -172,7 +172,7 @@ TEST_F(DwarfDebugFrameCIE, DistinctReturnColumnsGetDistinctCIEs) {
   // Collect CIEs and their return-address registers.
   SmallVector<uint64_t, 4> CIEReturnRegs;
   unsigned FDECount = 0;
-  for (const auto &Entry : DebugFrame) {
+  for (const dwarf::FrameEntry &Entry : DebugFrame) {
     if (const auto *CIEp = dyn_cast<dwarf::CIE>(&Entry))
       CIEReturnRegs.push_back(CIEp->getReturnAddressRegister());
     else if (isa<dwarf::FDE>(Entry))
@@ -238,7 +238,7 @@ TEST_F(DwarfDebugFrameCIE, SameReturnColumnsShareCIE) {
 
   unsigned CIECount = 0;
   unsigned FDECount = 0;
-  for (const auto &Entry : DebugFrame) {
+  for (const dwarf::FrameEntry &Entry : DebugFrame) {
     if (isa<dwarf::CIE>(Entry))
       ++CIECount;
     else if (isa<dwarf::FDE>(Entry))



More information about the llvm-commits mailing list