/**
 * VampirTrace
 * http://www.tu-dresden.de/zih/vampirtrace
 *
 * Copyright (c) 2005-2013, ZIH, TU Dresden, Federal Republic of Germany
 *
 * Copyright (c) 1998-2005, Forschungszentrum Juelich, Juelich Supercomputing
 *                          Centre, Federal Republic of Germany
 *
 * See the file COPYING in the package base directory for details
 **/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "vt_comp.h"
#include "vt_defs.h"
#include "vt_error.h"
#include "vt_mallocwrap.h"
#include "vt_pform.h"
#include "vt_thrd.h"
#include "vt_trc.h"

/*
 * Macro for getting id of calling thread
 */

#define GET_THREAD_ID(tid) \
  VT_CHECK_THREAD;         \
  (tid) = VT_MY_THREAD

/*
 *-----------------------------------------------------------------------------
 * Simple hash table to map function data to region identifier
 *-----------------------------------------------------------------------------
 */

typedef struct HN {
  long id;            /* hash code (address of function name) */
  uint32_t vtid;      /* associated region identifier  */
  char* func;
  char* file;
  int lno;
  struct HN* next;
} HashNode;

#define HASH_MAX 1021

static int xl_init = 1;       /* is initialization needed? */

static HashNode* htab[HASH_MAX];

#if __IBMC__ > 1100
static HashNode ignored_func = { 0, VT_NO_ID, NULL, NULL, 0, NULL };
#endif /* __IBMC__ */

/*
 * Stores region identifier `e' under hash code `h'
 */

static HashNode *hash_put(long h, uint32_t e) {
  long id = h % HASH_MAX;
  HashNode *add = (HashNode*)malloc(sizeof(HashNode));
  add->id = h;
  add->vtid = e;
  add->next = htab[id];
  htab[id] = add;
  return add;
}

/*
 * Lookup hash code `h'
 * Returns pointer to function data if already stored, otherwise 0
 */

static HashNode *hash_get(long h) {
  long id = h % HASH_MAX;
  HashNode *curr = htab[id];
  while ( curr ) {
    if ( curr->id == h ) {
      return curr;
    }
    curr = curr->next;
  }
  return 0;
}

/*
 * Register new region
 */

static HashNode *register_region(uint32_t tid, char *func, char *file,
                                 int lno) {
  uint32_t rid;
  uint32_t fid;
  HashNode* nhn;

  /* -- register file and region and store region identifier -- */
  fid = vt_def_scl_file(tid, file);
  rid = vt_def_region(tid, func, fid, lno, VT_NO_LNO, NULL, VT_FUNCTION);
  nhn = hash_put((long) func, rid);
  nhn->func = func;
  nhn->file = file;
  nhn->lno  = lno;
  return nhn;
}

void xl_finalize(void);
#if __IBMC__ > 1100
void __func_trace_enter(char* name, char* fname, int lno, HashNode** ihn);
void __func_trace_exit(char* name, char* fname, int lno, HashNode** ihn);
#else /* __IBMC__ */
void __func_trace_enter(char* name, char* fname, int lno);
void __func_trace_exit(char* name, char* fname, int lno);
#endif /* __IBMC__ */

/*
 * Finalize instrumentation interface
 */

void xl_finalize()
{
  int i;

  for ( i = 0; i < HASH_MAX; i++ )
  {
    if ( htab[i] ) {
      free(htab[i]);
      htab[i] = NULL;
    }
  }
  xl_init = 1;
}

/*
 * This function is called at the entry of each function
 * The call is generated by the IBM xl compilers
 *
 * XL C/C++ 11.x / XLF 13.x or later pass the address of a static pointer
 * variable, initialized with NULL, as additional parameter.
 */

#if __IBMC__ > 1100
void __func_trace_enter(char* name, char* fname, int lno, HashNode** ihn)
#else /* __IBMC__ */
void __func_trace_enter(char* name, char* fname, int lno)
#endif /* __IBMC__ */
{
  HashNode *hn;
  uint32_t tid;
  uint64_t time;

  /* -- ignore IBM OMP runtime functions -- */
# if __IBMC__ > 1100
  if ( *ihn == &ignored_func ) return;
# endif /* __IBMC__ */
  if ( ( strchr(name, '@') != NULL ) || (strchr(name, '$') != NULL) ) {
#   if __IBMC__ > 1100
    *ihn = &ignored_func;
#   endif /* __IBMC__ */
    return;
  }

  /* -- if not yet initialized, initialize VampirTrace -- */
  if ( xl_init ) {
    xl_init = 0;
    vt_open();
    vt_comp_finalize = &xl_finalize;
  }

  /* -- if VampirTrace already finalized, return -- */
  if ( !vt_is_alive ) return;

  /* -- get calling thread id -- */
  GET_THREAD_ID(tid);

  VT_SUSPEND_MALLOC_TRACING(tid);

  time = vt_pform_wtime();

# if __IBMC__ > 1100
  /* -- region not yet registered -- */
  if ( *ihn == NULL ) {
# endif /* __IBMC__ */

  /* -- get region identifier -- */
  if ( (hn = hash_get((long) name)) == 0 ) {
    /* -- region entered the first time, register region -- */
#if (defined(VT_MT) || defined(VT_HYB))
    VTTHRD_LOCK_IDS();
    if ( (hn = hash_get((long) name)) == 0 ) {
      hn = register_region(tid, name, fname, lno);
#     if __IBMC__ > 1100
      *ihn = hn;
#     endif /* __IBMC__ */
    }
    VTTHRD_UNLOCK_IDS();
#else /* VT_MT || VT_HYB */
    hn = register_region(tid, name, fname, lno);
#   if __IBMC__ > 1100
    *ihn = hn;
#   endif /* __IBMC__ */
#endif /* VT_MT || VT_HYB */
  }

#if __IBMC__ > 1100
  } else {
    /* -- region already registered -- */
    hn = *ihn;
  }
#endif /* __IBMC__ */

  /* -- write enter record -- */
  vt_enter(tid, &time, hn->vtid);

  VT_RESUME_MALLOC_TRACING(tid);
}

/*
 * This function is called at the exit of each function
 * The call is generated by the IBM xl compilers
 *
 * XL C/C++ 11.x / XLF 13.x or later pass the address of a static pointer
 * variable, initialized with NULL, as additional parameter.
 */

#if __IBMC__ > 1100
void __func_trace_exit(char* name, char* fname, int lno, HashNode** ihn)
#else
void __func_trace_exit(char* name, char* fname, int lno)
#endif
{
  HashNode *hn;
  uint32_t tid;
  uint64_t time;

  /* -- if VampirTrace already finalized, return -- */
  if ( !vt_is_alive ) return;

# if __IBMC__ > 1100
  vt_libassert(*ihn != NULL);

  /* -- ignore IBM OMP runtime functions -- */
  if ( *ihn == &ignored_func ) return;

  hn = *ihn;
# else /* __IBMC__ */
  /* -- ignore IBM OMP runtime functions -- */
  if ( ( strchr(name, '@') != NULL ) || (strchr(name, '$') != NULL) )
    return;

  hn = hash_get((long) name);
  vt_libassert(hn != NULL);
# endif /* __IBMC__ */

  /* -- get calling thread id -- */
  GET_THREAD_ID(tid);

  VT_SUSPEND_MALLOC_TRACING(tid);

  time = vt_pform_wtime();

  /* -- write exit record -- */
  vt_exit(tid, &time);

  VT_RESUME_MALLOC_TRACING(tid);
}
