/*
 * %CopyrightBegin%
 *
 * Copyright Ericsson AB 2014-2023. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * %CopyrightEnd%
 */

#ifndef ERL_MSACC_H__
#define ERL_MSACC_H__

/* Can be enabled/disabled via configure */
#if defined(ERTS_ENABLE_MSACC) && ERTS_ENABLE_MSACC == 2
#define ERTS_MSACC_EXTENDED_STATES 1
#endif

/* Uncomment this to also count the number of
   transitions to a state. This will add a count
   to the counter map. */
/* #define ERTS_MSACC_STATE_COUNTERS 1 */

/* Uncomment this to make msacc to always be on,
   this reduces overhead a little bit when profiling */
/* #define ERTS_MSACC_ALWAYS_ON 1 */

/* Uncomment this to keep individual stats for all
   of the bifs when extended states is enabled */
/* #define ERTS_MSACC_EXTENDED_BIFS 1 */

#define ERTS_MSACC_DISABLE 0
#define ERTS_MSACC_ENABLE  1
#define ERTS_MSACC_RESET   2
#define ERTS_MSACC_GATHER  3

/*
 * When adding a new state, you have to:
 * * Add it here
 * * Increment ERTS_MSACC_STATE_COUNT
 * * Add string value to erts_msacc_states
 * * Have to be in alphabetical order!
 * * Only add states to the non-extended section after
 *   careful benchmarking to make sure the overhead
 *   when disabled is minimal.
 */

#ifndef ERTS_MSACC_EXTENDED_STATES
#define ERTS_MSACC_STATE_AUX       0
#define ERTS_MSACC_STATE_CHECK_IO  1
#define ERTS_MSACC_STATE_EMULATOR  2
#define ERTS_MSACC_STATE_GC        3
#define ERTS_MSACC_STATE_OTHER     4
#define ERTS_MSACC_STATE_PORT      5
#define ERTS_MSACC_STATE_SLEEP     6

#define ERTS_MSACC_STATE_COUNT 7

#if defined(ERTS_MSACC_STATE_STRINGS) && defined(ERTS_ENABLE_MSACC)
static char *erts_msacc_states[] = {
    "aux",
    "check_io",
    "emulator",
    "gc",
    "other",
    "port",
    "sleep"
};
#endif

#else

#define ERTS_MSACC_STATE_ALLOC     0
#define ERTS_MSACC_STATE_AUX       1
#define ERTS_MSACC_STATE_BIF       2
#define ERTS_MSACC_STATE_BUSY_WAIT 3
#define ERTS_MSACC_STATE_CHECK_IO  4
#define ERTS_MSACC_STATE_EMULATOR  5
#define ERTS_MSACC_STATE_ETS       6
#define ERTS_MSACC_STATE_GC        7
#define ERTS_MSACC_STATE_GC_FULL   8
#define ERTS_MSACC_STATE_NIF       9
#define ERTS_MSACC_STATE_OTHER     10
#define ERTS_MSACC_STATE_PORT      11
#define ERTS_MSACC_STATE_SEND      12
#define ERTS_MSACC_STATE_SLEEP     13
#define ERTS_MSACC_STATE_TIMERS    14

#define ERTS_MSACC_STATIC_STATE_COUNT 15

#ifdef ERTS_MSACC_EXTENDED_BIFS
#define ERTS_MSACC_STATE_COUNT (ERTS_MSACC_STATIC_STATE_COUNT + BIF_SIZE)
#else
#define ERTS_MSACC_STATE_COUNT ERTS_MSACC_STATIC_STATE_COUNT
#endif

#ifdef ERTS_MSACC_STATE_STRINGS
static char *erts_msacc_states[] = {
    "alloc",
    "aux",
    "bif",
    "busy_wait",
    "check_io",
    "emulator",
    "ets",
    "gc",
    "gc_full",
    "nif",
    "other",
    "port",
    "send",
    "sleep",
    "timers"
#ifdef ERTS_MSACC_EXTENDED_BIFS
#define BIF_LIST(Mod,Func,Arity,BifFuncAddr,FuncAddr,Num)	\
        ,"bif_" #Mod "_" #Func "_" #Arity
#include "erl_bif_list.h"
#undef BIF_LIST
#endif
};
#endif

