//===-- Shared/Debug.h - Target independent OpenMP target RTL -- C++ ------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Routines used to provide debug messages and information from libomptarget
// and plugin RTLs to the user.
//
// Each plugin RTL and libomptarget define TARGET_NAME and DEBUG_PREFIX for use
// when sending messages to the user. These indicate which RTL sent the message
//
// Debug and information messages are controlled by the environment variables
// LIBOMPTARGET_DEBUG and LIBOMPTARGET_INFO which is set upon initialization
// of libomptarget or the plugin RTL.
//
// To printf a pointer in hex with a fixed width of 16 digits and a leading 0x,
// use printf("ptr=" DPxMOD "...\n", DPxPTR(ptr));
//
// DPxMOD expands to:
//   "0x%0*" PRIxPTR
// where PRIxPTR expands to an appropriate modifier for the type uintptr_t on a
// specific platform, e.g. "lu" if uintptr_t is typedef'd as unsigned long:
//   "0x%0*lu"
//
// Ultimately, the whole statement expands to:
//   printf("ptr=0x%0*lu...\n",  // the 0* modifier expects an extra argument
//                               // specifying the width of the output
//   (int)(2*sizeof(uintptr_t)), // the extra argument specifying the width
//                               // 8 digits for 32bit systems
//                               // 16 digits for 64bit
//   (uintptr_t) ptr);
//
//===----------------------------------------------------------------------===//

#ifndef OMPTARGET_SHARED_DEBUG_H
#define OMPTARGET_SHARED_DEBUG_H

#include <atomic>
#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.
enum OpenMPInfoType : uint32_t {
  // Print data arguments and attributes upon entering an OpenMP device kernel.
  OMP_INFOTYPE_KERNEL_ARGS = 0x0001,
  // Indicate when an address already exists in the device mapping table.
  OMP_INFOTYPE_MAPPING_EXISTS = 0x0002,
  // Dump the contents of the device pointer map at kernel exit or failure.
  OMP_INFOTYPE_DUMP_TABLE = 0x0004,
  // Indicate when an address is added to the device mapping table.
  OMP_INFOTYPE_MAPPING_CHANGED = 0x0008,
  // Print kernel information from target device plugins.
  OMP_INFOTYPE_PLUGIN_KERNEL = 0x0010,
  // Print whenever data is transferred to the device
  OMP_INFOTYPE_DATA_TRANSFER = 0x0020,
  // Print whenever data does not have a viable device counterpart.
  OMP_INFOTYPE_EMPTY_MAPPING = 0x0040,
  // Enable every flag.
  OMP_INFOTYPE_ALL = 0xffffffff,
};

inline std::atomic<uint32_t> &getInfoLevelInternal() {
  static std::atomic<uint32_t> InfoLevel;
  static std::once_flag Flag{};
  std::call_once(Flag, []() {
    if (char *EnvStr = getenv("LIBOMPTARGET_INFO"))
      InfoLevel.store(std::stoi(EnvStr));
  });

  return InfoLevel;
}

inline uint32_t getInfoLevel() { return getInfoLevelInternal().load(); }

inline uint32_t getDebugLevel() {
  static uint32_t DebugLevel = 0;
  static std::once_flag Flag{};
  std::call_once(Flag, []() {
    if (char *EnvStr = getenv("LIBOMPTARGET_DEBUG"))
      DebugLevel = std::stoi(EnvStr);
  });

  return DebugLevel;
}

#undef USED
#undef GCC_VERSION

#ifndef __STDC_FORMAT_MACROS
#define __STDC_FORMAT_MACROS
#endif
#include <inttypes.h>
#undef __STDC_FORMAT_MACROS

#define DPxMOD "0x%0*" PRIxPTR
#define DPxPTR(ptr) ((int)(2 * sizeof(uintptr_t))), ((uintptr_t)(ptr))
#define GETNAME2(name) #name
#define GETNAME(name) GETNAME2(name)

/// Print a generic message string from libomptarget or a plugin RTL
#define MESSAGE0(_str)                                                         \
  do {                                                                         \
    fprintf(stderr, GETNAME(TARGET_NAME) " message: %s\n", _str);              \
  } while (0)

/// Print a printf formatting string message from libomptarget or a plugin RTL
#define MESSAGE(_str, ...)                                                     \
  do {                                                                         \
    fprintf(stderr, GETNAME(TARGET_NAME) " message: " _str "\n", __VA_ARGS__); \
  } while (0)

/// Print fatal error message with an error string and error identifier
#define FATAL_MESSAGE0(_num, _str)                                             \
  do {                                                                         \
    fprintf(stderr, GETNAME(TARGET_NAME) " fatal error %d: %s\n", (int)_num,   \
            _str);                                                             \
    abort();                                                                   \
  } while (0)

