/*===-- jitprofiling.c - JIT (Just-In-Time) Profiling API----------*- C -*-===*
 *
 * Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 * See https://llvm.org/LICENSE.txt for license information.
 * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 *
 *===----------------------------------------------------------------------===*
 *
 * This file provides Intel(R) Performance Analyzer JIT (Just-In-Time) 
 * Profiling API implementation. 
 *
 * NOTE: This file comes in a style different from the rest of LLVM
 * source base since  this is a piece of code shared from Intel(R)
 * products.  Please do not reformat / re-style this code to make
 * subsequent merges and contributions from the original source base eaiser.
 *
 *===----------------------------------------------------------------------===*/
#include "ittnotify_config.h"

#if ITT_PLATFORM==ITT_PLATFORM_WIN
#include <windows.h>
#pragma optimize("", off)
#else  /* ITT_PLATFORM==ITT_PLATFORM_WIN */
#include <dlfcn.h>
#include <pthread.h>
#include <stdint.h>
#endif /* ITT_PLATFORM==ITT_PLATFORM_WIN */
#include <stdlib.h>

#include "jitprofiling.h"

static const char rcsid[] = "\n@(#) $Revision: 243501 $\n";

#define DLL_ENVIRONMENT_VAR             "VS_PROFILER"

#ifndef NEW_DLL_ENVIRONMENT_VAR
#if ITT_ARCH==ITT_ARCH_IA32
#define NEW_DLL_ENVIRONMENT_VAR	        "INTEL_JIT_PROFILER32"
#else
#define NEW_DLL_ENVIRONMENT_VAR	        "INTEL_JIT_PROFILER64"
#endif
#endif /* NEW_DLL_ENVIRONMENT_VAR */

#if ITT_PLATFORM==ITT_PLATFORM_WIN
#define DEFAULT_DLLNAME                 "JitPI.dll"
HINSTANCE m_libHandle = NULL;
#else  /* ITT_PLATFORM==ITT_PLATFORM_WIN */
#define DEFAULT_DLLNAME                 "libJitPI.so"
void* m_libHandle = NULL;
#endif /* ITT_PLATFORM==ITT_PLATFORM_WIN */

/* default location of JIT profiling agent on Android */
#define ANDROID_JIT_AGENT_PATH  "/data/intel/libittnotify.so"

/* the function pointers */
typedef unsigned int(*TPInitialize)(void);
static TPInitialize FUNC_Initialize=NULL;

typedef unsigned int(*TPNotify)(unsigned int, void*);
static TPNotify FUNC_NotifyEvent=NULL;

static iJIT_IsProfilingActiveFlags executionMode = iJIT_NOTHING_RUNNING;

/* end collector dll part. */

/* loadiJIT_Funcs() : this function is called just in the beginning 
 *  and is responsible to load the functions from BistroJavaCollector.dll
 * result:
 *  on success: the functions loads, iJIT_DLL_is_missing=0, return value = 1
 *  on failure: the functions are NULL, iJIT_DLL_is_missing=1, return value = 0
 */ 
static int loadiJIT_Funcs(void);

/* global representing whether the BistroJavaCollector can't be loaded */
static int iJIT_DLL_is_missing = 0;

/* Virtual stack - the struct is used as a virtual stack for each thread.
 * Every thread initializes with a stack of size INIT_TOP_STACK.
 * Every method entry decreases from the current stack point,
 * and when a thread stack reaches its top of stack (return from the global 
 * function), the top of stack and the current stack increase. Notice that 
 * when returning from a function the stack pointer is the address of 
 * the function return.
*/
#if ITT_PLATFORM==ITT_PLATFORM_WIN
static DWORD threadLocalStorageHandle = 0;
#else  /* ITT_PLATFORM==ITT_PLATFORM_WIN */
static pthread_key_t threadLocalStorageHandle = (pthread_key_t)0;
#endif /* ITT_PLATFORM==ITT_PLATFORM_WIN */

#define INIT_TOP_Stack 10000

typedef struct 
{
    unsigned int TopStack;
    unsigned int CurrentStack;
} ThreadStack, *pThreadStack;

/* end of virtual stack. */

/*
 * The function for reporting virtual-machine related events to VTune.
 * Note: when reporting iJVM_EVENT_TYPE_ENTER_NIDS, there is no need to fill 
 * in the stack_id field in the iJIT_Method_NIDS structure, as VTune fills it.
 * The return value in iJVM_EVENT_TYPE_ENTER_NIDS && 
 * iJVM_EVENT_TYPE_LEAVE_NIDS events will be 0 in case of failure.
 * in iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED event 
 * it will be -1 if EventSpecificData == 0 otherwise it will be 0.
*/

