Skip to content
Open
9 changes: 7 additions & 2 deletions .luarc.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@
"code-after-break": "Warning",
"empty-block": "Warning",
"trailing-space": "Warning"
}
},
"ignoredFiles": "Disable"
},
"workspace.checkThirdParty": false
"workspace.checkThirdParty": false,
"workspace.ignoreDir": [
".vscode",
"test/test_optional_chain.lua"
]
Comment thread
Copilot marked this conversation as resolved.
}
29 changes: 29 additions & 0 deletions 3rd/lua-patch/apply_patch.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
-- 3rd/lua-patch/apply_patch.lua
-- 复制官方 Lua 源码目录到构建目录,并应用可选链补丁(git apply)。
-- 用法: apply_patch.lua <src_dir> <patch_file> <dst_dir>
local src, patch, dst = ...
assert(src and patch and dst, "usage: apply_patch.lua <src_dir> <patch_file> <dst_dir>")

local is_windows = package.config:sub(1, 1) == "\\"

-- 清空并重建目标目录
if is_windows then
os.execute(('if exist "%s" rmdir /s /q "%s"'):format(dst, dst))
os.execute(('mkdir "%s"'):format(dst))
else
os.execute(('rm -rf "%s"'):format(dst))
os.execute(('mkdir -p "%s"'):format(dst))
end

-- 复制官方源码目录(保持完整,编译时 include 自包含)
if is_windows then
os.execute(('xcopy /e /i /y /q "%s" "%s"'):format(src, dst))
else
os.execute(('cp -r "%s/." "%s/"'):format(src, dst))
end

-- 应用补丁(patch 内为相对路径,--directory 指定目标目录)
local dst_str = dst:gsub("\\", "/")
local ok = os.execute(('git apply --directory="%s" "%s"'):format(dst_str, patch))
assert(ok, "git apply failed for " .. patch)
print("apply_patch: OK")
252 changes: 252 additions & 0 deletions 3rd/lua-patch/optchain/lua54.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
diff --git a/lopcodes.c b/lopcodes.c
index c67aa22..0db8fed 100644
--- a/lopcodes.c
+++ b/lopcodes.c
@@ -100,5 +100,8 @@ LUAI_DDEF const lu_byte luaP_opmodes[NUM_OPCODES] = {
,opmode(0, 1, 0, 0, 1, iABC) /* OP_VARARG */
,opmode(0, 0, 1, 0, 1, iABC) /* OP_VARARGPREP */
,opmode(0, 0, 0, 0, 0, iAx) /* OP_EXTRAARG */
+#if defined(BEE_OPTCHAIN)
+ ,opmode(0, 0, 0, 0, 1, iABC) /* OP_SETTOP */
+#endif
};

diff --git a/lopcodes.h b/lopcodes.h
index 46911ca..4a4c84c 100644
--- a/lopcodes.h
+++ b/lopcodes.h
@@ -307,10 +307,17 @@ OP_VARARG,/* A C R[A], R[A+1], ..., R[A+C-2] = vararg */
OP_VARARGPREP,/*A (adjust vararg parameters) */

OP_EXTRAARG/* Ax extra (larger) argument for previous opcode */
+#if defined(BEE_OPTCHAIN)
+,OP_SETTOP/* A B R[A], ..., R[A+B] := nil; top := A+B+1 */
+#endif
} OpCode;


+#if defined(BEE_OPTCHAIN)
+#define NUM_OPCODES ((int)(OP_SETTOP) + 1)
+#else
#define NUM_OPCODES ((int)(OP_EXTRAARG) + 1)
+#endif



diff --git a/lopnames.h b/lopnames.h
index 965cec9..44843b1 100644
--- a/lopnames.h
+++ b/lopnames.h
@@ -96,6 +96,9 @@ static const char *const opnames[] = {
"VARARG",
"VARARGPREP",
"EXTRAARG",
+#if defined(BEE_OPTCHAIN)
+ "SETTOP",
+#endif
NULL
};

diff --git a/lparser.c b/lparser.c
index eed008c..9b90ebd 100644
--- a/lparser.c
+++ b/lparser.c
@@ -37,6 +37,29 @@

#define hasmultret(k) ((k) == VCALL || (k) == VVARARG)

