/*
 * Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
 *
 * SPDX-License-Identifier: MIT
 */

#if !defined(_WIN32) && !defined(__CYGWIN__)
#include <unistd.h>
#include <sys/syscall.h>
#include "os/os.hpp"
#include "thread/thread.hpp"

#include <iostream>
#include <stdarg.h>

#include <sys/mman.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/sysinfo.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <dlfcn.h>
#include <signal.h>
#include <cxxabi.h>

#include <sys/prctl.h>
#include <sys/resource.h>

#include <link.h>
#include <time.h>
#ifndef DT_GNU_HASH
#define DT_GNU_HASH 0x6ffffef5
#endif  // DT_GNU_HASH
#include <atomic>
#include <vector>
#include <string>
#include <sstream>
#include <cstring>  // for strncmp
#include <cstdlib>
#include <cstdio>  // for tempnam
#include <limits.h>
#include <memory>
#include <algorithm>
#include <mutex>
#include <fstream>

namespace amd {

namespace {
struct ThreadAffinityState {
  std::vector<uint64_t> runtime_set_affinity_mask;
  bool runtime_set_affinity_mask_valid = false;
  uint32_t runtime_set_affinity_node = static_cast<uint32_t>(-1);
  std::vector<uint64_t> original_affinity_mask;
  bool original_affinity_mask_valid = false;
};

ThreadAffinityState*& GetThreadAffinityStatePtr() {
  thread_local ThreadAffinityState* state = nullptr;
  return state;
}

ThreadAffinityState& GetThreadAffinityState() {
  // Runtime teardown can run after non-trivial TLS destructors at process exit.
  // Keep only a trivial TLS pointer so affinity reset never observes destroyed vector storage.
  ThreadAffinityState*& state = GetThreadAffinityStatePtr();
  if (state == nullptr) {
    state = new ThreadAffinityState();
  }
  return *state;
}

void ReleaseThreadAffinityState() {
  ThreadAffinityState*& state = GetThreadAffinityStatePtr();
  delete state;
  state = nullptr;
}

bool GetCurrentThreadAffinity(std::vector<uint64_t>* affinity) {
  const uint32_t size = CPU_ALLOC_SIZE(amd::Os::processorCount());
  affinity->assign((size + sizeof(uint64_t) - 1) / sizeof(uint64_t), 0);
  if (syscall(__NR_sched_getaffinity, 0, size, affinity->data()) < 0) {
    ClPrint(amd::LOG_DEBUG, amd::LOG_RESOURCE,
            "syscall(__NR_sched_getaffinity, size=%u) failed", size);
    return false;
  }
  return true;
}

bool SetCurrentThreadAffinity(const std::vector<uint64_t>& affinity) {
  const uint32_t size = static_cast<uint32_t>(affinity.size() * sizeof(uint64_t));
  if (syscall(__NR_sched_setaffinity, 0, size, affinity.data()) < 0) {
    ClPrint(amd::LOG_DEBUG, amd::LOG_RESOURCE,
            "syscall(__NR_sched_setaffinity, size=%u) failed", size);
    return false;
  }
  return true;
}

bool IsCpuSet(const std::vector<uint64_t>& affinity, uint32_t cpu) {
  const uint32_t index = cpu / numa::kBitsPerUInt64;
  return index < affinity.size() && ((affinity[index] >> (cpu % numa::kBitsPerUInt64)) & 1);
}

bool IsUnrestrictedAffinity(const std::vector<uint64_t>& affinity) {
  for (uint32_t cpu = 0; cpu < static_cast<uint32_t>(amd::Os::processorCount()); ++cpu) {
    if (!IsCpuSet(affinity, cpu)) {
      return false;
    }
  }
  return true;
}

bool IsSameAffinity(const std::vector<uint64_t>& lhs, const std::vector<uint64_t>& rhs) {
  const size_t size = std::max(lhs.size(), rhs.size());
  for (size_t i = 0; i < size; ++i) {
    const uint64_t l = (i < lhs.size()) ? lhs[i] : 0;
    const uint64_t r = (i < rhs.size()) ? rhs[i] : 0;
    if (l != r) {
      return false;
    }
  }
  return true;
}
}  // namespace

// Crash signal handling
static const int kCrashSignals[] = {SIGSEGV, SIGABRT, SIGBUS, SIGILL, SIGFPE};
static constexpr size_t kNumCrashSignals = sizeof(kCrashSignals) / sizeof(kCrashSignals[0]);
static struct sigaction oldCrashHandlers_[kNumCrashSignals];
static Os::CrashCallback crashCallback_ = nullptr;
static std::atomic<bool> crashHandlersInstalled_{false};

static void crashSignalHandler(int sig) {
  if (crashCallback_ != nullptr) {
    crashCallback_();
  }

  kill(getpid(), sig);
}

bool Os::installExceptionHandlers(CrashCallback callback) {
  if (crashHandlersInstalled_.exchange(true)) {
    return true;
  }

  crashCallback_ = callback;

  struct sigaction sa;
  sigemptyset(&sa.sa_mask);
  sa.sa_handler = crashSignalHandler;
  sa.sa_flags = SA_RESETHAND;

  for (size_t i = 0; i < kNumCrashSignals; ++i) {
    if (sigaction(kCrashSignals[i], &sa, &oldCrashHandlers_[i]) != 0) {
      return false;
    }
  }
  return true;
}

void Os::uninstallExceptionHandlers() {
  if (!crashHandlersInstalled_.load()) {
    return;
  }

  for (size_t i = 0; i < kNumCrashSignals; ++i) {
    sigaction(kCrashSignals[i], &oldCrashHandlers_[i], nullptr);
  }
  crashCallback_ = nullptr;
  crashHandlersInstalled_.store(false);
}

static struct sigaction oldSigAction;

static bool callOldSignalHandler(int sig, siginfo_t* info, void* ptr) {
  if (oldSigAction.sa_handler == SIG_DFL) {
    // no signal handler was previously installed.
    return false;
  } else if (oldSigAction.sa_handler != SIG_IGN) {
    if ((oldSigAction.sa_flags & SA_NODEFER) == 0) {
      sigaddset(&oldSigAction.sa_mask, sig);
    }

    void (*handler)(int) = oldSigAction.sa_handler;
    if (oldSigAction.sa_flags & SA_RESETHAND) {
      oldSigAction.sa_handler = SIG_DFL;
    }

    sigset_t savedSigSet;
    pthread_sigmask(SIG_SETMASK, &oldSigAction.sa_mask, &savedSigSet);

    if (oldSigAction.sa_flags & SA_SIGINFO) {
      oldSigAction.sa_sigaction(sig, info, ptr);
    } else {
      handler(sig);
    }

    pthread_sigmask(SIG_SETMASK, &savedSigSet, NULL);
  }

  return true;
}

static void divisionErrorHandler(int sig, siginfo_t* info, void* ptr) {
  assert(info != NULL && ptr != NULL && "just checking");
  ucontext_t* uc = (ucontext_t*)ptr;
  address insn;

#if defined(ATI_ARCH_X86)
  insn = (address)uc->uc_mcontext.gregs[LP64_SWITCH(REG_EIP, REG_RIP)];
#else
  assert(!"Unimplemented");
#endif

  if (Thread::current()->isWorkerThread()) {
    if (Os::skipIDIV(insn)) {
#if defined(ATI_ARCH_X86)
      uc->uc_mcontext.gregs[LP64_SWITCH(REG_EIP, REG_RIP)] = (greg_t)insn;
#else
      assert(!"Unimplemented");
#endif
      return;
    }
  }

  // Call the chained signal handler
  if (callOldSignalHandler(sig, info, ptr)) {
    return;
  }

  std::cerr << "Unhandled signal in divisionErrorHandler()" << std::endl;
  ::abort();
}

typedef int (*pthread_setaffinity_fn)(pthread_t, size_t, const cpu_set_t*);
static pthread_setaffinity_fn pthread_setaffinity_fptr;

static void init() __attribute__((constructor(101)));
static void init() { Os::init(); }
static cpu_set_t nativeMask_;

bool Os::installSigfpeHandler() {
  // Install a SIGFPE signal handler @todo: Chain the handlers
  struct sigaction sa;
  sigfillset(&sa.sa_mask);
  sa.sa_handler = SIG_DFL;
  sa.sa_sigaction = divisionErrorHandler;
  sa.sa_flags = SA_SIGINFO | SA_RESTART;

  if (sigaction(SIGFPE, &sa, &oldSigAction) != 0) {
    return false;
  }
  return true;
}

void Os::uninstallSigfpeHandler() {}

bool Os::init() {
  static bool initialized_ = false;

  // We could use pthread_once here:
  if (initialized_) {
    return true;
  }
  initialized_ = true;

  pageSize_ = (size_t)::sysconf(_SC_PAGESIZE);
  processorCount_ = ::sysconf(_SC_NPROCESSORS_CONF);

  pthread_getaffinity_np(pthread_self(), sizeof(cpu_set_t), &nativeMask_);
  pthread_setaffinity_fptr = (pthread_setaffinity_fn)dlsym(RTLD_NEXT, "pthread_setaffinity_np");

  return Thread::init();
}

static void __exit() __attribute__((destructor(101)));
static void __exit() { Os::tearDown(); }

void Os::tearDown() { Thread::tearDown(); }

void* Os::loadLibrary_(const char* filename) {
  return (*filename == '\0') ? NULL : ::dlopen(filename, RTLD_LAZY);
}

void Os::unloadLibrary(void* handle) { ::dlclose(handle); }

void* Os::getSymbol(void* handle, const char* name) { return ::dlsym(handle, name); }

static inline int memProtToOsProt(Os::MemProt prot) {
  switch (prot) {
    case Os::MEM_PROT_NONE:
      return PROT_NONE;
    case Os::MEM_PROT_READ:
      return PROT_READ;
    case Os::MEM_PROT_RW:
      return PROT_READ | PROT_WRITE;
    case Os::MEM_PROT_RWX:
      return PROT_READ | PROT_WRITE | PROT_EXEC;
    default:
      break;
  }
  ShouldNotReachHere();
  return -1;
}

address Os::reserveMemory(address start, size_t size, size_t alignment, MemProt prot) {
  size = alignUp(size, pageSize());
  // check for invalid input size
  if (size == 0) {
    return NULL;
  }
  alignment = std::max(pageSize(), alignUp(alignment, pageSize()));
  assert(isPowerOfTwo(alignment) && "not a power of 2");

  size_t requested = size + alignment - pageSize();
  address mem = (address)::mmap(start, requested, memProtToOsProt(prot),
                                MAP_PRIVATE | MAP_NORESERVE | MAP_ANONYMOUS, 0, 0);

  // check for out of memory
  if (mem == MAP_FAILED) return NULL;

  address aligned = alignUp(mem, alignment);

  // return the unused leading pages to the free state
  if (&aligned[0] != &mem[0]) {
    assert(&aligned[0] > &mem[0] && "check this code");
    if (::munmap(&mem[0], &aligned[0] - &mem[0]) != 0) {
      assert(!"::munmap failed");
    }
  }
  // return the unused trailing pages to the free state
  if (&aligned[size] != &mem[requested]) {
    assert(&aligned[size] < &mem[requested] && "check this code");
    if (::munmap(&aligned[size], &mem[requested] - &aligned[size]) != 0) {
      assert(!"::munmap failed");
    }
  }

  // Hint to enable THP for large host allocations which can help in performance gain
  constexpr size_t kLargePageSize = 2 * Mi;
  if (size >= kLargePageSize) {
    int status = madvise(aligned, size, MADV_HUGEPAGE);
    if (status) {
      ClPrint(amd::LOG_DEBUG, amd::LOG_CODE,
              "madvise with advice MADV_HUGEPAGE"
              " starting at address %p and page size 0x%zx, returned %d, errno: %s",
              aligned, size, status, strerror(errno));
    }
  }

  return aligned;
}

bool Os::releaseMemory(void* addr, size_t size) {
  assert(isMultipleOf(addr, pageSize()) && "not page aligned!");
  size = alignUp(size, pageSize());

  return 0 == ::munmap(addr, size);
}

bool Os::commitMemory(void* addr, size_t size, MemProt prot) {
  assert(isMultipleOf(addr, pageSize()) && "not page aligned!");
  size = alignUp(size, pageSize());

  return ::mmap(addr, size, memProtToOsProt(prot), MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS, -1,
                0) != MAP_FAILED;
}

bool Os::uncommitMemory(void* addr, size_t size) {
  assert(isMultipleOf(addr, pageSize()) && "not page aligned!");
  size = alignUp(size, pageSize());

  return ::mmap(addr, size, PROT_NONE, MAP_PRIVATE | MAP_FIXED | MAP_NORESERVE | MAP_ANONYMOUS, -1,
                0) != MAP_FAILED;
}

bool Os::protectMemory(void* addr, size_t size, MemProt prot) {
  assert(isMultipleOf(addr, pageSize()) && "not page aligned!");
  size = alignUp(size, pageSize());

  return 0 == ::mprotect(addr, size, memProtToOsProt(prot));
}

uint64_t Os::hostTotalPhysicalMemory() {
  static uint64_t totalPhys = 0;

  if (totalPhys != 0) {
    return totalPhys;
  }

  totalPhys = sysconf(_SC_PAGESIZE) * sysconf(_SC_PHYS_PAGES);
  return totalPhys;
}

void* Os::alignedMalloc(size_t size, size_t alignment) {
  void* ptr = NULL;
  if (0 == ::posix_memalign(&ptr, alignment, size)) {
    return ptr;
  }
  return NULL;
}

void Os::alignedFree(void* mem) { ::free(mem); }

void Os::currentStackInfo(address* base, size_t* size) {
  // There could be some issue trying to get the pthread_attr of
  // the primordial thread if the pthread library is not present
  // at load time (a binary loads the OpenCL/HIP app/runtime dynamically.
  // We should look into this... -laurent

  pthread_t self = ::pthread_self();

  pthread_attr_t threadAttr;
  if (0 != ::pthread_getattr_np(self, &threadAttr)) {
    fatal("pthread_getattr_np() failed");
  }

  if (0 != ::pthread_attr_getstack(&threadAttr, (void**)base, size)) {
    fatal("pthread_attr_getstack() failed");
  }
  *base += *size;

  ::pthread_attr_destroy(&threadAttr);

  assert(Os::currentStackPtr() >= *base - *size && Os::currentStackPtr() < *base &&
         "just checking");
}

void Os::setCurrentThreadName(const char* name) { ::prctl(PR_SET_NAME, name); }

void* Thread::entry(Thread* thread) {
  sigset_t set;

  sigfillset(&set);
  pthread_sigmask(SIG_BLOCK, &set, NULL);

  sigemptyset(&set);
  sigaddset(&set, SIGFPE);
  pthread_sigmask(SIG_UNBLOCK, &set, NULL);

  return thread->main();
}

bool Os::isThreadAlive(const Thread& thread) {
  return ::pthread_kill((pthread_t)thread.handle(), 0) == 0;
}

const void* Os::createOsThread(amd::Thread* thread) {
  pthread_attr_t threadAttr;
  ::pthread_attr_init(&threadAttr);

  if (thread->stackSize_ != 0) {
    size_t guardsize = 0;
    if (0 != ::pthread_attr_getguardsize(&threadAttr, &guardsize)) {
      fatal("pthread_attr_getguardsize() failed");
    }

    if (0 != ::pthread_attr_setstacksize(&threadAttr, thread->stackSize_ + guardsize)) {
      fatal("pthread_attr_setstacksize() failed");
    }
  }

  // We never plan the use join, so free the resources now.
  if (0 != ::pthread_attr_setdetachstate(&threadAttr, PTHREAD_CREATE_DETACHED)) {
    fatal("pthread_attr_setdetachstate() failed");
  }

  pthread_t handle = 0;
  if (0 != ::pthread_create(&handle, &threadAttr, (void* (*)(void*)) & Thread::entry, thread)) {
    thread->setState(Thread::FAILED);
    guarantee(false, "pthread_create() failed");
  }

  if (0 != ::pthread_attr_destroy(&threadAttr)) {
    fatal("pthread_attr_destroy() failed");
  };
  return reinterpret_cast<const void*>(handle);
}

void Os::yield() { ::sched_yield(); }

uint64_t Os::timeNanos() {
  struct timespec tp;
  ::clock_gettime(CLOCK_MONOTONIC, &tp);
  return (uint64_t)tp.tv_sec * (1000ULL * 1000ULL * 1000ULL) + (uint64_t)tp.tv_nsec;
}

uint64_t Os::timerResolutionNanos() {
  static uint64_t resolution = 0;
  if (resolution == 0) {
    struct timespec tp;
    ::clock_getres(CLOCK_MONOTONIC, &tp);
    resolution = (uint64_t)tp.tv_sec * (1000ULL * 1000ULL * 1000ULL) + (uint64_t)tp.tv_nsec;
  }
  return resolution;
}


const char* Os::libraryExtension() { return MACOS_SWITCH(".dylib", ".so"); }

const char* Os::libraryPrefix() { return "lib"; }

const char* Os::objectExtension() { return ".o"; }

char Os::fileSeparator() { return '/'; }

char Os::pathSeparator() { return ':'; }

bool Os::pathExists(const std::string& path) {
  struct stat st;
  if (stat(path.c_str(), &st) != 0) return false;
  return S_ISDIR(st.st_mode);
}

bool Os::createPath(const std::string& path) {
  mode_t mode = S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH;
  size_t pos = 0;
  while (true) {
    pos = path.find(fileSeparator(), pos);
    const std::string currPath = path.substr(0, pos);
    if (!currPath.empty() && !pathExists(currPath)) {
      int ret = mkdir(currPath.c_str(), mode);
      if (ret == -1) return false;
    }
    if (pos == std::string::npos) break;
    ++pos;
  }
  return true;
}

bool Os::removePath(const std::string& path) {
  size_t pos = std::string::npos;
  bool removed = false;
  while (true) {
    const std::string currPath = path.substr(0, pos);
    if (!currPath.empty()) {
      int ret = rmdir(currPath.c_str());
      if (ret == -1) return removed;
      removed = true;
    }
    if (pos == 0) break;
    pos = path.rfind(fileSeparator(), pos == std::string::npos ? pos : pos - 1);
    if (pos == std::string::npos) break;
  }
  return true;
}

int Os::printf(const char* fmt, ...) {
  va_list ap;

  va_start(ap, fmt);
  int len = ::vprintf(fmt, ap);
  va_end(ap);

  return len;
}

// Os::systemCall()
// ================
// Execute a program and return the program exitcode or -1 if there were problems.
// The input argument 'command' is expected to be a space separated string of
// command-line arguments with arguments containing spaces between double-quotes.
//
// In order to avoid duplication of memory, we use vfork()+exec(). vfork() has
// potiential security risks;
//
// In spite of these risks, the alternatives (system() or fork()) create resource
// issues when running conformance test_allocation which stretches the system
// memory to its limits. Thus we will accept this compromise under the condition
// that the runtime will soon remove any need to call out to external commands.
//
// Note that stdin/stdout/stderr of the command are sent to /dev/null.
//
int Os::systemCall(const std::string& command) {
#if 1
  size_t len = command.size();
  char* cmd = new char[len + 1];
  std::memcpy(cmd, command.c_str(), len);
  cmd[len] = 0;

  // Split the command into arguments. This is a very
  // simple parser that only takes care of quotes and
  // doesn't support escaping with back-slash. In
  // the future, Os::systemCall() will either
  // disappear or it will be replaced with an
  // argc/argv interface. This parser also assumes
  // that if an argument is quoted, the whole
  // argument starts and ends with a double-quote.
  bool inQuote = false;
  int argLength = 0;
  int n = 0;
  char* cp = cmd;
  while (*cp) {
    switch (static_cast<int>(*cp)) {
      case ' ':
        if (inQuote) {
          ++argLength;
        } else {
          *cp = '\0';
          argLength = 0;
        }
        break;
      case '"':
        if (inQuote) {
          inQuote = false;
          *cp = '\0';
        } else {
          inQuote = true;
          *cp = '\0';
          argLength = 1;
          ++n;
        }
        break;
      default:
        if (++argLength == 1) {
          ++n;
        }
        break;
    }
    ++cp;
  }

  char** argv = new char*[n + 1];
  int argc = 0;
  cp = cmd;
  do {
    while ('\0' == *cp) {
      ++cp;
    }
    argv[argc++] = cp;
    while ('\0' != *cp) {
      ++cp;
    }
  } while (argc < n);
  argv[argc] = NULL;

  int ret = -1;
  pid_t pid = vfork();
  if (0 == pid) {
    // Child. Redirect stdin/stdout/stderr to /dev/null
    int fdIn = open("/dev/null", O_RDONLY);
    int fdOut = open("/dev/null", O_WRONLY);
    if (0 <= fdIn || 0 <= fdOut) {
      dup2(fdIn, 0);
      dup2(fdOut, 1);
      dup2(fdOut, 2);

      // Execute the program
      execvp(argv[0], argv);
    }
    _exit(-1);
  } else if (0 > pid) {
    // Can't vfork
  } else {
    // Parent - wait for program to complete and get exit code.
    int exitCode;
    if (0 <= waitpid(pid, &exitCode, 0)) {
      ret = exitCode;
    }
  }
  delete[] argv;
  delete[] cmd;

  return ret;
#else
  return ::system(command.c_str());
#endif
}

std::string Os::getEnvironment(const std::string& name) {
  char* dstBuf;

  dstBuf = ::getenv(name.c_str());
  if (dstBuf == NULL) {
    return std::string("");
  }
  return std::string(dstBuf);
}

std::string Os::getTempPath() {
  std::string tempFolder = amd::Os::getEnvironment("TEMP");
  if (tempFolder.empty()) {
    tempFolder = amd::Os::getEnvironment("TMP");
  }

  if (tempFolder.empty()) {
    tempFolder = "/tmp";
    ;
  }
  return tempFolder;
}

std::string Os::getTempFileName() {
  static std::atomic_size_t counter(0);

  std::string tempPath = getTempPath();
  std::stringstream tempFileName;

  tempFileName << tempPath << "/OCL" << ::getpid() << 'T' << counter++;
  return tempFileName.str();
}

int Os::unlink(const std::string& path) { return ::unlink(path.c_str()); }
int Os::shm_unlink(const std::string& path) { return ::shm_unlink(path.c_str()); }

#if defined(ATI_ARCH_X86)
void Os::cpuid(int regs[4], int info) {
#ifdef _LP64
  __asm__ __volatile__(
      "movq %%rbx, %%rsi;"
      "cpuid;"
      "xchgq %%rbx, %%rsi;"
      : "=a"(regs[0]), "=S"(regs[1]), "=c"(regs[2]), "=d"(regs[3])
      : "a"(info));
#else
  __asm__ __volatile__(
      "movl %%ebx, %%esi;"
      "cpuid;"
      "xchgl %%ebx, %%esi;"
      : "=a"(regs[0]), "=S"(regs[1]), "=c"(regs[2]), "=d"(regs[3])
      : "a"(info));
#endif
}

uint64_t Os::xgetbv(uint32_t ecx) {
  uint32_t eax, edx;

  __asm__ __volatile__(".byte 0x0f,0x01,0xd0"  // in case assembler doesn't recognize xgetbv
                       : "=a"(eax), "=d"(edx)
                       : "c"(ecx));

  return ((uint64_t)edx << 32) | (uint64_t)eax;
}
#endif  // ATI_ARCH_X86

uint64_t Os::offsetToEpochNanos() {
  static uint64_t offset = 0;

  if (offset != 0) {
    return offset;
  }

  struct timeval now;
  if (::gettimeofday(&now, NULL) != 0) {
    return 0;
  }

  offset = (now.tv_sec * UINT64_C(1000000) + now.tv_usec) * UINT64_C(1000) - timeNanos();

  return offset;
}

address Os::currentStackPtr() {
  intptr_t value;

#if defined(__GNUC__)
  __asm__ __volatile__(
#if defined(ATI_ARCH_X86)
      LP64_SWITCH("movl %%esp", "movq %%rsp") ",%0"
      : "=r"(value)
#elif defined(ATI_ARCH_ARM)
      "mov %0,sp"
      : "=r"(value)
#else
      ""
#endif
  );
#else   // !__GNUC__
  __asm mov value, esp;
#endif  // !__GNUC__

  return (address)value;
}

size_t Os::getPhysicalMemSize() {
  struct ::sysinfo si;

  if (::sysinfo(&si) != 0) {
    return 0;
  }

  if (si.mem_unit == 0) {
    // Linux kernels prior to 2.3.23 return sizes in bytes.
    si.mem_unit = 1;
  }

  return (size_t)si.totalram * si.mem_unit;
}

void Os::getAppPathAndFileName(std::string& appName, std::string& appPathAndName) {
  std::unique_ptr<char[]> buff(new char[FILE_PATH_MAX_LENGTH]());

  if (readlink("/proc/self/exe", buff.get(), FILE_PATH_MAX_LENGTH) > 0) {
    // Get filename without path and extension.
    appName = std::string(basename(buff.get()));
    appPathAndName = std::string(buff.get());
  } else {
    appName = "";
    appPathAndName = "";
  }
  return;
}

bool Os::GetURIFromMemory(const void* image, size_t image_size, std::string& uri) {
  pid_t pid = getpid();
  std::ostringstream uri_stream;
  // Create a unique resource indicator to the memory address
  uri_stream << "memory://" << pid << "#offset=0x" << std::hex << (uintptr_t)image << std::dec
             << "&size=" << image_size;
  uri = uri_stream.str();
  return true;
}

bool Os::CloseFileHandle(FileDesc fdesc) {
  // Return false if close system call fails
  if (close(fdesc) < 0) {
    return false;
  }

  return true;
}

amd::Os::FileDesc Os::DupFileHandle(FileDesc fdesc) {
  return dup(fdesc);
}

bool Os::GetFileHandle(const char* fname, FileDesc* fd_ptr, size_t* sz_ptr) {
  if ((fd_ptr == nullptr) || (sz_ptr == nullptr)) {
    return false;
  }

  // open system function call, return false on fail
  struct stat stat_buf;
  *fd_ptr = open(fname, O_RDONLY);
  if (*fd_ptr < 0) {
    return false;
  }

  // Retrieve stat info and size
  if (fstat(*fd_ptr, &stat_buf) != 0) {
    close(*fd_ptr);
    return false;
  }

  *sz_ptr = stat_buf.st_size;

  return true;
}

bool amd::Os::FindFileNameFromAddress(const void* image, std::string* fname_ptr,
                                      size_t* foffset_ptr) {
  // Get the list of mapped file list
  bool ret_value = false;
  std::ifstream proc_maps;
  proc_maps.open("/proc/self/maps", std::ifstream::in);
  if (!proc_maps.is_open() || !proc_maps.good()) {
    return ret_value;
  }

  // For every line on the list map find out low, high address
  std::string line;
  while (std::getline(proc_maps, line)) {
    char dash;
    std::stringstream tokens(line);
    uintptr_t low_address, high_address;
    tokens >> std::hex >> low_address >> std::dec >> dash >> std::hex >> high_address >> std::dec;
    if (dash != '-') {
      continue;
    }

    // If address is > low_address and < high_address, then this
    // is the mapped file. Get the URI path and offset.
    uintptr_t address = reinterpret_cast<uintptr_t>(image);
    if ((address >= low_address) && (address < high_address)) {
      std::string permissions, device, uri_file_path;
      size_t offset;
      uint64_t inode;
      tokens >> permissions >> std::hex >> offset >> std::dec >> device >> inode;
      std::getline(tokens >> std::ws, uri_file_path);

      if (inode == 0 || uri_file_path.empty()) {
        return ret_value;
      }

      *fname_ptr = uri_file_path;
      *foffset_ptr = offset + address - low_address;
      ret_value = true;
      break;
    }
  }

  return ret_value;
}

bool Os::MemoryMapFileDesc(FileDesc fdesc, size_t fsize, size_t foffset, const void** mmap_ptr) {
  if (fdesc <= 0) {
    return false;
  }

  // If the offset is not aligned then align it
  // and recalculate the new size
  if (foffset > 0) {
    size_t old_foffset = foffset;
    foffset = alignUp(foffset, pageSize());
    fsize += (foffset - old_foffset);
  }

  *mmap_ptr = mmap(NULL, fsize, PROT_READ, MAP_SHARED, fdesc, foffset);
  return (*mmap_ptr == MAP_FAILED) ? false : true;
}

bool Os::MemoryUnmapFile(const void* mmap_ptr, size_t mmap_size) {
  if (munmap(const_cast<void*>(mmap_ptr), mmap_size) != 0) {
    return false;
  }

  return true;
}

bool Os::MemoryMapFile(const char* fname, const void** mmap_ptr, size_t* mmap_size) {
  if ((mmap_ptr == nullptr) || (mmap_size == nullptr)) {
    return false;
  }

  struct stat stat_buf;
  int fd = open(fname, O_RDONLY);
  if (fd < 0) {
    return false;
  }

  if (fstat(fd, &stat_buf) != 0) {
    close(fd);
    return false;
  }

  *mmap_size = stat_buf.st_size;
  *mmap_ptr = mmap(NULL, stat_buf.st_size, PROT_READ, MAP_SHARED, fd, 0);

  close(fd);

  if (*mmap_ptr == MAP_FAILED) {
    return false;
  }

  return true;
}

bool Os::MemoryMapFileTruncated(const char* fname, const void** mmap_ptr, size_t mmap_size) {
  if (mmap_ptr == nullptr) {
    return false;
  }

  struct stat stat_buf;
  int fd = shm_open(fname, O_RDWR | O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO);
  if (fd < 0) {
    return false;
  }

  if (ftruncate(fd, mmap_size) != 0) {
    return false;
  }
  *mmap_ptr = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

  close(fd);

  if (*mmap_ptr == MAP_FAILED) {
    return false;
  }

  return true;
}

int Os::getProcessId() { return ::getpid(); }

// ================================================================================================
void* Os::CreateIpcMemory(const char* fname, size_t size, FileDesc* desc) {
  *desc = shm_open(fname, O_RDWR | O_CREAT, S_IRWXU | S_IRWXG | S_IRWXO);
  if (*desc < 0) {
    return nullptr;
  }

  int status = ftruncate(*desc, size);
  if (status != 0) {
    return nullptr;
  }

  auto addr = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, *desc, 0);
  return addr;
}

