-
Notifications
You must be signed in to change notification settings - Fork 40
添加可选链操作符支持 (?. ?: ?[ ?():支持多返回值,新增 OP_SETTOP 指令,由 BEE_OPTCHAIN 宏启用 #67
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
sumneko
wants to merge
9
commits into
master
Choose a base branch
from
feature/optional-chaining
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
d8d8919
添加可选链操作符支持 (?. ?: ?[ ?(,由 BEE_OPTCHAIN 宏启用)
sumneko 9c09152
fix: 修正 optchain 补丁源路径(目录为 lua55/lua54,而非 55/54)
sumneko 75a05b4
诊断完全忽略新的测试文件
sumneko 6b4d2e6
feat(optchain): 新增 OP_SETTOP 指令,多值短路精确设置栈顶
sumneko 91cf01f
跳过诊断使用完整路径
sumneko df0dcb1
fix(optchain): 用 luaK_reserveregs 分配 nil 临时寄存器
sumneko 99bdce7
test(optchain): 补充带参数的可选调用测试用例
sumneko 4390940
refactor(optchain): 补丁直接写入官方源码,删除整文件复制
sumneko 320a2f7
refactor(optchain): 改为 git diff 补丁 + 构建时 apply
sumneko File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.