ITT_EXTERN_C int JITAPI 
iJIT_NotifyEvent(iJIT_JVM_EVENT event_type, void *EventSpecificData)
{
    int ReturnValue;

    /*
     * This section is for debugging outside of VTune. 
     * It creates the environment variables that indicates call graph mode.
     * If running outside of VTune remove the remark.
     *
     *
     * static int firstTime = 1;
     * char DoCallGraph[12] = "DoCallGraph";
     * if (firstTime)
     * {
     * firstTime = 0;
     * SetEnvironmentVariable( "BISTRO_COLLECTORS_DO_CALLGRAPH", DoCallGraph);
     * }
     *
     * end of section.
    */

    /* initialization part - the functions have not been loaded yet. This part
     *        will load the functions, and check if we are in Call Graph mode. 
     *        (for special treatment).
     */
    if (!FUNC_NotifyEvent) 
    {
        if (iJIT_DLL_is_missing) 
            return 0;

        /* load the Function from the DLL */
        if (!loadiJIT_Funcs()) 
            return 0;

        /* Call Graph initialization. */
    }

    /* If the event is method entry/exit, check that in the current mode 
     * VTune is allowed to receive it
     */
    if ((event_type == iJVM_EVENT_TYPE_ENTER_NIDS || 
         event_type == iJVM_EVENT_TYPE_LEAVE_NIDS) &&
        (executionMode != iJIT_CALLGRAPH_ON))
    {
        return 0;
    }
    /* This section is performed when method enter event occurs.
     * It updates the virtual stack, or creates it if this is the first 
     * method entry in the thread. The stack pointer is decreased.
     */
    if (event_type == iJVM_EVENT_TYPE_ENTER_NIDS)
    {
#if ITT_PLATFORM==ITT_PLATFORM_WIN
        pThreadStack threadStack = 
            (pThreadStack)TlsGetValue (threadLocalStorageHandle);
#else  /* ITT_PLATFORM==ITT_PLATFORM_WIN */
        pThreadStack threadStack = 
            (pThreadStack)pthread_getspecific(threadLocalStorageHandle);
#endif /* ITT_PLATFORM==ITT_PLATFORM_WIN */

        /* check for use of reserved method IDs */
        if ( ((piJIT_Method_NIDS) EventSpecificData)->method_id <= 999 )
            return 0;

        if (!threadStack)
        {
            /* initialize the stack. */
            threadStack = (pThreadStack) calloc (sizeof(ThreadStack), 1);
            threadStack->TopStack = INIT_TOP_Stack;
            threadStack->CurrentStack = INIT_TOP_Stack;
#if ITT_PLATFORM==ITT_PLATFORM_WIN
            TlsSetValue(threadLocalStorageHandle,(void*)threadStack);
#else  /* ITT_PLATFORM==ITT_PLATFORM_WIN */
            pthread_setspecific(threadLocalStorageHandle,(void*)threadStack);
#endif /* ITT_PLATFORM==ITT_PLATFORM_WIN */
        }

        /* decrease the stack. */
        ((piJIT_Method_NIDS) EventSpecificData)->stack_id = 
            (threadStack->CurrentStack)--;
    }

    /* This section is performed when method leave event occurs
     * It updates the virtual stack.
     *    Increases the stack pointer.
     *    If the stack pointer reached the top (left the global function)
     *        increase the pointer and the top pointer.
     */
    if (event_type == iJVM_EVENT_TYPE_LEAVE_NIDS)
    {
#if ITT_PLATFORM==ITT_PLATFORM_WIN
        pThreadStack threadStack = 
           (pThreadStack)TlsGetValue (threadLocalStorageHandle);
#else  /* ITT_PLATFORM==ITT_PLATFORM_WIN */
        pThreadStack threadStack = 
            (pThreadStack)pthread_getspecific(threadLocalStorageHandle);
#endif /* ITT_PLATFORM==ITT_PLATFORM_WIN */

        /* check for use of reserved method IDs */
        if ( ((piJIT_Method_NIDS) EventSpecificData)->method_id <= 999 )
            return 0;

        if (!threadStack)
        {
            /* Error: first report in this thread is method exit */
            exit (1);
        }

        ((piJIT_Method_NIDS) EventSpecificData)->stack_id = 
            ++(threadStack->CurrentStack) + 1;

        if (((piJIT_Method_NIDS) EventSpecificData)->stack_id 
               > threadStack->TopStack)
            ((piJIT_Method_NIDS) EventSpecificData)->stack_id = 
                (unsigned int)-1;
    }

    if (event_type == iJVM_EVENT_TYPE_METHOD_LOAD_FINISHED)
    {
        /* check for use of reserved method IDs */
        if ( ((piJIT_Method_Load) EventSpecificData)->method_id <= 999 )
            return 0;
    }

    ReturnValue = (int)FUNC_NotifyEvent(event_type, EventSpecificData);   

    return ReturnValue;
}

