[llvm] Implement a custom stream for LDBG macro to handle newlines (PR #150750)

Mehdi Amini via llvm-commits llvm-commits at lists.llvm.org
Sun Jul 27 13:15:27 PDT 2025


https://github.com/joker-eph updated https://github.com/llvm/llvm-project/pull/150750

>From 213d1b60e66b242e535f55c458746987d79d6d2f Mon Sep 17 00:00:00 2001
From: Mehdi Amini <joker.eph at gmail.com>
Date: Sat, 26 Jul 2025 03:51:17 -0700
Subject: [PATCH] Implement a custom stream for LDBG macro to handle newlines

This prints the prefix on every new line, allowing for an output that looks like:

[dead-code-analysis] DeadCodeAnalysis.cpp:284 Visiting program point: 0x55ea5be2d4c8 <after operation>:func.func private @private_1() -> (i32, i32) {...}
[dead-code-analysis] DeadCodeAnalysis.cpp:288 Visiting operation: func.func private @private_1() -> (i32, i32) {
[dead-code-analysis] DeadCodeAnalysis.cpp:288   %c0_i32 = arith.constant 0 : i32
[dead-code-analysis] DeadCodeAnalysis.cpp:288   %0 = arith.addi %c0_i32, %c0_i32 {tag = "one"} : i32
[dead-code-analysis] DeadCodeAnalysis.cpp:288   return %c0_i32, %0 : i32, i32
[dead-code-analysis] DeadCodeAnalysis.cpp:288 }
[dead-code-analysis] DeadCodeAnalysis.cpp:313 Visiting callable operation: func.func private @private_1() -> (i32, i32) {
[dead-code-analysis] DeadCodeAnalysis.cpp:313   %c0_i32 = arith.constant 0 : i32
[dead-code-analysis] DeadCodeAnalysis.cpp:313   %0 = arith.addi %c0_i32, %c0_i32 {tag = "one"} : i32
[dead-code-analysis] DeadCodeAnalysis.cpp:313   return %c0_i32, %0 : i32, i32
[dead-code-analysis] DeadCodeAnalysis.cpp:313 }
---
 llvm/include/llvm/Support/DebugLog.h    | 122 +++++++++++++++++-------
 llvm/unittests/Support/DebugLogTest.cpp |  21 ++++
 2 files changed, 108 insertions(+), 35 deletions(-)

diff --git a/llvm/include/llvm/Support/DebugLog.h b/llvm/include/llvm/Support/DebugLog.h
index b1b17e3ede950..3d326d773d1a1 100644
--- a/llvm/include/llvm/Support/DebugLog.h
+++ b/llvm/include/llvm/Support/DebugLog.h
@@ -26,55 +26,107 @@ namespace llvm {
 //              << "] " << "Bitset contains: " << Bitset << "\n");
 #define LDBG() DEBUGLOG_WITH_STREAM_AND_TYPE(llvm::dbgs(), DEBUG_TYPE)
 
-#if defined(__SHORT_FILE__)
-#define DEBUGLOG_WITH_STREAM_AND_TYPE(STREAM, TYPE)                            \
+#define DEBUGLOG_WITH_STREAM_TYPE_AND_FILE(STREAM, TYPE, FILE)                 \
   for (bool _c = (::llvm::DebugFlag && ::llvm::isCurrentDebugType(TYPE)); _c;  \
        _c = false)                                                             \
-  ::llvm::impl::LogWithNewline(TYPE, __SHORT_FILE__, __LINE__, (STREAM))
+    ::llvm::impl::raw_ldbg_ostream {                                           \
+      ::llvm::impl::computePrefix(TYPE, FILE, __LINE__), (STREAM)              \
+    }
+// When __SHORT_FILE__ is not define, the File is the full path,
+// otherwise __SHORT_FILE__ is defined in CMake to provide the file name
+// without the path prefix.
+#if defined(__SHORT_FILE__)
+#define DEBUGLOG_WITH_STREAM_AND_TYPE(STREAM, TYPE)                            \
+  DEBUGLOG_WITH_STREAM_TYPE_AND_FILE(STREAM, TYPE, __SHORT_FILE__)
 #else
 #define DEBUGLOG_WITH_STREAM_AND_TYPE(STREAM, TYPE)                            \
-  for (bool _c = (::llvm::DebugFlag && ::llvm::isCurrentDebugType(TYPE)); _c;  \
-       _c = false)                                                             \
-  ::llvm::impl::LogWithNewline(TYPE, __FILE__, __LINE__, (STREAM))
+  DEBUGLOG_WITH_STREAM_TYPE_AND _FILE(                                         \
+      STREAM, TYPE, ::llvm::impl::LogWithNewline::getShortFileName(__FILE__))
 #endif
 
 namespace impl {
-class LogWithNewline {
+
+/// A raw_ostream that tracks `\n` and print the prefix.
+class LLVM_ABI raw_ldbg_ostream final : public raw_ostream {
+  std::string Prefix;
+  raw_ostream &Os;
+  bool HasPendingNewline = true;
+
+  /// Split the line on newlines and insert the prefix before each newline.
+  /// Forward everything to the underlying stream.
+  void write_impl(const char *Ptr, size_t Size) final {
+    auto Str = StringRef(Ptr, Size);
+    if (!Str.empty()) {
+      if (HasPendingNewline) {
+        emitPrefix();
+        HasPendingNewline = false;
+      }
+    }
+    auto Eol = Str.find('\n');
+    while (Eol != StringRef::npos) {
+      StringRef Line = Str.take_front(Eol + 1);
+      if (!Line.empty()) {
+        if (HasPendingNewline) {
+          emitPrefix();
+          HasPendingNewline = false;
+        }
+        Os.write(Line.data(), Line.size());
+      }
+      HasPendingNewline = true;
+      Str = Str.drop_front(Eol + 1);
+      Eol = Str.find('\n');
+    }
+    if (!Str.empty()) {
+      if (HasPendingNewline) {
+        emitPrefix();
+        HasPendingNewline = false;
+      }
+      Os.write(Str.data(), Str.size());
+    }
+  }
+  void emitPrefix() { Os.write(Prefix.c_str(), Prefix.size()); }
+
 public:
-  LogWithNewline(const char *debug_type, const char *file, int line,
-                 raw_ostream &os)
-      : os(os) {
-#if !defined(__SHORT_FILE__)
-    file = ::llvm::impl::LogWithNewline::getShortFileName(file);
-#endif
-    if (debug_type)
-      os << "[" << debug_type << "] ";
-    os << file << ":" << line << " ";
+  explicit raw_ldbg_ostream(std::string Prefix, raw_ostream &Os)
+      : Prefix(std::move(Prefix)), Os(Os) {
+    SetUnbuffered();
   }
-  ~LogWithNewline() { os << '\n'; }
-  template <typename T> raw_ostream &operator<<(const T &t) && {
-    return os << t;
+  ~raw_ldbg_ostream() final {
+    flushEol();
+    Os << '\n';
   }
-
-  // Prevent copying, as this class manages newline responsibility and is
-  // intended for use as a temporary.
-  LogWithNewline(const LogWithNewline &) = delete;
-  LogWithNewline &operator=(const LogWithNewline &) = delete;
-  LogWithNewline &operator=(LogWithNewline &&) = delete;
-  static constexpr const char *getShortFileName(const char *path) {
-    // Remove the path prefix from the file name.
-    const char *filename = path;
-    for (const char *p = path; *p != '\0'; ++p) {
-      if (*p == '/' || *p == '\\') {
-        filename = p + 1;
-      }
+  void flushEol() {
+    if (HasPendingNewline) {
+      emitPrefix();
+      HasPendingNewline = false;
     }
-    return filename;
   }
 
-private:
-  raw_ostream &os;
+  /// Forward the current_pos method to the underlying stream.
+  uint64_t current_pos() const final { return Os.tell(); }
 };
+
+/// Remove the path prefix from the file name.
+static constexpr const char *getShortFileName(const char *path) {
+  for (const char *p = path; *p != '\0'; ++p) {
+    if (*p == '/' || *p == '\\')
+      path = p + 1;
+  }
+  return path;
+}
+
+/// Compute the prefix for the debug log in the form of:
+/// "[DebugType] File:Line "
+/// Where the File is the file name without the path prefix.
+static std::string computePrefix(const char *DebugType, const char *File,
+                                 int Line) {
+  std::string Prefix;
+  raw_string_ostream OsPrefix(Prefix);
+  if (DebugType)
+    OsPrefix << "[" << DebugType << "] ";
+  OsPrefix << File << ":" << Line << " ";
+  return OsPrefix.str();
+}
 } // end namespace impl
 #else
 // As others in Debug, When compiling without assertions, the -debug-* options
diff --git a/llvm/unittests/Support/DebugLogTest.cpp b/llvm/unittests/Support/DebugLogTest.cpp
index b26fa40cc8687..b594a97d11e45 100644
--- a/llvm/unittests/Support/DebugLogTest.cpp
+++ b/llvm/unittests/Support/DebugLogTest.cpp
@@ -62,6 +62,27 @@ TEST(DebugLogTest, Basic) {
     EXPECT_THAT(count, Eq(1));
   }
 }
+
+TEST(DebugLogTest, StreamPrefix) {
+  llvm::DebugFlag = true;
+  static const char *DT[] = {"A", "B"};
+  setCurrentDebugTypes(DT, 2);
+
+  std::string str;
+  raw_string_ostream os(str);
+  std::string expected = "PrefixA 1\nPrefixA 2\nPrefixB "
+                         "3\nPrefixB 4\nPrefixA 5";
+  {
+    llvm::impl::raw_ldbg_ostream ldbg_osB("PrefixB ", os);
+    llvm::impl::raw_ldbg_ostream ldbg_osA("PrefixA ", os);
+    ldbg_osA << "1\n2\n";
+    ldbg_osB << "3\n4\n";
+    ldbg_osA << "5";
+    EXPECT_EQ(os.str(), expected);
+  }
+  // After destructors, there was a pending newline for stream B.
+  EXPECT_EQ(os.str(), expected + "\nPrefixB \n");
+}
 #else
 TEST(DebugLogTest, Basic) {
   // LDBG should be compiled out in NDEBUG, so just check it compiles and has



More information about the llvm-commits mailing list