#endif

typedef struct erl_msacc_t_ ErtsMsAcc;
typedef struct erl_msacc_p_cnt_t_ {
    ErtsSysPerfCounter pc;
#ifdef ERTS_MSACC_STATE_COUNTERS
    Uint64 sc;
#endif
} ErtsMsAccPerfCntr;

struct erl_msacc_t_ {

    /* protected by msacc_mutex in erl_msacc.c, and should be constant */
    int unmanaged;
    erts_mtx_t mtx;
    ErtsMsAcc *next;
    erts_tid_t tid;
    Eterm id;
    char *type;

    /* the values below are protected by mtx iff unmanaged = 1 */
    ErtsSysPerfCounter perf_counter;
    Uint state;
    ErtsMsAccPerfCntr counters[];

};

#ifdef ERTS_ENABLE_MSACC

extern erts_tsd_key_t ERTS_WRITE_UNLIKELY(erts_msacc_key);

#ifdef ERTS_MSACC_ALWAYS_ON
#define erts_msacc_enabled 1
#else
extern int ERTS_WRITE_UNLIKELY(erts_msacc_enabled);
#endif

#define ERTS_MSACC_TSD_GET() ((ErtsMsAcc *)erts_tsd_get(erts_msacc_key))
#define ERTS_MSACC_TSD_SET(tsd) erts_tsd_set(erts_msacc_key,tsd)

void erts_msacc_early_init(void);
void erts_msacc_init(void);
void erts_msacc_init_thread(char *type, int id, int liberty);
void erts_msacc_update_cache(ErtsMsAcc **cache);

/* The defines below are used to instrument the vm code
 * with different state changes. There are two variants
 * of each define. One that has a cached ErtsMsAcc *
 * that it can use, and one that does not.
 * The cached values are necessary to have in order to get
 * low enough overhead when running without msacc enabled.
 *
 * The two most common patterns to use the function with are:
 *
 *   ERTS_MSACC_PUSH_AND_SET_STATE(ERTS_MSACC_STATE_NEWSTATE);
 *     ... call other function in new state ...
 *   ERTS_MSACC_POP_STATE();
 *
 * Note that the erts_msacc_push* function declare new variables, so
 * to conform with C89 we have to call it in the beginning of a function.
 * We might not want to change state it the beginning though, so we use this:
 *
 *   ERTS_MSACC_PUSH_STATE();
 *     ... some other code ...
 *   ERTS_MSACC_SET_STATE_CACHED(ERTS_MSACC_STATE_NEWSTATE);
 *     ... call other function in new state ...
 *   ERTS_MSACC_POP_STATE();
 *
 * Notice that we used the cached version of set_state as push_state already
 * read the erts_msacc_enabled to the cache.
 *
 * Most macros also have other variants with the suffix _m which means that
 * they are known to only be called in managed threads, or with the _x suffix
 * which means that it should only be used in an emulator compiled with
 * extended states.
 *
 * Here is a listing of the entire api:
 *
 *  void ERTS_MSACC_DECLARE_CACHE()
 *  void ERTS_MSACC_UPDATE_CACHE()
 *  void ERTS_MSACC_IS_ENABLED()
 *  void ERTS_MSACC_IS_ENABLED_CACHED()
 *
 *  void ERTS_MSACC_PUSH_STATE()
 *  void ERTS_MSACC_SET_STATE(int state)
 *  void ERTS_MSACC_PUSH_AND_SET_STATE(int state)
 *
 *  void ERTS_MSACC_PUSH_STATE_CACHED()
 *  void ERTS_MSACC_SET_STATE_CACHED(int state)
 *  void ERTS_MSACC_PUSH_AND_SET_STATE_CACHED(int state)
 *  void ERTS_MSACC_POP_STATE()
 *
 *  void ERTS_MSACC_PUSH_STATE_M()
 *  void ERTS_MSACC_PUSH_STATE_CACHED_M()
 *  void ERTS_MSACC_SET_STATE_CACHED_M(int state)
 *  void ERTS_MSACC_SET_STATE_M(int state)
 *  void ERTS_MSACC_POP_STATE_M()
 *  void ERTS_MSACC_PUSH_AND_SET_STATE_M(int state)
 *
 *  Most functions are also available with an _x suffix that are only enabled
 *  when using the extra states. If they are not, just add them to the end
 *  of this file.
 */

