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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 61 additions & 18 deletions src/compile/c_gen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,14 @@ GenResult CGen::Generate(const ParseResult &pr, const InferResult &ir, const Ana
ir_ = ir;
ar_ = ar;

// 运行构建主流程
GenResult gr = Build(pr, cfg);
// 运行构建主流程。逃逸出 Build 的 spec-miss 说明该处数值特化无可回退路径,
// 仍是与历史一致的硬编译错误(含同样的 ERROR 日志与报错文本)。
GenResult gr;
try {
gr = Build(pr, cfg);
} catch (const FakeluaSpecMissException &e) {
ThrowFakeluaException(std::format("Code generate failed, {}", e.what()));
}

LOG_DEBUG(s_, "engine", "CGen::Build complete: {} functions, {} bytes of C code", gr.function_names.size(), gr.c_code.size());

Expand Down Expand Up @@ -574,6 +580,12 @@ void CGen::GenerateDecls(const SyntaxTreeInterfacePtr &chunk, GenResult &gr) {
// 本地调用(同文件直接调用)。
gr.function_names[spec_name] = JitFunctionInfo{static_cast<int>(func->params.size()), is_vararg, spec_name};
}
// 动态去优化变体(签名与入口分发器一致),前向声明与定义保持同步。
Out() << "CVar " << name << "__dyn(VarClosure *_CL";
for (size_t i = 0; i < cparams.size(); ++i) {
Out() << ", CVar " << cparams[i];
}
Out() << ");\n";
}
}
}
Expand Down Expand Up @@ -602,6 +614,12 @@ std::string CGen::CompileFuncName(const SyntaxTreeInterfacePtr &ptr) {
ThrowFakeluaException(std::format("Code generate failed, {} at {}", msg, SyntaxTreeLocationStr(file_name_, ptr)));
}

[[noreturn]] void CGen::ThrowSpecMiss(const std::string &msg, const SyntaxTreeInterfacePtr &ptr) {
// 与 ThrowError 文本保持一致,仅省去 BuildFakeluaErrorMessage 的 ERROR 日志:
// 这是「试不了原生就回退动态」的常规信号,每个多态函数都可能触发,不应刷错误日志。
throw FakeluaSpecMissException(std::format("{} at {}", msg, SyntaxTreeLocationStr(file_name_, ptr)));
}

