/* -*- c-file-style: "GNU" -*- */
/*
 * Copyright (C) CNRS, INRIA, Universite Bordeaux 1, Telecom SudParis
 * See COPYING in top-level directory.
 */

#define _REENTRANT

#include <unistd.h>
#include <stdlib.h>
#include <stdint.h>
#include <dlfcn.h>
#include <pthread.h>
#include <stdio.h>
#include <semaphore.h>
#include <omp.h>

#include "pomp-lib-dummy/pomp2_lib.h"
#include "gomp_ev_codes.h"
#include "eztrace.h"
#include "pptrace.h"

static int record_callsite = 0;

static int pomp2_found = 0;
#define GOMP_RECORD if(!pomp2_found)

// todo: add hooks for
// OMP_Barrier
// OMP_critical ?

/* each thread has its own counter */
static __thread POMP2_Region_handle _next_section_id = -1;

static POMP2_Region_handle __get_next_section_id() {
  /* make sure that the counters of each thread do not collide */
  if(_next_section_id < 0) {
    /* POMP2_Region_handle is an int, this will work for:
     * - ~ 2^11 threads
     * - ~ 2^20 parallel region created by a thread
     */
    _next_section_id = omp_get_thread_num() << 20;
  }
  return _next_section_id++;
}

void (*libGOMP_parallel_loop_static_start)(void (*)(void *), void *, unsigned, long, long, long, long);
void (*libGOMP_parallel_loop_runtime_start)(void (*)(void *), void *, unsigned, long, long, long, long);
void (*libGOMP_parallel_loop_dynamic_start)(void (*)(void *), void *, unsigned, long, long, long, long);
void (*libGOMP_parallel_loop_guided_start)(void (*)(void *), void *, unsigned, long, long, long, long);

void (*libGOMP_parallel_start)(void (*fn)(void *), void *data, unsigned num_threads);
void (*libGOMP_parallel_end)();

void (*libGOMP_critical_start)(void);
void (*libGOMP_critical_end)(void);

/* these function are only available in GOMP 4 */
void (*libGOMP_parallel_loop_static) (void (*)(void *), void *, unsigned, long, long, long, long, unsigned);
void (*libGOMP_parallel_loop_dynamic) (void (*)(void *), void *, unsigned, long, long, long, long, unsigned);
void (*libGOMP_parallel_loop_guided) (void (*)(void *), void *, unsigned, long, long, long, long, unsigned);
void (*libGOMP_parallel_loop_runtime) (void (*)(void *), void *, unsigned, long, long, long, unsigned);

static int use_pomp = 1;

struct gomp_arg_t {
    void (*func)(void *);
    void *data;
  POMP2_Region_handle id;
};

/* Function called by GOMP_parallel_start for each thread */
void gomp_new_thread(void *arg) {
  FUNCTION_ENTRY;
  struct gomp_arg_t *_arg = (struct gomp_arg_t*) arg;
  void (*func)(void *) = _arg->func;
  void *data = _arg->data;
  POMP2_Region_handle section_id = _arg->id;
  int nb_threads = omp_get_num_threads();
  int my_id = omp_get_thread_num();
  /* Since the runtime functions provide more information, let's use it instead of the compiler functions */
  GOMP_RECORD {
    EZTRACE_EVENT_PACKED_3(EZTRACE_GOMP_NEW_FORK, section_id, my_id, nb_threads);
  }
  func(data);
  GOMP_RECORD {
    EZTRACE_EVENT_PACKED_1(EZTRACE_GOMP_NEW_JOIN, my_id);
  }
  return;
}

/* generic implementation of parallel loop
 */
#define GOMP_PARALLEL_LOOP_GENERIC(fn, data, varname, gomp_func, section_id) \
  {									\
    EZTRACE_PROTECT_ON();						\
    /* Since the runtime functions provide more information, let's use it instead of the compiler functions */ \
    /* call unprotected function 'cause the block is protected */	\
    EZTRACE_EVENT1_PACKED_UNPROTECTED(EZTRACE_GOMP_PARALLEL_START, section_id);	\
    struct gomp_arg_t *varname = (struct gomp_arg_t*) malloc (sizeof (struct gomp_arg_t)); \
    varname->func = fn;							\
    varname->data = data;						\
    varname->id = section_id;						\
    EZTRACE_PROTECT_OFF();						\
    gomp_func;								\
  }

