Replace upvalue-based shell dispatch with OP_LUSH opcode

Add a dedicated OP_LUSH A B instruction that loads shell C functions
directly from the registry, eliminating the need for upvalue allocation
in mainfunc(), upvalue population in lua_load(), and the fragile
LUSH_NUM_UPVALS heuristic. Every chunk no longer carries 4 extra
upvalues.
This commit is contained in:
Cormac Shannon
2026-03-12 22:47:25 +00:00
parent f88b17959f
commit f09a033160
9 changed files with 46 additions and 78 deletions

22
lapi.c
View File

@@ -29,7 +29,6 @@
#include "ltm.h" #include "ltm.h"
#include "lundump.h" #include "lundump.h"
#include "lvm.h" #include "lvm.h"
#include "lcmd.h"
@@ -1136,27 +1135,6 @@ LUA_API int lua_load (lua_State *L, lua_Reader reader, void *data,
setobj(L, f->upvals[0]->v.p, &gt); setobj(L, f->upvals[0]->v.p, &gt);
luaC_barrier(L, f->upvals[0], &gt); luaC_barrier(L, f->upvals[0], &gt);
} }
if (f->nupvalues >= LUSH_NUM_UPVALS) { /* has lush upvalues? */
/* populate shell function upvalues from registry */
Table *regt = hvalue(&G(L)->l_registry);
TValue lushtv;
lu_byte tag = luaH_getint(regt, LUA_RIDX_LUSH, &lushtv);
if (novariant(tag) == LUA_TTABLE) {
static const char *const fields[] = {
"command", "interactive", "getenv", "setenv"
};
Table *lusht = hvalue(&lushtv);
int i;
for (i = 0; i < 4; i++) {
TValue val;
TString *key = luaS_new(L, fields[i]);
if (luaH_getstr(lusht, key, &val) != LUA_TNIL) {
setobj(L, f->upvals[i + 1]->v.p, &val);
luaC_barrier(L, f->upvals[i + 1], &val);
}
}
}
}
} }
lua_unlock(L); lua_unlock(L);
return APIstatus(status); return APIstatus(status);

13
lcmd.h
View File

@@ -9,13 +9,12 @@
#include "lua.h" #include "lua.h"
/* Upvalue indices for shell functions on the main chunk. /* OP_LUSH sub-operation indices (B operand selects which function) */
** Index 0 is _ENV (standard). Indices 1-4 are lush shell functions. */ #define LUSH_OP_COMMAND 0
#define LUSH_UPVAL_COMMAND 1 #define LUSH_OP_INTERACTIVE 1
#define LUSH_UPVAL_INTERACTIVE 2 #define LUSH_OP_GETENV 2
#define LUSH_UPVAL_GETENV 3 #define LUSH_OP_SETENV 3
#define LUSH_UPVAL_SETENV 4 #define LUSH_OP_COUNT 4
#define LUSH_NUM_UPVALS 5 /* total: _ENV(0) + 4 shell functions */
int lushCmd_command (lua_State *L); int lushCmd_command (lua_State *L);
int lushCmd_interactive (lua_State *L); int lushCmd_interactive (lua_State *L);

13
linit.c
View File