bool CGen::BlockEndsWithReturn(const SyntaxTreeInterfacePtr &block) {
const auto block_ptr = std::dynamic_pointer_cast<SyntaxTreeBlock>(block);
const auto &stmts = block_ptr->Stmts();
Expand Down Expand Up @@ -687,7 +705,22 @@ void CGen::GenerateImpl(const SyntaxTreeInterfacePtr &chunk, GenResult &gr) {
}
Out() << "}\n";
}
GenerateEntryDispatcher(name, func->name, c_func_params, math_params);
// 动态去优化变体:与未特化函数同样按 CVar 动态编译。入口分发器在
// 数学参数为非数值(string/bool/nil 等)时分派到这里,由函数体内的
// Lua 层 type() 守卫/动态运算决定语义(与 INTERP 后端行为一致),
// 而不是在函数入口直接抛错导致整个进程 abort。
const auto dyn_name = name + "__dyn";
Out() << "CVar " << dyn_name << "(VarClosure *_CL";
for (size_t i = 0; i < c_func_params.size(); ++i) {
Out() << ", CVar " << c_func_params[i];
}
Out() << ") {\n";
CompileFuncBody(func->name, func_block, -1, sections_[static_cast<size_t>(Section::Impls)]);
if (!BlockEndsWithReturn(func_block)) {
Out() << " return kNil;\n";
}
Out() << "}\n";
GenerateEntryDispatcher(name, func->name, c_func_params, math_params, dyn_name);
} else {
Out() << "CVar " << name << "(VarClosure *_CL";
for (size_t i = 0; i < c_func_params.size(); ++i) {
Expand Down Expand Up @@ -720,7 +753,8 @@ void CGen::GenerateImpl(const SyntaxTreeInterfacePtr &chunk, GenResult &gr) {
}
}

void CGen::GenerateEntryDispatcher(const std::string &c_name, const std::string &lua_name, const std::vector<std::string> &func_params, const std::vector<int> &math_param_indices) {
void CGen::GenerateEntryDispatcher(const std::string &c_name, const std::string &lua_name, const std::vector<std::string> &func_params, const std::vector<int> &math_param_indices,
const std::string &dyn_name) {
const int k = static_cast<int>(math_param_indices.size());
const int num_specs = 1 << k;

Expand All @@ -730,13 +764,22 @@ void CGen::GenerateEntryDispatcher(const std::string &c_name, const std::string
}
Out() << ") {\n";

// 任一数学参数不是数值时(string/bool/nil/table 等),spec miss → 去优化到
// 动态变体执行,对齐 INTERP 后端:函数体内可能存在 type() 守卫提前 return,
// 或 tonumber() 转换等合法 Lua 语义,入口无权直接判错。
Out() << " if (UNLIKELY(";
for (int i = 0; i < k; ++i) {
const int param_idx = math_param_indices[i];
const auto &mp_name = func_params[static_cast<size_t>(param_idx)];
Out() << std::format(" if (UNLIKELY({0}.type_ != VAR_INT && {0}.type_ != VAR_FLOAT)) {{ FakeluaThrowError(_S, \"bad argument #{1} ({2}): "
"attempt to perform arithmetic on non-numeric value\"); }}\n",
mp_name, param_idx + 1, mp_name);
if (i > 0) {
Out() << " || ";
}
const auto &mp_name = func_params[static_cast<size_t>(math_param_indices[i])];
Out() << std::format("({0}.type_ != VAR_INT && {0}.type_ != VAR_FLOAT)", mp_name);
}
Out() << ")) { return " << dyn_name << "(_CL";
for (size_t i = 0; i < func_params.size(); ++i) {
Out() << ", " << func_params[i];
}
Out() << "); }\n";

Out() << " int flua_spec_idx = ";
for (int i = 0; i < k; ++i) {
Expand Down Expand Up @@ -927,7 +970,7 @@ bool CGen::TryInferMathCallBitmask(const std::string &callee_name, const std::ve
for (int i = 0; i < static_cast<int>(math_params.size()); ++i) {
const int param_pos = math_params[i];
if (param_pos >= static_cast<int>(raw_args.size())) {
return false;// fewer args than math params: let slow-path ThrowError handle it
return false;// fewer args than math params: let slow-path deopt handle it
}
const auto &arg = raw_args[static_cast<size_t>(param_pos)];
DEBUG_ASSERT(arg && arg->Type() == SyntaxTreeType::Exp);
Expand Down Expand Up @@ -2716,7 +2759,7 @@ std::string CGen::CompileRawNativeArithBinop(const SyntaxTreeInterfacePtr &left,
Out() << GenTab() << std::format("FlToIntChecked(({}), {});\n", native_operand, itmp);
return itmp;
}
ThrowError("bitwise operand is not numeric", operand_node);
ThrowSpecMiss("bitwise operand is not numeric", operand_node);
return {};
};

Expand Down Expand Up @@ -3084,7 +3127,7 @@ std::string CGen::CompileNumericExp(const SyntaxTreeInterfacePtr &exp) {
if (LookupNodeType(e.get()) == T_FLOAT) {
return std::format("{}", ToFloat(e->ExpValue()));
}
ThrowError("number node is not inferred as numeric", exp);
ThrowSpecMiss("number node is not inferred as numeric", exp);
} else if (exp_kind == ExpKind::kPrefixExp) {
const auto pe = std::dynamic_pointer_cast<SyntaxTreePrefixexp>(e->Right());
DEBUG_ASSERT(pe);
Expand Down Expand Up @@ -3116,12 +3159,12 @@ std::string CGen::CompileNumericExp(const SyntaxTreeInterfacePtr &exp) {
if (pe->GetPrefixKind() == PrefixExpKind::kFunctionCall) {
const auto inferred = GetType(exp);
if (inferred == T_DYNAMIC) {
ThrowError("function call cannot be specialized as numeric", exp);
ThrowSpecMiss("function call cannot be specialized as numeric", exp);
}
if (const auto native_result = TryCompileNativeSpecCallExpr(pe->GetValue()); !native_result.empty()) {
return native_result;
}
ThrowError("function call cannot be specialized as numeric", exp);
ThrowSpecMiss("function call cannot be specialized as numeric", exp);
}
} else if (exp_kind == ExpKind::kBinop) {
const auto op = std::dynamic_pointer_cast<SyntaxTreeBinop>(e->Op());
Expand Down Expand Up @@ -3161,7 +3204,7 @@ std::string CGen::CompileNumericExp(const SyntaxTreeInterfacePtr &exp) {
if (!res.empty()) {
return res;
}
ThrowError("operator is not supported in numeric specialization", exp);
ThrowSpecMiss("operator is not supported in numeric specialization", exp);
} else if (exp_kind == ExpKind::kUnop) {
const auto op = std::dynamic_pointer_cast<SyntaxTreeUnop>(e->Op());
DEBUG_ASSERT(op);
Expand All @@ -3179,12 +3222,12 @@ std::string CGen::CompileNumericExp(const SyntaxTreeInterfacePtr &exp) {
return res;
}
if (op_kind == UnOpKind::kBitNot) {
ThrowError("bitwise operand is not numeric", e->Right());
ThrowSpecMiss("bitwise operand is not numeric", e->Right());
}
ThrowError("unary operator is not supported in numeric specialization", exp);
ThrowSpecMiss("unary operator is not supported in numeric specialization", exp);
}

ThrowError("unsupported numeric-specialized expression", exp);
ThrowSpecMiss("unsupported numeric-specialized expression", exp);
}

// TryCompileNativeSpecCallExpr —— 将调用特化函数的结果编译为原生数值临时变量
Expand Down
6 changes: 5 additions & 1 deletion src/compile/c_gen.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ class CGen {
void CompileFuncBody(const std::string &func_name, const SyntaxTreeInterfacePtr &func_block, int spec_bitmask, std::ostream &out);
// 为特定函数生成分发入口(用于处理动态调用到特化强类型调用的转发)
// c_name 是 C 符号(unique_c_name),lua_name 用于查 specialization_return_types。
void GenerateEntryDispatcher(const std::string &c_name, const std::string &lua_name, const std::vector<std::string> &func_params, const std::vector<int> &math_param_indices);
// dyn_name 是动态去优化变体的 C 符号:数学参数非数值时分派到它而非抛错。
void GenerateEntryDispatcher(const std::string &c_name, const std::string &lua_name, const std::vector<std::string> &func_params, const std::vector<int> &math_param_indices,
const std::string &dyn_name);
// 辅助工具:判断一个基本块(Block)的结尾是否显式包含 return 语句
[[nodiscard]] static bool BlockEndsWithReturn(const SyntaxTreeInterfacePtr &block);
// 获取特定特化签名下函数的返回类型
Expand Down Expand Up @@ -262,6 +264,8 @@ class CGen {
// ==========================================
// 代码生成阶段抛出带有上下文树节点的异常错误
[[noreturn]] void ThrowError(const std::string &msg, const SyntaxTreeInterfacePtr &ptr);
// 试探性原生数值编译失败的静默信号(不打 ERROR 日志),供回退路径捕获
[[noreturn]] void ThrowSpecMiss(const std::string &msg, const SyntaxTreeInterfacePtr &ptr);
// 根据当前缩进深度生成 C 代码缩进空白字符
[[nodiscard]] std::string GenTab() const;

Expand Down
10 changes: 10 additions & 0 deletions src/util/exception.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ class FakeluaException : public std::runtime_error {
}
};

// 静默的「无法按数值特化编译」信号:CGen 试探性地把表达式编译为原生数值失败时抛出,
// 由 TryCompileNativeExpr 等回退路径捕获并改走动态编译,属于正常控制流,不记录 ERROR。
// 若它逃逸到 CGen 顶层(调用点不允许回退),会在边界处转换为普通 FakeluaException
// 编译错误,保持与历史行为一致的对外报错。
class FakeluaSpecMissException : public FakeluaException {
public:
explicit FakeluaSpecMissException(const std::string &msg) : FakeluaException(msg) {
}
};

[[noreturn]] void ThrowFakeluaException(const std::string &msg);

// 构造 FakeluaException 的错误文本(追加当前堆栈)并记录日志,但不抛出。
Expand Down
142 changes: 142 additions & 0 deletions test/lua/infer/test_spec_deopt.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
-- Math-specialized functions must deoptimize to a dynamic variant when a math
-- parameter receives a non-numeric value (string/bool/nil/table), instead of
-- throwing a hard error at the entry dispatcher. This keeps JIT (TCC/GCC)
-- behavior consistent with the INTERP backend and with Lua semantics: the
-- function body may contain a type() guard, and real arithmetic errors must
-- remain pcall-catchable rather than aborting the whole process.

-- type() guard: non-numeric argument takes the fallback branch.
function deopt_guard(x)
if type(x) ~= "number" then
return -1
end
return x + 1
end

-- Two math parameters: any non-numeric one triggers deoptimization.
function deopt_multi(a, b)
if type(a) ~= "number" or type(b) ~= "number" then
return 0
end
return a * 2 + b
end

-- Dynamic variant must be allowed to return a value of a different kind
-- (string) than the numeric specialized variants.
function deopt_kind(x)
if type(x) == "number" then
return x + 100
end
return "kind"
end

-- No guard: arithmetic on a non-numeric value must surface as a Lua error
-- (pcall catches it), not crash the process.
function deopt_arith(x)
return x + 1
end

-- Recursive specialized function reached with a non-numeric argument.
function deopt_fact(n)
if type(n) ~= "number" then
return -1
end
if n <= 1 then
return 1
end
return n * deopt_fact(n - 1)
end

-- Specialized function without explicit return at block end:
-- exercise !BlockEndsWithReturn(func_block) in __dyn emitting "return kNil;"
function deopt_noreturn(x)
if type(x) ~= "number" then
-- no return
else
x = x + 1
end
end

local function dyn_callee()
return "dyn_str"
end

-- Expressions with unsupported operators or dynamic calls trigger
-- ThrowSpecMiss during tentative numeric compilation and fall back cleanly.
function deopt_unsupported_ops(x)
if type(x) ~= "number" then
return -1
end
if x < 0 then
local a = x + (~"abc")
local b = x + (#"abc")
local c = x + ("a" .. "b")
local d = x + dyn_callee()
return a + b + c + d
end
return x * 2
end

-- In-Lua verification entry: returns 0 only when every expectation holds.
function test_deopt()
if deopt_guard(10) ~= 11 then
return 1
end
if deopt_guard("abc") ~= -1 then
return 2
end
if deopt_guard(true) ~= -1 then
return 3
end
local ok4, res4 = pcall(deopt_guard)
if not ok4 or res4 ~= -1 then
return 4
end
if deopt_multi(1, 2) ~= 4 then
return 5
end
if deopt_multi("x", 2) ~= 0 then
return 6
end
if deopt_multi(2, "y") ~= 0 then
return 7
end
local ok8, res8 = pcall(deopt_multi, 1)
if not ok8 or res8 ~= 0 then
return 8
end
if deopt_kind(5) ~= 105 then
return 9
end
if deopt_kind("z") ~= "kind" then
return 10
end
if deopt_fact(5) ~= 120 then
return 11
end
if deopt_fact("z") ~= -1 then
return 12
end
if pcall(deopt_arith, "abc") then
return 13
end
if pcall(deopt_arith, {}) then
return 14
end
if deopt_arith(41) ~= 42 then
return 15
end
if deopt_noreturn(10) ~= nil then
return 16
end
if deopt_noreturn("abc") ~= nil then
return 17
end
if deopt_unsupported_ops(5) ~= 10 then
return 18
end
if deopt_unsupported_ops("z") ~= -1 then
return 19
end
return 0
end
7 changes: 5 additions & 2 deletions test/test_exception.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1156,21 +1156,24 @@ TEST(exception, function_too_many_params) {
}
}

// 非数值参数传入数学特化函数:入口不再直接硬失败,而是去优化到 __dyn 动态
// 变体执行函数体;函数体内的算术仍抛错(可 pcall),三后端消息一致,
// 对齐 INTERP 后端历来行为。
TEST(exception, math_param_non_numeric_error) {
FakeluaStateGuard sg;
auto s = sg.GetState();
ASSERT_NE(s, nullptr);
SetDebugLogLevel(s, 0);
CompileFile(s, "./exception/test_math_param_non_numeric_error.lua", {});

for (auto jit_type: CCodeJitTypes()) {
for (auto jit_type: AllJitTypes()) {
try {
CVar ret;
Call(s, jit_type, "test", ret, "hello", 1);
ASSERT_TRUE(false);
} catch (const std::exception &e) {
std::cout << e.what() << std::endl;
ASSERT_TRUE(std::string(e.what()).find("bad argument #1 (a): attempt to perform arithmetic on non-numeric value") != std::string::npos);
ASSERT_TRUE(std::string(e.what()).find("attempt to perform arithmetic on non-numeric value") != std::string::npos);
}
}
}
Expand Down
Loading
Loading