/* The new mode call back routine */
ITT_EXTERN_C void JITAPI 
iJIT_RegisterCallbackEx(void *userdata, iJIT_ModeChangedEx 
                        NewModeCallBackFuncEx) 
{
    /* is it already missing... or the load of functions from the DLL failed */
    if (iJIT_DLL_is_missing || !loadiJIT_Funcs())
    {
        /* then do not bother with notifications */
        NewModeCallBackFuncEx(userdata, iJIT_NO_NOTIFICATIONS);  
        /* Error: could not load JIT functions. */
        return;
    }
    /* nothing to do with the callback */
}

/*
 * This function allows the user to query in which mode, if at all, 
 *VTune is running
 */
ITT_EXTERN_C iJIT_IsProfilingActiveFlags JITAPI iJIT_IsProfilingActive()
{
    if (!iJIT_DLL_is_missing)
    {
        loadiJIT_Funcs();
    }

    return executionMode;
}

/* this function loads the collector dll (BistroJavaCollector) 
 * and the relevant functions.
 * on success: all functions load,     iJIT_DLL_is_missing = 0, return value = 1
 * on failure: all functions are NULL, iJIT_DLL_is_missing = 1, return value = 0
 */ 
static int loadiJIT_Funcs()
{
    static int bDllWasLoaded = 0;
    char *dllName = (char*)rcsid; /* !! Just to avoid unused code elimination */
#if ITT_PLATFORM==ITT_PLATFORM_WIN
    DWORD dNameLength = 0;
#endif /* ITT_PLATFORM==ITT_PLATFORM_WIN */

    if(bDllWasLoaded)
    {
        /* dll was already loaded, no need to do it for the second time */
        return 1;
    }

    /* Assumes that the DLL will not be found */
    iJIT_DLL_is_missing = 1;
    FUNC_NotifyEvent = NULL;

    if (m_libHandle) 
    {
#if ITT_PLATFORM==ITT_PLATFORM_WIN
        FreeLibrary(m_libHandle);
#else  /* ITT_PLATFORM==ITT_PLATFORM_WIN */
        dlclose(m_libHandle);
#endif /* ITT_PLATFORM==ITT_PLATFORM_WIN */
        m_libHandle = NULL;
    }

    /* Try to get the dll name from the environment */
#if ITT_PLATFORM==ITT_PLATFORM_WIN
    dNameLength = GetEnvironmentVariableA(NEW_DLL_ENVIRONMENT_VAR, NULL, 0);
    if (dNameLength)
    {
        DWORD envret = 0;
        dllName = (char*)malloc(sizeof(char) * (dNameLength + 1));
        envret = GetEnvironmentVariableA(NEW_DLL_ENVIRONMENT_VAR, 
                                         dllName, dNameLength);
        if (envret)
        {
            /* Try to load the dll from the PATH... */
            m_libHandle = LoadLibraryExA(dllName, 
                                         NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
        }
        free(dllName);
    } else {
        /* Try to use old VS_PROFILER variable */
        dNameLength = GetEnvironmentVariableA(DLL_ENVIRONMENT_VAR, NULL, 0);
        if (dNameLength)
        {
            DWORD envret = 0;
            dllName = (char*)malloc(sizeof(char) * (dNameLength + 1));
            envret = GetEnvironmentVariableA(DLL_ENVIRONMENT_VAR, 
                                             dllName, dNameLength);
            if (envret)
            {
                /* Try to load the dll from the PATH... */
                m_libHandle = LoadLibraryA(dllName);
            }
            free(dllName);
        }
    }
#else  /* ITT_PLATFORM==ITT_PLATFORM_WIN */
    dllName = getenv(NEW_DLL_ENVIRONMENT_VAR);
    if (!dllName)
        dllName = getenv(DLL_ENVIRONMENT_VAR);
#ifdef ANDROID
    if (!dllName)
        dllName = ANDROID_JIT_AGENT_PATH;
#endif
    if (dllName)
    {
        /* Try to load the dll from the PATH... */
        m_libHandle = dlopen(dllName, RTLD_LAZY);
    }
#endif /* ITT_PLATFORM==ITT_PLATFORM_WIN */

    if (!m_libHandle)
    {
#if ITT_PLATFORM==ITT_PLATFORM_WIN
        m_libHandle = LoadLibraryA(DEFAULT_DLLNAME);
#else  /* ITT_PLATFORM==ITT_PLATFORM_WIN */
        m_libHandle = dlopen(DEFAULT_DLLNAME, RTLD_LAZY);
#endif /* ITT_PLATFORM==ITT_PLATFORM_WIN */
    }

    /* if the dll wasn't loaded - exit. */
    if (!m_libHandle)
    {
        iJIT_DLL_is_missing = 1; /* don't try to initialize 
                                  * JIT agent the second time 
                                  */
        return 0;
    }

#if ITT_PLATFORM==ITT_PLATFORM_WIN
    FUNC_NotifyEvent = (TPNotify)GetProcAddress(m_libHandle, "NotifyEvent");
#else  /* ITT_PLATFORM==ITT_PLATFORM_WIN */
    FUNC_NotifyEvent = (TPNotify)(intptr_t)dlsym(m_libHandle, "NotifyEvent");
#endif /* ITT_PLATFORM==ITT_PLATFORM_WIN */
    if (!FUNC_NotifyEvent) 
    {
        FUNC_Initialize = NULL;
        return 0;
    }

#if ITT_PLATFORM==ITT_PLATFORM_WIN
    FUNC_Initialize = (TPInitialize)GetProcAddress(m_libHandle, "Initialize");
#else  /* ITT_PLATFORM==ITT_PLATFORM_WIN */
    FUNC_Initialize = (TPInitialize)(intptr_t)dlsym(m_libHandle, "Initialize");
#endif /* ITT_PLATFORM==ITT_PLATFORM_WIN */
    if (!FUNC_Initialize) 
    {
        FUNC_NotifyEvent = NULL;
        return 0;
    }

    executionMode = (iJIT_IsProfilingActiveFlags)FUNC_Initialize();

    bDllWasLoaded = 1;
    iJIT_DLL_is_missing = 0; /* DLL is ok. */

    /*
     * Call Graph mode: init the thread local storage
     * (need to store the virtual stack there).
     */
    if ( executionMode == iJIT_CALLGRAPH_ON )
    {
        /* Allocate a thread local storage slot for the thread "stack" */
        if (!threadLocalStorageHandle)
#if ITT_PLATFORM==ITT_PLATFORM_WIN
            threadLocalStorageHandle = TlsAlloc();
#else  /* ITT_PLATFORM==ITT_PLATFORM_WIN */
        pthread_key_create(&threadLocalStorageHandle, NULL);
#endif /* ITT_PLATFORM==ITT_PLATFORM_WIN */
    }

    return 1;
}

/*
 * This function should be called by the user whenever a thread ends, 
 * to free the thread "virtual stack" storage
 */
ITT_EXTERN_C void JITAPI FinalizeThread()
{
    if (threadLocalStorageHandle)
    {
#if ITT_PLATFORM==ITT_PLATFORM_WIN
        pThreadStack threadStack = 
            (pThreadStack)TlsGetValue (threadLocalStorageHandle);
#else  /* ITT_PLATFORM==ITT_PLATFORM_WIN */
        pThreadStack threadStack = 
            (pThreadStack)pthread_getspecific(threadLocalStorageHandle);
#endif /* ITT_PLATFORM==ITT_PLATFORM_WIN */
        if (threadStack)
        {
            free (threadStack);
            threadStack = NULL;
#if ITT_PLATFORM==ITT_PLATFORM_WIN
            TlsSetValue (threadLocalStorageHandle, threadStack);
#else  /* ITT_PLATFORM==ITT_PLATFORM_WIN */
            pthread_setspecific(threadLocalStorageHandle, threadStack);
#endif /* ITT_PLATFORM==ITT_PLATFORM_WIN */
        }
    }
}

/*
 * This function should be called by the user when the process ends, 
 * to free the local storage index
*/
ITT_EXTERN_C void JITAPI FinalizeProcess()
{
    if (m_libHandle) 
    {
#if ITT_PLATFORM==ITT_PLATFORM_WIN
        FreeLibrary(m_libHandle);
#else  /* ITT_PLATFORM==ITT_PLATFORM_WIN */
        dlclose(m_libHandle);
#endif /* ITT_PLATFORM==ITT_PLATFORM_WIN */
        m_libHandle = NULL;
    }

    if (threadLocalStorageHandle)
#if ITT_PLATFORM==ITT_PLATFORM_WIN
        TlsFree (threadLocalStorageHandle);
#else  /* ITT_PLATFORM==ITT_PLATFORM_WIN */
    pthread_key_delete(threadLocalStorageHandle);
#endif /* ITT_PLATFORM==ITT_PLATFORM_WIN */
}

/*
 * This function should be called by the user for any method once.
 * The function will return a unique method ID, the user should maintain 
 * the ID for each method
 */
ITT_EXTERN_C unsigned int JITAPI iJIT_GetNewMethodID()
{
    static unsigned int methodID = 0x100000;

    if (methodID == 0)
        return 0;  /* ERROR : this is not a valid value */

    return methodID++;
}
