-
Notifications
You must be signed in to change notification settings - Fork 41
添加可选链操作符支持 (?. ?: ?[ ?():支持多返回值,新增 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
13
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
13 commits
Select commit
Hold shift + click to select a range
e907c33
添加可选链操作符支持 (?. ?: ?[ ?(,由 BEE_OPTCHAIN 宏启用)
sumneko 6d439db
fix: 修正 optchain 补丁源路径(目录为 lua55/lua54,而非 55/54)
sumneko 03cca5d
诊断完全忽略新的测试文件
sumneko 2b799ee
feat(optchain): 新增 OP_SETTOP 指令,多值短路精确设置栈顶
sumneko 081f0dd
跳过诊断使用完整路径
sumneko 6edc608
fix(optchain): 用 luaK_reserveregs 分配 nil 临时寄存器
sumneko d4d7b5b
test(optchain): 补充带参数的可选调用测试用例
sumneko 6adf8c6
refactor(optchain): 补丁直接写入官方源码,删除整文件复制
sumneko 7041f7e
refactor(optchain): 改为 git diff 补丁 + 构建时 apply
sumneko 6a42cd4
fix(optchain): source_set 用 objdeps 保证 apply_patch 先于 obj 编译
sumneko 0a49f07
refactor(optchain): 用 luamake 依赖规则重写补丁构建,消除硬编码
sumneko 5e85bdf
refactor(optchain): 按 review 意见简化补丁脚本与构建配置
sumneko 61c5007
refactor(optchain): 自查清理:精确依赖、去死代码
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,36 @@ | ||
| -- 将源文件复制到构建目录,并应用 git 补丁。 | ||
| -- 用法: apply_patch.lua <src_dir> <dst_dir> <patch_file> [file...] | ||
| -- | ||
| -- 把 file... 指定的文件从 src_dir 复制到 dst_dir(父目录不存在则创建), | ||
| -- 然后在 dst_dir 上应用补丁。每次都从 src_dir 重新复制,因此脚本幂等, | ||
| -- 无需清理 dst_dir;内容没有变化的文件不会被重写(保持 mtime 稳定, | ||
| -- 配合构建规则的 restat 支持增量编译)。 | ||
| local fs = require "bee.filesystem" | ||
|
|
||
| local src, dst, patch = ... | ||
| assert(src and dst and patch, "usage: apply_patch.lua <src_dir> <dst_dir> <patch_file> [file...]") | ||
|
|
||
| local function read_file(path) | ||
| local f <close> = io.open(path, "rb") | ||
| if not f then | ||
| return nil | ||
| end | ||
| return f:read "a" | ||
| end | ||
|
|
||
| fs.create_directories(dst) | ||
| for i = 4, select("#", ...) do | ||
| local file = select(i, ...) | ||
| local new = assert(read_file(src .. "/" .. file), "file not found: " .. file) | ||
| if read_file(dst .. "/" .. file) ~= new then | ||
| local parent = file:match "^(.*)/[^/]+$" | ||
| if parent then | ||
| fs.create_directories(dst .. "/" .. parent) | ||
| end | ||
| local f <close> = assert(io.open(dst .. "/" .. file, "wb")) | ||
| f:write(new) | ||
| end | ||
| end | ||
|
|
||
| local ok = os.execute(('git apply --directory="%s" "%s"'):format(dst, patch)) | ||
| assert(ok, "git apply failed: " .. patch) |
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.