Skip to content

Commit bc97000

Browse files
committed
'__close' methods can yield in the return of a C function
When, inside a coroutine, a C function with to-be-closed slots return, the corresponding metamethods can yield. ('__close' metamethods called through 'lua_closeslot' still cannot yield, as there is no continuation to go when resuming.)
1 parent f79ccdc commit bc97000

5 files changed

Lines changed: 131 additions & 34 deletions

File tree

lapi.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242

4343
#define hastocloseCfunc(n) ((n) < LUA_MULTRET)
4444

45+
/* Map [-1, inf) (range of 'nresults') into (-inf, -2] */
4546
#define codeNresults(n) (-(n) - 3)
47+
#define decodeNresults(n) (-(n) - 3)
4648

4749
#endif

ldo.c

Lines changed: 43 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -408,24 +408,27 @@ static void moveresults (lua_State *L, StkId res, int nres, int wanted) {
408408
case LUA_MULTRET:
409409
wanted = nres; /* we want all results */
410410
break;
411-
default: /* multiple results (or to-be-closed variables) */
411+
default: /* two/more results and/or to-be-closed variables */
412412
if (hastocloseCfunc(wanted)) { /* to-be-closed variables? */
413413
ptrdiff_t savedres = savestack(L, res);
414-
luaF_close(L, res, CLOSEKTOP, 0); /* may change the stack */
415-
wanted = codeNresults(wanted); /* correct value */
416-
if (wanted == LUA_MULTRET)
417-
wanted = nres;
414+
L->ci->callstatus |= CIST_CLSRET; /* in case of yields */
415+
L->ci->u2.nres = nres;
416+
luaF_close(L, res, CLOSEKTOP, 1);
417+
L->ci->callstatus &= ~CIST_CLSRET;
418418
if (L->hookmask) /* if needed, call hook after '__close's */
419419
rethook(L, L->ci, nres);
420420
res = restorestack(L, savedres); /* close and hook can move stack */
421+
wanted = decodeNresults(wanted);
422+
if (wanted == LUA_MULTRET)
423+
wanted = nres; /* we want all results */
421424
}
422425
break;
423426
}
427+
/* generic case */
424428
firstresult = L->top - nres; /* index of first result */
425-
/* move all results to correct place */
426-
if (nres > wanted)
427-
nres = wanted; /* don't need more than that */
428-
for (i = 0; i < nres; i++)
429+
if (nres > wanted) /* extra results? */
430+
nres = wanted; /* don't need them */
431+
for (i = 0; i < nres; i++) /* move all results to correct place */
429432
setobjs2s(L, res + i, firstresult + i);
430433
for (; i < wanted; i++) /* complete wanted number of results */
431434
setnilvalue(s2v(res + i));
@@ -445,6 +448,9 @@ void luaD_poscall (lua_State *L, CallInfo *ci, int nres) {
445448
rethook(L, ci, nres);
446449
/* move results to proper place */
447450
moveresults(L, ci->func, nres, wanted);
451+
/* function cannot be in any of these cases when returning */
452+
lua_assert(!(ci->callstatus &
453+
(CIST_HOOKED | CIST_YPCALL | CIST_FIN | CIST_TRAN | CIST_CLSRET)));
448454
L->ci = ci->previous; /* back to caller (after closing variables) */
449455
}
450456