/// Print fatal error message with a printf string and error identifier
#define FATAL_MESSAGE(_num, _str, ...)                                         \
  do {                                                                         \
    fprintf(stderr, GETNAME(TARGET_NAME) " fatal error %d: " _str "\n",        \
            (int)_num, __VA_ARGS__);                                           \
    abort();                                                                   \
  } while (0)

/// Print a generic error string from libomptarget or a plugin RTL
#define FAILURE_MESSAGE(...)                                                   \
  do {                                                                         \
    fprintf(stderr, GETNAME(TARGET_NAME) " error: ");                          \
    fprintf(stderr, __VA_ARGS__);                                              \
  } while (0)

/// Print a generic information string used if LIBOMPTARGET_INFO=1
#define INFO_MESSAGE(_num, ...) INFO_MESSAGE_TO(stderr, _num, __VA_ARGS__)

#define INFO_MESSAGE_TO(_stdDst, _num, ...)                                    \
  do {                                                                         \
    fprintf(_stdDst, GETNAME(TARGET_NAME) " device %d info: ", (int)_num);     \
    fprintf(_stdDst, __VA_ARGS__);                                             \
  } while (0)

// Debugging messages
#ifdef OMPTARGET_DEBUG
#include <stdio.h>

#define DEBUGP(prefix, ...)                                                    \
  {                                                                            \
    fprintf(stderr, "%s --> ", prefix);                                        \
    fprintf(stderr, __VA_ARGS__);                                              \
  }

/// Emit a message for debugging
#define DP(...)                                                                \
  do {                                                                         \
    if (getDebugLevel() > 0) {                                                 \
      DEBUGP(DEBUG_PREFIX, __VA_ARGS__);                                       \
    }                                                                          \
  } while (false)

/// Emit a message for debugging or failure if debugging is disabled
#define REPORT(...)                                                            \
  do {                                                                         \
    if (getDebugLevel() > 0) {                                                 \
      DP(__VA_ARGS__);                                                         \
    } else {                                                                   \
      FAILURE_MESSAGE(__VA_ARGS__);                                            \
    }                                                                          \
  } while (false)
#else
#define DEBUGP(prefix, ...)                                                    \
  {}
#define DP(...)                                                                \
  {}
#define REPORT(...) FAILURE_MESSAGE(__VA_ARGS__);
#endif // OMPTARGET_DEBUG

/// Emit a message giving the user extra information about the runtime if
#define INFO(_flags, _id, ...)                                                 \
  do {                                                                         \
    if (getDebugLevel() > 0) {                                                 \
      DEBUGP(DEBUG_PREFIX, __VA_ARGS__);                                       \
    } else if (getInfoLevel() & _flags) {                                      \
      INFO_MESSAGE(_id, __VA_ARGS__);                                          \
    }                                                                          \
  } while (false)

#define DUMP_INFO(toStdOut, _flags, _id, ...)                                  \
  do {                                                                         \
    if (toStdOut) {                                                            \
      INFO_MESSAGE_TO(stdout, _id, __VA_ARGS__);                               \
    } else {                                                                   \
      INFO(_flags, _id, __VA_ARGS__);                                          \
    }                                                                          \
  } while (false)

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.
[[maybe_unused]] static 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;
}

[[maybe_unused]] static 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};
}

[[maybe_unused]] static 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");
    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;

    for (auto &FilterSpec : llvm::split(EnvRef, ',')) {
      if (FilterSpec.empty())
        continue;
      Settings.Filters.push_back(parseDebugFilter(FilterSpec));
    }
  });

  return Settings;
}

inline bool isDebugEnabled() { return getDebugSettings().Enabled; }

[[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()) {
    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) ||
        DT.Type.equals_insensitive(Component)) {
      Level = DT.Level;
      return true;
    }
  }

  return false;
}

/// 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';
  }

  /// Forward the current_pos method to the underlying stream.
  uint64_t current_pos() const final { return Os.tell(); }

  /// Some of the `<<` operators expect an lvalue, so we trick the type
  /// system.
  odbg_ostream &asLvalue() { return *this; }

  void shouldMute(const IfLevel Filter) { Muted = Filter > BaseLevel; }
  void shouldMute(const OnlyLevel Filter) { Muted = BaseLevel != Filter; }
};

/// 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();
}

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;
}

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;
}

#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

// 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)

#else

#define ODBG_NULL                                                              \
  for (bool _c = false; _c; _c = false)                                        \
  ::llvm::nulls()

// Don't print anything if debugging is disabled
#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

} // namespace llvm::offload::debug

#endif // OMPTARGET_SHARED_DEBUG_H