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.
This commit is contained in:
Roberto I
2025-11-26 11:18:29 -03:00
parent d94f7ba304
commit f33cc4ddec
11 changed files with 155 additions and 70 deletions

View File

@@ -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 */

View File

@@ -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 */
/*

View File

@@ -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 */

View File

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

66
ltm.c
View File

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

4
ltm.h
View File

@@ -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

5
lvm.c
View File

@@ -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) {

View File

@@ -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.

View File

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

View File

@@ -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

View File

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