Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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