From ab7a7e81f37effab9065813963c065522a5471c2 Mon Sep 17 00:00:00 2001 From: esrrhs Date: Wed, 11 Nov 2026 00:25:20 +0800 Subject: [PATCH] perf(interp): faster named calls and looped tail calls Exact-arity INTERP-to-INTERP named and closure calls go through InterpreterExecute without copying VmFunction or entering GuardJitEntry. CALLNAME uses string_view on the success path. return f(...) emits TAIL/TAILNAME. Common exact-arity no-box self tails reuse the current frame in place; mutual, vararg, Multi, and cross-template cases use a generic fallback and a TLS trampoline so C-stack depth stays one. Tests cover self, mutual, vararg, upvalue, multi-return, deep tails, and error locations. --- src/interp/codegen.cpp | 48 ++- src/interp/codegen.h | 2 +- src/interp/interpreter.cpp | 382 +++++++++++++++++- src/interp/opcode.h | 6 + src/jit/vm.cpp | 33 +- src/jit/vm.h | 17 +- test/lua/closure/test_closure_tail_deep.lua | 11 + test/lua/closure/test_closure_tail_mutual.lua | 18 + test/lua/closure/test_closure_tail_self.lua | 10 + .../lua/closure/test_closure_tail_upvalue.lua | 22 + test/lua/closure/test_closure_tail_values.lua | 16 + test/lua/closure/test_closure_tail_vararg.lua | 22 + .../exception/test_error_location_tail.lua | 16 + test/test_closure.cpp | 60 +++ test/test_exception.cpp | 49 +++ 15 files changed, 678 insertions(+), 34 deletions(-) create mode 100644 test/lua/closure/test_closure_tail_deep.lua create mode 100644 test/lua/closure/test_closure_tail_mutual.lua create mode 100644 test/lua/closure/test_closure_tail_self.lua create mode 100644 test/lua/closure/test_closure_tail_upvalue.lua create mode 100644 test/lua/closure/test_closure_tail_values.lua create mode 100644 test/lua/closure/test_closure_tail_vararg.lua create mode 100644 test/lua/exception/test_error_location_tail.lua diff --git a/src/interp/codegen.cpp b/src/interp/codegen.cpp index 3ae24fed..61bd7b14 100644 --- a/src/interp/codegen.cpp +++ b/src/interp/codegen.cpp @@ -825,6 +825,13 @@ void InterpCodegen::CompileStmtReturn(const SyntaxTreeInterfacePtr &stmt) { return; } if (exps.size() == 1) { + // Lua 语义:`return f(...)` 是尾调用;`return (f(...))` 不是(括号是 kExp)。 + if (IsFunctionCallExp(exps[0])) { + const auto e = std::dynamic_pointer_cast(exps[0]); + const auto pe = std::dynamic_pointer_cast(e->Right()); + CompileFunctioncall(pe->GetValue(), true, true); + return; + } const int r = CompileExp(exps[0], true); Emit(Op::RETURN, r, 0); return; @@ -1546,7 +1553,7 @@ void InterpCodegen::EmitCallArgs(const std::shared_ptr &args_ptr } } -int InterpCodegen::CompileFunctioncall(const SyntaxTreeInterfacePtr &functioncall, bool preserve_multi) { +int InterpCodegen::CompileFunctioncall(const SyntaxTreeInterfacePtr &functioncall, bool preserve_multi, bool is_tail) { const auto fc = std::dynamic_pointer_cast(functioncall); const auto args_ptr = std::dynamic_pointer_cast(fc->Args()); const auto pe_pre_ptr = std::dynamic_pointer_cast(fc->prefixexp()); @@ -1554,6 +1561,26 @@ int InterpCodegen::CompileFunctioncall(const SyntaxTreeInterfacePtr &functioncal std::vector arg_regs; bool last_expand = false; + auto self_tail_c = [&](int n, bool named, const std::string &name, bool self_closure) -> int { + if (!is_tail || last_expand || !cur_func_ || cur_func_->is_vararg || !cur_func_->proto) { + return 0; + } + if (n != cur_func_->proto->param_count) { + return 0; + } + if (self_closure) { + return kTailSelfExact; + } + if (named && !name.empty()) { + if (name == cur_func_->name || name == cur_func_->unique_c_name) { + return kTailSelfExact; + } + if (!cur_package_name_.empty() && !cur_func_->name.empty() && name == cur_package_name_ + "." + cur_func_->name) { + return kTailSelfExact; + } + } + return 0; + }; auto place_callname = [&](const std::string &name, const std::vector &args) { const int n = static_cast(args.size()); // 末尾是 f() / ... 这类展开时实参个数运行期才知道,检查不了。 @@ -1576,11 +1603,12 @@ int InterpCodegen::CompileFunctioncall(const SyntaxTreeInterfacePtr &functioncal } stack_top_ = base + n; } - Emit(Op::CALLNAME, base, n, unbox, AddStringConst(name)); + const int c = is_tail ? self_tail_c(n, true, name, false) : unbox; + Emit(is_tail ? Op::TAILNAME : Op::CALLNAME, base, n, c, AddStringConst(name)); stack_top_ = base + 1; return base; }; - auto place_call = [&](int fn, const std::vector &args) { + auto place_call = [&](int fn, const std::vector &args, bool self_closure = false) { const int n = static_cast(args.size()); const int base = stack_top_; EnsureStack(base + 1 + n); @@ -1589,7 +1617,8 @@ int InterpCodegen::CompileFunctioncall(const SyntaxTreeInterfacePtr &functioncal MoveTo(base + 1 + i, args[i]); } stack_top_ = base + 1 + n; - Emit(Op::CALL, base, n, unbox); + const int c = is_tail ? self_tail_c(n, false, {}, self_closure) : unbox; + Emit(is_tail ? Op::TAIL : Op::CALL, base, n, c); stack_top_ = base + 1; return base; }; @@ -1605,6 +1634,9 @@ int InterpCodegen::CompileFunctioncall(const SyntaxTreeInterfacePtr &functioncal Emit(Op::SETTABLE, t, k, val); const int r = AllocReg(); Emit(Op::LOADNIL, r); + if (is_tail) { + Emit(Op::RETURN, r, 1); + } return r; } } @@ -1681,7 +1713,13 @@ int InterpCodegen::CompileFunctioncall(const SyntaxTreeInterfacePtr &functioncal } else { fn = CompilePrefixexp(fc->prefixexp(), false); } - return place_call(fn, arg_regs); + bool self_closure = false; + if (is_tail && is_local_callee && var_ptr && cur_func_ && cur_func_->node) { + if (const auto it = var_to_def_map_.find(var_ptr); it != var_to_def_map_.end() && it->second->def_node == cur_func_->node) { + self_closure = true; + } + } + return place_call(fn, arg_regs, self_closure); } int InterpCodegen::CompileTableconstructor(const SyntaxTreeInterfacePtr &tc) { diff --git a/src/interp/codegen.h b/src/interp/codegen.h index 6b0bdd3e..a491a37a 100644 --- a/src/interp/codegen.h +++ b/src/interp/codegen.h @@ -87,7 +87,7 @@ class InterpCodegen { int CompileExp(const SyntaxTreeInterfacePtr &exp, bool preserve_multi = false, int dest = -1); int CompilePrefixexp(const SyntaxTreeInterfacePtr &pe, bool preserve_multi, int dest = -1); int CompileVar(const SyntaxTreeInterfacePtr &v); - int CompileFunctioncall(const SyntaxTreeInterfacePtr &functioncall, bool preserve_multi); + int CompileFunctioncall(const SyntaxTreeInterfacePtr &functioncall, bool preserve_multi, bool is_tail = false); int CompileTableconstructor(const SyntaxTreeInterfacePtr &tc); int CompileBinop(const SyntaxTreeInterfacePtr &exp, int dest = -1); int CompileUnop(const SyntaxTreeInterfacePtr &exp, int dest = -1); diff --git a/src/interp/interpreter.cpp b/src/interp/interpreter.cpp index 1fbc6f22..182ab235 100644 --- a/src/interp/interpreter.cpp +++ b/src/interp/interpreter.cpp @@ -7,6 +7,7 @@ #include "state/const_string.h" #include "state/state.h" #include "util/exception.h" +#include "util/macro.h" #include "var/var.h" #include "var/var_closure.h" #include "var/var_multi.h" @@ -77,6 +78,318 @@ void AnnotateInterpLocation(FuncProto *proto, const Inst *ip, FakeluaException & e.SetLoc(std::format("{}:{}", proto->unit->file_name, line)); } +// 解释器帧内再调解释器:当前引擎通常已是 INTERP,错误位置走 lineinfo, +// 不必再套 DispatchCall 的 JitContextScope / LuaLocScope。若从尚未切换引擎 +// 的入口进来(例如 init),先把 CurrentJit 设成 INTERP 再执行。 +CVar InterpExecuteInContext(State *s, FuncProto *proto, const CVar *args, int arg_count, VarClosure *cl) { + if (s->CurrentJit() != JIT_INTERP) { + State::JitContextScope jit_scope(s, JIT_INTERP); + return InterpreterExecute(s, proto, args, arg_count, cl); + } + return InterpreterExecute(s, proto, args, arg_count, cl); +} + +// 文件级 / 全局命名调用的快路径:签名匹配且目标是 INTERP 原型时直接 InterpreterExecute, +// 跳过 CallByName 的 VmFunction 拷贝、GuardJitEntry 与参数再装箱。 +// 变参、末参 Multi、参数个数不一致、原生函数或无 INTERP 地址时返回 false,由调用方走 CallByNameArgs。 +bool TryInterpNamedCall(State *s, std::string_view name, const CVar *args, int nargs, CVar &ret) { + const VmFunction *fn = s->GetVM().FindFunction(name); + if (!fn) { + return false; + } + void *addr = fn->GetAddr(JIT_INTERP); + if (!addr || fn->IsVararg()) { + return false; + } + const int expected = fn->GetArgCount(); + const bool last_is_multi = (nargs > 0 && args[nargs - 1].type_ == static_cast(VarType::Multi)); + if (last_is_multi || nargs != expected) { + return false; + } + ret = InterpExecuteInContext(s, AsInterpProto(addr), args, nargs, nullptr); + return true; +} + +// 尾调用跨 InterpreterExecuteT 模板(无 boxes 的帧尾调到 uses_boxes 函数) +// 或需要在根帧重新 reserve 时,通过此槽把目标原型交还给 InterpreterExecute 的外层循环。 +// 不增加 C 栈深度。 +struct InterpTailJob { + bool pending = false; + FuncProto *proto = nullptr; + VarClosure *cl = nullptr; + int arg_count = 0; + CVar args[kMaxFunctionInputParams]{}; +}; +thread_local InterpTailJob g_interp_tail; + +void RequestTailTrampoline(FuncProto *new_proto, VarClosure *new_cl, const CVar *args, int arg_count) { + g_interp_tail.pending = true; + g_interp_tail.proto = new_proto; + g_interp_tail.cl = new_cl; + g_interp_tail.arg_count = arg_count; + for (int i = 0; i < arg_count; ++i) { + g_interp_tail.args[i] = args[i]; + } +} + +int FlattenByNameArgs(const CVar *raw, int arg_num, CVar *flat) { + int flat_count = 0; + for (int i = 0; i < arg_num; ++i) { + if (i == arg_num - 1 && raw[i].type_ == static_cast(VarType::Multi)) { + VarMulti *m = raw[i].data_.m; + const int extra = m ? static_cast(m->GetCount()) : 0; + if (flat_count + extra > static_cast(kMaxFunctionInputParams)) { + ThrowFakeluaException(std::format("interpreter: too many arguments ({}), max is {}", flat_count + extra, kMaxFunctionInputParams)); + } + if (m) { + for (uint32_t j = 0; j < m->GetCount(); ++j) { + flat[flat_count++] = m->GetVars()[j]; + } + } + } else if (raw[i].type_ == static_cast(VarType::Multi)) { + VarMulti *m = raw[i].data_.m; + if (flat_count >= static_cast(kMaxFunctionInputParams)) { + ThrowFakeluaException(std::format("interpreter: too many arguments, max is {}", kMaxFunctionInputParams)); + } + flat[flat_count++] = (m && m->GetCount() > 0) ? m->GetVars()[0] : interp_rt::Nil(); + } else { + if (flat_count >= static_cast(kMaxFunctionInputParams)) { + ThrowFakeluaException(std::format("interpreter: too many arguments, max is {}", kMaxFunctionInputParams)); + } + flat[flat_count++] = raw[i]; + } + } + return flat_count; +} + +// 按闭包签名装箱(与 DispatchCallClosure 一致):变参打进最后的 Multi,缺参补 nil,多余丢掉。 +int PackClosureCallArgs(State *s, VarClosure *cl, const CVar *flat, int flat_count, CVar *packed) { + const int expected = cl->expected_arg_count; + if (expected < 0 || expected > static_cast(kMaxFunctionInputParams)) { + ThrowFakeluaException("too many arguments"); + } + if (cl->is_vararg) { + const int fixed = (expected > 0) ? (expected - 1) : 0; + for (int i = 0; i < fixed; ++i) { + packed[i] = (i < flat_count) ? flat[i] : interp_rt::Nil(); + } + const int extra = (flat_count > fixed) ? (flat_count - fixed) : 0; + CVar multi = inter::AllocMultiCVar(s, extra); + for (int i = 0; i < extra; ++i) { + inter::SetMultiCVarElement(multi, i, flat[fixed + i]); + } + packed[fixed] = multi; + return (expected > 0) ? expected : 1; + } + for (int i = 0; i < expected; ++i) { + packed[i] = (i < flat_count) ? flat[i] : interp_rt::Nil(); + } + return expected; +} + +// 按 CallByName 规则装箱:非变参且末参不是 Multi 时实参个数必须精确匹配。 +int PackNamedCallArgs(State *s, std::string_view name, bool is_vararg, int expected, const CVar *raw, int raw_n, const CVar *flat, int flat_count, CVar *packed) { + if (expected < 0 || expected > static_cast(kMaxFunctionInputParams)) { + ThrowFakeluaException(std::format("FakeluaCallByName: function '{}' declares too many parameters ({}), max is {}", name, expected, kMaxFunctionInputParams)); + } + const int fixed = is_vararg ? std::max(0, expected - 1) : expected; + const bool last_is_multi = (raw_n > 0 && raw[raw_n - 1].type_ == static_cast(VarType::Multi)); + if (is_vararg) { + for (int i = 0; i < fixed; ++i) { + packed[i] = (i < flat_count) ? flat[i] : interp_rt::Nil(); + } + const int vararg_count = std::max(0, flat_count - fixed); + CVar multi = inter::AllocMultiCVar(s, vararg_count); + for (int i = 0; i < vararg_count; ++i) { + inter::SetMultiCVarElement(multi, i, flat[fixed + i]); + } + packed[fixed] = multi; + return expected; + } + if (!last_is_multi && flat_count != expected) { + ThrowFakeluaException(std::format("FakeluaCallByName: function '{}' expects {} argument(s), got {}", name, expected, flat_count)); + } + for (int i = 0; i < expected; ++i) { + packed[i] = (i < flat_count) ? flat[i] : interp_rt::Nil(); + } + return expected; +} + +// 在本 InterpreterExecuteT 帧内切到新的 INTERP 原型;需要换 HasBoxes 模板或扩容时走 trampoline。 +// 成功且应继续分发返回 true(已更新 proto/cl/ip/stk);应 return ret 则返回 false。 +template +bool ApplyInterpTail(State *s, FuncProto *&proto, VarClosure *&cl, CVar *const stk, CVar **const boxp, size_t saved, const Inst *&ip, FuncProto *new_proto, VarClosure *new_cl, const CVar *args, int arg_count, CVar &ret) { + if (!new_proto) { + ThrowFakeluaException("interpreter: null function prototype"); + } + CVar copy[kMaxFunctionInputParams]; + for (int i = 0; i < arg_count; ++i) { + copy[i] = args[i]; + } + if (new_proto->uses_boxes && !HasBoxes) { + RequestTailTrampoline(new_proto, new_cl, copy, arg_count); + ret = {}; + return false; + } + const size_t new_slots = static_cast(std::max(new_proto->max_stack, new_proto->param_count)) + 8; + const size_t need = saved + new_slots; + // 不在本帧 realloc:父帧可能仍持有 data() 指针。容量不够就 trampoline(根帧)或报溢出。 + // 因此 stk/boxp 地址不变,热路径可以把它们当成不变指针。 + if (need > g_interp_stack.capacity()) { + if (saved == 0) { + RequestTailTrampoline(new_proto, new_cl, copy, arg_count); + ret = {}; + return false; + } + ThrowFakeluaException("interpreter stack overflow"); + } + if (HasBoxes && need > g_interp_boxes.capacity()) { + if (g_interp_boxed_frames > 1) { + ThrowFakeluaException("interpreter stack overflow"); + } + RequestTailTrampoline(new_proto, new_cl, copy, arg_count); + ret = {}; + return false; + } + if (need > g_interp_top) { + g_interp_stack.resize(need); + } + g_interp_top = need; + const int nparam = new_proto->param_count; + // 形参马上覆盖,不必先清;只清剩余寄存器,避免脏局部漏到下一帧。 + if (new_slots > static_cast(nparam)) { + std::fill(stk + nparam, stk + new_slots, CVar{}); + } + if (HasBoxes) { + if (need > g_interp_boxes.size()) { + g_interp_boxes.resize(need); + } + std::fill(boxp, boxp + new_slots, nullptr); + } + for (int i = 0; i < nparam; ++i) { + stk[static_cast(i)] = (i < arg_count) ? copy[i] : interp_rt::Nil(); + } + proto = new_proto; + cl = new_cl; + if (proto->code.empty()) { + ret = interp_rt::Nil(); + return false; + } + ip = proto->code.data(); + return true; +} + + +// 精确自尾调用:复用当前帧与 proto/cl,不做查表、装箱、整帧 memset。 +// 参数先拷到 TLS(可能与 R(0..) 重叠),再写形参并清空其余寄存器。 +// 不用 InterpreterExecuteT 的栈上大数组,以免抬高非尾调用的 C 帧。 +template +#if defined(__GNUC__) +__attribute__((always_inline)) +#endif +inline bool ReuseFrameSelfTail(FuncProto *proto, CVar *const stk, CVar **const boxp, const Inst *&ip, int arg_base, int nargs) { + CVar *const tmp = g_interp_tail.args; + for (int i = 0; i < nargs; ++i) { + tmp[i] = interp_rt::VmSlot(stk, boxp, arg_base + i); + } + const int nparam = proto->param_count; + const int live = std::max(proto->max_stack, nparam); + if (live > nparam) { + std::fill(stk + nparam, stk + live, CVar{}); + } + if (HasBoxes && boxp) { + std::fill(boxp, boxp + live, nullptr); + } + for (int i = 0; i < nargs; ++i) { + stk[static_cast(i)] = tmp[i]; + } + for (int i = nargs; i < nparam; ++i) { + stk[static_cast(i)] = interp_rt::Nil(); + } + if (proto->code.empty()) { + return false; + } + ip = proto->code.data(); + return true; +} + +// 尾调用实现单独成函数,避免把 InterpreterExecuteT 热路径胀大、扰乱寄存器分配。 +template +#if defined(__GNUC__) +__attribute__((noinline)) +#endif +bool InterpDoTail(State *s, FuncProto *&proto, VarClosure *&cl, CVar *const stk, CVar **const boxp, size_t saved, const Inst *&ip, const Inst &inst, CVar &ret) { + CVar fn = interp_rt::VmSlot(stk, boxp, inst.a); + if (fn.type_ != static_cast(VarType::Closure) || !fn.data_.cl) { + ThrowFakeluaException("attempt to call a non-function value"); + } + CVar call_args[kMaxFunctionInputParams]; + const int nargs = inst.b; + if (nargs < 0 || nargs > static_cast(kMaxFunctionInputParams)) { + ThrowFakeluaException(std::format("interpreter: too many arguments ({}), max is {}", nargs, kMaxFunctionInputParams)); + } + int nflat = 0; + for (int i = 0; i < nargs; ++i) { + CVar av = interp_rt::VmSlot(stk, boxp, inst.a + 1 + i); + if (i == nargs - 1 && av.type_ == static_cast(VarType::Multi)) { + VarMulti *m = av.data_.m; + const int extra = m ? static_cast(m->GetCount()) : 0; + if (nflat + extra > static_cast(kMaxFunctionInputParams)) { + ThrowFakeluaException(std::format("interpreter: too many arguments ({}), max is {}", nflat + extra, kMaxFunctionInputParams)); + } + for (uint32_t j = 0; j < m->GetCount(); ++j) { + call_args[nflat++] = m->GetVars()[j]; + } + } else { + call_args[nflat++] = interp_rt::UnboxMulti(av, 0); + } + } + VarClosure *callee = fn.data_.cl; + if (!IsInterpClosure(callee)) { + ret = inter::DispatchCallClosure(s, callee, call_args, nflat, JIT_INTERP); + return false; + } + CVar packed[kMaxFunctionInputParams]; + const int n_dispatch = PackClosureCallArgs(s, callee, call_args, nflat, packed); + return ApplyInterpTail(s, proto, cl, stk, boxp, saved, ip, AsInterpProto(callee->func_ptr), callee, packed, n_dispatch, ret); +} + +template +#if defined(__GNUC__) +__attribute__((noinline)) +#endif +bool InterpDoTailName(State *s, FuncProto *&proto, VarClosure *&cl, CVar *const stk, CVar **const boxp, size_t saved, const Inst *&ip, const Inst &inst, CVar &ret) { + const std::string_view name = ConstToName(proto->constants[static_cast(inst.sbx)]); + CVar raw[kMaxFunctionInputParams]; + const int nargs = inst.b; + if (nargs < 0 || nargs > static_cast(kMaxFunctionInputParams)) { + ThrowFakeluaException(std::format("interpreter: too many arguments ({}) passed for function '{}', max is {}", nargs, name, kMaxFunctionInputParams)); + } + for (int i = 0; i < nargs; ++i) { + raw[i] = interp_rt::VmSlot(stk, boxp, inst.a + i); + } + const VmFunction *fn = s->GetVM().FindFunction(name); + if (fn) { + void *addr = fn->GetAddr(JIT_INTERP); + const bool is_vararg = fn->IsVararg(); + const int expected = fn->GetArgCount(); + CVar flat[kMaxFunctionInputParams]; + const int nflat = FlattenByNameArgs(raw, nargs, flat); + CVar packed[kMaxFunctionInputParams]; + const int n_dispatch = PackNamedCallArgs(s, name, is_vararg, expected, raw, nargs, flat, nflat, packed); + if (addr) { + return ApplyInterpTail(s, proto, cl, stk, boxp, saved, ip, AsInterpProto(addr), nullptr, packed, n_dispatch, ret); + } + ret = CallByNameArgs(s, JIT_INTERP, name, nargs, raw); + return false; + } + if (!s->GetVM().FindNativeFunction(name)) { + ThrowFakeluaException(std::format("FakeluaCallByName: function '{}' not found", name)); + } + ret = CallByNameArgs(s, JIT_INTERP, name, nargs, raw); + return false; +} + constexpr size_t kInterpStackReserve = 262144;// 4 MiB of CVar; nested realloc would invalidate parent pointers struct InterpFrame { @@ -369,14 +682,27 @@ CVar InterpreterExecuteT(State *s, FuncProto *proto, const CVar *args, int arg_c call_args[nflat++] = interp_rt::UnboxMulti(av, 0); } } - CVar ret = inter::DispatchCallClosure(s, fn.data_.cl, call_args, nflat, JIT_INTERP); + CVar ret; + VarClosure *callee = fn.data_.cl; + // 解释器闭包且非变参、实参个数正好等于形参时,直接 InterpreterExecute, + // 跳过 DispatchCallClosure 的再装箱与 LuaLocScope。其余情况(变参、少参/多参、 + // load() 源码闭包、TCC/GCC 闭包)仍走原来的 DispatchCallClosure。 + if (IsInterpClosure(callee) && !callee->is_vararg && + callee->expected_arg_count == nflat && + nflat <= static_cast(kMaxFunctionInputParams)) { + ret = InterpExecuteInContext(s, AsInterpProto(callee->func_ptr), call_args, nflat, callee); + } else { + ret = inter::DispatchCallClosure(s, callee, call_args, nflat, JIT_INTERP); + } VM_W(inst.a, inst.c == 0 ? ret : interp_rt::UnboxMulti(ret, 0)); VM_DISPATCH_NEXT(); } VM_CASE(CALLNAME) { - // std::string 必须在 computed-goto 之前析构。 + // ConstToName 返回 string_view(interned,不一定带 NUL)。成功路径不再堆分配 + // std::string;报错走 std::format 的 string_view,仅在 CallByNameImpl 的失败路径 + // 才可能分配。 { - const std::string name(ConstToName(proto->constants[static_cast(inst.sbx)])); + const std::string_view name = ConstToName(proto->constants[static_cast(inst.sbx)]); CVar call_args[kMaxFunctionInputParams]; const int nargs = inst.b; if (nargs < 0 || nargs > static_cast(kMaxFunctionInputParams)) { @@ -385,11 +711,43 @@ CVar InterpreterExecuteT(State *s, FuncProto *proto, const CVar *args, int arg_c for (int i = 0; i < nargs; ++i) { call_args[i] = VM_R(inst.a + i); } - CVar ret = CallByNameArgs(s, JIT_INTERP, name.c_str(), nargs, call_args); + CVar ret; + if (!TryInterpNamedCall(s, name, call_args, nargs, ret)) { + ret = CallByNameArgs(s, JIT_INTERP, name, nargs, call_args); + } VM_W(inst.a, inst.c == 0 ? ret : interp_rt::UnboxMulti(ret, 0)); } VM_DISPATCH_NEXT(); } + VM_CASE(TAIL) { + if (inst.c == kTailSelfExact) { + // 本函数精确自尾调用:保持 cl,直接重置 ip。 + if (ReuseFrameSelfTail(proto, stk, boxp, ip, inst.a + 1, inst.b)) { + VM_DISPATCH_NEXT(); + } + return interp_rt::Nil(); + } + CVar ret{}; + const bool cont = InterpDoTail(s, proto, cl, stk, boxp, frame.saved, ip, inst, ret); + if (cont) { + VM_DISPATCH_NEXT(); + } + return ret; + } + VM_CASE(TAILNAME) { + if (inst.c == kTailSelfExact) { + if (ReuseFrameSelfTail(proto, stk, boxp, ip, inst.a, inst.b)) { + VM_DISPATCH_NEXT(); + } + return interp_rt::Nil(); + } + CVar ret{}; + const bool cont = InterpDoTailName(s, proto, cl, stk, boxp, frame.saved, ip, inst, ret); + if (cont) { + VM_DISPATCH_NEXT(); + } + return ret; + } VM_CASE(RETURN) { CVar ret = VM_R(inst.a); return inst.b == 0 ? ret : interp_rt::UnboxMulti(ret, 0); @@ -541,10 +899,18 @@ CVar InterpreterExecute(State *s, FuncProto *proto, const CVar *args, int arg_co if (!proto) { ThrowFakeluaException("interpreter: null function prototype"); } - if (proto->uses_boxes) { - return InterpreterExecuteT(s, proto, args, arg_count, cl); - } - return InterpreterExecuteT(s, proto, args, arg_count, cl); + // 热路径与引入尾调用前相同:一次 ExecuteT 后直接返回。 + // 仅在需要换 HasBoxes 模板或根帧重新 reserve 时,才用 TLS 槽循环再进入(C 栈深度仍为 1)。 + CVar ret = proto->uses_boxes ? InterpreterExecuteT(s, proto, args, arg_count, cl) : InterpreterExecuteT(s, proto, args, arg_count, cl); + while (UNLIKELY(g_interp_tail.pending)) { + proto = g_interp_tail.proto; + cl = g_interp_tail.cl; + arg_count = g_interp_tail.arg_count; + args = g_interp_tail.args; + g_interp_tail.pending = false; + ret = proto->uses_boxes ? InterpreterExecuteT(s, proto, args, arg_count, cl) : InterpreterExecuteT(s, proto, args, arg_count, cl); + } + return ret; } extern "C" __attribute__((used)) CVar FakeluaInterpCall(State *state, VarClosure *cl, int arg_num, const CVar *args) { diff --git a/src/interp/opcode.h b/src/interp/opcode.h index fc2899fa..86b99d7e 100644 --- a/src/interp/opcode.h +++ b/src/interp/opcode.h @@ -43,6 +43,8 @@ namespace fakelua { X(JMP) /* pc += sbx */ \ X(CALL) /* R(A) = call R(A) with B args at R(A+1)..; C=0 keep multi, C=1 unbox */ \ X(CALLNAME) /* R(A) = CallByName(Kstr(Bx), B args at R(A)..); C as CALL */ \ + X(TAIL) /* tail-call R(A) with B args at R(A+1)..; C=kTailSelfExact: reuse this proto/cl */ \ + X(TAILNAME) /* tail CallByName(Kstr(Bx), B args at R(A)..); C=kTailSelfExact: no name lookup */ \ X(RETURN) /* return R(A); B=0 keep multi, B=1 single */ \ X(CLOSURE) /* R(A) = closure(proto Bx) */ \ X(SETLIST) /* expand multi R(C) into table R(A) starting at int sbx */ \ @@ -55,6 +57,10 @@ namespace fakelua { X(FORPREP) /* int: R(A+1)=count; float: keep limit; skip if empty (pc+=sbx); R(A+3)=R(A) */ \ X(FORLOOP) /* int: if --count>=0: R(A)+=step, R(A+3)=R(A), pc+=sbx; float: add+compare */ +// TAIL/TAILNAME 的 C 操作数:精确自尾调用(非变参、编译期已知实参个数、无末参展开)。 +// 文件级函数不可再赋,因此解释器可跳过查表,原地复用当前 proto/cl。 +constexpr uint8_t kTailSelfExact = 2; + enum class Op : uint8_t { #define FAKELUA_OP_ENUM(name) name, FAKELUA_FOR_EACH_OP(FAKELUA_OP_ENUM) diff --git a/src/jit/vm.cpp b/src/jit/vm.cpp index bdd4ddec..3a5a7048 100644 --- a/src/jit/vm.cpp +++ b/src/jit/vm.cpp @@ -62,7 +62,7 @@ extern "C" __attribute__((used)) void FakeluaRegisterLuaLoc(State *s, int jit_ty } } -static CVar CallByNameImpl(State *state, int jit_type, const char *name, int arg_num, const CVar *raw_arg_arr); +static CVar CallByNameImpl(State *state, int jit_type, std::string_view name, int arg_num, const CVar *raw_arg_arr); extern "C" __attribute__((used)) CVar FakeluaCallByName(State *state, int jit_type, const char *name, int arg_num, ...) { // 参数必须在 varargs 函数自身里取出,随后的分发交给普通函数处理。 @@ -91,28 +91,30 @@ extern "C" __attribute__((used)) CVar FakeluaCallByName(State *state, int jit_ty return GuardJitEntry(state, [&] { return CallByNameImpl(state, jit_type, name, arg_num, raw_arg_arr); }); } -static CVar CallByNameImpl(State *state, int jit_type, const char *name, int arg_num, const CVar *raw_arg_arr) { +static CVar CallByNameImpl(State *state, int jit_type, std::string_view name, int arg_num, const CVar *raw_arg_arr) { // 记录"当前 Lua 调用方引擎":native 函数内登记的异步回调据此派回同一引擎。 // 作用域覆盖整个函数(含嵌套的 Lua 回调),退出时恢复外层引擎。 State::JitContextScope jit_scope(state, static_cast(jit_type)); - // 查找函数:优先 JIT,其次 C++ 原生 - // 用 string_view 查表,避免每次调用都堆分配 std::string - const std::string_view func_name(name); - const auto jit_func = state->GetVM().GetFunction(func_name); - const bool has_jit = !jit_func.Empty(); + // 查找函数:优先 JIT,其次 C++ 原生。FindFunction 避免每次拷贝 VmFunction。 + const VmFunction *jit_func = state->GetVM().FindFunction(name); + const bool has_jit = jit_func != nullptr; - // 确定 is_vararg / expected_arg_count / fixed_arg_count + // 确定 is_vararg / expected_arg_count / fixed_arg_count,并提前取出 addr, + // 避免调用期间 RegisterFunction 触发 rehash 后仍持有 jit_func 指针。 bool is_vararg = false; int expected_arg_count = 0; int fixed_arg_count = 0; const NativeFuncEntry *native_entry = nullptr; + void *jit_addr = nullptr; if (has_jit) { - is_vararg = jit_func.IsVararg(); - expected_arg_count = jit_func.GetArgCount(); + is_vararg = jit_func->IsVararg(); + expected_arg_count = jit_func->GetArgCount(); fixed_arg_count = is_vararg ? std::max(0, expected_arg_count - 1) : expected_arg_count; + jit_addr = jit_func->GetAddr(static_cast(jit_type)); + jit_func = nullptr; } else { - native_entry = state->GetVM().FindNativeFunction(func_name); + native_entry = state->GetVM().FindNativeFunction(name); if (UNLIKELY(!native_entry)) { ThrowFakeluaException(std::format("FakeluaCallByName: function '{}' not found", name)); } @@ -227,17 +229,16 @@ static CVar CallByNameImpl(State *state, int jit_type, const char *name, int arg } // 情形 2:JIT 编译的 lua 函数 - void *addr = jit_func.GetAddr(static_cast(jit_type)); - if (UNLIKELY(!addr)) { + if (UNLIKELY(!jit_addr)) { ThrowFakeluaException(std::format("FakeluaCallByName: function '{}' has no address for jit_type {}", name, jit_type)); } // 走 DispatchCall 而不是在这里再展开一遍调用阶梯:它自带 JIT 错误边界, - // 被调用方出错时本函数的帧(持有 VmFunction 里的 shared_ptr)才能正常析构。 - return inter::DispatchCall(state, addr, arg_arr, expected_arg_count, static_cast(jit_type)); + // 被调用方出错时本函数的帧才能正常析构。 + return inter::DispatchCall(state, jit_addr, arg_arr, expected_arg_count, static_cast(jit_type)); } -CVar CallByNameArgs(State *state, int jit_type, const char *name, int arg_num, const CVar *args) { +CVar CallByNameArgs(State *state, int jit_type, std::string_view name, int arg_num, const CVar *args) { return GuardJitEntry(state, [&] { return CallByNameImpl(state, jit_type, name, arg_num, args); }); } diff --git a/src/jit/vm.h b/src/jit/vm.h index ef5b6fb1..a8f89a07 100644 --- a/src/jit/vm.h +++ b/src/jit/vm.h @@ -37,14 +37,23 @@ class Vm { } } - // 获取 JIT 函数 + // 获取 JIT 函数(拷贝 VmFunction,含 name 与各后端 handle 的 shared_ptr) [[nodiscard]] VmFunction GetFunction(std::string_view name) const { - if (const auto iter = vm_functions_.find(name); iter != vm_functions_.end()) { - return iter->second; + if (const auto *fn = FindFunction(name)) { + return *fn; } return {}; } + // 热路径查找:返回表内指针,避免每次调用拷贝 VmFunction。 + // 指针在下一次 RegisterFunction 触发 rehash 前有效;调用前先取出 addr / 签名。 + [[nodiscard]] const VmFunction *FindFunction(std::string_view name) const { + if (const auto iter = vm_functions_.find(name); iter != vm_functions_.end()) { + return &iter->second; + } + return nullptr; + } + // 注册 C++ 原生函数(可在编译前或编译后调用) // 注册后可在 lua 脚本中直接以函数名调用,通过 FakeluaCallByName 分发 void RegisterNativeFunction(const std::string &name, int arg_count, bool is_vararg, NativeFuncCallback callback) { @@ -117,6 +126,6 @@ extern "C" int FakeluaJitContextPush(State *s, int jit); extern "C" void FakeluaJitContextPop(State *s, int prev); // 数组版 FakeluaCallByName,供解释器使用 -CVar CallByNameArgs(State *state, int jit_type, const char *name, int arg_num, const CVar *args); +CVar CallByNameArgs(State *state, int jit_type, std::string_view name, int arg_num, const CVar *args); }// namespace fakelua diff --git a/test/lua/closure/test_closure_tail_deep.lua b/test/lua/closure/test_closure_tail_deep.lua new file mode 100644 index 00000000..736dfb15 --- /dev/null +++ b/test/lua/closure/test_closure_tail_deep.lua @@ -0,0 +1,11 @@ +-- 足够深的尾递归:若仍按 C 栈一层一层 InterpreterExecute,会在默认栈上炸掉。 +function rec(i, acc) + if i <= 0 then + return acc + end + return rec(i - 1, acc + 1) +end + +function test(n) + return rec(n, 0) +end diff --git a/test/lua/closure/test_closure_tail_mutual.lua b/test/lua/closure/test_closure_tail_mutual.lua new file mode 100644 index 00000000..4bea18ad --- /dev/null +++ b/test/lua/closure/test_closure_tail_mutual.lua @@ -0,0 +1,18 @@ +-- 文件级命名互递归尾调用(CALLNAME/TAILNAME) +function even(n) + if n == 0 then + return 1 + end + return odd(n - 1) +end + +function odd(n) + if n == 0 then + return 0 + end + return even(n - 1) +end + +function test() + return even(10) + even(11) + odd(10) + odd(11) +end diff --git a/test/lua/closure/test_closure_tail_self.lua b/test/lua/closure/test_closure_tail_self.lua new file mode 100644 index 00000000..f19f9ca9 --- /dev/null +++ b/test/lua/closure/test_closure_tail_self.lua @@ -0,0 +1,10 @@ +-- 局部闭包自递归尾调用:return rec(...) +function test() + local function rec(n, acc) + if n <= 0 then + return acc + end + return rec(n - 1, acc + n) + end + return rec(20, 0) +end diff --git a/test/lua/closure/test_closure_tail_upvalue.lua b/test/lua/closure/test_closure_tail_upvalue.lua new file mode 100644 index 00000000..13c378f5 --- /dev/null +++ b/test/lua/closure/test_closure_tail_upvalue.lua @@ -0,0 +1,22 @@ +-- 捕获 upvalue 的尾递归,以及无捕获函数尾调到带捕获的函数(trampoline) +function test() + local acc = 0 + local function rec(n) + if n <= 0 then + return acc + end + acc = acc + n + return rec(n - 1) + end + local x = 7 + local function boxed(n) + if n <= 0 then + return x + end + return boxed(n - 1) + end + local function start(n) + return boxed(n) + end + return rec(10) + start(5) +end diff --git a/test/lua/closure/test_closure_tail_values.lua b/test/lua/closure/test_closure_tail_values.lua new file mode 100644 index 00000000..d6e9dd3a --- /dev/null +++ b/test/lua/closure/test_closure_tail_values.lua @@ -0,0 +1,16 @@ +-- 尾调用必须保留全部返回值;return (f()) 不是尾调用,这里只断言尾路径的多返回值。 +function multi() + return 1, 2 +end + +function tail_multi() + return multi() +end + +function test() + local a, b = tail_multi() + if b == nil then + return -1 + end + return a + b +end diff --git a/test/lua/closure/test_closure_tail_vararg.lua b/test/lua/closure/test_closure_tail_vararg.lua new file mode 100644 index 00000000..9b738658 --- /dev/null +++ b/test/lua/closure/test_closure_tail_vararg.lua @@ -0,0 +1,22 @@ +-- 尾调用传递 ... / 多返回值 +function ident(...) + return ... +end + +function wrap(...) + return ident(...) +end + +function add3(a, b, c) + return a + b + c +end + +function via_multi(...) + return add3(...) +end + +function test() + local a, b, c = wrap(10, 20, 30) + local s = via_multi(1, 2, 3) + return a + b + c + s +end diff --git a/test/lua/exception/test_error_location_tail.lua b/test/lua/exception/test_error_location_tail.lua new file mode 100644 index 00000000..3aac2e2b --- /dev/null +++ b/test/lua/exception/test_error_location_tail.lua @@ -0,0 +1,16 @@ +package "TailErr" + +-- 深层尾递归后出错,位置必须是最内层那条语句,而不是 C 栈溢出。 +function boom(n) + if n <= 0 then + return 1 + nil + end + return boom(n - 1) +end + +function call_missing(n) + if n <= 0 then + return does_not_exist() + end + return call_missing(n - 1) +end diff --git a/test/test_closure.cpp b/test/test_closure.cpp index 77100254..9ec42a51 100644 --- a/test/test_closure.cpp +++ b/test/test_closure.cpp @@ -115,3 +115,63 @@ TEST(closure, test_closure_generic_for_in) { ASSERT_EQ(ret, 180); }); } + +TEST(closure, test_closure_tail_self) { + ClosureRunHelper([](State *s, JITType type, bool debug_mode) { + CompileFile(s, "./closure/test_closure_tail_self.lua", {.debug_mode = debug_mode}); + int ret = 0; + Call(s, type, "test", ret); + ASSERT_EQ(ret, 210); + }); +} + +TEST(closure, test_closure_tail_mutual) { + ClosureRunHelper([](State *s, JITType type, bool debug_mode) { + CompileFile(s, "./closure/test_closure_tail_mutual.lua", {.debug_mode = debug_mode}); + int ret = 0; + Call(s, type, "test", ret); + ASSERT_EQ(ret, 2); + }); +} + +TEST(closure, test_closure_tail_vararg) { + ClosureRunHelper([](State *s, JITType type, bool debug_mode) { + CompileFile(s, "./closure/test_closure_tail_vararg.lua", {.debug_mode = debug_mode}); + int ret = 0; + Call(s, type, "test", ret); + ASSERT_EQ(ret, 66); + }); +} + +TEST(closure, test_closure_tail_upvalue) { + ClosureRunHelper([](State *s, JITType type, bool debug_mode) { + CompileFile(s, "./closure/test_closure_tail_upvalue.lua", {.debug_mode = debug_mode}); + int ret = 0; + Call(s, type, "test", ret); + ASSERT_EQ(ret, 62); + }); +} + +TEST(closure, test_closure_tail_values) { + ClosureRunHelper([](State *s, JITType type, bool debug_mode) { + CompileFile(s, "./closure/test_closure_tail_values.lua", {.debug_mode = debug_mode}); + int ret = 0; + Call(s, type, "test", ret); + ASSERT_EQ(ret, 3); + }); +} + +// INTERP 专用:n=100000 若仍按 C 栈递归 InterpreterExecute,会在默认 8MiB 栈上溢出。 +TEST(closure, test_closure_tail_deep_interp) { + const auto s = FakeluaNewState(); + ASSERT_NE(s, nullptr); + CompileConfig cfg; + cfg.debug_mode = false; + cfg.disable_jit[JIT_TCC] = true; + cfg.disable_jit[JIT_GCC] = true; + CompileFile(s, "./closure/test_closure_tail_deep.lua", cfg); + int ret = 0; + Call(s, JIT_INTERP, "test", ret, 100000); + ASSERT_EQ(ret, 100000); + FakeluaDeleteState(s); +} diff --git a/test/test_exception.cpp b/test/test_exception.cpp index 78d8f6d4..85c1496b 100644 --- a/test/test_exception.cpp +++ b/test/test_exception.cpp @@ -1801,3 +1801,52 @@ TEST(exception, runtime_error_location_multi_so_closure) { } } } + +// 尾调用替换当前解释器帧后,运行期错误仍必须定位到最内层那条语句。 +TEST(exception, runtime_error_location_tail_call) { + FakeluaStateGuard sg; + auto s = sg.GetState(); + ASSERT_NE(s, nullptr); + SetDebugLogLevel(s, static_cast(LogLevel::Error)); + CompileFile(s, "./exception/test_error_location_tail.lua", {}); + + for (const auto jit_type: AllJitTypes()) { + int64_t ret = 0; + try { + Call(s, jit_type, "TailErr.boom", ret, 5); + ADD_FAILURE() << JitTypeName(jit_type) << " TailErr.boom should have thrown"; + } catch (const std::exception &e) { + const std::string msg = e.what(); + EXPECT_NE(msg.find("test_error_location_tail.lua:6"), std::string::npos) << JitTypeName(jit_type) << ": " << msg; + } + try { + Call(s, jit_type, "TailErr.call_missing", ret, 5); + ADD_FAILURE() << JitTypeName(jit_type) << " TailErr.call_missing should have thrown"; + } catch (const std::exception &e) { + const std::string msg = e.what(); + EXPECT_NE(msg.find("test_error_location_tail.lua:13"), std::string::npos) << JitTypeName(jit_type) << ": " << msg; + EXPECT_NE(msg.find("does_not_exist"), std::string::npos) << JitTypeName(jit_type) << ": " << msg; + } + } +} + +// INTERP 深层尾调用后报错,不能变成 C 栈溢出,位置仍是 boom 里那一行。 +TEST(exception, runtime_error_location_tail_call_deep_interp) { + FakeluaStateGuard sg; + auto s = sg.GetState(); + ASSERT_NE(s, nullptr); + SetDebugLogLevel(s, static_cast(LogLevel::Error)); + CompileConfig cfg; + cfg.debug_mode = false; + cfg.disable_jit[JIT_TCC] = true; + cfg.disable_jit[JIT_GCC] = true; + CompileFile(s, "./exception/test_error_location_tail.lua", cfg); + int64_t ret = 0; + try { + Call(s, JIT_INTERP, "TailErr.boom", ret, 20000); + ADD_FAILURE() << "deep INTERP TailErr.boom should have thrown"; + } catch (const std::exception &e) { + const std::string msg = e.what(); + EXPECT_NE(msg.find("test_error_location_tail.lua:6"), std::string::npos) << msg; + } +}