#define GOMP4_PARALLEL_LOOP_GENERIC(fn, data, varname, gomp_func) \
  {									\
    FUNCTION_ENTRY;							\
    POMP2_Region_handle section_id = __get_next_section_id();		\
    GOMP_PARALLEL_LOOP_GENERIC(fn, data, varname, gomp_func, section_id); \
  }

/* generic implementation of parallel loop
 */
#define GOMP3_PARALLEL_LOOP_GENERIC(fn, data, varname, gomp_func)	\
  {									\
    FUNCTION_ENTRY;							\
    POMP2_Region_handle section_id = __get_next_section_id();		\
    GOMP_PARALLEL_LOOP_GENERIC(fn, data, varname, gomp_func, section_id); \
    int nb_threads = omp_get_num_threads();				\
    int my_id = omp_get_thread_num();					\
    GOMP_RECORD {							\
      EZTRACE_EVENT_PACKED_3 (EZTRACE_GOMP_NEW_FORK, section_id, my_id, nb_threads); \
    }									\
    return;								\
  }



/* should be called when reaching #pragma omp parallel for schedule(static)
 * However, this function doesn't seem to be called. Let's implement it just in case.
 */
void GOMP_parallel_loop_static_start(void (*fn)(void *), void * data,
				     unsigned num_threads, long a1, long a2,
				     long a3, long a4) {
  GOMP3_PARALLEL_LOOP_GENERIC(fn,
			     data,
			     arg,
			     libGOMP_parallel_loop_static_start(gomp_new_thread,
								arg, num_threads,
								a1, a2, a3, a4));
}

/* Function called when reaching  #pragma omp parallel for schedule(runtime) */
void GOMP_parallel_loop_runtime_start(void (*fn)(void *), void * data,
				      unsigned num_threads, long a1, long a2,
				      long a3, long a4) {
  GOMP3_PARALLEL_LOOP_GENERIC(fn,
			     data,
			     arg,
			     libGOMP_parallel_loop_runtime_start(gomp_new_thread,
								 arg, num_threads,
								 a1, a2, a3, a4));
}

/* Function called when reaching  #pragma omp parallel for schedule(dynamic) */
void GOMP_parallel_loop_dynamic_start(void (*fn)(void *), void * data,
				      unsigned num_threads, long a1, long a2,
				      long a3, long a4) {
  GOMP3_PARALLEL_LOOP_GENERIC(fn,
			     data,
			     arg,
			     libGOMP_parallel_loop_dynamic_start(gomp_new_thread,
								 arg, num_threads,
								 a1, a2, a3, a4));
}

/* Function called when reaching  #pragma omp parallel for schedule(guided) */
void GOMP_parallel_loop_guided_start(void (*fn)(void *), void * data,
				     unsigned num_threads, long a1, long a2,
				     long a3, long a4) {
  GOMP3_PARALLEL_LOOP_GENERIC(fn,
			     data,
			     arg,
			     libGOMP_parallel_loop_guided_start(gomp_new_thread,
								arg, num_threads,
								a1, a2, a3, a4));
}

void GOMP_parallel_loop_static (void (*fn)(void *), void *data,
				unsigned num_threads, long start, long end,
				long incr, long chunk_size, unsigned flags) {
  GOMP4_PARALLEL_LOOP_GENERIC(fn,
			     data,
			     arg,
			     libGOMP_parallel_loop_static(gomp_new_thread,
							  arg, num_threads,
							  start, end, incr,
							  chunk_size, flags));
}

void
GOMP_parallel_loop_dynamic (void (*fn) (void *), void *data,
			    unsigned num_threads, long start, long end,
			    long incr, long chunk_size, unsigned flags) {
  GOMP4_PARALLEL_LOOP_GENERIC(fn,
			     data,
			     arg,
			     libGOMP_parallel_loop_dynamic(gomp_new_thread,
							   arg, num_threads,
							   start, end, incr,
							   chunk_size, flags));
}

