Browse Source

Back to a stackless implementation

A "with stack" implementation gains too little in performance to be
worth all the noise from C-stack overflows.

This commit is almost a sketch, to test performance. There are several
pending stuff:

- review control of C-stack overflow and error messages;
- what to do with setcstacklimit;
- review comments;
- review unroll of Lua calls.
pull/25/head
Roberto Ierusalimschy 4 years ago
parent
commit
5d8ce05b3f
  1. 51
      ldo.c
  2. 1
      ldo.h
  3. 12
      lparser.c
  4. 43
      lstate.c
  5. 7
      lstate.h
  6. 5
      ltests.h
  7. 8
      luaconf.h
  8. 25
      lvm.c
  9. 4
      testes/all.lua
  10. 2
      testes/cstack.lua
  11. 7
      testes/errors.lua

51
ldo.c

@ -139,8 +139,7 @@ l_noret luaD_throw (lua_State *L, int errcode) {
int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) {
global_State *g = G(L);
l_uint32 oldnCcalls = g->Cstacklimit - (L->nCcalls + L->nci);
l_uint32 oldnCcalls = L->nCcalls;
struct lua_longjmp lj;
lj.status = LUA_OK;
lj.previous = L->errorJmp; /* chain new error handler */
@ -149,7 +148,7 @@ int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) {
(*f)(L, ud);
);
L->errorJmp = lj.previous; /* restore old error handler */
L->nCcalls = g->Cstacklimit - oldnCcalls - L->nci;
L->nCcalls = oldnCcalls;
return lj.status;
}
@ -348,7 +347,7 @@ static StkId rethook (lua_State *L, CallInfo *ci, StkId firstres, int nres) {
/*
** Check whether 'func' has a '__call' metafield. If so, put it in the
** stack, below original 'func', so that 'luaD_call' can call it. Raise
** stack, below original 'func', so that 'luaD_precall' can call it. Raise
** an error if there is no '__call' metafield.
*/
void luaD_tryfuncTM (lua_State *L, StkId func) {
@ -454,7 +453,7 @@ void luaD_pretailcall (lua_State *L, CallInfo *ci, StkId func, int narg1) {
** When returns, all the results are on the stack, starting at the original
** function position.
*/
void luaD_call (lua_State *L, StkId func, int nresults) {
int luaD_precall (lua_State *L, StkId func, int nresults) {
lua_CFunction f;
retry:
switch (ttypetag(s2v(func))) {
@ -482,7 +481,7 @@ void luaD_call (lua_State *L, StkId func, int nresults) {
lua_lock(L);
api_checknelems(L, n);
luaD_poscall(L, ci, n);
break;
return 1;
}
case LUA_VLCL: { /* Lua function */
CallInfo *ci;
@ -501,8 +500,7 @@ void luaD_call (lua_State *L, StkId func, int nresults) {
for (; narg < nfixparams; narg++)
setnilvalue(s2v(L->top++)); /* complete missing arguments */
lua_assert(ci->top <= L->stack_last);
luaV_execute(L, ci); /* run the function */
break;
return 0;
}
default: { /* not a function */
checkstackGCp(L, 1, func); /* space for metamethod */
@ -513,17 +511,32 @@ void luaD_call (lua_State *L, StkId func, int nresults) {
}
static void stackerror (lua_State *L) {
if (getCcalls(L) == LUAI_MAXCCALLS)
luaG_runerror(L, "C stack overflow");
else if (getCcalls(L) >= (LUAI_MAXCCALLS + (LUAI_MAXCCALLS>>3)))
luaD_throw(L, LUA_ERRERR); /* error while handing stack error */
}
void luaD_call (lua_State *L, StkId func, int nResults) {
L->nCcalls++;
if (getCcalls(L) >= LUAI_MAXCCALLS)
stackerror(L);
if (!luaD_precall(L, func, nResults)) /* is a Lua function? */
luaV_execute(L, L->ci); /* call it */
L->nCcalls--;
}
/*
** Similar to 'luaD_call', but does not allow yields during the call.
*/
void luaD_callnoyield (lua_State *L, StkId func, int nResults) {
incXCcalls(L);
if (getCcalls(L) <= CSTACKERR) { /* possible C stack overflow? */
luaE_exitCcall(L); /* to compensate decrement in next call */
luaE_enterCcall(L); /* check properly */
}
incnny(L);
luaD_call(L, func, nResults);
decXCcalls(L);
decnny(L);
}
@ -638,7 +651,8 @@ static void resume (lua_State *L, void *ud) {
StkId firstArg = L->top - n; /* first argument */
CallInfo *ci = L->ci;
if (L->status == LUA_OK) { /* starting a coroutine? */
luaD_call(L, firstArg - 1, LUA_MULTRET);
if (!luaD_precall(L, firstArg - 1, LUA_MULTRET)) /* Lua function? */
luaV_execute(L, L->ci); /* call it */
}
else { /* resuming from previous yield */
lua_assert(L->status == LUA_YIELD);
@ -670,11 +684,8 @@ LUA_API int lua_resume (lua_State *L, lua_State *from, int nargs,
}
else if (L->status != LUA_YIELD) /* ended with errors? */
return resume_error(L, "cannot resume dead coroutine", nargs);
if (from == NULL)
L->nCcalls = CSTACKTHREAD;
else /* correct 'nCcalls' for this thread */
L->nCcalls = getCcalls(from) - L->nci - CSTACKCF;
if (L->nCcalls <= CSTACKERR)
L->nCcalls = (from) ? getCcalls(from) + 1 : 1;
if (getCcalls(L) >= LUAI_MAXCCALLS)
return resume_error(L, "C stack overflow", nargs);
luai_userstateresume(L, nargs);
api_checknelems(L, (L->status == LUA_OK) ? nargs + 1 : nargs);

1
ldo.h

@ -59,6 +59,7 @@ LUAI_FUNC void luaD_hook (lua_State *L, int event, int line,
int fTransfer, int nTransfer);
LUAI_FUNC void luaD_hookcall (lua_State *L, CallInfo *ci);
LUAI_FUNC void luaD_pretailcall (lua_State *L, CallInfo *ci, StkId func, int n);
LUAI_FUNC int luaD_precall (lua_State *L, StkId func, int nResults);
LUAI_FUNC void luaD_call (lua_State *L, StkId func, int nResults);
LUAI_FUNC void luaD_callnoyield (lua_State *L, StkId func, int nResults);
LUAI_FUNC void luaD_tryfuncTM (lua_State *L, StkId func);

12
lparser.c

@ -489,12 +489,14 @@ static void adjust_assign (LexState *ls, int nvars, int nexps, expdesc *e) {
}
/*
** Macros to limit the maximum recursion depth while parsing
*/
#define enterlevel(ls) luaE_enterCcall((ls)->L)
static void enterlevel (LexState *ls) {
lua_State *L = ls->L;
L->nCcalls++;
checklimit(ls->fs, getCcalls(L), LUAI_MAXCCALLS, "C levels");
}
#define leavelevel(ls) luaE_exitCcall((ls)->L)
#define leavelevel(ls) ((ls)->L->nCcalls--)
/*

43
lstate.c

@ -119,44 +119,9 @@ LUA_API int lua_setcstacklimit (lua_State *L, unsigned int limit) {
}
/*
** Decrement count of "C calls" and check for overflows. In case of
** a stack overflow, check appropriate error ("regular" overflow or
** overflow while handling stack overflow). If 'nCcalls' is smaller
** than CSTACKERR but larger than CSTACKMARK, it means it has just
** entered the "overflow zone", so the function raises an overflow
** error. If 'nCcalls' is smaller than CSTACKMARK (which means it is
** already handling an overflow) but larger than CSTACKERRMARK, does
** not report an error (to allow message handling to work). Otherwise,
** report a stack overflow while handling a stack overflow (probably
** caused by a repeating error in the message handling function).
*/
void luaE_enterCcall (lua_State *L) {
int ncalls = getCcalls(L);
L->nCcalls--;
if (ncalls <= CSTACKERR) { /* possible overflow? */
luaE_freeCI(L); /* release unused CIs */
ncalls = getCcalls(L); /* update call count */
if (ncalls <= CSTACKERR) { /* still overflow? */
if (ncalls <= CSTACKERRMARK) /* below error-handling zone? */
luaD_throw(L, LUA_ERRERR); /* error while handling stack error */
else if (ncalls >= CSTACKMARK) {
/* not in error-handling zone; raise the error now */
L->nCcalls = (CSTACKMARK - 1); /* enter error-handling zone */
luaG_runerror(L, "C stack overflow");
}
/* else stack is in the error-handling zone;
allow message handler to work */
}
}
}
CallInfo *luaE_extendCI (lua_State *L) {
CallInfo *ci;
lua_assert(L->ci->next == NULL);
luaE_enterCcall(L);
ci = luaM_new(L, CallInfo);
lua_assert(L->ci->next == NULL);
L->ci->next = ci;
@ -175,13 +140,11 @@ void luaE_freeCI (lua_State *L) {
CallInfo *ci = L->ci;
CallInfo *next = ci->next;
ci->next = NULL;
L->nCcalls += L->nci; /* add removed elements back to 'nCcalls' */
while ((ci = next) != NULL) {
next = ci->next;
luaM_free(L, ci);
L->nci--;
}
L->nCcalls -= L->nci; /* adjust result */
}
@ -194,7 +157,6 @@ void luaE_shrinkCI (lua_State *L) {
CallInfo *next;
if (ci == NULL)
return; /* no extra elements */
L->nCcalls += L->nci; /* add removed elements back to 'nCcalls' */
while ((next = ci->next) != NULL) { /* two extra elements? */
CallInfo *next2 = next->next; /* next's next */
ci->next = next2; /* remove next from the list */
@ -207,7 +169,6 @@ void luaE_shrinkCI (lua_State *L) {
ci = next2; /* continue */
}
}
L->nCcalls -= L->nci; /* adjust result */
}
@ -335,7 +296,7 @@ LUA_API lua_State *lua_newthread (lua_State *L) {
setthvalue2s(L, L->top, L1);
api_incr_top(L);
preinit_thread(L1, g);
L1->nCcalls = getCcalls(L);
L1->nCcalls = 0;
L1->hookmask = L->hookmask;
L1->basehookcount = L->basehookcount;
L1->hook = L->hook;
@ -396,7 +357,7 @@ LUA_API lua_State *lua_newstate (lua_Alloc f, void *ud) {
preinit_thread(L, g);
g->allgc = obj2gco(L); /* by now, only object is the main thread */
L->next = NULL;
g->Cstacklimit = L->nCcalls = LUAI_MAXCSTACK + CSTACKERR;
g->Cstacklimit = L->nCcalls = 0;
incnny(L); /* main thread is always non yieldable */
g->frealloc = f;
g->ud = ud;

7
lstate.h

@ -144,12 +144,6 @@
/* Decrement the number of non-yieldable calls */
#define decnny(L) ((L)->nCcalls -= 0x10000)
/* Increment the number of non-yieldable calls and decrement nCcalls */
#define incXCcalls(L) ((L)->nCcalls += 0x10000 - CSTACKCF)
/* Decrement the number of non-yieldable calls and increment nCcalls */
#define decXCcalls(L) ((L)->nCcalls -= 0x10000 - CSTACKCF)
@ -389,7 +383,6 @@ LUAI_FUNC void luaE_freethread (lua_State *L, lua_State *L1);
LUAI_FUNC CallInfo *luaE_extendCI (lua_State *L);
LUAI_FUNC void luaE_freeCI (lua_State *L);
LUAI_FUNC void luaE_shrinkCI (lua_State *L);
LUAI_FUNC void luaE_enterCcall (lua_State *L);
LUAI_FUNC void luaE_warning (lua_State *L, const char *msg, int tocont);
LUAI_FUNC void luaE_warnerror (lua_State *L, const char *where);

5
ltests.h

@ -23,11 +23,6 @@
#define LUAI_ASSERT
/* compiled with -O0, Lua uses a lot of C stack space... */
#undef LUAI_MAXCSTACK
#define LUAI_MAXCSTACK 400
/* to avoid warnings, and to make sure value is really unused */
#define UNUSED(x) (x=0, (void)(x))

8
luaconf.h

@ -36,8 +36,8 @@
** =====================================================================
*/
/*
@@ LUAI_MAXCSTACK defines the maximum depth for nested calls and
/* >>> move back to llimits.h
@@ LUAI_MAXCCALLS defines the maximum depth for nested calls and
** also limits the maximum depth of other recursive algorithms in
** the implementation, such as syntactic analysis. A value too
** large may allow the interpreter to crash (C-stack overflow).
@ -46,8 +46,8 @@
** The test file 'cstack.lua' may help finding a good limit.
** (It will crash with a limit too high.)
*/
#if !defined(LUAI_MAXCSTACK)
#define LUAI_MAXCSTACK 2000
#if !defined(LUAI_MAXCCALLS)
#define LUAI_MAXCCALLS 200
#endif

25
lvm.c

@ -229,7 +229,7 @@ static int forprep (lua_State *L, StkId ra) {
count /= l_castS2U(-(step + 1)) + 1u;
}
/* store the counter in place of the limit (which won't be
needed anymore */
needed anymore) */
setivalue(plimit, l_castU2S(count));
}
}
@ -1124,6 +1124,7 @@ void luaV_finishOp (lua_State *L) {
void luaV_execute (lua_State *L, CallInfo *ci) {
const CallInfo *origci = ci;
LClosure *cl;
TValue *k;
StkId base;
@ -1611,7 +1612,13 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
if (b != 0) /* fixed number of arguments? */
L->top = ra + b; /* top signals number of arguments */
/* else previous instruction set top */
ProtectNT(luaD_call(L, ra, nresults));
savepc(L); /* in case of errors */
if (luaD_precall(L, ra, nresults))
updatetrap(ci); /* C call; nothing else to be done */
else { /* Lua call: run function in this same invocation */
ci = L->ci;
goto tailcall;
}
vmbreak;
}
vmcase(OP_TAILCALL) {
@ -1637,12 +1644,12 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
checkstackGCp(L, 1, ra);
}
if (!ttisLclosure(s2v(ra))) { /* C function? */
luaD_call(L, ra, LUA_MULTRET); /* call it */
luaD_precall(L, ra, LUA_MULTRET); /* call it */
updatetrap(ci);
updatestack(ci); /* stack may have been relocated */
ci->func -= delta;
luaD_poscall(L, ci, cast_int(L->top - ra));
return;
goto ret;
}
ci->func -= delta;
luaD_pretailcall(L, ci, ra, b); /* prepare call frame */
@ -1665,7 +1672,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
ci->func -= ci->u.l.nextraargs + nparams1;
L->top = ra + n; /* set call for 'luaD_poscall' */
luaD_poscall(L, ci, n);
return;
goto ret;
}
vmcase(OP_RETURN0) {
if (L->hookmask) {
@ -1679,7 +1686,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
while (nres-- > 0)
setnilvalue(s2v(L->top++)); /* all results are nil */
}
return;
goto ret;
}
vmcase(OP_RETURN1) {
if (L->hookmask) {
@ -1698,7 +1705,13 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
setnilvalue(s2v(L->top++));
}
}
ret:
if (ci == origci)
return;
else {
ci = ci->previous;
goto tailcall;
}
}
vmcase(OP_FORLOOP) {
if (ttisinteger(s2v(ra + 2))) { /* integer loop? */

4
testes/all.lua

@ -127,8 +127,8 @@ else
end
Cstacklevel = function ()
local _, _, ncalls, nci = T.stacklevel()
return ncalls + nci -- number of free slots in the C stack
local _, _, ncalls = T.stacklevel()
return ncalls -- number of C calls
end
end

2
testes/cstack.lua

@ -1,6 +1,8 @@
-- $Id: testes/cstack.lua $
-- See Copyright Notice in file all.lua
do return end
local debug = require "debug"
print"testing C-stack overflow detection"

7
testes/errors.lua

@ -530,10 +530,9 @@ local function testrep (init, rep, close, repc, finalresult)
if (finalresult) then
assert(res() == finalresult)
end
s = init .. string.rep(rep, 10000)
local res, msg = load(s) -- 10000 levels not ok
assert(not res and (string.find(msg, "too many registers") or
string.find(msg, "stack overflow")))
s = init .. string.rep(rep, 500)
local res, msg = load(s) -- 500 levels not ok
assert(not res and string.find(msg, "too many"))
end
testrep("local a; a", ",a", "= 1", ",1") -- multiple assignment

Loading…
Cancel
Save