@@ -615,28 +621,36 @@ static int finishpcallk (lua_State *L, CallInfo *ci) {
615621

616622
/*
617623
** Completes the execution of a C function interrupted by an yield.
618-
** The interruption must have happened while the function was
619-
** executing 'lua_callk' or 'lua_pcallk'. In the second case, the
620-
** call to 'finishpcallk' finishes the interrupted execution of
621-
** 'lua_pcallk'. After that, it calls the continuation of the
622-
** interrupted function and finally it completes the job of the
623-
** 'luaD_call' that called the function.
624-
** In the call to 'adjustresults', we do not know the number of
625-
** results of the function called by 'lua_callk'/'lua_pcallk',
626-
** so we are conservative and use LUA_MULTRET (always adjust).
624+
** The interruption must have happened while the function was either
625+
** closing its tbc variables in 'moveresults' or executing
626+
** 'lua_callk'/'lua_pcallk'. In the first case, it just redoes
627+
** 'luaD_poscall'. In the second case, the call to 'finishpcallk'
628+
** finishes the interrupted execution of 'lua_pcallk'. After that, it
629+
** calls the continuation of the interrupted function and finally it
630+
** completes the job of the 'luaD_call' that called the function. In
631+
** the call to 'adjustresults', we do not know the number of results
632+
** of the function called by 'lua_callk'/'lua_pcallk', so we are
633+
** conservative and use LUA_MULTRET (always adjust).
627634
*/
628635
static void finishCcall (lua_State *L, CallInfo *ci) {
629-
int n;
630-
int status = LUA_YIELD; /* default if there were no errors */
631-
/* must have a continuation and must be able to call it */
632-
lua_assert(ci->u.c.k != NULL && yieldable(L));
633-
if (ci->callstatus & CIST_YPCALL) /* was inside a 'lua_pcallk'? */
634-
status = finishpcallk(L, ci); /* finish it */
635-
adjustresults(L, LUA_MULTRET); /* finish 'lua_callk' */
636-
lua_unlock(L);
637-
n = (*ci->u.c.k)(L, status, ci->u.c.ctx); /* call continuation */
638-
lua_lock(L);
639-
api_checknelems(L, n);
636+
int n; /* actual number of results from C function */
637+
if (ci->callstatus & CIST_CLSRET) { /* was returning? */
638+
lua_assert(hastocloseCfunc(ci->nresults));
639+
n = ci->u2.nres; /* just redo 'luaD_poscall' */
640+
/* don't need to reset CIST_CLSRET, as it will be set again anyway */
641+
}
642+
else {
643+
int status = LUA_YIELD; /* default if there were no errors */
644+
/* must have a continuation and must be able to call it */
645+
lua_assert(ci->u.c.k != NULL && yieldable(L));
646+
if (ci->callstatus & CIST_YPCALL) /* was inside a 'lua_pcallk'? */
647+
status = finishpcallk(L, ci); /* finish it */
648+
adjustresults(L, LUA_MULTRET); /* finish 'lua_callk' */
649+
lua_unlock(L);
650+
n = (*ci->u.c.k)(L, status, ci->u.c.ctx); /* call continuation */
651+
lua_lock(L);
652+
api_checknelems(L, n);
653+
}
640654
luaD_poscall(L, ci, n); /* finish 'luaD_call' */
641655
}
642656

lstate.h

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,8 @@ typedef struct stringtable {
164164
** protected call;
165165
** - field 'nyield' is used only while a function is "doing" an
166166
** yield (from the yield until the next resume);
167+
** - field 'nres' is used only while closing tbc variables when
168+
** returning from a C function;
167169
** - field 'transferinfo' is used only during call/returnhooks,
168170
** before the function starts or after it ends.
169171
*/
@@ -186,6 +188,7 @@ typedef struct CallInfo {
186188
union {
187189
int funcidx; /* called-function index */
188190
int nyield; /* number of values yielded */
191+
int nres; /* number of values returned */
189192
struct { /* info about transferred values (for call/return hooks) */
190193
unsigned short ftransfer; /* offset of first value transferred */
191194
unsigned short ntransfer; /* number of values transferred */
@@ -203,15 +206,16 @@ typedef struct CallInfo {
203206
#define CIST_C (1<<1) /* call is running a C function */
204207
#define CIST_FRESH (1<<2) /* call is on a fresh "luaV_execute" frame */
205208
#define CIST_HOOKED (1<<3) /* call is running a debug hook */
206-
#define CIST_YPCALL (1<<4) /* call is a yieldable protected call */
209+
#define CIST_YPCALL (1<<4) /* doing a yieldable protected call */
207210
#define CIST_TAIL (1<<5) /* call was tail called */
208211
#define CIST_HOOKYIELD (1<<6) /* last hook called yielded */
209212
#define CIST_FIN (1<<7) /* call is running a finalizer */
210213
#define CIST_TRAN (1<<8) /* 'ci' has transfer information */
211-
/* Bits 9-11 are used for CIST_RECST (see below) */
212-
#define CIST_RECST 9
214+
#define CIST_CLSRET (1<<9) /* function is closing tbc variables */
215+
/* Bits 10-12 are used for CIST_RECST (see below) */
216+
#define CIST_RECST 10
213217
#if defined(LUA_COMPAT_LT_LE)
214-
#define CIST_LEQ (1<<12) /* using __lt for __le */
218+
#define CIST_LEQ (1<<13) /* using __lt for __le */
215219
#endif
216220

217221

manual/manual.of

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3102,6 +3102,9 @@ Close the to-be-closed slot at the given index and set its value to @nil.
31023102
The index must be the last index previously marked to be closed
31033103
@see{lua_toclose} that is still active (that is, not closed yet).
31043104

3105+
A @Lid{__close} metamethod cannot yield
3106+
when called through this function.
3107+
31053108
(Exceptionally, this function was introduced in release 5.4.3.
31063109
It is not present in previous 5.4 releases.)
31073110

testes/locals.lua

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -707,7 +707,6 @@ if rawget(_G, "T") then
707707
-- results are correct
708708
checktable(t, {10, 20})
709709
end
710-
711710
end
712711

713712

@@ -930,6 +929,81 @@ assert(co == nil) -- eventually it will be collected
930929
collectgarbage()
931930

932931

932+
if rawget(_G, "T") then
933+
print("to-be-closed variables x coroutines in C")
934+
do
935+
local token = 0
936+
local count = 0
937+
local f = T.makeCfunc[[
938+
toclose 1
939+
toclose 2
940+
return .
941+
]]
942+
943+
local obj = func2close(function (_, msg)
944+
count = count + 1
945+
token = coroutine.yield(count, token)
946+
end)
947+
948+
local co = coroutine.wrap(f)
949+
local ct, res = co(obj, obj, 10, 20, 30, 3) -- will return 10, 20, 30
950+
-- initial token value, after closing 2nd obj
951+
assert(ct == 1 and res == 0)
952+
-- run until yield when closing 1st obj
953+
ct, res = co(100)
954+
assert(ct == 2 and res == 100)
955+
res = {co(200)} -- run until end
956+
assert(res[1] == 10 and res[2] == 20 and res[3] == 30 and res[4] == nil)
957+
assert(token == 200)
958+
end
959+
960+
do
961+
local f = T.makeCfunc[[
962+
toclose 1
963+
return .
964+
]]
965+
966+
local obj = func2close(function ()
967+
local temp
968+
local x <close> = func2close(function ()
969+
coroutine.yield(temp)
970+
return 1,2,3 -- to be ignored
971+
end)
972+
temp = coroutine.yield("closing obj")
973+
return 1,2,3 -- to be ignored
974+
end)
975+
976+
local co = coroutine.wrap(f)
977+
local res = co(obj, 10, 30, 1) -- will return only 30
978+
assert(res == "closing obj")
979+
res = co("closing x")
980+
assert(res == "closing x")
981+
res = {co()}
982+
assert(res[1] == 30 and res[2] == nil)
983+
end
984+
985+
do
986+
-- still cannot yield inside 'closeslot'
987+
local f = T.makeCfunc[[
988+
toclose 1
989+
closeslot 1
990+
]]
991+
local obj = func2close(coroutine.yield)
992+
local co = coroutine.create(f)
993+
local st, msg = coroutine.resume(co, obj)
994+
assert(not st and string.find(msg, "attempt to yield across"))
995+
996+
-- nor outside a coroutine
997+
local f = T.makeCfunc[[
998+
toclose 1
999+
]]
1000+
local st, msg = pcall(f, obj)
1001+
assert(not st and string.find(msg, "attempt to yield from outside"))
1002+
end
1003+
end
1004+
1005+
1006+
9331007
-- to-be-closed variables in generic for loops
9341008
do
9351009
local numopen = 0

0 commit comments

Comments
 (0)