// ================================================================================================
void* Os::OpenIpcMemory(const char* fname, const FileDesc desc, size_t size) {
  FileDesc handle = desc;
  if (fname != nullptr) {
    handle = shm_open(fname, O_RDWR, S_IRWXU | S_IRWXG | S_IRWXO);
  }

  if (handle < 0) {
    return nullptr;
  }

  auto addr = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, handle, 0);
  return addr;
}

// ================================================================================================
void Os::CloseIpcMemory(const FileDesc desc, const void* ptr, size_t size) {
  if (ptr != nullptr) {
    munmap(const_cast<void*>(ptr), size);
  }
  if (desc != 0) {
    close(desc);
  }
}

// ================================================================================================
void Os::PrintLibraryLocation() {
  Dl_info dl_info;
  if (dladdr(reinterpret_cast<void*>(Os::loadLibrary), &dl_info) && dl_info.dli_fname) {
    ClPrint(amd::LOG_INFO, amd::LOG_INIT, "Runtime Library Path: %s", dl_info.dli_fname);
  } else {
    ClPrint(amd::LOG_INFO, amd::LOG_INIT, "Runtime Library Path: <unknown>");
  }
}

// ================================================================================================
bool Os::DumpCoreFile() {
  // Execute the default handler if a GPU core file should be generated ...
  struct rlimit rlimit;
  return (getrlimit(RLIMIT_CORE, &rlimit) == 0 && rlimit.rlim_cur != 0);
}

