diff options
author | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2018-12-04 15:01:42 -0200 |
---|---|---|
committer | Roberto Ierusalimschy <roberto@inf.puc-rio.br> | 2018-12-04 15:01:42 -0200 |
commit | 28d829c86712ce5bc453feccc5129a32f78d80c0 (patch) | |
tree | 58197e763ca643bbe4a042372927bf81092b141c | |
parent | 6d04537ea660fd12fc16c328366b701fabaf4919 (diff) | |
download | lua-28d829c86712ce5bc453feccc5129a32f78d80c0.tar.gz lua-28d829c86712ce5bc453feccc5129a32f78d80c0.tar.bz2 lua-28d829c86712ce5bc453feccc5129a32f78d80c0.zip |
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.
-rw-r--r-- | lparser.c | 5 | ||||
-rw-r--r-- | lvm.c | 2 | ||||
-rw-r--r-- | manual/manual.of | 17 | ||||
-rw-r--r-- | testes/locals.lua | 13 |
4 files changed, 21 insertions, 16 deletions
@@ -53,6 +53,7 @@ typedef struct BlockCnt { | |||
53 | lu_byte nactvar; /* # active locals outside the block */ | 53 | lu_byte nactvar; /* # active locals outside the block */ |
54 | lu_byte upval; /* true if some variable in the block is an upvalue */ | 54 | lu_byte upval; /* true if some variable in the block is an upvalue */ |
55 | lu_byte isloop; /* true if 'block' is a loop */ | 55 | lu_byte isloop; /* true if 'block' is a loop */ |
56 | lu_byte insidetbc; /* true if inside the scope of a to-be-closed var. */ | ||
56 | } BlockCnt; | 57 | } BlockCnt; |
57 | 58 | ||
58 | 59 | ||
@@ -510,6 +511,7 @@ static void enterblock (FuncState *fs, BlockCnt *bl, lu_byte isloop) { | |||
510 | bl->firstlabel = fs->ls->dyd->label.n; | 511 | bl->firstlabel = fs->ls->dyd->label.n; |
511 | bl->firstgoto = fs->ls->dyd->gt.n; | 512 | bl->firstgoto = fs->ls->dyd->gt.n; |
512 | bl->upval = 0; | 513 | bl->upval = 0; |
514 | bl->insidetbc = (fs->bl != NULL && fs->bl->insidetbc); | ||
513 | bl->previous = fs->bl; | 515 | bl->previous = fs->bl; |
514 | fs->bl = bl; | 516 | fs->bl = bl; |
515 | lua_assert(fs->freereg == fs->nactvar); | 517 | lua_assert(fs->freereg == fs->nactvar); |
@@ -1631,6 +1633,7 @@ static void tocloselocalstat (LexState *ls) { | |||
1631 | checknext(ls, '='); | 1633 | checknext(ls, '='); |
1632 | exp1(ls, 0); | 1634 | exp1(ls, 0); |
1633 | markupval(fs, fs->nactvar); | 1635 | markupval(fs, fs->nactvar); |
1636 | fs->bl->insidetbc = 1; /* in the scope of a to-be-closed variable */ | ||
1634 | adjustlocalvars(ls, 1); | 1637 | adjustlocalvars(ls, 1); |
1635 | luaK_codeABC(fs, OP_TBC, fs->nactvar - 1, 0, 0); | 1638 | luaK_codeABC(fs, OP_TBC, fs->nactvar - 1, 0, 0); |
1636 | } | 1639 | } |
@@ -1701,7 +1704,7 @@ static void retstat (LexState *ls) { | |||
1701 | nret = explist(ls, &e); /* optional return values */ | 1704 | nret = explist(ls, &e); /* optional return values */ |
1702 | if (hasmultret(e.k)) { | 1705 | if (hasmultret(e.k)) { |
1703 | luaK_setmultret(fs, &e); | 1706 | luaK_setmultret(fs, &e); |
1704 | if (e.k == VCALL && nret == 1) { /* tail call? */ | 1707 | if (e.k == VCALL && nret == 1 && !fs->bl->insidetbc) { /* tail call? */ |
1705 | SET_OPCODE(getinstruction(fs,&e), OP_TAILCALL); | 1708 | SET_OPCODE(getinstruction(fs,&e), OP_TAILCALL); |
1706 | lua_assert(GETARG_A(getinstruction(fs,&e)) == fs->nactvar); | 1709 | lua_assert(GETARG_A(getinstruction(fs,&e)) == fs->nactvar); |
1707 | } | 1710 | } |
@@ -1565,7 +1565,7 @@ void luaV_execute (lua_State *L, CallInfo *ci) { | |||
1565 | if (nparams1) /* vararg function? */ | 1565 | if (nparams1) /* vararg function? */ |
1566 | delta = ci->u.l.nextraargs + nparams1; | 1566 | delta = ci->u.l.nextraargs + nparams1; |
1567 | /* close upvalues from current call */ | 1567 | /* close upvalues from current call */ |
1568 | ProtectNT(luaF_close(L, base, LUA_OK)); | 1568 | luaF_close(L, base, -1); /* (no to-be-closed vars. here) */ |
1569 | updatestack(ci); | 1569 | updatestack(ci); |
1570 | } | 1570 | } |
1571 | if (!ttisfunction(s2v(ra))) { /* not a function? */ | 1571 | if (!ttisfunction(s2v(ra))) { /* not a function? */ |
diff --git a/manual/manual.of b/manual/manual.of index 3902f2f3..8b5e5d93 100644 --- a/manual/manual.of +++ b/manual/manual.of | |||
@@ -1538,9 +1538,6 @@ except that its value is @emph{closed} whenever the variable | |||
1538 | goes out of scope, including normal block termination, | 1538 | goes out of scope, including normal block termination, |
1539 | exiting its block by @Rw{break}/@Rw{goto}/@Rw{return}, | 1539 | exiting its block by @Rw{break}/@Rw{goto}/@Rw{return}, |
1540 | or exiting by an error. | 1540 | or exiting by an error. |
1541 | If a block ends in a tail call @see{functioncall}, | ||
1542 | all variables of the caller function go out of scope | ||
1543 | before the start of the callee function. | ||
1544 | 1541 | ||
1545 | To \emph{close} a value has the following meaning here: | 1542 | To \emph{close} a value has the following meaning here: |
1546 | If the value of the variable when it goes out of scope is a function, | 1543 | 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}'} | |||
2038 | is syntactic sugar for @T{f('@rep{string}')}; | 2035 | is syntactic sugar for @T{f('@rep{string}')}; |
2039 | that is, the argument list is a single literal string. | 2036 | that is, the argument list is a single literal string. |
2040 | 2037 | ||
2041 | A call of the form @T{return @rep{functioncall}} is called | 2038 | A call of the form @T{return @rep{functioncall}} not in the |
2042 | a @def{tail call}. | 2039 | scope of a to-be-closed variable is called a @def{tail call}. |
2043 | Lua implements @def{proper tail calls} | 2040 | Lua implements @def{proper tail calls} |
2044 | (or @emph{proper tail recursion}): | 2041 | (or @emph{proper tail recursion}): |
2045 | in a tail call, | 2042 | in a tail call, |
@@ -2049,13 +2046,15 @@ a program can execute. | |||
2049 | However, a tail call erases any debug information about the | 2046 | However, a tail call erases any debug information about the |
2050 | calling function. | 2047 | calling function. |
2051 | Note that a tail call only happens with a particular syntax, | 2048 | Note that a tail call only happens with a particular syntax, |
2052 | where the @Rw{return} has one single function call as argument; | 2049 | where the @Rw{return} has one single function call as argument, |
2053 | this syntax makes the calling function return exactly | 2050 | and it is outside the scope of any to-be-closed variable. |
2054 | the returns of the called function. | 2051 | This syntax makes the calling function return exactly |
2052 | the returns of the called function, | ||
2053 | without any intervening action. | ||
2055 | So, none of the following examples are tail calls: | 2054 | So, none of the following examples are tail calls: |
2056 | @verbatim{ | 2055 | @verbatim{ |
2057 | return (f(x)) -- results adjusted to 1 | 2056 | return (f(x)) -- results adjusted to 1 |
2058 | return 2 * f(x) | 2057 | return 2 * f(x) -- result multiplied by 2 |
2059 | return x, f(x) -- additional results | 2058 | return x, f(x) -- additional results |
2060 | f(x); return -- results discarded | 2059 | f(x); return -- results discarded |
2061 | return x or f(x) -- results adjusted to 1 | 2060 | return x or f(x) -- results adjusted to 1 |
diff --git a/testes/locals.lua b/testes/locals.lua index 24681dd9..340af61c 100644 --- a/testes/locals.lua +++ b/testes/locals.lua | |||
@@ -225,21 +225,24 @@ end | |||
225 | 225 | ||
226 | 226 | ||
227 | do | 227 | do |
228 | -- to-be-closed variables must be closed in tail calls | 228 | -- calls cannot be tail in the scope of to-be-closed variables |
229 | local X, Y | 229 | local X, Y |
230 | local function foo () | 230 | local function foo () |
231 | local *toclose _ = function () Y = 10 end | 231 | local *toclose _ = function () Y = 10 end |
232 | assert(X == 20 and Y == nil) | 232 | assert(X == true and Y == nil) -- 'X' not closed yet |
233 | return 1,2,3 | 233 | return 1,2,3 |
234 | end | 234 | end |
235 | 235 | ||
236 | local function bar () | 236 | local function bar () |
237 | local *toclose _ = function () X = 20 end | 237 | local *toclose _ = function () X = false end |
238 | return foo() | 238 | X = true |
239 | do | ||
240 | return foo() -- not a tail call! | ||
241 | end | ||
239 | end | 242 | end |
240 | 243 | ||
241 | local a, b, c, d = bar() | 244 | local a, b, c, d = bar() |
242 | assert(a == 1 and b == 2 and c == 3 and X == 20 and Y == 10 and d == nil) | 245 | assert(a == 1 and b == 2 and c == 3 and X == false and Y == 10 and d == nil) |
243 | end | 246 | end |
244 | 247 | ||
245 | 248 | ||