@@ -45,18 +45,19 @@ static const luaL_Reg stdlibs[] = {
/* /*
** Store shell functions in a registry table at LUA_RIDX_LUSH. ** Store shell functions in a registry table at LUA_RIDX_LUSH.
** These are populated as upvalues on the main chunk by lua_load(). ** Integer keys 1..4 hold the C functions (accessed by OP_LUSH).
** String key "builtins" holds the builtins table (set by luaopen_builtins).
*/ */
static void opencommand (lua_State *L) { static void opencommand (lua_State *L) {
lua_createtable(L, 0, 5); lua_createtable(L, LUSH_OP_COUNT, 1);
lua_pushcfunction(L, lushCmd_command); lua_pushcfunction(L, lushCmd_command);
lua_setfield(L, -2, "command"); lua_rawseti(L, -2, LUSH_OP_COMMAND + 1);
lua_pushcfunction(L, lushCmd_interactive); lua_pushcfunction(L, lushCmd_interactive);
lua_setfield(L, -2, "interactive"); lua_rawseti(L, -2, LUSH_OP_INTERACTIVE + 1);
lua_pushcfunction(L, lushCmd_getenv); lua_pushcfunction(L, lushCmd_getenv);
lua_setfield(L, -2, "getenv"); lua_rawseti(L, -2, LUSH_OP_GETENV + 1);
lua_pushcfunction(L, lushCmd_setenv); lua_pushcfunction(L, lushCmd_setenv);
lua_setfield(L, -2, "setenv"); lua_rawseti(L, -2, LUSH_OP_SETENV + 1);
lua_rawseti(L, LUA_REGISTRYINDEX, LUA_RIDX_LUSH); lua_rawseti(L, LUA_REGISTRYINDEX, LUA_RIDX_LUSH);
} }

View File

@@ -106,6 +106,7 @@ static const void *const disptab[NUM_OPCODES] = {
&&L_OP_SETLIST, &&L_OP_SETLIST,
&&L_OP_CLOSURE, &&L_OP_CLOSURE,
&&L_OP_VARARG, &&L_OP_VARARG,
&&L_OP_LUSH,
&&L_OP_GETVARG, &&L_OP_GETVARG,
&&L_OP_ERRNNIL, &&L_OP_ERRNNIL,
&&L_OP_VARARGPREP, &&L_OP_VARARGPREP,

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, 1, 0, 0, ivABC) /* OP_SETLIST */
,opmode(0, 0, 0, 0, 1, iABx) /* OP_CLOSURE */ ,opmode(0, 0, 0, 0, 1, iABx) /* OP_CLOSURE */
,opmode(0, 1, 0, 0, 1, iABC) /* OP_VARARG */ ,opmode(0, 1, 0, 0, 1, iABC) /* OP_VARARG */
,opmode(0, 0, 0, 0, 1, iABC) /* OP_LUSH */
,opmode(0, 0, 0, 0, 1, iABC) /* OP_GETVARG */ ,opmode(0, 0, 0, 0, 1, iABC) /* OP_GETVARG */
,opmode(0, 0, 0, 0, 0, iABx) /* OP_ERRNNIL */ ,opmode(0, 0, 0, 0, 0, iABx) /* OP_ERRNNIL */
,opmode(0, 0, 1, 0, 1, iABC) /* OP_VARARGPREP */ ,opmode(0, 0, 1, 0, 1, iABC) /* OP_VARARGPREP */

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_VARARG,/* A C R[A], R[A+1], ..., R[A+C-2] = vararg */
OP_LUSH,/* A B R[A] := lush_func[B] */
OP_GETVARG, /* A B C R[A] := R[B][R[C]], R[B] is vararg parameter */ OP_GETVARG, /* A B C R[A] := R[B][R[C]], R[B] is vararg parameter */
OP_ERRNNIL,/* A Bx raise error if R[A] ~= nil (K[Bx] is global name)*/ OP_ERRNNIL,/* A Bx raise error if R[A] ~= nil (K[Bx] is global name)*/

View File

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

View File

@@ -501,15 +501,14 @@ static void singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) {
/* /*
** Look up a lush shell function (e.g. __command) that is registered ** Emit OP_LUSH to load a shell C function into a register.
** as an upvalue on the main chunk. singlevaraux will find it directly ** 'lushop' selects which function (LUSH_OP_COMMAND, etc.).
** as VUPVAL, emitting OP_GETUPVAL instead of OP_GETTABUP.
*/ */
static void buildlushvar (LexState *ls, TString *varname, expdesc *var) { static void codelushfunc (FuncState *fs, int lushop, expdesc *var) {
FuncState *fs = ls->fs; int reg = fs->freereg;
init_exp(var, VGLOBAL, -1); luaK_reserveregs(fs, 1);
singlevaraux(fs, varname, var, 1); init_exp(var, VNONRELOC, reg);
lua_assert(var->k == VUPVAL); /* must be found as upvalue */ luaK_codeABC(fs, OP_LUSH, reg, lushop, 0);
} }
@@ -564,11 +563,8 @@ static void codecommand (LexState *ls, expdesc *v, expdesc *cmdstr) {
FuncState *fs = ls->fs; FuncState *fs = ls->fs;
int base, line; int base, line;
expdesc func; expdesc func;
TString *cmdname = luaX_newstring(ls, "__command", 9);
line = ls->linenumber; line = ls->linenumber;
/* look up __command as upvalue */ codelushfunc(fs, LUSH_OP_COMMAND, &func);
buildlushvar(ls, cmdname, &func);
luaK_exp2nextreg(fs, &func);
base = func.u.info; base = func.u.info;
/* push the command string argument */ /* push the command string argument */
luaK_exp2nextreg(fs, cmdstr); luaK_exp2nextreg(fs, cmdstr);
@@ -1254,11 +1250,9 @@ static void commandexp (LexState *ls, expdesc *v) {
expdesc func, cmdstr; expdesc func, cmdstr;
int base, nconcat = 0; int base, nconcat = 0;
int line = ls->linenumber; int line = ls->linenumber;
TString *cmdname = luaX_newstring(ls, "__command", 9);
TString *tsname = luaX_newstring(ls, "tostring", 8); TString *tsname = luaX_newstring(ls, "tostring", 8);
/* load __command function first so it occupies the base register */ /* load __command function first so it occupies the base register */
buildlushvar(ls, cmdname, &func); codelushfunc(fs, LUSH_OP_COMMAND, &func);
luaK_exp2nextreg(fs, &func);
base = func.u.info; base = func.u.info;
/* load the first fragment */ /* load the first fragment */
codestring(&cmdstr, ls->t.seminfo.ts); codestring(&cmdstr, ls->t.seminfo.ts);
@@ -1320,10 +1314,8 @@ static void codeenvget (LexState *ls, expdesc *v, TString *name) {
FuncState *fs = ls->fs; FuncState *fs = ls->fs;
int base, line; int base, line;
expdesc func, arg; expdesc func, arg;
TString *fname = luaX_newstring(ls, "__getenv", 8);
line = ls->linenumber; line = ls->linenumber;
buildlushvar(ls, fname, &func); codelushfunc(fs, LUSH_OP_GETENV, &func);
luaK_exp2nextreg(fs, &func);
base = func.u.info; base = func.u.info;
codestring(&arg, name); codestring(&arg, name);
luaK_exp2nextreg(fs, &arg); luaK_exp2nextreg(fs, &arg);
@@ -2202,13 +2194,10 @@ static void envstat (LexState *ls) {
TString *name = ls->t.seminfo.ts; TString *name = ls->t.seminfo.ts;
int base, line; int base, line;
expdesc func, arg, val; expdesc func, arg, val;
TString *fname;
line = ls->linenumber; line = ls->linenumber;
luaX_next(ls); /* skip TK_ENVVAR */ luaX_next(ls); /* skip TK_ENVVAR */
checknext(ls, '='); checknext(ls, '=');
fname = luaX_newstring(ls, "__setenv", 8); codelushfunc(fs, LUSH_OP_SETENV, &func);
buildlushvar(ls, fname, &func);
luaK_exp2nextreg(fs, &func);
base = func.u.info; base = func.u.info;
codestring(&arg, name); codestring(&arg, name);
luaK_exp2nextreg(fs, &arg); luaK_exp2nextreg(fs, &arg);
@@ -2231,10 +2220,8 @@ static void interactivestat (LexState *ls) {
/* simple interactive command, no interpolation */ /* simple interactive command, no interpolation */
int base, line; int base, line;
expdesc func, cmdstr, v; expdesc func, cmdstr, v;
TString *fname = luaX_newstring(ls, "__interactive", 13);
line = ls->linenumber; line = ls->linenumber;
buildlushvar(ls, fname, &func); codelushfunc(fs, LUSH_OP_INTERACTIVE, &func);
luaK_exp2nextreg(fs, &func);
base = func.u.info; base = func.u.info;
codestring(&cmdstr, ls->t.seminfo.ts); codestring(&cmdstr, ls->t.seminfo.ts);
luaK_exp2nextreg(fs, &cmdstr); luaK_exp2nextreg(fs, &cmdstr);
@@ -2248,10 +2235,8 @@ static void interactivestat (LexState *ls) {
expdesc func, cmdstr, v; expdesc func, cmdstr, v;
int base, nconcat = 0; int base, nconcat = 0;
int line = ls->linenumber; int line = ls->linenumber;
TString *fname = luaX_newstring(ls, "__interactive", 13);
TString *tsname = luaX_newstring(ls, "tostring", 8); TString *tsname = luaX_newstring(ls, "tostring", 8);
buildlushvar(ls, fname, &func); codelushfunc(fs, LUSH_OP_INTERACTIVE, &func);
luaK_exp2nextreg(fs, &func);
base = func.u.info; base = func.u.info;
/* load the first fragment */ /* load the first fragment */
codestring(&cmdstr, ls->t.seminfo.ts); codestring(&cmdstr, ls->t.seminfo.ts);
@@ -2410,12 +2395,8 @@ static void statement (LexState *ls) {
** upvalue named LUA_ENV ** upvalue named LUA_ENV
*/ */
static void mainfunc (LexState *ls, FuncState *fs) { static void mainfunc (LexState *ls, FuncState *fs) {
static const char *const lush_upval_names[] = {
"__command", "__interactive", "__getenv", "__setenv"
};
BlockCnt bl; BlockCnt bl;
Upvaldesc *env; Upvaldesc *env;
int i;
open_func(ls, fs, &bl); open_func(ls, fs, &bl);
setvararg(fs, PF_ISVARARG); /* main function is always vararg */ setvararg(fs, PF_ISVARARG); /* main function is always vararg */
env = allocupvalue(fs); /* ...set environment upvalue */ env = allocupvalue(fs); /* ...set environment upvalue */
@@ -2424,15 +2405,6 @@ static void mainfunc (LexState *ls, FuncState *fs) {
env->kind = VDKREG; env->kind = VDKREG;
env->name = ls->envn; env->name = ls->envn;
luaC_objbarrier(ls->L, fs->f, env->name); luaC_objbarrier(ls->L, fs->f, env->name);
/* allocate upvalues for lush shell functions */
for (i = 0; i < 4; i++) {
Upvaldesc *uv = allocupvalue(fs);
uv->instack = 1;
uv->idx = 0;
uv->kind = VDKREG;
uv->name = luaS_new(ls->L, lush_upval_names[i]);
luaC_objbarrier(ls->L, fs->f, uv->name);
}
luaX_next(ls); /* read first token */ luaX_next(ls); /* read first token */
statlist(ls); /* parse main body */ statlist(ls); /* parse main body */
check(ls, TK_EOS); check(ls, TK_EOS);
@@ -2444,7 +2416,7 @@ LClosure *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff,
Dyndata *dyd, const char *name, int firstchar) { Dyndata *dyd, const char *name, int firstchar) {
LexState lexstate; LexState lexstate;
FuncState funcstate; FuncState funcstate;
LClosure *cl = luaF_newLclosure(L, LUSH_NUM_UPVALS); /* create main closure */ LClosure *cl = luaF_newLclosure(L, 1); /* create main closure */
setclLvalue2s(L, L->top.p, cl); /* anchor it (to avoid being collected) */ setclLvalue2s(L, L->top.p, cl); /* anchor it (to avoid being collected) */
luaD_inctop(L); luaD_inctop(L);
lexstate.h = luaH_new(L); /* create table for scanner */ lexstate.h = luaH_new(L); /* create table for scanner */
@@ -2459,7 +2431,7 @@ LClosure *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff,
dyd->actvar.n = dyd->gt.n = dyd->label.n = 0; dyd->actvar.n = dyd->gt.n = dyd->label.n = 0;
luaX_setinput(L, &lexstate, z, funcstate.f->source, firstchar); luaX_setinput(L, &lexstate, z, funcstate.f->source, firstchar);
mainfunc(&lexstate, &funcstate); mainfunc(&lexstate, &funcstate);
lua_assert(!funcstate.prev && funcstate.nups == LUSH_NUM_UPVALS && !lexstate.fs); lua_assert(!funcstate.prev && funcstate.nups == 1 && !lexstate.fs);
/* all scopes should be correctly finished */ /* all scopes should be correctly finished */
lua_assert(dyd->actvar.n == 0 && dyd->gt.n == 0 && dyd->label.n == 0); lua_assert(dyd->actvar.n == 0 && dyd->gt.n == 0 && dyd->label.n == 0);
L->top.p--; /* remove scanner's table */ L->top.p--; /* remove scanner's table */

13
lvm.c
View File

@@ -1939,6 +1939,19 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
Protect(luaT_getvarargs(L, ci, ra, n)); Protect(luaT_getvarargs(L, ci, ra, n));
vmbreak; vmbreak;
} }
vmcase(OP_LUSH) {
StkId ra = RA(i);
int idx = GETARG_B(i);
Table *regt = hvalue(&G(L)->l_registry);
TValue lushtv;
lu_byte tag = luaH_getint(regt, LUA_RIDX_LUSH, &lushtv);
if (novariant(tag) == LUA_TTABLE) {
TValue func;
luaH_getint(hvalue(&lushtv), idx + 1, &func); /* 1-based */
setobj2s(L, ra, &func);
}
vmbreak;
}
vmcase(OP_GETVARG) { vmcase(OP_GETVARG) {
StkId ra = RA(i); StkId ra = RA(i);
TValue *rc = vRC(i); TValue *rc = vRC(i);