/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

// This contains things related to the Gecko profiler, for use in third_party
// code. It is very minimal and is designed to be used by patching over
// upstream code.
// Only use the C ABI and guard C++ code with #ifdefs, don't pull anything from
// Gecko, it must be possible to include the header file into any C++ codebase.

#ifndef MICRO_GECKO_PROFILER
#define MICRO_GECKO_PROFILER

#ifdef __cplusplus
extern "C" {
#endif

#include <mozilla/Types.h>
#include <stdio.h>

#ifdef _WIN32
#  include <libloaderapi.h>
#else
#  include <dlfcn.h>
#endif

extern MOZ_EXPORT void uprofiler_register_thread(const char* aName,
                                                 void* aGuessStackTop);

extern MOZ_EXPORT void uprofiler_unregister_thread();

extern MOZ_EXPORT void uprofiler_simple_event_marker(
    const char* name, char phase, int num_args, const char** arg_names,
    const unsigned char* arg_types, const unsigned long long* arg_values);

extern MOZ_EXPORT void uprofiler_simple_event_marker_with_stack(
    const char* name, char phase, int num_args, const char** arg_names,
    const unsigned char* arg_types, const unsigned long long* arg_values);
#ifdef __cplusplus
}

struct AutoRegisterProfiler {
  AutoRegisterProfiler(const char* name, char* stacktop) {
    if (getenv("MOZ_UPROFILER_LOG_THREAD_CREATION")) {
      printf("### UProfiler: new thread: '%s'\n", name);
    }
    uprofiler_register_thread(name, stacktop);
  }
  ~AutoRegisterProfiler() { uprofiler_unregister_thread(); }
};
#endif  // __cplusplus

void uprofiler_simple_event_marker(const char* name, char phase, int num_args,
                                   const char** arg_names,
                                   const unsigned char* arg_types,
                                   const unsigned long long* arg_values);

struct UprofilerFuncPtrs {
  void (*register_thread)(const char* aName, void* aGuessStackTop);
  void (*unregister_thread)();
  void (*simple_event_marker)(const char* name, char phase, int num_args,
                              const char** arg_names,
                              const unsigned char* arg_types,
                              const unsigned long long* arg_values);
  void (*simple_event_marker_with_stack)(const char* name, char phase,
                                         int num_args, const char** arg_names,
                                         const unsigned char* arg_types,
                                         const unsigned long long* arg_values);
};

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-function"

static void register_thread_noop(const char* aName, void* aGuessStackTop) {
  /* no-op */
}
static void unregister_thread_noop() { /* no-op */
}
static void simple_event_marker_noop(const char* name, char phase, int num_args,
                                     const char** arg_names,
                                     const unsigned char* arg_types,
                                     const unsigned long long* arg_values) {
  /* no-op */
}

static void simple_event_marker_with_stack_noop(
    const char* name, char phase, int num_args, const char** arg_names,
    const unsigned char* arg_types, const unsigned long long* arg_values) {
  /* no-op */
}

#pragma GCC diagnostic pop

#if defined(_WIN32)
#  define UPROFILER_OPENLIB() GetModuleHandle(NULL)
#else
#  define UPROFILER_OPENLIB() dlopen(NULL, RTLD_NOW)
#endif

#if defined(_WIN32)
#  define UPROFILER_GET_SYM(handle, sym) GetProcAddress(handle, sym)
#else
#  define UPROFILER_GET_SYM(handle, sym) (typeof(sym)*)(dlsym(handle, #sym))
#endif

#if defined(_WIN32)
#  define UPROFILER_PRINT_ERROR(func) fprintf(stderr, "%s error\n", #func);
#else
#  define UPROFILER_PRINT_ERROR(func) \
    fprintf(stderr, "%s error: %s\n", #func, dlerror());
#endif

#define FETCH(func)                                             \
  uprofiler.func = UPROFILER_GET_SYM(handle, uprofiler_##func); \
  if (!uprofiler.func) {                                        \
    UPROFILER_PRINT_ERROR(uprofiler_##func);                    \
    uprofiler.func = func##_noop;                               \
  }

#define UPROFILER_VISIT()    \
  FETCH(register_thread)     \
  FETCH(unregister_thread)   \
  FETCH(simple_event_marker) \
  FETCH(simple_event_marker_with_stack)

// Assumes that a variable of type UprofilerFuncPtrs, named uprofiler
// is accessible in the scope
#define UPROFILER_GET_FUNCTIONS()                                            \
  void* handle = UPROFILER_OPENLIB();                                        \
  if (!handle) {                                                             \
    UPROFILER_PRINT_ERROR(UPROFILER_OPENLIB);                                \
    uprofiler.register_thread = register_thread_noop;                        \
    uprofiler.unregister_thread = unregister_thread_noop;                    \
    uprofiler.simple_event_marker = simple_event_marker_noop;                \
    uprofiler.simple_event_marker_with_stack = simple_event_with_stack_noop; \
  }                                                                          \
  UPROFILER_VISIT()

#endif  // MICRO_GECKO_PROFILER