static int recff_metacall(jit_State *J, RecordFFData *rd, MMS mm) { RecordIndex ix; ix.tab = J->base[0]; copyTV(J->L, &ix.tabv, &rd->argv[0]); if (lj_record_mm_lookup(J, &ix, mm)) { /* Has metamethod? */ int errcode; TValue argv0; /* Temporarily insert metamethod below object. */ J->base[1] = J->base[0]; J->base[0] = ix.mobj; copyTV(J->L, &argv0, &rd->argv[0]); copyTV(J->L, &rd->argv[1], &rd->argv[0]); copyTV(J->L, &rd->argv[0], &ix.mobjv); /* Need to protect lj_record_tailcall because it may throw. */ errcode = lj_vm_cpcall(J->L, NULL, J, recff_metacall_cp); /* Always undo Lua stack changes to avoid confusing the interpreter. */ copyTV(J->L, &rd->argv[0], &argv0); if (errcode) lj_err_throw(J->L, errcode); /* Propagate errors. */ rd->nres = -1; /* Pending call. */ return 1; /* Tailcalled to metamethod. */ } return 0; }
/* Setup call to metamethod to be run by Assembler VM. */ static TValue *mmcall(lua_State *L, ASMFunction cont, cTValue *mo, cTValue *a, cTValue *b) { /* ** |-- framesize -> top top+1 top+2 top+3 ** before: [func slots ...] ** mm setup: [func slots ...] [cont|?] [mo|tmtype] [a] [b] ** in asm: [func slots ...] [cont|PC] [mo|delta] [a] [b] ** ^-- func base ^-- mm base ** after mm: [func slots ...] [result] ** ^-- copy to base[PC_RA] --/ for lj_cont_ra ** istruecond + branch for lj_cont_cond* ** ignore for lj_cont_nop ** next PC: [func slots ...] */ TValue *top = L->top; if (curr_funcisL(L)) top = curr_topL(L); setcont(top++, cont); /* Assembler VM stores PC in upper word or FR2. */ if (LJ_FR2) setnilV(top++); copyTV(L, top++, mo); /* Store metamethod and two arguments. */ if (LJ_FR2) setnilV(top++); copyTV(L, top, a); copyTV(L, top+1, b); return top; /* Return new base. */ }
/* Handle ctype __index/__newindex metamethods. */ static int ffi_index_meta(lua_State *L, CTState *cts, CType *ct, MMS mm) { CTypeID id = ctype_typeid(cts, ct); cTValue *tv = lj_ctype_meta(cts, id, mm); TValue *base = L->base; if (!tv) { const char *s; err_index: s = strdata(lj_ctype_repr(L, id, NULL)); if (tvisstr(L->base+1)) lj_err_callerv(L, LJ_ERR_FFI_BADMEMBER, s, strVdata(L->base+1)); else lj_err_callerv(L, LJ_ERR_FFI_BADIDX, s); } if (!tvisfunc(tv)) { if (mm == MM_index) { cTValue *o = lj_meta_tget(L, tv, base+1); if (o) { if (tvisnil(o)) goto err_index; copyTV(L, L->top-1, o); return 1; } } else { TValue *o = lj_meta_tset(L, tv, base+1); if (o) { copyTV(L, o, base+2); return 0; } } tv = L->top-1; } return lj_meta_tailcall(L, tv); }
/* Helper for calls. __call metamethod. */ void lj_meta_call(lua_State *L, TValue *func, TValue *top) { cTValue *mo = lj_meta_lookup(L, func, MM_call); TValue *p; if (!tvisfunc(mo)) lj_err_optype_call(L, func); for (p = top; p > func; p--) copyTV(L, p, p-1); copyTV(L, func, mo); }
static void LJ_FASTCALL recff_rawset(jit_State *J, RecordFFData *rd) { RecordIndex ix; ix.tab = J->base[0]; ix.key = J->base[1]; ix.val = J->base[2]; if (tref_istab(ix.tab) && ix.key && ix.val) { ix.idxchain = 0; settabV(J->L, &ix.tabv, tabV(&rd->argv[0])); copyTV(J->L, &ix.keyv, &rd->argv[1]); copyTV(J->L, &ix.valv, &rd->argv[2]); lj_record_idx(J, &ix); /* Pass through table at J->base[0] as result. */ } /* else: Interpreter will throw. */ }
/* Tailcall from C function. */ int lj_meta_tailcall(lua_State *L, cTValue *tv) { TValue *base = L->base; TValue *top = L->top; const BCIns *pc = frame_pc(base-1); /* Preserve old PC from frame. */ copyTV(L, base-1-LJ_FR2, tv); /* Replace frame with new object. */ if (LJ_FR2) (top++)->u64 = LJ_CONT_TAILCALL; else top->u32.lo = LJ_CONT_TAILCALL; setframe_pc(top++, pc); if (LJ_FR2) top++; setframe_gc(top, obj2gco(L), LJ_TTHREAD); /* Dummy frame object. */ setframe_ftsz(top, ((char *)(top+1) - (char *)base) + FRAME_CONT); L->base = L->top = top+1; /* ** before: [old_mo|PC] [... ...] ** ^base ^top ** after: [new_mo|itype] [... ...] [NULL|PC] [dummy|delta] ** ^base/top ** tailcall: [new_mo|PC] [... ...] ** ^base ^top */ return 0; }
LUALIB_API int luaL_loadfilex(lua_State *L, const char *filename, const char *mode) { FileReaderCtx ctx; int status; const char *chunkname; if (filename) { ctx.fp = _lua_fopen(filename, "rb"); if (ctx.fp == NULL) { lua_pushfstring(L, "cannot open %s: %s", filename, strerror(errno)); return LUA_ERRFILE; } chunkname = lua_pushfstring(L, "@%s", filename); } else { ctx.fp = stdin; chunkname = "=stdin"; } status = lua_loadx(L, reader_file, &ctx, chunkname, mode); if (ferror(ctx.fp)) { L->top -= filename ? 2 : 1; lua_pushfstring(L, "cannot read %s: %s", chunkname+1, strerror(errno)); if (filename) fclose(ctx.fp); return LUA_ERRFILE; } if (filename) { L->top--; copyTV(L, L->top-1, L->top); fclose(ctx.fp); } return status; }
/* Unwind Lua stack and move error message to new top. */ LJ_NOINLINE static void unwindstack(lua_State *L, TValue *top) { lj_func_closeuv(L, top); if (top < L->top-1) { copyTV(L, top, L->top-1); L->top = top+1; } lj_state_relimitstack(L); }
/* Runtime error. */ LJ_NOINLINE void lj_err_run(lua_State *L) { ptrdiff_t ef = finderrfunc(L); if (ef) { TValue *errfunc = restorestack(L, ef); TValue *top = L->top; lj_trace_abort(G(L)); if (!tvisfunc(errfunc) || L->status == LUA_ERRERR) { setstrV(L, top-1, lj_err_str(L, LJ_ERR_ERRERR)); lj_err_throw(L, LUA_ERRERR); } L->status = LUA_ERRERR; copyTV(L, top, top-1); copyTV(L, top-1, errfunc); L->top = top+1; lj_vm_call(L, top, 1+1); /* Stack: |errfunc|msg| -> |msg| */ } lj_err_throw(L, LUA_ERRRUN); }
static void LJ_FASTCALL recff_rawget(jit_State *J, RecordFFData *rd) { RecordIndex ix; ix.tab = J->base[0]; ix.key = J->base[1]; if (tref_istab(ix.tab) && ix.key) { ix.val = 0; ix.idxchain = 0; settabV(J->L, &ix.tabv, tabV(&rd->argv[0])); copyTV(J->L, &ix.keyv, &rd->argv[1]); J->base[0] = lj_record_idx(J, &ix); } /* else: Interpreter will throw. */ }
void cgAssertType(IRLS& env, const IRInstruction* inst) { auto& v = vmain(env); auto const& dtype = inst->dst()->type(); if (dtype == TBottom) { v << ud2(); v = v.makeBlock(); return; } auto const dst = dstLoc(env, inst, 0); auto const src = srcLoc(env, inst, 0); copyTV(v, src, dst, dtype); }
static void LJ_FASTCALL recff_getmetatable(jit_State *J, RecordFFData *rd) { TRef tr = J->base[0]; if (tr) { RecordIndex ix; ix.tab = tr; copyTV(J->L, &ix.tabv, &rd->argv[0]); if (lj_record_mm_lookup(J, &ix, MM_metatable)) J->base[0] = ix.mobj; else J->base[0] = ix.mt; } /* else: Interpreter will throw. */ }
static int debug_getupvalue(lua_State *L, int get) { int32_t n = lj_lib_checkint(L, 2); if (isluafunc(lj_lib_checkfunc(L, 1))) { const char *name = get ? lua_getupvalue(L, 1, n) : lua_setupvalue(L, 1, n); if (name) { lua_pushstring(L, name); if (!get) return 1; copyTV(L, L->top, L->top-2); L->top++; return 2; } } return 0; }
static void LJ_FASTCALL recff_setmetatable(jit_State *J, RecordFFData *rd) { TRef tr = J->base[0]; TRef mt = J->base[1]; if (tref_istab(tr) && (tref_istab(mt) || (mt && tref_isnil(mt)))) { TRef fref, mtref; RecordIndex ix; ix.tab = tr; copyTV(J->L, &ix.tabv, &rd->argv[0]); lj_record_mm_lookup(J, &ix, MM_metatable); /* Guard for no __metatable. */ fref = emitir(IRT(IR_FREF, IRT_P32), tr, IRFL_TAB_META); mtref = tref_isnil(mt) ? lj_ir_knull(J, IRT_TAB) : mt; emitir(IRT(IR_FSTORE, IRT_TAB), fref, mtref); if (!tref_isnil(mt)) emitir(IRT(IR_TBAR, IRT_TAB), tr, 0); J->base[0] = tr; J->needsnap = 1; } /* else: Interpreter will throw. */ }
/* Tailcall from C function. */ int lj_meta_tailcall(lua_State *L, cTValue *tv) { TValue *base = L->base; TValue *top = L->top; const BCIns *pc = frame_pc(base-1); /* Preserve old PC from frame. */ copyTV(L, base-1, tv); /* Replace frame with new object. */ top->u64 = 0; setframe_pc(top, pc); setframe_gc(top+1, obj2gco(L)); /* Dummy frame object. */ setframe_ftsz(top+1, (int)((char *)(top+2) - (char *)base) + FRAME_CONT); L->base = L->top = top+2; /* ** before: [old_mo|PC] [... ...] ** ^base ^top ** after: [new_mo|itype] [... ...] [NULL|PC] [dummy|delta] ** ^base/top ** tailcall: [new_mo|PC] [... ...] ** ^base ^top */ return 0; }
/* Helper for equality comparisons. __eq metamethod. */ TValue *lj_meta_equal(lua_State *L, GCobj *o1, GCobj *o2, int ne) { /* Field metatable must be at same offset for GCtab and GCudata! */ cTValue *mo = lj_meta_fast(L, tabref(o1->gch.metatable), MM_eq); if (mo) { TValue *top; uint32_t it; if (tabref(o1->gch.metatable) != tabref(o2->gch.metatable)) { cTValue *mo2 = lj_meta_fast(L, tabref(o2->gch.metatable), MM_eq); if (mo2 == NULL || !lj_obj_equal(mo, mo2)) return (TValue *)(intptr_t)ne; } top = curr_top(L); setcont(top, ne ? lj_cont_condf : lj_cont_condt); copyTV(L, top+1, mo); it = ~(uint32_t)o1->gch.gct; setgcV(L, top+2, o1, it); setgcV(L, top+3, o2, it); return top+2; /* Trigger metamethod call. */ } return (TValue *)(intptr_t)ne; }
/* Helper for TSET*. __newindex chain and metamethod. */ TValue *lj_meta_tset(lua_State *L, cTValue *o, cTValue *k) { TValue tmp; int loop; for (loop = 0; loop < LJ_MAX_IDXCHAIN; loop++) { cTValue *mo; if (LJ_LIKELY(tvistab(o))) { GCtab *t = tabV(o); cTValue *tv = lj_tab_get(L, t, k); if (LJ_LIKELY(!tvisnil(tv))) { t->nomm = 0; /* Invalidate negative metamethod cache. */ lj_gc_anybarriert(L, t); return (TValue *)tv; } else if (!(mo = lj_meta_fast(L, tabref(t->metatable), MM_newindex))) { t->nomm = 0; /* Invalidate negative metamethod cache. */ lj_gc_anybarriert(L, t); if (tv != niltv(L)) return (TValue *)tv; if (tvisnil(k)) lj_err_msg(L, LJ_ERR_NILIDX); else if (tvisint(k)) { setnumV(&tmp, (lua_Number)intV(k)); k = &tmp; } else if (tvisnum(k) && tvisnan(k)) lj_err_msg(L, LJ_ERR_NANIDX); return lj_tab_newkey(L, t, k); } } else if (tvisnil(mo = lj_meta_lookup(L, o, MM_newindex))) { lj_err_optype(L, o, LJ_ERR_OPINDEX); return NULL; /* unreachable */ } if (tvisfunc(mo)) { L->top = mmcall(L, lj_cont_nop, mo, o, k); /* L->top+2 = v filled in by caller. */ return NULL; /* Trigger metamethod call. */ } copyTV(L, &tmp, mo); o = &tmp; } lj_err_msg(L, LJ_ERR_SETLOOP); return NULL; /* unreachable */ }
static void LJ_FASTCALL recff_xpcall(jit_State *J, RecordFFData *rd) { if (J->maxslot >= 2) { TValue argv0, argv1; TRef tmp; int errcode; /* Swap function and traceback. */ tmp = J->base[0]; J->base[0] = J->base[1]; J->base[1] = tmp; copyTV(J->L, &argv0, &rd->argv[0]); copyTV(J->L, &argv1, &rd->argv[1]); copyTV(J->L, &rd->argv[0], &argv1); copyTV(J->L, &rd->argv[1], &argv0); /* Need to protect lj_record_call because it may throw. */ errcode = lj_vm_cpcall(J->L, NULL, J, recff_xpcall_cp); /* Always undo Lua stack swap to avoid confusing the interpreter. */ copyTV(J->L, &rd->argv[0], &argv0); copyTV(J->L, &rd->argv[1], &argv1); if (errcode) lj_err_throw(J->L, errcode); /* Propagate errors. */ rd->nres = -1; /* Pending call. */ } /* else: Interpreter will throw. */ }
/* Restore interpreter state from exit state with the help of a snapshot. */ const BCIns *lj_snap_restore(jit_State *J, void *exptr) { ExitState *ex = (ExitState *)exptr; SnapNo snapno = J->exitno; /* For now, snapno == exitno. */ GCtrace *T = traceref(J, J->parent); SnapShot *snap = &T->snap[snapno]; MSize n, nent = snap->nent; SnapEntry *map = &T->snapmap[snap->mapofs]; SnapEntry *flinks = &T->snapmap[snap_nextofs(T, snap)-1]; ptrdiff_t ftsz0; TValue *frame; BloomFilter rfilt = snap_renamefilter(T, snapno); const BCIns *pc = snap_pc(map[nent]); lua_State *L = J->L; /* Set interpreter PC to the next PC to get correct error messages. */ setcframe_pc(cframe_raw(L->cframe), pc+1); /* Make sure the stack is big enough for the slots from the snapshot. */ if (LJ_UNLIKELY(L->base + snap->topslot >= tvref(L->maxstack))) { L->top = curr_topL(L); lj_state_growstack(L, snap->topslot - curr_proto(L)->framesize); } /* Fill stack slots with data from the registers and spill slots. */ frame = L->base-1; ftsz0 = frame_ftsz(frame); /* Preserve link to previous frame in slot #0. */ for (n = 0; n < nent; n++) { SnapEntry sn = map[n]; if (!(sn & SNAP_NORESTORE)) { TValue *o = &frame[snap_slot(sn)]; IRRef ref = snap_ref(sn); IRIns *ir = &T->ir[ref]; if (ir->r == RID_SUNK) { MSize j; for (j = 0; j < n; j++) if (snap_ref(map[j]) == ref) { /* De-duplicate sunk allocations. */ copyTV(L, o, &frame[snap_slot(map[j])]); goto dupslot; } snap_unsink(J, T, ex, snapno, rfilt, ir, o); dupslot: continue; } snap_restoreval(J, T, ex, snapno, rfilt, ref, o); if (LJ_SOFTFP && (sn & SNAP_SOFTFPNUM) && tvisint(o)) { TValue tmp; snap_restoreval(J, T, ex, snapno, rfilt, ref+1, &tmp); o->u32.hi = tmp.u32.lo; } else if ((sn & (SNAP_CONT|SNAP_FRAME))) { lua_assert(!LJ_FR2); /* TODO_FR2: store 64 bit PCs. */ /* Overwrite tag with frame link. */ setframe_ftsz(o, snap_slot(sn) != 0 ? (int32_t)*flinks-- : ftsz0); L->base = o+1; } } } lua_assert(map + nent == flinks); /* Compute current stack top. */ switch (bc_op(*pc)) { default: if (bc_op(*pc) < BC_FUNCF) { L->top = curr_topL(L); break; } /* fallthrough */ case BC_CALLM: case BC_CALLMT: case BC_RETM: case BC_TSETM: L->top = frame + snap->nslots; break; } return pc; }
/* Helper for CAT. Coercion, iterative concat, __concat metamethod. */ TValue *lj_meta_cat(lua_State *L, TValue *top, int left) { int fromc = 0; if (left < 0) { left = -left; fromc = 1; } do { if (!(tvisstr(top) || tvisnumber(top)) || !(tvisstr(top-1) || tvisnumber(top-1))) { cTValue *mo = lj_meta_lookup(L, top-1, MM_concat); if (tvisnil(mo)) { mo = lj_meta_lookup(L, top, MM_concat); if (tvisnil(mo)) { if (tvisstr(top-1) || tvisnumber(top-1)) top++; lj_err_optype(L, top-1, LJ_ERR_OPCAT); return NULL; /* unreachable */ } } /* One of the top two elements is not a string, call __cat metamethod: ** ** before: [...][CAT stack .........................] ** top-1 top top+1 top+2 ** pick two: [...][CAT stack ...] [o1] [o2] ** setup mm: [...][CAT stack ...] [cont|?] [mo|tmtype] [o1] [o2] ** in asm: [...][CAT stack ...] [cont|PC] [mo|delta] [o1] [o2] ** ^-- func base ^-- mm base ** after mm: [...][CAT stack ...] <--push-- [result] ** next step: [...][CAT stack .............] */ copyTV(L, top+2*LJ_FR2+2, top); /* Carefully ordered stack copies! */ copyTV(L, top+2*LJ_FR2+1, top-1); copyTV(L, top+LJ_FR2, mo); setcont(top-1, lj_cont_cat); if (LJ_FR2) { setnilV(top); setnilV(top+2); top += 2; } return top+1; /* Trigger metamethod call. */ } else { /* Pick as many strings as possible from the top and concatenate them: ** ** before: [...][CAT stack ...........................] ** pick str: [...][CAT stack ...] [...... strings ......] ** concat: [...][CAT stack ...] [result] ** next step: [...][CAT stack ............] */ TValue *e, *o = top; uint64_t tlen = tvisstr(o) ? strV(o)->len : STRFMT_MAXBUF_NUM; char *p, *buf; do { o--; tlen += tvisstr(o) ? strV(o)->len : STRFMT_MAXBUF_NUM; } while (--left > 0 && (tvisstr(o-1) || tvisnumber(o-1))); if (tlen >= LJ_MAX_STR) lj_err_msg(L, LJ_ERR_STROV); p = buf = lj_buf_tmp(L, (MSize)tlen); for (e = top, top = o; o <= e; o++) { if (tvisstr(o)) { GCstr *s = strV(o); MSize len = s->len; p = lj_buf_wmem(p, strdata(s), len); } else if (tvisint(o)) { p = lj_strfmt_wint(p, intV(o)); } else { lua_assert(tvisnum(o)); p = lj_strfmt_wnum(p, o); } } setstrV(L, top, lj_str_new(L, buf, (size_t)(p-buf))); } } while (left >= 1); if (LJ_UNLIKELY(G(L)->gc.total >= G(L)->gc.threshold)) { if (!fromc) L->top = curr_topL(L); lj_gc_step(L); } return NULL; }
void lj_lib_register(lua_State *L, const char *libname, const uint8_t *p, const lua_CFunction *cf) { GCtab *env = tabref(L->env); GCfunc *ofn = NULL; int ffid = *p++; BCIns *bcff = &L2GG(L)->bcff[*p++]; GCtab *tab = lib_create_table(L, libname, *p++); ptrdiff_t tpos = L->top - L->base; /* Avoid barriers further down. */ lj_gc_anybarriert(L, tab); tab->nomm = 0; for (;;) { uint32_t tag = *p++; MSize len = tag & LIBINIT_LENMASK; tag &= LIBINIT_TAGMASK; if (tag != LIBINIT_STRING) { const char *name; MSize nuv = (MSize)(L->top - L->base - tpos); GCfunc *fn = lj_func_newC(L, nuv, env); if (nuv) { L->top = L->base + tpos; memcpy(fn->c.upvalue, L->top, sizeof(TValue)*nuv); } fn->c.ffid = (uint8_t)(ffid++); name = (const char *)p; p += len; if (tag == LIBINIT_CF) setmref(fn->c.pc, &G(L)->bc_cfunc_int); else setmref(fn->c.pc, bcff++); if (tag == LIBINIT_ASM_) fn->c.f = ofn->c.f; /* Copy handler from previous function. */ else fn->c.f = *cf++; /* Get cf or handler from C function table. */ if (len) { /* NOBARRIER: See above for common barrier. */ setfuncV(L, lj_tab_setstr(L, tab, lj_str_new(L, name, len)), fn); } ofn = fn; } else { switch (tag | len) { case LIBINIT_SET: L->top -= 2; if (tvisstr(L->top+1) && strV(L->top+1)->len == 0) env = tabV(L->top); else /* NOBARRIER: See above for common barrier. */ copyTV(L, lj_tab_set(L, tab, L->top+1), L->top); break; case LIBINIT_NUMBER: memcpy(&L->top->n, p, sizeof(double)); L->top++; p += sizeof(double); break; case LIBINIT_COPY: copyTV(L, L->top, L->top - *p++); L->top++; break; case LIBINIT_LASTCL: setfuncV(L, L->top++, ofn); break; case LIBINIT_FFID: ffid++; break; case LIBINIT_END: return; default: setstrV(L, L->top++, lj_str_new(L, (const char *)p, len)); p += len; break; } } } }
/* Helper for CAT. Coercion, iterative concat, __concat metamethod. */ TValue *lj_meta_cat(lua_State *L, TValue *top, int left) { do { int n = 1; if (!(tvisstr(top-1) || tvisnumber(top-1)) || !tostring(L, top)) { cTValue *mo = lj_meta_lookup(L, top-1, MM_concat); if (tvisnil(mo)) { mo = lj_meta_lookup(L, top, MM_concat); if (tvisnil(mo)) { if (tvisstr(top-1) || tvisnumber(top-1)) top++; lj_err_optype(L, top-1, LJ_ERR_OPCAT); return NULL; /* unreachable */ } } /* One of the top two elements is not a string, call __cat metamethod: ** ** before: [...][CAT stack .........................] ** top-1 top top+1 top+2 ** pick two: [...][CAT stack ...] [o1] [o2] ** setup mm: [...][CAT stack ...] [cont|?] [mo|tmtype] [o1] [o2] ** in asm: [...][CAT stack ...] [cont|PC] [mo|delta] [o1] [o2] ** ^-- func base ^-- mm base ** after mm: [...][CAT stack ...] <--push-- [result] ** next step: [...][CAT stack .............] */ copyTV(L, top+2, top); /* Careful with the order of stack copies! */ copyTV(L, top+1, top-1); copyTV(L, top, mo); setcont(top-1, lj_cont_cat); return top+1; /* Trigger metamethod call. */ } else if (strV(top)->len == 0) { /* Shortcut. */ (void)tostring(L, top-1); } else { /* Pick as many strings as possible from the top and concatenate them: ** ** before: [...][CAT stack ...........................] ** pick str: [...][CAT stack ...] [...... strings ......] ** concat: [...][CAT stack ...] [result] ** next step: [...][CAT stack ............] */ MSize tlen = strV(top)->len; char *buffer; int i; for (n = 1; n <= left && tostring(L, top-n); n++) { MSize len = strV(top-n)->len; if (len >= LJ_MAX_STR - tlen) lj_err_msg(L, LJ_ERR_STROV); tlen += len; } buffer = lj_str_needbuf(L, &G(L)->tmpbuf, tlen); n--; tlen = 0; for (i = n; i >= 0; i--) { MSize len = strV(top-i)->len; memcpy(buffer + tlen, strVdata(top-i), len); tlen += len; } setstrV(L, top-n, lj_str_new(L, buffer, tlen)); } left -= n; top -= n; } while (left >= 1); lj_gc_check_fixtop(L); return NULL; }