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

#ifndef _REENTRANT
#define _REENTRANT
#endif


/* This plugin relies on compiler instrumentation
 * You can ask the compiler to instrument functions using -finstrument-functions (you may need to link with -rdynamic too)
 * In this case, the compiler calls __cyg_profile_func_enter/__cyg_profile_func_exit
 * at the entry/exit of instrumented functions.
 *
 * To control which functions should be traced, you can:
 * - specify to the compiler the functions to exclude (eg with -finstrument-functions-exclude-function-list=sym,sym,...)
 * - specify to EZTrace the functions to exclude with (eg set EZTRACE_EXCLUDE_LIST="sym1 sym2 ...")
 */


#include <eztrace-core/eztrace_config.h>
#include <eztrace-core/eztrace_attributes.h>
#include <eztrace-instrumentation/pptrace.h>
#include <eztrace-lib/eztrace.h>
#include <eztrace-lib/eztrace_module.h>
#include <pthread.h>

/* set to 1 when all the hooks are set.
 * This is usefull in order to avoid recursive calls to mutex_lock for example
 */
static volatile int _compiler_instrumentation_initialized = 0;

#define CURRENT_MODULE compiler_instrumentation
DECLARE_CURRENT_MODULE;



static pthread_mutex_t instrumented_functions_lock = PTHREAD_MUTEX_INITIALIZER;
static struct ezt_instrumented_function* instrumented_functions = NULL;
static int nb_instrumented_functions = 0;
static int nb_alloc_instrumented_functions = 0;

static int function_in_exclude_list(const char* function_name);
static struct ezt_instrumented_function* find_instrumented_function_from_addr(void* addr);

/* Wrapper functions */



/* This function is called at the begining of instrumented functions */
void __cyg_profile_func_enter (void *this_fn,
			       void *call_site MAYBE_UNUSED) {
  if(!  _compiler_instrumentation_initialized ) return;

  struct ezt_instrumented_function* f = find_instrumented_function_from_addr(this_fn);
  if(f && f->event_id >= 0) {
    if(todo_get_status("eztrace") == init_complete &&
       todo_get_status("ezt_otf2") == init_complete &&
       _ezt_trace.status < ezt_trace_status_being_finalized) {
    EZT_OTF2_EvtWriter_Enter(evt_writer,
			     NULL,
			     ezt_get_timestamp(),
			     f->event_id);

    }
  }
}

/* This function is called at the end of instrumented functions */
void __cyg_profile_func_exit  (void *this_fn,
			       void *call_site MAYBE_UNUSED) {
  if(!  _compiler_instrumentation_initialized ) return;

  struct ezt_instrumented_function* f = find_instrumented_function_from_addr(this_fn);
  if(f && f->event_id>=0) {
    if(todo_get_status("eztrace") == init_complete &&
       todo_get_status("ezt_otf2") == init_complete &&
       _ezt_trace.status < ezt_trace_status_being_finalized) {
      EZT_OTF2_EvtWriter_Leave(evt_writer,
			       NULL,
			       ezt_get_timestamp(),
			       f->event_id);

    }
  }
}




PPTRACE_START_INTERCEPT_FUNCTIONS(compiler_instrumentation)

PPTRACE_END_INTERCEPT_FUNCTIONS(compiler_instrumentation)

static char **excluded_functions = NULL;
static int nb_excluded_functions = 0;

static void init_exclusion_list() {
  char* env_str = getenv("EZTRACE_EXCLUDE_LIST ");
  if(!env_str)
    return ;
  env_str = strdup(env_str);
  char* save_ptr = NULL;
  char* token = strtok_r(env_str, " ", &save_ptr);
  while(token) {
    int index = nb_excluded_functions++;
    excluded_functions = realloc(excluded_functions, sizeof(char*) * nb_excluded_functions);
    excluded_functions[index] = strdup(token);

    eztrace_log(dbg_lvl_debug, "[EZTrace:compiler instrumentation] I will not trace %s\n", excluded_functions[index]);

    printf("exclusion_list[%d]: %s\n", index, excluded_functions[index]);
    token = strtok_r(NULL, " ", &save_ptr);
  }
  free(env_str);
}

