From 3cd9b56ae6002b4ef28d2467abd119606ae625d3 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Wed, 12 Jun 2019 10:31:38 -0300 Subject: [PATCH] Revamp around 'L->nCcalls' count The field 'L->nCcalls' now counts downwards, so that the C-stack limits do not depend on the stack size. --- ldo.c | 13 ++++++------- lstate.c | 53 +++++++++++++++++++++++++------------------------- lstate.h | 51 ++++++++++++++++++++++++++++++++---------------- testes/all.lua | 14 +++++++++++++ 4 files changed, 80 insertions(+), 51 deletions(-) diff --git a/ldo.c b/ldo.c index e7e76a65..0ad3120b 100644 --- a/ldo.c +++ b/ldo.c @@ -139,9 +139,8 @@ l_noret luaD_throw (lua_State *L, int errcode) { int luaD_rawrunprotected (lua_State *L, Pfunc f, void *ud) { - l_uint32 oldnCcalls = L->nCcalls - L->nci; + l_uint32 oldnCcalls = L->nCcalls + L->nci; struct lua_longjmp lj; - lua_assert(L->nCcalls >= L->nci); lj.status = LUA_OK; lj.previous = L->errorJmp; /* chain new error handler */ L->errorJmp = &lj; @@ -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 = oldnCcalls + L->nci; + L->nCcalls = oldnCcalls - L->nci; return lj.status; } @@ -521,7 +520,7 @@ void luaD_call (lua_State *L, StkId func, int nresults) { */ void luaD_callnoyield (lua_State *L, StkId func, int nResults) { incXCcalls(L); - if (getCcalls(L) >= LUAI_MAXCSTACK) /* possible stack overflow? */ + if (getCcalls(L) <= CSTACKERR) /* possible stack overflow? */ luaE_freeCI(L); luaD_call(L, func, nResults); decXCcalls(L); @@ -672,10 +671,10 @@ 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 = 1; + L->nCcalls = LUAI_MAXCSTACK; else /* correct 'nCcalls' for this thread */ - L->nCcalls = getCcalls(from) - from->nci + L->nci + CSTACKCF; - if (L->nCcalls >= LUAI_MAXCSTACK) + L->nCcalls = getCcalls(from) + from->nci - L->nci - CSTACKCF; + if (L->nCcalls <= CSTACKERR) return resume_error(L, "C stack overflow", nargs); luai_userstateresume(L, nargs); api_checknelems(L, (L->status == LUA_OK) ? nargs + 1 : nargs); diff --git a/lstate.c b/lstate.c index 387cd362..296cec2a 100644 --- a/lstate.c +++ b/lstate.c @@ -97,35 +97,34 @@ void luaE_setdebt (global_State *g, l_mem debt) { /* -** Increment count of "C calls" and check for overflows. In case of +** 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 larger than LUAI_MAXCSTACK but smaller than -** LUAI_MAXCSTACK + CSTACKCF (plus 2 to avoid by-one errors), it means -** it has just entered the "overflow zone", so the function raises an -** overflow error. -** If 'nCcalls' is larger than LUAI_MAXCSTACK + CSTACKCF + 2 -** (which means it is already handling an overflow) but smaller than -** 9/8 of LUAI_MAXCSTACK, 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). +** 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 >= LUAI_MAXCSTACK) { /* possible overflow? */ + L->nCcalls--; + if (ncalls <= CSTACKERR) { /* possible overflow? */ luaE_freeCI(L); /* release unused CIs */ ncalls = getCcalls(L); /* update call count */ - if (ncalls >= LUAI_MAXCSTACK) { /* still overflow? */ - if (ncalls <= LUAI_MAXCSTACK + CSTACKCF + 2) { - /* no error before increments; raise the error now */ - L->nCcalls += (CSTACKCF + 4); /* avoid raising it again */ - luaG_runerror(L, "C stack overflow"); - } - else if (ncalls >= (LUAI_MAXCSTACK + (LUAI_MAXCSTACK >> 3))) + 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 overflow1"); + } + /* else stack is in the error-handling zone; + allow message handler to work */ } } } @@ -153,13 +152,13 @@ void luaE_freeCI (lua_State *L) { CallInfo *ci = L->ci; CallInfo *next = ci->next; ci->next = NULL; - L->nCcalls -= L->nci; /* subtract removed elements from 'nCcalls' */ + 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 */ + L->nCcalls -= L->nci; /* adjust result */ } @@ -169,7 +168,7 @@ void luaE_freeCI (lua_State *L) { void luaE_shrinkCI (lua_State *L) { CallInfo *ci = L->ci; CallInfo *next2; /* next's next */ - L->nCcalls -= L->nci; /* subtract removed elements from 'nCcalls' */ + L->nCcalls += L->nci; /* add removed elements back to 'nCcalls' */ /* while there are two nexts */ while (ci->next != NULL && (next2 = ci->next->next) != NULL) { luaM_free(L, ci->next); /* free next */ @@ -178,7 +177,7 @@ void luaE_shrinkCI (lua_State *L) { next2->previous = ci; ci = next2; /* keep next's next */ } - L->nCcalls += L->nci; /* adjust result */ + L->nCcalls -= L->nci; /* adjust result */ } @@ -264,7 +263,7 @@ static void preinit_thread (lua_State *L, global_State *g) { L->stacksize = 0; L->twups = L; /* thread has no upvalues */ L->errorJmp = NULL; - L->nCcalls = 0; + L->nCcalls = LUAI_MAXCSTACK + CSTACKERR; L->hook = NULL; L->hookmask = 0; L->basehookcount = 0; diff --git a/lstate.h b/lstate.h index 3bd52973..858da5be 100644 --- a/lstate.h +++ b/lstate.h @@ -64,28 +64,45 @@ /* ** About 'nCcalls': each thread in Lua (a lua_State) keeps a count of -** how many "C calls" it can do in the C stack, to avoid C-stack overflow. -** This count is very rough approximation; it considers only recursive -** functions inside the interpreter, as non-recursive calls can be -** considered using a fixed (although unknown) amount of stack space. +** how many "C calls" it still can do in the C stack, to avoid C-stack +** overflow. This count is very rough approximation; it considers only +** recursive functions inside the interpreter, as non-recursive calls +** can be considered using a fixed (although unknown) amount of stack +** space. ** -** The count itself has two parts: the lower part is the count itself; -** the higher part counts the number of non-yieldable calls in the stack. +** The count has two parts: the lower part is the count itself; the +** higher part counts the number of non-yieldable calls in the stack. +** (They are together so that we can change both with one instruction.) ** ** Because calls to external C functions can use of unkown amount ** of space (e.g., functions using an auxiliary buffer), calls -** to these functions add more than one to the count. +** to these functions add more than one to the count (see CSTACKCF). ** -** The proper count also includes the number of CallInfo structures -** allocated by Lua, as a kind of "potential" calls. So, when Lua -** calls a function (and "consumes" one CallInfo), it needs neither to -** increment nor to check 'nCcalls', as its use of C stack is already -** accounted for. +** The proper count excludes the number of CallInfo structures allocated +** by Lua, as a kind of "potential" calls. So, when Lua calls a function +** (and "consumes" one CallInfo), it needs neither to decrement nor to +** check 'nCcalls', as its use of C stack is already accounted for. */ /* number of "C stack slots" used by an external C function */ #define CSTACKCF 10 + +/* +** The C-stack size is sliced in the following zones: +** - larger than CSTACKERR: normal stack; +** - [CSTACKMARK, CSTACKERR]: buffer zone to signal a stack overflow; +** - [CSTACKCF, CSTACKERRMARK]: error-handling zone; +** - below CSTACKERRMARK: buffer zone to signal overflow during overflow; +** (Because the counter can be decremented CSTACKCF at once, we need +** the so called "buffer zones", with at least that size, to properly +** detect a change from one zone to the next.) +*/ +#define CSTACKERR (8 * CSTACKCF) +#define CSTACKMARK (CSTACKERR - (CSTACKCF + 2)) +#define CSTACKERRMARK (CSTACKCF + 2) + + /* true if this thread does not have non-yieldable calls in the stack */ #define yieldable(L) (((L)->nCcalls & 0xffff0000) == 0) @@ -99,11 +116,11 @@ /* Decrement the number of non-yieldable calls */ #define decnny(L) ((L)->nCcalls -= 0x10000) -/* Increment the number of non-yieldable calls and nCcalls */ -#define incXCcalls(L) ((L)->nCcalls += 0x10000 + CSTACKCF) +/* 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 nCcalls */ -#define decXCcalls(L) ((L)->nCcalls -= 0x10000 + CSTACKCF) +/* Decrement the number of non-yieldable calls and increment nCcalls */ +#define decXCcalls(L) ((L)->nCcalls -= 0x10000 - CSTACKCF) @@ -336,7 +353,7 @@ LUAI_FUNC void luaE_enterCcall (lua_State *L); LUAI_FUNC void luaE_warning (lua_State *L, const char *msg, int tocont); -#define luaE_exitCcall(L) ((L)->nCcalls--) +#define luaE_exitCcall(L) ((L)->nCcalls++) #endif diff --git a/testes/all.lua b/testes/all.lua index 2e6fe038..72121e8d 100644 --- a/testes/all.lua +++ b/testes/all.lua @@ -95,6 +95,8 @@ local function F (m) end end +local Cstacklevel + local showmem if not T then local max = 0 @@ -104,6 +106,7 @@ if not T then print(format(" ---- total memory: %s, max memory: %s ----\n", F(m), F(max))) end + Cstacklevel = function () return 0 end -- no info about stack level else showmem = function () T.checkmemory() @@ -117,9 +120,16 @@ else T.totalmem"string", T.totalmem"table", T.totalmem"function", T.totalmem"userdata", T.totalmem"thread")) end + + Cstacklevel = function () + local _, _, ncalls, nci = T.stacklevel() + return ncalls + nci -- number of free slots in the C stack + end end +local Cstack = Cstacklevel() + -- -- redefine dofile to run files through dump/undump -- @@ -211,6 +221,10 @@ debug.sethook(function (a) assert(type(a) == 'string') end, "cr") -- to survive outside block _G.showmem = showmem + +assert(Cstack == Cstacklevel(), + "should be at the same C-stack level it was when started the tests") + end --) local _G, showmem, print, format, clock, time, difftime,