diff --git a/docs/luadebug/hookmgr.lua b/docs/luadebug/hookmgr.lua index a64b4e9c..0096a131 100644 --- a/docs/luadebug/hookmgr.lua +++ b/docs/luadebug/hookmgr.lua @@ -135,4 +135,11 @@ end function hookmgr.coroutine_from(co) end +--- +---@param enable boolean +---启用luajit的jit支持 +--- +function hookmgr.enable_jit(enable) +end + return hookmgr diff --git a/extension/script/backend/worker.lua b/extension/script/backend/worker.lua index 82129993..4abce6ea 100644 --- a/extension/script/backend/worker.lua +++ b/extension/script/backend/worker.lua @@ -659,9 +659,7 @@ function event.step(line) end end -function event.newproto(proto, level) - if not debuggeeReady() then return end - rdebug.getinfo(level, "S", info) +local function newprotoimpl(proto, info) local src = source.create(info.source) if not source.valid(src) then return false @@ -669,6 +667,17 @@ function event.newproto(proto, level) return breakpoint.newproto(proto, src, info.linedefined.."-"..info.lastlinedefined) end +function event.newproto(proto, level) + if not debuggeeReady() then return end + rdebug.getinfo(level, "S", info) + return newprotoimpl(proto, info) +end + +function event.newprotoimpl(proto, info) + if not debuggeeReady() then return end + return newprotoimpl(proto, info) +end + function event.update() debuggeeReady() workerThreadUpdate() @@ -881,3 +890,5 @@ end) sendToMaster 'initWorker' {} hookmgr.update_open(true) + +return event diff --git a/extension/script/backend/worker/eval.lua b/extension/script/backend/worker/eval.lua index cea4e9e8..d7e033da 100644 --- a/extension/script/backend/worker/eval.lua +++ b/extension/script/backend/worker/eval.lua @@ -64,4 +64,22 @@ generate("ffi_reflect", function () end end) +generate("vmevent", function () + if not luaver.isjit then + return + end + local handler = assert(rdebug.load(readfile "backend.worker.eval.vmevent")) + local ok, fn = rdebug.eval(handler) + if not ok then + return + end + if rdebug.type(fn) ~= "function" then + return function () + end + end + return function (...) + return rdebug.eval(fn, ...) + end +end) + return m diff --git a/extension/script/backend/worker/eval/vmevent.lua b/extension/script/backend/worker/eval/vmevent.lua new file mode 100644 index 00000000..00e84396 --- /dev/null +++ b/extension/script/backend/worker/eval/vmevent.lua @@ -0,0 +1,56 @@ +local jit = require("jit") +if not jit.status() then + return "LuaJIT jit is not enabled" +end +if not pcall(require, 'jit.profile') then + --需要profile来update防止正在运行的代码全部被jit了而不无法update + return "LuaJIT profile is not enabled" +end +local active = false +local record_count = 0 +local record_updeta_count = 1024 + +---@module "script.debugger" +local luadebugger = debug.getregistry()["lua-debug"] + +local function update_trace() + luadebugger:event("update") +end +local function update_record() + record_count = record_count + 1 + if record_count < record_updeta_count then + return + end + luadebugger:event("update") + record_count = 0 +end +local function update_texit() + luadebugger:event("update") +end + +local function update_bc(pt) + luadebugger:event("create_proto", pt) +end + +local function on() + jit.attach(update_bc, "bc") + jit.attach(update_trace, "trace") + jit.attach(update_record, "record") + jit.attach(update_texit, "texit") + active = true +end +local function off() + jit.attach(update_texit) + jit.attach(update_record) + jit.attach(update_trace) + jit.attach(update_bc) + active = false +end + +return function (enable) + if enable and not active then + on() + elseif active then + off() + end +end diff --git a/extension/script/backend/worker/luajit.lua b/extension/script/backend/worker/luajit.lua new file mode 100644 index 00000000..98d18093 --- /dev/null +++ b/extension/script/backend/worker/luajit.lua @@ -0,0 +1,62 @@ +local ev = require 'backend.event' +local rdebug = require 'luadebug.visitor' +local luaver = require 'backend.worker.luaver' +local hookmgr = require 'luadebug.hookmgr' + +local enable_jit = false +local no_vmevent = false + +local function set_jitmode(on) + on = on and "on" or "off" + local code = ([[ + if jit and jit.%s then + jit.%s() + end + ]]):format(on, on) + local thunk = rdebug.load(code) + if thunk then + rdebug.eval(thunk) + end +end + +local function attach_vmevent() + local eval = require 'backend.worker.eval' + return eval.vmevent(true) +end + +local function detach_vmevent() + local eval = require 'backend.worker.eval' + eval.vmevent(false) +end + +ev.on('initializing', function (config) + if not luaver.isjit then + return + end + enable_jit = no_vmevent or not config.disable_jit + if not enable_jit then + set_jitmode(false) + return + end + if not attach_vmevent() then + no_vmevent = true + return + end + local worker = require 'backend.worker' + worker.autoUpdate(false) + hookmgr.enable_jit(true) +end) + +local function terminated() + if not luaver.isjit then + return + end + if not enable_jit then + set_jitmode(true) + return + end + detach_vmevent() + hookmgr.enable_jit(false) +end + +ev.on('terminated', terminated) diff --git a/extension/script/debugger.lua b/extension/script/debugger.lua index f1766216..1f77c3e3 100644 --- a/extension/script/debugger.lua +++ b/extension/script/debugger.lua @@ -123,7 +123,6 @@ local function detectLuaDebugPath(cfg) elseif _VERSION == "Lua 5.1" then if (tostring(assert):match('builtin') ~= nil) then rt = rt.."/luajit" - jit.off() else rt = rt.."/lua51" end diff --git a/src/luadebug/compat/internal.h b/src/luadebug/compat/internal.h index 8f48fe56..6d8b16f8 100644 --- a/src/luadebug/compat/internal.h +++ b/src/luadebug/compat/internal.h @@ -12,8 +12,10 @@ const void* lua_tocfunction_pointer(lua_State* L, int idx); #ifdef LUAJIT_VERSION union TValue; struct GCproto; +union GCobj; using CallInfo = TValue; using Proto = GCproto; +using GCobject = GCobj; #else struct CallInfo; struct Proto; @@ -26,6 +28,16 @@ CallInfo* lua_debug2ci(lua_State* L, const lua_Debug* ar); #ifdef LUAJIT_VERSION int lua_isluafunc(lua_State* L, lua_Debug* ar); +using lua_foreach_gcobj_cb = void (*)(lua_State* L, void* ud, GCobject* o); +void lua_foreach_gcobj(lua_State* L, lua_foreach_gcobj_cb cb, void* ud); +Proto* lua_toproto(GCobject* o); +bool luajit_set_jitmode(lua_State* L, GCproto* pt, bool enable); +struct ProtoInfo { + const char* source; + int linedefined; + int lastlinedefined; +}; +ProtoInfo lua_getprotoinfo(lua_State* L, Proto* pt); #endif int lua_stacklevel(lua_State* L); diff --git a/src/luadebug/compat/jit/fun.cpp b/src/luadebug/compat/jit/fun.cpp new file mode 100644 index 00000000..893cd22b --- /dev/null +++ b/src/luadebug/compat/jit/fun.cpp @@ -0,0 +1,10 @@ +#include +#include +ProtoInfo lua_getprotoinfo(lua_State* L, Proto* pt) { + GCstr* name = proto_chunkname(pt); + ProtoInfo info; + info.source = strdata(name); + info.linedefined = pt->firstline; + info.lastlinedefined = (pt->firstline || !pt->numline) ? pt->firstline + pt->numline : 0; + return info; +} diff --git a/src/luadebug/compat/jit/gc.cpp b/src/luadebug/compat/jit/gc.cpp new file mode 100644 index 00000000..f9339f20 --- /dev/null +++ b/src/luadebug/compat/jit/gc.cpp @@ -0,0 +1,18 @@ +#include +#include + +void lua_foreach_gcobj(lua_State* L, lua_foreach_gcobj_cb cb, void* ud) { + auto g = G(L); + auto p = gcref(g->gc.root); + while (p != NULL) { + cb(L, ud, p); + p = gcref(p->gch.nextgc); + } +} + +Proto* lua_toproto(GCobject* o) { + if (o->gch.gct == ~LJ_TPROTO) { + return gco2pt(o); + } + return nullptr; +} diff --git a/src/luadebug/compat/jit/trace.cpp b/src/luadebug/compat/jit/trace.cpp new file mode 100644 index 00000000..5662cb8b --- /dev/null +++ b/src/luadebug/compat/jit/trace.cpp @@ -0,0 +1,128 @@ +#include +#include +#include + +#include + +#if LJ_HASJIT +/* Unpatch the bytecode modified by a root trace. */ +static void trace_unpatch(jit_State* J, GCtrace* T) { + BCOp op = bc_op(T->startins); + BCIns* pc = mref(T->startpc, BCIns); + UNUSED(J); + if (op == BC_JMP) + return; /* No need to unpatch branches in parent traces (yet). */ + switch (bc_op(*pc)) { + case BC_JFORL: + lj_assertJ(traceref(J, bc_d(*pc)) == T, "JFORL references other trace"); + *pc = T->startins; + pc += bc_j(T->startins); + lj_assertJ(bc_op(*pc) == BC_JFORI, "FORL does not point to JFORI"); + setbc_op(pc, BC_FORI); + break; + case BC_JITERL: + case BC_JLOOP: + lj_assertJ(op == BC_ITERL || op == BC_ITERN || op == BC_LOOP || bc_isret(op), "bad original bytecode %d", op); + *pc = T->startins; + break; + case BC_JMP: + lj_assertJ(op == BC_ITERL, "bad original bytecode %d", op); + pc += bc_j(*pc) + 2; + if (bc_op(*pc) == BC_JITERL) { + lj_assertJ(traceref(J, bc_d(*pc)) == T, "JITERL references other trace"); + *pc = T->startins; + } + break; + case BC_JFUNCF: + lj_assertJ(op == BC_FUNCF, "bad original bytecode %d", op); + *pc = T->startins; + break; + default: /* Already unpatched. */ + break; + } +} +static void trace_flushroot(jit_State* J, GCtrace* T) { + GCproto* pt = &gcref(T->startpt)->pt; + lj_assertJ(T->root == 0, "not a root trace"); + lj_assertJ(pt != NULL, "trace has no prototype"); + /* First unpatch any modified bytecode. */ + trace_unpatch(J, T); + /* Unlink root trace from chain anchored in prototype. */ + if (pt->trace == T->traceno) { /* Trace is first in chain. Easy. */ + pt->trace = T->nextroot; + } + else if (pt->trace) { /* Otherwise search in chain of root traces. */ + GCtrace* T2 = traceref(J, pt->trace); + if (T2) { + for (; T2->nextroot; T2 = traceref(J, T2->nextroot)) + if (T2->nextroot == T->traceno) { + T2->nextroot = T->nextroot; /* Unlink from chain. */ + break; + } + } + } +} +void lj_trace_flushproto(global_State* g, GCproto* pt) { + while (pt->trace != 0) + trace_flushroot(G2J(g), traceref(G2J(g), pt->trace)); +} + +/* Re-enable compiling a prototype by unpatching any modified bytecode. */ +void lj_trace_reenableproto(GCproto* pt) { + if ((pt->flags & PROTO_ILOOP)) { + BCIns* bc = proto_bc(pt); + BCPos i, sizebc = pt->sizebc; + pt->flags &= ~PROTO_ILOOP; + if (bc_op(bc[0]) == BC_IFUNCF) + setbc_op(&bc[0], BC_FUNCF); + for (i = 1; i < sizebc; i++) { + BCOp op = bc_op(bc[i]); + if (op == BC_IFORL || op == BC_IITERL || op == BC_ILOOP) + setbc_op(&bc[i], (int)op + (int)BC_LOOP - (int)BC_ILOOP); + } + } +} + +/* Set JIT mode for a single prototype. */ +static void setptmode(global_State* g, GCproto* pt, int mode) { + if ((mode & LUAJIT_MODE_ON)) { /* (Re-)enable JIT compilation. */ + pt->flags &= ~PROTO_NOJIT; + lj_trace_reenableproto(pt); /* Unpatch all ILOOP etc. bytecodes. */ + } + else { /* Flush and/or disable JIT compilation. */ + if (!(mode & LUAJIT_MODE_FLUSH)) + pt->flags |= PROTO_NOJIT; + lj_trace_flushproto(g, pt); /* Flush all traces of prototype. */ + } +} + +/* Recursively set the JIT mode for all children of a prototype. */ +static void setptmode_all(global_State* g, GCproto* pt, int mode) { + ptrdiff_t i; + if (!(pt->flags & PROTO_CHILD)) return; + for (i = -(ptrdiff_t)pt->sizekgc; i < 0; i++) { + GCobj* o = proto_kgc(pt, i); + if (o->gch.gct == ~LJ_TPROTO) { + setptmode(g, gco2pt(o), mode); + setptmode_all(g, gco2pt(o), mode); + } + } +} +#endif + +bool luajit_set_jitmode(lua_State* L, GCproto* pt, bool enable) { +#ifdef LJ_HASJIT + bool nojit = pt->flags & PROTO_NOJIT; + if (enable) { + if (nojit) + setptmode_all(G(L), pt, LUAJIT_MODE_ON); + } + else { + if (!nojit) + setptmode_all(G(L), pt, LUAJIT_MODE_OFF); + } + return nojit; +#else + return false; +#endif +} diff --git a/src/luadebug/rdebug_hookmgr.cpp b/src/luadebug/rdebug_hookmgr.cpp index 7315662a..20a16fdb 100644 --- a/src/luadebug/rdebug_hookmgr.cpp +++ b/src/luadebug/rdebug_hookmgr.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include "compat/internal.h" #include "rdebug_debughost.h" @@ -106,19 +107,46 @@ struct hookmgr { // bpmap break_proto; int break_mask = 0; +#ifdef LUAJIT_VERSION + luadebug::flatmap break_jitblack; +#endif void break_add(lua_State* hL, Proto* p) { break_proto.set(p, bpmap::status::Break); +#ifdef LUAJIT_VERSION + if (enable_jit_flag) { + auto old_nojit = luajit_set_jitmode(hL, p, false); + if (old_nojit) { + break_jitblack.insert_or_assign(p, true); + } + } +#endif } void break_del(lua_State* hL, Proto* p) { break_proto.set(p, bpmap::status::Ignore); +#ifdef LUAJIT_VERSION + if (enable_jit_flag) { + // 如果原来就关闭了就不再启用 + if (break_jitblack.find(p)) { + break_jitblack.erase(p); + } + else { + luajit_set_jitmode(hL, p, true); + } + } +#endif } void break_freeobj(Proto* p) { break_proto.set(p, bpmap::status::None); } void break_open(lua_State* hL, bool enable) { - if (enable) + if (enable) { break_update(hL, lua_getcallinfo(hL), LUA_HOOKCALL); +#ifdef LUAJIT_VERSION + if (enable_jit_flag) + break_updateprotos(hL); +#endif + } else break_hookmask(hL, 0); } @@ -187,6 +215,44 @@ struct hookmgr { updatehookmask(hL); } } +#ifdef LUAJIT_VERSION + inline void break_updateproto(lua_State* hL, Proto* pt) { + auto status = break_proto.get(pt); + if (status != bpmap::status::None) { + return; + } + break_del(hL, pt); + + luadbgL_checkstack(L, 4, NULL); + push_callback(L); + luadebug::debughost::set(L, hL); + luadbg_pushstring(L, "newprotoimpl"); + luadbg_pushlightuserdata(L, pt); + luadbg_createtable(L, 0, 3); + auto info = lua_getprotoinfo(hL, pt); + luadbg_pushstring(L, info.source); + luadbg_setfield(L, -2, "source"); + luadbg_pushinteger(L, info.linedefined); + luadbg_setfield(L, -2, "linedefined"); + luadbg_pushinteger(L, info.lastlinedefined); + luadbg_setfield(L, -2, "lastlinedefined"); + if (luadbg_pcall(L, 3, 0, 0) != LUADBG_OK) { + luadbg_pop(L, 1); + } + } + void break_updateprotos(lua_State* hL) { + lua_foreach_gcobj( + hL, [](lua_State* hL, void* ud, GCobject* o) { + auto _this = (decltype(this))ud; + auto proto = lua_toproto(o); + if (!proto) + return; + _this->break_updateproto(hL, proto); + }, + this + ); + } +#endif // // funcbp @@ -361,6 +427,29 @@ struct hookmgr { } #endif +#if defined(LUAJIT_VERSION) + static void luaJIT_profile_callback(void* data, lua_State* hL, int samples, int vmstate) { + auto _this = (hookmgr*)data; + _this->update_hook(hL); + } + bool enable_jit_flag = false; + void enable_jit(lua_State* hL, int enable) { + constexpr auto profile_ms = + "i" + "200"; + if (enable) { + if (enable_jit_flag) + luaJIT_profile_stop(hL); + luaJIT_profile_start(hL, profile_ms, luaJIT_profile_callback, this); + enable_jit_flag = true; + } + else { + luaJIT_profile_stop(hL); + enable_jit_flag = false; + } + } +#endif + // // common // @@ -715,8 +804,13 @@ static int coroutine_from(luadbg_State* L) { } #endif -LUADEBUG_FUNC -int luaopen_luadebug_hookmgr(luadbg_State* L) { +#if defined(LUAJIT_VERSION) +static int enable_jit(luadbg_State* L) { + hookmgr::get_self(L)->enable_jit(luadebug::debughost::get(L), luadbg_toboolean(L, 1)); + return 0; +} +#endif +LUADEBUG_FUNC int luaopen_luadebug_hookmgr(luadbg_State* L) { luadebug::debughost::get(L); luadbg_newtable(L); @@ -756,6 +850,9 @@ int luaopen_luadebug_hookmgr(luadbg_State* L) { #if defined(LUA_HOOKTHREAD) { "thread_open", thread_open }, { "coroutine_from", coroutine_from }, +#endif +#if defined(LUAJIT_VERSION) + { "enable_jit", enable_jit }, #endif { NULL, NULL }, }; @@ -778,6 +875,13 @@ static bool call_event(luadbg_State* L, int nargs) { } bool event(luadbg_State* L, lua_State* hL, const char* name, int start) { +#ifdef LUAJIT_VERSION + using namespace std::string_view_literals; + if (name == "create_proto"sv) { + hookmgr::get_self(L)->break_updateproto(hL, lua_getproto(hL, start + 1)); + return true; + } +#endif if (luadbg_rawgetp(L, LUADBG_REGISTRYINDEX, &HOOK_CALLBACK) != LUADBG_TFUNCTION) { // TODO cache event? luadbg_pop(L, 1);