To-be-closed variables in the C API

This commit is contained in:
Roberto Ierusalimschy 2018-10-25 15:30:15 -03:00
parent 41c800b352
commit 34840301b5
6 changed files with 122 additions and 16 deletions

15
lapi.c
View File

@ -173,15 +173,17 @@ LUA_API void lua_settop (lua_State *L, int idx) {
StkId func = L->ci->func;
lua_lock(L);
if (idx >= 0) {
StkId newtop = (func + 1) + idx;
api_check(L, idx <= L->stack_last - (func + 1), "new top too large");
while (L->top < (func + 1) + idx)
while (L->top < newtop)
setnilvalue(s2v(L->top++));
L->top = (func + 1) + idx;
L->top = newtop;
}
else {
api_check(L, -(idx+1) <= (L->top - (func + 1)), "invalid new top");
L->top += idx+1; /* 'subtract' index (index is negative) */
}
luaF_close(L, L->top, LUA_OK);
lua_unlock(L);
}
@ -1205,6 +1207,15 @@ LUA_API int lua_next (lua_State *L, int idx) {
}
LUA_API void lua_tobeclosed (lua_State *L) {
int nresults = L->ci->nresults;
luaF_newtbcupval(L, L->top - 1); /* create new to-be-closed upvalue */
if (!hastocloseCfunc(nresults)) /* function not marked yet? */
L->ci->nresults = codeNresults(nresults); /* mark it */
lua_assert(hastocloseCfunc(L->ci->nresults));
}
LUA_API void lua_concat (lua_State *L, int n) {
lua_lock(L);
api_checknelems(L, n);

15
lapi.h
View File

@ -15,10 +15,23 @@
"stack overflow");}
#define adjustresults(L,nres) \
{ if ((nres) == LUA_MULTRET && L->ci->top < L->top) L->ci->top = L->top; }
{ if ((nres) <= LUA_MULTRET && L->ci->top < L->top) L->ci->top = L->top; }
#define api_checknelems(L,n) api_check(L, (n) < (L->top - L->ci->func), \
"not enough elements in the stack")
/*
** To reduce the overhead of returning from C functions, the presence of
** to-be-closed variables in these functions is coded in the CallInfo's
** field 'nresults', in a way that functions with no to-be-closed variables
** with zero, one, or "all" wanted results have no overhead. Functions
** with other number of wanted results, as well as functions with
** variables to be closed, have an extra check.
*/
#define hastocloseCfunc(n) ((n) < LUA_MULTRET)
#define codeNresults(n) (-(n) - 3)
#endif

24
ldo.c
View File

@ -366,32 +366,38 @@ void luaD_tryfuncTM (lua_State *L, StkId func) {
** separated.
*/
static void moveresults (lua_State *L, StkId res, int nres, int wanted) {
StkId firstresult;
int i;
switch (wanted) { /* handle typical cases separately */
case 0: /* no values needed */
L->top = res;
break;
return;
case 1: /* one value needed */
if (nres == 0) /* no results? */
setnilvalue(s2v(res)); /* adjust with nil */
else
setobjs2s(L, res, L->top - nres); /* move it to proper place */
L->top = res + 1;
break;
return;
case LUA_MULTRET:
wanted = nres; /* we want all results */
/* FALLTHROUGH */
default: { /* multiple results */
StkId firstresult = L->top - nres; /* index of first result */
int i;
break;
default: /* multiple results (or to-be-closed variables) */
if (hastocloseCfunc(wanted)) {
luaF_close(L, res, LUA_OK);
wanted = codeNresults(wanted); /* correct value */
if (wanted == LUA_MULTRET)
wanted = nres;
}
break;
}
firstresult = L->top - nres; /* index of first result */
/* move all results to correct place */
for (i = 0; i < nres && i < wanted; i++)
setobjs2s(L, res + i, firstresult + i);
for (; i < wanted; i++) /* complete wanted number of results */
setnilvalue(s2v(res + i));
L->top = res + wanted; /* top points after the last result */
break;
}
}
}

View File

@ -1554,6 +1554,9 @@ static struct X { int x; } x;
int i = getindex;
return lua_yieldk(L1, nres, i, Cfunck);
}
else if EQ("tobeclosed") {
lua_tobeclosed(L);
}
else luaL_error(L, "unknown instruction %s", buff);
}
return 0;

2
lua.h
View File

@ -333,6 +333,8 @@ LUA_API size_t (lua_stringtonumber) (lua_State *L, const char *s);
LUA_API lua_Alloc (lua_getallocf) (lua_State *L, void **ud);
LUA_API void (lua_setallocf) (lua_State *L, lua_Alloc f, void *ud);
LUA_API void (lua_tobeclosed) (lua_State *L);
/*
** {==============================================================

View File

@ -967,6 +967,77 @@ T.closestate(L1)
L1 = nil
print('+')
-------------------------------------------------------------------------
-- testing to-be-closed variables
-------------------------------------------------------------------------
print"testing to-be-closed variables"
do
local openresource = {}
local function newresource ()
local x = setmetatable({10}, {__close = function(y)
assert(openresource[#openresource] == y)
openresource[#openresource] = nil
y[1] = y[1] + 1
end})
openresource[#openresource + 1] = x
return x
end
local a = T.testC([[
call 0 1 # create resource
tobeclosed # mark it to be closed
return 1
]], newresource)
assert(a[1] == 11)
assert(#openresource == 0) -- was closed
-- repeat the test, but calling function in a 'multret' context
local a = {T.testC([[
call 0 1 # create resource
tobeclosed # mark it to be closed
return 2
]], newresource)}
assert(type(a[1]) == "string" and a[2][1] == 11)
assert(#openresource == 0) -- was closed
-- error
local a, b = pcall(T.testC, [[
call 0 1 # create resource
tobeclosed # mark it to be closed
error # resource is the error object
]], newresource)
assert(a == false and b[1] == 11)
assert(#openresource == 0) -- was closed
local function check (n)
assert(#openresource == n)
end
-- closing resources with 'settop'
local a = T.testC([[
pushvalue 2
call 0 1 # create resource
tobeclosed # mark it to be closed
pushvalue 2
call 0 1 # create another resource
tobeclosed # mark it to be closed
pushvalue 3
pushint 2 # there should be two open resources
call 1 0
pop 1 # pop second resource from the stack
pushvalue 3
pushint 1 # there should be one open resource
call 1 0
pop 1 # pop second resource from the stack
pushint *
return 1 # return stack size
]], newresource, check)
assert(a == 3) -- no extra items left in the stack
end
-------------------------------------------------------------------------
-- testing memory limits