Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
197 changes: 197 additions & 0 deletions offload/include/Shared/Debug.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -198,4 +200,199 @@ 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 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.

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, []() {
// 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");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we have environment variable helpers somewhere, can probably use those instead of raw getenv.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably the only place where they can't be used, because those helpers also have debug messages and it would create a circular dependence.

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
2 changes: 1 addition & 1 deletion offload/libomptarget/OffloadRTL.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
2 changes: 1 addition & 1 deletion offload/libomptarget/PluginManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) \
Expand Down
2 changes: 2 additions & 0 deletions offload/plugins-nextgen/host/src/rtl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a huge fan of this level concept, it feels totally arbitrary and just providing specific kinds gives people the tools required to recreate it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that you can kind of recreate it with kinds, but not having levels is not as intuitive or easy to use as a user.

Say you have 3 levels for Init. Of course you can create "Init1", "Init2", "Init3". But now you need to list of all of them get the behavior as just "Init:3". This of course because more verbose with more Kinds and more Levels. Furthermore, there's no easy way to say all "levels" of a Kind unless we want to support more complicated comparisons like "Init*" (which I personally don't think we should) but supporting syntax like "Init:*" is trivial.

IMO, yes, levels are not as fundamentally useful as Kinds (there's no dispute to that) but the complexity/benefit the provide is worth it. Furthemore, they're optional so it's not like we need to start specifying them everywhere but just where it might make sense to provide different levels of detail for the same Kind of messages.

"GenELF64 plugin detected" << NUM_DEVICES << " devices\n");

return NUM_DEVICES;
}
Expand Down
Loading