void GOMP_parallel_loop_guided (void (*fn) (void *), void *data,
				unsigned num_threads, long start, long end,
				long incr, long chunk_size, unsigned flags) {
  GOMP4_PARALLEL_LOOP_GENERIC(fn,
			     data,
			     arg,
			     libGOMP_parallel_loop_guided(gomp_new_thread,
							  arg, num_threads,
							  start, end, incr,
							  chunk_size,  flags));
}

void
GOMP_parallel_loop_runtime (void (*fn) (void *), void *data,
			    unsigned num_threads, long start, long end,
			    long incr, unsigned flags) {
  GOMP4_PARALLEL_LOOP_GENERIC(fn,
			     data,
			     arg,
			     libGOMP_parallel_loop_runtime(gomp_new_thread,
							   arg, num_threads,
							   start, end, incr,
							   flags));

}

// Called by the main thread (ie. only once) during #pragma omp parallel
// (fork)
void GOMP_parallel_start(void (*fn)(void *), void *data, unsigned num_threads) {
  GOMP4_PARALLEL_LOOP_GENERIC(fn, data, arg,
			     libGOMP_parallel_start(gomp_new_thread, arg,
						    num_threads));
}

// Called at the end of a parallel section (~ join)
void GOMP_parallel_end() {
  FUNCTION_ENTRY;
  /* Since the runtime functions provide more information, let's use it instead of the compiler functions */
  int my_id = omp_get_thread_num();
  GOMP_RECORD {
    EZTRACE_EVENT_PACKED_1(EZTRACE_GOMP_NEW_JOIN, my_id);
  }
  libGOMP_parallel_end();
}

void GOMP_critical_start() {
  FUNCTION_ENTRY;
  GOMP_RECORD
  EZTRACE_EVENT_PACKED_0(EZTRACE_GOMP_CRITICAL_START);
  libGOMP_critical_start();
  GOMP_RECORD
  EZTRACE_EVENT_PACKED_0(EZTRACE_GOMP_CRITICAL_START_DONE);
}

void GOMP_critical_end() {
  FUNCTION_ENTRY;
  GOMP_RECORD
  EZTRACE_EVENT_PACKED_0(EZTRACE_GOMP_CRITICAL_STOP);
  libGOMP_critical_end();
}

/* beginning of pomp2 internals */

void POMP2_Finalize() {
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_FINALIZE);
}

void POMP2_Init() {
  /* todo: initialize stuff ? */
}

void POMP2_Off() {
  /* todo: stop recording events ? */
}

void POMP2_On() {
  /* todo: initialize stuff ? */
}

void POMP2_Begin(POMP2_Region_handle* pomp2_handle __attribute__((unused))) {
  /** Called at the begin of a user defined POMP2 region.
   @param pomp2_handle  The handle of the started region.
   */

}

void POMP2_End(POMP2_Region_handle* pomp2_handle __attribute__((unused))) {
}

void POMP2_Assign_handle(
    POMP2_Region_handle* pomp2_handle __attribute__((unused)),
    const char ctc_string[] __attribute__((unused))) {
  /** Registers a POMP2 region and returns a region handle.
     @param pomp2_handle  Returns the handle for the newly registered region.
     @param ctc_string   A string containing the region data.
     */
}

/* end of pomp2 internals */

void POMP2_Atomic_enter(
    POMP2_Region_handle* pomp2_handle __attribute__((unused)),
    const char ctc_string[] __attribute__((unused))) {
  /** Called before an atomic statement.
   @param pomp2_handle The handle of the started region.
   @param ctc_string   Initialization string. May be ignored if \<pomp2_handle\> is already initialized.
   */
  FUNCTION_ENTRY;
  /* todo: record the initialization string ? */
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_ATOMIC_ENTER);

}

void POMP2_Atomic_exit(
    POMP2_Region_handle* pomp2_handle __attribute__((unused))) {
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_ATOMIC_EXIT);
}