/* cache handling functions */
#define ERTS_MSACC_IS_ENABLED() ERTS_UNLIKELY(erts_msacc_enabled)
#define ERTS_MSACC_DECLARE_CACHE()                                      \
    ErtsMsAcc *ERTS_MSACC_UPDATE_CACHE();                                 \
    ERTS_DECLARE_DUMMY(Uint __erts_msacc_state) = ERTS_MSACC_STATE_OTHER;
#define ERTS_MSACC_IS_ENABLED_CACHED() ERTS_UNLIKELY(__erts_msacc_cache != NULL)
#define ERTS_MSACC_UPDATE_CACHE()                                       \
    __erts_msacc_cache = erts_msacc_enabled ? ERTS_MSACC_TSD_GET() : NULL


/* The defines below implicitly declare and load a new cache */
#define ERTS_MSACC_PUSH_STATE()                           \
    ERTS_MSACC_DECLARE_CACHE();                           \
    ERTS_MSACC_PUSH_STATE_CACHED()
#define ERTS_MSACC_SET_STATE(state)                                     \
    ERTS_MSACC_DECLARE_CACHE();                                         \
    ERTS_MSACC_SET_STATE_CACHED(state)
#define ERTS_MSACC_PUSH_AND_SET_STATE(state)                    \
    ERTS_MSACC_PUSH_STATE(); ERTS_MSACC_SET_STATE_CACHED(state)

/* The defines below need an already declared cache to work */
#define ERTS_MSACC_PUSH_STATE_CACHED()                                  \
    __erts_msacc_state = ERTS_MSACC_IS_ENABLED_CACHED() ?               \
        erts_msacc_get_state_um__(__erts_msacc_cache) : ERTS_MSACC_STATE_OTHER
#define ERTS_MSACC_SET_STATE_CACHED(state) \
    if (ERTS_MSACC_IS_ENABLED_CACHED())                         \
        erts_msacc_set_state_um__(__erts_msacc_cache, state, 1)
#define ERTS_MSACC_PUSH_AND_SET_STATE_CACHED(state) \
    ERTS_MSACC_PUSH_STATE_CACHED(); ERTS_MSACC_SET_STATE_CACHED(state)
#define ERTS_MSACC_POP_STATE()                                          \
    if (ERTS_MSACC_IS_ENABLED_CACHED())                                 \
        erts_msacc_set_state_um__(__erts_msacc_cache, __erts_msacc_state, 0)

/* Only use these defines when we know that we have in a managed thread */
#define ERTS_MSACC_PUSH_STATE_M()                         \
    ERTS_MSACC_DECLARE_CACHE();                           \
    ERTS_MSACC_PUSH_STATE_CACHED_M()
#define ERTS_MSACC_PUSH_STATE_CACHED_M() \
    do { \
        if (ERTS_MSACC_IS_ENABLED_CACHED()) { \
            ASSERT(!__erts_msacc_cache->unmanaged); \
            __erts_msacc_state = erts_msacc_get_state_m__(__erts_msacc_cache); \
        } else { \
            __erts_msacc_state = ERTS_MSACC_STATE_OTHER; \
        } \
    } while(0)
#define ERTS_MSACC_SET_STATE_M(state)                   \
    ERTS_MSACC_DECLARE_CACHE();                         \
    ERTS_MSACC_SET_STATE_CACHED_M(state)
#define ERTS_MSACC_SET_STATE_CACHED_M(state) \
    do { \
        if (ERTS_MSACC_IS_ENABLED_CACHED()) { \
            ASSERT(!__erts_msacc_cache->unmanaged); \
            erts_msacc_set_state_m__(__erts_msacc_cache, state, 1); \
        } \
    } while(0)
#define ERTS_MSACC_POP_STATE_M() \
    do { \
        if (ERTS_MSACC_IS_ENABLED_CACHED()) { \
            ASSERT(!__erts_msacc_cache->unmanaged); \
            erts_msacc_set_state_m__(__erts_msacc_cache, __erts_msacc_state, 0); \
        } \
    } while(0)
