Compile table traversals: next(), pairs(), BC_ISNEXT/BC_ITERN.

Sponsored by OpenResty Inc.
This commit is contained in:
Mike Pall
2021-09-19 17:49:25 +02:00
parent 986bb406ad
commit bb0f241015
27 changed files with 781 additions and 47 deletions

View File

@@ -156,6 +156,9 @@ static void rec_check_slots(jit_State *J)
lj_assertJ((J->slot[s+1+LJ_FR2] & TREF_FRAME),
"cont slot %d not followed by frame", s);
depth++;
} else if ((tr & TREF_KEYINDEX)) {
lj_assertJ(tref_isint(tr), "keyindex slot %d bad type %d",
s, tref_type(tr));
} else {
/* Number repr. may differ, but other types must be the same. */
lj_assertJ(tvisnumber(tv) ? tref_isnumber(tr) :
@@ -283,9 +286,9 @@ static void canonicalize_slots(jit_State *J)
if (LJ_DUALNUM) return;
for (s = J->baseslot+J->maxslot-1; s >= 1; s--) {
TRef tr = J->slot[s];
if (tref_isinteger(tr)) {
if (tref_isinteger(tr) && !(tr & TREF_KEYINDEX)) {
IRIns *ir = IR(tref_ref(tr));
if (!(ir->o == IR_SLOAD && (ir->op2 & IRSLOAD_READONLY)))
if (!(ir->o == IR_SLOAD && (ir->op2 & (IRSLOAD_READONLY))))
J->slot[s] = emitir(IRTN(IR_CONV), tr, IRCONV_NUM_INT);
}
}
@@ -606,6 +609,7 @@ static void rec_loop_interp(jit_State *J, const BCIns *pc, LoopEvent ev)
{
if (J->parent == 0 && J->exitno == 0) {
if (pc == J->startpc && J->framedepth + J->retdepth == 0) {
if (bc_op(J->cur.startins) == BC_ITERN) return; /* See rec_itern(). */
/* Same loop? */
if (ev == LOOPEV_LEAVE) /* Must loop back to form a root trace. */
lj_trace_err(J, LJ_TRERR_LLEAVE);
@@ -646,6 +650,68 @@ static void rec_loop_jit(jit_State *J, TraceNo lnk, LoopEvent ev)
} /* Side trace continues across a loop that's left or not entered. */
}
/* Record ITERN. */
static LoopEvent rec_itern(jit_State *J, BCReg ra, BCReg rb)
{
#if LJ_BE
/* YAGNI: Disabled on big-endian due to issues with lj_vm_next,
** IR_HIOP, RID_RETLO/RID_RETHI and ra_destpair.
*/
UNUSED(ra); UNUSED(rb);
setintV(&J->errinfo, (int32_t)BC_ITERN);
lj_trace_err_info(J, LJ_TRERR_NYIBC);
#else
RecordIndex ix;
/* Since ITERN is recorded at the start, we need our own loop detection. */
if (J->pc == J->startpc && J->cur.nins > REF_FIRST &&
J->framedepth + J->retdepth == 0 && J->parent == 0 && J->exitno == 0) {
lj_record_stop(J, LJ_TRLINK_LOOP, J->cur.traceno); /* Looping trace. */
return LOOPEV_ENTER;
}
J->maxslot = ra;
lj_snap_add(J); /* Required to make JLOOP the first ins in a side-trace. */
ix.tab = getslot(J, ra-2);
ix.key = J->base[ra-1] ? J->base[ra-1] :
sloadt(J, (int32_t)(ra-1), IRT_INT, IRSLOAD_KEYINDEX);
copyTV(J->L, &ix.tabv, &J->L->base[ra-2]);
copyTV(J->L, &ix.keyv, &J->L->base[ra-1]);
ix.idxchain = (rb < 3); /* Omit value type check, if unused. */
ix.mobj = 1; /* We need the next index, too. */
J->maxslot = ra + lj_record_next(J, &ix);
J->needsnap = 1;
if (!tref_isnil(ix.key)) { /* Looping back? */
J->base[ra-1] = ix.mobj | TREF_KEYINDEX; /* Control var has next index. */
J->base[ra] = ix.key;
J->base[ra+1] = ix.val;
J->pc += bc_j(J->pc[1])+2;
return LOOPEV_ENTER;
} else {
J->maxslot = ra-3;
J->pc += 2;
return LOOPEV_LEAVE;
}
#endif
}
/* Record ISNEXT. */
static void rec_isnext(jit_State *J, BCReg ra)
{
cTValue *b = &J->L->base[ra-3];
if (tvisfunc(b) && funcV(b)->c.ffid == FF_next &&
tvistab(b+1) && tvisnil(b+2)) {
/* These checks are folded away for a compiled pairs(). */
TRef func = getslot(J, ra-3);
TRef trid = emitir(IRT(IR_FLOAD, IRT_U8), func, IRFL_FUNC_FFID);
emitir(IRTGI(IR_EQ), trid, lj_ir_kint(J, FF_next));
(void)getslot(J, ra-2); /* Type check for table. */
(void)getslot(J, ra-1); /* Type check for nil key. */
J->base[ra-1] = lj_ir_kint(J, 0) | TREF_KEYINDEX;
J->maxslot = ra;
} else { /* Abort trace. Interpreter will despecialize bytecode. */
lj_trace_err(J, LJ_TRERR_RECERR);
}
}
/* -- Record profiler hook checks ----------------------------------------- */
#if LJ_HASPROFILE
@@ -716,7 +782,7 @@ static TRef rec_call_specialize(jit_State *J, GCfunc *fn, TRef tr)
/* NYI: io_file_iter doesn't have an ffid, yet. */
{ /* Specialize to the ffid. */
TRef trid = emitir(IRT(IR_FLOAD, IRT_U8), tr, IRFL_FUNC_FFID);
emitir(IRTG(IR_EQ, IRT_INT), trid, lj_ir_kint(J, fn->c.ffid));
emitir(IRTGI(IR_EQ), trid, lj_ir_kint(J, fn->c.ffid));
}
return tr;
default:
@@ -1565,6 +1631,47 @@ TRef lj_record_idx(jit_State *J, RecordIndex *ix)
}
}
/* Determine result type of table traversal. */
static IRType rec_next_types(GCtab *t, uint32_t idx)
{
for (; idx < t->asize; idx++) {
cTValue *a = arrayslot(t, idx);
if (LJ_LIKELY(!tvisnil(a)))
return (LJ_DUALNUM ? IRT_INT : IRT_NUM) + (itype2irt(a) << 8);
}
idx -= t->asize;
for (; idx <= t->hmask; idx++) {
Node *n = &noderef(t->node)[idx];
if (!tvisnil(&n->val))
return itype2irt(&n->key) + (itype2irt(&n->val) << 8);
}
return IRT_NIL + (IRT_NIL << 8);
}
/* Record a table traversal step aka next(). */
int lj_record_next(jit_State *J, RecordIndex *ix)
{
IRType t, tkey, tval;
TRef trvk;
t = rec_next_types(tabV(&ix->tabv), ix->keyv.u32.lo);
tkey = (t & 0xff); tval = (t >> 8);
trvk = lj_ir_call(J, IRCALL_lj_vm_next, ix->tab, ix->key);
if (ix->mobj || tkey == IRT_NIL) {
TRef idx = emitir(IRTI(IR_HIOP), trvk, trvk);
/* Always check for invalid key from next() for nil result. */
if (!ix->mobj) emitir(IRTGI(IR_NE), idx, lj_ir_kint(J, -1));
ix->mobj = idx;
}
ix->key = lj_record_vload(J, trvk, 1, tkey);
if (tkey == IRT_NIL || ix->idxchain) { /* Omit value type check. */
ix->val = TREF_NIL;
return 1;
} else { /* Need value. */
ix->val = lj_record_vload(J, trvk, 0, tval);
return 2;
}
}
static void rec_tsetm(jit_State *J, BCReg ra, BCReg rn, int32_t i)
{
RecordIndex ix;
@@ -2440,6 +2547,9 @@ void lj_record_ins(jit_State *J)
case BC_ITERL:
rec_loop_interp(J, pc, rec_iterl(J, *pc));
break;
case BC_ITERN:
rec_loop_interp(J, pc, rec_itern(J, ra, rb));
break;
case BC_LOOP:
rec_loop_interp(J, pc, rec_loop(J, ra, 1));
break;
@@ -2468,6 +2578,10 @@ void lj_record_ins(jit_State *J)
J->maxslot = ra; /* Shrink used slots. */
break;
case BC_ISNEXT:
rec_isnext(J, ra);
break;
/* -- Function headers -------------------------------------------------- */
case BC_FUNCF:
@@ -2497,8 +2611,6 @@ void lj_record_ins(jit_State *J)
break;
}
/* fallthrough */
case BC_ITERN:
case BC_ISNEXT:
case BC_UCLO:
case BC_FNEW:
setintV(&J->errinfo, (int32_t)op);
@@ -2550,6 +2662,13 @@ static const BCIns *rec_setup_root(jit_State *J)
lj_assertJ(bc_op(pc[-1]) == BC_JMP, "ITERL does not point to JMP+1");
J->bc_min = pc;
break;
case BC_ITERN:
lj_assertJ(bc_op(pc[1]) == BC_ITERL, "no ITERL after ITERN");
J->maxslot = ra;
J->bc_extent = (MSize)(-bc_j(pc[1]))*sizeof(BCIns);
J->bc_min = pc+2 + bc_j(pc[1]);
J->state = LJ_TRACE_RECORD_1ST; /* Record the first ITERN, too. */
break;
case BC_LOOP:
/* Only check BC range for real loops, but not for "repeat until true". */
pcj = pc + bc_j(ins);
@@ -2657,6 +2776,7 @@ void lj_record_setup(jit_State *J)
J->pc = rec_setup_root(J);
/* Note: the loop instruction itself is recorded at the end and not
** at the start! So snapshot #0 needs to point to the *next* instruction.
** The one exception is BC_ITERN, which sets LJ_TRACE_RECORD_1ST.
*/
lj_snap_add(J);
if (bc_op(J->cur.startins) == BC_FORL)