void POMP2_Implicit_barrier_enter(
				  POMP2_Region_handle* pomp2_handle __attribute__((unused)),
				  POMP2_Task_handle* pomp2_old_task __attribute__((unused))) {
  /** Called before an implicit barrier.

      \e OpenMP \e 3.0: Barriers can be used as scheduling points for
      tasks. When entering a barrier the task id of the currently
      executing task (\e pomp2_current_task) is saved in \e
      pomp2_old_task, which is defined inside the instrumented user
      code.

      @param pomp2_handle   The handle of the started region.
      @param pomp2_old_task Pointer to a "taskprivate" variable where the current task id is stored.
  */
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_IMPLICIT_BARRIER_ENTER);

  /* todo: implement */
}

 extern void POMP2_Implicit_barrier_exit(
					 POMP2_Region_handle* pomp2_handle __attribute__((unused)),
    POMP2_Task_handle pomp2_old_task __attribute__((unused))) {
  /* todo: implement */
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_IMPLICIT_BARRIER_EXIT);
}

void POMP2_Barrier_enter(
    POMP2_Region_handle* pomp2_handle __attribute__((unused)),
    POMP2_Task_handle* pomp2_old_task __attribute__((unused)),
    const char ctc_string[] __attribute__((unused))) {
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_BARRIER_ENTER);
}

void POMP2_Barrier_exit(
    POMP2_Region_handle* pomp2_handle __attribute__((unused)),
    POMP2_Task_handle pomp2_old_task __attribute__((unused))) {
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_BARRIER_EXIT);
}

void POMP2_Flush_enter(
    POMP2_Region_handle* pomp2_handle __attribute__((unused)),
    const char ctc_string[] __attribute__((unused))) {
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_FLUSH_ENTER);
}

void POMP2_Flush_exit(POMP2_Region_handle* pomp2_handle __attribute__((unused))) {
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_FLUSH_EXIT);
}

void POMP2_Critical_begin(
    POMP2_Region_handle* pomp2_handle __attribute__((unused))) {
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_CRITICAL_BEGIN);
}

void POMP2_Critical_end(
    POMP2_Region_handle* pomp2_handle __attribute__((unused))) {
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_CRITICAL_END);
}

void POMP2_Critical_enter(
    POMP2_Region_handle* pomp2_handle __attribute__((unused)),
    const char ctc_string[] __attribute__((unused))) {
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_CRITICAL_ENTER);
}

void POMP2_Critical_exit(
    POMP2_Region_handle* pomp2_handle __attribute__((unused))) {
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_CRITICAL_EXIT);
}

void POMP2_Master_begin(
    POMP2_Region_handle* pomp2_handle __attribute__((unused)),
    const char ctc_string[] __attribute__((unused))) {
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_MASTER_BEGIN);
}

void POMP2_Master_end(POMP2_Region_handle* pomp2_handle __attribute__((unused))) {
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_MASTER_END);
}

void POMP2_Parallel_fork(
    POMP2_Region_handle* pomp2_handle __attribute__((unused)),
    int if_clause __attribute__((unused)),
    int num_threads __attribute__((unused)),
    POMP2_Task_handle* pomp2_old_task __attribute__((unused)),
    const char ctc_string[] __attribute__((unused))) {
  FUNCTION_ENTRY;
  *pomp2_handle = __get_next_section_id();
  POMP2_Region_handle section_id = *pomp2_handle;
  EZTRACE_EVENT_PACKED_1(EZTRACE_GOMP_PARALLEL_START, section_id);
}

void POMP2_Parallel_join(
    POMP2_Region_handle* pomp2_handle __attribute__((unused)),
    POMP2_Task_handle pomp2_old_task __attribute__((unused))) {
  /* the runtime function GOMP_parallel end already records an event at the end
   * of a join. We don't need to do anuthing here.
   */
  /* todo: what about non-GNU OpenMP ? */
}

void POMP2_For_enter(POMP2_Region_handle* pomp2_handle __attribute__((unused)),
		     const char ctc_string[] __attribute__((unused))) {
  FUNCTION_ENTRY;
  if(record_callsite) {
    char buffer[1024];
    eztrace_get_stack_frame(2, buffer, 1024);
    EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_FOR_ENTER_EXTRA);
    litl_write_probe_raw(__ezt_trace.litl_trace, EZTRACE_CALLING_FUNCTION, strlen(buffer)+1, buffer);
  } else {
    EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_FOR_ENTER);
  }
}

