aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoberto Ierusalimschy <roberto@inf.puc-rio.br>2021-02-09 14:00:05 -0300
committerRoberto Ierusalimschy <roberto@inf.puc-rio.br>2021-02-09 14:00:05 -0300
commit4e47f81188d37e29027158b76271d02a781242e2 (patch)
treec360912d1901acf8371390cc1f716278e5d91bb4
parentc63e5d212bc5dec1b1c749e3f07b42cd83081826 (diff)
downloadlua-4e47f81188d37e29027158b76271d02a781242e2.tar.gz
lua-4e47f81188d37e29027158b76271d02a781242e2.tar.bz2
lua-4e47f81188d37e29027158b76271d02a781242e2.zip
New implementation for to-be-closed variables
To-be-closed variables are linked in their own list, embedded into the stack elements. (Due to alignment, this information does not change the size of the stack elements in most architectures.) This new list does not produce garbage and avoids memory errors when creating tbc variables.
-rw-r--r--lapi.c9
-rw-r--r--ldo.c2
-rw-r--r--lfunc.c66
-rw-r--r--lfunc.h9
-rw-r--r--lobject.h10
-rw-r--r--lstate.c15
-rw-r--r--lstate.h2
-rw-r--r--ltests.c1
-rw-r--r--lvm.c6
-rw-r--r--testes/locals.lua49
10 files changed, 103 insertions, 66 deletions
diff --git a/lapi.c b/lapi.c
index 41e6b86d..a9cf2fdb 100644
--- a/lapi.c
+++ b/lapi.c
@@ -192,9 +192,8 @@ LUA_API void lua_settop (lua_State *L, int idx) {
192 if (diff < 0 && hastocloseCfunc(ci->nresults)) 192 if (diff < 0 && hastocloseCfunc(ci->nresults))
193 luaF_close(L, L->top + diff, CLOSEKTOP, 0); 193 luaF_close(L, L->top + diff, CLOSEKTOP, 0);
194#endif 194#endif
195 api_check(L, L->tbclist < L->top + diff, "cannot pop an unclosed slot");
195 L->top += diff; 196 L->top += diff;
196 api_check(L, L->openupval == NULL || uplevel(L->openupval) < L->top,
197 "cannot pop an unclosed slot");
198 lua_unlock(L); 197 lua_unlock(L);
199} 198}
200 199
@@ -203,8 +202,7 @@ LUA_API void lua_closeslot (lua_State *L, int idx) {
203 StkId level; 202 StkId level;
204 lua_lock(L); 203 lua_lock(L);
205 level = index2stack(L, idx); 204 level = index2stack(L, idx);
206 api_check(L, hastocloseCfunc(L->ci->nresults) && L->openupval != NULL && 205 api_check(L, hastocloseCfunc(L->ci->nresults) && L->tbclist == level,
207 uplevel(L->openupval) == level,
208 "no variable to close at given level"); 206 "no variable to close at given level");
209 luaF_close(L, level, CLOSEKTOP, 0); 207 luaF_close(L, level, CLOSEKTOP, 0);
210 level = index2stack(L, idx); /* stack may be moved */ 208 level = index2stack(L, idx); /* stack may be moved */
@@ -1266,8 +1264,7 @@ LUA_API void lua_toclose (lua_State *L, int idx) {
1266 lua_lock(L); 1264 lua_lock(L);
1267 o = index2stack(L, idx); 1265 o = index2stack(L, idx);
1268 nresults = L->ci->nresults; 1266 nresults = L->ci->nresults;
1269 api_check(L, L->openupval == NULL || uplevel(L->openupval) <= o, 1267 api_check(L, L->tbclist < o, "given index below or equal a marked one");
1270 "marked index below or equal new one");
1271 luaF_newtbcupval(L, o); /* create new to-be-closed upvalue */ 1268 luaF_newtbcupval(L, o); /* create new to-be-closed upvalue */
1272 if (!hastocloseCfunc(nresults)) /* function not marked yet? */ 1269 if (!hastocloseCfunc(nresults)) /* function not marked yet? */
1273 L->ci->nresults = codeNresults(nresults); /* mark it */ 1270 L->ci->nresults = codeNresults(nresults); /* mark it */
diff --git a/ldo.c b/ldo.c
index 65f0a7b9..bc7212c6 100644
--- a/ldo.c
+++ b/ldo.c
@@ -163,7 +163,7 @@ static void correctstack (lua_State *L, StkId oldstack, StkId newstack) {
163 if (oldstack == newstack) 163 if (oldstack == newstack)
164 return; /* stack address did not change */ 164 return; /* stack address did not change */
165 L->top = (L->top - oldstack) + newstack; 165 L->top = (L->top - oldstack) + newstack;
166 lua_assert(L->ptbc == NULL); 166 L->tbclist = (L->tbclist - oldstack) + newstack;
167 for (up = L->openupval; up != NULL; up = up->u.open.next) 167 for (up = L->openupval; up != NULL; up = up->u.open.next)
168 up->v = s2v((uplevel(up) - oldstack) + newstack); 168 up->v = s2v((uplevel(up) - oldstack) + newstack);
169 for (ci = L->ci; ci != NULL; ci = ci->previous) { 169 for (ci = L->ci; ci != NULL; ci = ci->previous) {
diff --git a/lfunc.c b/lfunc.c
index 105590fc..b4c04bd0 100644
--- a/lfunc.c
+++ b/lfunc.c
@@ -120,11 +120,11 @@ static void callclosemethod (lua_State *L, TValue *obj, TValue *err, int yy) {
120 120
121 121
122/* 122/*
123** Check whether 'obj' has a close metamethod and raise an error 123** Check whether object at given level has a close metamethod and raise
124** if not. 124** an error if not.
125*/ 125*/
126static void checkclosemth (lua_State *L, StkId level, const TValue *obj) { 126static void checkclosemth (lua_State *L, StkId level) {
127 const TValue *tm = luaT_gettmbyobj(L, obj, TM_CLOSE); 127 const TValue *tm = luaT_gettmbyobj(L, s2v(level), TM_CLOSE);
128 if (ttisnil(tm)) { /* no metamethod? */ 128 if (ttisnil(tm)) { /* no metamethod? */
129 int idx = cast_int(level - L->ci->func); /* variable index */ 129 int idx = cast_int(level - L->ci->func); /* variable index */
130 const char *vname = luaG_findlocal(L, L->ci, idx, NULL); 130 const char *vname = luaG_findlocal(L, L->ci, idx, NULL);
@@ -155,20 +155,21 @@ static void prepcallclosemth (lua_State *L, StkId level, int status, int yy) {
155 155
156 156
157/* 157/*
158** Create a to-be-closed upvalue. If there is a memory allocation error, 158** Insert a variable in the list of to-be-closed variables.
159** 'ptbc' keeps the object so it can be closed as soon as possible.
160** (Since memory errors have no handler, that will happen before any
161** stack reallocation.)
162*/ 159*/
163void luaF_newtbcupval (lua_State *L, StkId level) { 160void luaF_newtbcupval (lua_State *L, StkId level) {
164 TValue *obj = s2v(level); 161 lua_assert(level > L->tbclist);
165 lua_assert(L->openupval == NULL || uplevel(L->openupval) < level); 162 if (l_isfalse(s2v(level)))
166 if (!l_isfalse(obj)) { /* false doesn't need to be closed */ 163 return; /* false doesn't need to be closed */
167 checkclosemth(L, level, obj); 164 checkclosemth(L, level); /* value must have a close method */
168 L->ptbc = level; /* in case of allocation error */ 165 while (level - L->tbclist > USHRT_MAX) { /* is delta too large? */
169 newupval(L, 1, level, &L->openupval); 166 L->tbclist += USHRT_MAX; /* create a dummy node at maximum delta */
170 L->ptbc = NULL; /* no errors */ 167 L->tbclist->tbclist.delta = USHRT_MAX;
168 L->tbclist->tbclist.isdummy = 1;
171 } 169 }
170 level->tbclist.delta = level - L->tbclist;
171 level->tbclist.isdummy = 0;
172 L->tbclist = level;
172} 173}
173 174
174 175
@@ -181,23 +182,11 @@ void luaF_unlinkupval (UpVal *uv) {
181 182
182 183
183/* 184/*
184** Close all upvalues up to the given stack level. A 'status' equal 185** Close all upvalues up to the given stack level.
185** to NOCLOSINGMETH closes upvalues without running any __close
186** metamethods. If there is a pending to-be-closed value, close
187** it before anything else.
188*/ 186*/
189void luaF_close (lua_State *L, StkId level, int status, int yy) { 187void luaF_closeupval (lua_State *L, StkId level) {
190 UpVal *uv; 188 UpVal *uv;
191 StkId upl; /* stack index pointed by 'uv' */ 189 StkId upl; /* stack index pointed by 'uv' */
192 if (unlikely(status == LUA_ERRMEM && L->ptbc != NULL)) {
193 ptrdiff_t levelrel = savestack(L, level);
194 upl = L->ptbc;
195 L->ptbc = NULL; /* remove from "list" before closing */
196 prepcallclosemth(L, upl, status, yy);
197 level = restorestack(L, levelrel);
198 }
199 else
200 lua_assert(L->ptbc == NULL); /* must be empty for other status */
201 while ((uv = L->openupval) != NULL && (upl = uplevel(uv)) >= level) { 190 while ((uv = L->openupval) != NULL && (upl = uplevel(uv)) >= level) {
202 TValue *slot = &uv->u.value; /* new position for value */ 191 TValue *slot = &uv->u.value; /* new position for value */
203 lua_assert(uplevel(uv) < L->top); 192 lua_assert(uplevel(uv) < L->top);
@@ -208,9 +197,22 @@ void luaF_close (lua_State *L, StkId level, int status, int yy) {
208 nw2black(uv); /* closed upvalues cannot be gray */ 197 nw2black(uv); /* closed upvalues cannot be gray */
209 luaC_barrier(L, uv, slot); 198 luaC_barrier(L, uv, slot);
210 } 199 }
211 if (uv->tbc && status != NOCLOSINGMETH) { 200 }
212 ptrdiff_t levelrel = savestack(L, level); 201}
213 prepcallclosemth(L, upl, status, yy); /* may change the stack */ 202
203
204/*
205** Close all upvalues and to-be-closed variables up to the given stack
206** level.
207*/
208void luaF_close (lua_State *L, StkId level, int status, int yy) {
209 ptrdiff_t levelrel = savestack(L, level);
210 luaF_closeupval(L, level); /* first, close the upvalues */
211 while (L->tbclist >= level) { /* traverse tbc's down to that level */
212 StkId tbc = L->tbclist; /* get variable index */
213 L->tbclist -= tbc->tbclist.delta; /* remove it from list */
214 if (!tbc->tbclist.isdummy) { /* not a dummy entry? */
215 prepcallclosemth(L, tbc, status, yy); /* close variable */
214 level = restorestack(L, levelrel); 216 level = restorestack(L, levelrel);
215 } 217 }
216 } 218 }
diff --git a/lfunc.h b/lfunc.h
index 2e6df535..dc1cebcc 100644
--- a/lfunc.h
+++ b/lfunc.h
@@ -42,15 +42,9 @@
42#define MAXMISS 10 42#define MAXMISS 10
43 43
44 44
45/*
46** Special "status" for 'luaF_close'
47*/
48
49/* close upvalues without running their closing methods */
50#define NOCLOSINGMETH (-1)
51 45
52/* special status to close upvalues preserving the top of the stack */ 46/* special status to close upvalues preserving the top of the stack */
53#define CLOSEKTOP (-2) 47#define CLOSEKTOP (-1)
54 48
55 49
56LUAI_FUNC Proto *luaF_newproto (lua_State *L); 50LUAI_FUNC Proto *luaF_newproto (lua_State *L);
@@ -59,6 +53,7 @@ LUAI_FUNC LClosure *luaF_newLclosure (lua_State *L, int nupvals);
59LUAI_FUNC void luaF_initupvals (lua_State *L, LClosure *cl); 53LUAI_FUNC void luaF_initupvals (lua_State *L, LClosure *cl);
60LUAI_FUNC UpVal *luaF_findupval (lua_State *L, StkId level); 54LUAI_FUNC UpVal *luaF_findupval (lua_State *L, StkId level);
61LUAI_FUNC void luaF_newtbcupval (lua_State *L, StkId level); 55LUAI_FUNC void luaF_newtbcupval (lua_State *L, StkId level);
56LUAI_FUNC void luaF_closeupval (lua_State *L, StkId level);
62LUAI_FUNC void luaF_close (lua_State *L, StkId level, int status, int yy); 57LUAI_FUNC void luaF_close (lua_State *L, StkId level, int status, int yy);
63LUAI_FUNC void luaF_unlinkupval (UpVal *uv); 58LUAI_FUNC void luaF_unlinkupval (UpVal *uv);
64LUAI_FUNC void luaF_freeproto (lua_State *L, Proto *f); 59LUAI_FUNC void luaF_freeproto (lua_State *L, Proto *f);
diff --git a/lobject.h b/lobject.h
index 470b17d5..1a7a7372 100644
--- a/lobject.h
+++ b/lobject.h
@@ -136,10 +136,18 @@ typedef struct TValue {
136 136
137 137
138/* 138/*
139** Entries in the Lua stack 139** Entries in a Lua stack. Field 'tbclist' forms a list of all
140** to-be-closed variables active in this stack. Dummy entries are
141** used when the distance between two tbc variables does not fit
142** in an unsigned short.
140*/ 143*/
141typedef union StackValue { 144typedef union StackValue {
142 TValue val; 145 TValue val;
146 struct {
147 TValuefields;
148 lu_byte isdummy;
149 unsigned short delta;
150 } tbclist;
143} StackValue; 151} StackValue;
144 152
145 153
diff --git a/lstate.c b/lstate.c
index 52336f44..38078521 100644
--- a/lstate.c
+++ b/lstate.c
@@ -181,6 +181,7 @@ static void stack_init (lua_State *L1, lua_State *L) {
181 int i; CallInfo *ci; 181 int i; CallInfo *ci;
182 /* initialize stack array */ 182 /* initialize stack array */
183 L1->stack = luaM_newvector(L, BASIC_STACK_SIZE + EXTRA_STACK, StackValue); 183 L1->stack = luaM_newvector(L, BASIC_STACK_SIZE + EXTRA_STACK, StackValue);
184 L1->tbclist = L1->stack;
184 for (i = 0; i < BASIC_STACK_SIZE + EXTRA_STACK; i++) 185 for (i = 0; i < BASIC_STACK_SIZE + EXTRA_STACK; i++)
185 setnilvalue(s2v(L1->stack + i)); /* erase new stack */ 186 setnilvalue(s2v(L1->stack + i)); /* erase new stack */
186 L1->top = L1->stack; 187 L1->top = L1->stack;
@@ -262,16 +263,18 @@ static void preinit_thread (lua_State *L, global_State *g) {
262 L->status = LUA_OK; 263 L->status = LUA_OK;
263 L->errfunc = 0; 264 L->errfunc = 0;
264 L->oldpc = 0; 265 L->oldpc = 0;
265 L->ptbc = NULL;
266} 266}
267 267
268 268
269static void close_state (lua_State *L) { 269static void close_state (lua_State *L) {
270 global_State *g = G(L); 270 global_State *g = G(L);
271 luaD_closeprotected(L, 0, LUA_OK); /* close all upvalues */ 271 if (!completestate(g)) /* closing a partially built state? */
272 luaC_freeallobjects(L); /* collect all objects */ 272 luaC_freeallobjects(L); /* jucst collect its objects */
273 if (completestate(g)) /* closing a fully built state? */ 273 else { /* closing a fully built state */
274 luaD_closeprotected(L, 1, LUA_OK); /* close all upvalues */
275 luaC_freeallobjects(L); /* collect all objects */
274 luai_userstateclose(L); 276 luai_userstateclose(L);
277 }
275 luaM_freearray(L, G(L)->strt.hash, G(L)->strt.size); 278 luaM_freearray(L, G(L)->strt.hash, G(L)->strt.size);
276 freestack(L); 279 freestack(L);
277 lua_assert(gettotalbytes(g) == sizeof(LG)); 280 lua_assert(gettotalbytes(g) == sizeof(LG));
@@ -312,7 +315,7 @@ LUA_API lua_State *lua_newthread (lua_State *L) {
312 315
313void luaE_freethread (lua_State *L, lua_State *L1) { 316void luaE_freethread (lua_State *L, lua_State *L1) {
314 LX *l = fromstate(L1); 317 LX *l = fromstate(L1);
315 luaF_close(L1, L1->stack, NOCLOSINGMETH, 0); /* close all upvalues */ 318 luaF_closeupval(L1, L1->stack); /* close all upvalues */
316 lua_assert(L1->openupval == NULL); 319 lua_assert(L1->openupval == NULL);
317 luai_userstatefree(L, L1); 320 luai_userstatefree(L, L1);
318 freestack(L1); 321 freestack(L1);
@@ -327,7 +330,7 @@ int luaE_resetthread (lua_State *L, int status) {
327 ci->callstatus = CIST_C; 330 ci->callstatus = CIST_C;
328 if (status == LUA_YIELD) 331 if (status == LUA_YIELD)
329 status = LUA_OK; 332 status = LUA_OK;
330 status = luaD_closeprotected(L, 0, status); 333 status = luaD_closeprotected(L, 1, status);
331 if (status != LUA_OK) /* errors? */ 334 if (status != LUA_OK) /* errors? */
332 luaD_seterrorobj(L, status, L->stack + 1); 335 luaD_seterrorobj(L, status, L->stack + 1);
333 else 336 else
diff --git a/lstate.h b/lstate.h
index 5ef55355..b6ade7c7 100644
--- a/lstate.h
+++ b/lstate.h
@@ -307,6 +307,7 @@ struct lua_State {
307 StkId stack_last; /* end of stack (last element + 1) */ 307 StkId stack_last; /* end of stack (last element + 1) */
308 StkId stack; /* stack base */ 308 StkId stack; /* stack base */
309 UpVal *openupval; /* list of open upvalues in this stack */ 309 UpVal *openupval; /* list of open upvalues in this stack */
310 StkId tbclist; /* list of to-be-closed variables */
310 GCObject *gclist; 311 GCObject *gclist;
311 struct lua_State *twups; /* list of threads with open upvalues */ 312 struct lua_State *twups; /* list of threads with open upvalues */
312 struct lua_longjmp *errorJmp; /* current error recover point */ 313 struct lua_longjmp *errorJmp; /* current error recover point */
@@ -318,7 +319,6 @@ struct lua_State {
318 int basehookcount; 319 int basehookcount;
319 int hookcount; 320 int hookcount;
320 volatile l_signalT hookmask; 321 volatile l_signalT hookmask;
321 StkId ptbc; /* pending to-be-closed variable */
322}; 322};
323 323
324 324
diff --git a/ltests.c b/ltests.c
index 9c13338a..da95d027 100644
--- a/ltests.c
+++ b/ltests.c
@@ -446,6 +446,7 @@ static void checkstack (global_State *g, lua_State *L1) {
446 for (uv = L1->openupval; uv != NULL; uv = uv->u.open.next) 446 for (uv = L1->openupval; uv != NULL; uv = uv->u.open.next)
447 assert(upisopen(uv)); /* must be open */ 447 assert(upisopen(uv)); /* must be open */
448 assert(L1->top <= L1->stack_last); 448 assert(L1->top <= L1->stack_last);
449 assert(L1->tbclist <= L1->top);
449 for (ci = L1->ci; ci != NULL; ci = ci->previous) { 450 for (ci = L1->ci; ci != NULL; ci = ci->previous) {
450 assert(ci->top <= L1->stack_last); 451 assert(ci->top <= L1->stack_last);
451 assert(lua_checkpc(ci)); 452 assert(lua_checkpc(ci));
diff --git a/lvm.c b/lvm.c
index e9b1dcdd..1252ecbf 100644
--- a/lvm.c
+++ b/lvm.c
@@ -1635,10 +1635,8 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
1635 b = cast_int(L->top - ra); 1635 b = cast_int(L->top - ra);
1636 savepc(ci); /* several calls here can raise errors */ 1636 savepc(ci); /* several calls here can raise errors */
1637 if (TESTARG_k(i)) { 1637 if (TESTARG_k(i)) {
1638 /* close upvalues from current call; the compiler ensures 1638 luaF_closeupval(L, base); /* close upvalues from current call */
1639 that there are no to-be-closed variables here, so this 1639 lua_assert(L->tbclist < base); /* no pending tbc variables */
1640 call cannot change the stack */
1641 luaF_close(L, base, NOCLOSINGMETH, 0);
1642 lua_assert(base == ci->func + 1); 1640 lua_assert(base == ci->func + 1);
1643 } 1641 }
1644 while (!ttisfunction(s2v(ra))) { /* not a function? */ 1642 while (!ttisfunction(s2v(ra))) { /* not a function? */
diff --git a/testes/locals.lua b/testes/locals.lua
index a25b2b9f..446ec13a 100644
--- a/testes/locals.lua
+++ b/testes/locals.lua
@@ -529,6 +529,40 @@ local function checktable (t1, t2)
529end 529end
530 530
531 531
532do -- test for tbc variable high in the stack
533
534 -- function to force a stack overflow
535 local function overflow (n)
536 overflow(n + 1)
537 end
538
539 -- error handler will create tbc variable handling a stack overflow,
540 -- high in the stack
541 local function errorh (m)
542 assert(string.find(m, "stack overflow"))
543 local x <close> = func2close(function (o) o[1] = 10 end)
544 return x
545 end
546
547 local flag
548 local st, obj
549 -- run test in a coroutine so as not to swell the main stack
550 local co = coroutine.wrap(function ()
551 -- tbc variable down the stack
552 local y <close> = func2close(function (obj, msg)
553 assert(msg == nil)
554 obj[1] = 100
555 flag = obj
556 end)
557 collectgarbage("stop")
558 st, obj = xpcall(overflow, errorh, 0)
559 collectgarbage("restart")
560 end)
561 co()
562 assert(not st and obj[1] == 10 and flag[1] == 100)
563end
564
565
532if rawget(_G, "T") then 566if rawget(_G, "T") then
533 567
534 -- memory error inside closing function 568 -- memory error inside closing function
@@ -563,13 +597,13 @@ if rawget(_G, "T") then
563 597
564 local function test () 598 local function test ()
565 local x <close> = enter(0) -- set a memory limit 599 local x <close> = enter(0) -- set a memory limit
566 -- creation of previous upvalue will raise a memory error 600 local y = {} -- raise a memory error
567 assert(false) -- should not run
568 end 601 end
569 602
570 local _, msg = pcall(test) 603 local _, msg = pcall(test)
571 assert(msg == "not enough memory" and closemsg == "not enough memory") 604 assert(msg == "not enough memory" and closemsg == "not enough memory")
572 605
606
573 -- repeat test with extra closing upvalues 607 -- repeat test with extra closing upvalues
574 local function test () 608 local function test ()
575 local xxx <close> = func2close(function (self, msg) 609 local xxx <close> = func2close(function (self, msg)
@@ -580,8 +614,7 @@ if rawget(_G, "T") then
580 assert(msg == "not enough memory"); 614 assert(msg == "not enough memory");
581 end) 615 end)
582 local x <close> = enter(0) -- set a memory limit 616 local x <close> = enter(0) -- set a memory limit
583 -- creation of previous upvalue will raise a memory error 617 local y = {} -- raise a memory error
584 os.exit(false) -- should not run
585 end 618 end
586 619
587 local _, msg = pcall(test) 620 local _, msg = pcall(test)
@@ -607,7 +640,7 @@ if rawget(_G, "T") then
607 -- concat this table needs two buffer resizes (one for each 's') 640 -- concat this table needs two buffer resizes (one for each 's')
608 local a = {s, s} 641 local a = {s, s}
609 642
610 collectgarbage() 643 collectgarbage(); collectgarbage()
611 644
612 m = T.totalmem() 645 m = T.totalmem()
613 collectgarbage("stop") 646 collectgarbage("stop")
@@ -630,7 +663,7 @@ if rawget(_G, "T") then
630 -- second buffer was released by 'toclose' 663 -- second buffer was released by 'toclose'
631 assert(T.totalmem() - m <= extra) 664 assert(T.totalmem() - m <= extra)
632 665
633 -- userdata, upvalue, buffer, buffer, final string 666 -- userdata, buffer, buffer, final string
634 T.totalmem(m + 4*lim + extra) 667 T.totalmem(m + 4*lim + extra)
635 assert(#table.concat(a) == 2*lim) 668 assert(#table.concat(a) == 2*lim)
636 669
@@ -753,8 +786,8 @@ do
753 checktable({co()}, {true, 10, 20, 30}) 786 checktable({co()}, {true, 10, 20, 30})
754 checktable(trace, {"nowX", "z1", "z2", "nowY", "y1", "y2", "x1", "x2"}) 787 checktable(trace, {"nowX", "z1", "z2", "nowY", "y1", "y2", "x1", "x2"})
755 788
756end 789end
757 790
758 791
759do 792do
760 -- yielding inside closing metamethods after an error 793 -- yielding inside closing metamethods after an error