[llvm] [OFFLOAD] Add support for more fine grained debug messages control (PR #165416)

Alex Duran via llvm-commits llvm-commits at lists.llvm.org
Wed Nov 19 21:29:51 PST 2025


https://github.com/adurang updated https://github.com/llvm/llvm-project/pull/165416

>From 2965164487fc0e4bf4c25047430e36dbe350a1a8 Mon Sep 17 00:00:00 2001
From: Alex Duran <alejandro.duran at intel.com>
Date: Tue, 28 Oct 2025 15:33:47 +0100
Subject: [PATCH 1/9] [OFFLOAD] Add support for more fine grained debug
 messages control

---
 offload/include/Shared/Debug.h           | 188 +++++++++++++++++++++++
 offload/libomptarget/OffloadRTL.cpp      |   2 +-
 offload/libomptarget/PluginManager.cpp   |   2 +-
 offload/plugins-nextgen/host/src/rtl.cpp |   2 +
 4 files changed, 192 insertions(+), 2 deletions(-)

diff --git a/offload/include/Shared/Debug.h b/offload/include/Shared/Debug.h
index 7c3db8dbf119f..25922d190d20e 100644
--- a/offload/include/Shared/Debug.h
+++ b/offload/include/Shared/Debug.h
@@ -42,6 +42,8 @@
 #include <mutex>
 #include <string>
 
+#include "llvm/Support/circular_raw_ostream.h"
+
 /// 32-Bit field data attributes controlling information presented to the user.
 enum OpenMPInfoType : uint32_t {
   // Print data arguments and attributes upon entering an OpenMP device kernel.
@@ -198,4 +200,190 @@ inline uint32_t getDebugLevel() {
     }                                                                          \
   } while (false)
 
+// New macros that will allow for more granular control over debugging output
+// Each message can be classified by Component, Type and Level
+// Component: The broad component of the offload runtime emitting the message.
+// Type: A cross-component classification of messages
+// Level: The verbosity level of the message
+//
+// The component is pulled from the TARGET_NAME macro, Type and Level can be
+// defined for each debug message but by default they are "default" and "1"
+// respectively.
+//
+// For liboffload and plugins, use OFFLOAD_DEBUG(...)
+// For libomptarget, use OPENMP_DEBUG(...)
+// Constructing messages should be done using C++ stream style syntax.
+//
+// Usage examples:
+// OFFLOAD_DEBUG("type1", 2, "This is a level 2 message of type1");
+// OFFLOAD_DEBUG("Init", "This is a default level of the init type");
+// OPENMP_DEBUG("This is a level 1 message of the default type");
+// OFFLOAD_DEBUG("Init", 3, NumDevices << " were initialized\n");
+// OFFLOAD_DEBUG("Kernel", "Starting kernel " << KernelName << " on device " <<
+//               DeviceId);
+//
+// Message output can be controlled by setting LIBOMPTARGET_DEBUG or
+// LIBOFFLOAD_DEBUG environment variables. Their syntax is as follows:
+// [integer]|all|<type1>[:<level1>][,<type2>[:<level2>],...]
+//
+// 0 : Disable all debug messages
+// all : Enable all debug messages
+// integer : Set the default level for all messages
+// <type> : Enable only messages of the specified type and level (more than one
+//          can be specified). Components are also supported as
+//          types.
+// <level> : Set the verbosity level for the specified type (default is 1)
+//
+// For very specific cases where more control is needed, use OFFLOAD_DEBUG_RAW
+// or OFFLOAD_DEBUG_BASE. See below for details.
+
+namespace llvm::offload::debug {
+
+#ifdef OMPTARGET_DEBUG
+
+struct DebugFilter {
+  StringRef Type;
+  uint32_t Level;
+};
+
+struct DebugSettings {
+  bool Enabled = false;
+  uint32_t DefaultLevel = 1;
+  llvm::SmallVector<DebugFilter> Filters;
+};
+
+/// dbgs - Return a circular-buffered debug stream.
+inline llvm::raw_ostream &dbgs() {
+  // Do one-time initialization in a thread-safe way.
+  static struct dbgstream {
+    llvm::circular_raw_ostream strm;
+
+    dbgstream() : strm(llvm::errs(), "*** Debug Log Output ***\n", 0) {}
+  } thestrm;
+
+  return thestrm.strm;
+}
+
+inline DebugFilter parseDebugFilter(StringRef Filter) {
+  size_t Pos = Filter.find(':');
+  if (Pos == StringRef::npos)
+    return {Filter, 1};
+
+  StringRef Type = Filter.slice(0, Pos);
+  uint32_t Level = 1;
+  if (Filter.slice(Pos + 1, Filter.size()).getAsInteger(10, Level))
+    Level = 1;
+
+  return {Type, Level};
+}
+
+inline DebugSettings &getDebugSettings() {
+  static DebugSettings Settings;
+  static std::once_flag Flag{};
+  std::call_once(Flag, []() {
+    printf("Configuring debug settings\n");
+    // Eventually, we probably should allow for the upper layers to set
+    // the debug settings directly according to their own env var
+    // (or other methods) settings.
+    // For now mantain compatibility with existing libomptarget env var
+    // and add a liboffload independent one.
+    char *Env = getenv("LIBOMPTARGET_DEBUG");
+    if (!Env) {
+      Env = getenv("LIBOFFLOAD_DEBUG");
+      if (!Env)
+        return;
+    }
+
+    StringRef EnvRef(Env);
+    if (EnvRef == "0")
+      return;
+
+    Settings.Enabled = true;
+    if (EnvRef.equals_insensitive("all"))
+      return;
+
+    if (!EnvRef.getAsInteger(10, Settings.DefaultLevel))
+      return;
+
+    Settings.DefaultLevel = 1;
+
+    SmallVector<StringRef> DbgTypes;
+    EnvRef.split(DbgTypes, ',', -1, false);
+
+    for (auto &DT : DbgTypes)
+      Settings.Filters.push_back(parseDebugFilter(DT));
+  });
+
+  return Settings;
+}
+
+inline bool isDebugEnabled() {
+  return getDebugSettings().Enabled;
+}
+
+inline bool shouldPrintDebug(const char *Component, const char *Type, uint32_t Level) {
+  const auto &Settings = getDebugSettings();
+  if (!Settings.Enabled)
+    return false;
+
+  if (Settings.Filters.empty())
+    return Level <= Settings.DefaultLevel;
+
+  for (const auto &DT : Settings.Filters) {
+    if (DT.Level < Level)
+      continue;
+    if (DT.Type.equals_insensitive(Type))
+      return true;
+    if (DT.Type.equals_insensitive(Component))
+      return true;
+  }
+
+  return false;
+}
+
+#define OFFLOAD_DEBUG_BASE(Component, Type, Level, ...)                        \
+  do {                                                                         \
+    if (llvm::offload::debug::isDebugEnabled() &&                              \
+        llvm::offload::debug::shouldPrintDebug(Component, Type, Level))        \
+      __VA_ARGS__;                                                             \
+  } while (0)
+
+#define OFFLOAD_DEBUG_RAW(Type, Level, X)                                      \
+  OFFLOAD_DEBUG_BASE(GETNAME(TARGET_NAME), Type, Level, X)
+
+#define OFFLOAD_DEBUG_1(X)                                                     \
+  OFFLOAD_DEBUG_BASE(GETNAME(TARGET_NAME), "default", 1,                       \
+                     llvm::offload::debug::dbgs()                              \
+                         << DEBUG_PREFIX << " --> " << X)
+
+#define OFFLOAD_DEBUG_2(Type, X)                                               \
+  OFFLOAD_DEBUG_BASE(GETNAME(TARGET_NAME), Type, 1,                            \
+                     llvm::offload::debug::dbgs()                              \
+                         << DEBUG_PREFIX << " --> " << X)
+
+#define OFFLOAD_DEBUG_3(Type, Level, X)                                        \
+  OFFLOAD_DEBUG_BASE(GETNAME(TARGET_NAME), Type, Level,                        \
+                     llvm::offload::debug::dbgs()                              \
+                         << DEBUG_PREFIX << " --> " << X)
+
+#define OFFLOAD_SELECT(Type, Level, X, NArgs, ...) OFFLOAD_DEBUG_##NArgs
+
+// To be used in liboffload and plugins
+#define OFFLOAD_DEBUG(...) OFFLOAD_SELECT(__VA_ARGS__, 3, 2, 1)(__VA_ARGS__)
+
+// To be used in libomptarget only
+#define OPENMP_DEBUG(...) OFFLOAD_DEBUG(__VA_ARGS__)
+
+#else
+
+// Don't print anything if debugging is disabled
+#define OFFLOAD_DEBUG_BASE(Component, Type, Level, ...)
+#define OFFLOAD_DEBUG_RAW(Type, Level, X)
+#define OFFLOAD_DEBUG(...)
+#define OPENMP_DEBUG(...)
+
+#endif
+
+} // namespace llvm::offload::debug
+
 #endif // OMPTARGET_SHARED_DEBUG_H
diff --git a/offload/libomptarget/OffloadRTL.cpp b/offload/libomptarget/OffloadRTL.cpp
index 04bd21ec91a49..9f9ec5c8bfabe 100644
--- a/offload/libomptarget/OffloadRTL.cpp
+++ b/offload/libomptarget/OffloadRTL.cpp
@@ -35,7 +35,7 @@ void initRuntime() {
 
   RefCount++;
   if (RefCount == 1) {
-    DP("Init offload library!\n");
+    OPENMP_DEBUG("Init offload library!\n");
 #ifdef OMPT_SUPPORT
     // Initialize OMPT first
     llvm::omp::target::ompt::connectLibrary();
diff --git a/offload/libomptarget/PluginManager.cpp b/offload/libomptarget/PluginManager.cpp
index c8d6b42114d0f..915fd8df80eb7 100644
--- a/offload/libomptarget/PluginManager.cpp
+++ b/offload/libomptarget/PluginManager.cpp
@@ -36,7 +36,7 @@ void PluginManager::init() {
     return;
   }
 
-  DP("Loading RTLs...\n");
+  OPENMP_DEBUG("Init", "Loading RTLs\n");
 
   // Attempt to create an instance of each supported plugin.
 #define PLUGIN_TARGET(Name)                                                    \
diff --git a/offload/plugins-nextgen/host/src/rtl.cpp b/offload/plugins-nextgen/host/src/rtl.cpp
index eb4ecac9907a1..3baadfebc541c 100644
--- a/offload/plugins-nextgen/host/src/rtl.cpp
+++ b/offload/plugins-nextgen/host/src/rtl.cpp
@@ -451,6 +451,8 @@ struct GenELF64PluginTy final : public GenericPluginTy {
     if (auto Err = Plugin::check(ffi_init(), "failed to initialize libffi"))
       return std::move(Err);
 #endif
+    OFFLOAD_DEBUG("Init", 2,
+                  "GenELF64 plugin detected" << NUM_DEVICES << " devices\n");
 
     return NUM_DEVICES;
   }

>From 7bc7ce8b69a1e3b1fb4602b2ae3477d7fa3748a0 Mon Sep 17 00:00:00 2001
From: Alex Duran <alejandro.duran at intel.com>
Date: Tue, 28 Oct 2025 16:04:18 +0100
Subject: [PATCH 2/9] formatting

---
 offload/include/Shared/Debug.h | 15 +++++++--------
 1 file changed, 7 insertions(+), 8 deletions(-)

diff --git a/offload/include/Shared/Debug.h b/offload/include/Shared/Debug.h
index 25922d190d20e..85e1c0e19445d 100644
--- a/offload/include/Shared/Debug.h
+++ b/offload/include/Shared/Debug.h
@@ -282,10 +282,10 @@ inline DebugSettings &getDebugSettings() {
   static std::once_flag Flag{};
   std::call_once(Flag, []() {
     printf("Configuring debug settings\n");
-    // Eventually, we probably should allow for the upper layers to set
-    // the debug settings directly according to their own env var
-    // (or other methods) settings.
-    // For now mantain compatibility with existing libomptarget env var
+    // Eventually, we probably should allow the upper layers to set
+    // debug settings directly according to their own env var or
+    // other methods.
+    // For now, mantain compatibility with existing libomptarget env var
     // and add a liboffload independent one.
     char *Env = getenv("LIBOMPTARGET_DEBUG");
     if (!Env) {
@@ -317,11 +317,10 @@ inline DebugSettings &getDebugSettings() {
   return Settings;
 }
 
-inline bool isDebugEnabled() {
-  return getDebugSettings().Enabled;
-}
+inline bool isDebugEnabled() { return getDebugSettings().Enabled; }
 
-inline bool shouldPrintDebug(const char *Component, const char *Type, uint32_t Level) {
+inline bool shouldPrintDebug(const char *Component, const char *Type,
+                             uint32_t Level) {
   const auto &Settings = getDebugSettings();
   if (!Settings.Enabled)
     return false;

>From c9fc54a3c8d07b34c6be74ef1e579bf1b8ffb2a5 Mon Sep 17 00:00:00 2001
From: Alex Duran <alejandro.duran at intel.com>
Date: Wed, 29 Oct 2025 16:04:32 +0100
Subject: [PATCH 3/9] add some env var examples

---
 offload/include/Shared/Debug.h | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/offload/include/Shared/Debug.h b/offload/include/Shared/Debug.h
index 85e1c0e19445d..3af4bb4abdd93 100644
--- a/offload/include/Shared/Debug.h
+++ b/offload/include/Shared/Debug.h
@@ -227,13 +227,24 @@ inline uint32_t getDebugLevel() {
 // [integer]|all|<type1>[:<level1>][,<type2>[:<level2>],...]
 //
 // 0 : Disable all debug messages
-// all : Enable all debug messages
+// all : Enable all level 1 debug messages
 // integer : Set the default level for all messages
 // <type> : Enable only messages of the specified type and level (more than one
 //          can be specified). Components are also supported as
 //          types.
 // <level> : Set the verbosity level for the specified type (default is 1)
 //
+// Some examples:
+// LIBOFFLOAD_DEBUG=1  (Print all messages of level 1 or lower)
+// LIBOFFLOAD_DEBUG=5  (Print all messages of level 5 or lower)
+// LIBOFFLOAD_DEBUG=init (Print messages of type "init" of level 1 or lower)
+// LIBOFFLOAD_DEBUG=init:3,mapping:2 (Print messages of type "init" of level 3
+//                                   or lower and messages of type "mapping" of
+//                                   level 2 or lower)
+// LIBOFFLOAD_DEBUG=omptarget:4, init (Print messages from component "omptarget" of
+//                                   level 4 or lower and messages of type
+//                                   "init" of level 1 or lower)
+//
 // For very specific cases where more control is needed, use OFFLOAD_DEBUG_RAW
 // or OFFLOAD_DEBUG_BASE. See below for details.
 

>From da323afd1959320804fefcc77cdaffa4f92a0864 Mon Sep 17 00:00:00 2001
From: Alex Duran <alejandro.duran at intel.com>
Date: Tue, 4 Nov 2025 21:31:42 +0100
Subject: [PATCH 4/9] remove forgotten printf

---
 offload/include/Shared/Debug.h | 1 -
 1 file changed, 1 deletion(-)

diff --git a/offload/include/Shared/Debug.h b/offload/include/Shared/Debug.h
index 3af4bb4abdd93..1fbf1c2a2645b 100644
--- a/offload/include/Shared/Debug.h
+++ b/offload/include/Shared/Debug.h
@@ -292,7 +292,6 @@ inline DebugSettings &getDebugSettings() {
   static DebugSettings Settings;
   static std::once_flag Flag{};
   std::call_once(Flag, []() {
-    printf("Configuring debug settings\n");
     // Eventually, we probably should allow the upper layers to set
     // debug settings directly according to their own env var or
     // other methods.

>From 562c93ec63e046b0a16ac96e8b58446ce5af81af Mon Sep 17 00:00:00 2001
From: Alex Duran <alejandro.duran at intel.com>
Date: Tue, 4 Nov 2025 21:49:04 +0100
Subject: [PATCH 5/9] minor comment format fix

---
 offload/include/Shared/Debug.h | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/offload/include/Shared/Debug.h b/offload/include/Shared/Debug.h
index 1fbf1c2a2645b..5d918f9afef91 100644
--- a/offload/include/Shared/Debug.h
+++ b/offload/include/Shared/Debug.h
@@ -241,8 +241,8 @@ inline uint32_t getDebugLevel() {
 // LIBOFFLOAD_DEBUG=init:3,mapping:2 (Print messages of type "init" of level 3
 //                                   or lower and messages of type "mapping" of
 //                                   level 2 or lower)
-// LIBOFFLOAD_DEBUG=omptarget:4, init (Print messages from component "omptarget" of
-//                                   level 4 or lower and messages of type
+// LIBOFFLOAD_DEBUG=omptarget:4, init (Print messages from component "omptarget" 
+//                                   of level 4 or lower and messages of type
 //                                   "init" of level 1 or lower)
 //
 // For very specific cases where more control is needed, use OFFLOAD_DEBUG_RAW

>From b13c11633db1903e0da9f81d46fef70e9d064281 Mon Sep 17 00:00:00 2001
From: Alex Duran <alejandro.duran at intel.com>
Date: Tue, 4 Nov 2025 21:53:26 +0100
Subject: [PATCH 6/9] nit

---
 offload/include/Shared/Debug.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/offload/include/Shared/Debug.h b/offload/include/Shared/Debug.h
index 5d918f9afef91..5ca79040cd268 100644
--- a/offload/include/Shared/Debug.h
+++ b/offload/include/Shared/Debug.h
@@ -241,7 +241,7 @@ inline uint32_t getDebugLevel() {
 // LIBOFFLOAD_DEBUG=init:3,mapping:2 (Print messages of type "init" of level 3
 //                                   or lower and messages of type "mapping" of
 //                                   level 2 or lower)
-// LIBOFFLOAD_DEBUG=omptarget:4, init (Print messages from component "omptarget" 
+// LIBOFFLOAD_DEBUG=omptarget:4, init (Print messages from component "omptarget"
 //                                   of level 4 or lower and messages of type
 //                                   "init" of level 1 or lower)
 //

>From 948354b2a3f655844396d68da021f31f061a0dd1 Mon Sep 17 00:00:00 2001
From: Alex Duran <alejandro.duran at intel.com>
Date: Tue, 11 Nov 2025 19:38:57 +0100
Subject: [PATCH 7/9] New implementation based on LDBG from Support/DebugLog

---
 offload/include/Shared/Debug.h           | 218 ++++++++++++++++++-----
 offload/libomptarget/OffloadRTL.cpp      |   2 +-
 offload/libomptarget/PluginManager.cpp   |   2 +-
 offload/plugins-nextgen/host/src/rtl.cpp |   5 +-
 4 files changed, 174 insertions(+), 53 deletions(-)

diff --git a/offload/include/Shared/Debug.h b/offload/include/Shared/Debug.h
index 5ca79040cd268..d7387aa8375e7 100644
--- a/offload/include/Shared/Debug.h
+++ b/offload/include/Shared/Debug.h
@@ -210,17 +210,30 @@ inline uint32_t getDebugLevel() {
 // defined for each debug message but by default they are "default" and "1"
 // respectively.
 //
-// For liboffload and plugins, use OFFLOAD_DEBUG(...)
-// For libomptarget, use OPENMP_DEBUG(...)
 // Constructing messages should be done using C++ stream style syntax.
 //
 // Usage examples:
-// OFFLOAD_DEBUG("type1", 2, "This is a level 2 message of type1");
-// OFFLOAD_DEBUG("Init", "This is a default level of the init type");
-// OPENMP_DEBUG("This is a level 1 message of the default type");
-// OFFLOAD_DEBUG("Init", 3, NumDevices << " were initialized\n");
-// OFFLOAD_DEBUG("Kernel", "Starting kernel " << KernelName << " on device " <<
-//               DeviceId);
+// ODBG("type1", 2) << "This is a level 2 message of type1";
+// ODBG("Init") << "This is a default level of the init type";
+// ODBG() << "This is a level 1 message of the default type";
+// ODBG("Init", 3) << NumDevices << " were initialized";
+// ODBG("Kernel") << "Launching " << KernelName << " on device " << DeviceId;
+//
+// Additionally, ODBG_IF_LEVEL can be used to have parts of a stream to provide
+// additional detail at different levels without needing to split the message.
+// Using ODBG_RESET_LEVEL will reset the level back to the original level.
+// E.g.:
+// ODBG("Mapping", 2) << "Function F"
+//   << ODBG_IF_LEVEL(3) << " with argument value=" << Arg
+//   << ODBG_IF_LEVEL(4) << " and address=" << &Arg
+//   << ODBG_RESET_LEVEL() << " called";
+//
+// Similarly the ODBG_ONLY_LEVEL can be used to print parts of a stream only at
+// a specific level, e.g.:
+// ODBG() << "Starting computation "
+//   << ODBG_ONLY_LEVEL(1) << "on a device"
+//   << ODBG_ONLY_LEVEL(2) << "and mapping data on device" << DeviceId;
+//   << ODBG_ONLY_LEVEL(3) << dumpDetailedMappingInfo(DeviceId);
 //
 // Message output can be controlled by setting LIBOMPTARGET_DEBUG or
 // LIBOFFLOAD_DEBUG environment variables. Their syntax is as follows:
@@ -245,8 +258,8 @@ inline uint32_t getDebugLevel() {
 //                                   of level 4 or lower and messages of type
 //                                   "init" of level 1 or lower)
 //
-// For very specific cases where more control is needed, use OFFLOAD_DEBUG_RAW
-// or OFFLOAD_DEBUG_BASE. See below for details.
+// For very specific cases where more control is needed, use ODBG_STREAM or
+// ODBG_BASE. See below for details.
 
 namespace llvm::offload::debug {
 
@@ -264,7 +277,7 @@ struct DebugSettings {
 };
 
 /// dbgs - Return a circular-buffered debug stream.
-inline llvm::raw_ostream &dbgs() {
+[[maybe_unused]] static llvm::raw_ostream &dbgs() {
   // Do one-time initialization in a thread-safe way.
   static struct dbgstream {
     llvm::circular_raw_ostream strm;
@@ -275,7 +288,7 @@ inline llvm::raw_ostream &dbgs() {
   return thestrm.strm;
 }
 
-inline DebugFilter parseDebugFilter(StringRef Filter) {
+[[maybe_unused]] static DebugFilter parseDebugFilter(StringRef Filter) {
   size_t Pos = Filter.find(':');
   if (Pos == StringRef::npos)
     return {Filter, 1};
@@ -288,7 +301,7 @@ inline DebugFilter parseDebugFilter(StringRef Filter) {
   return {Type, Level};
 }
 
-inline DebugSettings &getDebugSettings() {
+[[maybe_unused]] static DebugSettings &getDebugSettings() {
   static DebugSettings Settings;
   static std::once_flag Flag{};
   std::call_once(Flag, []() {
@@ -329,67 +342,174 @@ inline DebugSettings &getDebugSettings() {
 
 inline bool isDebugEnabled() { return getDebugSettings().Enabled; }
 
-inline bool shouldPrintDebug(const char *Component, const char *Type,
-                             uint32_t Level) {
+[[maybe_unused]] static bool
+shouldPrintDebug(const char *Component, const char *Type, uint32_t &Level) {
   const auto &Settings = getDebugSettings();
   if (!Settings.Enabled)
     return false;
 
-  if (Settings.Filters.empty())
-    return Level <= Settings.DefaultLevel;
+  if (Settings.Filters.empty()) {
+    if (Level <= Settings.DefaultLevel) {
+      Level = Settings.DefaultLevel;
+      return true;
+    }
+    return false;
+  }
 
   for (const auto &DT : Settings.Filters) {
     if (DT.Level < Level)
       continue;
-    if (DT.Type.equals_insensitive(Type))
-      return true;
-    if (DT.Type.equals_insensitive(Component))
+    if (DT.Type.equals_insensitive(Type) ||
+        DT.Type.equals_insensitive(Component)) {
+      Level = DT.Level;
       return true;
+    }
   }
 
   return false;
 }
 
-#define OFFLOAD_DEBUG_BASE(Component, Type, Level, ...)                        \
-  do {                                                                         \
-    if (llvm::offload::debug::isDebugEnabled() &&                              \
-        llvm::offload::debug::shouldPrintDebug(Component, Type, Level))        \
-      __VA_ARGS__;                                                             \
-  } while (0)
+/// A raw_ostream that tracks `\n` and print the prefix after each
+/// newline. Based on raw_ldbg_ostream from Support/DebugLog.h
+class LLVM_ABI odbg_ostream final : public raw_ostream {
+public:
+  enum IfLevel : uint32_t;
+  enum OnlyLevel : uint32_t;
+
+private:
+  std::string Prefix;
+  raw_ostream &Os;
+  uint32_t BaseLevel;
+  bool ShouldPrefixNextString;
+  bool ShouldEmitNewLineOnDestruction;
+
+  /// If the stream is muted, writes to it are ignored
+  bool Muted = false;
+
+  /// 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 {
+    if (Muted)
+      return;
+
+    auto Str = StringRef(Ptr, Size);
+    auto Eol = Str.find('\n');
+    // Handle `\n` occurring in the string, ensure to print the prefix at the
+    // beginning of each line.
+    while (Eol != StringRef::npos) {
+      // Take the line up to the newline (including the newline).
+      StringRef Line = Str.take_front(Eol + 1);
+      if (!Line.empty())
+        writeWithPrefix(Line);
+      // We printed a newline, record here to print a prefix.
+      ShouldPrefixNextString = true;
+      Str = Str.drop_front(Eol + 1);
+      Eol = Str.find('\n');
+    }
+    if (!Str.empty())
+      writeWithPrefix(Str);
+  }
+  void emitPrefix() { Os.write(Prefix.c_str(), Prefix.size()); }
+  void writeWithPrefix(StringRef Str) {
+    if (ShouldPrefixNextString) {
+      emitPrefix();
+      ShouldPrefixNextString = false;
+    }
+    Os.write(Str.data(), Str.size());
+  }
+
+public:
+  explicit odbg_ostream(std::string Prefix, raw_ostream &Os, uint32_t BaseLevel,
+                        bool ShouldPrefixNextString = true,
+                        bool ShouldEmitNewLineOnDestruction = false)
+      : Prefix(std::move(Prefix)), Os(Os), BaseLevel(BaseLevel),
+        ShouldPrefixNextString(ShouldPrefixNextString),
+        ShouldEmitNewLineOnDestruction(ShouldEmitNewLineOnDestruction) {
+    SetUnbuffered();
+  }
+  ~odbg_ostream() final {
+    if (ShouldEmitNewLineOnDestruction)
+      Os << '\n';
+  }
 
-#define OFFLOAD_DEBUG_RAW(Type, Level, X)                                      \
-  OFFLOAD_DEBUG_BASE(GETNAME(TARGET_NAME), Type, Level, X)
+  /// Forward the current_pos method to the underlying stream.
+  uint64_t current_pos() const final { return Os.tell(); }
 
-#define OFFLOAD_DEBUG_1(X)                                                     \
-  OFFLOAD_DEBUG_BASE(GETNAME(TARGET_NAME), "default", 1,                       \
-                     llvm::offload::debug::dbgs()                              \
-                         << DEBUG_PREFIX << " --> " << X)
+  /// Some of the `<<` operators expect an lvalue, so we trick the type
+  /// system.
+  odbg_ostream &asLvalue() { return *this; }
 
-#define OFFLOAD_DEBUG_2(Type, X)                                               \
-  OFFLOAD_DEBUG_BASE(GETNAME(TARGET_NAME), Type, 1,                            \
-                     llvm::offload::debug::dbgs()                              \
-                         << DEBUG_PREFIX << " --> " << X)
+  void shouldMute(const IfLevel Filter) { Muted = Filter > BaseLevel; }
+  void shouldMute(const OnlyLevel Filter) { Muted = BaseLevel != Filter; }
+};
 
-#define OFFLOAD_DEBUG_3(Type, Level, X)                                        \
-  OFFLOAD_DEBUG_BASE(GETNAME(TARGET_NAME), Type, Level,                        \
-                     llvm::offload::debug::dbgs()                              \
-                         << DEBUG_PREFIX << " --> " << X)
+/// Compute the prefix for the debug log in the form of:
+/// "Component --> "
+[[maybe_unused]] static std::string computePrefix(StringRef Component,
+                                                  StringRef DebugType) {
+  std::string Prefix;
+  raw_string_ostream OsPrefix(Prefix);
+  OsPrefix << Component << " --> ";
+  return OsPrefix.str();
+}
 
-#define OFFLOAD_SELECT(Type, Level, X, NArgs, ...) OFFLOAD_DEBUG_##NArgs
+static inline raw_ostream &operator<<(raw_ostream &Os,
+                                      const odbg_ostream::IfLevel Filter) {
+  odbg_ostream &Dbg = static_cast<odbg_ostream &>(Os);
+  Dbg.shouldMute(Filter);
+  return Dbg;
+}
 
-// To be used in liboffload and plugins
-#define OFFLOAD_DEBUG(...) OFFLOAD_SELECT(__VA_ARGS__, 3, 2, 1)(__VA_ARGS__)
+static inline raw_ostream &operator<<(raw_ostream &Os,
+                                      const odbg_ostream::OnlyLevel Filter) {
+  odbg_ostream &Dbg = static_cast<odbg_ostream &>(Os);
+  Dbg.shouldMute(Filter);
+  return Dbg;
+}
 
-// To be used in libomptarget only
-#define OPENMP_DEBUG(...) OFFLOAD_DEBUG(__VA_ARGS__)
+#define ODBG_BASE(Stream, Component, Prefix, Type, Level)                      \
+  for (uint32_t RealLevel = (Level),                                           \
+                _c = llvm::offload::debug::isDebugEnabled() &&                 \
+                     llvm::offload::debug::shouldPrintDebug(                   \
+                         (Component), (Type), RealLevel);                      \
+       _c; _c = 0)                                                             \
+  ::llvm::offload::debug::odbg_ostream{                                        \
+      ::llvm::offload::debug::computePrefix((Prefix), (Type)), (Stream),       \
+      RealLevel, /*ShouldPrefixNextString=*/true,                              \
+      /*ShouldEmitNewLineOnDestruction=*/true}                                 \
+      .asLvalue()
+
+#define ODBG_STREAM(Stream, Type, Level)                                       \
+  ODBG_BASE(Stream, GETNAME(TARGET_NAME), DEBUG_PREFIX, Type, Level)
+
+#define ODBG_0() ODBG_2("default", 1)
+#define ODBG_1(Type) ODBG_2(Type, 1)
+#define ODBG_2(Type, Level)                                                    \
+  ODBG_STREAM(llvm::offload::debug::dbgs(), Type, Level)
+#define ODBG_SELECT(Type, Level, NArgs, ...) ODBG_##NArgs
+
+#define ODBG_IF_LEVEL(Level)                                                   \
+  static_cast<llvm::offload::debug::odbg_ostream::IfLevel>(Level)
+#define ODBG_ONLY_LEVEL(Level)                                                 \
+  static_cast<llvm::offload::debug::odbg_ostream::OnlyLevel>(Level)
+#define ODBG_RESET_LEVEL()                                                     \
+  static_cast<llvm::offload::debug::odbg_ostream::IfLevel>(0)
+
+#define ODBG(...) ODBG_SELECT(__VA_ARGS__ __VA_OPT__(, ) 2, 1, 0)(__VA_ARGS__)
 
 #else
 
+#define ODBG_NULL                                                              \
+  for (bool _c = false; _c; _c = false)                                        \
+  ::llvm::nulls()
+
 // Don't print anything if debugging is disabled
-#define OFFLOAD_DEBUG_BASE(Component, Type, Level, ...)
-#define OFFLOAD_DEBUG_RAW(Type, Level, X)
-#define OFFLOAD_DEBUG(...)
-#define OPENMP_DEBUG(...)
+#define ODBG_BASE(Stream, Component, Prefix, Type, Level) ODBG_NULL
+#define ODBG_STREAM(Stream, Type, Level) ODBG_NULL
+#define ODBG_IF_LEVEL(Level) 0
+#define ODBG_ONLY_LEVEL(Level) 0
+#define ODBG_RESET_LEVEL() 0
+#define ODBG(...) ODBG_NULL
 
 #endif
 
diff --git a/offload/libomptarget/OffloadRTL.cpp b/offload/libomptarget/OffloadRTL.cpp
index 9f9ec5c8bfabe..0ae325bf496d9 100644
--- a/offload/libomptarget/OffloadRTL.cpp
+++ b/offload/libomptarget/OffloadRTL.cpp
@@ -35,7 +35,7 @@ void initRuntime() {
 
   RefCount++;
   if (RefCount == 1) {
-    OPENMP_DEBUG("Init offload library!\n");
+    ODBG() << "Init offload library!";
 #ifdef OMPT_SUPPORT
     // Initialize OMPT first
     llvm::omp::target::ompt::connectLibrary();
diff --git a/offload/libomptarget/PluginManager.cpp b/offload/libomptarget/PluginManager.cpp
index 915fd8df80eb7..d9db9b6450789 100644
--- a/offload/libomptarget/PluginManager.cpp
+++ b/offload/libomptarget/PluginManager.cpp
@@ -36,7 +36,7 @@ void PluginManager::init() {
     return;
   }
 
-  OPENMP_DEBUG("Init", "Loading RTLs\n");
+  ODBG("Init") << "Loading RTLs";
 
   // Attempt to create an instance of each supported plugin.
 #define PLUGIN_TARGET(Name)                                                    \
diff --git a/offload/plugins-nextgen/host/src/rtl.cpp b/offload/plugins-nextgen/host/src/rtl.cpp
index 3baadfebc541c..d717aef629e60 100644
--- a/offload/plugins-nextgen/host/src/rtl.cpp
+++ b/offload/plugins-nextgen/host/src/rtl.cpp
@@ -451,8 +451,9 @@ struct GenELF64PluginTy final : public GenericPluginTy {
     if (auto Err = Plugin::check(ffi_init(), "failed to initialize libffi"))
       return std::move(Err);
 #endif
-    OFFLOAD_DEBUG("Init", 2,
-                  "GenELF64 plugin detected" << NUM_DEVICES << " devices\n");
+    ODBG("Init") << "GenELF64 plugin detected "
+      << ODBG_IF_LEVEL(2) << NUM_DEVICES << " "
+      << ODBG_RESET_LEVEL() << "devices";
 
     return NUM_DEVICES;
   }

>From 04e44a7916ca4b9ff48f48bd962ba29ae3cfe323 Mon Sep 17 00:00:00 2001
From: Alex Duran <alejandro.duran at intel.com>
Date: Thu, 20 Nov 2025 06:16:51 +0100
Subject: [PATCH 8/9] minor refactoring, format

---
 offload/include/Shared/Debug.h           | 11 ++++++-----
 offload/plugins-nextgen/host/src/rtl.cpp |  5 ++---
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/offload/include/Shared/Debug.h b/offload/include/Shared/Debug.h
index d7387aa8375e7..668887309214e 100644
--- a/offload/include/Shared/Debug.h
+++ b/offload/include/Shared/Debug.h
@@ -42,6 +42,7 @@
 #include <mutex>
 #include <string>
 
+#include "llvm/ADT/StringExtras.h"
 #include "llvm/Support/circular_raw_ostream.h"
 
 /// 32-Bit field data attributes controlling information presented to the user.
@@ -330,11 +331,11 @@ struct DebugSettings {
 
     Settings.DefaultLevel = 1;
 
-    SmallVector<StringRef> DbgTypes;
-    EnvRef.split(DbgTypes, ',', -1, false);
-
-    for (auto &DT : DbgTypes)
-      Settings.Filters.push_back(parseDebugFilter(DT));
+    for (auto &FilterSpec : llvm::split(EnvRef, ',')) {
+      if (FilterSpec.empty())
+        continue;
+      Settings.Filters.push_back(parseDebugFilter(FilterSpec));
+    }
   });
 
   return Settings;
diff --git a/offload/plugins-nextgen/host/src/rtl.cpp b/offload/plugins-nextgen/host/src/rtl.cpp
index d717aef629e60..70ff135cfe3df 100644
--- a/offload/plugins-nextgen/host/src/rtl.cpp
+++ b/offload/plugins-nextgen/host/src/rtl.cpp
@@ -451,9 +451,8 @@ struct GenELF64PluginTy final : public GenericPluginTy {
     if (auto Err = Plugin::check(ffi_init(), "failed to initialize libffi"))
       return std::move(Err);
 #endif
-    ODBG("Init") << "GenELF64 plugin detected "
-      << ODBG_IF_LEVEL(2) << NUM_DEVICES << " "
-      << ODBG_RESET_LEVEL() << "devices";
+    ODBG("Init") << "GenELF64 plugin detected " << ODBG_IF_LEVEL(2)
+                 << NUM_DEVICES << " " << ODBG_RESET_LEVEL() << "devices";
 
     return NUM_DEVICES;
   }

>From a045ce80f75614660c2a11971eef3d9cb8fc6bbf Mon Sep 17 00:00:00 2001
From: Alex Duran <alejandro.duran at intel.com>
Date: Thu, 20 Nov 2025 06:29:15 +0100
Subject: [PATCH 9/9] simplify description

---
 offload/include/Shared/Debug.h | 89 ++++++++++------------------------
 1 file changed, 26 insertions(+), 63 deletions(-)

diff --git a/offload/include/Shared/Debug.h b/offload/include/Shared/Debug.h
index 668887309214e..41613a37c3548 100644
--- a/offload/include/Shared/Debug.h
+++ b/offload/include/Shared/Debug.h
@@ -201,67 +201,6 @@ inline uint32_t getDebugLevel() {
     }                                                                          \
   } while (false)
 
-// New macros that will allow for more granular control over debugging output
-// Each message can be classified by Component, Type and Level
-// Component: The broad component of the offload runtime emitting the message.
-// Type: A cross-component classification of messages
-// Level: The verbosity level of the message
-//
-// The component is pulled from the TARGET_NAME macro, Type and Level can be
-// defined for each debug message but by default they are "default" and "1"
-// respectively.
-//
-// Constructing messages should be done using C++ stream style syntax.
-//
-// Usage examples:
-// ODBG("type1", 2) << "This is a level 2 message of type1";
-// ODBG("Init") << "This is a default level of the init type";
-// ODBG() << "This is a level 1 message of the default type";
-// ODBG("Init", 3) << NumDevices << " were initialized";
-// ODBG("Kernel") << "Launching " << KernelName << " on device " << DeviceId;
-//
-// Additionally, ODBG_IF_LEVEL can be used to have parts of a stream to provide
-// additional detail at different levels without needing to split the message.
-// Using ODBG_RESET_LEVEL will reset the level back to the original level.
-// E.g.:
-// ODBG("Mapping", 2) << "Function F"
-//   << ODBG_IF_LEVEL(3) << " with argument value=" << Arg
-//   << ODBG_IF_LEVEL(4) << " and address=" << &Arg
-//   << ODBG_RESET_LEVEL() << " called";
-//
-// Similarly the ODBG_ONLY_LEVEL can be used to print parts of a stream only at
-// a specific level, e.g.:
-// ODBG() << "Starting computation "
-//   << ODBG_ONLY_LEVEL(1) << "on a device"
-//   << ODBG_ONLY_LEVEL(2) << "and mapping data on device" << DeviceId;
-//   << ODBG_ONLY_LEVEL(3) << dumpDetailedMappingInfo(DeviceId);
-//
-// Message output can be controlled by setting LIBOMPTARGET_DEBUG or
-// LIBOFFLOAD_DEBUG environment variables. Their syntax is as follows:
-// [integer]|all|<type1>[:<level1>][,<type2>[:<level2>],...]
-//
-// 0 : Disable all debug messages
-// all : Enable all level 1 debug messages
-// integer : Set the default level for all messages
-// <type> : Enable only messages of the specified type and level (more than one
-//          can be specified). Components are also supported as
-//          types.
-// <level> : Set the verbosity level for the specified type (default is 1)
-//
-// Some examples:
-// LIBOFFLOAD_DEBUG=1  (Print all messages of level 1 or lower)
-// LIBOFFLOAD_DEBUG=5  (Print all messages of level 5 or lower)
-// LIBOFFLOAD_DEBUG=init (Print messages of type "init" of level 1 or lower)
-// LIBOFFLOAD_DEBUG=init:3,mapping:2 (Print messages of type "init" of level 3
-//                                   or lower and messages of type "mapping" of
-//                                   level 2 or lower)
-// LIBOFFLOAD_DEBUG=omptarget:4, init (Print messages from component "omptarget"
-//                                   of level 4 or lower and messages of type
-//                                   "init" of level 1 or lower)
-//
-// For very specific cases where more control is needed, use ODBG_STREAM or
-// ODBG_BASE. See below for details.
-
 namespace llvm::offload::debug {
 
 #ifdef OMPTARGET_DEBUG
@@ -489,15 +428,39 @@ static inline raw_ostream &operator<<(raw_ostream &Os,
   ODBG_STREAM(llvm::offload::debug::dbgs(), Type, Level)
 #define ODBG_SELECT(Type, Level, NArgs, ...) ODBG_##NArgs
 
+// Print a debug message of a certain type and verbosity level. If no type
+// or level is provided, "default" and "1" are assumed respectively.
+// Usage examples:
+// ODBG("type1", 2) << "This is a level 2 message of type1";
+// ODBG("Init") << "This is a default level of the init type";
+// ODBG() << "This is a level 1 message of the default type";
+// ODBG("Init", 3) << NumDevices << " were initialized";
+// ODBG("Kernel") << "Launching " << KernelName << " on device " << DeviceId;
+#define ODBG(...) ODBG_SELECT(__VA_ARGS__ __VA_OPT__(, ) 2, 1, 0)(__VA_ARGS__)
+
+// Filter the next elements in the debug stream if the current debug level is
+// lower than  specified level. Example:
+// ODBG("Mapping", 2) << "level 2 info "
+//   << ODBG_IF_LEVEL(3) << " level 3 info" << Arg
+//   << ODBG_IF_LEVEL(4) << " level 4 info" << &Arg
+//   << ODBG_RESET_LEVEL() << " more level 2 info";
 #define ODBG_IF_LEVEL(Level)                                                   \
   static_cast<llvm::offload::debug::odbg_ostream::IfLevel>(Level)
+
+// Filter the next elements in the debug stream if the current debug level is
+// not exactly the specified level. Example:
+// ODBG() << "Starting computation "
+//   << ODBG_ONLY_LEVEL(1) << "on a device"
+//   << ODBG_ONLY_LEVEL(2) << "and mapping data on device" << DeviceId;
+//   << ODBG_ONLY_LEVEL(3) << dumpDetailedMappingInfo(DeviceId);
 #define ODBG_ONLY_LEVEL(Level)                                                 \
   static_cast<llvm::offload::debug::odbg_ostream::OnlyLevel>(Level)
+
+// Reset the level back to the original level after ODBG_IF_LEVEL or
+// ODBG_ONLY_LEVEL have been used
 #define ODBG_RESET_LEVEL()                                                     \
   static_cast<llvm::offload::debug::odbg_ostream::IfLevel>(0)
 
-#define ODBG(...) ODBG_SELECT(__VA_ARGS__ __VA_OPT__(, ) 2, 1, 0)(__VA_ARGS__)
-
 #else
 
 #define ODBG_NULL                                                              \



More information about the llvm-commits mailing list