void POMP2_For_exit(POMP2_Region_handle* pomp2_handle __attribute__((unused))) {
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_FOR_EXIT);
}

void POMP2_Parallel_begin(
    POMP2_Region_handle* pomp2_handle __attribute__((unused))) {
  FUNCTION_ENTRY;
  int nb_threads = omp_get_num_threads();
  int my_id = omp_get_thread_num();
  POMP2_Region_handle section_id = *pomp2_handle;
  EZTRACE_EVENT_PACKED_3(EZTRACE_GOMP_NEW_FORK, section_id, my_id, nb_threads);
}

void POMP2_Parallel_end(
    POMP2_Region_handle* pomp2_handle __attribute__((unused))) {
  FUNCTION_ENTRY;
  int my_id = omp_get_thread_num();
  EZTRACE_EVENT_PACKED_1(EZTRACE_GOMP_NEW_JOIN, my_id);
}

void POMP2_Section_begin(
    POMP2_Region_handle* pomp2_handle __attribute__((unused)),
    const char ctc_string[] __attribute__((unused))) {
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_SECTION_BEGIN);
}

void POMP2_Section_end(
    POMP2_Region_handle* pomp2_handle __attribute__((unused))) {
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_SECTION_END);
}

void POMP2_Sections_enter(
    POMP2_Region_handle* pomp2_handle __attribute__((unused)),
    const char ctc_string[] __attribute__((unused))) {
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_SECTIONS_ENTER);
}

void POMP2_Sections_exit(
    POMP2_Region_handle* pomp2_handle __attribute__((unused))) {
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_SECTIONS_EXIT);
}

void POMP2_Single_begin(
    POMP2_Region_handle* pomp2_handle __attribute__((unused))) {
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_SINGLE_BEGIN);
}

void POMP2_Single_end(POMP2_Region_handle* pomp2_handle __attribute__((unused))) {
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_SINGLE_END);
}

void POMP2_Single_enter(
    POMP2_Region_handle* pomp2_handle __attribute__((unused)),
    const char ctc_string[] __attribute__((unused))) {
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_SINGLE_ENTER);
}

void POMP2_Single_exit(
    POMP2_Region_handle* pomp2_handle __attribute__((unused))) {
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_SINGLE_EXIT);
}

void POMP2_Workshare_enter(
    POMP2_Region_handle* pomp2_handle __attribute__((unused)),
    const char ctc_string[] __attribute__((unused))) {
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_WORKSHARE_ENTER);
}

void POMP2_Workshare_exit(
    POMP2_Region_handle* pomp2_handle __attribute__((unused))) {
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_WORKSHARE_EXIT);
}

void POMP2_Ordered_begin(
    POMP2_Region_handle* pomp2_handle __attribute__((unused))) {
  /** Called at the start of an ordered region.
   @param pomp2_handle  The handle of the region.
   */
  FUNCTION_ENTRY;
  // todo :   EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_ORDERED_BEGIN);
}

void POMP2_Ordered_end(
    POMP2_Region_handle* pomp2_handle __attribute__((unused))) {
  /* todo: implement that */
}

void POMP2_Ordered_enter(
    POMP2_Region_handle* pomp2_handle __attribute__((unused)),
    const char ctc_string[] __attribute__((unused))) {
  /* todo: implement that */
}

void POMP2_Ordered_exit(
    POMP2_Region_handle* pomp2_handle __attribute__((unused))) {
  /* todo: implement that */
}

/** \e OpenMP \e 3.0: When a task encounters a task construct it creates
 a new task. The task may be scheduled for later execution or
 executed immediately. In both cases the pomp-adapter assigns the
 id of the currently active task to \e pomp2_old_task which is
 defined in the instrumented user code.

 @param pomp2_handle   The handle of the region.
 @param pomp2_old_task Pointer to the task id in the instrumented user code
 @param pomp2_if       If an if clause is present on the task
 directive this variable holds the evaluated
 result of the argument of the if
 clause. Else it is 1.
 @param ctc_string     The initialization string.

 */
