From 00606057c42e61b45f1312e54b5d6a5be47bbe4e Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Wed, 26 Aug 2026 16:25:37 +0800 Subject: [PATCH] =?UTF-8?q?fix(gen):=20=E6=A8=A1=E5=9D=97=E5=88=AB?= =?UTF-8?q?=E5=90=8D=E8=A7=A3=E6=9E=90=E4=BC=98=E5=85=88=E5=88=AB=E5=90=8D?= =?UTF-8?q?=E8=BF=98=E5=8E=9F=EF=BC=8C=E4=BF=AE=E5=A4=8D=20process=20?= =?UTF-8?q?=E5=90=8E=E7=BC=80=E8=AF=AF=E5=91=BD=E4=B8=AD=E5=86=85=E6=A0=B8?= =?UTF-8?q?=E6=A8=A1=E5=9D=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit import vpsdk.process as process 时,_imported_modules 含别名 "process", store 同时存在 "vpsdk.process" 与 "VKernel.Kernel.sched.process"(ViperOS includes 目录扫描含 VKernel)。裸名 "process" 后缀匹配命中先扫描的内核 模块 → 跨模块调用解析到未编译的内核 → 返回类型解析落空 → Phase A _deps_pending 永不收敛(Scene3D "Phase A 无法完成翻译" 根因)。 - StructGenMixin._resolve_module_sha1:别名还原(LookupModNameByAlias → vpsdk.process → 精确 SHA1)提升到裸名后缀匹配之前;裸名后缀仅作兜底 - Phase2 依赖探测:同构修复,别名还原先于后缀匹配,避免依赖缺失诊断 误报内核 process 缺 text.ll --- App/lib/Phase/Phase2/__init__.vp | 234 +++++++++++++++++++++++++++-- App/lib/core/Gen/StructGenMixin.vp | 207 ++++++++++++++++++++++--- 2 files changed, 408 insertions(+), 33 deletions(-) diff --git a/App/lib/Phase/Phase2/__init__.vp b/App/lib/Phase/Phase2/__init__.vp index c08d08b..bf73d76 100644 --- a/App/lib/Phase/Phase2/__init__.vp +++ b/App/lib/Phase/Phase2/__init__.vp @@ -135,8 +135,8 @@ def _ScanDirForPyFiles(mb: memhub.MemBuddy | t.CPtr, if w32.win32file.FindNextFileA(handle, find_data) == 0: break continue - # 检查是否是 .vp 或 .py 文件(.vp 为 Viper 源,.py 向后兼容) - if fname_len > 3 and Utils.is_vp_or_py_at(fname, 0, fname_len) != 0 \ + # 检查是否是 .vp 源文件(Viper 源仅 .vp,不再识别 .py) + if fname_len > 3 and Utils.is_vp_at(fname, 0, fname_len) != 0 \ and file_count < max_files: full_path: bytes = Utils.JoinPath(dir_path, fname) if full_path is not None: @@ -173,6 +173,108 @@ def _ScanDirForPyFiles(mb: memhub.MemBuddy | t.CPtr, return file_count +# ============================================================ +# _ScanDirForAsmFiles - 递归扫描目录,收集 .s 汇编文件 +# +# 与 _ScanDirForPyFiles 同构:VKernel 等裸机项目含 .s(如 +# Kernel/intr/isr.s 定义 isr0/syscall_entry/_per_cpu 等符号)。 +# TPV 不对 .s 翻译,直接用 llvm-mc 汇编为 .obj 参与链接。 +# ============================================================ +def _ScanDirForAsmFiles(mb: memhub.MemBuddy | t.CPtr, + dir_path: str, + entries: SrcFileEntry | t.CPtr, + entry_size: t.CSizeT, + file_count: int, max_files: int) -> int: + """递归扫描目录,收集 .s 汇编文件到 entries,返回新的 file_count""" + if dir_path is None or entries is None: + return file_count + if file_count >= max_files: + return file_count + + dir_len: t.CSizeT = string.strlen(dir_path) + pattern: bytes = stdlib.malloc(dir_len + 16) + if pattern is None: + return file_count + viperlib.snprintf(pattern, dir_len + 16, "%s/*", dir_path) + + find_data_size: t.CSizeT = w32.win32file.WIN32_FIND_DATAA.__sizeof__() + find_data: w32.win32file.WIN32_FIND_DATAA | t.CPtr = stdlib.malloc(find_data_size + 16) + if find_data is None: + stdlib.free(pattern) + return file_count + string.memset(find_data, 0, find_data_size + 16) + + handle: w32.win32base.HANDLE = w32.win32file.FindFirstFileA(pattern, find_data) + if handle == w32.win32base.INVALID_HANDLE_VALUE: + stdlib.free(pattern) + stdlib.free(find_data) + return file_count + + while True: + fname: str = find_data.cFileName + if fname is None: + if w32.win32file.FindNextFileA(handle, find_data) == 0: + break + continue + fname_len: t.CSizeT = string.strlen(fname) + if fname_len <= 0: + if w32.win32file.FindNextFileA(handle, find_data) == 0: + break + continue + # 跳过 . 和 .. + is_dot: int = 0 + if fname_len == 1 and fname[0] == '.': + is_dot = 1 + elif fname_len == 2 and fname[0] == '.' and fname[1] == '.': + is_dot = 1 + if is_dot != 0: + if w32.win32file.FindNextFileA(handle, find_data) == 0: + break + continue + # 检查是否是目录 + attrs: ULONG = find_data.dwFileAttributes + is_dir: int = 0 + if (attrs & w32.win32file.FILE_ATTRIBUTE_DIRECTORY) != 0: + is_dir = 1 + if is_dir != 0: + sub_dir: bytes = Utils.JoinPath(dir_path, fname) + if sub_dir is not None: + file_count = _ScanDirForAsmFiles(mb, sub_dir, entries, entry_size, file_count, max_files) + stdlib.free(sub_dir) + if w32.win32file.FindNextFileA(handle, find_data) == 0: + break + continue + # 检查是否以 .s 后缀结尾(汇编源文件) + is_asm: int = 0 + if fname_len > 2 and fname[fname_len - 2] == '.' and fname[fname_len - 1] == 's': + is_asm = 1 + if is_asm != 0 and file_count < max_files: + full_path: bytes = Utils.JoinPath(dir_path, fname) + if full_path is not None: + # SHA1 与 .vp 同口径(CRLF→LF 归一化),用于切片路径 + sha1_val: str = IncludesScanner.compute_file_sha1(mb, full_path) + if sha1_val is not None: + ea: t.CUInt64T = t.CUInt64T(entries) + file_count * entry_size + ent: SrcFileEntry | t.CPtr = (SrcFileEntry | t.CPtr)(t.CVoid(ea, t.CPtr)) + if ent is not None: + ent.Path = full_path + ent.Sha1 = sha1_val + file_count += 1 + fb: str = VLogger.fmt_buf() + if fb is not None: + viperlib.snprintf(fb, 1024, "%s (asm sha1=%s)", fname, sha1_val) + VLogger.info(fb, "project") + # full_path 不释放:ent.Path 引用它 + + if w32.win32file.FindNextFileA(handle, find_data) == 0: + break + + w32.win32file.FindClose(handle) + stdlib.free(pattern) + stdlib.free(find_data) + return file_count + + # ============================================================ # _PreRegisterAppGenericTemplates - mark2 前预注册 App 源文件泛型类模板 # @@ -291,8 +393,22 @@ def RunMultiFileProject(mb: memhub.MemBuddy | t.CPtr, viperlib.snprintf(fb, 1024, "共 %d 个源文件", file_count) VLogger.info(fb, "project") + # 扫描 .s 汇编文件(独立数组,不参与 Phase A 翻译,Phase B 后 llvm-mc 汇编) + asm_entries: SrcFileEntry | t.CPtr = stdlib.malloc(MAX_SRC_FILES * entry_size) + if asm_entries is None: + stdlib.free(entries) + return 1 + string.memset(asm_entries, 0, MAX_SRC_FILES * entry_size) + asm_file_count: int = _ScanDirForAsmFiles(mb, source_dir, asm_entries, entry_size, 0, MAX_SRC_FILES) + if asm_file_count > 0: + fb_asm: str = VLogger.fmt_buf() + if fb_asm is not None: + viperlib.snprintf(fb_asm, 1024, "汇编文件 %d 个(llvm-mc 汇编参与链接)", asm_file_count) + VLogger.info(fb_asm, "project") + if file_count == 0: stdlib.free(entries) + stdlib.free(asm_entries) return 1 # 初始化 AST 表 @@ -655,6 +771,8 @@ def RunMultiFileProject(mb: memhub.MemBuddy | t.CPtr, continue ea: t.CUInt64T = t.CUInt64T(entries) + i * entry_size ent: SrcFileEntry | t.CPtr = (SrcFileEntry | t.CPtr)(t.CVoid(ea, t.CPtr)) + # 计时点 0:本文件处理开始(translate 段起点) + t_pa0: t.CUInt64T = w32.win32base.GetTickCount64() if ent is None or ent.Path is None: done_flags[i] = 1 completed_this_pass += 1 @@ -666,6 +784,8 @@ def RunMultiFileProject(mb: memhub.MemBuddy | t.CPtr, rel_path_pa: str = ent.Path + src_dir_len_pa + 1 pkg_pa = GenApi.compute_package_from_relpath(mb, rel_path_pa) tr_a: HandlesTranslator.Translator | t.CPtr = BuildPipeline.TranslateFileGetTrans(mb, ent.Path, ent.Sha1, pkg_pa, 0, temp_dir) + # 计时点 1:translate 返回 + t_pa1: t.CUInt64T = w32.win32base.GetTickCount64() if tr_a is None: # 翻译失败(语法/解析错误):记录待下遍重试,收敛检查统一报错 # OOM 清理:失败翻译也已产生 AST/tokens 瞬时数据,立即回收 @@ -735,14 +855,26 @@ def RunMultiFileProject(mb: memhub.MemBuddy | t.CPtr, continue string.strncpy(im_dp_buf, tr_a._imported_modules + im_dp_start, im_dp_mod_len) im_dp_buf[im_dp_mod_len] = '\0' - # 别名/基名解析:精确匹配 → 后缀匹配(import X as Y 且 Y==X 基名时命中, - # 如 GenTypes → lib.core.Gen.GenTypes;改名别名如 HT→HandlesTranslator 不命中) + # 别名/基名解析:精确匹配 → 别名还原(from_imports + # "别名:模块真名",import X as Y 的 Y)→ 后缀匹配兜底。 + # 别名还原必须优先于裸名后缀:store 可能同时存在 + # "vpsdk.process" 与 "VKernel.Kernel.sched.process", + # 裸名 "process" 后缀匹配会命中先扫描的内核模块 → + # 依赖探测误报内核缺 text.ll(Phase A 永不收敛)。 im_dp_sha1: str = StubMergerStore.LookupSha1ByModName(im_dp_buf) if im_dp_sha1 is None: - im_dp_sha1 = StubMergerStore.LookupSha1ByModNameSuffix(im_dp_buf) + # 改名别名(import X as Y):从 _from_imports 查 Y→X 还原真名, + # 再精确/后缀查 SHA1。LookupSha1ByAlias 只精确匹配——store 键 + # 是全路径模块名(Kernel.drivers.fs.fat32.fat32_diskio)时 + # fat32_diskio 精确不中 → 别名依赖误判 miss → 依赖未就绪死锁 + # (fat32_types 依赖 diskio → Phase A 无法收敛,VKernel 根因)。 + real_dp: str = StubMergerStore.LookupModNameByAlias(tr_a._from_imports, im_dp_buf) + if real_dp is not None: + im_dp_sha1 = StubMergerStore.LookupSha1ByModName(real_dp) + if im_dp_sha1 is None: + im_dp_sha1 = StubMergerStore.LookupSha1ByModNameSuffix(real_dp) if im_dp_sha1 is None: - # 改名别名(import X as Y):从 _from_imports 查 Y→X 再查 SHA1 - im_dp_sha1 = StubMergerStore.LookupSha1ByAlias(tr_a._from_imports, im_dp_buf) + im_dp_sha1 = StubMergerStore.LookupSha1ByModNameSuffix(im_dp_buf) im_dp_resolved: int = 0 im_dp_missing: int = 0 if im_dp_sha1 is not None: @@ -848,6 +980,16 @@ def RunMultiFileProject(mb: memhub.MemBuddy | t.CPtr, df_a.close() stdlib.free(deps_path_a) + # 计时点 2:stub+text dump 及写盘完成;输出逐文件进度(对齐 Phase1 1b) + t_pa2: t.CUInt64T = w32.win32base.GetTickCount64() + fb_pa: str = VLogger.fmt_buf() + if fb_pa is not None: + viperlib.snprintf(fb_pa, 1024, + "[%d/%d] PhaseA: %s (translate=%llu ms, dump=%llu ms, total=%llu ms)", + i + 1, file_count, ent.Path, + t_pa1 - t_pa0, t_pa2 - t_pa1, t_pa2 - t_pa0) + VLogger.info(fb_pa, "PhaseA") + done_flags[i] = 1 completed_this_pass += 1 # OOM 清理:stub/text/deps 已写盘,回收本文件翻译瞬时数据 @@ -986,12 +1128,12 @@ def RunMultiFileProject(mb: memhub.MemBuddy | t.CPtr, i + 1, file_count, ent.Path, t_pb1 - t_pb0, t_pb2 - t_pb1, t_pb3 - t_pb2, t_pb3 - t_pb0) VLogger.info(fb_comp, "PhaseB") - # 构造 .obj 路径,检测是否是 main 模块(test_main.vp/.py 或 main.vp/.py) + # 构造 .obj 路径,检测是否是 main 模块(test_main.vp 或 main.vp,不再识别 .py) od_len: t.CSizeT = string.strlen(output_dir) is_main_mod: int = 0 - if string.strstr(ent.Path, "test_main.vp") is not None or string.strstr(ent.Path, "test_main.py") is not None: + if string.strstr(ent.Path, "test_main.vp") is not None: is_main_mod = 1 - elif string.strstr(ent.Path, "main.vp") is not None or string.strstr(ent.Path, "main.py") is not None: + elif string.strstr(ent.Path, "main.vp") is not None: is_main_mod = 1 # 切片路径: output_dir/{sha1前缀}/{sha1}.obj @@ -1033,6 +1175,40 @@ def RunMultiFileProject(mb: memhub.MemBuddy | t.CPtr, ti += 1 stdlib.free(llc_tasks) + # 汇编 .s 文件:llvm-mc → .obj(isr.s 等,符号为裸名,与 .vp 的 + # CExport/State stub 声明匹配)。obj 加入 obj_paths 参与 Phase C 链接。 + od_len_as: t.CSizeT = string.strlen(output_dir) + asm_compiled: int = 0 + for ai_as in range(asm_file_count): + ea_as: t.CUInt64T = t.CUInt64T(asm_entries) + ai_as * entry_size + ent_as: SrcFileEntry | t.CPtr = (SrcFileEntry | t.CPtr)(t.CVoid(ea_as, t.CPtr)) + if ent_as is None or ent_as.Path is None or ent_as.Sha1 is None: + continue + aret_as: int = BuildPipeline.asm_to_obj( + ent_as.Path, ent_as.Sha1, output_dir, Config.TargetTriple) + if aret_as != 0: + fb_asm_fail: str = VLogger.fmt_buf() + if fb_asm_fail is not None: + viperlib.snprintf(fb_asm_fail, 1024, "llvm-mc 汇编失败,终止编译: %s", ent_as.Path) + VLogger.error(fb_asm_fail, "PhaseB") + sys.exit(1) + obj_path_asm: str = StubMergerCollect._sliced_path(output_dir, od_len_as, ent_as.Sha1, "obj") + if obj_path_asm is not None: + op_len_asm: t.CSizeT = string.strlen(obj_path_asm) + if obj_pos + op_len_asm + 2 < OBJ_PATHS_SIZE: + if obj_pos > 0: + obj_paths[obj_pos] = ' ' + obj_pos += 1 + string.strcpy(obj_paths + obj_pos, obj_path_asm) + obj_pos += op_len_asm + obj_paths[obj_pos] = '\0' + stdlib.free(obj_path_asm) + asm_compiled += 1 + fb_asm_ok: str = VLogger.fmt_buf() + if fb_asm_ok is not None: + viperlib.snprintf(fb_asm_ok, 1024, "[asm %d] 汇编完成: %s", asm_compiled, ent_as.Path) + VLogger.info(fb_asm_ok, "PhaseB") + fb: str = VLogger.fmt_buf() if fb is not None: viperlib.snprintf(fb, 1024, "编译完成: %d/%d", compiled_count, file_count) @@ -1041,6 +1217,7 @@ def RunMultiFileProject(mb: memhub.MemBuddy | t.CPtr, if compiled_count == 0: VLogger.error("无成功编译的文件", "PhaseB") stdlib.free(entries) + stdlib.free(asm_entries) return 1 # === 3.5 编译缺失的 includes 文件(按依赖图按需编译)=== @@ -1104,6 +1281,11 @@ def RunMultiFileProject(mb: memhub.MemBuddy | t.CPtr, temp_dir, td_len_pb, store_sha1_arr_mi, store_mod_arr_mi) store_rel_arr_mi: bytes = StubMergerStore.GetSha1StoreRelArrPtr() store_incdir_arr_mi: bytes = StubMergerStore.GetSha1StoreIncdirArrPtr() + # 诊断:_BuildIncludesSha1Map 复制的条目数(PhaseB+ 循环执行前提) + fb_diag_scm: str = VLogger.fmt_buf() + if fb_diag_scm is not None: + viperlib.snprintf(fb_diag_scm, 1024, "诊断 store_count_mi=%d", store_count_mi) + VLogger.debug(fb_diag_scm, "PhaseB+") if store_count_mi > 0: # 拓扑排序可达 includes:被依赖模块先完整翻译。跨模块 vtable 生成 @@ -1118,6 +1300,23 @@ def RunMultiFileProject(mb: memhub.MemBuddy | t.CPtr, store_count_mi, store_sha1_arr_mi, store_mod_arr_mi, reachable_set, reachable_count, topo_idx) + # 诊断:拓扑排序条数(0 则循环不执行) + fb_diag_tc: str = VLogger.fmt_buf() + if fb_diag_tc is not None: + viperlib.snprintf(fb_diag_tc, 1024, "诊断 topo_count=%d reachable_count=%d", + topo_count, reachable_count) + VLogger.debug(fb_diag_tc, "PhaseB+") + + # 诊断:dump reachable_set 内容(验证 BFS 写入的 sha1 是否有效) + ri_dbg: int = 0 + for ri_dbg in range(reachable_count): + rsha1_dbg: str = reachable_set + t.CSizeT(ri_dbg) * 17 + fb_diag_rs: str = VLogger.fmt_buf() + if fb_diag_rs is not None: + viperlib.snprintf(fb_diag_rs, 1024, "诊断 reachable[%d]=%s", + ri_dbg, rsha1_dbg) + VLogger.debug(fb_diag_rs, "PhaseB+") + for si_mi in range(topo_count): # 当前条目(拓扑序索引,仅含可达模块) ti: int = topo_idx[si_mi] @@ -1141,6 +1340,13 @@ def RunMultiFileProject(mb: memhub.MemBuddy | t.CPtr, stdlib.free(check_fd_mi) continue + # 诊断:obj 不存在,进入 is_decl 判定 + fb_diag_obj: str = VLogger.fmt_buf() + if fb_diag_obj is not None: + viperlib.snprintf(fb_diag_obj, 1024, "诊断 循环 si=%d ti=%d sha1=%s obj_exists=%d", + si_mi, ti, inc_sha1_mi, obj_exists_mi) + VLogger.debug(fb_diag_obj, "PhaseB+") + # .obj 不存在,需要编译 # 构造源文件路径: {所属 includes 根目录}/{rel_path}(多目录逐条取 IncDir,空串回退 includes_dir) rel_path_len_mi: t.CSizeT = string.strlen(inc_rel_mi) @@ -1193,6 +1399,12 @@ def RunMultiFileProject(mb: memhub.MemBuddy | t.CPtr, stdlib.free(tbuf_mi) tf_mi.close() stdlib.free(tpath_mi) + # 诊断:is_decl 判定结果 + fb_diag_decl: str = VLogger.fmt_buf() + if fb_diag_decl is not None: + viperlib.snprintf(fb_diag_decl, 1024, "诊断 is_decl=%d src=%s", + is_decl_mi, src_fp_mi) + VLogger.debug(fb_diag_decl, "PhaseB+") # is_decl_mi == -1: text.ll 不存在(Phase1 未翻译此文件) # is_decl_mi == 1: 声明文件(仅 declare 无 define) # is_decl_mi == 0: 实现文件(有 define) @@ -1475,7 +1687,9 @@ def RunMultiFileProject(mb: memhub.MemBuddy | t.CPtr, else: VLogger.error("链接失败", "PhaseC") stdlib.free(entries) + stdlib.free(asm_entries) return 1 stdlib.free(entries) + stdlib.free(asm_entries) return 0 diff --git a/App/lib/core/Gen/StructGenMixin.vp b/App/lib/core/Gen/StructGenMixin.vp index a0e7643..2601db4 100644 --- a/App/lib/core/Gen/StructGenMixin.vp +++ b/App/lib/core/Gen/StructGenMixin.vp @@ -12,6 +12,7 @@ import hashtable import viperlib import lib.core.CompilerFactory as CompilerFactory import lib.core.VLogger as VLogger +import lib.Stub.StubMerger.StubMergerStore as StubMergerStore # ============================================================ @@ -354,7 +355,7 @@ class StructGenMixin: 字段默认值(fe.DefaultVal)除定义文件内 _generate_before_init 使用外, 跨文件结构体构造(_translate_struct_ctor → _apply_struct_defaults)会在 调用方文件翻译时读取——原 pool AST 已 reset 回收必须深拷贝。 - 支持 Constant(标量)与 List(列表字面量,如 t.CArray[str] = ["py", "pyw"])。 + 支持 Constant(标量)与 List(列表字面量,如 t.CArray[str] = [...])。 其他非常量默认值(Name/BinOp 等引用表达式)无法安全深拷贝:返回 None (调用方 fe.DefaultVal 保持 None,仅定义文件内 _generate_before_init 仍持有原始 pool 指针,不受影响)。 @@ -448,11 +449,43 @@ class StructGenMixin: """返回当前已注册的结构体数量""" return self.StructCount + def _resolve_module_sha1(self, mod_name: str, + from_imports: str = None) -> str: + """模块名 → 模块 SHA1(精确 → import 别名还原后再查 → 后缀兜底) + + 模块名可能是:模块真名(fat32_types)、包后缀(sched.sched)、 + import 别名(import X as Y 的 Y)。逐级尝试: + 1. LookupSha1ByModName:精确 + "__init__" 包回退 + 2. LookupModNameByAlias:from_imports "别名:模块真名" 还原后 + 精确/后缀重试——必须优先于裸名后缀匹配:store 可能同时存在 + "vpsdk.process" 与 "VKernel.Kernel.sched.process",裸名 + "process" 后缀匹配会命中先扫描的内核模块(别名 process 被 + 劫持 → 跨模块调用解析到未编译的内核 → Phase A _deps_pending + 永不收敛,Scene3D root cause) + 3. LookupSha1ByModNameSuffix:后缀匹配(store 键是全路径模块名, + 如 "Kernel.drivers.fs.fat32.fat32_types"),非别名裸名兜底 + """ + if mod_name is None: + return None + s_rm: str = StubMergerStore.LookupSha1ByModName(mod_name) + if s_rm is None and from_imports is not None: + real_rm: str = StubMergerStore.LookupModNameByAlias(from_imports, mod_name) + if real_rm is not None: + s_rm = StubMergerStore.LookupSha1ByModName(real_rm) + if s_rm is None: + s_rm = StubMergerStore.LookupSha1ByModNameSuffix(real_rm) + if s_rm is None: + s_rm = StubMergerStore.LookupSha1ByModNameSuffix(mod_name) + return s_rm + def find_struct(self, name: str) -> GenTypes.StructEntry | t.CPtr: """按类名查找结构体,返回 StructEntry 或 None 支持模块限定名(如 "HT.Translator"): - 第一遍精确匹配,第二遍按类名(最后一个 '.' 之后的部分)回退 + 第一遍精确匹配,第二遍按类名(最后一个 '.' 之后的部分)回退。 + 模块可解析的限定名(module.Class)按模块归属精确匹配,不做裸名 + 跨库回退(防 sched.Thread 被 vthreading.Thread 顶掉的同类问题); + 仅模块名无法解析时保留裸名回退(兼容旧行为)。 """ if name is None or self.StructTable is None: return None @@ -466,7 +499,42 @@ class StructGenMixin: if entry is not None and entry.Name is not None: if string.strcmp(entry.Name, name) == 0: return entry - # 第二遍:类名回退(处理模块限定名 "HT.Translator" → "Translator") + # 模块限定名(module.Class):模块可解析时按模块归属精确匹配, + # 防止裸名回退抓跨模块同名类。模块未命中时若裸名类唯一(无冲突) + # 仍回退,避免误杀未登记模块的既有解析。 + dot_pos_q: str = string.strrchr(name, '.') + if dot_pos_q is not None and dot_pos_q[1] != '\0': + bare_f: str = dot_pos_q + 1 + mod_len_f: t.CSizeT = string.strlen(name) - string.strlen(bare_f) - 1 + if mod_len_f > 0: + mod_buf_f: str = stdlib.malloc(mod_len_f + 1) + if mod_buf_f is not None: + string.strncpy(mod_buf_f, name, mod_len_f) + mod_buf_f[mod_len_f] = '\0' + if string.strcmp(mod_buf_f, "t") != 0 and string.strcmp(mod_buf_f, "c") != 0: + mod_sha1_f: str = self._resolve_module_sha1(mod_buf_f) + if mod_sha1_f is not None: + me_f: GenTypes.StructEntry | t.CPtr = \ + self.find_struct_by_module(bare_f, mod_sha1_f) + if me_f is not None: + stdlib.free(mod_buf_f) + return me_f + # 模块类未注册:统计裸名同名类数量,唯一时回退(无冲突) + same_cnt_f: int = 0 + same_entry_f: GenTypes.StructEntry | t.CPtr = None + for i2 in range(self.StructCount): + e2: GenTypes.StructEntry | t.CPtr = self._get_struct_entry(i2) + if e2 is not None and e2.Name is not None: + if string.strcmp(e2.Name, bare_f) == 0: + same_cnt_f += 1 + same_entry_f = e2 + stdlib.free(mod_buf_f) + if same_cnt_f == 1: + return same_entry_f + return None + stdlib.free(mod_buf_f) + # 第二遍:类名回退(处理模块限定名 "HT.Translator" → "Translator", + # 仅模块名无法解析时走到这里) dot_pos: str = string.strrchr(name, '.') if dot_pos is not None: class_name: str = dot_pos + 1 @@ -627,10 +695,16 @@ class StructGenMixin: def lookup_field_by_class(self, class_name: str, field_name: str, - sha1: str = None) -> GenTypes.FieldEntry | t.CPtr: + sha1: str = None, + node: ast.AST | t.CPtr = None, + from_imports: str = None) -> GenTypes.FieldEntry | t.CPtr: """按类名和字段名查找字段信息 - 优先用 SHA1 匹配(跨模块同名类区分),无 SHA1 时按类名查找第一个 + 优先用 SHA1 匹配(跨模块同名类区分),无 SHA1 时按类名查找第一个。 + node: 出错位置 AST 节点(可选,供失败报错输出 Python 风格 traceback 帧; + 传 None 时用 trace 栈兜底定位) + from_imports: from-import 映射("local:module" 格式),供模块限定名 + 的 import 别名(import X as Y → Y.Class)解析为模块 sha1 """ if class_name is None or field_name is None: return None @@ -668,16 +742,76 @@ class StructGenMixin: return fe return None + # 模块限定名(module.Class,module 非 t/c 内置):按模块归属解析, + # 禁止裸名回退抓跨模块同名类(根因:VKernel sched.Thread 被 includes/ + # vthreading.vp 同名 Thread 顶掉 → 'tid' 字段缺失)。 + # 模块名经 StubMergerStore sha1 映射表解析为模块 sha1,再精确匹配 + # name+sha1(vpi stub 注入 / 定义侧注册的裸名 entry)。 + dot_pos_q: str = string.strrchr(class_name, '.') + if dot_pos_q is not None and dot_pos_q[1] != '\0': + bare_q: str = dot_pos_q + 1 + mod_len_q: t.CSizeT = string.strlen(class_name) - string.strlen(bare_q) - 1 + if mod_len_q > 0: + mod_buf_q: str = stdlib.malloc(mod_len_q + 1) + if mod_buf_q is not None: + string.strncpy(mod_buf_q, class_name, mod_len_q) + mod_buf_q[mod_len_q] = '\0' + if string.strcmp(mod_buf_q, "t") != 0 and string.strcmp(mod_buf_q, "c") != 0: + mod_sha1_q: str = self._resolve_module_sha1(mod_buf_q, from_imports) + if mod_sha1_q is not None: + mod_entry_q: GenTypes.StructEntry | t.CPtr = \ + self.find_struct_by_module(bare_q, mod_sha1_q) + if mod_entry_q is not None: + for fi_q in range(mod_entry_q.FieldCount): + fe_q: GenTypes.FieldEntry | t.CPtr = \ + self._get_field_entry(mod_entry_q, fi_q) + if fe_q is not None and fe_q.Name is not None: + if string.strcmp(fe_q.Name, field_name) == 0: + stdlib.free(mod_buf_q) + return fe_q + # 类命中但字段未找到:可读化报错(traceback 格式) + fb_qf: str = VLogger.fmt_buf() + if fb_qf is not None: + viperlib.snprintf(fb_qf, 1024, + "lookup_field_by_class: 类 '%s' 已按模块归属解析" + "(ModuleSha1=%s, FieldCount=%d)但字段 '%s' 未找到", + class_name, mod_sha1_q, mod_entry_q.FieldCount, field_name) + self.Gen.TypeGenErr.fatal_error(node, fb_qf, "AttributeError") + stdlib.free(mod_buf_q) + return None + # 模块可解析但类未注册:fail-fast(暴露 vpi 注入/注册缺陷, + # 不再裸名跨库抓其他模块同名类) + fb_qm: str = VLogger.fmt_buf() + if fb_qm is not None: + viperlib.snprintf(fb_qm, 1024, + "lookup_field_by_class: 类 '%s' 未找到(模块 '%s' 可解析" + " sha1=%s,但无同名注册类;请检查 vpi stub 注入或编译顺序)", + class_name, mod_buf_q, mod_sha1_q) + self.Gen.TypeGenErr.fatal_error(node, fb_qm, "AttributeError") + stdlib.free(mod_buf_q) + return None + # 模块名未登记:fail-fast(不再裸名跨库回退) + fb_qs: str = VLogger.fmt_buf() + if fb_qs is not None: + viperlib.snprintf(fb_qs, 1024, + "lookup_field_by_class: 类 '%s' 的模块 '%s' 无法解析" + "(sha1 映射表未登记,请检查 import 与编译顺序)", + class_name, mod_buf_q) + self.Gen.TypeGenErr.fatal_error(node, fb_qs, "AttributeError") + stdlib.free(mod_buf_q) + return None + stdlib.free(mod_buf_q) + # 回退: 无 SHA1 或 SHA1 匹配失败,按类名查找第一个 entry = self.find_struct(class_name) if entry is None: - # 诊断:目标类未注册 + # 诊断:目标类未注册(仿 Python traceback 格式,立即终止编译) fb_lfbc: str = VLogger.fmt_buf() if fb_lfbc is not None: viperlib.snprintf(fb_lfbc, 1024, "lookup_field_by_class: 类 '%s' 未注册 (struct_count=%d)", class_name, self.StructCount) - VLogger.error(fb_lfbc, "STRUCT") + self.Gen.TypeGenErr.fatal_error(node, fb_lfbc, "AttributeError") return None # 先查找字段(诊断/error 之前返回,否则 error 立即退出导致查找永远失败) for fi in range(entry.FieldCount): @@ -715,18 +849,19 @@ class StructGenMixin: self.Gen.StructGen._get_field_entry(entry, rn_alias_l) if rn_fe_l is not None and rn_fe_l.Ty is not None: return rn_fe_l - # 诊断:找到结构体但字段确实未找到,打印字段表 - # 注意:VLogger.error 会立即退出进程,字段表须在 error 之前用 warning 打印 - for fi in range(entry.FieldCount): - fe: GenTypes.FieldEntry | t.CPtr = self._get_field_entry(entry, fi) - if fe is not None: - fb_fn: str = VLogger.fmt_buf() - if fb_fn is not None: - if fe.Name is not None: - viperlib.snprintf(fb_fn, 1024, " 字段[%d]: '%s'", fi, fe.Name) - else: - viperlib.snprintf(fb_fn, 1024, " 字段[%d]: (null)", fi) - VLogger.warning(fb_fn, "STRUCT") + # 诊断:找到结构体但字段确实未找到 + # 拼接注册字段列表(一行逗号分隔,供报错一眼定位) + # 注意:不能用 VLogger.fmt_buf()(与 fb_fields 同一静态缓冲,会被覆盖) + fb_flds_lst: str = stdlib.malloc(1024) + if fb_flds_lst is not None: + fb_flds_lst[0] = '\0' + for fi in range(entry.FieldCount): + fe: GenTypes.FieldEntry | t.CPtr = self._get_field_entry(entry, fi) + if fe is not None and fe.Name is not None: + if string.strlen(fb_flds_lst) > 0 and string.strlen(fb_flds_lst) + 3 < 1024: + string.strcat(fb_flds_lst, ", ") + if string.strlen(fb_flds_lst) + string.strlen(fe.Name) < 1024: + string.strcat(fb_flds_lst, fe.Name) fb_fields: str = VLogger.fmt_buf() if fb_fields is not None: if rn_is_renum_l != 0: @@ -739,10 +874,30 @@ class StructGenMixin: "请检查该变体字段是否已注册别名,或 class_name 指针是否统一)", class_name, entry.FieldCount, field_name, rn_cls_l, rn_alias_l) else: + # 可读化:附 entry.Name/ModuleSha1(跨模块同名定位)+ 注册字段 + # 列表 + 期望 sha1(调用方)→ 一眼分辨"跨模块同名类误匹配" + # vs "vpi stub 注入字段不全" vs "类定义与访问处不一致" + sha1_txt: str = "?" + if entry.ModuleSha1 is not None: + sha1_txt = entry.ModuleSha1 + want_sha1_txt: str = "?" + if sha1 is not None: + want_sha1_txt = sha1 + fld_lst_txt: str = "(无)" + if fb_flds_lst is not None and string.strlen(fb_flds_lst) > 0: + fld_lst_txt = fb_flds_lst viperlib.snprintf(fb_fields, 1024, - "lookup_field_by_class: 类 '%s' 已注册 (FieldCount=%d) 但字段 '%s' 未找到", - class_name, entry.FieldCount, field_name) - VLogger.error(fb_fields, "STRUCT") + "lookup_field_by_class: 类 '%s' 已注册 (FieldCount=%d, " + "entry.Name='%s', ModuleSha1=%s, 调用方期望 sha1=%s) " + "但字段 '%s' 未找到;注册字段: [%s]。可能原因: 跨模块同名类" + "被误匹配 / vpi stub 注入字段不全 / 类定义与访问处不一致", + class_name, entry.FieldCount, entry.Name, sha1_txt, + want_sha1_txt, field_name, fld_lst_txt) + # 仿 Python traceback 格式输出(Traceback 帧 + 文件:行号 + 错误类型), + # 立即终止编译(与 VLogger.error 同语义,格式对齐 fatal_error) + self.Gen.TypeGenErr.fatal_error(node, fb_fields, "AttributeError") + if fb_flds_lst is not None: + stdlib.free(fb_flds_lst) # 诊断:找到结构体但字段未找到 return None @@ -790,13 +945,19 @@ class StructGenMixin: return None ty: llvmlite.LLVMType | t.CPtr = val.Ty while ty is not None: + # 同类编译 bug(见 TypeGenResolve L1463 注释):case body 直接 + # 更新 while 条件变量(ty = pointee)时自举编译出的 match 把赋值 + # 写到 subject 快照槽,while 条件读旧槽 → 死循环。改法:case + # 绑定到局部变量,match 外更新循环变量。 + nxt_ty: llvmlite.LLVMType | t.CPtr = None match ty: case llvmlite.LLVMType.Ptr(pointee): - ty = pointee + nxt_ty = pointee case llvmlite.LLVMType.Struct(fields, fcount, name): return ty case _: return None + ty = nxt_ty return None def mark_as_union(self, name: str) -> int: