[llvm] [LTO] Refactor LTO link optimization remarks handling using RAII (NFC) (PR #181269)

Teresa Johnson via llvm-commits llvm-commits at lists.llvm.org
Tue Feb 17 09:59:39 PST 2026


https://github.com/teresajohnson updated https://github.com/llvm/llvm-project/pull/181269

>From 77059de1c183348ddb528e75c0b352daf3bc564c Mon Sep 17 00:00:00 2001
From: Teresa Johnson <tejohnson at google.com>
Date: Thu, 12 Feb 2026 13:21:30 -0800
Subject: [PATCH 1/2] [LTO] Refactor LTO link optimization remarks handling
 using RAII (NFC)

Introduce LTO::DiagnosticSession as an RAII guard in LTO::run to centralize the
setup and finalization of optimization remarks for LTO link actions outside
of the pass pipeline. This ensures that remarks are correctly captured across
all exit paths from the LTO pipeline.

The motivation for this refactoring is to provide a cleaner and more robust
interface for managing diagnostics, and to enable easier remark emission during
the thin link phase in a follow-on PR.

Key changes:
- Added a unified diagnostic API to the LTO class (emitRemark).
- The RAII guard manages the setup and finalization of the remark streamer,
  simplifying the core LTO logic.
- Updated runRegularLTO and linkRegularLTO to leverage the unified session
  management, removing redundant setup/cleanup logic.
---
 llvm/include/llvm/LTO/LTO.h | 19 +++++++++++++
 llvm/lib/LTO/LTO.cpp        | 56 ++++++++++++++++++++++++++-----------
 2 files changed, 59 insertions(+), 16 deletions(-)

diff --git a/llvm/include/llvm/LTO/LTO.h b/llvm/include/llvm/LTO/LTO.h
index fe8ef19759404..dda7cb63dbc9a 100644
--- a/llvm/include/llvm/LTO/LTO.h
+++ b/llvm/include/llvm/LTO/LTO.h
@@ -427,6 +427,21 @@ class LTO {
     LTOK_UnifiedThin,
   };
 
+  /// RAII guard for optimization remarks and LTO-link diagnostics.
+  struct DiagnosticSession {
+    /// The LTO object that owns the diagnostic resources.
+    LTO &Parent;
+
+    DiagnosticSession(LTO &L) : Parent(L) {}
+
+    /// Factory method to initialize a diagnostic session. This sets up the
+    /// remark streamer.
+    static Expected<std::unique_ptr<DiagnosticSession>> create(LTO &L);
+
+    /// Finalizes optimization remarks.
+    ~DiagnosticSession();
+  };
+
   /// Create an LTO object. A default constructed LTO object has a reasonable
   /// production configuration, but you can customize it by passing arguments to
   /// this constructor.
@@ -643,6 +658,10 @@ class LTO {
   LLVMRemarkFileHandle DiagnosticOutputFile;
 
 public:
+  /// Helper to emit an optimization remark during the LTO link when outside of
+  /// the standard optimization pass pipeline.
+  void emitRemark(OptimizationRemark &Remark);
+
   virtual Expected<std::shared_ptr<lto::InputFile>>
   addInput(std::unique_ptr<lto::InputFile> InputPtr) {
     return std::shared_ptr<lto::InputFile>(InputPtr.release());
diff --git a/llvm/lib/LTO/LTO.cpp b/llvm/lib/LTO/LTO.cpp
index 749af6eda3fdb..a3cd4bf907533 100644
--- a/llvm/lib/LTO/LTO.cpp
+++ b/llvm/lib/LTO/LTO.cpp
@@ -71,10 +71,39 @@ using namespace object;
 
 #define DEBUG_TYPE "lto"
 
+Expected<std::unique_ptr<LTO::DiagnosticSession>>
+LTO::DiagnosticSession::create(LTO &L) {
+  // Setup the remark streamer according to the provided configuration.
+  auto DiagFileOrErr = lto::setupLLVMOptimizationRemarks(
+      L.RegularLTO.Ctx, L.Conf.RemarksFilename, L.Conf.RemarksPasses,
+      L.Conf.RemarksFormat, L.Conf.RemarksWithHotness,
+      L.Conf.RemarksHotnessThreshold);
+  if (!DiagFileOrErr)
+    return DiagFileOrErr.takeError();
+
+  auto Session = std::unique_ptr<DiagnosticSession>(new DiagnosticSession(L));
+  L.DiagnosticOutputFile = std::move(*DiagFileOrErr);
+  if (L.DiagnosticOutputFile)
+    L.DiagnosticOutputFile->keep();
+
+  return std::move(Session);
+}
+
+LTO::DiagnosticSession::~DiagnosticSession() {
+  // Finalize the remarks, flushing the stream and closing the file.
+  consumeError(
+      finalizeOptimizationRemarks(std::move(Parent.DiagnosticOutputFile)));
+}
+
+void LTO::emitRemark(OptimizationRemark &Remark) {
+  const Function &F = Remark.getFunction();
+  OptimizationRemarkEmitter ORE(const_cast<Function *>(&F));
+  ORE.emit(Remark);
+}
+
 static cl::opt<bool>
     DumpThinCGSCCs("dump-thin-cg-sccs", cl::init(false), cl::Hidden,
                    cl::desc("Dump the SCCs in the ThinLTO index's callgraph"));
-
 namespace llvm {
 extern cl::opt<bool> CodeGenDataThinLTOTwoRounds;
 extern cl::opt<bool> ForceImportAll;
@@ -1068,10 +1097,9 @@ Error LTO::linkRegularLTO(RegularLTOState::AddedModule Mod,
         if (DiagnosticOutputFile) {
           if (Error Err = F->materialize())
             return Err;
-          OptimizationRemarkEmitter ORE(F, nullptr);
-          ORE.emit(OptimizationRemark(DEBUG_TYPE, "deadfunction", F)
-                   << ore::NV("Function", F)
-                   << " not added to the combined module ");
+          auto R = OptimizationRemark(DEBUG_TYPE, "deadfunction", F);
+          R << ore::NV("Function", F) << " not added to the combined module ";
+          emitRemark(R);
         }
       }
       continue;
@@ -1269,6 +1297,10 @@ Error LTO::run(AddStreamFn AddStream, FileCache Cache) {
     return StatsFileOrErr.takeError();
   std::unique_ptr<ToolOutputFile> StatsFile = std::move(StatsFileOrErr.get());
 
+  auto DiagSessionOrErr = DiagnosticSession::create(*this);
+  if (!DiagSessionOrErr)
+    return DiagSessionOrErr.takeError();
+
   // TODO: Ideally this would be controlled automatically by detecting that we
   // are linking with an allocator that supports these interfaces, rather than
   // an internal option (which would still be needed for tests, however). For
@@ -1291,15 +1323,7 @@ Error LTO::run(AddStreamFn AddStream, FileCache Cache) {
 
 Error LTO::runRegularLTO(AddStreamFn AddStream) {
   llvm::TimeTraceScope timeScope("Run regular LTO");
-  LLVMContext &CombinedCtx = RegularLTO.CombinedModule->getContext();
-  // Setup optimization remarks.
-  auto DiagFileOrErr = lto::setupLLVMOptimizationRemarks(
-      CombinedCtx, Conf.RemarksFilename, Conf.RemarksPasses, Conf.RemarksFormat,
-      Conf.RemarksWithHotness, Conf.RemarksHotnessThreshold);
   LLVM_DEBUG(dbgs() << "Running regular LTO\n");
-  if (!DiagFileOrErr)
-    return DiagFileOrErr.takeError();
-  DiagnosticOutputFile = std::move(*DiagFileOrErr);
 
   // Finalize linking of regular LTO modules containing summaries now that
   // we have computed liveness information.
@@ -1371,7 +1395,7 @@ Error LTO::runRegularLTO(AddStreamFn AddStream) {
 
   if (Conf.PreOptModuleHook &&
       !Conf.PreOptModuleHook(0, *RegularLTO.CombinedModule))
-    return finalizeOptimizationRemarks(std::move(DiagnosticOutputFile));
+    return Error::success();
 
   if (!Conf.CodeGenOnly) {
     for (const auto &R : *GlobalResolutions) {
@@ -1410,7 +1434,7 @@ Error LTO::runRegularLTO(AddStreamFn AddStream) {
 
     if (Conf.PostInternalizeModuleHook &&
         !Conf.PostInternalizeModuleHook(0, *RegularLTO.CombinedModule))
-      return finalizeOptimizationRemarks(std::move(DiagnosticOutputFile));
+      return Error::success();
   }
 
   if (!RegularLTO.EmptyCombinedModule || Conf.AlwaysEmitRegularLTOObj) {
@@ -1420,7 +1444,7 @@ Error LTO::runRegularLTO(AddStreamFn AddStream) {
       return Err;
   }
 
-  return finalizeOptimizationRemarks(std::move(DiagnosticOutputFile));
+  return Error::success();
 }
 
 SmallVector<const char *> LTO::getRuntimeLibcallSymbols(const Triple &TT) {

>From e740640dec0cce57202b987a5ce3d47c3aabf7e6 Mon Sep 17 00:00:00 2001
From: Teresa Johnson <tejohnson at google.com>
Date: Fri, 13 Feb 2026 11:10:53 -0800
Subject: [PATCH 2/2] Remove new RAII struct and move to already called virtual
 cleanup method.

---
 llvm/include/llvm/LTO/LTO.h | 20 ++++----------------
 llvm/lib/LTO/LTO.cpp        | 32 +++++++++++++-------------------
 2 files changed, 17 insertions(+), 35 deletions(-)

diff --git a/llvm/include/llvm/LTO/LTO.h b/llvm/include/llvm/LTO/LTO.h
index dda7cb63dbc9a..c49d5a2198efd 100644
--- a/llvm/include/llvm/LTO/LTO.h
+++ b/llvm/include/llvm/LTO/LTO.h
@@ -427,21 +427,6 @@ class LTO {
     LTOK_UnifiedThin,
   };
 
-  /// RAII guard for optimization remarks and LTO-link diagnostics.
-  struct DiagnosticSession {
-    /// The LTO object that owns the diagnostic resources.
-    LTO &Parent;
-
-    DiagnosticSession(LTO &L) : Parent(L) {}
-
-    /// Factory method to initialize a diagnostic session. This sets up the
-    /// remark streamer.
-    static Expected<std::unique_ptr<DiagnosticSession>> create(LTO &L);
-
-    /// Finalizes optimization remarks.
-    ~DiagnosticSession();
-  };
-
   /// Create an LTO object. A default constructed LTO object has a reasonable
   /// production configuration, but you can customize it by passing arguments to
   /// this constructor.
@@ -483,7 +468,7 @@ class LTO {
   virtual Error serializeInputsForDistribution() { return Error::success(); }
 
   // Called before returning from run().
-  virtual void cleanup() {}
+  virtual void cleanup();
 
 private:
   Config Conf;
@@ -657,6 +642,9 @@ class LTO {
   // Diagnostic optimization remarks file
   LLVMRemarkFileHandle DiagnosticOutputFile;
 
+  // Setup optimization remarks according to the provided configuration.
+  Error setupOptimizationRemarks();
+
 public:
   /// Helper to emit an optimization remark during the LTO link when outside of
   /// the standard optimization pass pipeline.
diff --git a/llvm/lib/LTO/LTO.cpp b/llvm/lib/LTO/LTO.cpp
index a3cd4bf907533..5302a5e8e3df0 100644
--- a/llvm/lib/LTO/LTO.cpp
+++ b/llvm/lib/LTO/LTO.cpp
@@ -71,31 +71,22 @@ using namespace object;
 
 #define DEBUG_TYPE "lto"
 
-Expected<std::unique_ptr<LTO::DiagnosticSession>>
-LTO::DiagnosticSession::create(LTO &L) {
+Error LTO::setupOptimizationRemarks() {
   // Setup the remark streamer according to the provided configuration.
   auto DiagFileOrErr = lto::setupLLVMOptimizationRemarks(
-      L.RegularLTO.Ctx, L.Conf.RemarksFilename, L.Conf.RemarksPasses,
-      L.Conf.RemarksFormat, L.Conf.RemarksWithHotness,
-      L.Conf.RemarksHotnessThreshold);
+      RegularLTO.Ctx, Conf.RemarksFilename, Conf.RemarksPasses,
+      Conf.RemarksFormat, Conf.RemarksWithHotness,
+      Conf.RemarksHotnessThreshold);
   if (!DiagFileOrErr)
     return DiagFileOrErr.takeError();
 
-  auto Session = std::unique_ptr<DiagnosticSession>(new DiagnosticSession(L));
-  L.DiagnosticOutputFile = std::move(*DiagFileOrErr);
-  if (L.DiagnosticOutputFile)
-    L.DiagnosticOutputFile->keep();
+  DiagnosticOutputFile = std::move(*DiagFileOrErr);
 
-  return std::move(Session);
-}
-
-LTO::DiagnosticSession::~DiagnosticSession() {
-  // Finalize the remarks, flushing the stream and closing the file.
-  consumeError(
-      finalizeOptimizationRemarks(std::move(Parent.DiagnosticOutputFile)));
+  return Error::success();
 }
 
 void LTO::emitRemark(OptimizationRemark &Remark) {
+
   const Function &F = Remark.getFunction();
   OptimizationRemarkEmitter ORE(const_cast<Function *>(&F));
   ORE.emit(Remark);
@@ -672,6 +663,10 @@ LTO::LTO(Config Conf, ThinBackend Backend,
 // Requires a destructor for MapVector<BitcodeModule>.
 LTO::~LTO() = default;
 
+void LTO::cleanup() {
+  consumeError(finalizeOptimizationRemarks(std::move(DiagnosticOutputFile)));
+}
+
 // Add the symbols in the given module to the GlobalResolutions map, and resolve
 // their partitions.
 void LTO::addModuleToGlobalRes(ArrayRef<InputFile::Symbol> Syms,
@@ -1297,9 +1292,8 @@ Error LTO::run(AddStreamFn AddStream, FileCache Cache) {
     return StatsFileOrErr.takeError();
   std::unique_ptr<ToolOutputFile> StatsFile = std::move(StatsFileOrErr.get());
 
-  auto DiagSessionOrErr = DiagnosticSession::create(*this);
-  if (!DiagSessionOrErr)
-    return DiagSessionOrErr.takeError();
+  if (Error Err = setupOptimizationRemarks())
+    return Err;
 
   // TODO: Ideally this would be controlled automatically by detecting that we
   // are linking with an allocator that supports these interfaces, rather than



More information about the llvm-commits mailing list