From dc4e06d88bb502b808a26f93f57d64cdf1fccc80 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Thu, 1 Oct 2026 14:46:47 +0800 Subject: [PATCH] =?UTF-8?q?fix(TPV):=20=E6=9C=AC=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E8=87=AA=E5=B7=B1=E5=A3=B0=E6=98=8E=E7=9A=84=20extern=20?= =?UTF-8?q?=E8=A3=B8=E5=90=8D=E4=BC=98=E5=85=88=E4=BA=8E=E4=BE=9D=E8=B5=96?= =?UTF-8?q?=20vpi=20=E6=B3=A8=E5=85=A5=E7=9A=84=E5=90=8C=E5=90=8D=E5=A3=B0?= =?UTF-8?q?=E6=98=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 现场:Test/WebTest 编译 includes/socket.vp:236 报 CastError: 指针隐式转指针已禁止:源 %"56d0ee99ecf0a0b5.SocketAddr"* → 目标 %"99c88d4cbd933ac6.WSASocketAddr"* 源码: result = connect(self.fd, c.Addr(addr), 16) 机制(**链接期无冲突**——两边都不是 export,@connect 是同一个 C 符号,最终只有 ws2_32 里一个定义;问题在"编译 socket.vp 时的同一个模块内"): 1. extern/state/export 声明的符号**不带 SHA1 前缀**(FuncGenDef.vp:172-173 `mangled_name = fd.name`)⇒ socket.vp 与 w32.winsock2 都产出裸名 @connect (前者形参 SocketAddr|t.CPtr,后者 WSASocketAddr|t.CPtr)。 2. Translator 的 prologue 先注入依赖 w32.winsock2 的 vpi (StubMergerBuild.LoadDepVpiIntoModule → VpiReader.ParseVpiIntoModule → _inject_function_declare:create_declare + module_add_function),当前模块里 先出现了 winsock2 版的 @connect。 3. 随后 forward_declare_functions 处理 socket.vp 自己的 `extern def connect` 时, find_func_in_module 命中已存在 ⇒ `continue`(FuncGenDef.vp:178-182) ⇒ **本文件自己的声明被静默丢弃**,调用点改按 winsock2 的形参 coerce (报错里的目标类型 WSASocketAddr* 正是 winsock2 版的指纹)。 (同文件 send/recv/socket/listen 亦被顶掉,只是形参是 i8*/u64,不报错—— 即 socket.vp 的 POSIX 分支本来就在调用 winsock2 的声明。) 改动(注入期跳过"本文件已声明的裸名",让本文件自己的声明优先): - StructGenMixin:新增 FileFuncNames 表 + register_file_funcs(pool, tree) / is_file_func(name),与既有的 FileClassNames / register_file_classes / is_file_class 同规矩(键与表同池、每文件随 reset_visible_structs 重建)。 - GenApi:新增 register_file_funcs / is_file_func 委托包装。 - Translator._translate_module_level:在 register_file_classes 之后调用 register_file_funcs(三种模式共用、早于依赖 vpi 注入)。 - VpiReader._inject_function_declare:算出 mangled name 后,若 `use_bare_name != 0` 且 `is_file_func(fd.name) != 0` 直接返回(不注入)。判据只用**裸名**:带 SHA1 前缀的依赖函数名与本文件同名声明不会互相顶替,避免误跳过跨模块调用。 待验证:重编译 TransPyV(自举两遍)→ WebTest / JsonTest / FileIoTest。 --- App/lib/Stub/StubGen/VpiReader.vp | 13 ++++++ App/lib/core/Gen/GenApi.vp | 18 +++++++++ App/lib/core/Gen/StructGenMixin.vp | 65 ++++++++++++++++++++++++++++++ App/lib/core/Gen/Translator.vp | 9 ++++- 4 files changed, 104 insertions(+), 1 deletion(-) diff --git a/App/lib/Stub/StubGen/VpiReader.vp b/App/lib/Stub/StubGen/VpiReader.vp index 26bbe28..7f45b31 100644 --- a/App/lib/Stub/StubGen/VpiReader.vp +++ b/App/lib/Stub/StubGen/VpiReader.vp @@ -3028,6 +3028,19 @@ def _inject_function_declare(pool: memhub.MemBuddy*, else: mangled_name = GenApi._mangle_name_with_sha1(src_sha1, fd.name) + # 本文件自己的裸名声明优先:extern/state/export 声明的符号**不带 SHA1 前缀** + # (与 FuncGenDef.vp:172-173 同口径),若本文件自己也声明了同名符号,先注入的 + # 依赖版本会占用该名字 → forward_declare_functions 见名已存在即 continue + # (FuncGenDef.vp:178-182)→ 本文件自己的签名被静默丢弃,调用点改按依赖签名 + # coerce。现场:includes/socket.vp 的 + # `extern def connect(fd: u64, addr: SocketAddr | t.CPtr, addrlen: INT)` + # 被 w32.winsock2 的 `connect(..., WSASocketAddr | t.CPtr, ...)` 顶掉 → + # socket.vp:236 `connect(self.fd, c.Addr(addr), 16)` 报 + # 「指针隐式转指针已禁止:SocketAddr* → WSASocketAddr*」。 + # 判据只用**裸名**:带 SHA1 前缀的依赖函数名与本文件同名声明不会相互顶替。 + if use_bare_name != 0 and GenApi.is_file_func(fd.name) != 0: + return + # 去重:如果函数已存在则跳过 existing: llvmlite.Function* = GenApi.find_func_in_module(mod, mangled_name) if existing is not None: diff --git a/App/lib/core/Gen/GenApi.vp b/App/lib/core/Gen/GenApi.vp index da9a914..4481e87 100644 --- a/App/lib/core/Gen/GenApi.vp +++ b/App/lib/core/Gen/GenApi.vp @@ -358,6 +358,24 @@ def register_file_classes(pool: memhub.MemBuddy*, sha1: str, tree: ast.AST*) -> _g_gen.StructGen.register_file_classes(pool, sha1, tree) +# ---------------- register_file_funcs / is_file_func(委托 StructGenMixin) ---------------- +def register_file_funcs(pool: memhub.MemBuddy*, tree: ast.AST*) -> None: + """文件级预登记本文件顶层 FunctionDef 的函数名。 + + 与 register_file_classes 同址调用(Translator 文件入口 prologue,早于依赖 vpi + 注入):注入侧据此跳过"本文件已声明的裸名",避免依赖的 extern 声明顶掉本文件 + 自己的 extern 声明(现场 includes/socket.vp connect 被 w32.winsock2 顶掉)。""" + if _g_gen is not None: + _g_gen.StructGen.register_file_funcs(pool, tree) + + +def is_file_func(name: str) -> int: + """裸名是否为**本文件**顶层声明的函数(1=是 / 0=否)""" + if _g_gen is not None: + return _g_gen.StructGen.is_file_func(name) + return 0 + + # ---------------- find_struct_by_type(委托 StructGenMixin.find_struct_by_type) ---------------- def find_struct_by_type(struct_ty: llvmlite.LLVMType*) -> GenTypes.StructEntry*: """委托到 gen.find_struct_by_type(原 Handles 壳)""" diff --git a/App/lib/core/Gen/StructGenMixin.vp b/App/lib/core/Gen/StructGenMixin.vp index 469c08d..f0bb818 100644 --- a/App/lib/core/Gen/StructGenMixin.vp +++ b/App/lib/core/Gen/StructGenMixin.vp @@ -139,6 +139,13 @@ novirtual class StructGenMixin: # 误归属(旧 sha1 与"本文件声明了该类"两个正向事实不会同时成立)。 FileModuleSha1: str + # 本文件顶层 FunctionDef 的函数名表:用途见 register_file_funcs —— + # 依赖 stub 注入的**裸名**声明(extern/state/export,不带 SHA1 前缀)若与 + # 本文件自己的声明同名,会先占用该名字,使本文件声明在 + # forward_declare_functions 里被静默丢弃(调用点改按依赖签名 coerce)。 + # 注入侧(VpiReader._inject_function_declare)据此跳过这些名字。 + FileFuncNames: hashtable.HashTable* + # --- 名 | 类型 下标索引(查找快速路径;索引不可信时回退原线性扫描) --- StructNameIdx: sizet* # 名 → 首注册下标(槽 u64:0=空槽,否则 index+1) StructTypeIdx: sizet* # Ty 指针值 → 首注册下标(同布局) @@ -158,6 +165,7 @@ novirtual class StructGenMixin: self.CurDefClassName = None self.FileClassNames = None self.FileModuleSha1 = None + self.FileFuncNames = None self.StructNameIdx = None self.StructTypeIdx = None self.StructIdxCap = 0 @@ -200,6 +208,7 @@ novirtual class StructGenMixin: # (同池串)⇒ 必须一并清空,避免下一文件读到已回收内存。 self.FileClassNames = None self.FileModuleSha1 = None + self.FileFuncNames = None self.ModuleClasses = hashtable.HashTable(pool) if strict != 0: # 严格模式:创建新的 HashTable 记录可见结构体 @@ -331,6 +340,62 @@ novirtual class StructGenMixin: return 0 return self.FileClassNames.__contains__(name) + def register_file_funcs(self, pool: memhub.MemBuddy*, tree: ast.AST*) -> None: + """文件级预登记:把**本文件顶层 FunctionDef 的函数名**记下来。 + + 用途(依赖 stub 注入的裸名去重):编译本文件时,prologue 会**先**把依赖模块的 + .vpi 注入进来(StubMergerBuild.LoadDepVpiIntoModule);而 extern/state/export + 声明的符号**不带 SHA1 前缀**(FuncGenDef.vp:172-173 `mangled_name = fd.name`) + ⇒ 依赖的裸名声明先占用该名字;随后 forward_declare_functions 处理本文件自己的 + 同名声明时见名已存在即 `continue`(FuncGenDef.vp:178-182)⇒ **本文件自己的 + 声明被静默丢弃**,调用点改按依赖的签名 coerce。 + + 现场:includes/socket.vp 的 + `extern def connect(fd: u64, addr: SocketAddr | t.CPtr, addrlen: INT)` + 被 w32.winsock2 的 `connect(..., WSASocketAddr | t.CPtr, ...)` 顶掉 → + socket.vp:236 `connect(self.fd, c.Addr(addr), 16)` 报 + 「指针隐式转指针已禁止:%"…SocketAddr"* → %"…WSASocketAddr"*」。 + + 注入侧据本表跳过"本文件已声明的裸名"(VpiReader._inject_function_declare), + 使本文件自己的声明优先。表与键同池、每文件重建(与 FileClassNames 同规矩)。 + """ + if pool is None or tree is None: + return + ch: list[ast.AST*]* = tree.children + if ch is None: + return + if self.FileFuncNames is None: + self.FileFuncNames = hashtable.HashTable(pool) + if self.FileFuncNames is None: + return + cn: sizet = len(ch) + ci: sizet + for ci in range(cn): + child: ast.AST* = ch.get(ci) + if child is None: + continue + if child.kind() != ast.ASTKind.FunctionDef: + continue + fd: ast.FunctionDef* = ast.FunctionDef*(child) + if fd is None or fd.name is None: + continue + if self.FileFuncNames.__contains__(fd.name) != 0: + continue + # 键必须与表同池(AST 的 name 串零拷贝指向 src_buf,跨阶段可能失效): + # 复制到 pool 后再登记,避免表 resize/查询时 strcmp 到悬空内存。 + nm_len: sizet = string.strlen(fd.name) + nm_buf: str = pool.alloc(nm_len + 1) + if nm_buf is None: + continue + string.strcpy(nm_buf, fd.name) + self.FileFuncNames.set_int(nm_buf, 1) + + def is_file_func(self, name: str) -> int: + """裸名 name 是否为本文件顶层声明的函数(1=是 / 0=否)""" + if name is None or self.FileFuncNames is None: + return 0 + return self.FileFuncNames.__contains__(name) + def get_file_module_sha1(self) -> str: """本文件(正在翻译的文件)的模块 SHA1(register_file_classes 传入;未登记返回 None)。""" return self.FileModuleSha1 diff --git a/App/lib/core/Gen/Translator.vp b/App/lib/core/Gen/Translator.vp index d2d1a72..4502adc 100644 --- a/App/lib/core/Gen/Translator.vp +++ b/App/lib/core/Gen/Translator.vp @@ -555,7 +555,14 @@ novirtual class Translator: # "非基本类型指针不可隐式转换"报出。 # 登记只依赖 tree + 本文件模块 SHA1,与导入、类定义窗口、类定义顺序都无关, # 故提到门之前,三种模式(1a=1 / 1a-pre=2 / 全量=0)共用。 - GenApi.register_file_classes(pool, self.ModuleSha1, tree) + GenApi.register_file_classes(pool, self.ModuleSha1, tree) + # 本文件顶层函数名预登记(同址、同目的):依赖 vpi 注入的**裸名**声明 + # (extern/state/export 不带 SHA1 前缀)若与本文件自己的声明同名,会先占用 + # 该名字,使本文件声明在 forward_declare_functions 被静默丢弃(调用点改按 + # 依赖签名 coerce)。现场:includes/socket.vp 的 extern def connect 被 + # w32.winsock2 的 connect 顶掉 → socket.vp:236 报 + # 「指针隐式转指针已禁止:SocketAddr* → WSASocketAddr*」。 + GenApi.register_file_funcs(pool, tree) # 全量翻译模式下,先处理导入语句,再创建前向声明,解决前向引用问题 if self._declare_only == 0: # 预处理导入语句,确保 _imported_modules 和 _from_imports 已填充