/* ** Low-overhead profiling. ** Copyright (C) 2005-2022 Mike Pall. See Copyright Notice in luajit.h */ #define lj_profile_c #define LUA_CORE #include "lj_obj.h" #if LJ_HASPROFILE #include "lj_buf.h" #include "lj_frame.h" #include "lj_debug.h" #include "lj_dispatch.h" #if LJ_HASJIT #include "lj_jit.h" #include "lj_trace.h" #endif #include "lj_profile.h" #include "luajit.h" #if LJ_PROFILE_SIGPROF #include #include #define profile_lock(ps) UNUSED(ps) #define profile_unlock(ps) UNUSED(ps) #elif LJ_PROFILE_PTHREAD #include #include #if LJ_TARGET_PS3 #include #endif #define profile_lock(ps) pthread_mutex_lock(&ps->lock) #define profile_unlock(ps) pthread_mutex_unlock(&ps->lock) #elif LJ_PROFILE_WTHREAD #define WIN32_LEAN_AND_MEAN #if LJ_TARGET_XBOX360 #include #include #else #include #endif typedef unsigned int (WINAPI *WMM_TPFUNC)(unsigned int); #define profile_lock(ps) EnterCriticalSection(&ps->lock) #define profile_unlock(ps) LeaveCriticalSection(&ps->lock) #endif /* Profiler state. */ typedef struct ProfileState { global_State *g; /* VM state that started the profiler. */ luaJIT_profile_callback cb; /* Profiler callback. */ void *data; /* Profiler callback data. */ SBuf sb; /* String buffer for stack dumps. */ int interval; /* Sample interval in milliseconds. */ int samples; /* Number of samples for next callback. */ int vmstate; /* VM state when profile timer triggered. */ #if LJ_PROFILE_SIGPROF struct sigaction oldsa; /* Previous SIGPROF state. */ #elif LJ_PROFILE_PTHREAD pthread_mutex_t lock; /* g->hookmask update lock. */ pthread_t thread; /* Timer thread. */ int abort; /* Abort timer thread. */ #elif LJ_PROFILE_WTHREAD #if LJ_TARGET_WINDOWS HINSTANCE wmm; /* WinMM library handle. */ WMM_TPFUNC wmm_tbp; /* WinMM timeBeginPeriod function. */ WMM_TPFUNC wmm_tep; /* WinMM timeEndPeriod function. */ #endif CRITICAL_SECTION lock; /* g->hookmask update lock. */ HANDLE thread; /* Timer thread. */ int abort; /* Abort timer thread. */ #endif } ProfileState; /* Sadly, we have to use a static profiler state. ** ** The SIGPROF variant needs a static pointer to the global state, anyway. ** And it would be hard to extend for multiple threads. You can still use ** multiple VMs in multiple threads, but only profile one at a time. */ static ProfileState profile_state; /* Default sample interval in milliseconds. */ #define LJ_PROFILE_INTERVAL_DEFAULT 10 /* -- Profiler/hook interaction ------------------------------------------- */ #if !LJ_PROFILE_SIGPROF void LJ_FASTCALL lj_profile_hook_enter(global_State *g) { ProfileState *ps = &profile_state; if (ps->g) { profile_lock(ps); hook_enter(g); profile_unlock(ps); } else { hook_enter(g); } } void LJ_FASTCALL lj_profile_hook_leave(global_State *g) { ProfileState *ps = &profile_state; if (ps->g) { profile_lock(ps); hook_leave(g); profile_unlock(ps); } else { hook_leave(g); } } #endif /* -- Profile callbacks --------------------------------------------------- */ /* Callback from profile hook (HOOK_PROFILE already cleared). */ void LJ_FASTCALL lj_profile_interpreter(lua_State *L) { ProfileState *ps = &profile_state; global_State *g = G(L); uint8_t mask; profile_lock(ps); mask = (g->hookmask & ~HOOK_PROFILE); if (!(mask & HOOK_VMEVENT)) { int samples = ps->samples; ps->samples = 0; g->hookmask = HOOK_VMEVENT; lj_dispatch_update(g); profile_unlock(ps); ps->cb(ps->data, L, samples, ps->vmstate); /* Invoke user callback. */ profile_lock(ps); mask |= (g->hookmask & HOOK_PROFILE); } g->hookmask = mask; lj_dispatch_update(g); profile_unlock(ps); } /* Trigger profile hook. Asynchronous call from OS-specific profile timer. */ static void profile_trigger(ProfileState *ps) { global_State *g = ps->g; uint8_t mask; profile_lock(ps); ps->samples++; /* Always increment number of samples. */ mask = g->hookmask; if (!(mask & (HOOK_PROFILE|HOOK_VMEVENT|HOOK_GC))) { /* Set profile hook. */ int st = g->vmstate; ps->vmstate = st >= 0 ? 'N' : st == ~LJ_VMST_INTERP ? 'I' : st == ~LJ_VMST_C ? 'C' : st == ~LJ_VMST_GC ? 'G' : 'J'; g->hookmask = (mask | HOOK_PROFILE); lj_dispatch_update(g); } profile_unlock(ps); } /* -- OS-specific profile timer handling ---------------------------------- */ #if LJ_PROFILE_SIGPROF /* SIGPROF handler. */ static void profile_signal(int sig) { UNUSED(sig); profile_trigger(&profile_state); } /* Start profiling timer. */ static void profile_timer_start(ProfileState *ps) { int interval = ps->interval; struct itimerval tm; struct sigaction sa; tm.it_value.tv_sec = tm.it_interval.tv_sec = interval / 1000; tm.it_value.tv_usec = tm.it_interval.tv_usec = (interval % 1000) * 1000; setitimer(ITIMER_PROF, &tm, NULL); sa.sa_flags = SA_RESTART; sa.sa_handler = profile_signal; sigemptyset(&sa.sa_mask); sigaction(SIGPROF, &sa, &ps->oldsa); } /* Stop profiling timer. */ static void profile_timer_stop(ProfileState *ps) { struct itimerval tm; tm.it_value.tv_sec = tm.it_interval.tv_sec = 0; tm.it_value.tv_usec = tm.it_interval.tv_usec = 0; setitimer(ITIMER_PROF, &tm, NULL); sigaction(SIGPROF, &ps->oldsa, NULL); } #elif LJ_PROFILE_PTHREAD /* POSIX timer thread. */ static void *profile_thread(ProfileState *ps) { int interval = ps->interval; #if !LJ_TARGET_PS3 struct timespec ts; ts.tv_sec = interval / 1000; ts.tv_nsec = (interval % 1000) * 1000000; #endif while (1) { #if LJ_TARGET_PS3 sys_timer_usleep(interval * 1000); #else nanosleep(&ts, NULL); #endif if (ps->abort) break; profile_trigger(ps); } return NULL; } /* Start profiling timer thread. */ static void profile_timer_start(ProfileState *ps) { pthread_mutex_init(&ps->lock, 0); ps->abort = 0; pthread_create(&ps->thread, NULL, (void *(*)(void *))profile_thread, ps); } /* Stop profiling timer thread. */ static void profile_timer_stop(ProfileState *ps) { ps->abort = 1; pthread_join(ps->thread, NULL); pthread_mutex_destroy(&ps->lock); } #elif LJ_PROFILE_WTHREAD /* Windows timer thread. */ static DWORD WINAPI profile_thread(void *psx) { ProfileState *ps = (ProfileState *)psx; int interval = ps->interval; #if LJ_TARGET_WINDOWS && !LJ_TARGET_UWP ps->wmm_tbp(interval); #endif while (1) { Sleep(interval); if (ps->abort) break; profile_trigger(ps); } #if LJ_TARGET_WINDOWS && !LJ_TARGET_UWP ps->wmm_tep(interval); #endif return 0; } /* Start profiling timer thread. */ static void profile_timer_start(ProfileState *ps) { #if LJ_TARGET_WINDOWS && !LJ_TARGET_UWP if (!ps->wmm) { /* Load WinMM library on-demand. */ ps->wmm = LJ_WIN_LOADLIBA("winmm.dll"); if (ps->wmm) { ps->wmm_tbp = (WMM_TPFUNC)GetProcAddress(ps->wmm, "timeBeginPeriod"); ps->wmm_tep = (WMM_TPFUNC)GetProcAddress(ps->wmm, "timeEndPeriod"); if (!ps->wmm_tbp || !ps->wmm_tep) { ps->wmm = NULL; return; } } } #endif InitializeCriticalSection(&ps->lock); ps->abort = 0; ps->thread = CreateThread(NULL, 0, profile_thread, ps, 0, NULL); } /* Stop profiling timer thread. */ static void profile_timer_stop(ProfileState *ps) { ps->abort = 1; WaitForSingleObject(ps->thread, INFINITE); DeleteCriticalSection(&ps->lock); } #endif /* -- Public profiling API ------------------------------------------------ */ /* Start profiling. */ LUA_API void luaJIT_profile_start(lua_State *L, const char *mode, luaJIT_profile_callback cb, void *data) { ProfileState *ps = &profile_state; int interval = LJ_PROFILE_INTERVAL_DEFAULT; while (*mode) { int m = *mode++; switch (m) { case 'i': interval = 0; while (*mode >= '0' && *mode <= '9') interval = interval * 10 + (*mode++ - '0'); if (interval <= 0) interval = 1; break; #if LJ_HASJIT case 'l': case 'f': L2J(L)->prof_mode = m; lj_trace_flushall(L); break; #endif default: /* Ignore unknown mode chars. */ break; } } if (ps->g) { luaJIT_profile_stop(L); if (ps->g) return; /* Profiler in use by another VM. */ } ps->g = G(L); ps->interval = interval; ps->cb = cb; ps->data = data; ps->samples = 0; lj_buf_init(L, &ps->sb); profile_timer_start(ps); } /* Stop profiling. */ LUA_API void luaJIT_profile_stop(lua_State *L) { ProfileState *ps = &profile_state; global_State *g = ps->g; if (G(L) == g) { /* Only stop profiler if started by this VM. */ profile_timer_stop(ps); g->hookmask &= ~HOOK_PROFILE; lj_dispatch_update(g); #if LJ_HASJIT G2J(g)->prof_mode = 0; lj_trace_flushall(L); #endif lj_buf_free(g, &ps->sb); ps->sb.w = ps->sb.e = NULL; ps->g = NULL; } } /* Return a compact stack dump. */ LUA_API const char *luaJIT_profile_dumpstack(lua_State *L, const char *fmt, int depth, size_t *len) { ProfileState *ps = &profile_state; SBuf *sb = &ps->sb; setsbufL(sb, L); lj_buf_reset(sb); lj_debug_dumpstack(L, sb, fmt, depth); *len = (size_t)sbuflen(sb); return sb->b; } #endif