/******************************************************************************
* Copyright (c) Intel Corporation - All rights reserved.                      *
* This file is part of the LIBXSMM library.                                   *
*                                                                             *
* For information on the license, see the LICENSE file.                       *
* Further information: https://github.com/hfp/libxsmm/                        *
* SPDX-License-Identifier: BSD-3-Clause                                       *
******************************************************************************/
/* Maciej Debski (Google Inc.)
******************************************************************************/
#include "libxsmm_perf.h"
#include <libxsmm_memory.h>
#include <libxsmm_timer.h>
#include <libxsmm_sync.h>

#if defined(LIBXSMM_OFFLOAD_TARGET)
# pragma offload_attribute(push,target(LIBXSMM_OFFLOAD_TARGET))
#endif
#include "perf_jitdump.h"
#if defined(LIBXSMM_PERF_JITDUMP) && !defined(_WIN32)
# include <sys/mman.h>
# include <sys/types.h>
# include <sys/types.h>
# include <sys/stat.h>
# include <fcntl.h>
# include <errno.h>
# include <time.h>
#endif
#if defined(__linux__)
# include <syscall.h>
#endif
#if defined(_WIN32)
# include <windows.h>
# define LIBXSMM_MAX_PATH MAX_PATH
#else
# if defined(__linux__)
#   include <linux/limits.h>
#   define LIBXSMM_MAX_PATH PATH_MAX
# elif defined(PATH_MAX)
#   define LIBXSMM_MAX_PATH PATH_MAX
# else /* fallback */
#   define LIBXSMM_MAX_PATH 1024
# endif
# include <unistd.h>
#endif
#if defined(LIBXSMM_OFFLOAD_TARGET)
# pragma offload_attribute(pop)
#endif

#if !defined(NDEBUG)
# define LIBXSMM_PERF_ERROR(msg) fprintf(stderr, msg)
#else
# define LIBXSMM_PERF_ERROR(msg)
#endif

#if !defined(PERF_JITDUMP_NOLIBXSMM)
LIBXSMM_APIVAR_PRIVATE_DEF(/*const*/ uint32_t JITDUMP_MAGIC);
LIBXSMM_APIVAR_PRIVATE_DEF(/*const*/ uint32_t JITDUMP_MAGIC_SWAPPED);
LIBXSMM_APIVAR_PRIVATE_DEF(/*const*/ uint32_t JITDUMP_VERSION);
LIBXSMM_APIVAR_PRIVATE_DEF(/*const*/ uint64_t JITDUMP_FLAGS_ARCH_TIMESTAMP);
LIBXSMM_APIVAR_PRIVATE_DEF(/*const*/ uint32_t JITDUMP_CODE_LOAD);
LIBXSMM_APIVAR_PRIVATE_DEF(/*const*/ uint32_t JITDUMP_CODE_MOVE);
LIBXSMM_APIVAR_PRIVATE_DEF(/*const*/ uint32_t JITDUMP_CODE_DEBUG_INFO);
LIBXSMM_APIVAR_PRIVATE_DEF(/*const*/ uint32_t JITDUMP_CODE_CLOSE);
#endif

LIBXSMM_APIVAR_DEFINE(FILE* internal_perf_fp);
#if defined(LIBXSMM_PERF_JITDUMP) && !defined(_WIN32)
LIBXSMM_APIVAR_DEFINE(void* internal_perf_marker);
LIBXSMM_APIVAR_DEFINE(int internal_perf_codeidx);
#endif