+#if defined(BEE_OPTCHAIN)
+/*
+** Patch the short-circuit path of an optional-chain call so that it
+** produces 'nresults' nil values instead of a single one. This allows
+** chains ending in a call to yield multiple results (e.g.
+** 'local a, b = f?()'). The short-circuit path is a fixed layout right
+** after the call (see suffixedexp): CALL (with 'k' flag set) / JMP /
+** OP_SETTOP. The OP_SETTOP (which also fixes the stack top; see lvm.c)
+** holds the number of nil slots minus one in its B field.
+*/
+static void luaK_setreturns_optchain (FuncState *fs, expdesc *e, int nresults) {
+ if (e->k == VCALL) { /* open function call? */
+ int pc = e->u.info; /* position of the call */
+ if (TESTARG_k(fs->f->code[pc])) { /* optional-chain call (fixed layout)? */
+ /* A fixed 'nresults' needs exactly that many nils, while
+ LUA_MULTRET needs exactly one (B stays 0). */
+ if (nresults != LUA_MULTRET) /* fixed number of results? */
+ SETARG_B(fs->f->code[pc + 2], nresults - 1); /* widen OP_SETTOP */
+ }
+ }
+}
+#endif
+

/* because all strings are unified by the scanner, the parser
can use pointer equality for string equality */
@@ -486,6 +509,9 @@ static void adjust_assign (LexState *ls, int nvars, int nexps, expdesc *e) {
int extra = needed + 1; /* discount last expression itself */
if (extra < 0)
extra = 0;
+#if defined(BEE_OPTCHAIN)
+ luaK_setreturns_optchain(fs, e, extra);
+#endif
luaK_setreturns(fs, e, extra); /* last exp. provides the difference */
}
else {
@@ -879,6 +905,9 @@ static void closelistfield (FuncState *fs, ConsControl *cc) {
static void lastlistfield (FuncState *fs, ConsControl *cc) {
if (cc->tostore == 0) return;
if (hasmultret(cc->v.k)) {
+#if defined(BEE_OPTCHAIN)
+ luaK_setreturns_optchain(fs, &cc->v, LUA_MULTRET);
+#endif
luaK_setmultret(fs, &cc->v);
luaK_setlist(fs, cc->t->u.info, cc->na, LUA_MULTRET);
cc->na--; /* do not count last expression (unknown number of elements) */
@@ -1035,8 +1064,12 @@ static void funcargs (LexState *ls, expdesc *f) {
args.k = VVOID;
else {
explist(ls, &args);
- if (hasmultret(args.k))
+ if (hasmultret(args.k)) {
+#if defined(BEE_OPTCHAIN)
+ luaK_setreturns_optchain(fs, &args, LUA_MULTRET);
+#endif
luaK_setmultret(fs, &args);
+ }
}
check_match(ls, ')', '(', line);
break;
@@ -1105,9 +1138,33 @@ static void suffixedexp (LexState *ls, expdesc *v) {
/* suffixedexp ->
primaryexp { '.' NAME | '[' exp ']' | ':' NAME funcargs | funcargs } */
FuncState *fs = ls->fs;
+#if defined(BEE_OPTCHAIN)
+ int niljumps = NO_JUMP; /* patch list of optional-chain exits (nil) */
+ int chain_base = fs->freereg; /* result register of the chain */
+#endif
primaryexp(ls, v);
for (;;) {
switch (ls->t.token) {
+#if defined(BEE_OPTCHAIN)
+ case '?': { /* optional chain: '?.' '?:' '?[' '?(' */
+ int reg, nilreg;
+ luaX_next(ls); /* consume '?' */
+ if (ls->t.token != '.' && ls->t.token != ':' &&
+ ls->t.token != '[' && ls->t.token != '(')
+ luaX_syntaxerror(ls, "unexpected symbol near '?'");
+ reg = luaK_exp2anyreg(fs, v); /* evaluate receiver only once */
+ nilreg = fs->freereg;
+ luaK_nil(fs, nilreg, 1); /* ensure it is nil at runtime */
+ /* Reserve the temp through luaK_reserveregs (which also grows
+ 'maxstacksize') instead of a raw freereg++ that would bypass
+ luaK_checkstack and leave 'maxstacksize' stale. */
+ luaK_reserveregs(fs, 1);
+ luaK_codeABCk(fs, OP_EQ, reg, nilreg, 0, 1); /* jump when nil */
+ luaK_concat(fs, &niljumps, luaK_jump(fs));
+ fs->freereg--; /* release the nil slot; only used by OP_EQ */
+ break; /* next iteration handles '.' ':' '[' '(' */
+ }
+#endif
case '.': { /* fieldsel */
fieldsel(ls, v);
break;
@@ -1132,7 +1189,50 @@ static void suffixedexp (LexState *ls, expdesc *v) {
funcargs(ls, v);
break;
}
- default: return;
+ default:
+#if defined(BEE_OPTCHAIN)
+ if (niljumps != NO_JUMP) {
+ int skip, nilpc;
+ if (v->k == VCALL) {
+ /* A chain ending in a call can yield multiple results: keep
+ the call open instead of collapsing it to a single value.
+ We emit a fixed layout so that later consumers can patch
+ the short-circuit path without storing extra info in 'e':
+ CALL base ... (with the 'k' flag set, marking the chain)
+ JMP skip
+ OP_SETTOP base 0 (fills 1 nil and fixes the stack top)
+ skip:
+ The OP_SETTOP is always at call+2; luaK_setreturns_optchain
+ widens its B field when more nils are needed. 'e' keeps the
+ plain VCALL semantics (t/f stay NO_JUMP), so single-value
+ consumers work unchanged. */
+ int base = GETARG_A(fs->f->code[v->u.info]); /* call base */
+ SETARG_k(fs->f->code[v->u.info], 1); /* mark optional chain */
+ skip = luaK_jump(fs); /* non-nil path skips the nil fill */
+ nilpc = luaK_codeABC(fs, OP_SETTOP, base, 0, 0);
+ luaK_patchtohere(fs, skip);
+ fs->freereg = base + 1;
+ luaK_patchlist(fs, niljumps, nilpc); /* nil exits jump here */
+ }
+ else {
+ int r;
+ if (vkisindexed(v->k))
+ luaK_exp2anyreg(fs, v); /* make it a value, not a var */
+ r = v->u.info; /* now a VNONRELOC register */
+ if (r != chain_base) { /* move result to the chain base register */
+ luaK_codeABC(fs, OP_MOVE, chain_base, r, 0);
+ v->u.info = chain_base;
+ v->k = VNONRELOC;
+ }
+ skip = luaK_jump(fs); /* non-nil path skips the nil fill */
+ nilpc = luaK_codeABC(fs, OP_LOADNIL, chain_base, 0, 0);
+ luaK_patchtohere(fs, skip);
+ fs->freereg = chain_base + 1; /* free chain temporaries */
+ luaK_patchlist(fs, niljumps, nilpc); /* nil exits jump to LOADNIL */
+ }
+ }
+#endif
+ return;
}
}
}
@@ -1822,9 +1922,16 @@ static void retstat (LexState *ls) {
else {
nret = explist(ls, &e); /* optional return values */
if (hasmultret(e.k)) {
+#if defined(BEE_OPTCHAIN)
+ luaK_setreturns_optchain(fs, &e, LUA_MULTRET);
+#endif
luaK_setmultret(fs, &e);
#if defined(NDEBUG)
- if (e.k == VCALL && nret == 1 && !fs->bl->insidetbc) { /* tail call? */
+ if (e.k == VCALL && nret == 1 && !fs->bl->insidetbc
+#if defined(BEE_OPTCHAIN)
+ && !TESTARG_k(fs->f->code[e.u.info]) /* no tail call for optional-chain calls */
+#endif
+ ) { /* tail call? */
SET_OPCODE(getinstruction(fs,&e), OP_TAILCALL);
lua_assert(GETARG_A(getinstruction(fs,&e)) == luaY_nvarstack(fs));
}
@@ -1967,4 +2074,3 @@ LClosure *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff,
L->top.p--; /* remove scanner's table */
return cl; /* closure is on the stack, too */
}
-
diff --git a/lvm.c b/lvm.c
index 7023a04..57dcd17 100644
--- a/lvm.c
+++ b/lvm.c
@@ -1236,6 +1236,21 @@ void luaV_execute (lua_State *L, CallInfo *ci) {
} while (b--);
vmbreak;
}
+#if defined(BEE_OPTCHAIN)
+ vmcase(OP_SETTOP) {
+ /* Optional-chain short circuit: fill R[A..A+B] with nils and
+ also fix the stack top so that open instructions (OP_RETURN,
+ OP_CALL, OP_SETLIST) read exactly the nils produced here.
+ (Only the optional-chain compiler emits this instruction.) */
+ StkId ra = RA(i);
+ int b = GETARG_B(i);
+ do {
+ setnilvalue(s2v(ra++));
+ } while (b--);
+ L->top.p = RA(i) + GETARG_B(i) + 1;
+ vmbreak;
+ }
+#endif
vmcase(OP_GETUPVAL) {
StkId ra = RA(i);
int b = GETARG_B(i);
Loading
Loading