483 lines · c
1//===-- Shared/Debug.h - Target independent OpenMP target RTL -- C++ ------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8//9// Routines used to provide debug messages and information from libomptarget10// and plugin RTLs to the user.11//12// Each plugin RTL and libomptarget define TARGET_NAME and DEBUG_PREFIX for use13// when sending messages to the user. These indicate which RTL sent the message14//15// Debug and information messages are controlled by the environment variables16// LIBOMPTARGET_DEBUG and LIBOMPTARGET_INFO which is set upon initialization17// of libomptarget or the plugin RTL.18//19// To printf a pointer in hex with a fixed width of 16 digits and a leading 0x,20// use printf("ptr=" DPxMOD "...\n", DPxPTR(ptr));21//22// DPxMOD expands to:23// "0x%0*" PRIxPTR24// where PRIxPTR expands to an appropriate modifier for the type uintptr_t on a25// specific platform, e.g. "lu" if uintptr_t is typedef'd as unsigned long:26// "0x%0*lu"27//28// Ultimately, the whole statement expands to:29// printf("ptr=0x%0*lu...\n", // the 0* modifier expects an extra argument30// // specifying the width of the output31// (int)(2*sizeof(uintptr_t)), // the extra argument specifying the width32// // 8 digits for 32bit systems33// // 16 digits for 64bit34// (uintptr_t) ptr);35//36//===----------------------------------------------------------------------===//37 38#ifndef OMPTARGET_SHARED_DEBUG_H39#define OMPTARGET_SHARED_DEBUG_H40 41#include <atomic>42#include <mutex>43#include <string>44 45#include "llvm/ADT/StringExtras.h"46#include "llvm/Support/circular_raw_ostream.h"47 48/// 32-Bit field data attributes controlling information presented to the user.49enum OpenMPInfoType : uint32_t {50 // Print data arguments and attributes upon entering an OpenMP device kernel.51 OMP_INFOTYPE_KERNEL_ARGS = 0x0001,52 // Indicate when an address already exists in the device mapping table.53 OMP_INFOTYPE_MAPPING_EXISTS = 0x0002,54 // Dump the contents of the device pointer map at kernel exit or failure.55 OMP_INFOTYPE_DUMP_TABLE = 0x0004,56 // Indicate when an address is added to the device mapping table.57 OMP_INFOTYPE_MAPPING_CHANGED = 0x0008,58 // Print kernel information from target device plugins.59 OMP_INFOTYPE_PLUGIN_KERNEL = 0x0010,60 // Print whenever data is transferred to the device61 OMP_INFOTYPE_DATA_TRANSFER = 0x0020,62 // Print whenever data does not have a viable device counterpart.63 OMP_INFOTYPE_EMPTY_MAPPING = 0x0040,64 // Enable every flag.65 OMP_INFOTYPE_ALL = 0xffffffff,66};67 68inline std::atomic<uint32_t> &getInfoLevelInternal() {69 static std::atomic<uint32_t> InfoLevel;70 static std::once_flag Flag{};71 std::call_once(Flag, []() {72 if (char *EnvStr = getenv("LIBOMPTARGET_INFO"))73 InfoLevel.store(std::stoi(EnvStr));74 });75 76 return InfoLevel;77}78 79inline uint32_t getInfoLevel() { return getInfoLevelInternal().load(); }80 81inline uint32_t getDebugLevel() {82 static uint32_t DebugLevel = 0;83 static std::once_flag Flag{};84 std::call_once(Flag, []() {85 if (char *EnvStr = getenv("LIBOMPTARGET_DEBUG"))86 DebugLevel = std::stoi(EnvStr);87 });88 89 return DebugLevel;90}91 92#undef USED93#undef GCC_VERSION94 95#ifndef __STDC_FORMAT_MACROS96#define __STDC_FORMAT_MACROS97#endif98#include <inttypes.h>99#undef __STDC_FORMAT_MACROS100 101#define DPxMOD "0x%0*" PRIxPTR102#define DPxPTR(ptr) ((int)(2 * sizeof(uintptr_t))), ((uintptr_t)(ptr))103#define GETNAME2(name) #name104#define GETNAME(name) GETNAME2(name)105 106/// Print a generic message string from libomptarget or a plugin RTL107#define MESSAGE0(_str) \108 do { \109 fprintf(stderr, GETNAME(TARGET_NAME) " message: %s\n", _str); \110 } while (0)111 112/// Print a printf formatting string message from libomptarget or a plugin RTL113#define MESSAGE(_str, ...) \114 do { \115 fprintf(stderr, GETNAME(TARGET_NAME) " message: " _str "\n", __VA_ARGS__); \116 } while (0)117 118/// Print fatal error message with an error string and error identifier119#define FATAL_MESSAGE0(_num, _str) \120 do { \121 fprintf(stderr, GETNAME(TARGET_NAME) " fatal error %d: %s\n", (int)_num, \122 _str); \123 abort(); \124 } while (0)125 126/// Print fatal error message with a printf string and error identifier127#define FATAL_MESSAGE(_num, _str, ...) \128 do { \129 fprintf(stderr, GETNAME(TARGET_NAME) " fatal error %d: " _str "\n", \130 (int)_num, __VA_ARGS__); \131 abort(); \132 } while (0)133 134/// Print a generic error string from libomptarget or a plugin RTL135#define FAILURE_MESSAGE(...) \136 do { \137 fprintf(stderr, GETNAME(TARGET_NAME) " error: "); \138 fprintf(stderr, __VA_ARGS__); \139 } while (0)140 141/// Print a generic information string used if LIBOMPTARGET_INFO=1142#define INFO_MESSAGE(_num, ...) INFO_MESSAGE_TO(stderr, _num, __VA_ARGS__)143 144#define INFO_MESSAGE_TO(_stdDst, _num, ...) \145 do { \146 fprintf(_stdDst, GETNAME(TARGET_NAME) " device %d info: ", (int)_num); \147 fprintf(_stdDst, __VA_ARGS__); \148 } while (0)149 150// Debugging messages151#ifdef OMPTARGET_DEBUG152#include <stdio.h>153 154#define DEBUGP(prefix, ...) \155 { \156 fprintf(stderr, "%s --> ", prefix); \157 fprintf(stderr, __VA_ARGS__); \158 }159 160/// Emit a message for debugging161#define DP(...) \162 do { \163 if (getDebugLevel() > 0) { \164 DEBUGP(DEBUG_PREFIX, __VA_ARGS__); \165 } \166 } while (false)167 168/// Emit a message for debugging or failure if debugging is disabled169#define REPORT(...) \170 do { \171 if (getDebugLevel() > 0) { \172 DP(__VA_ARGS__); \173 } else { \174 FAILURE_MESSAGE(__VA_ARGS__); \175 } \176 } while (false)177#else178#define DEBUGP(prefix, ...) \179 {}180#define DP(...) \181 {}182#define REPORT(...) FAILURE_MESSAGE(__VA_ARGS__);183#endif // OMPTARGET_DEBUG184 185/// Emit a message giving the user extra information about the runtime if186#define INFO(_flags, _id, ...) \187 do { \188 if (getDebugLevel() > 0) { \189 DEBUGP(DEBUG_PREFIX, __VA_ARGS__); \190 } else if (getInfoLevel() & _flags) { \191 INFO_MESSAGE(_id, __VA_ARGS__); \192 } \193 } while (false)194 195#define DUMP_INFO(toStdOut, _flags, _id, ...) \196 do { \197 if (toStdOut) { \198 INFO_MESSAGE_TO(stdout, _id, __VA_ARGS__); \199 } else { \200 INFO(_flags, _id, __VA_ARGS__); \201 } \202 } while (false)203 204namespace llvm::offload::debug {205 206#ifdef OMPTARGET_DEBUG207 208struct DebugFilter {209 StringRef Type;210 uint32_t Level;211};212 213struct DebugSettings {214 bool Enabled = false;215 uint32_t DefaultLevel = 1;216 llvm::SmallVector<DebugFilter> Filters;217};218 219/// dbgs - Return a circular-buffered debug stream.220[[maybe_unused]] static llvm::raw_ostream &dbgs() {221 // Do one-time initialization in a thread-safe way.222 static struct dbgstream {223 llvm::circular_raw_ostream strm;224 225 dbgstream() : strm(llvm::errs(), "*** Debug Log Output ***\n", 0) {}226 } thestrm;227 228 return thestrm.strm;229}230 231[[maybe_unused]] static DebugFilter parseDebugFilter(StringRef Filter) {232 size_t Pos = Filter.find(':');233 if (Pos == StringRef::npos)234 return {Filter, 1};235 236 StringRef Type = Filter.slice(0, Pos);237 uint32_t Level = 1;238 if (Filter.slice(Pos + 1, Filter.size()).getAsInteger(10, Level))239 Level = 1;240 241 return {Type, Level};242}243 244[[maybe_unused]] static DebugSettings &getDebugSettings() {245 static DebugSettings Settings;246 static std::once_flag Flag{};247 std::call_once(Flag, []() {248 // Eventually, we probably should allow the upper layers to set249 // debug settings directly according to their own env var or250 // other methods.251 // For now, mantain compatibility with existing libomptarget env var252 // and add a liboffload independent one.253 char *Env = getenv("LIBOMPTARGET_DEBUG");254 if (!Env) {255 Env = getenv("LIBOFFLOAD_DEBUG");256 if (!Env)257 return;258 }259 260 StringRef EnvRef(Env);261 if (EnvRef == "0")262 return;263 264 Settings.Enabled = true;265 if (EnvRef.equals_insensitive("all"))266 return;267 268 if (!EnvRef.getAsInteger(10, Settings.DefaultLevel))269 return;270 271 Settings.DefaultLevel = 1;272 273 for (auto &FilterSpec : llvm::split(EnvRef, ',')) {274 if (FilterSpec.empty())275 continue;276 Settings.Filters.push_back(parseDebugFilter(FilterSpec));277 }278 });279 280 return Settings;281}282 283inline bool isDebugEnabled() { return getDebugSettings().Enabled; }284 285[[maybe_unused]] static bool286shouldPrintDebug(const char *Component, const char *Type, uint32_t &Level) {287 const auto &Settings = getDebugSettings();288 if (!Settings.Enabled)289 return false;290 291 if (Settings.Filters.empty()) {292 if (Level <= Settings.DefaultLevel) {293 Level = Settings.DefaultLevel;294 return true;295 }296 return false;297 }298 299 for (const auto &DT : Settings.Filters) {300 if (DT.Level < Level)301 continue;302 if (DT.Type.equals_insensitive(Type) ||303 DT.Type.equals_insensitive(Component)) {304 Level = DT.Level;305 return true;306 }307 }308 309 return false;310}311 312/// A raw_ostream that tracks `\n` and print the prefix after each313/// newline. Based on raw_ldbg_ostream from Support/DebugLog.h314class LLVM_ABI odbg_ostream final : public raw_ostream {315public:316 enum IfLevel : uint32_t;317 enum OnlyLevel : uint32_t;318 319private:320 std::string Prefix;321 raw_ostream &Os;322 uint32_t BaseLevel;323 bool ShouldPrefixNextString;324 bool ShouldEmitNewLineOnDestruction;325 326 /// If the stream is muted, writes to it are ignored327 bool Muted = false;328 329 /// Split the line on newlines and insert the prefix before each330 /// newline. Forward everything to the underlying stream.331 void write_impl(const char *Ptr, size_t Size) final {332 if (Muted)333 return;334 335 auto Str = StringRef(Ptr, Size);336 auto Eol = Str.find('\n');337 // Handle `\n` occurring in the string, ensure to print the prefix at the338 // beginning of each line.339 while (Eol != StringRef::npos) {340 // Take the line up to the newline (including the newline).341 StringRef Line = Str.take_front(Eol + 1);342 if (!Line.empty())343 writeWithPrefix(Line);344 // We printed a newline, record here to print a prefix.345 ShouldPrefixNextString = true;346 Str = Str.drop_front(Eol + 1);347 Eol = Str.find('\n');348 }349 if (!Str.empty())350 writeWithPrefix(Str);351 }352 void emitPrefix() { Os.write(Prefix.c_str(), Prefix.size()); }353 void writeWithPrefix(StringRef Str) {354 if (ShouldPrefixNextString) {355 emitPrefix();356 ShouldPrefixNextString = false;357 }358 Os.write(Str.data(), Str.size());359 }360 361public:362 explicit odbg_ostream(std::string Prefix, raw_ostream &Os, uint32_t BaseLevel,363 bool ShouldPrefixNextString = true,364 bool ShouldEmitNewLineOnDestruction = false)365 : Prefix(std::move(Prefix)), Os(Os), BaseLevel(BaseLevel),366 ShouldPrefixNextString(ShouldPrefixNextString),367 ShouldEmitNewLineOnDestruction(ShouldEmitNewLineOnDestruction) {368 SetUnbuffered();369 }370 ~odbg_ostream() final {371 if (ShouldEmitNewLineOnDestruction)372 Os << '\n';373 }374 375 /// Forward the current_pos method to the underlying stream.376 uint64_t current_pos() const final { return Os.tell(); }377 378 /// Some of the `<<` operators expect an lvalue, so we trick the type379 /// system.380 odbg_ostream &asLvalue() { return *this; }381 382 void shouldMute(const IfLevel Filter) { Muted = Filter > BaseLevel; }383 void shouldMute(const OnlyLevel Filter) { Muted = BaseLevel != Filter; }384};385 386/// Compute the prefix for the debug log in the form of:387/// "Component --> "388[[maybe_unused]] static std::string computePrefix(StringRef Component,389 StringRef DebugType) {390 std::string Prefix;391 raw_string_ostream OsPrefix(Prefix);392 OsPrefix << Component << " --> ";393 return OsPrefix.str();394}395 396static inline raw_ostream &operator<<(raw_ostream &Os,397 const odbg_ostream::IfLevel Filter) {398 odbg_ostream &Dbg = static_cast<odbg_ostream &>(Os);399 Dbg.shouldMute(Filter);400 return Dbg;401}402 403static inline raw_ostream &operator<<(raw_ostream &Os,404 const odbg_ostream::OnlyLevel Filter) {405 odbg_ostream &Dbg = static_cast<odbg_ostream &>(Os);406 Dbg.shouldMute(Filter);407 return Dbg;408}409 410#define ODBG_BASE(Stream, Component, Prefix, Type, Level) \411 for (uint32_t RealLevel = (Level), \412 _c = llvm::offload::debug::isDebugEnabled() && \413 llvm::offload::debug::shouldPrintDebug( \414 (Component), (Type), RealLevel); \415 _c; _c = 0) \416 ::llvm::offload::debug::odbg_ostream{ \417 ::llvm::offload::debug::computePrefix((Prefix), (Type)), (Stream), \418 RealLevel, /*ShouldPrefixNextString=*/true, \419 /*ShouldEmitNewLineOnDestruction=*/true} \420 .asLvalue()421 422#define ODBG_STREAM(Stream, Type, Level) \423 ODBG_BASE(Stream, GETNAME(TARGET_NAME), DEBUG_PREFIX, Type, Level)424 425#define ODBG_0() ODBG_2("default", 1)426#define ODBG_1(Type) ODBG_2(Type, 1)427#define ODBG_2(Type, Level) \428 ODBG_STREAM(llvm::offload::debug::dbgs(), Type, Level)429#define ODBG_SELECT(Type, Level, NArgs, ...) ODBG_##NArgs430 431// Print a debug message of a certain type and verbosity level. If no type432// or level is provided, "default" and "1" are assumed respectively.433// Usage examples:434// ODBG("type1", 2) << "This is a level 2 message of type1";435// ODBG("Init") << "This is a default level of the init type";436// ODBG() << "This is a level 1 message of the default type";437// ODBG("Init", 3) << NumDevices << " were initialized";438// ODBG("Kernel") << "Launching " << KernelName << " on device " << DeviceId;439#define ODBG(...) ODBG_SELECT(__VA_ARGS__ __VA_OPT__(, ) 2, 1, 0)(__VA_ARGS__)440 441// Filter the next elements in the debug stream if the current debug level is442// lower than specified level. Example:443// ODBG("Mapping", 2) << "level 2 info "444// << ODBG_IF_LEVEL(3) << " level 3 info" << Arg445// << ODBG_IF_LEVEL(4) << " level 4 info" << &Arg446// << ODBG_RESET_LEVEL() << " more level 2 info";447#define ODBG_IF_LEVEL(Level) \448 static_cast<llvm::offload::debug::odbg_ostream::IfLevel>(Level)449 450// Filter the next elements in the debug stream if the current debug level is451// not exactly the specified level. Example:452// ODBG() << "Starting computation "453// << ODBG_ONLY_LEVEL(1) << "on a device"454// << ODBG_ONLY_LEVEL(2) << "and mapping data on device" << DeviceId;455// << ODBG_ONLY_LEVEL(3) << dumpDetailedMappingInfo(DeviceId);456#define ODBG_ONLY_LEVEL(Level) \457 static_cast<llvm::offload::debug::odbg_ostream::OnlyLevel>(Level)458 459// Reset the level back to the original level after ODBG_IF_LEVEL or460// ODBG_ONLY_LEVEL have been used461#define ODBG_RESET_LEVEL() \462 static_cast<llvm::offload::debug::odbg_ostream::IfLevel>(0)463 464#else465 466#define ODBG_NULL \467 for (bool _c = false; _c; _c = false) \468 ::llvm::nulls()469 470// Don't print anything if debugging is disabled471#define ODBG_BASE(Stream, Component, Prefix, Type, Level) ODBG_NULL472#define ODBG_STREAM(Stream, Type, Level) ODBG_NULL473#define ODBG_IF_LEVEL(Level) 0474#define ODBG_ONLY_LEVEL(Level) 0475#define ODBG_RESET_LEVEL() 0476#define ODBG(...) ODBG_NULL477 478#endif479 480} // namespace llvm::offload::debug481 482#endif // OMPTARGET_SHARED_DEBUG_H483