void POMP2_Task_create_begin(
    POMP2_Region_handle* pomp2_handle __attribute__((unused)),
    POMP2_Task_handle* pomp2_new_task __attribute__((unused)),
    POMP2_Task_handle* pomp2_old_task __attribute__((unused)),
    int pomp2_if __attribute__((unused)),
    const char ctc_string[] __attribute__((unused))) {
  FUNCTION_ENTRY;
  /* we could use pomp2_new_task to store the task id, but this would require a mutex
   * (or at least an atomic increment primitive) that would kill the performance.
   * In order to keep the overhead as low as possible, let's only record the event.
   */
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_TASK_CREATE_BEGIN);
}

void POMP2_Task_create_end(
    POMP2_Region_handle* pomp2_handle __attribute__((unused)),
    POMP2_Task_handle pomp2_old_task __attribute__((unused))) {
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_TASK_CREATE_END);
}

void POMP2_Task_begin(POMP2_Region_handle* pomp2_handle __attribute__((unused)),
		      POMP2_Task_handle pomp2_task __attribute__((unused))) {
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_TASK_BEGIN);
}

void POMP2_Task_end(POMP2_Region_handle* pomp2_handle __attribute__((unused))) {
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_TASK_END);
}

void POMP2_Untied_task_create_begin(
    POMP2_Region_handle* pomp2_handle __attribute__((unused)),
    POMP2_Task_handle* pomp2_new_task __attribute__((unused)),
    POMP2_Task_handle* pomp2_old_task __attribute__((unused)),
    int pomp2_if __attribute__((unused)),
    const char ctc_string[] __attribute__((unused))) {
  FUNCTION_ENTRY;
  /* we could use pomp2_new_task to store the task id, but this would require a mutex
   * (or at least an atomic increment primitive) that would kill the performance.
   * In order to keep the overhead as low as possible, let's only record the event.
   */
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_UNTIED_TASK_CREATE_BEGIN);
}

void POMP2_Untied_task_create_end(
    POMP2_Region_handle* pomp2_handle __attribute__((unused)),
    POMP2_Task_handle pomp2_old_task __attribute__((unused))) {
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_UNTIED_TASK_CREATE_END);
}

void POMP2_Untied_task_begin(
    POMP2_Region_handle* pomp2_handle __attribute__((unused)),
    POMP2_Task_handle pomp2_parent_task __attribute__((unused))) {
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_UNTIED_TASK_BEGIN);
}

void POMP2_Untied_task_end(
    POMP2_Region_handle* pomp2_handle __attribute__((unused))) {
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_UNTIED_TASK_END);
}

void POMP2_Taskwait_begin(
    POMP2_Region_handle* pomp2_handle __attribute__((unused)),
    POMP2_Task_handle* pomp2_old_task __attribute__((unused)),
    const char ctc_string[] __attribute__((unused))) {
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_TASKWAIT_BEGIN);
}

void POMP2_Taskwait_end(
    POMP2_Region_handle* pomp2_handle __attribute__((unused)),
    POMP2_Task_handle pomp2_old_task __attribute__((unused))) {
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_0(EZTRACE_POMP2_TASKWAIT_END);
}

#ifdef OPENMP_FOUND

void
POMP2_Init_lock( omp_lock_t* s )
{
    omp_init_lock(s);
}

void
POMP2_Destroy_lock( omp_lock_t* s )
{
    omp_destroy_lock(s);
}

void
POMP2_Set_lock( omp_lock_t* s )
{
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_1(EZTRACE_POMP2_SET_LOCK_ENTRY, s);
  omp_set_lock(s);
  EZTRACE_EVENT_PACKED_1(EZTRACE_POMP2_SET_LOCK_EXIT, s);
}

void
POMP2_Unset_lock( omp_lock_t* s )
{
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_1(EZTRACE_POMP2_UNSET_LOCK, s);
  omp_unset_lock(s);
}