LIBXSMM_API_INTERN void libxsmm_perf_init(void)
{
  const uint32_t pid = (uint32_t)libxsmm_get_pid();
  char file_name[LIBXSMM_MAX_PATH];
#if defined(LIBXSMM_PERF_JITDUMP) && !defined(_WIN32)
  char file_path[LIBXSMM_MAX_PATH];
  int fd, page_size, res;
  struct jitdump_file_header header;
  char * path_base;
  char date[64];
  time_t t = time(NULL);
  struct tm tm = *localtime(&t);

  /* initialize global variables */
  JITDUMP_MAGIC = ('J' << 24 | 'i' << 16 | 'T' << 8 | 'D');
  JITDUMP_MAGIC_SWAPPED = ('J' | 'i' << 8 | 'T' << 16 | 'D' << 24);
  JITDUMP_VERSION = 1;
  JITDUMP_FLAGS_ARCH_TIMESTAMP = 1ULL /*<< 0*/;
  JITDUMP_CODE_LOAD = 0;
  JITDUMP_CODE_MOVE = 1;
  JITDUMP_CODE_DEBUG_INFO = 2;
  JITDUMP_CODE_CLOSE = 3;

  path_base = getenv("JITDUMPDIR");
  if (path_base == NULL) {
    path_base = getenv("HOME");
  }
  if (path_base == NULL) {
    path_base = ".";
  }

  LIBXSMM_SNPRINTF(file_path, sizeof(file_path), "%s/.debug/", path_base);
  res = mkdir(file_path, S_IRWXU);
  if (res < 0 && errno != EEXIST) {
    LIBXSMM_PERF_ERROR("LIBXSMM ERROR: failed to create .debug dir\n");
    goto error;
  }

  LIBXSMM_SNPRINTF(file_path, sizeof(file_path), "%s/.debug/jit", path_base);
  res = mkdir(file_path, S_IRWXU);
  if (res < 0 && errno != EEXIST) {
    LIBXSMM_PERF_ERROR("LIBXSMM ERROR: failed to create .debug/jit dir\n");
    goto error;
  }

  strftime(date, sizeof(date), "%Y%m%d", &tm);

  LIBXSMM_SNPRINTF(file_path, sizeof(file_path),
           "%s/.debug/jit/libxsmm-jit-%s.XXXXXX", path_base, date);
  path_base = mkdtemp(file_path);
  if (path_base == NULL) {
    LIBXSMM_PERF_ERROR("LIBXSMM ERROR: failed to create temporary dir\n");
    goto error;
  }

  LIBXSMM_SNPRINTF(file_name, sizeof(file_name), "%s/jit-%u.dump", path_base, pid);

  fd = open(file_name, O_CREAT|O_TRUNC|O_RDWR, 0600);
  if (fd < 0) {
    LIBXSMM_PERF_ERROR("LIBXSMM ERROR: failed to open file\n");
    goto error;
  }

  page_size = sysconf(_SC_PAGESIZE);
  if (page_size < 0) {
    LIBXSMM_PERF_ERROR("LIBXSMM ERROR: failed to get page size\n");
    goto error;
  }
  internal_perf_marker = mmap(NULL, page_size, PROT_READ|PROT_EXEC, MAP_PRIVATE, fd, 0);
  if (internal_perf_marker == MAP_FAILED) {
    LIBXSMM_PERF_ERROR("LIBXSMM ERROR: mmap failed.\n");
    goto error;
  }

  /* initialize code index */
  internal_perf_codeidx = 0;

  internal_perf_fp = fdopen(fd, "wb+");
  if (internal_perf_fp == NULL) {
    LIBXSMM_PERF_ERROR("LIBXSMM ERROR: fdopen failed.\n");
    goto error;
  }

  LIBXSMM_MEMZERO127(&header);
  header.magic      = JITDUMP_MAGIC;
  header.version    = JITDUMP_VERSION;
  header.elf_mach   = 62;  /* EM_X86_64 */
  header.total_size = sizeof(header);
  header.pid        = pid;
  header.timestamp  = libxsmm_timer_tick();
  header.flags      = JITDUMP_FLAGS_ARCH_TIMESTAMP;

  res = fwrite(&header, sizeof(header), 1, internal_perf_fp);
  if (res != 1) {
    LIBXSMM_PERF_ERROR("LIBXSMM ERROR: failed to write header.\n");
    goto error;
  }
#else
  LIBXSMM_SNPRINTF(file_name, sizeof(file_name), "/tmp/perf-%u.map", pid);
  internal_perf_fp = fopen(file_name, "w+");
  if (internal_perf_fp == NULL) {
    LIBXSMM_PERF_ERROR("LIBXSMM ERROR: failed to open map file\n");
    goto error;
  }
#endif
  return;
error:
  if (internal_perf_fp != NULL) {
    fclose(internal_perf_fp);
    internal_perf_fp = NULL;
  }
  assert(0);
}


