Calls cannot be tail in the scope of a to-be-closed variable
A to-be-closed variable must be closed when a block ends, so even a 'return foo()' cannot directly returns the results of 'foo'; the function must close the scope before returning.
This commit is contained in:
@@ -53,6 +53,7 @@ typedef struct BlockCnt {
|
|||||||
lu_byte nactvar; /* # active locals outside the block */
|
lu_byte nactvar; /* # active locals outside the block */
|
||||||
lu_byte upval; /* true if some variable in the block is an upvalue */
|
lu_byte upval; /* true if some variable in the block is an upvalue */
|
||||||
lu_byte isloop; /* true if 'block' is a loop */
|
lu_byte isloop; /* true if 'block' is a loop */
|
||||||
|
lu_byte insidetbc; /* true if inside the scope of a to-be-closed var. */
|
||||||
} BlockCnt;
|
} BlockCnt;
|
||||||
|
|
||||||
|
|
||||||
@@ -510,6 +511,7 @@ static void enterblock (FuncState *fs, BlockCnt *bl, lu_byte isloop) {
|
|||||||
bl->firstlabel = fs->ls->dyd->label.n;
|
bl->firstlabel = fs->ls->dyd->label.n;
|
||||||
bl->firstgoto = fs->ls->dyd->gt.n;
|
bl->firstgoto = fs->ls->dyd->gt.n;
|
||||||
bl->upval = 0;
|
bl->upval = 0;
|
||||||
|
bl->insidetbc = (fs->bl != NULL && fs->bl->insidetbc);
|
||||||
bl->previous = fs->bl;
|
bl->previous = fs->bl;
|
||||||
fs->bl = bl;
|
fs->bl = bl;
|
||||||
lua_assert(fs->freereg == fs->nactvar);
|
lua_assert(fs->freereg == fs->nactvar);
|
||||||
@@ -1631,6 +1633,7 @@ static void tocloselocalstat (LexState *ls) {
|
|||||||
checknext(ls, '=');
|
checknext(ls, '=');
|
||||||
exp1(ls, 0);
|
exp1(ls, 0);
|
||||||
markupval(fs, fs->nactvar);
|
markupval(fs, fs->nactvar);
|
||||||
|
fs->bl->insidetbc = 1; /* in the scope of a to-be-closed variable */
|
||||||
adjustlocalvars(ls, 1);
|
adjustlocalvars(ls, 1);
|
||||||
luaK_codeABC(fs, OP_TBC, fs->nactvar - 1, 0, 0);
|
luaK_codeABC(fs, OP_TBC, fs->nactvar - 1, 0, 0);
|
||||||
}
|
}
|
||||||
@@ -1701,7 +1704,7 @@ static void retstat (LexState *ls) {
|
|||||||
nret = explist(ls, &e); /* optional return values */
|
nret = explist(ls, &e); /* optional return values */
|
||||||
if (hasmultret(e.k)) {
|
if (hasmultret(e.k)) {
|
||||||
luaK_setmultret(fs, &e);
|
luaK_setmultret(fs, &e);
|
||||||
if (e.k == VCALL && nret == 1) { /* tail call? */
|
if (e.k == VCALL && nret == 1 && !fs->bl->insidetbc) { /* tail call? */
|
||||||
SET_OPCODE(getinstruction(fs,&e), OP_TAILCALL);
|
SET_OPCODE(getinstruction(fs,&e), OP_TAILCALL);
|
||||||
lua_assert(GETARG_A(getinstruction(fs,&e)) == fs->nactvar);
|
lua_assert(GETARG_A(getinstruction(fs,&e)) == fs->nactvar);
|
||||||
}
|
}
|
||||||
|
|||||||
2
lvm.c
2
lvm.c
@@ -1565,7 +1565,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
|
|||||||
if (nparams1) /* vararg function? */
|
if (nparams1) /* vararg function? */
|
||||||
delta = ci->u.l.nextraargs + nparams1;
|
delta = ci->u.l.nextraargs + nparams1;
|
||||||
/* close upvalues from current call */
|
/* close upvalues from current call */
|
||||||
ProtectNT(luaF_close(L, base, LUA_OK));
|
luaF_close(L, base, -1); /* (no to-be-closed vars. here) */
|
||||||
updatestack(ci);
|
updatestack(ci);
|
||||||
}
|
}
|
||||||
if (!ttisfunction(s2v(ra))) { /* not a function? */
|
if (!ttisfunction(s2v(ra))) { /* not a function? */
|
||||||
|
|||||||
@@ -1538,9 +1538,6 @@ except that its value is @emph{closed} whenever the variable
|
|||||||
goes out of scope, including normal block termination,
|
goes out of scope, including normal block termination,
|
||||||
exiting its block by @Rw{break}/@Rw{goto}/@Rw{return},
|
exiting its block by @Rw{break}/@Rw{goto}/@Rw{return},
|
||||||
or exiting by an error.
|
or exiting by an error.
|
||||||
If a block ends in a tail call @see{functioncall},
|
|
||||||
all variables of the caller function go out of scope
|
|
||||||
before the start of the callee function.
|
|
||||||
|
|
||||||
To \emph{close} a value has the following meaning here:
|
To \emph{close} a value has the following meaning here:
|
||||||
If the value of the variable when it goes out of scope is a function,
|
If the value of the variable when it goes out of scope is a function,
|
||||||
@@ -2038,8 +2035,8 @@ A call of the form @T{f'@rep{string}'}
|
|||||||
is syntactic sugar for @T{f('@rep{string}')};
|
is syntactic sugar for @T{f('@rep{string}')};
|
||||||
that is, the argument list is a single literal string.
|
that is, the argument list is a single literal string.
|
||||||
|
|
||||||
A call of the form @T{return @rep{functioncall}} is called
|
A call of the form @T{return @rep{functioncall}} not in the
|
||||||
a @def{tail call}.
|
scope of a to-be-closed variable is called a @def{tail call}.
|
||||||
Lua implements @def{proper tail calls}
|
Lua implements @def{proper tail calls}
|
||||||
(or @emph{proper tail recursion}):
|
(or @emph{proper tail recursion}):
|
||||||
in a tail call,
|
in a tail call,
|
||||||
@@ -2049,13 +2046,15 @@ a program can execute.
|
|||||||
However, a tail call erases any debug information about the
|
However, a tail call erases any debug information about the
|
||||||
calling function.
|
calling function.
|
||||||
Note that a tail call only happens with a particular syntax,
|
Note that a tail call only happens with a particular syntax,
|
||||||
where the @Rw{return} has one single function call as argument;
|
where the @Rw{return} has one single function call as argument,
|
||||||
this syntax makes the calling function return exactly
|
and it is outside the scope of any to-be-closed variable.
|
||||||
the returns of the called function.
|
This syntax makes the calling function return exactly
|
||||||
|
the returns of the called function,
|
||||||
|
without any intervening action.
|
||||||
So, none of the following examples are tail calls:
|
So, none of the following examples are tail calls:
|
||||||
@verbatim{
|
@verbatim{
|
||||||
return (f(x)) -- results adjusted to 1
|
return (f(x)) -- results adjusted to 1
|
||||||
return 2 * f(x)
|
return 2 * f(x) -- result multiplied by 2
|
||||||
return x, f(x) -- additional results
|
return x, f(x) -- additional results
|
||||||
f(x); return -- results discarded
|
f(x); return -- results discarded
|
||||||
return x or f(x) -- results adjusted to 1
|
return x or f(x) -- results adjusted to 1
|
||||||
|
|||||||
@@ -225,21 +225,24 @@ end
|
|||||||
|
|
||||||
|
|
||||||
do
|
do
|
||||||
-- to-be-closed variables must be closed in tail calls
|
-- calls cannot be tail in the scope of to-be-closed variables
|
||||||
local X, Y
|
local X, Y
|
||||||
local function foo ()
|
local function foo ()
|
||||||
local *toclose _ = function () Y = 10 end
|
local *toclose _ = function () Y = 10 end
|
||||||
assert(X == 20 and Y == nil)
|
assert(X == true and Y == nil) -- 'X' not closed yet
|
||||||
return 1,2,3
|
return 1,2,3
|
||||||
end
|
end
|
||||||
|
|
||||||
local function bar ()
|
local function bar ()
|
||||||
local *toclose _ = function () X = 20 end
|
local *toclose _ = function () X = false end
|
||||||
return foo()
|
X = true
|
||||||
|
do
|
||||||
|
return foo() -- not a tail call!
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
local a, b, c, d = bar()
|
local a, b, c, d = bar()
|
||||||
assert(a == 1 and b == 2 and c == 3 and X == 20 and Y == 10 and d == nil)
|
assert(a == 1 and b == 2 and c == 3 and X == false and Y == 10 and d == nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user