[llvm] Implement a custom stream for LDBG macro to handle newlines (PR #150750)
Mehdi Amini via llvm-commits
llvm-commits at lists.llvm.org
Sat Jul 26 08:19:33 PDT 2025
https://github.com/joker-eph updated https://github.com/llvm/llvm-project/pull/150750
>From b78e452a49354b7a0811e27aefab18a9553b096d 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 | 104 ++++++++++++++++++++----
llvm/unittests/Support/DebugLogTest.cpp | 21 +++++
2 files changed, 107 insertions(+), 18 deletions(-)
diff --git a/llvm/include/llvm/Support/DebugLog.h b/llvm/include/llvm/Support/DebugLog.h
index b1b17e3ede950..e7bdb33d19cbf 100644
--- a/llvm/include/llvm/Support/DebugLog.h
+++ b/llvm/include/llvm/Support/DebugLog.h
@@ -30,37 +30,95 @@ namespace llvm {
#define DEBUGLOG_WITH_STREAM_AND_TYPE(STREAM, TYPE) \
for (bool _c = (::llvm::DebugFlag && ::llvm::isCurrentDebugType(TYPE)); _c; \
_c = false) \
- ::llvm::impl::LogWithNewline(TYPE, __SHORT_FILE__, __LINE__, (STREAM))
+ ::llvm::impl::LogWithNewline(TYPE, __SHORT_FILE__, __LINE__, (STREAM)) \
+ .stream()
#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))
+ ::llvm::impl::LogWithNewline(TYPE, __FILE__, __LINE__, (STREAM)).stream()
#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(); }
+ void flushEol() {
+ if (HasPendingNewline) {
+ emitPrefix();
+ HasPendingNewline = false;
+ }
}
+ raw_ostream &getOs() { return Os; }
+
+ /// Forward the current_pos method to the underlying stream.
+ uint64_t current_pos() const final { return Os.tell(); }
+};
+
+class LogWithNewline {
+public:
+ LogWithNewline(const char *DebugType, const char *File, int Line,
+ raw_ostream &Os)
+ : Os(computePrefix(DebugType, File, Line), Os) {}
+ ~LogWithNewline() {
+ Os.flushEol();
+ Os.getOs() << '\n';
+ }
+
+ raw_ostream &stream() { return Os; }
+
// 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;
+
+private:
static constexpr const char *getShortFileName(const char *path) {
// Remove the path prefix from the file name.
const char *filename = path;
@@ -71,9 +129,19 @@ class LogWithNewline {
}
return filename;
}
-
-private:
- raw_ostream &os;
+ static std::string computePrefix(const char *DebugType, const char *File,
+ int Line) {
+ std::string Prefix;
+ raw_string_ostream OsPrefix(Prefix);
+#if !defined(__SHORT_FILE__)
+ File = ::llvm::impl::LogWithNewline::getShortFileName(File);
+#endif
+ if (DebugType)
+ OsPrefix << "[" << DebugType << "] ";
+ OsPrefix << File << ":" << Line << " ";
+ return OsPrefix.str();
+ }
+ raw_ldbg_ostream Os;
};
} // end namespace impl
#else
diff --git a/llvm/unittests/Support/DebugLogTest.cpp b/llvm/unittests/Support/DebugLogTest.cpp
index b26fa40cc8687..e40ca62ae2658 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 = "[Prefix] A:1 1\n[Prefix] A:1 2\n[Prefix] B:1 "
+ "3\n[Prefix] B:1 4\n[Prefix] A:1 5";
+ {
+ llvm::impl::LogWithNewline ldbg_osB("Prefix", "B", 1, os);
+ llvm::impl::LogWithNewline ldbg_osA("Prefix", "A", 1, os);
+ ldbg_osA.stream() << "1\n2\n";
+ ldbg_osB.stream() << "3\n4\n";
+ ldbg_osA.stream() << "5";
+ EXPECT_EQ(os.str(), expected);
+ }
+ // After destructors, there was a pending newline for stream B.
+ EXPECT_EQ(os.str(), expected + "\n[Prefix] B:1 \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