// ================================================================================================
void Os::CxaDemangle(const std::string& name, std::string* result) {
  int status = 0;
  char* demangled = abi::__cxa_demangle(name.c_str(), nullptr, nullptr, &status);
  *result = (status == 0 && demangled != nullptr) ? demangled : name;
  free(demangled);
}

namespace numa {

// ================================================================================================
uint32_t getCurrentNumaNode() {
  unsigned cpu, node;
  if (syscall(__NR_getcpu, &cpu, &node, nullptr) < 0) {
    return static_cast<uint32_t>(-1);
  }
  return static_cast<uint32_t>(node);
}

// ================================================================================================
NumaPolicy::NumaPolicy(const uint32_t numa_node_count) :
  node_map_((numa_node_count + kBitsPerUInt64 - 1) / kBitsPerUInt64, 0) { }

// ================================================================================================
bool NumaPolicy::GetMemPolicy() {
  int policy = 0;
  if (syscall(__NR_get_mempolicy, &policy, node_map_.data(),
      node_map_.size() * kBitsPerUInt64, nullptr, 0) < 0) {
    ClPrint(amd::LOG_DEBUG, amd::LOG_RESOURCE,
        "syscall(__NR_get_mempolicy, size=%zu) failed to query policy",
        node_map_.size() * kBitsPerUInt64);
    return false;
  }
  if (policy < static_cast<int>(Policy::kDefault) || policy > static_cast<int>(Policy::kMax)) {
    ClPrint(amd::LOG_DEBUG, amd::LOG_RESOURCE,
            "syscall(__NR_get_mempolicy) returned wrong policy %d", policy);
    return false;
  }
  policy_ = static_cast<Policy>(policy);
  return true;
}

// ================================================================================================
bool NumaPolicy::IsPolicySetAt(uint32_t node_index) const {
  const uint32_t i = node_index / kBitsPerUInt64;
  if (i < node_map_.size()) {
    return ((node_map_[i] >> (node_index % kBitsPerUInt64)) & 1) ?
        true: false;
  } else {
    return false;
  }
}

// ================================================================================================
NumaNode::~NumaNode() {
  if (affinity_) {
    delete static_cast<std::vector<uint64_t> *>(affinity_);
    affinity_ = nullptr;
  }
}

// ================================================================================================
bool NumaNode::GetAffinity() {
  const std::string path = "/sys/devices/system/node/node" + std::to_string(node_index_) +
      "/cpumap";
  std::ifstream file(path);
  if (!file) {
    ClPrint(amd::LOG_DEBUG, amd::LOG_RESOURCE, "%s cannot be opened", path.c_str());
    return false;
  }
  std::string line;
  std::getline(file, line);
  file.close();

  // To remove commas and whitespace
  line.erase(std::remove_if(line.begin(), line.end(),
             [](unsigned char x) { return std::isspace(x) || x == ','; }), line.end());

  constexpr uint32_t kHexsPerUInt64 = 2 * sizeof(uint64_t);
  auto affinity = new std::vector<uint64_t>((line.size() + kHexsPerUInt64 - 1) / kHexsPerUInt64);
  auto iter = affinity->begin();
  // To parse from the end (little-endian layout)
  for (int i = line.size(); i > 0; i -= kHexsPerUInt64) {
    uint32_t start = (i >= kHexsPerUInt64) ? i - kHexsPerUInt64 : 0;
    uint32_t len = (i >= kHexsPerUInt64) ? kHexsPerUInt64 : i;

    const std::string chunk = line.substr(start, len);
    const uint64_t value = std::stoul(chunk, nullptr, 16);
    *(iter++) = value;
    if (len == kHexsPerUInt64) {
      size_ += kBitsPerUInt64;
    } else {
      // Last one
      size_ = kBitsPerUInt64 - __builtin_clzl(value);
    }
  }
  affinity_ = affinity;
  return true;
}

// ================================================================================================
bool NumaNode::SchedSetAffinity() {
  if (!GetAffinity()) {
    return false;
  }
  if (syscall(__NR_sched_setaffinity, 0, size_,
              static_cast<std::vector<uint64_t>*>(affinity_)->data()) < 0) {
    ClPrint(amd::LOG_DEBUG, amd::LOG_RESOURCE,
            "syscall(__NR_sched_setaffinity, size=%u) failed", size_);
    return false;
  }
  return true;
}

// ================================================================================================
bool NumaNode::SchedSetAffinityIfAllowed() {
  ThreadAffinityState* affinity_state = GetThreadAffinityStatePtr();
  const bool had_affinity_state = (affinity_state != nullptr);
  if (affinity_state != nullptr && affinity_state->runtime_set_affinity_mask_valid &&
      affinity_state->runtime_set_affinity_node == node_index_) {
    return true;
  }

  std::vector<uint64_t> current_affinity;
  if (!GetCurrentThreadAffinity(&current_affinity)) {
    return false;
  }

  const bool current_mask_is_runtime_set =
      affinity_state != nullptr && affinity_state->runtime_set_affinity_mask_valid &&
      IsSameAffinity(current_affinity, affinity_state->runtime_set_affinity_mask);
  if (!current_mask_is_runtime_set && !IsUnrestrictedAffinity(current_affinity)) {
    // Respect application, launcher, or cpuset affinity masks.
    return false;
  }

  affinity_state = &GetThreadAffinityState();
  if (!affinity_state->original_affinity_mask_valid) {
    affinity_state->original_affinity_mask = current_affinity;
    affinity_state->original_affinity_mask_valid = true;
  }

  if (!SchedSetAffinity()) {
    if (!had_affinity_state) {
      ReleaseThreadAffinityState();
    }
    return false;
  }

  affinity_state->runtime_set_affinity_mask_valid =
      GetCurrentThreadAffinity(&affinity_state->runtime_set_affinity_mask);
  if (affinity_state->runtime_set_affinity_mask_valid) {
    affinity_state->runtime_set_affinity_node = node_index_;
  } else {
    SetCurrentThreadAffinity(affinity_state->original_affinity_mask);
    ReleaseThreadAffinityState();
    return false;
  }
  return true;
}

// ================================================================================================
bool resetThreadAffinity() {
  ThreadAffinityState* affinity_state = GetThreadAffinityStatePtr();
  if (affinity_state == nullptr || !affinity_state->runtime_set_affinity_mask_valid ||
      !affinity_state->original_affinity_mask_valid) {
    return false;
  }

  std::vector<uint64_t> current_affinity;
  if (!GetCurrentThreadAffinity(&current_affinity)) {
    return false;
  }

  if (!IsSameAffinity(current_affinity, affinity_state->runtime_set_affinity_mask)) {
    // The application changed affinity after ROCclr. Leave it intact.
    ReleaseThreadAffinityState();
    return false;
  }

  const bool restored = SetCurrentThreadAffinity(affinity_state->original_affinity_mask);
  ReleaseThreadAffinityState();
  return restored;
}
}  // namespace numa

}  // namespace amd

#endif  // !defined(_WIN32) && !defined(__CYGWIN__)
