fix(tpv): 外部函数返回类型不可收敛时 fail-fast,豁免 from-import 与多层模块限定调用

- _infer_external_func_ret_ty 白名单未命中改为 fatal_error(Python 风格
  traceback),移除默认 i32 的 WARN 输出
- 裸名调用 fail-fast 前查 from_imports 可解析性:from-import 函数走
  mangled 精确解析收敛(SSAValue 场景),纯外部 C 函数才 fail-fast
- mod_ret_sha1 解析支持多层模块路径(w32.win32file.CreatePipe 场景),
  避免嵌套模块限定调用被误判为裸名外部函数
- 移除 FuncGenBase/StructGenMixin 遗留调试输出
This commit is contained in:
TermiNexus committed 2026-08-29 05:46:25 +08:00
1 parent 5551db3432
commit 4748915472
6 files changed
+68 -40

No files matched your search

+22 -13
View File
@@ -4,7 +4,6 @@ import ast
import llvmlite
import memhub
import string
import lib.core.VLogger as VLogger
import lib.core.Gen.Translator as HT
from . import CallGenBase
@@ -111,13 +110,21 @@ class CallGenLib(CallGenBase.CallGenBase):
llvmlite.add_param(pool, fn, i64_ty, "n")
return fn
def _infer_external_func_ret_ty(self, pool: memhub.MemBuddy | t.CPtr,
func_name: str) -> llvmlite.LLVMType | t.CPtr:
func_name: str,
node: ast.AST | t.CPtr,
fatal_if_unknown: int) -> llvmlite.LLVMType | t.CPtr:
"""根据函数名推断外部 includes 函数的返回类型
支持三种函数名形式:
1. 裸名: "parse", "strlen"
2. 别名: "json_parse"(from X import parse as json_parse)
3. 带 SHA1 前缀: "240a9a4157959a9f.parse"(跨模块调用 mangled name)
fatal_if_unknown: 白名单全部未命中时的行为。
=1(纯裸名外部函数,无法收敛)→ fatal_error fail-fast(默认 i32
会截断指针/i64,静默生成导致运行时数据损坏,不兜底原则)。
=0(Phase A 中间遍,_deps_pending 下遍重译可收敛)→ 返回默认
i32 占位,不输出 WARN(下遍依赖就绪后走 text.ll/vpi 精确解析)。
"""
if pool is None or func_name is None:
return llvmlite.Int32(pool)
@@ -296,16 +303,16 @@ class CallGenLib(CallGenBase.CallGenBase):
string.strcmp(func_name, "function_get_param_head") == 0 or string.strcmp(func_name, "param_get_next") == 0:
return llvmlite.Ptr(pool, llvmlite.Int8(pool))
# 默认 i32(整数):与本模块函数定义的默认返回类型一致(HandlesFunctions.py line 324)
# 已知返回指针的函数在上方显式列出(返回 i8*),已知返回 i64 的函数也显式列出
# 避免使用 i64 导致 stub 声明(declare i64)与实际定义(define i32)类型不匹配
# x86-64 ABI 中 i32 返回值在 rax 低 32 位,与 define i32 完全一致
# WARNING: 未登记的外部函数返回类型默认推断为 i32,若实际返回指针/i64 会被截断
# --nowarn-register 可抑制此 WARN(自举编译中大量出现,刷屏影响日志收敛)
if VLogger._nowarn_register == 0:
VLogger.warning(
f"外部函数返回类型未登记,默认推断为 i32(可能截断指针/i64): {func_name}",
"HandlesExprCall")
# 默认 i32:无登记、无模块归属、白名单全部未命中的外部函数。
# fatal_if_unknown=1(纯裸名外部调用,无法收敛)→ fail-fast 报错:
# 默认 i32 会截断指针/i64,静默生成导致运行时数据损坏(不兜底原则)。
# fatal_if_unknown=0(Phase A 中间遍,_deps_pending 下遍重译收敛)→
# 保持默认 i32 占位,不输出 WARN(下遍依赖就绪后走 text.ll/vpi 精确解析)。
if fatal_if_unknown != 0:
self.Gen.TypeGenErr.fatal_error(
node,
f"外部函数返回类型未登记且无法收敛推断(默认 i32 会截断指针/i64,需提供 stub 声明或加入白名单): {func_name}",
"TypeError")
return llvmlite.Int32(pool)
def _get_external_func_param_ty(self, pool: memhub.MemBuddy | t.CPtr,
func_name: str,
@@ -566,7 +573,9 @@ class CallGenLib(CallGenBase.CallGenBase):
# 返回类型(使用访问器)
call_ret_ty: llvmlite.LLVMType | t.CPtr = llvmlite.function_get_ret_ty(found_func)
if call_ret_ty is None:
call_ret_ty = self._infer_external_func_ret_ty(pool, func_name)
# found_func 已有 declare 却读不到返回类型(访问器异常,罕见防御):
# 白名单未命中也不 fail-fast,保持默认 i32(有声明即有类型来源)。
call_ret_ty = self._infer_external_func_ret_ty(pool, func_name, None, 0)
# memcpy → @llvm.memcpy 内联函数(避免 monomorphization 跨模块 @memcpy 声明缺失)
if string.strcmp(func_name, "memcpy") == 0 and actual_count >= 3:
+41 -16
View File
@@ -729,21 +729,29 @@ class CallGenMixin(CallGenBase.CallGenBase):
mod_ret_sha1: str = None
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
at_ret: ast.Attribute | t.CPtr = (ast.Attribute | t.CPtr)(cl.func)
# 解析调用模块路径:单层(stdlib.free,value 是 Name)与多层
# (w32.win32file.CreatePipe,value 是嵌套 Attribute)都支持。
# 缺失多层路径 → mod_ret_sha1=None → 误走裸名外部函数路径,
# fail-fast 误杀可收敛的模块限定调用(CreatePipe 根因)。
mod_path_ret: str = None
if at_ret.value is not None and at_ret.value.kind() == ast.ASTKind.Name:
mod_ret_nm: ast.Name | t.CPtr = (ast.Name | t.CPtr)(at_ret.value)
if mod_ret_nm.id is not None:
mod_ret_sha1 = self._lookup_module_sha1(mod_ret_nm.id)
if mod_ret_sha1 is not None:
# 函数内懒 import:VpiReader imports HandlesExprCall,
# 顶层 import 会成环(与 _lookup_method_ret_ty_via_vpi 一致)
VpiReader._ensure_vpi_ret_annot_loaded(mod_ret_sha1, self.Gen.CallGenBase.TempDir)
ret_annot_fc: str = VpiReader._lookup_vpi_ret_annot(
mod_ret_sha1, None, func_name)
if ret_annot_fc is not None:
vpi_ret_ty = VpiReader._resolve_vpi_ret_annot_str(
pool, ret_annot_fc,
llvmlite.Int32(pool),
llvmlite.Ptr(pool, llvmlite.Int8(pool)))
mod_path_ret = mod_ret_nm.id
elif at_ret.value is not None and at_ret.value.kind() == ast.ASTKind.Attribute:
mod_path_ret = self.Gen.ExprGenAttr._attr_module_path_str(pool, at_ret.value)
if mod_path_ret is not None:
mod_ret_sha1 = self._lookup_module_sha1(mod_path_ret)
if mod_ret_sha1 is not None:
# 函数内懒 import:VpiReader imports HandlesExprCall,
# 顶层 import 会成环(与 _lookup_method_ret_ty_via_vpi 一致)
VpiReader._ensure_vpi_ret_annot_loaded(mod_ret_sha1, self.Gen.CallGenBase.TempDir)
ret_annot_fc: str = VpiReader._lookup_vpi_ret_annot(
mod_ret_sha1, None, func_name)
if ret_annot_fc is not None:
vpi_ret_ty = VpiReader._resolve_vpi_ret_annot_str(
pool, ret_annot_fc,
llvmlite.Int32(pool),
llvmlite.Ptr(pool, llvmlite.Int8(pool)))
if vpi_ret_ty is not None:
call_ret_ty = vpi_ret_ty
elif mod_ret_sha1 is not None:
@@ -766,9 +774,23 @@ class CallGenMixin(CallGenBase.CallGenBase):
if trans is not None and trans._declare_only == 0:
if self._dep_text_ll_exists(pool, mod_ret_sha1) == 0:
trans._deps_pending = 1
call_ret_ty = self.Gen.CallGenLib._infer_external_func_ret_ty(pool, func_name)
# 已标记 _deps_pending,下遍重译会收敛(依赖 text.ll 就绪后
# 走精确解析)→ fatal_if_unknown=0,本遍仅临时占位。
call_ret_ty = self.Gen.CallGenLib._infer_external_func_ret_ty(pool, func_name, node, 0)
else:
call_ret_ty = self.Gen.CallGenLib._infer_external_func_ret_ty(pool, func_name)
# 裸名调用(cl.func 是 Name,mod_ret_sha1=None)。两类来源:
# 1. from-import 的函数(如 SSAValue from .__values)→ 后续
# L1193-1251 解析模块归属构造 mangled call_name,L1272 经
# text.ll/vpi 精确解析返回类型(可收敛)→ fatal_if_unknown=0
# 仅临时占位,不 fail-fast。
# 2. 纯外部 C 函数(无登记、无模块归属)→ 白名单未命中无法收敛
# → fail-fast(fatal_if_unknown=1)。
bare_resolvable: int = 0
if trans is not None and trans._from_imports is not None:
if self._lookup_mod_sha1(pool, trans._from_imports, func_name, 0) is not None:
bare_resolvable = 1
call_ret_ty = self.Gen.CallGenLib._infer_external_func_ret_ty(
pool, func_name, node, 1 - bare_resolvable)
if found_func is None:
fp_alloca: llvmlite.Value | t.CPtr = None
@@ -1255,7 +1277,10 @@ class CallGenMixin(CallGenBase.CallGenBase):
# 用 orig_func_name="parse" 重新推断,能正确匹配返回 i8*(JsonValue*)
# 避免指针被截断为 i32 后 inttoptr 丢失高 32 位导致崩溃
if orig_func_name is not None and orig_func_name != func_name:
re_inferred_ty: llvmlite.LLVMType | t.CPtr = self.Gen.CallGenLib._infer_external_func_ret_ty(pool, orig_func_name)
# 别名附加尝试:仅作为 mangled 精确解析前的临时推断(fatal_if_unknown=0)。
# 若 call_name 是 mangled,L1272 起的 text.ll/vpi 精确解析会覆盖并 fail-fast;
# 未命中时保持白名单/默认 i32 由后续路径兜底。
re_inferred_ty: llvmlite.LLVMType | t.CPtr = self.Gen.CallGenLib._infer_external_func_ret_ty(pool, orig_func_name, node, 0)
if re_inferred_ty is not None:
call_ret_ty = re_inferred_ty
# ============================================================
+1 -1
View File
@@ -50,7 +50,7 @@ class FuncGenBase(CallGenBase.CallGenBase):
cur_file: str = self.Gen.TypeGenResolve.get_current_file()
if cur_file is None:
cur_file = "?"
stdio.printf("[ATTR] extract_func_attrs: file='%s' dlist_len=%d\n", cur_file, dn)
for di in range(dn):
deco: ast.AST | t.CPtr = decorator_list.get(di)
if deco is None:
-6
View File
@@ -401,12 +401,6 @@ class StructGenMixin:
fe.DefaultVal = default_val
struct_entry.FieldCount = idx + 1
# [ADF-DIAG] 确认字段表实际注册内容(定位 Thread.arg 缺失/索引错位)
dbg_adf: str = fname_pool.alloc(192)
if dbg_adf is not None:
viperlib.snprintf(dbg_adf, 192, "[ADF] struct=%s field=%s idx=%d\n",
struct_entry.Name, field_name, idx)
stdio.printf("%s", dbg_adf)
return idx
def _deep_copy_default_ast(self, pool: memhub.MemBuddy | t.CPtr,
+3 -3
View File
@@ -208,9 +208,9 @@ def init_logger():
# ============================================================
# --nowarn-register 开关(追加):抑制 Phase A 依赖类 WARN 输出——
# ①"外部函数返回类型未登记"(HandlesExprCall)②"Phase A 依赖未就绪,
# 下遍重试"(PhaseA-DBG)。自举编译中大量出现,刷屏影响日志收敛。
# 仅抑制输出,不影响内部逻辑(默认 i32 推断、标记重试等照常执行)。
# ①"跨模块函数依赖模块 text.ll 尚未生成"(HandlesExprCall,Phase A 多遍
# 收敛的中间遍,下遍重译后精确解析)。自举编译中大量出现,刷屏影响日志收敛。
# 仅抑制输出,不影响内部逻辑(标记重试等照常执行)。
# ============================================================
_nowarn_register: int = 0
+1 -1
View File
@@ -236,7 +236,7 @@ Target: %s
BuildPipeline._phase2_cmd_path = p2c
log.info(f"--phase2command: 不执行 llc/链接,命令写入 {p2c}", "phase2cmd")
# --nowarn-register:抑制"外部函数返回类型未登记"类 WARN 输出
# --nowarn-register:抑制 Phase A 依赖类 WARN 输出(跨模块 text.ll 未生成,下遍收敛)
if args.get_bool("nowarn-register"):
VLogger._nowarn_register = 1
log.info("--nowarn-register 已启用:抑制注册类 WARN 输出", "vlogger")