LIBXSMM_API_INTERN void libxsmm_perf_finalize(void)
{
#if defined(LIBXSMM_PERF_JITDUMP) && !defined(_WIN32)
  int res, page_size;
  struct jitdump_record_header hdr;

  if (internal_perf_fp == NULL) {
    LIBXSMM_PERF_ERROR("LIBXSMM ERROR: jit dump file not opened\n");
    goto error;
  }

  LIBXSMM_MEMZERO127(&hdr);
  hdr.id = JITDUMP_CODE_CLOSE;
  hdr.total_size = sizeof(hdr);
  hdr.timestamp = libxsmm_timer_tick();
  res = fwrite(&hdr, sizeof(hdr), 1, internal_perf_fp);

  if (res != 1) {
    LIBXSMM_PERF_ERROR("LIBXSMM ERROR: failed to write JIT_CODE_CLOSE record\n");
    goto error;
  }

  page_size = sysconf(_SC_PAGESIZE);
  if (page_size < 0) {
    LIBXSMM_PERF_ERROR("LIBXSMM ERROR: failed to get page_size\n");
    goto error;
  }
  munmap(internal_perf_marker, page_size);
  fclose(internal_perf_fp);
  return;
error:
  assert(0);
#else
  fclose(internal_perf_fp);
#endif
}


#if defined(LIBXSMM_PERF_JITDUMP) && !defined(_WIN32)
/** Utility function to receive the OS-specific thread ID. */
LIBXSMM_API_INLINE unsigned int internal_perf_get_tid(void)
{
#if defined(__linux__)
  return (unsigned int)syscall(__NR_gettid);
#else /* fallback */
  return libxsmm_get_tid();
#endif
}
#endif


LIBXSMM_API_INTERN void libxsmm_perf_dump_code(const void* memory, size_t size, const char* name)
{
  assert(internal_perf_fp != NULL);
  assert(name && *name);
  assert(memory != NULL && size != 0);
  if (internal_perf_fp != NULL) {
#if defined(LIBXSMM_PERF_JITDUMP) && !defined(_WIN32)
    int res;
    struct jitdump_record_header hdr;
    struct jitdump_record_code_load rec;
    size_t name_len = strlen(name) + 1;

    LIBXSMM_MEMZERO127(&hdr);
    LIBXSMM_MEMZERO127(&rec);

    hdr.id = JITDUMP_CODE_LOAD;
    hdr.total_size = sizeof(hdr) + sizeof(rec) + name_len + size;
    hdr.timestamp = libxsmm_timer_tick();

    rec.code_size = size;
    rec.vma = (uintptr_t) memory;
    rec.code_addr = (uintptr_t) memory;
    rec.pid = (uint32_t) libxsmm_get_pid();
    rec.tid = (uint32_t) internal_perf_get_tid();

    LIBXSMM_FLOCK(internal_perf_fp);

    /* This will be unique as we hold the file lock. */
    rec.code_index = internal_perf_codeidx++;

    /* Count number of written items to check for errors. */
    res = 0;
    res += fwrite_unlocked(&hdr, sizeof(hdr), 1, internal_perf_fp);
    res += fwrite_unlocked(&rec, sizeof(rec), 1, internal_perf_fp);
    res += fwrite_unlocked(name, name_len, 1, internal_perf_fp);
    res += fwrite_unlocked((const void*) memory, size, 1, internal_perf_fp);

    LIBXSMM_FUNLOCK(internal_perf_fp);
    fflush(internal_perf_fp);

    assert(res == 4); /* Expected 4 items written above */
#else
    fprintf(internal_perf_fp, "%" PRIxPTR " %lx %s\n", (uintptr_t)memory, (unsigned long)size, name);
    fflush(internal_perf_fp);
#endif
  }
}

