浏览代码

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.
Roberto Ierusalimschy 6 年之前
父节点
当前提交
28d829c867
共有 4 个文件被更改,包括 21 次插入16 次删除
  1. 4 1
      lparser.c
  2. 1 1
      lvm.c
  3. 8 9
      manual/manual.of
  4. 8 5
      testes/locals.lua

+ 4 - 1
lparser.c

@@ -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);
       }
       }

+ 1 - 1
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? */

+ 8 - 9
manual/manual.of

@@ -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 @def{tail call}.
+A call of the form @T{return @rep{functioncall}} not in the
+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;
-this syntax makes the calling function return exactly
-the returns of the called function.
+where the @Rw{return} has one single function call as argument,
+and it is outside the scope of any to-be-closed variable.
+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

+ 8 - 5
testes/locals.lua

@@ -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
-    return foo()
+    local *toclose _ = function () X = false end
+    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