static int function_in_exclude_list(const char* function_name) {
  for(int i=0; i<nb_excluded_functions; i++) {
    if(strcmp(excluded_functions[i], function_name) == 0)
      return 1;
  }
  return 0;
}

static struct ezt_instrumented_function*
find_instrumented_function_from_addr(void* addr) {
  for(int i=0; i< nb_instrumented_functions; i++) {
    if(instrumented_functions[i].callback == addr)
      return &instrumented_functions[i];
  }

  if(todo_get_status("eztrace") == init_complete &&
     todo_get_status("ezt_otf2") == init_complete &&
     _ezt_trace.status < ezt_trace_status_being_finalized) {

    EZTRACE_PROTECT {
      EZTRACE_PROTECT_ON();

      struct ezt_instrumented_function* f = NULL;
      /* not found */
      pthread_mutex_lock(&instrumented_functions_lock);
      /* check again */
      for(int i=0; i< nb_instrumented_functions; i++) {
	if(instrumented_functions[i].callback == addr) {
	  f = &instrumented_functions[i];
	  goto out;
	}
      }

      /* we are sure that the function is unknown. add it to the list of known functions */
      Dl_info info_fn;
      dladdr(addr, &info_fn);
      if(! info_fn.dli_sname) {
	goto out_err;
      }

      if(nb_alloc_instrumented_functions == 0) {
	nb_alloc_instrumented_functions = 1024;
	instrumented_functions = malloc(sizeof(struct ezt_instrumented_function) * nb_alloc_instrumented_functions);
      } else if (nb_instrumented_functions >= nb_alloc_instrumented_functions) {
	nb_alloc_instrumented_functions *= 2;
	instrumented_functions = realloc(instrumented_functions,
					 sizeof(struct ezt_instrumented_function) * nb_alloc_instrumented_functions);
	if(!instrumented_functions) {
	  eztrace_error("[EZTrace:compiler instrumentation]  cannot allocate memory\n");
	}
      }

      f = &instrumented_functions[nb_instrumented_functions++];
      f->callback = addr;
      strncpy(f->function_name, info_fn.dli_sname, sizeof(f->function_name));
      if(function_in_exclude_list(f->function_name)) {
	eztrace_log(dbg_lvl_debug, "[EZTrace:compiler instrumentation] Do not instrument %s as it is excluded\n", f->function_name);
	f->event_id = -1;
      } else {
	f->event_id = ezt_otf2_register_function(f->function_name);
      }
    out:
      pthread_mutex_unlock(&instrumented_functions_lock);
      EZTRACE_PROTECT_OFF();	  
      return f;
    out_err:
      f = NULL;
      goto out;
    }
  }
  return NULL;
}

static void init_compiler_instrumentation() {
  INSTRUMENT_FUNCTIONS(compiler_instrumentation);

  init_exclusion_list();

  if (eztrace_autostart_enabled())
    eztrace_start();

  _compiler_instrumentation_initialized = 1;
}

static void finalize_compiler_instrumentation() {
  _compiler_instrumentation_initialized = 0;

  eztrace_stop();
}

static void _compiler_instrumentation_init(void) __attribute__((constructor));
static void _compiler_instrumentation_init(void) {
  eztrace_log(dbg_lvl_debug, "eztrace_compiler_instrumentation constructor starts\n");
  EZT_REGISTER_MODULE(compiler_instrumentation, "Module for tracing compiler-instrumented compilers",
		      init_compiler_instrumentation, finalize_compiler_instrumentation);
  eztrace_log(dbg_lvl_debug, "eztrace_compiler_instrumentation constructor ends\n");
}
