Просмотр исходного кода

New conceptual model for vararg

Conceptually, all functions get their vararg arguments in a vararg
table. The storing of vararg arguments in the stack is always treated
as an optimization.
Roberto I 2 месяцев назад
Родитель
Сommit
f33cc4ddec
11 измененных файлов с 154 добавлено и 69 удалено
  1. 5 0
      lcode.c
  2. 2 3
      lobject.h
  3. 1 1
      lopcodes.h
  4. 11 13
      lparser.c
  5. 51 13
      ltm.c
  6. 2 2
      ltm.h
  7. 3 2
      lvm.c
  8. 38 30
      manual/manual.of
  9. 3 1
      testes/coroutine.lua
  10. 5 4
      testes/db.lua
  11. 33 0
      testes/vararg.lua

+ 5 - 0
lcode.c

@@ -1951,6 +1951,11 @@ void luaK_finish (FuncState *fs) {
           SET_OPCODE(*pc, OP_GETTABLE);  /* must get vararg there */
         break;
       }
+      case OP_VARARG: {
+        if (p->flag & PF_VATAB)  /* function has a vararg table? */
+          SETARG_k(*pc, 1);  /* must get vararg there */
+        break;
+      }
       case OP_JMP: {  /* to optimize jumps to jumps */
         int target = finaltarget(p->code, i);
         fixjump(fs, i, target);  /* jump directly to final target */

+ 2 - 3
lobject.h

@@ -584,9 +584,8 @@ typedef struct AbsLineInfo {
 ** Flags in Prototypes
 */
 #define PF_ISVARARG	1  /* function is vararg */
-#define PF_VAVAR	2  /* function has vararg parameter */
-#define PF_VATAB	4  /* function has vararg table */
-#define PF_FIXED	8  /* prototype has parts in fixed memory */
+#define PF_VATAB	2  /* function has vararg table */
+#define PF_FIXED	4  /* prototype has parts in fixed memory */
 
 
 /*

+ 1 - 1
lopcodes.h

@@ -336,7 +336,7 @@ OP_SETLIST,/*	A vB vC k	R[A][vC+i] := R[A+i], 1 <= i <= vB	*/
 
 OP_CLOSURE,/*	A Bx	R[A] := closure(KPROTO[Bx])			*/
 
-OP_VARARG,/*	A C	R[A], R[A+1], ..., R[A+C-2] = vararg		*/
+OP_VARARG,/*	A C	R[A], ..., R[A+C-2] = vararg, R[B] is vararg param. */
 
 OP_GETVARG, /* A B C	R[A] := R[B][R[C]], R[B] is vararg parameter    */
 

+ 11 - 13
lparser.c

@@ -1056,9 +1056,8 @@ static void constructor (LexState *ls, expdesc *t) {
 /* }====================================================================== */
 
 
-static void setvararg (FuncState *fs, int kind) {
-  lua_assert(kind & PF_ISVARARG);
-  fs->f->flag |= cast_byte(kind);
+static void setvararg (FuncState *fs) {
+  fs->f->flag |= PF_ISVARARG;
   luaK_codeABC(fs, OP_VARARGPREP, 0, 0, 0);
 }
 
@@ -1078,12 +1077,12 @@ static void parlist (LexState *ls) {
           break;
         }
         case TK_DOTS: {
-          varargk |= PF_ISVARARG;
+          varargk = 1;
           luaX_next(ls);  /* skip '...' */
-          if (ls->t.token == TK_NAME) {
+          if (ls->t.token == TK_NAME)
             new_varkind(ls, str_checkname(ls), RDKVAVAR);
-            varargk |= PF_VAVAR;
-          }
+          else
+            new_localvarliteral(ls, "(vararg table)");
           break;
         }
         default: luaX_syntaxerror(ls, "<name> or '...' expected");
@@ -1092,10 +1091,9 @@ static void parlist (LexState *ls) {
   }
   adjustlocalvars(ls, nparams);
   f->numparams = cast_byte(fs->nactvar);
-  if (varargk != 0) {
-    setvararg(fs, varargk);  /* declared vararg */
-    if (varargk & PF_VAVAR)
-      adjustlocalvars(ls, 1);  /* vararg parameter */
+  if (varargk) {
+    setvararg(fs);  /* declared vararg */
+    adjustlocalvars(ls, 1);  /* vararg parameter */
   }
   /* reserve registers for parameters (plus vararg parameter, if present) */
   luaK_reserveregs(fs, fs->nactvar);
@@ -1287,7 +1285,7 @@ static void simpleexp (LexState *ls, expdesc *v) {
       FuncState *fs = ls->fs;
       check_condition(ls, fs->f->flag & PF_ISVARARG,
                       "cannot use '...' outside a vararg function");
-      init_exp(v, VVARARG, luaK_codeABC(fs, OP_VARARG, 0, 0, 1));
+      init_exp(v, VVARARG, luaK_codeABC(fs, OP_VARARG, 0, fs->f->numparams, 1));
       break;
     }
     case '{' /*}*/: {  /* constructor */
@@ -2153,7 +2151,7 @@ static void mainfunc (LexState *ls, FuncState *fs) {
   BlockCnt bl;
   Upvaldesc *env;
   open_func(ls, fs, &bl);
-  setvararg(fs, PF_ISVARARG);  /* main function is always vararg */
+  setvararg(fs);  /* main function is always vararg */
   env = allocupvalue(fs);  /* ...set environment upvalue */
   env->instack = 1;
   env->idx = 0;

+ 51 - 13
ltm.c

@@ -242,6 +242,7 @@ static void createvarargtab (lua_State *L, StkId f, int n) {
   luaH_set(L, t, &key, &value);  /* t.n = n */
   for (i = 0; i < n; i++)
     luaH_setint(L, t, i + 1, s2v(f + i));
+  luaC_checkGC(L);
 }
 
 
@@ -265,11 +266,11 @@ void luaT_adjustvarargs (lua_State *L, CallInfo *ci, const Proto *p) {
     setobjs2s(L, L->top.p++, ci->func.p + i);
     setnilvalue(s2v(ci->func.p + i));  /* erase original parameter (for GC) */
   }
-  if (p->flag & PF_VAVAR) {  /* is there a vararg parameter? */
-    if (p->flag & PF_VATAB)  /* does it need a vararg table? */
-      createvarargtab(L, ci->func.p + nfixparams + 1, nextra);
-    else  /* no table; set parameter to nil */
-      setnilvalue(s2v(L->top.p));
+  if (p->flag & PF_VATAB)  /* does it need a vararg table? */
+    createvarargtab(L, ci->func.p + nfixparams + 1, nextra);
+  else {  /* no table; set parameter to nil */
+    setnilvalue(s2v(L->top.p));
+    L->top.p++;
   }
   ci->func.p += totalargs + 1;
   ci->top.p += totalargs + 1;
@@ -299,16 +300,53 @@ void luaT_getvararg (CallInfo *ci, StkId ra, TValue *rc) {
 }
 
 
-void luaT_getvarargs (lua_State *L, CallInfo *ci, StkId where, int wanted) {
-  int i;
-  int nextra = ci->u.l.nextraargs;
+/*
+** Get the number of extra arguments in a vararg function. If vararg
+** table has been optimized away, that number is in the call info.
+** Otherwise, get the field 'n' from the vararg table and check that it
+** has a proper value (non-negative integer not larger than the stack
+** limit).
+*/
+static int getnumargs (lua_State *L, CallInfo *ci, Table *h) {
+  if (h == NULL)  /* no vararg table? */
+    return ci->u.l.nextraargs;
+  else {
+    TValue res;
+    if (luaH_getshortstr(h, luaS_new(L, "n"), &res) != LUA_VNUMINT ||
+        l_castS2U(ivalue(&res)) > cast_uint(INT_MAX/2))
+      luaG_runerror(L, "vararg table has no proper 'n'");
+    return cast_int(ivalue(&res));
+  }
+}
+
+
+/*
+** Get 'wanted' vararg arguments and put them in 'where'. 'vatab' is
+** the register of the vararg table or -1 if there is no vararg table.
+*/
+void luaT_getvarargs (lua_State *L, CallInfo *ci, StkId where, int wanted,
+                                    int vatab) {
+  Table *h = (vatab < 0) ? NULL : hvalue(s2v(ci->func.p + vatab + 1));
+  int nargs = getnumargs(L, ci, h);  /* number of available vararg args. */
+  int i, touse;  /* 'touse' is minimum between 'wanted' and 'nargs' */
   if (wanted < 0) {
-    wanted = nextra;  /* get all extra arguments available */
-    checkstackp(L, nextra, where);  /* ensure stack space */
-    L->top.p = where + nextra;  /* next instruction will need top */
+    touse = wanted = nargs;  /* get all extra arguments available */
+    checkstackp(L, nargs, where);  /* ensure stack space */
+    L->top.p = where + nargs;  /* next instruction will need top */
+  }
+  else
+    touse = (nargs > wanted) ? wanted : nargs;
+  if (h == NULL) {  /* no vararg table? */
+    for (i = 0; i < touse; i++)  /* get vararg values from the stack */
+      setobjs2s(L, where + i, ci->func.p - nargs + i);
+  }
+  else {  /* get vararg values from vararg table */
+    for (i = 0; i < touse; i++) {
+      lu_byte tag = luaH_getint(h, i + 1, s2v(where + i));
+      if (tagisempty(tag))
+       setnilvalue(s2v(where + i));
+    }
   }
-  for (i = 0; i < wanted && i < nextra; i++)
-    setobjs2s(L, where + i, ci->func.p - nextra + i);
   for (; i < wanted; i++)   /* complete required results with nil */
     setnilvalue(s2v(where + i));
 }

+ 2 - 2
ltm.h

@@ -98,8 +98,8 @@ LUAI_FUNC int luaT_callorderiTM (lua_State *L, const TValue *p1, int v2,
 LUAI_FUNC void luaT_adjustvarargs (lua_State *L, struct CallInfo *ci,
                                                  const Proto *p);
 LUAI_FUNC void luaT_getvararg (CallInfo *ci, StkId ra, TValue *rc);
-LUAI_FUNC void luaT_getvarargs (lua_State *L, struct CallInfo *ci,
-                                              StkId where, int wanted);
+LUAI_FUNC void luaT_getvarargs (lua_State *L, struct CallInfo *ci, StkId where,
+                                              int wanted, int vatab);
 
 
 #endif

+ 3 - 2
lvm.c

@@ -1935,8 +1935,9 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
       }
       vmcase(OP_VARARG) {
         StkId ra = RA(i);
-        int n = GETARG_C(i) - 1;  /* required results */
-        Protect(luaT_getvarargs(L, ci, ra, n));
+        int n = GETARG_C(i) - 1;  /* required results (-1 means all) */
+        int vatab = GETARG_k(i) ? GETARG_B(i) : -1;
+        Protect(luaT_getvarargs(L, ci, ra, n, vatab));
         vmbreak;
       }
       vmcase(OP_GETVARG) {

+ 38 - 30
manual/manual.of

@@ -2221,7 +2221,7 @@ The form
 }
 can be used to emulate methods.
 A call @T{v:name(@rep{args})}
-is syntactic sugar for @T{v.name(v,@rep{args})},
+is syntactic sugar for @T{v.name(v, @rep{args})},
 except that @id{v} is evaluated only once.
 
 Arguments have the following syntax:
@@ -2372,12 +2372,10 @@ which is indicated by three dots (@Char{...})
 at the end of its parameter list.
 A variadic function does not adjust its argument list;
 instead, it collects all extra arguments and supplies them
-to the function through a @def{vararg expression} and,
-if present, a @def{vararg table}.
-
-A vararg expression is also written as three dots,
-and its value is a list of all actual extra arguments,
-similar to a function with multiple results @see{multires}.
+to the function through a @def{vararg table}.
+In that table,
+the values at indices 1, 2, etc. are the extra arguments,
+and the value at index @St{n} is the number of extra arguments.
 
 As an example, consider the following definitions:
 @verbatim{
@@ -2386,7 +2384,7 @@ function g(a, b, ...) end
 function r() return 1,2,3 end
 }
 Then, we have the following mapping from arguments to parameters and
-to the vararg expression:
+to the vararg table:
 @verbatim{
 CALL             PARAMETERS
 
@@ -2396,33 +2394,39 @@ f(3, 4, 5)       a=3, b=4
 f(r(), 10)       a=1, b=10
 f(r())           a=1, b=2
 
-g(3)             a=3, b=nil, ... ->  (nothing)
-g(3, 4)          a=3, b=4,   ... ->  (nothing)
-g(3, 4, 5, 8)    a=3, b=4,   ... ->  5  8
-g(5, r())        a=5, b=1,   ... ->  2  3
+g(3)             a=3, b=nil, va. table ->  {n = 0}
+g(3, 4)          a=3, b=4,   va. table ->  {n = 0}
+g(3, 4, 5, 8)    a=3, b=4,   va. table ->  {5, 8, n = 2}
+g(5, r())        a=5, b=1,   va. table ->  {2, 3, n = 2}
 }
 
-The presence of a vararg table in a variadic function is indicated
-by a name after the three dots.
+A vararg table in a variadic function can have an optional name,
+given after the three dots.
 When present,
-a vararg table behaves like a read-only local variable
-with the given name that is initialized with a table.
-In that table,
-the values at indices 1, 2, etc. are the extra arguments,
-and the value at index @St{n} is the number of extra arguments.
-In other words, the code behaves as if the function started with
-the following statement,
-assuming the standard behavior of @Lid{table.pack}:
-@verbatim{
-local <const> name = table.pack(...)
-}
+that name denotes a read-only local variable that
+refers to the vararg table.
+If the vararg table does not have a name,
+it can only be accessed through a vararg expression.
+
+A vararg expression is also written as three dots,
+and its value is a list of the values in the vararg table,
+from 1 to the integer value at index @St{n}.
+(Therefore, if the code does not modify the vararg table,
+this list corresponds to the extra arguments in the function call.)
+This list behaves like the results from a
+function with multiple results @see{multires}.
 
 As an optimization,
-if the vararg table is used only as the base table
-in the syntactic constructions @T{t[exp]} or @T{t.id})
-and it is not an upvalue,
+if the vararg table satisfies some conditions,
 the code does not create an actual table and instead translates
-the indexing expressions into accesses to the internal vararg data.
+the indexing expressions and the vararg expressions
+into accesses to the internal vararg data.
+The conditions are as follows:
+If the vararg table has a name,
+that name is not an upvalue in a nested function
+and it is used only as the base table
+in the syntactic constructions @T{t[exp]} or @T{t.id}).
+Note that an anonymous vararg table always satisfy these conditions.
 
 }
 
@@ -3103,7 +3107,7 @@ void *luaL_alloc (void *ud, void *ptr, size_t osize,
 }
 Note that @N{ISO C} ensures
 that @T{free(NULL)} has no effect and that
-@T{realloc(NULL,size)} is equivalent to @T{malloc(size)}.
+@T{realloc(NULL, size)} is equivalent to @T{malloc(size)}.
 
 }
 
@@ -9197,6 +9201,10 @@ Compile-time constants may not appear in this listing,
 if they were optimized away by the compiler.
 Negative indices refer to vararg arguments;
 @num{-1} is the first vararg argument.
+These negative indices are only available when the vararg table
+has been optimized away;
+otherwise, the vararg arguments are available in the vararg table.
+
 The function returns @fail
 if there is no variable with the given index,
 and raises an error when called with a level out of range.

+ 3 - 1
testes/coroutine.lua

@@ -702,7 +702,9 @@ else
     assert(t.currentline == t.linedefined + 2)
     assert(not debug.getinfo(c, 1))      -- no other level
     assert(coroutine.resume(c))          -- run next line
-    local n,v = debug.getlocal(c, 0, 2)    -- check next local
+    local n,v = debug.getlocal(c, 0, 2)    -- check vararg table
+    assert(n == "(vararg table)" and v == nil)
+    local n,v = debug.getlocal(c, 0, 3)    -- check next local
     assert(n == "b" and v == 10)
     v = {coroutine.resume(c)}         -- finish coroutine
     assert(v[1] == true and v[2] == 2 and v[3] == 3 and v[4] == undef)

+ 5 - 4
testes/db.lua

@@ -356,8 +356,8 @@ function f(a,b)
   global assert, g, string
   local _, y = debug.getlocal(1, 2)
   assert(x == a and y == b)
-  assert(debug.setlocal(2, 3, "pera") == "AA".."AA")
-  assert(debug.setlocal(2, 4, "manga") == "B")
+  assert(debug.setlocal(2, 4, "pera") == "AA".."AA")
+  assert(debug.setlocal(2, 5, "manga") == "B")
   x = debug.getinfo(2)
   assert(x.func == g and x.what == "Lua" and x.name == 'g' and
          x.nups == 2 and string.find(x.source, "^@.*db%.lua$"))
@@ -392,7 +392,7 @@ function g (...)
      global *
      local B = 13
      global<const> assert
-     local x,y = debug.getlocal(1,5)
+     local x,y = debug.getlocal(1,6)
      assert(x == 'B' and y == 13)
   end
 end
@@ -458,7 +458,8 @@ local function collectlocals (level)
   local tab = {}
   for i = 1, math.huge do
     local n, v = debug.getlocal(level + 1, i)
-    if not (n and string.find(n, "^[a-zA-Z0-9_]+$")) then
+    if not (n and string.find(n, "^[a-zA-Z0-9_]+$") or
+            n == "(vararg table)") then
        break   -- consider only real variables
     end
     tab[n] = v

+ 33 - 0
testes/vararg.lua

@@ -101,6 +101,38 @@ a,b,c,d,e = f(4)
 assert(a==nil and b==nil and c==nil and d==nil and e==nil)
 
 
+do  -- vararg expressions using unpack
+  local function aux (a, v, ...t)
+    for k, val in pairs(v) do t[k] = val end
+    return ...
+  end
+
+  local t = table.pack(aux(10, {11, [5] = 24}, 1, 2, 3, nil, 4))
+  assert(t.n == 5 and t[1] == 11 and t[2] == 2 and t[3] == 3
+                  and t[4] == nil and t[5] == 24)
+
+  local t = table.pack(aux(nil, {1, [20] = "a", [30] = "b", n = 30}))
+  assert(t.n == 30 and t[1] == 1 and t[20] == "a" and t[30] == "b")
+  -- table has only those four elements
+  assert(next(t, next(t, next(t, next(t, next(t, nil))))) == nil)
+
+  local a, b, c, d = aux(nil, {}, 10, 20, 30)
+  assert(a == 10 and b == 20 and c == 30 and d == nil)
+
+  local function aux (a, b, n, ...t) t.n = n; return b, ... end
+  local t = table.pack(aux(10, 1, 10000))
+  assert(t.n == 10001 and t[1] == 1 and #t == 1)
+
+  local function checkerr (emsg, f, ...)
+    local st, msg = pcall(f, ...)
+    assert(not st and string.find(msg, emsg))
+  end
+  checkerr("no proper 'n'", aux, 1, 1, -1)
+  checkerr("no proper 'n'", aux, 1, 1, math.maxinteger)
+  checkerr("no proper 'n'", aux, 1, 1, math.mininteger)
+  checkerr("no proper 'n'", aux, 1, 1, 1.0)
+end
+
 -- varargs for main chunks
 local f = assert(load[[ return {...} ]])
 local x = f(2,3)
@@ -205,6 +237,7 @@ do   -- access to vararg parameter
       assert(t[k] == v[k])
     end
     assert(t.n == v.n)
+    return ...
   end
 
   local t = table.pack(10, 20, 30)