Optimization for vararg tables

A vararg table can be virtual. If the vararg table is used only as
a base in indexing expressions, the code does not need to create an
actual table for it. Instead, it compiles the indexing expressions
into direct accesses to the internal vararg data.
This commit is contained in:
Roberto I 2025-09-24 18:33:08 -03:00
parent 0cc3c9447c
commit 25c54fe60e
13 changed files with 186 additions and 63 deletions

57
lcode.c
View File

@ -842,6 +842,12 @@ void luaK_dischargevars (FuncState *fs, expdesc *e) {
e->k = VRELOC;
break;
}
case VVARGIND: {
freeregs(fs, e->u.ind.t, e->u.ind.idx);
e->u.info = luaK_codeABC(fs, OP_GETVARG, 0, e->u.ind.t, e->u.ind.idx);
e->k = VRELOC;
break;
}
case VVARARG: case VCALL: {
luaK_setoneret(fs, e);
break;
@ -1004,11 +1010,11 @@ int luaK_exp2anyreg (FuncState *fs, expdesc *e) {
/*
** Ensures final expression result is either in a register
** or in an upvalue.
** Ensures final expression result is either in a register,
** in an upvalue, or it is the vararg parameter.
*/
void luaK_exp2anyregup (FuncState *fs, expdesc *e) {
if (e->k != VUPVAL || hasjumps(e))
if ((e->k != VUPVAL && e->k != VVARGVAR) || hasjumps(e))
luaK_exp2anyreg(fs, e);
}
@ -1314,6 +1320,13 @@ void luaK_self (FuncState *fs, expdesc *e, expdesc *key) {
}
/* auxiliary function to define indexing expressions */
static void fillidxk (expdesc *t, int idx, expkind k) {
t->u.ind.idx = cast_byte(idx);
t->k = k;
}
/*
** Create expression 't[k]'. 't' must have its final result already in a
** register or upvalue. Upvalues can only be indexed by literal strings.
@ -1325,31 +1338,30 @@ void luaK_indexed (FuncState *fs, expdesc *t, expdesc *k) {
if (k->k == VKSTR)
keystr = str2K(fs, k);
lua_assert(!hasjumps(t) &&
(t->k == VLOCAL || t->k == VNONRELOC || t->k == VUPVAL));
(t->k == VLOCAL || t->k == VVARGVAR ||
t->k == VNONRELOC || t->k == VUPVAL));
if (t->k == VUPVAL && !isKstr(fs, k)) /* upvalue indexed by non 'Kstr'? */
luaK_exp2anyreg(fs, t); /* put it in a register */
if (t->k == VUPVAL) {
lu_byte temp = cast_byte(t->u.info); /* upvalue index */
t->u.ind.t = temp; /* (can't do a direct assignment; values overlap) */
lua_assert(isKstr(fs, k));
t->u.ind.idx = cast_short(k->u.info); /* literal short string */
t->k = VINDEXUP;
fillidxk(t, k->u.info, VINDEXUP); /* literal short string */
}
else if (t->k == VVARGVAR) { /* indexing the vararg parameter? */
lua_assert(t->u.ind.t == fs->f->numparams);
t->u.ind.t = cast_byte(t->u.var.ridx);
fillidxk(t, luaK_exp2anyreg(fs, k), VVARGIND); /* register */
}
else {
/* register index of the table */
t->u.ind.t = cast_byte((t->k == VLOCAL) ? t->u.var.ridx: t->u.info);
if (isKstr(fs, k)) {
t->u.ind.idx = cast_short(k->u.info); /* literal short string */
t->k = VINDEXSTR;
}
else if (isCint(k)) { /* int. constant in proper range? */
t->u.ind.idx = cast_short(k->u.ival);
t->k = VINDEXI;
}
else {
t->u.ind.idx = cast_short(luaK_exp2anyreg(fs, k)); /* register */
t->k = VINDEXED;
}
if (isKstr(fs, k))
fillidxk(t, k->u.info, VINDEXSTR); /* literal short string */
else if (isCint(k)) /* int. constant in proper range? */
fillidxk(t, cast_int(k->u.ival), VINDEXI);
else
fillidxk(t, luaK_exp2anyreg(fs, k), VINDEXED); /* register */
}
t->u.ind.keystr = keystr; /* string index in 'k' */
t->u.ind.ro = 0; /* by default, not read-only */
@ -1913,9 +1925,14 @@ void luaK_finish (FuncState *fs) {
SETARG_C(*pc, p->numparams + 1); /* signal that it is vararg */
break;
}
case OP_JMP: {
case OP_GETVARG: {
if (p->flag & PF_VATAB) /* function has a vararg table? */
SET_OPCODE(*pc, OP_GETTABLE); /* must get vararg there */
break;
}
case OP_JMP: { /* to optimize jumps to jumps */
int target = finaltarget(p->code, i);
fixjump(fs, i, target);
fixjump(fs, i, target); /* jump directly to final target */
break;
}
default: break;

View File

@ -21,7 +21,7 @@ static const void *const disptab[NUM_OPCODES] = {
#if 0
** you can update the following list with this command:
**
** sed -n '/^OP_/\!d; s/OP_/\&\&L_OP_/ ; s/,.*/,/ ; s/\/.*// ; p' lopcodes.h
** sed -n '/^OP_/!d; s/OP_/\&\&L_OP_/ ; s/,.*/,/ ; s/\/.*// ; p' lopcodes.h
**
#endif
@ -106,6 +106,7 @@ static const void *const disptab[NUM_OPCODES] = {
&&L_OP_SETLIST,
&&L_OP_CLOSURE,
&&L_OP_VARARG,
&&L_OP_GETVARG,
&&L_OP_VARARGPREP,
&&L_OP_EXTRAARG

View File

@ -102,6 +102,7 @@ LUAI_DDEF const lu_byte luaP_opmodes[NUM_OPCODES] = {
,opmode(0, 0, 1, 0, 0, ivABC) /* OP_SETLIST */
,opmode(0, 0, 0, 0, 1, iABx) /* OP_CLOSURE */
,opmode(0, 1, 0, 0, 1, iABC) /* OP_VARARG */
,opmode(0, 0, 0, 0, 1, iABC) /* OP_GETVARG */
,opmode(0, 0, 1, 0, 1, iABC) /* OP_VARARGPREP */
,opmode(0, 0, 0, 0, 0, iAx) /* OP_EXTRAARG */
};

View File

@ -338,6 +338,8 @@ OP_CLOSURE,/* A Bx R[A] := closure(KPROTO[Bx]) */
OP_VARARG,/* A C R[A], R[A+1], ..., R[A+C-2] = vararg */
OP_GETVARG, /* A B C R[A] := R[B][R[C]], R[B] is vararg parameter */
OP_VARARGPREP,/* (adjust vararg parameters) */
OP_EXTRAARG/* Ax extra (larger) argument for previous opcode */

View File

@ -94,6 +94,7 @@ static const char *const opnames[] = {
"SETLIST",
"CLOSURE",
"VARARG",
"GETVARG",
"VARARGPREP",
"EXTRAARG",
NULL

View File

@ -279,7 +279,9 @@ static void init_var (FuncState *fs, expdesc *e, int vidx) {
/*
** Raises an error if variable described by 'e' is read only
** Raises an error if variable described by 'e' is read only; moreover,
** if 'e' is t[exp] where t is the vararg parameter, change it to index
** a real table. (Virtual vararg tables cannot be changed.)
*/
static void check_readonly (LexState *ls, expdesc *e) {
FuncState *fs = ls->fs;
@ -301,6 +303,10 @@ static void check_readonly (LexState *ls, expdesc *e) {
varname = up->name;
break;
}
case VVARGIND: {
fs->f->flag |= PF_VATAB; /* function will need a vararg table */
e->k = VINDEXED;
} /* FALLTHROUGH */
case VINDEXUP: case VINDEXSTR: case VINDEXED: { /* global variable */
if (e->u.ind.ro) /* read-only? */
varname = tsvalue(&fs->f->k[e->u.ind.keystr]);
@ -1073,7 +1079,7 @@ static void parlist (LexState *ls) {
case TK_DOTS: {
varargk |= PF_ISVARARG;
luaX_next(ls);
if (testnext(ls, '=')) {
if (testnext(ls, '|')) {
new_varkind(ls, str_checkname(ls), RDKVAVAR);
varargk |= PF_VAVAR;
}

View File

@ -51,6 +51,8 @@ typedef enum {
ind.ro = true if it represents a read-only global;
ind.keystr = if key is a string, index in 'k' of that string;
-1 if key is not a string */
VVARGIND, /* indexed vararg parameter;
ind.* as in VINDEXED */
VINDEXUP, /* indexed upvalue;
ind.idx = key's K index;
ind.* as in VINDEXED */

22
ltm.c
View File

@ -277,6 +277,28 @@ void luaT_adjustvarargs (lua_State *L, CallInfo *ci, const Proto *p) {
}
void luaT_getvararg (CallInfo *ci, StkId ra, TValue *rc) {
int nextra = ci->u.l.nextraargs;
lua_Integer n;
if (tointegerns(rc, &n)) { /* integral value? */
if (l_castS2U(n) - 1 < cast_uint(nextra)) {
StkId slot = ci->func.p - nextra + cast_int(n) - 1;
setobjs2s(((lua_State*)NULL), ra, slot);
return;
}
}
else if (ttisshrstring(rc)) { /* short-string value? */
size_t len;
const char *s = getlstr(tsvalue(rc), len);
if (len == 1 && s[0] == 'n') { /* key is "n"? */
setivalue(s2v(ra), nextra);
return;
}
}
setnilvalue(s2v(ra)); /* else produce nil */
}
void luaT_getvarargs (lua_State *L, CallInfo *ci, StkId where, int wanted) {
int i;
int nextra = ci->u.l.nextraargs;

1
ltm.h
View File

@ -97,6 +97,7 @@ 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);

6
lvm.c
View File

@ -1926,6 +1926,12 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
Protect(luaT_getvarargs(L, ci, ra, n));
vmbreak;
}
vmcase(OP_GETVARG) {
StkId ra = RA(i);
TValue *rc = vRC(i);
luaT_getvararg(ci, ra, rc);
vmbreak;
}
vmcase(OP_VARARGPREP) {
ProtectNT(luaT_adjustvarargs(L, ci, cl->p));
if (l_unlikely(trap)) { /* previous "Protect" updated trap */

View File

@ -2262,7 +2262,7 @@ return x or f(x) -- results adjusted to 1
@sect3{func-def| @title{Function Definitions}
The syntax for function definition is
The syntax for a function definition is
@Produc{
@producname{functiondef}@producbody{@Rw{function} funcbody}
@producname{funcbody}@producbody{@bnfter{(} @bnfopt{parlist} @bnfter{)} block @Rw{end}}
@ -2315,6 +2315,18 @@ translates to
global f; f = function () @rep{body} end
}
The @emphx{colon} syntax
is used to emulate @def{methods},
adding an implicit extra parameter @idx{self} to the function.
Thus, the statement
@verbatim{
function t.a.b.c:f (@rep{params}) @rep{body} end
}
is syntactic sugar for
@verbatim{
t.a.b.c.f = function (self, @rep{params}) @rep{body} end
}
A function definition is an executable expression,
whose value has type @emph{function}.
When Lua precompiles a chunk,
@ -2325,11 +2337,25 @@ the function is @emph{instantiated} (or @emph{closed}).
This function instance, or @emphx{closure},
is the final value of the expression.
Results are returned using the @Rw{return} statement @see{control}.
If control reaches the end of a function
without encountering a @Rw{return} statement,
then the function returns with no results.
@index{multiple return}
There is a system-dependent limit on the number of values
that a function may return.
This limit is guaranteed to be at least 1000.
@sect4{@title{Parameters}
Parameters act as local variables that are
initialized with the argument values:
@Produc{
@producname{parlist}@producbody{namelist @bnfopt{@bnfter{,} @bnfter{...}} @Or
@bnfter{...}}
@producname{parlist}@producbody{namelist @bnfopt{@bnfter{,} varargparam} @Or
varargparam}
@producname{varargparam}@producbody{@bnfter{...}
@bnfopt{@bnfter{|} @bnfNter{Name}}}
}
When a Lua function is called,
it adjusts its list of @x{arguments} to
@ -2339,11 +2365,12 @@ 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},
which is also written as three dots.
The value of this expression is a list of all actual extra arguments,
similar to a function with multiple results @see{multires}.
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}.
As an example, consider the following definitions:
@verbatim{
@ -2368,26 +2395,27 @@ g(3, 4, 5, 8) a=3, b=4, ... -> 5 8
g(5, r()) a=5, b=1, ... -> 2 3
}
Results are returned using the @Rw{return} statement @see{control}.
If control reaches the end of a function
without encountering a @Rw{return} statement,
then the function returns with no results.
@index{multiple return}
There is a system-dependent limit on the number of values
that a function may return.
This limit is guaranteed to be at least 1000.
The @emphx{colon} syntax
is used to emulate @def{methods},
adding an implicit extra parameter @idx{self} to the function.
Thus, the statement
The presence of a varag table in a variadic function is indicated
by the @T{|name} syntax 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{
function t.a.b.c:f (@rep{params}) @rep{body} end
local <const> name = table.pack(...)
}
is syntactic sugar for
@verbatim{
t.a.b.c.f = function (self, @rep{params}) @rep{body} end
As an optimization,
if the vararg table is used only as a base in indexing expressions
(the @T{t} in @T{t[exp]} or @T{t.id}) and it is not an upvalue,
the code does not create an actual table and instead translates
the indexing expressions into accesses to the internal vararg data.
}
}
@ -2422,7 +2450,7 @@ for instance @T{foo(e1, e2, e3)} @see{functioncall}.}
for instance @T{a , b, c = e1, e2, e3} @see{assignment}.}
@item{A local or global declaration,
which is a special case of multiple assignment.}
which is similar to a multiple assignment.}
@item{The initial values in a generic @rw{for} loop,
for instance @T{for k in e1, e2, e3 do ... end} @see{for}.}
@ -3016,7 +3044,7 @@ typedef void * (*lua_Alloc) (void *ud,
size_t osize,
size_t nsize);|
The type of the @x{memory-allocation function} used by Lua states.
The type of the @x{memory-allocator function} used by Lua states.
The allocator function must provide a
functionality similar to @id{realloc},
but not exactly the same.
@ -3482,7 +3510,7 @@ This function should not be called by a finalizer.
@APIEntry{lua_Alloc lua_getallocf (lua_State *L, void **ud);|
@apii{0,0,-}
Returns the @x{memory-allocation function} of a given state.
Returns the @x{memory-allocator function} of a given state.
If @id{ud} is not @id{NULL}, Lua stores in @T{*ud} the
opaque pointer given when the memory-allocator function was set.
@ -9732,8 +9760,11 @@ and @bnfNter{LiteralString}, see @See{lexical}.)
@producname{funcbody}@producbody{@bnfter{(} @bnfopt{parlist} @bnfter{)} block @Rw{end}}
@producname{parlist}@producbody{namelist @bnfopt{@bnfter{,} @bnfter{...}}
@Or @bnfter{...}}
@producname{parlist}@producbody{namelist @bnfopt{@bnfter{,} varargparam} @Or
varargparam}
@producname{varargparam}@producbody{@bnfter{...}
@bnfopt{@bnfter{|} @bnfNter{Name}}}
@producname{tableconstructor}@producbody{@bnfter{@Open} @bnfopt{fieldlist} @bnfter{@Close}}

View File

@ -310,8 +310,7 @@ do -- testing presence of second argument
local function foo (howtoclose, obj, n)
local ca -- copy of 'a' visible inside its close metamethod
do
local a <close> = func2close(function (...)
local t = table.pack(...)
local a <close> = func2close(function (... | t)
assert(select("#", ...) == n)
assert(t.n == n and t[1] == ca and (t.n < 2 or t[2] == obj))
ca = 15 -- final value to be returned if howtoclose=="scope"
@ -911,8 +910,7 @@ do
local extrares -- result from extra yield (if any)
local function check (body, extra, ...)
local t = table.pack(...) -- expected returns
local function check (body, extra, ...|t)
local co = coroutine.wrap(body)
if extra then
extrares = co() -- runs until first (extra) yield

View File

@ -3,7 +3,7 @@
print('testing vararg')
local function f (a, ...=t)
local function f (a, ...|t)
local x = {n = select('#', ...), ...}
assert(x.n == t.n)
for i = 1, x.n do
@ -20,7 +20,7 @@ local function c12 (...)
return res, 2
end
local function vararg (...=t) return t end
local function vararg (... | t) return t end
local call = function (f, args) return f(table.unpack(args, 1, args.n)) end
@ -153,8 +153,8 @@ end
do -- vararg parameter used in nested functions
local function foo (... = tab1)
return function (... = tab2)
local function foo (... | tab1)
return function (... | tab2)
return {tab1, tab2}
end
end
@ -165,16 +165,51 @@ do -- vararg parameter used in nested functions
end
do -- vararg parameter is read-only
local st, msg = load("return function (... = t) t = 10 end")
local st, msg = load("return function (... | t) t = 10 end")
assert(string.find(msg, "const variable 't'"))
local st, msg = load[[
local function foo (... = extra)
local function foo (... | extra)
return function (...) extra = nil end
end
]]
assert(string.find(msg, "const variable 'extra'"))
end
do -- _ENV as vararg parameter
local st, msg = load[[
local function aux (... | _ENV)
global <const> a
a = 10
end ]]
assert(string.find(msg, "const variable 'a'"))
end
do -- access to vararg parameter
local function notab (keys, t, ... | v)
for _, k in pairs(keys) do
assert(t[k] == v[k])
end
assert(t.n == v.n)
end
local t = table.pack(10, 20, 30)
local keys = {-1, 0, 1, t.n, t.n + 1, 1.0, 1.1, "n", print, "k", "1"}
notab(keys, t, 10, 20, 30) -- ensure stack space
local m = collectgarbage"count"
notab(keys, t, 10, 20, 30)
-- 'notab' does not create any table/object
assert(m == collectgarbage"count")
-- writing to the vararg table
local function foo (... | t)
t[1] = t[1] + 10
return t[1]
end
assert(foo(10, 30) == 20)
end
print('OK')