#define ERTS_MSACC_PUSH_AND_SET_STATE_M(state)                    \
    ERTS_MSACC_PUSH_STATE_M(); ERTS_MSACC_SET_STATE_CACHED_M(state)

ERTS_GLB_INLINE
void erts_msacc_set_state_um__(ErtsMsAcc *msacc,Uint state,int increment);
ERTS_GLB_INLINE
void erts_msacc_set_state_m__(ErtsMsAcc *msacc,Uint state,int increment);

ERTS_GLB_INLINE
Uint erts_msacc_get_state_um__(ErtsMsAcc *msacc);
ERTS_GLB_INLINE
Uint erts_msacc_get_state_m__(ErtsMsAcc *msacc);


#if ERTS_GLB_INLINE_INCL_FUNC_DEF

ERTS_GLB_INLINE
Uint erts_msacc_get_state_um__(ErtsMsAcc *msacc) {
    Uint state;
    if (msacc->unmanaged)
        erts_mtx_lock(&msacc->mtx);
    state = msacc->state;
    if (msacc->unmanaged)
        erts_mtx_unlock(&msacc->mtx);
    return state;
}

ERTS_GLB_INLINE
Uint erts_msacc_get_state_m__(ErtsMsAcc *msacc) {
    return msacc->state;
}

ERTS_GLB_INLINE
void erts_msacc_set_state_um__(ErtsMsAcc *msacc, Uint new_state, int increment) {
    if (ERTS_UNLIKELY(msacc->unmanaged)) {
        erts_mtx_lock(&msacc->mtx);
        if (ERTS_LIKELY(!msacc->perf_counter)) {
            msacc->state = new_state;
            erts_mtx_unlock(&msacc->mtx);
            return;
        }
    }

    erts_msacc_set_state_m__(msacc,new_state,increment);

    if (ERTS_UNLIKELY(msacc->unmanaged))
        erts_mtx_unlock(&msacc->mtx);
}

ERTS_GLB_INLINE
void erts_msacc_set_state_m__(ErtsMsAcc *msacc, Uint new_state, int increment) {
    ErtsSysPerfCounter prev_perf_counter;
    Sint64 diff;

    if (new_state == msacc->state)
        return;

    prev_perf_counter = msacc->perf_counter;
    msacc->perf_counter = erts_sys_perf_counter();
    diff = msacc->perf_counter - prev_perf_counter;
    ASSERT(diff >= 0);
    msacc->counters[msacc->state].pc += diff;
#ifdef ERTS_MSACC_STATE_COUNTERS
    msacc->counters[new_state].sc += increment;
#endif
    msacc->state = new_state;
}

#endif /* ERTS_GLB_INLINE_INCL_FUNC_DEF */

#else

#define ERTS_MSACC_IS_ENABLED() 0
#define erts_msacc_early_init() do {} while(0)
#define erts_msacc_init() do {} while(0)
#define erts_msacc_init_thread(type, id, liberty) do {} while(0)
#define ERTS_MSACC_PUSH_STATE() do {} while(0)
#define ERTS_MSACC_PUSH_STATE_CACHED() do {} while(0)
#define ERTS_MSACC_POP_STATE() do {} while(0)
#define ERTS_MSACC_SET_STATE(state) do {} while(0)
#define ERTS_MSACC_SET_STATE_CACHED(state) do {} while(0)
#define ERTS_MSACC_PUSH_AND_SET_STATE(state) do {} while(0)
#define ERTS_MSACC_PUSH_AND_SET_STATE_CACHED(state) do {} while(0)
#define ERTS_MSACC_UPDATE_CACHE() do {} while(0)
#define ERTS_MSACC_IS_ENABLED_CACHED() do {} while(0)
#define ERTS_MSACC_DECLARE_CACHE() 
#define ERTS_MSACC_PUSH_STATE_M() do {} while(0)
#define ERTS_MSACC_PUSH_STATE_CACHED_M() do {} while(0)
#define ERTS_MSACC_SET_STATE_CACHED_M(state) do {} while(0)
#define ERTS_MSACC_POP_STATE_M() do {} while(0)
#define ERTS_MSACC_PUSH_AND_SET_STATE_M(state) do {} while(0)
#define ERTS_MSACC_SET_BIF_STATE_CACHED_X(Mod,Addr) do {} while(0)

