diff --git a/src/Makefile b/src/Makefile index ec9ca80586..0e6142155e 100644 --- a/src/Makefile +++ b/src/Makefile @@ -228,7 +228,7 @@ LJLIB_C= $(LJLIB_O:.o=.c) LJCORE_O= lj_gc.o lj_err.o lj_char.o lj_bc.o lj_obj.o lj_buf.o \ lj_str.o lj_tab.o lj_func.o lj_udata.o lj_meta.o lj_debug.o \ lj_state.o lj_dispatch.o lj_vmmath.o lj_strscan.o \ - lj_strfmt.o lj_strfmt_num.o lj_api.o \ + lj_strfmt.o lj_strfmt_num.o lj_api.o lj_vmprofile.o \ lj_lex.o lj_parse.o lj_bcread.o lj_bcwrite.o lj_load.o \ lj_ir.o lj_opt_mem.o lj_opt_fold.o lj_opt_narrow.o \ lj_opt_dce.o lj_opt_loop.o lj_opt_split.o lj_opt_sink.o \ diff --git a/src/lib_jit.c b/src/lib_jit.c index e8b1169775..d019b252d0 100644 --- a/src/lib_jit.c +++ b/src/lib_jit.c @@ -200,6 +200,29 @@ LJLIB_CF(jit_opt_start) #include "lj_libdef.h" +/* -- jit.vmprofile module ----------------------------------------------- */ + +#define LJLIB_MODULE_jit_vmprofile + +LJLIB_CF(jit_vmprofile_start) +{ + luaJIT_vmprofile_start(L); + return 0; +} + +LJLIB_CF(jit_vmprofile_stop) +{ + luaJIT_vmprofile_stop(L); + return 0; +} + +#include "lj_libdef.h" + +static int luaopen_jit_vmprofile(lua_State *L) +{ + LJ_LIB_REG(L, NULL, jit_vmprofile); + return 1; +} /* -- JIT compiler initialization ----------------------------------------- */ @@ -259,6 +282,8 @@ LUALIB_API int luaopen_jit(lua_State *L) lua_pushinteger(L, LUAJIT_VERSION_NUM); lua_pushliteral(L, LUAJIT_VERSION); LJ_LIB_REG(L, LUA_JITLIBNAME, jit); + lj_lib_prereg(L, LUA_JITLIBNAME ".vmprofile", luaopen_jit_vmprofile, + tabref(L->env)); LJ_LIB_REG(L, "jit.opt", jit_opt); L->top -= 2; return 1; diff --git a/src/lj_gc.c b/src/lj_gc.c index c4a9ab5803..7347b23518 100644 --- a/src/lj_gc.c +++ b/src/lj_gc.c @@ -648,6 +648,7 @@ int lj_gc_step(lua_State *L) global_State *g = G(L); GCSize lim; int32_t ostate = g->vmstate; + g->gcvmstate = ostate; setvmstate(g, GC); lim = (GCSTEPSIZE/100) * g->gc.stepmul; if (lim == 0) @@ -698,6 +699,7 @@ void lj_gc_fullgc(lua_State *L) { global_State *g = G(L); int32_t ostate = g->vmstate; + g->gcvmstate = ostate; setvmstate(g, GC); if (g->gc.state <= GCSatomic) { /* Caught somewhere in the middle. */ setmref(g->gc.sweep, &g->gc.root); /* Sweep everything (preserving it). */ diff --git a/src/lj_obj.h b/src/lj_obj.h index 12403d503e..4025177843 100644 --- a/src/lj_obj.h +++ b/src/lj_obj.h @@ -516,7 +516,8 @@ typedef struct global_State { lua_Alloc allocf; /* Memory allocator. */ void *allocd; /* Memory allocator data. */ GCState gc; /* Garbage collector. */ - volatile int32_t vmstate; /* VM state or current JIT code trace number. */ + volatile int32_t vmstate; /* VM state or current JIT code trace number. */ + volatile int32_t gcvmstate; /* Previous VM state (only when state is GC). */ SBuf tmpbuf; /* Temporary string buffer. */ GCstr strempty; /* Empty string. */ uint8_t stremptyz; /* Zero terminator of empty string. */ diff --git a/src/lj_vmprofile.c b/src/lj_vmprofile.c new file mode 100644 index 0000000000..3f625761e7 --- /dev/null +++ b/src/lj_vmprofile.c @@ -0,0 +1,113 @@ +/* +** VM profiling. +** Copyright (C) 2016 Luke Gorrie. See Copyright Notice in luajit.h +*/ + +#define lj_vmprofile_c +#define LUA_CORE + +#define _GNU_SOURCE 1 +#include +#include +#include +#include +#include +#include +#undef _GNU_SOURCE + +#include "lj_obj.h" +#include "lj_dispatch.h" +#include "lj_jit.h" +#include "lj_trace.h" +#include "lj_vmprofile.h" + +static struct { + global_State *g; + struct sigaction oldsa; +} state; + +/* -- State that the application can manage via FFI ----------------------- */ + +static VMProfile *profile; /* Current counters */ + +/* How much memory to allocate for profiler counters. */ +int vmprofile_get_profile_size() { + return sizeof(VMProfile); +} + +/* Set the memory where the next samples will be counted. + Size of the memory must match vmprofile_get_profile_size(). */ +void vmprofile_set_profile(void *counters) { + profile = (VMProfile*)counters; + profile->magic = 0x1d50f007; + profile->major = 2; + profile->minor = 0; +} + +/* -- Signal handler ------------------------------------------------------ */ + +/* Signal handler: bumps one counter. */ +static void vmprofile_signal(int sig, siginfo_t *si, void *data) +{ + if (profile != NULL) { + lua_State *L = gco2th(gcref(state.g->cur_L)); + int vmstate = state.g->vmstate; + int trace = ~vmstate == LJ_VMST_GC ? state.g->gcvmstate : vmstate; + /* Not in a trace */ + if (trace < 0) { + profile->vm[~vmstate]++; + } else { + int bucket = trace > LJ_VMPROFILE_TRACE_MAX ? 0 : trace; + VMProfileTraceCount *count = &profile->trace[bucket]; + GCtrace *T = traceref(L2J(L), (TraceNo)trace); + intptr_t ip = (intptr_t)((ucontext_t*)data)->uc_mcontext.gregs[REG_RIP]; + ptrdiff_t mcposition = ip - (intptr_t)T->mcode; + if (~vmstate == LJ_VMST_GC) { + count->gc++; + } else if ((mcposition < 0) || (mcposition >= T->szmcode)) { + count->other++; + } else if ((T->mcloop != 0) && (mcposition >= T->mcloop)) { + count->loop++; + } else { + count->head++; + } + } + } +} + +static void start_timer(int 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_SIGINFO | SA_RESTART; + sa.sa_sigaction = vmprofile_signal; + sigemptyset(&sa.sa_mask); + sigaction(SIGPROF, &sa, &state.oldsa); +} + +static void stop_timer() +{ + 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, NULL, &state.oldsa); +} + +/* -- Lua API ------------------------------------------------------------- */ + +LUA_API void luaJIT_vmprofile_start(lua_State *L) +{ + memset(&state, 0, sizeof(state)); + state.g = G(L); + start_timer(1); /* Sample every 1ms */ +} + +LUA_API void luaJIT_vmprofile_stop(lua_State *L) +{ + stop_timer(); +} + diff --git a/src/lj_vmprofile.h b/src/lj_vmprofile.h new file mode 100644 index 0000000000..b03c75e109 --- /dev/null +++ b/src/lj_vmprofile.h @@ -0,0 +1,38 @@ +/* +** Virtual machine profiling. +** Copyright (C) 2017 Luke Gorrie. See Copyright Notice in luajit.h +*/ + +#ifndef _LJ_VMPROFILE_H +#define _LJ_VMPROFILE_H + + +/* Counters are 64-bit to avoid overflow even in long running processes. */ +typedef uint64_t VMProfileCount; + +/* Maximum trace number for distinct counter buckets. Traces with + higher numbers will be counted together in bucket zero. */ +#define LJ_VMPROFILE_TRACE_MAX 4096 + +/* Traces have separate counters for different machine code regions. */ +typedef struct VMProfileTraceCount { + VMProfileCount head; /* Head of the trace (non-looping part) */ + VMProfileCount loop; /* Loop of the trace */ + VMProfileCount other; /* Outside the trace mcode (unidentified) */ + VMProfileCount gc; /* Garbage collection from this trace. */ +} VMProfileTraceCount; + +/* Complete set of counters for VM and traces. */ +typedef struct VMProfile { + uint32_t magic; /* 0x1d50f007 */ + uint16_t major, minor; /* 2, 0 */ + VMProfileCount vm[LJ_VMST__MAX]; + VMProfileTraceCount trace[LJ_VMPROFILE_TRACE_MAX+1]; +} VMProfile; + +/* Functions that should be accessed via FFI. */ + +void vmprofile_set_profile(void *counters); +int vmprofile_get_profile_size(); + +#endif diff --git a/src/luajit.h b/src/luajit.h index 8944d47c42..597b3e1f27 100644 --- a/src/luajit.h +++ b/src/luajit.h @@ -64,6 +64,10 @@ enum { /* Control the JIT engine. */ LUA_API int luaJIT_setmode(lua_State *L, int idx, int mode); +/* VM profiling API. */ +LUA_API void luaJIT_vmprofile_start(lua_State *L); +LUA_API void luaJIT_vmprofile_stop(lua_State *L); + /* Enforce (dynamic) linker error for version mismatches. Call from main. */ LUA_API void LUAJIT_VERSION_SYM(void);