diff --git a/src/compile/c_gen.cpp b/src/compile/c_gen.cpp index 5d6dfed..ef35f84 100644 --- a/src/compile/c_gen.cpp +++ b/src/compile/c_gen.cpp @@ -93,7 +93,19 @@ void CGen::UniquifyFuncCNames() { if (func.get() == real_init) { continue; } - std::string base = func->unique_c_name.empty() ? "flua_fn" : func->unique_c_name; + std::string base; + if (func->unique_c_name.empty()) { + base = "flua_fn"; + } else if (func->parent == nullptr) { + // 顶层用户函数统一加 flua_fn_ 前缀: + // 1) 规避 C 宿主对 main 的入口签名校验(Clang 报 first parameter of 'main'); + // 2) 规避与 libc/头文件同名符号(sin/printf/...)的重定义冲突; + // 3) 顺带规避其余 C 保留/外部标识符。 + // 嵌套函数在 ResolveScopes 中已取得 __fl_func_N 形式的唯一名,无需再加前缀。 + base = std::string("flua_fn_") + func->unique_c_name; + } else { + base = func->unique_c_name; + } if (base == kInitFunctionName) { base = std::string("flua_id_") + kInitFunctionName; } @@ -2096,7 +2108,9 @@ void CGen::CompileStmtForIn(const SyntaxTreeInterfacePtr &stmt) { Out() << GenTab() << iter_s << " = FlUnboxMulti(" << e1 << ", 0);\n"; Out() << GenTab() << iter_var << " = FlUnboxMulti(" << e2 << ", 0);\n"; for (size_t i = 3; i < exps.size(); ++i) { - Out() << GenTab() << "(void)(" << CompileExp(exps[i], i + 1 == exps.size()) << ");\n"; + // 同 CompileCallArgs:CompileExp 可能先发语句宏,必须先求值再拼接输出。 + const std::string discarded_exp = CompileExp(exps[i], i + 1 == exps.size()); + Out() << GenTab() << "(void)(" << discarded_exp << ");\n"; } } @@ -4464,10 +4478,14 @@ void CGen::CompileCallArgs(const std::shared_ptr &args_ptr, Args for (size_t i = 0; i < raw_args.size() - 1; ++i) { compiled_args.push_back(CompileExp(raw_args[i])); } - // Compile the last function call/vararg into a temporary variable + // Compile the last function call/vararg into a temporary variable. + // 注意:必须先求值 CompileExp(它会把慢路径语句宏/if-else 直接写入输出流), + // 再输出赋值语句,否则「左值前缀」与「结果表达式」会被中途发出的语句撕开, + // 生成 x = do{...}while(0); 这类非法 C(见 FAKELUA_JIT_BUG_REPORT 缺陷 2)。 expansion_tmp = std::format("flua_call_res_{}", tmp_var_counter_++); func_temp_decls_ << " CVar " << expansion_tmp << ";\n"; - Out() << GenTab() << expansion_tmp << " = " << CompileExp(raw_args.back(), true) << ";\n"; + const std::string last_expansion_arg = CompileExp(raw_args.back(), true); + Out() << GenTab() << expansion_tmp << " = " << last_expansion_arg << ";\n"; expansion_start_idx = raw_args.size() - 1; } else { for (const auto &exp: raw_args) { diff --git a/src/compile/c_runtime_header.h b/src/compile/c_runtime_header.h index 9651bf9..e6be3dd 100644 --- a/src/compile/c_runtime_header.h +++ b/src/compile/c_runtime_header.h @@ -341,7 +341,10 @@ extern CVar FakeluaInterpCall(State *state, VarClosure *cl, int arg_num, const C #define kMaxFunctionInputParams 32 -static inline CVar FlMakeClosure(State *state, void *func_ptr, int upvalue_count, int expected_arg_count, bool is_vararg, ...) { +// 注意:is_vararg 必须声明为 int 而非 bool。C17 7.16.1.4 规定 va_start 的最后一个 +// 具名形参若类型会受默认实参提升影响(bool/_Bool 会被提升为 int),行为未定义。 +// 调用处传入的 true/false 会隐式转换为 int。 +static inline CVar FlMakeClosure(State *state, void *func_ptr, int upvalue_count, int expected_arg_count, int is_vararg, ...) { VarClosure *cl = (VarClosure *)FakeluaAlloc(state, sizeof(VarClosure) + upvalue_count * sizeof(CVar *), !__fakelua_init_flag__); cl->func_ptr = func_ptr; cl->upvalue_count = upvalue_count; diff --git a/src/compile/compile_common.h b/src/compile/compile_common.h index 44ff6a6..e5529d2 100644 --- a/src/compile/compile_common.h +++ b/src/compile/compile_common.h @@ -294,6 +294,11 @@ struct ParseResult { struct AnalysisResult { // 函数名 -> 最大返回值数量(-1 代表动态,例如以函数调用结尾) std::unordered_map function_max_returns; + // 函数名 -> 有效返回值数量:在 function_max_returns 基础上把「return f()」尾调用 + // 沿被调函数链做定点求解(含递归自调用锚点),例如 f()=return g()、g()=return 1 + // 会解析为 1。-1 表示无法静态确定(未知被调/vararg/无锚点递归环)。 + // 仅供数学参数特化的资格判定使用,不影响既有代码生成路径。 + std::unordered_map function_effective_returns; // 语法分析出的所有函数调用表达式节点集合,供 CGen 直接查询 std::unordered_set function_call_exps; // 语法分析出的所有函数调用到其被调用者名字的映射,供 CGen 直接查询 diff --git a/src/compile/compiler.cpp b/src/compile/compiler.cpp index 0186d73..f63c88a 100644 --- a/src/compile/compiler.cpp +++ b/src/compile/compiler.cpp @@ -82,7 +82,7 @@ ParseResult Compiler::Compile(MyFlexer &f, const CompileConfig &cfg) { // 5. 类型推导(同时识别数学参数) LOG_DEBUG(s_, "engine", "step 5: type inference"); TypeInferencer inferencer(s_); - InferResult ir = inferencer.InferTypes(pr, cfg); + InferResult ir = inferencer.InferTypes(pr, ar, cfg); // 6. 转译为C(TCC/GCC 需要;仅解释器时跳过) const bool need_c = !cfg.disable_jit[JIT_TCC] || !cfg.disable_jit[JIT_GCC] || cfg.record_c_code; diff --git a/src/compile/semantic_analysis.cpp b/src/compile/semantic_analysis.cpp index 667493f..6d36637 100644 --- a/src/compile/semantic_analysis.cpp +++ b/src/compile/semantic_analysis.cpp @@ -89,6 +89,22 @@ void SemanticAnalysis::AnalyzeGlobalConstNames(const SyntaxTreeInterfacePtr &chu void SemanticAnalysis::AnalyzeFunctionReturnCounts(const SyntaxTreeInterfacePtr &chunk, AnalysisResult &ar) { DEBUG_ASSERT(chunk->Type() == SyntaxTreeType::Block); const auto block = std::dynamic_pointer_cast(chunk); + + // 每个文件级函数的返回值明细,用于在 function_max_returns 之上做 + // 「尾调用链有效返回数」定点求解(仅供数学参数特化资格判定)。 + struct FuncReturnInfo { + // 是否存在不以尾调用/vararg 结尾的 return(含裸 return),其最大显式返回数 + bool has_concrete = false; + int concrete_max = 0; + // 存在 return ...(vararg 尾展开) + bool has_vararg_tail = false; + // 存在尾调用指向无法解析的被调(原生函数/方法/外部符号):返回数不可知 + bool has_unknown_tail = false; + // 尾调用指向的本文件级函数名 + std::vector tail_callees; + }; + std::unordered_map infos; + for (const auto &stmt: block->Stmts()) { std::string name; SyntaxTreeInterfacePtr funcbody; @@ -114,22 +130,117 @@ void SemanticAnalysis::AnalyzeFunctionReturnCounts(const SyntaxTreeInterfacePtr CollectReturnsForBlock(func_block, returns); int max_returns = 0; + auto &info = infos[name]; for (const auto &ret_node: returns) { const auto ret = std::dynamic_pointer_cast(ret_node); const auto el = std::dynamic_pointer_cast(ret->Explist()); if (!el || el->Exps().empty()) { // return count is 0 + info.has_concrete = true; + continue; + } + const auto &ret_exps = el->Exps(); + int count = static_cast(ret_exps.size()); + if (IsFunctionCallExp(ret_exps.back())) { + max_returns = -1;// dynamic + if (count == 1) { + // 唯一返回表达式是尾调用:记录被调函数名用于定点求解 + const std::string callee = GetCalleeName(ret_exps.back()); + if (callee.empty()) { + info.has_unknown_tail = true; + } else { + info.tail_callees.push_back(callee); + } + } else { + // 多返回值 return 且末位是调用:显式部分贡献确定数量, + // 尾调用部分另按被调链传播 + info.has_concrete = true; + info.concrete_max = std::max(info.concrete_max, count); + const std::string callee = GetCalleeName(ret_exps.back()); + if (callee.empty()) { + info.has_unknown_tail = true; + } else { + info.tail_callees.push_back(callee); + } + } } else { - int count = static_cast(el->Exps().size()); - if (IsFunctionCallExp(el->Exps().back())) { - max_returns = -1;// dynamic + if (IsVarargExp(ret_exps.back()) && count == 1) { + // return ...:尾 vararg 展开,返回数随调用者变化 + info.has_vararg_tail = true; + max_returns = -1; } else if (max_returns >= 0) { max_returns = std::max(max_returns, count); } + info.has_concrete = true; + info.concrete_max = std::max(info.concrete_max, count); } } ar.function_max_returns[name] = max_returns; } + + // 尾调用被调若不是本文件级函数(外部 chunk 符号),返回数同样不可知,按 tainted 处理。 + for (auto &[name, info]: infos) { + for (const auto &callee: info.tail_callees) { + if (!infos.contains(callee)) { + info.has_unknown_tail = true; + break; + } + } + } + + // 定点求解有效返回数,内部使用双状态: + // kTainted(-2):永久不可知(vararg 尾展开 / 未知或外部被调 / 被调链污染); + // kUnknown(-1):暂未解析(被调链尚未收敛,无 concrete 锚点的递归环停留于此); + // >=0:当前确定的最大返回数。 + // 初值:直接 tainted 标记者 → kTainted;存在确定 return(含裸 return)→ concrete_max; + // 仅有尾调用 return → kUnknown 等待被调链解析。 + // 迭代:边到 kTainted → 自身 kTainted(动态返回路径会污染所有上游); + // 边到 >=0 → 取 max;边到 kUnknown → 本轮等待。 + // 递归自调用(如 fact 有 return 1 基线)由 concrete 锚点稳定为 1。 + constexpr int kUnknown = -1; + constexpr int kTainted = -2; + for (const auto &[name, info]: infos) { + if (info.has_vararg_tail || info.has_unknown_tail) { + ar.function_effective_returns[name] = kTainted; + } else if (info.has_concrete) { + ar.function_effective_returns[name] = info.concrete_max; + } else { + ar.function_effective_returns[name] = kUnknown; + } + } + for (size_t round = 0; round < infos.size() + 1; ++round) { + bool changed = false; + for (const auto &[name, info]: infos) { + int &cur = ar.function_effective_returns[name]; + if (cur == kTainted) { + continue; + } + for (const auto &callee: info.tail_callees) { + const int cv = ar.function_effective_returns.at(callee); + if (cv == kTainted) { + cur = kTainted; + changed = true; + break; + } + if (cv == kUnknown) { + continue; + } + if (cur == kUnknown || cv > cur) { + cur = cv; + changed = true; + } + } + } + if (!changed) { + break; + } + } + // 对外统一输出:kTainted 与仍未解析的 kUnknown 都记为 -1(不可特化)。 + for (auto &[name, v]: ar.function_effective_returns) { + if (v < 0) { + v = -1; + } + } } void SemanticAnalysis::CollectReturnsForBlock(const SyntaxTreeInterfacePtr &node, std::vector &returns) { diff --git a/src/compile/type_inferencer.cpp b/src/compile/type_inferencer.cpp index 84a2b65..328bbcb 100644 --- a/src/compile/type_inferencer.cpp +++ b/src/compile/type_inferencer.cpp @@ -381,7 +381,7 @@ InferredType TypeInferencer::TypeEnvironment::MergeType(const InferredType old_t // 第二部分:核心 AST 类型推断引擎 // =========================================================================== -InferResult TypeInferencer::InferTypes(const ParseResult &pr, const CompileConfig &cfg) { +InferResult TypeInferencer::InferTypes(const ParseResult &pr, const AnalysisResult &ar, const CompileConfig &cfg) { LOG_DEBUG(s_, "engine", "InferTypes: start for {}", pr.file_name); file_level_types_.clear(); InferResult ir; @@ -401,7 +401,7 @@ InferResult TypeInferencer::InferTypes(const ParseResult &pr, const CompileConfi // 在正常推断之后,通过三个阶段发现数学参数并生成特化信息: // IdentifyMathParams:多轮迭代识别数学参数 LOG_DEBUG(s_, "engine", "InferTypes: step 3 - IdentifyMathParams"); - const auto math_func_info = IdentifyMathParams(pr, ir); + const auto math_func_info = IdentifyMathParams(pr, ir, ar); if (!math_func_info.empty()) { LOG_DEBUG(s_, "engine", "InferTypes: found {} math-param functions", math_func_info.size()); // GenerateInitialSnapshots:生成各特化版本的初始类型快照 @@ -975,7 +975,121 @@ void TypeInferencer::InferBlock(const std::shared_ptr &block, c // 第三部分:特化与数学参数识别逻辑 // =========================================================================== -TypeInferencer::MathFuncInfoMap TypeInferencer::IdentifyMathParams(const ParseResult &pr, InferResult &ir) { +bool TypeInferencer::IsEligibleForMathSpec(const SyntaxTreeInterfacePtr &block_node, const AnalysisResult &ar) const { + if (!block_node) { + return true; + } + const auto block = std::dynamic_pointer_cast(block_node); + DEBUG_ASSERT(block); + for (const auto &stmt: block->Stmts()) { + switch (stmt->Type()) { + case SyntaxTreeType::Return: { + const auto ret = std::dynamic_pointer_cast(stmt); + const auto el = ret->Explist() ? std::dynamic_pointer_cast(ret->Explist()) : nullptr; + // 裸 return(0 个返回值):特化返回类型会退化为 CVar,通用返回路径可正确处理。 + if (!el || el->Exps().empty()) { + break; + } + const auto &ret_exps = el->Exps(); + // 多值返回:标量特化体只返回一个 int64_t/double,额外返回值在调用约定层 + // 无法承载(特化体内会生成 return FlMakeMulti(...) 导致 C 编译失败, + // 即使编译通过 dispatcher/调用点也会丢值)。 + if (ret_exps.size() > 1) { + return false; + } + // 唯一返回值是尾位置 vararg 展开(return ...):实际元数随调用者变化,排除。 + // 唯一返回值是尾位置函数调用(return f()):特化体经 CompileNumericExp + // 直发被调的标量特化,故仅当 f 的有效返回数静态可知恰好为 1 时才安全 + // (见 SemanticAnalysis 的 function_effective_returns 定点求解, + // 可穿透多层尾调用与带单值基线的递归)。 + const auto &only = ret_exps[0]; + if (IsVarargExp(only)) { + return false; + } + if (IsFunctionCallExp(only)) { + const auto callee_it = ar.callee_names.find(only.get()); + const std::string callee = (callee_it != ar.callee_names.end()) ? callee_it->second : ""; + // 用跨函数定点求解后的有效返回数:return f() 仅当 f 链静态可知恰好 + // 返回 1 个值时才安全(递归自调用带单值基线也算 1)。 + const auto eff_it = ar.function_effective_returns.find(callee); + if (eff_it == ar.function_effective_returns.end() || eff_it->second != 1) { + return false; + } + } + break; + } + case SyntaxTreeType::If: { + const auto if_node = std::dynamic_pointer_cast(stmt); + if (!IsEligibleForMathSpec(if_node->Block(), ar)) { + return false; + } + if (const auto elseifs = if_node->ElseIfs()) { + const auto el = std::dynamic_pointer_cast(elseifs); + for (const auto &blk: el->ElseifBlocks()) { + if (!IsEligibleForMathSpec(blk, ar)) { + return false; + } + } + } + if (if_node->ElseBlock() && !IsEligibleForMathSpec(if_node->ElseBlock(), ar)) { + return false; + } + break; + } + case SyntaxTreeType::While: { + const auto while_node = std::dynamic_pointer_cast(stmt); + if (!IsEligibleForMathSpec(while_node->Block(), ar)) { + return false; + } + break; + } + case SyntaxTreeType::Repeat: { + const auto rep = std::dynamic_pointer_cast(stmt); + if (!IsEligibleForMathSpec(rep->Block(), ar)) { + return false; + } + break; + } + case SyntaxTreeType::ForLoop: { + const auto for_loop = std::dynamic_pointer_cast(stmt); + if (!IsEligibleForMathSpec(for_loop->Block(), ar)) { + return false; + } + break; + } + case SyntaxTreeType::ForIn: { + const auto for_in = std::dynamic_pointer_cast(stmt); + if (!IsEligibleForMathSpec(for_in->Block(), ar)) { + return false; + } + break; + } + case SyntaxTreeType::Block: + // do...end 块:递归检查其内部的 return。 + if (!IsEligibleForMathSpec(stmt, ar)) { + return false; + } + break; + case SyntaxTreeType::Assign: + case SyntaxTreeType::LocalVar: + case SyntaxTreeType::LocalFunction: + case SyntaxTreeType::Function: + case SyntaxTreeType::FunctionCall: + case SyntaxTreeType::Break: + case SyntaxTreeType::Continue: + case SyntaxTreeType::Goto: + case SyntaxTreeType::Label: + case SyntaxTreeType::Empty: + // 不含外层函数的 return(嵌套函数定义不递归进入)。 + break; + default: + ThrowFakeluaException(std::format("IsEligibleForMathSpec: unexpected statement type {}", SyntaxTreeTypeToString(stmt->Type()))); + } + } + return true; +} + +TypeInferencer::MathFuncInfoMap TypeInferencer::IdentifyMathParams(const ParseResult &pr, InferResult &ir, const AnalysisResult &ar) { MathFuncInfoMap math_func_info; const auto function_infos = CollectFunctionSpecInfos(pr); @@ -989,6 +1103,11 @@ TypeInferencer::MathFuncInfoMap TypeInferencer::IdentifyMathParams(const ParseRe if (ir.math_param_positions.contains(info.name)) { continue; } + // 返回形态资格检查:多返回值/尾位置展开的函数只能走通用 CVar 变体, + // 标量特化体会生成 return FlMakeMulti(...) 这类非法 C(见 FAKELUA_JIT_BUG_REPORT 缺陷 1)。 + if (!IsEligibleForMathSpec(info.block, ar)) { + continue; + } // baseline:所有参数均假设为 T_DYNAMIC;all_int:所有参数均假设为 T_INT。 // 两次推断的对比用于判断该函数的算术表达式是否能因参数类型已知而改善。 const auto baseline = RunTrialInference(info.block, info.params, MakeAssumedParamTypes(info.params, "", T_DYNAMIC, T_DYNAMIC), nullptr, nullptr, true, ir.var_define_nodes); diff --git a/src/compile/type_inferencer.h b/src/compile/type_inferencer.h index c190263..519eeb1 100644 --- a/src/compile/type_inferencer.h +++ b/src/compile/type_inferencer.h @@ -12,7 +12,7 @@ class TypeInferencer { explicit TypeInferencer(State *s); // 运行全局类型推断,并在返回的 InferResult 中填充数学参数特化信息。 - InferResult InferTypes(const ParseResult &pr, const CompileConfig &cfg); + InferResult InferTypes(const ParseResult &pr, const AnalysisResult &ar, const CompileConfig &cfg); private: struct TraversalContext; @@ -132,7 +132,14 @@ class TypeInferencer { // 多轮迭代识别数学参数,记录到 ir.math_param_positions, // 同时返回数学函数信息。 - MathFuncInfoMap IdentifyMathParams(const ParseResult &pr, InferResult &ir); + MathFuncInfoMap IdentifyMathParams(const ParseResult &pr, InferResult &ir, const AnalysisResult &ar); + + // 数学参数特化资格检查:函数的每条 return 都必须与标量/CVar 特化调用约定兼容。 + // 多值返回(return a, b, ...)、尾位置 vararg 展开(return ...)、以及尾位置 + // 调用无法静态确定为单返回值函数(return f() 且 f 可能返回多个值)的函数不参与 + // 特化——这些形态只有通用 CVar 变体才能正确处理。 + // 不递归进入嵌套函数定义;裸 return(0 值)会使特化返回类型退化为 CVar,是安全的。 + [[nodiscard]] bool IsEligibleForMathSpec(const SyntaxTreeInterfacePtr &block_node, const AnalysisResult &ar) const; // 为所有数学函数生成初始特化快照,写入 ir.specialization_snapshots。 // 每个函数生成 2^k 个快照(k = 数学参数个数)。 diff --git a/test/lua/infer/test_jitbug_multi_return_spec.lua b/test/lua/infer/test_jitbug_multi_return_spec.lua new file mode 100644 index 0000000..d3416c5 --- /dev/null +++ b/test/lua/infer/test_jitbug_multi_return_spec.lua @@ -0,0 +1,53 @@ +-- 回归:多返回值 / 变参返回 / 尾位置透传多返回值的函数,即使形参命中数学参数, +-- 也不得生成标量返回值特化(否则特化函数会在 int64_t/double 返回值函数里 +-- return FlMakeMulti(...),产生非法 C:cannot convert 'struct CVar' to 'long long')。 + +-- 固定返回 3 个值 +local function mr(x) + local y = x + 1 + return y, y + 1, y + 2 +end + +-- 尾表达式为 vararg 展开,返回值数量不确定;t 让 x 命中数学参数 +local function va(x, ...) + local t = x + 1 + return t, ... +end + +-- 返回 2 个值 +local function mr2(x) + return x, x + 1 +end + +-- 尾位置透传 mr2 的多返回值;z 让 x 命中数学参数 +local function tailmr(x) + local z = x + 1 - 1 + return mr2(z) +end + +local function sum3(a, b, c) + return a + b + c +end + +-- 带单值基线的尾递归:return factacc(...) 的被调链指向自身,定点求解靠 +-- concrete 锚点(return acc)解析为有效返回 1,必须仍然生成标量特化。 +local function factacc(n, acc) + if n <= 0 then + return acc + end + return factacc(n - 1, acc * n) +end + +function test(n) + local a, b, c = mr(n) + local s = a + b + c + local d, e = va(n, n + 1, n + 2) + local s2 = d + e + local f, g = tailmr(n) + local s3 = f + g + -- 尾位置把多返回值直接展开进另一个调用 + local s4 = sum3(mr(n)) + -- 尾递归单值函数仍可特化,factacc(5,1)=120 + local s5 = factacc(5, 1) + return s + s2 + s3 + s4 + s5 +end diff --git a/test/lua/infer/test_jitbug_reserved_func_name.lua b/test/lua/infer/test_jitbug_reserved_func_name.lua new file mode 100644 index 0000000..cd81a5f --- /dev/null +++ b/test/lua/infer/test_jitbug_reserved_func_name.lua @@ -0,0 +1,23 @@ +-- 回归:顶层 Lua 函数若与 C 入口名(main)或 libc/头文件符号(sin 等)同名, +-- 生成的 C 符号必须统一加 flua_fn_ 前缀。否则: +-- * 名为 main 的函数:Clang 报 first parameter of 'main' must be of type 'int'; +-- * 名为 sin 的函数:TCC 报 incompatible types for redefinition of 'sin'。 +-- Lua 侧仍以原名调用。 + +function main(x) + return x + 1 +end + +function sin(x) + return x * 2 +end + +function test() + if main(41) ~= 42 then + return 1 + end + if sin(21) ~= 42 then + return 2 + end + return 0 +end diff --git a/test/lua/infer/test_jitbug_tail_expand_stmt.lua b/test/lua/infer/test_jitbug_tail_expand_stmt.lua new file mode 100644 index 0000000..2ea7bf5 --- /dev/null +++ b/test/lua/infer/test_jitbug_tail_expand_stmt.lua @@ -0,0 +1,51 @@ +-- 回归:尾参数展开路径曾把「语句宏」(OpAdd 展开成 do{}while(0)、math.floor 的 +-- if/else 类型分支) 直接拼接进表达式位置,生成非法 C:expression expected before 'do'。 +-- 触发条件:最后一个实参为需要展开的函数调用,且该调用(或其嵌套子表达式)含 +-- 动态类型操作数的算术(os.time() 等原生调用结果推导为 T_DYNAMIC)。 + +local function consume(x, y) + if x ~= 1 then + return -1 + end + return y +end + +-- 多返回值 local 函数:max_returns=2,调用点走尾展开分支 +local function two(x) + return x, x + 100 +end + +-- generic for 用迭代器 +local function it(tt, idx) + idx = idx + 1 + local v = tt[idx] + if v then + return idx, v + end +end + +function test() + local a = os.time() -- T_DYNAMIC,使 a + 1 走慢路径语句宏 + -- 变体 A:尾实参为原生函数 math.floor,嵌套动态算术 a + 1 + local r1 = consume(1, math.floor(a + 1)) + if type(r1) ~= "number" then + return 1 + end + -- 变体 B:尾实参为多返回值 local 函数 two,实参 a + 1 含动态算术 + local r2 = consume(1, two(a + 1)) + if r2 ~= a + 1 then + return 2 + end + -- 变体 C:generic for 超过 3 个表达式时,第 4 个(丢弃)表达式含同类语句宏 + local t = {10, 20, 30} + local sum = 0 + local cnt = 0 + for idx, v in it, t, 0, math.floor(a + 1) do + sum = sum + v + cnt = cnt + 1 + end + if cnt ~= 3 then + return 3 + end + return sum +end diff --git a/test/test_infer.cpp b/test/test_infer.cpp index c99920b..84b3aee 100644 --- a/test/test_infer.cpp +++ b/test/test_infer.cpp @@ -218,7 +218,7 @@ TEST(infer, test_infer_if_scope_degrade) { // Both specializations must exist. ASSERT_NE(code.find("test_0(int64_t n)"), std::string::npos); ASSERT_NE(code.find("test_1(double n)"), std::string::npos); - ASSERT_NE(code.find("CVar test(VarClosure *_CL, CVar n)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_test(VarClosure *_CL, CVar n)"), std::string::npos); // In the int specialization (test_0), x = n with n=T_INT keeps x as T_INT. ASSERT_NE(code.find("int64_t x"), std::string::npos); // In the float specialization (test_1), MergeType(T_INT, T_FLOAT) = T_FLOAT @@ -686,10 +686,10 @@ TEST(infer, test_infer_typed_int_negative_mod) { TEST(infer, test_spec_fib) { const auto code = InferGetCCode("./infer/test_spec_fib.lua"); // Entry dispatcher must exist (original CVar signature). - ASSERT_NE(code.find("CVar fib(VarClosure *_CL, CVar n)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_fib(VarClosure *_CL, CVar n)"), std::string::npos); // Two specialization declarations — now return native types directly. - ASSERT_NE(code.find("int64_t fib_0(int64_t n)"), std::string::npos); - ASSERT_NE(code.find("double fib_1(double n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_fib_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("double flua_fn_fib_1(double n)"), std::string::npos); // Dispatcher boxes the native result back into CVar. ASSERT_NE(code.find("fib_0(n.data_.i)"), std::string::npos); ASSERT_NE(code.find("fib_1(n.data_.f)"), std::string::npos); @@ -724,7 +724,7 @@ TEST(infer, test_spec_multi_param) { ASSERT_NE(code.find("test_0_1"), std::string::npos); ASSERT_NE(code.find("test_1_1"), std::string::npos); // Entry function with CVar params. - ASSERT_NE(code.find("CVar test(VarClosure *_CL, CVar a, CVar b, CVar c, CVar d, CVar e)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_test(VarClosure *_CL, CVar a, CVar b, CVar c, CVar d, CVar e)"), std::string::npos); // int/int case: both b and e are int64_t in test_0_0. ASSERT_NE(code.find("test_0_0(CVar a, int64_t b"), std::string::npos); ASSERT_NE(code.find("test_1_1(CVar a, double b"), std::string::npos); @@ -747,7 +747,7 @@ TEST(infer, test_spec_wrapper_var) { ASSERT_NE(code.find("test_0(int64_t n)"), std::string::npos); ASSERT_NE(code.find("test_1(double n)"), std::string::npos); // Entry dispatcher must check type. - ASSERT_NE(code.find("CVar test(VarClosure *_CL, CVar n)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_test(VarClosure *_CL, CVar n)"), std::string::npos); // Functional verification: test(5) == 25. InferRunHelper([](State *s, JITType type, bool debug_mode) { @@ -782,7 +782,7 @@ TEST(infer, test_infer_bitand_integer_repr_float) { const auto code = InferGetCCode("./infer/test_infer_bitand_integer_repr_float.lua"); ASSERT_NE(code.find("test_1(double n)"), std::string::npos); ASSERT_NE(code.find("FlToIntChecked("), std::string::npos); - ASSERT_NE(code.find("CVar test_err(VarClosure *_CL)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_test_err(VarClosure *_CL)"), std::string::npos); ASSERT_NE(code.find("test_1(1.5)"), std::string::npos); InferRunHelper([](State *s, JITType type, bool debug_mode) { @@ -1155,7 +1155,7 @@ TEST(infer, test_spec_reassign_gcd) { ASSERT_NE(code.find("test_0_0(int64_t a, int64_t b)"), std::string::npos); ASSERT_NE(code.find("test_1_1(double a, double b)"), std::string::npos); // Entry dispatcher must check type and route. - ASSERT_NE(code.find("CVar test(VarClosure *_CL, CVar a, CVar b)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_test(VarClosure *_CL, CVar a, CVar b)"), std::string::npos); // In the all-int specialization, the reassignment of b must use native int64_t modulo. ASSERT_NE(code.find("FlModInt("), std::string::npos); // In the all-int specialization, while condition b != 0 must be a direct C comparison. @@ -1182,7 +1182,7 @@ TEST(infer, test_spec_reassign_powmod) { // With three math params the all-int suffix is _0_0_0. ASSERT_NE(code.find("test_0_0_0(int64_t base, int64_t exp, int64_t mod)"), std::string::npos); // Entry dispatcher must exist with CVar params. - ASSERT_NE(code.find("CVar test(VarClosure *_CL, CVar base, CVar exp, CVar mod)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_test(VarClosure *_CL, CVar base, CVar exp, CVar mod)"), std::string::npos); // In the int specialization, loop body arithmetic must use native int64_t. ASSERT_NE(code.find("FlModInt("), std::string::npos); ASSERT_NE(code.find("FlFloorDivInt("), std::string::npos); @@ -1448,7 +1448,7 @@ TEST(infer, test_spec_len_param) { ASSERT_EQ(code.find("OpLen("), std::string::npos); // Fix: NUMBER_SIGN in EvalReturnExpType now returns T_INT, so n+#s yields // a T_INT return and the int specialization returns int64_t natively. - ASSERT_NE(code.find("int64_t test_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_test_0(int64_t n)"), std::string::npos); InferRunHelper([](State *s, JITType type, bool debug_mode) { CompileFile(s, "./infer/test_spec_len_param.lua", {.debug_mode = debug_mode}); @@ -1474,7 +1474,7 @@ TEST(infer, test_spec_for_bound_param) { ASSERT_NE(code.find("test_0(int64_t n)"), std::string::npos); ASSERT_NE(code.find("test_1(double n)"), std::string::npos); // Entry dispatcher must exist. - ASSERT_NE(code.find("CVar test(VarClosure *_CL, CVar n)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_test(VarClosure *_CL, CVar n)"), std::string::npos); // In the int specialization: native int64_t for-loop control variables. ASSERT_NE(code.find("int64_t flua_for_ctrl_"), std::string::npos); ASSERT_NE(code.find("int64_t i = flua_for_ctrl_"), std::string::npos); @@ -1506,8 +1506,8 @@ TEST(infer, test_spec_for_bound_param) { TEST(infer, test_spec_compare_func_result) { const auto code = InferGetCCode("./infer/test_spec_compare_func_result.lua"); // Both f and test must be specialized and now return native int64_t directly. - ASSERT_NE(code.find("int64_t f_0(int64_t n)"), std::string::npos); - ASSERT_NE(code.find("int64_t test_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_f_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_test_0(int64_t n)"), std::string::npos); // The if condition must use a direct C comparison: f_0(n) is called natively. ASSERT_NE(code.find("f_0(n)"), std::string::npos); // With native returns, there is no .data_.i extraction for the spec call result. @@ -1535,8 +1535,8 @@ TEST(infer, test_spec_compare_func_result) { // specialised bodies, and the == condition uses OpEq + IsTrue. TEST(infer, test_spec_compare_equal) { const auto code = InferGetCCode("./infer/test_spec_compare_equal.lua"); - ASSERT_NE(code.find("int64_t test_0(int64_t n, CVar m)"), std::string::npos); - ASSERT_NE(code.find("double test_1(double n, CVar m)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_test_0(int64_t n, CVar m)"), std::string::npos); + ASSERT_NE(code.find("double flua_fn_test_1(double n, CVar m)"), std::string::npos); InferRunHelper([](State *s, JITType type, bool debug_mode) { CompileFile(s, "./infer/test_spec_compare_equal.lua", {.debug_mode = debug_mode}); @@ -1565,7 +1565,7 @@ TEST(infer, test_spec_no_arith_compare_only) { ASSERT_EQ(code.find("test_0"), std::string::npos); ASSERT_EQ(code.find("test_1"), std::string::npos); // The generic CVar entry point must exist. - ASSERT_NE(code.find("CVar test(VarClosure *_CL, CVar n, CVar m)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_test(VarClosure *_CL, CVar n, CVar m)"), std::string::npos); InferRunHelper([](State *s, JITType type, bool debug_mode) { CompileFile(s, "./infer/test_spec_no_arith_compare_only.lua", {.debug_mode = debug_mode}); @@ -1625,8 +1625,8 @@ TEST(infer, test_spec_unary_minus_param) { ASSERT_NE(code.find("(-(n))"), std::string::npos); ASSERT_EQ(code.find("OpUnaryMinus("), std::string::npos); // Both specializations must return natively (int64_t / double). - ASSERT_NE(code.find("int64_t test_0(int64_t n)"), std::string::npos); - ASSERT_NE(code.find("double test_1(double n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_test_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("double flua_fn_test_1(double n)"), std::string::npos); InferRunHelper([](State *s, JITType type, bool debug_mode) { CompileFile(s, "./infer/test_spec_unary_minus_param.lua", {.debug_mode = debug_mode}); @@ -1655,7 +1655,7 @@ TEST(infer, test_spec_bitnot_standalone_param) { // Int specialization: native BITNOT (~((int64_t)(n))). ASSERT_NE(code.find("(~((int64_t)(n)))"), std::string::npos); // Int specialization must return int64_t natively. - ASSERT_NE(code.find("int64_t test_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_test_0(int64_t n)"), std::string::npos); InferRunHelper([](State *s, JITType type, bool debug_mode) { CompileFile(s, "./infer/test_spec_bitnot_standalone_param.lua", {.debug_mode = debug_mode}); @@ -1679,7 +1679,7 @@ TEST(infer, test_spec_do_return) { ASSERT_NE(code.find("test_0(int64_t n)"), std::string::npos); ASSERT_NE(code.find("test_1(double n)"), std::string::npos); // Fix: return inside do...end is now detected, so int spec returns int64_t. - ASSERT_NE(code.find("int64_t test_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_test_0(int64_t n)"), std::string::npos); InferRunHelper([](State *s, JITType type, bool debug_mode) { CompileFile(s, "./infer/test_spec_do_return.lua", {.debug_mode = debug_mode}); @@ -1732,17 +1732,17 @@ TEST(infer, test_spec_nested_call) { ASSERT_NE(code.find("func1_0(int64_t n)"), std::string::npos); ASSERT_NE(code.find("test_0(int64_t n)"), std::string::npos); // All three specialisations must return native int64_t (not CVar). - ASSERT_NE(code.find("int64_t func2_0(int64_t n)"), std::string::npos); - ASSERT_NE(code.find("int64_t func1_0(int64_t n)"), std::string::npos); - ASSERT_NE(code.find("int64_t test_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_func2_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_func1_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_test_0(int64_t n)"), std::string::npos); // func1_0 must call func2_0 directly (native spec call, no boxing). ASSERT_NE(code.find("func2_0(n)"), std::string::npos); // test_0 must call func1_0 directly. ASSERT_NE(code.find("func1_0(n)"), std::string::npos); // CVar entry dispatchers must still exist for runtime polymorphism. - ASSERT_NE(code.find("CVar func2(VarClosure *_CL, CVar n)"), std::string::npos); - ASSERT_NE(code.find("CVar func1(VarClosure *_CL, CVar n)"), std::string::npos); - ASSERT_NE(code.find("CVar test(VarClosure *_CL, CVar n)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_func2(VarClosure *_CL, CVar n)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_func1(VarClosure *_CL, CVar n)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_test(VarClosure *_CL, CVar n)"), std::string::npos); InferRunHelper([](State *s, JITType type, bool debug_mode) { CompileFile(s, "./infer/test_spec_nested_call.lua", {.debug_mode = debug_mode}); @@ -1762,10 +1762,10 @@ TEST(infer, test_spec_nested_call) { // without missing the normal explist-based specialisation path. TEST(infer, test_spec_args_syntax_mix) { const auto code = InferGetCCode("./infer/test_spec_args_syntax_mix.lua"); - ASSERT_NE(code.find("int64_t callee_0(int64_t n)"), std::string::npos); - ASSERT_NE(code.find("double callee_1(double n)"), std::string::npos); - ASSERT_NE(code.find("int64_t test_0(int64_t n)"), std::string::npos); - ASSERT_NE(code.find("double test_1(double n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_callee_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("double flua_fn_callee_1(double n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_test_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("double flua_fn_test_1(double n)"), std::string::npos); InferRunHelper([](State *s, JITType type, bool debug_mode) { CompileFile(s, "./infer/test_spec_args_syntax_mix.lua", {.debug_mode = debug_mode}); @@ -1781,9 +1781,9 @@ TEST(infer, test_spec_args_syntax_mix) { TEST(infer, test_spec_local_from_func_call) { const auto code = InferGetCCode("./infer/test_spec_local_from_func_call.lua"); // func must be specialised (has direct arithmetic n+1). - ASSERT_NE(code.find("int64_t func_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_func_0(int64_t n)"), std::string::npos); // wrapper must also be specialised because it passes n to func's math param. - ASSERT_NE(code.find("int64_t wrapper_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_wrapper_0(int64_t n)"), std::string::npos); // local x should be declared as int64_t (not CVar) inside wrapper_0. ASSERT_NE(code.find("int64_t x ="), std::string::npos); @@ -1801,9 +1801,9 @@ TEST(infer, test_spec_local_from_func_call) { TEST(infer, test_spec_local_chain_from_func_call) { const auto code = InferGetCCode("./infer/test_spec_local_chain_from_func_call.lua"); // func must be specialised (direct arithmetic n*2). - ASSERT_NE(code.find("int64_t func_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_func_0(int64_t n)"), std::string::npos); // chain must also be specialised. - ASSERT_NE(code.find("int64_t chain_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_chain_0(int64_t n)"), std::string::npos); // Both x and y should be int64_t inside chain_0. ASSERT_NE(code.find("int64_t x ="), std::string::npos); ASSERT_NE(code.find("int64_t y ="), std::string::npos); @@ -1831,13 +1831,13 @@ TEST(infer, test_spec_local_chain_from_func_call) { TEST(infer, test_spec_local_func) { const auto code = InferGetCCode("./infer/test_spec_local_func.lua"); // local function square must be specialised. - ASSERT_NE(code.find("int64_t square_0(int64_t n)"), std::string::npos); - ASSERT_NE(code.find("double square_1(double n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_square_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("double flua_fn_square_1(double n)"), std::string::npos); // Entry dispatcher must exist for square. - ASSERT_NE(code.find("CVar square(VarClosure *_CL, CVar n)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_square(VarClosure *_CL, CVar n)"), std::string::npos); // test must also be specialised via the nested-call improvement. - ASSERT_NE(code.find("int64_t test_0(int64_t n)"), std::string::npos); - ASSERT_NE(code.find("CVar test(VarClosure *_CL, CVar n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_test_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_test(VarClosure *_CL, CVar n)"), std::string::npos); InferRunHelper([](State *s, JITType type, bool debug_mode) { CompileFile(s, "./infer/test_spec_local_func.lua", {.debug_mode = debug_mode}); @@ -1935,8 +1935,8 @@ TEST(infer, test_spec_for_break) { TEST(infer, test_spec_arith_chain) { const auto code = InferGetCCode("./infer/test_spec_arith_chain.lua"); // Both int and float specializations must be declared. - ASSERT_NE(code.find("int64_t test_0(int64_t n)"), std::string::npos); - ASSERT_NE(code.find("double test_1(double n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_test_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("double flua_fn_test_1(double n)"), std::string::npos); // x and y must be declared as native types (int64_t) in the int spec. ASSERT_NE(code.find("int64_t x ="), std::string::npos); ASSERT_NE(code.find("int64_t y ="), std::string::npos); @@ -1966,7 +1966,7 @@ TEST(infer, test_spec_repeat_arith) { ASSERT_NE(code.find("test_0(int64_t n)"), std::string::npos); ASSERT_NE(code.find("test_1(double n)"), std::string::npos); // Entry dispatcher must exist. - ASSERT_NE(code.find("CVar test(VarClosure *_CL, CVar n)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_test(VarClosure *_CL, CVar n)"), std::string::npos); // The do...while loop must be emitted. ASSERT_NE(code.find("do {"), std::string::npos); @@ -2000,8 +2000,8 @@ TEST(infer, test_spec_min_param) { ASSERT_NE(code.find("min_0_0(int64_t a, int64_t b)"), std::string::npos); ASSERT_NE(code.find("test_0_0(int64_t a, int64_t b)"), std::string::npos); // Entry dispatchers must exist. - ASSERT_NE(code.find("CVar min(VarClosure *_CL, CVar a, CVar b)"), std::string::npos); - ASSERT_NE(code.find("CVar test(VarClosure *_CL, CVar a, CVar b)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_min(VarClosure *_CL, CVar a, CVar b)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_test(VarClosure *_CL, CVar a, CVar b)"), std::string::npos); // The int specialization must use a native C comparison, not IsTrue. ASSERT_NE(code.find("(a) < (b)"), std::string::npos); ASSERT_EQ(code.find("IsTrue"), std::string::npos); @@ -2033,8 +2033,8 @@ TEST(infer, test_spec_max_param) { ASSERT_NE(code.find("max_0_0(int64_t a, int64_t b)"), std::string::npos); ASSERT_NE(code.find("test_0_0(int64_t a, int64_t b)"), std::string::npos); // Entry dispatchers must exist. - ASSERT_NE(code.find("CVar max(VarClosure *_CL, CVar a, CVar b)"), std::string::npos); - ASSERT_NE(code.find("CVar test(VarClosure *_CL, CVar a, CVar b)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_max(VarClosure *_CL, CVar a, CVar b)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_test(VarClosure *_CL, CVar a, CVar b)"), std::string::npos); // The int specialization must use a native C comparison, not IsTrue. ASSERT_NE(code.find("(a) > (b)"), std::string::npos); ASSERT_EQ(code.find("IsTrue"), std::string::npos); @@ -2067,8 +2067,8 @@ TEST(infer, test_spec_clamp_param) { ASSERT_NE(code.find("clamp_0_0_0(int64_t x, int64_t lo, int64_t hi)"), std::string::npos); ASSERT_NE(code.find("test_0_0_0(int64_t x, int64_t lo, int64_t hi)"), std::string::npos); // Entry dispatchers must exist. - ASSERT_NE(code.find("CVar clamp(VarClosure *_CL, CVar x, CVar lo, CVar hi)"), std::string::npos); - ASSERT_NE(code.find("CVar test(VarClosure *_CL, CVar x, CVar lo, CVar hi)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_clamp(VarClosure *_CL, CVar x, CVar lo, CVar hi)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_test(VarClosure *_CL, CVar x, CVar lo, CVar hi)"), std::string::npos); // Both if conditions must be emitted as native C comparisons. ASSERT_NE(code.find("(x) < (lo)"), std::string::npos); ASSERT_NE(code.find("(x) > (hi)"), std::string::npos); @@ -2133,7 +2133,7 @@ TEST(infer, test_spec_and_param) { ASSERT_NE(code.find("test_0(int64_t n)"), std::string::npos); ASSERT_NE(code.find("test_1(double n)"), std::string::npos); // Entry dispatcher must exist. - ASSERT_NE(code.find("CVar test(VarClosure *_CL, CVar n)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_test(VarClosure *_CL, CVar n)"), std::string::npos); InferRunHelper([](State *s, JITType type, bool debug_mode) { CompileFile(s, "./infer/test_spec_and_param.lua", {.debug_mode = debug_mode}); @@ -2429,7 +2429,7 @@ TEST(infer, test_infer_native_binop_mod_float) { // test(10) = 1+2+3 + 10 = 16. TEST(infer, test_spec_for_in_body) { const auto code = InferGetCCode("./infer/test_spec_for_in_body.lua"); - ASSERT_NE(code.find("CVar test(VarClosure *_CL, CVar n)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_test(VarClosure *_CL, CVar n)"), std::string::npos); ASSERT_NE(code.find("CVar sum = "), std::string::npos); ASSERT_NE(code.find("OpAdd((sum), (n),"), std::string::npos); @@ -2451,7 +2451,7 @@ TEST(infer, test_spec_elseif_no_all_return) { const auto code = InferGetCCode("./infer/test_spec_elseif_no_all_return.lua"); ASSERT_NE(code.find("test_0(int64_t n)"), std::string::npos); ASSERT_NE(code.find("test_1(double n)"), std::string::npos); - ASSERT_NE(code.find("CVar test(VarClosure *_CL, CVar n)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_test(VarClosure *_CL, CVar n)"), std::string::npos); InferRunHelper([](State *s, JITType type, bool debug_mode) { CompileFile(s, "./infer/test_spec_elseif_no_all_return.lua", {.debug_mode = debug_mode}); @@ -2473,7 +2473,7 @@ TEST(infer, test_spec_bare_return) { const auto code = InferGetCCode("./infer/test_spec_bare_return.lua"); ASSERT_NE(code.find("test_0(int64_t n)"), std::string::npos); ASSERT_NE(code.find("test_1(double n)"), std::string::npos); - ASSERT_NE(code.find("CVar test(VarClosure *_CL, CVar n)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_test(VarClosure *_CL, CVar n)"), std::string::npos); ASSERT_NE(code.find("((n) * (2))"), std::string::npos); InferRunHelper([](State *s, JITType type, bool debug_mode) { @@ -2560,8 +2560,8 @@ TEST(infer, test_spec_clamp_le) { ASSERT_NE(code.find("clamp_le_0_0_0(int64_t x, int64_t lo, int64_t hi)"), std::string::npos); ASSERT_NE(code.find("test_0_0_0(int64_t x, int64_t lo, int64_t hi)"), std::string::npos); // Entry dispatchers must exist. - ASSERT_NE(code.find("CVar clamp_le(VarClosure *_CL, CVar x, CVar lo, CVar hi)"), std::string::npos); - ASSERT_NE(code.find("CVar test(VarClosure *_CL, CVar x, CVar lo, CVar hi)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_clamp_le(VarClosure *_CL, CVar x, CVar lo, CVar hi)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_test(VarClosure *_CL, CVar x, CVar lo, CVar hi)"), std::string::npos); // Both if conditions must use native C comparisons. ASSERT_NE(code.find("(x) <= (lo)"), std::string::npos); ASSERT_NE(code.find("(x) >= (hi)"), std::string::npos); @@ -2602,8 +2602,8 @@ TEST(infer, test_spec_funcdef_assignment) { // test must be specialised too (math call improvement via square). ASSERT_NE(code.find("test_0(int64_t n)"), std::string::npos); // Entry dispatchers must exist. - ASSERT_NE(code.find("CVar square(VarClosure *_CL, CVar n)"), std::string::npos); - ASSERT_NE(code.find("CVar test(VarClosure *_CL, CVar n)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_square(VarClosure *_CL, CVar n)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_test(VarClosure *_CL, CVar n)"), std::string::npos); InferRunHelper([](State *s, JITType type, bool debug_mode) { CompileFile(s, "./infer/test_spec_funcdef_assignment.lua", {.debug_mode = debug_mode}); @@ -2664,7 +2664,7 @@ TEST(infer, test_spec_and_cond) { ASSERT_NE(code.find("test_0_0(int64_t a, int64_t b)"), std::string::npos); ASSERT_NE(code.find("test_1_1(double a, double b)"), std::string::npos); // Entry dispatcher must exist. - ASSERT_NE(code.find("CVar test(VarClosure *_CL, CVar a, CVar b)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_test(VarClosure *_CL, CVar a, CVar b)"), std::string::npos); // and 走 CompileBinop 短路,条件里会出现 IsTrue。 InferRunHelper([](State *s, JITType type, bool debug_mode) { @@ -2707,7 +2707,7 @@ TEST(infer, test_spec_or_cond) { ASSERT_NE(code.find("test_0(int64_t n)"), std::string::npos); ASSERT_NE(code.find("test_1(double n)"), std::string::npos); // Entry dispatcher must exist. - ASSERT_NE(code.find("CVar test(VarClosure *_CL, CVar n)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_test(VarClosure *_CL, CVar n)"), std::string::npos); // or 走 CompileBinop 短路,条件里会出现 IsTrue。 InferRunHelper([](State *s, JITType type, bool debug_mode) { @@ -2771,7 +2771,7 @@ TEST(infer, test_spec_concat_no_interfere) { ASSERT_NE(code.find("test_0(int64_t n)"), std::string::npos); ASSERT_NE(code.find("test_1(double n)"), std::string::npos); // Entry dispatcher must exist. - ASSERT_NE(code.find("CVar test(VarClosure *_CL, CVar n)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_test(VarClosure *_CL, CVar n)"), std::string::npos); // The arithmetic n + 1 must use the native int fast path. ASSERT_NE(code.find("((n) + (1))"), std::string::npos); @@ -2801,7 +2801,7 @@ TEST(infer, test_spec_and_or_only_no_spec) { ASSERT_EQ(code.find("test_0("), std::string::npos); ASSERT_EQ(code.find("test_1("), std::string::npos); // Only the generic CVar entry function should exist. - ASSERT_NE(code.find("CVar test(VarClosure *_CL, CVar a, CVar b)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_test(VarClosure *_CL, CVar a, CVar b)"), std::string::npos); InferRunHelper([](State *s, JITType type, bool debug_mode) { CompileFile(s, "./infer/test_spec_and_or_only_no_spec.lua", {.debug_mode = debug_mode}); @@ -3039,11 +3039,11 @@ TEST(infer, test_global_const_float) { TEST(infer, test_spec_return_arith_of_calls) { const auto code = InferGetCCode("./infer/test_spec_return_arith_of_calls.lua"); // func must be specialized. - ASSERT_NE(code.find("int64_t func_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_func_0(int64_t n)"), std::string::npos); // caller must be specialized too (func(n) arg n improves in all_int). - ASSERT_NE(code.find("int64_t caller_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_caller_0(int64_t n)"), std::string::npos); // The CVar dispatcher for caller must also exist for runtime polymorphism. - ASSERT_NE(code.find("CVar caller(VarClosure *_CL, CVar n)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_caller(VarClosure *_CL, CVar n)"), std::string::npos); InferRunHelper([](State *s, JITType type, bool debug_mode) { CompileFile(s, "./infer/test_spec_return_arith_of_calls.lua", {.debug_mode = debug_mode}); @@ -3066,9 +3066,9 @@ TEST(infer, test_spec_return_arith_of_calls) { TEST(infer, test_spec_return_call_arg_is_call) { const auto code = InferGetCCode("./infer/test_spec_return_call_arg_is_call.lua"); // f must be specialized. - ASSERT_NE(code.find("int64_t f_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_f_0(int64_t n)"), std::string::npos); // caller must be specialized. - ASSERT_NE(code.find("int64_t caller_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_caller_0(int64_t n)"), std::string::npos); InferRunHelper([](State *s, JITType type, bool debug_mode) { CompileFile(s, "./infer/test_spec_return_call_arg_is_call.lua", {.debug_mode = debug_mode}); @@ -3091,10 +3091,10 @@ TEST(infer, test_spec_return_call_arg_is_call) { TEST(infer, test_spec_return_call_arg_is_other_call) { const auto code = InferGetCCode("./infer/test_spec_return_call_arg_is_other_call.lua"); // Both func1 and func2 must be specialized. - ASSERT_NE(code.find("int64_t func2_0(int64_t n)"), std::string::npos); - ASSERT_NE(code.find("int64_t func1_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_func2_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_func1_0(int64_t n)"), std::string::npos); // caller must be specialized. - ASSERT_NE(code.find("int64_t caller_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_caller_0(int64_t n)"), std::string::npos); InferRunHelper([](State *s, JITType type, bool debug_mode) { CompileFile(s, "./infer/test_spec_return_call_arg_is_other_call.lua", {.debug_mode = debug_mode}); @@ -3117,10 +3117,10 @@ TEST(infer, test_spec_return_call_arg_is_other_call) { TEST(infer, test_spec_local_var_in_if) { const auto code = InferGetCCode("./infer/test_spec_local_var_in_if.lua"); // func must be specialized (has direct arithmetic n*2). - ASSERT_NE(code.find("int64_t func_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_func_0(int64_t n)"), std::string::npos); // outer must also be specialized with int64_t return, not CVar. // Without the fix this assertion would fail (outer_0 returns CVar). - ASSERT_NE(code.find("int64_t outer_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_outer_0(int64_t n)"), std::string::npos); // The local x inside the if block must be declared as int64_t. ASSERT_NE(code.find("int64_t x ="), std::string::npos); @@ -3146,13 +3146,13 @@ TEST(infer, test_spec_local_var_in_if) { TEST(infer, test_spec_callee_return_propagates_in_local) { const auto code = InferGetCCode("./infer/test_spec_callee_return_propagates_in_local.lua"); // inner 因直接算术而特化。 - ASSERT_NE(code.find("int64_t inner_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_inner_0(int64_t n)"), std::string::npos); // middle 的返回类型因快照注入 inner 返回类型而得到 T_INT,生成原生整数返回。 - ASSERT_NE(code.find("int64_t middle_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_middle_0(int64_t n)"), std::string::npos); // middle 中的 local r 应是 int64_t(由 inner_0 的 T_INT 返回值注入)。 ASSERT_NE(code.find("int64_t r ="), std::string::npos); // outer 因 middle 返回 T_INT 而也生成原生整数返回。 - ASSERT_NE(code.find("int64_t outer_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_outer_0(int64_t n)"), std::string::npos); InferRunHelper([](State *s, JITType type, bool debug_mode) { CompileFile(s, "./infer/test_spec_callee_return_propagates_in_local.lua", {.debug_mode = debug_mode}); @@ -3204,7 +3204,7 @@ TEST(infer, test_table_field_expr) { const auto code = InferGetCCode("./infer/test_table_field_expr.lua"); // Table subscript is dynamic, so test is not specialized. ASSERT_EQ(code.find("test_0(int64_t n)"), std::string::npos); - ASSERT_NE(code.find("CVar test(VarClosure *_CL, CVar n)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_test(VarClosure *_CL, CVar n)"), std::string::npos); InferRunHelper([](State *s, JITType type, bool debug_mode) { CompileFile(s, "./infer/test_table_field_expr.lua", {.debug_mode = debug_mode}); @@ -3300,9 +3300,9 @@ TEST(infer, test_forloop_dynamic) { TEST(infer, test_spec_for_dynamic_bound) { const auto code = InferGetCCode("./infer/test_spec_for_dynamic_bound.lua"); // mul2, iter, and test must all be specialised. - ASSERT_NE(code.find("int64_t mul2_0(int64_t n)"), std::string::npos); - ASSERT_NE(code.find("int64_t iter_0(int64_t n)"), std::string::npos); - ASSERT_NE(code.find("int64_t test_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_mul2_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_iter_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_test_0(int64_t n)"), std::string::npos); // With return-type hints, the for loop in iter_0 becomes native int. ASSERT_NE(code.find("int64_t flua_for_ctrl_"), std::string::npos); // sum must be declared as int64_t in the int specialisation. @@ -3572,7 +3572,7 @@ TEST(infer, test_spec_compare_arg_dynamic) { ASSERT_NE(code.find("add_0(int64_t n)"), std::string::npos); // test should NOT pass specialized args to add since a < b is T_DYNAMIC. // The entry function test should exist. - ASSERT_NE(code.find("CVar test(VarClosure *_CL, CVar a, CVar b)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_test(VarClosure *_CL, CVar a, CVar b)"), std::string::npos); } // 除法表达式(a / b)中操作数为数学参数时,InferNumericBinopResultType 返回 T_FLOAT。 @@ -3658,7 +3658,7 @@ TEST(infer, test_spec_direct_access) { TEST(infer, test_spec_ternary) { const auto code = InferGetCCode("./infer/test_spec_ternary.lua"); // test_0 必须被生成,且内部使用原生三元条件运算符 (n) > (0) ? 1 : 2 - ASSERT_NE(code.find("int64_t test_0(int64_t n)"), std::string::npos); + ASSERT_NE(code.find("int64_t flua_fn_test_0(int64_t n)"), std::string::npos); ASSERT_NE(code.find("? 1 : 2"), std::string::npos); InferRunHelper([](State *s, JITType type, bool debug_mode) { @@ -3676,7 +3676,7 @@ TEST(infer, test_math_spec_9params) { // Should NOT have specialized variants. ASSERT_EQ(code.find("test_math_spec_9params_0"), std::string::npos); // Entry function with CVar params. - ASSERT_NE(code.find("CVar test_math_spec_9params(VarClosure *_CL, CVar p1, CVar p2, CVar p3, CVar p4, CVar p5, CVar p6, CVar p7, CVar p8, CVar p9)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_test_math_spec_9params(VarClosure *_CL, CVar p1, CVar p2, CVar p3, CVar p4, CVar p5, CVar p6, CVar p7, CVar p8, CVar p9)"), std::string::npos); InferRunHelper([](State *s, JITType type, bool debug_mode) { CompileFile(s, "./infer/test_math_spec_9params.lua", {.debug_mode = debug_mode}); @@ -3692,13 +3692,13 @@ TEST(infer, test_math_spec_mixed) { // The name pattern suffix starts with _0_0_0_0_0_0_0_0 for 8 parameters. ASSERT_NE(code.find("test_math_spec_mixed_0_0_0_0_0_0_0_0("), std::string::npos); // The entry function should take all 10 CVar parameters. - ASSERT_NE(code.find("CVar test_math_spec_mixed(VarClosure *_CL, CVar p1, CVar p2, CVar p3, CVar p4, CVar p5, CVar p6, CVar p7, CVar p8, CVar p9, CVar p10)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_test_math_spec_mixed(VarClosure *_CL, CVar p1, CVar p2, CVar p3, CVar p4, CVar p5, CVar p6, CVar p7, CVar p8, CVar p9, CVar p10)"), std::string::npos); // Verify that specialized functions have correct parameter types: // p1 and p10 are CVar, while p2..p9 are int64_t/double depending on the specialization. - ASSERT_NE(code.find("int64_t test_math_spec_mixed_0_0_0_0_0_0_0_0(CVar p1, int64_t p2, int64_t p3, int64_t p4, int64_t p5, int64_t p6, " + ASSERT_NE(code.find("int64_t flua_fn_test_math_spec_mixed_0_0_0_0_0_0_0_0(CVar p1, int64_t p2, int64_t p3, int64_t p4, int64_t p5, int64_t p6, " "int64_t p7, int64_t p8, int64_t p9, CVar p10)"), std::string::npos); - ASSERT_NE(code.find("double test_math_spec_mixed_1_0_0_0_0_0_0_0(CVar p1, double p2, int64_t p3, int64_t p4, int64_t p5, int64_t p6, " + ASSERT_NE(code.find("double flua_fn_test_math_spec_mixed_1_0_0_0_0_0_0_0(CVar p1, double p2, int64_t p3, int64_t p4, int64_t p5, int64_t p6, " "int64_t p7, int64_t p8, int64_t p9, CVar p10)"), std::string::npos); @@ -3733,7 +3733,7 @@ TEST(infer, test_global_table_spec) { TEST(infer, test_global_init_multi_names_funcall) { const auto code = InferGetCCode("./infer/test_global_init_multi_names_funcall.lua"); // The init function must actually call func() rather than assigning nil to both. - ASSERT_NE(code.find("= func(NULL);"), std::string::npos); + ASSERT_NE(code.find("= flua_fn_func(NULL);"), std::string::npos); ASSERT_EQ(code.find("a = kNil;"), std::string::npos); ASSERT_EQ(code.find("b = kNil;"), std::string::npos); @@ -4372,6 +4372,73 @@ TEST(infer, test_infer_cvar_to_int) { }); } +// 多返回值 / vararg 展开 / 尾位置透传多返回值的函数即使命中数学参数,也不得生成 +// 标量特化(特化函数 return FlMakeMulti 会产生非法 C)。 +// test(10)=115, test(10.5)=120。 +TEST(infer, test_jitbug_multi_return_no_spec) { + const auto code = InferGetCCode("./infer/test_jitbug_multi_return_spec.lua"); + // 通用 CVar 变体必须存在。 + ASSERT_NE(code.find("CVar flua_fn_mr(VarClosure *_CL, CVar x)"), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_tailmr(VarClosure *_CL, CVar x)"), std::string::npos); + // 多返回值函数不得有任何标量特化。 + ASSERT_EQ(code.find("flua_fn_mr_0("), std::string::npos); + ASSERT_EQ(code.find("flua_fn_mr_1("), std::string::npos); + // 尾表达式为 vararg 展开,返回元数不确定。 + ASSERT_EQ(code.find("flua_fn_va_0("), std::string::npos); + ASSERT_EQ(code.find("flua_fn_va_1("), std::string::npos); + // 尾位置透传多返回值被调函数(mr2 返回 2 个值),同样不得特化。 + ASSERT_EQ(code.find("flua_fn_tailmr_0("), std::string::npos); + ASSERT_EQ(code.find("flua_fn_tailmr_1("), std::string::npos); + // 反向锁定:带单值基线的尾递归必须仍能特化(有效返回数经锚点解析为 1)。 + ASSERT_NE(code.find("flua_fn_factacc_0_0("), std::string::npos); + + InferRunHelper([](State *s, JITType type, bool debug_mode) { + CompileFile(s, "./infer/test_jitbug_multi_return_spec.lua", {.debug_mode = debug_mode}); + int64_t ri = 0; + Call(s, type, "test", ri, 10); + ASSERT_EQ(ri, 235); + double rf = 0; + Call(s, type, "test", rf, 10.5); + ASSERT_NEAR(rf, 240.0, 0.001); + }); +} + +// 尾参数展开(调用末参为需展开的调用,且含动态类型算术语句宏)不得把语句拼进 +// 表达式位置;generic-for 超过 3 个表达式时丢弃位同理。test()=60。 +TEST(infer, test_jitbug_tail_expand_stmt) { + const auto code = InferGetCCode("./infer/test_jitbug_tail_expand_stmt.lua"); + // generic-for 第 4 个(丢弃)表达式以独立语句形式求值。 + ASSERT_NE(code.find("(void)("), std::string::npos); + + InferRunHelper([](State *s, JITType type, bool debug_mode) { + CompileFile(s, "./infer/test_jitbug_tail_expand_stmt.lua", {.debug_mode = debug_mode}); + int64_t ret = -1; + Call(s, type, "test", ret); + ASSERT_EQ(ret, 60); + }); +} + +// 顶层函数名为 C 保留入口名(main)或 libc 符号(sin)时,C 符号统一加 flua_fn_ +// 前缀;Lua 侧仍按原名调用。 +TEST(infer, test_jitbug_reserved_func_name) { + const auto code = InferGetCCode("./infer/test_jitbug_reserved_func_name.lua"); + ASSERT_NE(code.find("CVar flua_fn_main("), std::string::npos); + ASSERT_NE(code.find("CVar flua_fn_sin("), std::string::npos); + ASSERT_EQ(code.find("CVar main("), std::string::npos); + ASSERT_EQ(code.find("CVar sin("), std::string::npos); + + InferRunHelper([](State *s, JITType type, bool debug_mode) { + CompileFile(s, "./infer/test_jitbug_reserved_func_name.lua", {.debug_mode = debug_mode}); + int64_t ret = 0; + Call(s, type, "main", ret, 41); + ASSERT_EQ(ret, 42); + Call(s, type, "sin", ret, 21); + ASSERT_EQ(ret, 42); + Call(s, type, "test", ret); + ASSERT_EQ(ret, 0); + }); +} + TEST(infer, test_spec_literal_keys) { InferRunHelper([](State *s, JITType type, bool debug_mode) { CompileFile(s, "./infer/test_spec_literal_keys.lua", {.debug_mode = debug_mode});