#endif /* ERTS_ENABLE_MSACC */

#ifndef ERTS_MSACC_EXTENDED_STATES

#define ERTS_MSACC_PUSH_STATE_X() do {} while(0)
#define ERTS_MSACC_POP_STATE_X() do {} while(0)
#define ERTS_MSACC_SET_STATE_X(state) do {} while(0)
#define ERTS_MSACC_SET_STATE_M_X(state) do {} while(0)
#define ERTS_MSACC_SET_STATE_CACHED_X(state) do {} while(0)
#define ERTS_MSACC_PUSH_AND_SET_STATE_X(state) do {} while(0)
#define ERTS_MSACC_PUSH_AND_SET_STATE_CACHED_X(state) do {} while(0)
#define ERTS_MSACC_UPDATE_CACHE_X() do {} while(0)
#define ERTS_MSACC_IS_ENABLED_CACHED_X() 0
#define ERTS_MSACC_DECLARE_CACHE_X()
#define ERTS_MSACC_PUSH_STATE_M_X() do {} while(0)
#define ERTS_MSACC_PUSH_STATE_CACHED_M_X() do {} while(0)
#define ERTS_MSACC_SET_STATE_CACHED_M_X(state)
#define ERTS_MSACC_POP_STATE_M_X() do {} while(0)
#define ERTS_MSACC_PUSH_AND_SET_STATE_M_X(state) do {} while(0)
#define ERTS_MSACC_PUSH_AND_SET_STATE_CACHED_M_X(state) do {} while(0)
#define ERTS_MSACC_SET_BIF_STATE_CACHED_X(Mod,Addr) do {} while(0)

#else

/* Enters BIF state, returning the given `bif` so we don't have to spill it in
 * jitted code. */
const void *erts_msacc_set_bif_state(ErtsMsAcc *msacc,
                                     Eterm mod,
                                     const void *bif);

#define ERTS_MSACC_PUSH_STATE_X() ERTS_MSACC_PUSH_STATE()
#define ERTS_MSACC_POP_STATE_X() ERTS_MSACC_POP_STATE()
#define ERTS_MSACC_SET_STATE_X(state) ERTS_MSACC_SET_STATE(state)
#define ERTS_MSACC_SET_STATE_M_X(state) ERTS_MSACC_SET_STATE_M(state)
#define ERTS_MSACC_SET_STATE_CACHED_X(state) ERTS_MSACC_SET_STATE_CACHED(state)
#define ERTS_MSACC_PUSH_AND_SET_STATE_X(state) ERTS_MSACC_PUSH_AND_SET_STATE(state)
#define ERTS_MSACC_PUSH_AND_SET_STATE_CACHED_X(state) ERTS_MSACC_PUSH_AND_SET_STATE_CACHED(state)
#define ERTS_MSACC_UPDATE_CACHE_X() ERTS_MSACC_UPDATE_CACHE()
#define ERTS_MSACC_IS_ENABLED_CACHED_X() ERTS_MSACC_IS_ENABLED_CACHED()
#define ERTS_MSACC_DECLARE_CACHE_X() ERTS_MSACC_DECLARE_CACHE()
#define ERTS_MSACC_PUSH_STATE_M_X() ERTS_MSACC_PUSH_STATE_M()
#define ERTS_MSACC_PUSH_STATE_CACHED_M_X() ERTS_MSACC_PUSH_STATE_CACHED_M()
#define ERTS_MSACC_SET_STATE_CACHED_M_X(state) ERTS_MSACC_SET_STATE_CACHED_M(state)
#define ERTS_MSACC_POP_STATE_M_X() ERTS_MSACC_POP_STATE_M()
#define ERTS_MSACC_PUSH_AND_SET_STATE_M_X(state) ERTS_MSACC_PUSH_AND_SET_STATE_M(state)
#define ERTS_MSACC_SET_BIF_STATE_CACHED_X(Mod,Addr)                       \
    if (ERTS_MSACC_IS_ENABLED_CACHED_X()) {                               \
        (void)erts_msacc_set_bif_state(__erts_msacc_cache, Mod, Addr);    \
    }

#endif /* !ERTS_MSACC_EXTENDED_STATES */

#endif /* ERL_MSACC_H__ */
