diff options
author | sanine <sanine.not@pm.me> | 2023-03-11 15:58:20 -0600 |
---|---|---|
committer | sanine <sanine.not@pm.me> | 2023-03-11 15:58:20 -0600 |
commit | ebc50b387ab209c9f9a0d92e340ac293d5697274 (patch) | |
tree | ea8c8b3677a18c994d2b9d33dbef3461dcf18113 /libs/luajit-cmake/luajit/src/lj_trace.c | |
parent | c2329b4c8258baa9429c77566c9def97d00e96d7 (diff) |
build & link with luajit instead of lua5.1
Diffstat (limited to 'libs/luajit-cmake/luajit/src/lj_trace.c')
-rw-r--r-- | libs/luajit-cmake/luajit/src/lj_trace.c | 987 |
1 files changed, 987 insertions, 0 deletions
diff --git a/libs/luajit-cmake/luajit/src/lj_trace.c b/libs/luajit-cmake/luajit/src/lj_trace.c new file mode 100644 index 0000000..c232939 --- /dev/null +++ b/libs/luajit-cmake/luajit/src/lj_trace.c @@ -0,0 +1,987 @@ +/* +** Trace management. +** Copyright (C) 2005-2022 Mike Pall. See Copyright Notice in luajit.h +*/ + +#define lj_trace_c +#define LUA_CORE + +#include "lj_obj.h" + +#if LJ_HASJIT + +#include "lj_gc.h" +#include "lj_err.h" +#include "lj_debug.h" +#include "lj_str.h" +#include "lj_frame.h" +#include "lj_state.h" +#include "lj_bc.h" +#include "lj_ir.h" +#include "lj_jit.h" +#include "lj_iropt.h" +#include "lj_mcode.h" +#include "lj_trace.h" +#include "lj_snap.h" +#include "lj_gdbjit.h" +#include "lj_record.h" +#include "lj_asm.h" +#include "lj_dispatch.h" +#include "lj_vm.h" +#include "lj_vmevent.h" +#include "lj_target.h" +#include "lj_prng.h" + +/* -- Error handling ------------------------------------------------------ */ + +/* Synchronous abort with error message. */ +void lj_trace_err(jit_State *J, TraceError e) +{ + setnilV(&J->errinfo); /* No error info. */ + setintV(J->L->top++, (int32_t)e); + lj_err_throw(J->L, LUA_ERRRUN); +} + +/* Synchronous abort with error message and error info. */ +void lj_trace_err_info(jit_State *J, TraceError e) +{ + setintV(J->L->top++, (int32_t)e); + lj_err_throw(J->L, LUA_ERRRUN); +} + +/* -- Trace management ---------------------------------------------------- */ + +/* The current trace is first assembled in J->cur. The variable length +** arrays point to shared, growable buffers (J->irbuf etc.). When trace +** recording ends successfully, the current trace and its data structures +** are copied to a new (compact) GCtrace object. +*/ + +/* Find a free trace number. */ +static TraceNo trace_findfree(jit_State *J) +{ + MSize osz, lim; + if (J->freetrace == 0) + J->freetrace = 1; + for (; J->freetrace < J->sizetrace; J->freetrace++) + if (traceref(J, J->freetrace) == NULL) + return J->freetrace++; + /* Need to grow trace array. */ + lim = (MSize)J->param[JIT_P_maxtrace] + 1; + if (lim < 2) lim = 2; else if (lim > 65535) lim = 65535; + osz = J->sizetrace; + if (osz >= lim) + return 0; /* Too many traces. */ + lj_mem_growvec(J->L, J->trace, J->sizetrace, lim, GCRef); + for (; osz < J->sizetrace; osz++) + setgcrefnull(J->trace[osz]); + return J->freetrace; +} + +#define TRACE_APPENDVEC(field, szfield, tp) \ + T->field = (tp *)p; \ + memcpy(p, J->cur.field, J->cur.szfield*sizeof(tp)); \ + p += J->cur.szfield*sizeof(tp); + +#ifdef LUAJIT_USE_PERFTOOLS +/* +** Create symbol table of JIT-compiled code. For use with Linux perf tools. +** Example usage: +** perf record -f -e cycles luajit test.lua +** perf report -s symbol +** rm perf.data /tmp/perf-*.map +*/ +#include <stdio.h> +#include <unistd.h> + +static void perftools_addtrace(GCtrace *T) +{ + static FILE *fp; + GCproto *pt = &gcref(T->startpt)->pt; + const BCIns *startpc = mref(T->startpc, const BCIns); + const char *name = proto_chunknamestr(pt); + BCLine lineno; + if (name[0] == '@' || name[0] == '=') + name++; + else + name = "(string)"; + lj_assertX(startpc >= proto_bc(pt) && startpc < proto_bc(pt) + pt->sizebc, + "trace PC out of range"); + lineno = lj_debug_line(pt, proto_bcpos(pt, startpc)); + if (!fp) { + char fname[40]; + sprintf(fname, "/tmp/perf-%d.map", getpid()); + if (!(fp = fopen(fname, "w"))) return; + setlinebuf(fp); + } + fprintf(fp, "%lx %x TRACE_%d::%s:%u\n", + (long)T->mcode, T->szmcode, T->traceno, name, lineno); +} +#endif + +/* Allocate space for copy of T. */ +GCtrace * LJ_FASTCALL lj_trace_alloc(lua_State *L, GCtrace *T) +{ + size_t sztr = ((sizeof(GCtrace)+7)&~7); + size_t szins = (T->nins-T->nk)*sizeof(IRIns); + size_t sz = sztr + szins + + T->nsnap*sizeof(SnapShot) + + T->nsnapmap*sizeof(SnapEntry); + GCtrace *T2 = lj_mem_newt(L, (MSize)sz, GCtrace); + char *p = (char *)T2 + sztr; + T2->gct = ~LJ_TTRACE; + T2->marked = 0; + T2->traceno = 0; + T2->ir = (IRIns *)p - T->nk; + T2->nins = T->nins; + T2->nk = T->nk; + T2->nsnap = T->nsnap; + T2->nsnapmap = T->nsnapmap; + memcpy(p, T->ir + T->nk, szins); + return T2; +} + +/* Save current trace by copying and compacting it. */ +static void trace_save(jit_State *J, GCtrace *T) +{ + size_t sztr = ((sizeof(GCtrace)+7)&~7); + size_t szins = (J->cur.nins-J->cur.nk)*sizeof(IRIns); + char *p = (char *)T + sztr; + memcpy(T, &J->cur, sizeof(GCtrace)); + setgcrefr(T->nextgc, J2G(J)->gc.root); + setgcrefp(J2G(J)->gc.root, T); + newwhite(J2G(J), T); + T->gct = ~LJ_TTRACE; + T->ir = (IRIns *)p - J->cur.nk; /* The IR has already been copied above. */ + p += szins; + TRACE_APPENDVEC(snap, nsnap, SnapShot) + TRACE_APPENDVEC(snapmap, nsnapmap, SnapEntry) + J->cur.traceno = 0; + J->curfinal = NULL; + setgcrefp(J->trace[T->traceno], T); + lj_gc_barriertrace(J2G(J), T->traceno); + lj_gdbjit_addtrace(J, T); +#ifdef LUAJIT_USE_PERFTOOLS + perftools_addtrace(T); +#endif +} + +void LJ_FASTCALL lj_trace_free(global_State *g, GCtrace *T) +{ + jit_State *J = G2J(g); + if (T->traceno) { + lj_gdbjit_deltrace(J, T); + if (T->traceno < J->freetrace) + J->freetrace = T->traceno; + setgcrefnull(J->trace[T->traceno]); + } + lj_mem_free(g, T, + ((sizeof(GCtrace)+7)&~7) + (T->nins-T->nk)*sizeof(IRIns) + + T->nsnap*sizeof(SnapShot) + T->nsnapmap*sizeof(SnapEntry)); +} + +/* 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); + } + } +} + +/* 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; + } +} + +/* Flush a root trace. */ +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; + } + } + } +} + +/* Flush a trace. Only root traces are considered. */ +void lj_trace_flush(jit_State *J, TraceNo traceno) +{ + if (traceno > 0 && traceno < J->sizetrace) { + GCtrace *T = traceref(J, traceno); + if (T && T->root == 0) + trace_flushroot(J, T); + } +} + +/* Flush all traces associated with a prototype. */ +void lj_trace_flushproto(global_State *g, GCproto *pt) +{ + while (pt->trace != 0) + trace_flushroot(G2J(g), traceref(G2J(g), pt->trace)); +} + +/* Flush all traces. */ +int lj_trace_flushall(lua_State *L) +{ + jit_State *J = L2J(L); + ptrdiff_t i; + if ((J2G(J)->hookmask & HOOK_GC)) + return 1; + for (i = (ptrdiff_t)J->sizetrace-1; i > 0; i--) { + GCtrace *T = traceref(J, i); + if (T) { + if (T->root == 0) + trace_flushroot(J, T); + lj_gdbjit_deltrace(J, T); + T->traceno = T->link = 0; /* Blacklist the link for cont_stitch. */ + setgcrefnull(J->trace[i]); + } + } + J->cur.traceno = 0; + J->freetrace = 0; + /* Clear penalty cache. */ + memset(J->penalty, 0, sizeof(J->penalty)); + /* Free the whole machine code and invalidate all exit stub groups. */ + lj_mcode_free(J); + memset(J->exitstubgroup, 0, sizeof(J->exitstubgroup)); + lj_vmevent_send(L, TRACE, + setstrV(L, L->top++, lj_str_newlit(L, "flush")); + ); + return 0; +} + +/* Initialize JIT compiler state. */ +void lj_trace_initstate(global_State *g) +{ + jit_State *J = G2J(g); + TValue *tv; + + /* Initialize aligned SIMD constants. */ + tv = LJ_KSIMD(J, LJ_KSIMD_ABS); + tv[0].u64 = U64x(7fffffff,ffffffff); + tv[1].u64 = U64x(7fffffff,ffffffff); + tv = LJ_KSIMD(J, LJ_KSIMD_NEG); + tv[0].u64 = U64x(80000000,00000000); + tv[1].u64 = U64x(80000000,00000000); + + /* Initialize 32/64 bit constants. */ +#if LJ_TARGET_X86ORX64 + J->k64[LJ_K64_TOBIT].u64 = U64x(43380000,00000000); +#if LJ_32 + J->k64[LJ_K64_M2P64_31].u64 = U64x(c1e00000,00000000); +#endif + J->k64[LJ_K64_2P64].u64 = U64x(43f00000,00000000); + J->k32[LJ_K32_M2P64_31] = LJ_64 ? 0xdf800000 : 0xcf000000; +#endif +#if LJ_TARGET_X86ORX64 || LJ_TARGET_MIPS64 + J->k64[LJ_K64_M2P64].u64 = U64x(c3f00000,00000000); +#endif +#if LJ_TARGET_PPC + J->k32[LJ_K32_2P52_2P31] = 0x59800004; + J->k32[LJ_K32_2P52] = 0x59800000; +#endif +#if LJ_TARGET_PPC || LJ_TARGET_MIPS + J->k32[LJ_K32_2P31] = 0x4f000000; +#endif +#if LJ_TARGET_MIPS + J->k64[LJ_K64_2P31].u64 = U64x(41e00000,00000000); +#if LJ_64 + J->k64[LJ_K64_2P63].u64 = U64x(43e00000,00000000); + J->k32[LJ_K32_2P63] = 0x5f000000; + J->k32[LJ_K32_M2P64] = 0xdf800000; +#endif +#endif +} + +/* Free everything associated with the JIT compiler state. */ +void lj_trace_freestate(global_State *g) +{ + jit_State *J = G2J(g); +#ifdef LUA_USE_ASSERT + { /* This assumes all traces have already been freed. */ + ptrdiff_t i; + for (i = 1; i < (ptrdiff_t)J->sizetrace; i++) + lj_assertG(i == (ptrdiff_t)J->cur.traceno || traceref(J, i) == NULL, + "trace still allocated"); + } +#endif + lj_mcode_free(J); + lj_mem_freevec(g, J->snapmapbuf, J->sizesnapmap, SnapEntry); + lj_mem_freevec(g, J->snapbuf, J->sizesnap, SnapShot); + lj_mem_freevec(g, J->irbuf + J->irbotlim, J->irtoplim - J->irbotlim, IRIns); + lj_mem_freevec(g, J->trace, J->sizetrace, GCRef); +} + +/* -- Penalties and blacklisting ------------------------------------------ */ + +/* Blacklist a bytecode instruction. */ +static void blacklist_pc(GCproto *pt, BCIns *pc) +{ + if (bc_op(*pc) == BC_ITERN) { + setbc_op(pc, BC_ITERC); + setbc_op(pc+1+bc_j(pc[1]), BC_JMP); + } else { + setbc_op(pc, (int)bc_op(*pc)+(int)BC_ILOOP-(int)BC_LOOP); + pt->flags |= PROTO_ILOOP; + } +} + +/* Penalize a bytecode instruction. */ +static void penalty_pc(jit_State *J, GCproto *pt, BCIns *pc, TraceError e) +{ + uint32_t i, val = PENALTY_MIN; + for (i = 0; i < PENALTY_SLOTS; i++) + if (mref(J->penalty[i].pc, const BCIns) == pc) { /* Cache slot found? */ + /* First try to bump its hotcount several times. */ + val = ((uint32_t)J->penalty[i].val << 1) + + (lj_prng_u64(&J2G(J)->prng) & ((1u<<PENALTY_RNDBITS)-1)); + if (val > PENALTY_MAX) { + blacklist_pc(pt, pc); /* Blacklist it, if that didn't help. */ + return; + } + goto setpenalty; + } + /* Assign a new penalty cache slot. */ + i = J->penaltyslot; + J->penaltyslot = (J->penaltyslot + 1) & (PENALTY_SLOTS-1); + setmref(J->penalty[i].pc, pc); +setpenalty: + J->penalty[i].val = (uint16_t)val; + J->penalty[i].reason = e; + hotcount_set(J2GG(J), pc+1, val); +} + +/* -- Trace compiler state machine ---------------------------------------- */ + +/* Start tracing. */ +static void trace_start(jit_State *J) +{ + lua_State *L; + TraceNo traceno; + + if ((J->pt->flags & PROTO_NOJIT)) { /* JIT disabled for this proto? */ + if (J->parent == 0 && J->exitno == 0 && bc_op(*J->pc) != BC_ITERN) { + /* Lazy bytecode patching to disable hotcount events. */ + lj_assertJ(bc_op(*J->pc) == BC_FORL || bc_op(*J->pc) == BC_ITERL || + bc_op(*J->pc) == BC_LOOP || bc_op(*J->pc) == BC_FUNCF, + "bad hot bytecode %d", bc_op(*J->pc)); + setbc_op(J->pc, (int)bc_op(*J->pc)+(int)BC_ILOOP-(int)BC_LOOP); + J->pt->flags |= PROTO_ILOOP; + } + J->state = LJ_TRACE_IDLE; /* Silently ignored. */ + return; + } + + /* Get a new trace number. */ + traceno = trace_findfree(J); + if (LJ_UNLIKELY(traceno == 0)) { /* No free trace? */ + lj_assertJ((J2G(J)->hookmask & HOOK_GC) == 0, + "recorder called from GC hook"); + lj_trace_flushall(J->L); + J->state = LJ_TRACE_IDLE; /* Silently ignored. */ + return; + } + setgcrefp(J->trace[traceno], &J->cur); + + /* Setup enough of the current trace to be able to send the vmevent. */ + memset(&J->cur, 0, sizeof(GCtrace)); + J->cur.traceno = traceno; + J->cur.nins = J->cur.nk = REF_BASE; + J->cur.ir = J->irbuf; + J->cur.snap = J->snapbuf; + J->cur.snapmap = J->snapmapbuf; + J->mergesnap = 0; + J->needsnap = 0; + J->bcskip = 0; + J->guardemit.irt = 0; + J->postproc = LJ_POST_NONE; + lj_resetsplit(J); + J->retryrec = 0; + J->ktrace = 0; + setgcref(J->cur.startpt, obj2gco(J->pt)); + + L = J->L; + lj_vmevent_send(L, TRACE, + setstrV(L, L->top++, lj_str_newlit(L, "start")); + setintV(L->top++, traceno); + setfuncV(L, L->top++, J->fn); + setintV(L->top++, proto_bcpos(J->pt, J->pc)); + if (J->parent) { + setintV(L->top++, J->parent); + setintV(L->top++, J->exitno); + } else { + BCOp op = bc_op(*J->pc); + if (op == BC_CALLM || op == BC_CALL || op == BC_ITERC) { + setintV(L->top++, J->exitno); /* Parent of stitched trace. */ + setintV(L->top++, -1); + } + } + ); + lj_record_setup(J); +} + +/* Stop tracing. */ +static void trace_stop(jit_State *J) +{ + BCIns *pc = mref(J->cur.startpc, BCIns); + BCOp op = bc_op(J->cur.startins); + GCproto *pt = &gcref(J->cur.startpt)->pt; + TraceNo traceno = J->cur.traceno; + GCtrace *T = J->curfinal; + lua_State *L; + + switch (op) { + case BC_FORL: + setbc_op(pc+bc_j(J->cur.startins), BC_JFORI); /* Patch FORI, too. */ + /* fallthrough */ + case BC_LOOP: + case BC_ITERL: + case BC_FUNCF: + /* Patch bytecode of starting instruction in root trace. */ + setbc_op(pc, (int)op+(int)BC_JLOOP-(int)BC_LOOP); + setbc_d(pc, traceno); + addroot: + /* Add to root trace chain in prototype. */ + J->cur.nextroot = pt->trace; + pt->trace = (TraceNo1)traceno; + break; + case BC_ITERN: + case BC_RET: + case BC_RET0: + case BC_RET1: + *pc = BCINS_AD(BC_JLOOP, J->cur.snap[0].nslots, traceno); + goto addroot; + case BC_JMP: + /* Patch exit branch in parent to side trace entry. */ + lj_assertJ(J->parent != 0 && J->cur.root != 0, "not a side trace"); + lj_asm_patchexit(J, traceref(J, J->parent), J->exitno, J->cur.mcode); + /* Avoid compiling a side trace twice (stack resizing uses parent exit). */ + { + SnapShot *snap = &traceref(J, J->parent)->snap[J->exitno]; + snap->count = SNAPCOUNT_DONE; + if (J->cur.topslot > snap->topslot) snap->topslot = J->cur.topslot; + } + /* Add to side trace chain in root trace. */ + { + GCtrace *root = traceref(J, J->cur.root); + root->nchild++; + J->cur.nextside = root->nextside; + root->nextside = (TraceNo1)traceno; + } + break; + case BC_CALLM: + case BC_CALL: + case BC_ITERC: + /* Trace stitching: patch link of previous trace. */ + traceref(J, J->exitno)->link = traceno; + break; + default: + lj_assertJ(0, "bad stop bytecode %d", op); + break; + } + + /* Commit new mcode only after all patching is done. */ + lj_mcode_commit(J, J->cur.mcode); + J->postproc = LJ_POST_NONE; + trace_save(J, T); + + L = J->L; + lj_vmevent_send(L, TRACE, + setstrV(L, L->top++, lj_str_newlit(L, "stop")); + setintV(L->top++, traceno); + setfuncV(L, L->top++, J->fn); + ); +} + +/* Start a new root trace for down-recursion. */ +static int trace_downrec(jit_State *J) +{ + /* Restart recording at the return instruction. */ + lj_assertJ(J->pt != NULL, "no active prototype"); + lj_assertJ(bc_isret(bc_op(*J->pc)), "not at a return bytecode"); + if (bc_op(*J->pc) == BC_RETM) + return 0; /* NYI: down-recursion with RETM. */ + J->parent = 0; + J->exitno = 0; + J->state = LJ_TRACE_RECORD; + trace_start(J); + return 1; +} + +/* Abort tracing. */ +static int trace_abort(jit_State *J) +{ + lua_State *L = J->L; + TraceError e = LJ_TRERR_RECERR; + TraceNo traceno; + + J->postproc = LJ_POST_NONE; + lj_mcode_abort(J); + if (J->curfinal) { + lj_trace_free(J2G(J), J->curfinal); + J->curfinal = NULL; + } + if (tvisnumber(L->top-1)) + e = (TraceError)numberVint(L->top-1); + if (e == LJ_TRERR_MCODELM) { + L->top--; /* Remove error object */ + J->state = LJ_TRACE_ASM; + return 1; /* Retry ASM with new MCode area. */ + } + /* Penalize or blacklist starting bytecode instruction. */ + if (J->parent == 0 && !bc_isret(bc_op(J->cur.startins))) { + if (J->exitno == 0) { + BCIns *startpc = mref(J->cur.startpc, BCIns); + if (e == LJ_TRERR_RETRY) + hotcount_set(J2GG(J), startpc+1, 1); /* Immediate retry. */ + else + penalty_pc(J, &gcref(J->cur.startpt)->pt, startpc, e); + } else { + traceref(J, J->exitno)->link = J->exitno; /* Self-link is blacklisted. */ + } + } + + /* Is there anything to abort? */ + traceno = J->cur.traceno; + if (traceno) { + ptrdiff_t errobj = savestack(L, L->top-1); /* Stack may be resized. */ + J->cur.link = 0; + J->cur.linktype = LJ_TRLINK_NONE; + lj_vmevent_send(L, TRACE, + TValue *frame; + const BCIns *pc; + GCfunc *fn; + setstrV(L, L->top++, lj_str_newlit(L, "abort")); + setintV(L->top++, traceno); + /* Find original Lua function call to generate a better error message. */ + frame = J->L->base-1; + pc = J->pc; + while (!isluafunc(frame_func(frame))) { + pc = (frame_iscont(frame) ? frame_contpc(frame) : frame_pc(frame)) - 1; + frame = frame_prev(frame); + } + fn = frame_func(frame); + setfuncV(L, L->top++, fn); + setintV(L->top++, proto_bcpos(funcproto(fn), pc)); + copyTV(L, L->top++, restorestack(L, errobj)); + copyTV(L, L->top++, &J->errinfo); + ); + /* Drop aborted trace after the vmevent (which may still access it). */ + setgcrefnull(J->trace[traceno]); + if (traceno < J->freetrace) + J->freetrace = traceno; + J->cur.traceno = 0; + } + L->top--; /* Remove error object */ + if (e == LJ_TRERR_DOWNREC) + return trace_downrec(J); + else if (e == LJ_TRERR_MCODEAL) + lj_trace_flushall(L); + return 0; +} + +/* Perform pending re-patch of a bytecode instruction. */ +static LJ_AINLINE void trace_pendpatch(jit_State *J, int force) +{ + if (LJ_UNLIKELY(J->patchpc)) { + if (force || J->bcskip == 0) { + *J->patchpc = J->patchins; + J->patchpc = NULL; + } else { + J->bcskip = 0; + } + } +} + +/* State machine for the trace compiler. Protected callback. */ +static TValue *trace_state(lua_State *L, lua_CFunction dummy, void *ud) +{ + jit_State *J = (jit_State *)ud; + UNUSED(dummy); + do { + retry: + switch (J->state) { + case LJ_TRACE_START: + J->state = LJ_TRACE_RECORD; /* trace_start() may change state. */ + trace_start(J); + lj_dispatch_update(J2G(J)); + if (J->state != LJ_TRACE_RECORD_1ST) + break; + /* fallthrough */ + + case LJ_TRACE_RECORD_1ST: + J->state = LJ_TRACE_RECORD; + /* fallthrough */ + case LJ_TRACE_RECORD: + trace_pendpatch(J, 0); + setvmstate(J2G(J), RECORD); + lj_vmevent_send_(L, RECORD, + /* Save/restore state for trace recorder. */ + TValue savetv = J2G(J)->tmptv; + TValue savetv2 = J2G(J)->tmptv2; + TraceNo parent = J->parent; + ExitNo exitno = J->exitno; + setintV(L->top++, J->cur.traceno); + setfuncV(L, L->top++, J->fn); + setintV(L->top++, J->pt ? (int32_t)proto_bcpos(J->pt, J->pc) : -1); + setintV(L->top++, J->framedepth); + , + J2G(J)->tmptv = savetv; + J2G(J)->tmptv2 = savetv2; + J->parent = parent; + J->exitno = exitno; + ); + lj_record_ins(J); + break; + + case LJ_TRACE_END: + trace_pendpatch(J, 1); + J->loopref = 0; + if ((J->flags & JIT_F_OPT_LOOP) && + J->cur.link == J->cur.traceno && J->framedepth + J->retdepth == 0) { + setvmstate(J2G(J), OPT); + lj_opt_dce(J); + if (lj_opt_loop(J)) { /* Loop optimization failed? */ + J->cur.link = 0; + J->cur.linktype = LJ_TRLINK_NONE; + J->loopref = J->cur.nins; + J->state = LJ_TRACE_RECORD; /* Try to continue recording. */ + break; + } + J->loopref = J->chain[IR_LOOP]; /* Needed by assembler. */ + } + lj_opt_split(J); + lj_opt_sink(J); + if (!J->loopref) J->cur.snap[J->cur.nsnap-1].count = SNAPCOUNT_DONE; + J->state = LJ_TRACE_ASM; + break; + + case LJ_TRACE_ASM: + setvmstate(J2G(J), ASM); + lj_asm_trace(J, &J->cur); + trace_stop(J); + setvmstate(J2G(J), INTERP); + J->state = LJ_TRACE_IDLE; + lj_dispatch_update(J2G(J)); + return NULL; + + default: /* Trace aborted asynchronously. */ + setintV(L->top++, (int32_t)LJ_TRERR_RECERR); + /* fallthrough */ + case LJ_TRACE_ERR: + trace_pendpatch(J, 1); + if (trace_abort(J)) + goto retry; + setvmstate(J2G(J), INTERP); + J->state = LJ_TRACE_IDLE; + lj_dispatch_update(J2G(J)); + return NULL; + } + } while (J->state > LJ_TRACE_RECORD); + return NULL; +} + +/* -- Event handling ------------------------------------------------------ */ + +/* A bytecode instruction is about to be executed. Record it. */ +void lj_trace_ins(jit_State *J, const BCIns *pc) +{ + /* Note: J->L must already be set. pc is the true bytecode PC here. */ + J->pc = pc; + J->fn = curr_func(J->L); + J->pt = isluafunc(J->fn) ? funcproto(J->fn) : NULL; + while (lj_vm_cpcall(J->L, NULL, (void *)J, trace_state) != 0) + J->state = LJ_TRACE_ERR; +} + +/* A hotcount triggered. Start recording a root trace. */ +void LJ_FASTCALL lj_trace_hot(jit_State *J, const BCIns *pc) +{ + /* Note: pc is the interpreter bytecode PC here. It's offset by 1. */ + ERRNO_SAVE + /* Reset hotcount. */ + hotcount_set(J2GG(J), pc, J->param[JIT_P_hotloop]*HOTCOUNT_LOOP); + /* Only start a new trace if not recording or inside __gc call or vmevent. */ + if (J->state == LJ_TRACE_IDLE && + !(J2G(J)->hookmask & (HOOK_GC|HOOK_VMEVENT))) { + J->parent = 0; /* Root trace. */ + J->exitno = 0; + J->state = LJ_TRACE_START; + lj_trace_ins(J, pc-1); + } + ERRNO_RESTORE +} + +/* Check for a hot side exit. If yes, start recording a side trace. */ +static void trace_hotside(jit_State *J, const BCIns *pc) +{ + SnapShot *snap = &traceref(J, J->parent)->snap[J->exitno]; + if (!(J2G(J)->hookmask & (HOOK_GC|HOOK_VMEVENT)) && + isluafunc(curr_func(J->L)) && + snap->count != SNAPCOUNT_DONE && + ++snap->count >= J->param[JIT_P_hotexit]) { + lj_assertJ(J->state == LJ_TRACE_IDLE, "hot side exit while recording"); + /* J->parent is non-zero for a side trace. */ + J->state = LJ_TRACE_START; + lj_trace_ins(J, pc); + } +} + +/* Stitch a new trace to the previous trace. */ +void LJ_FASTCALL lj_trace_stitch(jit_State *J, const BCIns *pc) +{ + /* Only start a new trace if not recording or inside __gc call or vmevent. */ + if (J->state == LJ_TRACE_IDLE && + !(J2G(J)->hookmask & (HOOK_GC|HOOK_VMEVENT))) { + J->parent = 0; /* Have to treat it like a root trace. */ + /* J->exitno is set to the invoking trace. */ + J->state = LJ_TRACE_START; + lj_trace_ins(J, pc); + } +} + + +/* Tiny struct to pass data to protected call. */ +typedef struct ExitDataCP { + jit_State *J; + void *exptr; /* Pointer to exit state. */ + const BCIns *pc; /* Restart interpreter at this PC. */ +} ExitDataCP; + +/* Need to protect lj_snap_restore because it may throw. */ +static TValue *trace_exit_cp(lua_State *L, lua_CFunction dummy, void *ud) +{ + ExitDataCP *exd = (ExitDataCP *)ud; + /* Always catch error here and don't call error function. */ + cframe_errfunc(L->cframe) = 0; + cframe_nres(L->cframe) = -2*LUAI_MAXSTACK*(int)sizeof(TValue); + exd->pc = lj_snap_restore(exd->J, exd->exptr); + UNUSED(dummy); + return NULL; +} + +#ifndef LUAJIT_DISABLE_VMEVENT +/* Push all registers from exit state. */ +static void trace_exit_regs(lua_State *L, ExitState *ex) +{ + int32_t i; + setintV(L->top++, RID_NUM_GPR); + setintV(L->top++, RID_NUM_FPR); + for (i = 0; i < RID_NUM_GPR; i++) { + if (sizeof(ex->gpr[i]) == sizeof(int32_t)) + setintV(L->top++, (int32_t)ex->gpr[i]); + else + setnumV(L->top++, (lua_Number)ex->gpr[i]); + } +#if !LJ_SOFTFP + for (i = 0; i < RID_NUM_FPR; i++) { + setnumV(L->top, ex->fpr[i]); + if (LJ_UNLIKELY(tvisnan(L->top))) + setnanV(L->top); + L->top++; + } +#endif +} +#endif + +#if defined(EXITSTATE_PCREG) || (LJ_UNWIND_JIT && !EXITTRACE_VMSTATE) +/* Determine trace number from pc of exit instruction. */ +static TraceNo trace_exit_find(jit_State *J, MCode *pc) +{ + TraceNo traceno; + for (traceno = 1; traceno < J->sizetrace; traceno++) { + GCtrace *T = traceref(J, traceno); + if (T && pc >= T->mcode && pc < (MCode *)((char *)T->mcode + T->szmcode)) + return traceno; + } + lj_assertJ(0, "bad exit pc"); + return 0; +} +#endif + +/* A trace exited. Restore interpreter state. */ +int LJ_FASTCALL lj_trace_exit(jit_State *J, void *exptr) +{ + ERRNO_SAVE + lua_State *L = J->L; + ExitState *ex = (ExitState *)exptr; + ExitDataCP exd; + int errcode, exitcode = J->exitcode; + TValue exiterr; + const BCIns *pc; + void *cf; + GCtrace *T; + + setnilV(&exiterr); + if (exitcode) { /* Trace unwound with error code. */ + J->exitcode = 0; + copyTV(L, &exiterr, L->top-1); + } + +#ifdef EXITSTATE_PCREG + J->parent = trace_exit_find(J, (MCode *)(intptr_t)ex->gpr[EXITSTATE_PCREG]); +#endif + T = traceref(J, J->parent); UNUSED(T); +#ifdef EXITSTATE_CHECKEXIT + if (J->exitno == T->nsnap) { /* Treat stack check like a parent exit. */ + lj_assertJ(T->root != 0, "stack check in root trace"); + J->exitno = T->ir[REF_BASE].op2; + J->parent = T->ir[REF_BASE].op1; + T = traceref(J, J->parent); + } +#endif + lj_assertJ(T != NULL && J->exitno < T->nsnap, "bad trace or exit number"); + exd.J = J; + exd.exptr = exptr; + errcode = lj_vm_cpcall(L, NULL, &exd, trace_exit_cp); + if (errcode) + return -errcode; /* Return negated error code. */ + + if (exitcode) copyTV(L, L->top++, &exiterr); /* Anchor the error object. */ + + if (!(LJ_HASPROFILE && (G(L)->hookmask & HOOK_PROFILE))) + lj_vmevent_send(L, TEXIT, + lj_state_checkstack(L, 4+RID_NUM_GPR+RID_NUM_FPR+LUA_MINSTACK); + setintV(L->top++, J->parent); + setintV(L->top++, J->exitno); + trace_exit_regs(L, ex); + ); + + pc = exd.pc; + cf = cframe_raw(L->cframe); + setcframe_pc(cf, pc); + if (exitcode) { + return -exitcode; + } else if (LJ_HASPROFILE && (G(L)->hookmask & HOOK_PROFILE)) { + /* Just exit to interpreter. */ + } else if (G(L)->gc.state == GCSatomic || G(L)->gc.state == GCSfinalize) { + if (!(G(L)->hookmask & HOOK_GC)) + lj_gc_step(L); /* Exited because of GC: drive GC forward. */ + } else { + trace_hotside(J, pc); + } + if (bc_op(*pc) == BC_JLOOP) { + BCIns *retpc = &traceref(J, bc_d(*pc))->startins; + int isret = bc_isret(bc_op(*retpc)); + if (isret || bc_op(*retpc) == BC_ITERN) { + if (J->state == LJ_TRACE_RECORD) { + J->patchins = *pc; + J->patchpc = (BCIns *)pc; + *J->patchpc = *retpc; + J->bcskip = 1; + } else if (isret) { + pc = retpc; + setcframe_pc(cf, pc); + } + } + } + /* Return MULTRES or 0. */ + ERRNO_RESTORE + switch (bc_op(*pc)) { + case BC_CALLM: case BC_CALLMT: + return (int)((BCReg)(L->top - L->base) - bc_a(*pc) - bc_c(*pc) - LJ_FR2); + case BC_RETM: + return (int)((BCReg)(L->top - L->base) + 1 - bc_a(*pc) - bc_d(*pc)); + case BC_TSETM: + return (int)((BCReg)(L->top - L->base) + 1 - bc_a(*pc)); + default: + if (bc_op(*pc) >= BC_FUNCF) + return (int)((BCReg)(L->top - L->base) + 1); + return 0; + } +} + +#if LJ_UNWIND_JIT +/* Given an mcode address determine trace exit address for unwinding. */ +uintptr_t LJ_FASTCALL lj_trace_unwind(jit_State *J, uintptr_t addr, ExitNo *ep) +{ +#if EXITTRACE_VMSTATE + TraceNo traceno = J2G(J)->vmstate; +#else + TraceNo traceno = trace_exit_find(J, (MCode *)addr); +#endif + GCtrace *T = traceref(J, traceno); + if (T +#if EXITTRACE_VMSTATE + && addr >= (uintptr_t)T->mcode && addr < (uintptr_t)T->mcode + T->szmcode +#endif + ) { + SnapShot *snap = T->snap; + SnapNo lo = 0, exitno = T->nsnap; + uintptr_t ofs = (uintptr_t)((MCode *)addr - T->mcode); /* MCode units! */ + /* Rightmost binary search for mcode offset to determine exit number. */ + do { + SnapNo mid = (lo+exitno) >> 1; + if (ofs < snap[mid].mcofs) exitno = mid; else lo = mid + 1; + } while (lo < exitno); + exitno--; + *ep = exitno; +#ifdef EXITSTUBS_PER_GROUP + return (uintptr_t)exitstub_addr(J, exitno); +#else + return (uintptr_t)exitstub_trace_addr(T, exitno); +#endif + } + /* Cannot correlate addr with trace/exit. This will be fatal. */ + lj_assertJ(0, "bad exit pc"); + return 0; +} +#endif + +#endif |