int
POMP2_Test_lock( omp_lock_t* s )
{
  int ret = omp_test_lock(s);
  if(ret) {
    FUNCTION_ENTRY;
    EZTRACE_EVENT_PACKED_1(EZTRACE_POMP2_TEST_LOCK_SUCCESS, s);
  }
  return ret;
}

void
POMP2_Init_nest_lock( omp_nest_lock_t* s )
{
    omp_init_nest_lock(s);
}

void
POMP2_Destroy_nest_lock( omp_nest_lock_t* s )
{
    omp_destroy_nest_lock(s);
}

void
POMP2_Set_nest_lock( omp_nest_lock_t* s )
{
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_1(EZTRACE_POMP2_SET_NEST_LOCK_ENTRY, s);
  omp_set_nest_lock(s);
  EZTRACE_EVENT_PACKED_1(EZTRACE_POMP2_SET_NEST_LOCK_EXIT, s);
}

void
POMP2_Unset_nest_lock( omp_nest_lock_t* s )
{
  FUNCTION_ENTRY;
  EZTRACE_EVENT_PACKED_1(EZTRACE_POMP2_UNSET_NEST_LOCK, s);
  omp_unset_nest_lock(s);
}

int
POMP2_Test_nest_lock( omp_nest_lock_t* s )
{
  int ret = omp_test_nest_lock(s);
  if(ret) {
    FUNCTION_ENTRY;
    EZTRACE_EVENT_PACKED_1(EZTRACE_POMP2_TEST_NEST_LOCK_SUCCESS, s);
  }
  return ret;
}

#endif	/* OPENMP_FOUND */

void (*libPOMP2_Finalize)();

START_INTERCEPT_MODULE(omp)
  INTERCEPT2("GOMP_parallel_start", libGOMP_parallel_start)
  INTERCEPT2("GOMP_parallel_end", libGOMP_parallel_end)
  INTERCEPT2("GOMP_parallel_loop_static_start", libGOMP_parallel_loop_static_start)
  INTERCEPT2("GOMP_parallel_loop_runtime_start", libGOMP_parallel_loop_runtime_start)
  INTERCEPT2("GOMP_parallel_loop_dynamic_start", libGOMP_parallel_loop_dynamic_start)
  INTERCEPT2("GOMP_parallel_loop_guided_start", libGOMP_parallel_loop_guided_start)

  INTERCEPT2("GOMP_critical_start", libGOMP_critical_start)
  INTERCEPT2("GOMP_critical_end", libGOMP_critical_end)

  INTERCEPT2("GOMP_parallel_loop_static", libGOMP_parallel_loop_static)
  INTERCEPT2("GOMP_parallel_loop_dynamic",libGOMP_parallel_loop_dynamic)
  INTERCEPT2("GOMP_parallel_loop_guided", libGOMP_parallel_loop_guided)
  INTERCEPT2("GOMP_parallel_loop_runtime",libGOMP_parallel_loop_runtime)
END_INTERCEPT_MODULE(omp)

void __gomp_init(void) __attribute__ ((constructor));
void __gomp_init(void) {

  DYNAMIC_INTERCEPT_ALL_MODULE(omp);
  /* This symbol is only available if the program was compiled with eztrace_cc.
   * Use this information as a test and print a warning message.
   */
  INTERCEPT("POMP2_Finalize", libPOMP2_Finalize);

  if (libGOMP_critical_end && !libPOMP2_Finalize) {
    printf(
	"Only GNU OpenMP runtime functions will be intercepted. For a more precise trace, please instrument your program with eztrace_cc.\n");
    use_pomp = 0;
  }

  if (libPOMP2_Finalize)
    pomp2_found = 1;
  else
    pomp2_found = 0;

  char* str = getenv("OMP_RECORD_CALLSITE");
  if(str && strcmp(str, "0")!=0) {
    printf("EZTrace OpenMP: callsite recording enabled\n");
    record_callsite = 1;
  }
#ifdef EZTRACE_AUTOSTART
    eztrace_start ();
#endif
}

void __gomp_conclude(void) __attribute__ ((destructor));
void __gomp_conclude(void) {
  eztrace_stop();
}
