fix(Phase2): Phase A 多遍重试修复同项目依赖翻译顺序

main.vp 先于其 import 的 mbuddytest.vp 翻译(文件系统顺序),跨模块函数返回类型精确解析因依赖 text.ll 未生成而 fail-fast。新增 Translator._deps_pending + text.ll 存在性探测区分依赖未翻译/真错误,Phase A 循环改多遍,依赖就绪后下遍重译命中。
This commit is contained in:
TermiNexus committed 2026-08-11 01:33:52 +08:00
1 parent b5657b3790
commit 120a2d7ad7
3 files changed
+314 -82

No files matched your search

+181 -10
View File
@@ -243,6 +243,13 @@ class CallGenMixin:
# 第二遍:后缀匹配(.name) # 第二遍:后缀匹配(.name)
# 不跳过 declare:方法 stub 也是 declare,需要通过后缀匹配找到 # 不跳过 declare:方法 stub 也是 declare,需要通过后缀匹配找到
# 调用处(translate_call 等)负责判断 IsDeclared 并决定使用裸名还是 sha1 前缀名 # 调用处(translate_call 等)负责判断 IsDeclared 并决定使用裸名还是 sha1 前缀名
#
# 修复:本模块 define 优先于跨模块 stub declare。
# 根因:跨模块同名函数(如 lexer.vp 的 _advance 与 parser.vp 的 _advance)
# 的 stub declare 在 stub 注入时排在 FuncHead 前面,裸名调用 _advance 后缀
# 匹配按链表顺序先命中跨模块 declare → 参数按 Lexer* coerce → Parser* 被
# bitcast 成 Lexer* → lexer 版 _advance 按 Lexer 布局操作 Parser 指针,
# cur 永不推进 → parse 无限循环。本模块同名 define 必须优先。
cur = mod.FuncHead cur = mod.FuncHead
while cur is not None: while cur is not None:
cur_name2: t.CChar | t.CPtr = llvmlite.function_get_name(cur) cur_name2: t.CChar | t.CPtr = llvmlite.function_get_name(cur)
@@ -257,6 +264,24 @@ class CallGenMixin:
match = 0 match = 0
break break
if match == 1: if match == 1:
if cur.IsDeclared == 0:
return cur
cur = llvmlite.function_get_next(cur)
# 本模块无同名 define:回退到 stub declare(跨模块方法/函数调用)
cur = mod.FuncHead
while cur is not None:
cur_name3: t.CChar | t.CPtr = llvmlite.function_get_name(cur)
if cur_name3 is not None:
cur_len3: t.CSizeT = string.strlen(cur_name3)
if cur_len3 > name_len + 1:
suffix_start3: t.CSizeT = cur_len3 - name_len
if cur_name3[suffix_start3 - 1] == '.':
match3: int = 1
for i in range(name_len):
if cur_name3[suffix_start3 + i] != name[i]:
match3 = 0
break
if match3 == 1:
return cur return cur
cur = llvmlite.function_get_next(cur) cur = llvmlite.function_get_next(cur)
return None return None
@@ -564,6 +589,28 @@ class CallGenMixin:
return None return None
def _is_sha1_mangled_call(self, call_name: str) -> int:
"""判断 call_name 是否为跨模块 Viper 函数("{16位hex}.func" 形式)
C 库函数(strlen 等裸名)和 Win32 API(VirtualAlloc 等裸名)返回 0,
不参与精确返回类型解析(走 _infer_external_func_ret_ty 白名单)。
"""
if call_name is None:
return 0
first_dot: t.CChar | t.CPtr = string.strchr(call_name, '.')
if first_dot is None:
return 0
pre_len: t.CSizeT = t.CSizeT(t.CUInt64T(first_dot) - t.CUInt64T(call_name))
if pre_len != 16:
return 0
for hx in range(16):
ch: int = call_name[hx]
if ch < 48 or ch > 57:
if ch < 97 or ch > 102:
if ch < 65 or ch > 70:
return 0
return 1
def _emit_llvm_memcpy_intrinsic(self, pool: memhub.MemBuddy | t.CPtr, def _emit_llvm_memcpy_intrinsic(self, pool: memhub.MemBuddy | t.CPtr,
builder: llvmlite.IRBuilder | t.CPtr, builder: llvmlite.IRBuilder | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr, mod: llvmlite.LLVMModule | t.CPtr,
@@ -2917,9 +2964,14 @@ class CallGenMixin:
def _LookupMethodRetTyFromTextLL(self, pool: memhub.MemBuddy | t.CPtr, def _LookupMethodRetTyFromTextLL(self, pool: memhub.MemBuddy | t.CPtr,
class_sha1: str, class_sha1: str,
class_name: str, class_name: str,
method_name: str) -> llvmlite.LLVMType | t.CPtr: method_name: str,
"""从依赖模块的 text.ll 中查找方法返回类型""" use_bare: int = 0) -> llvmlite.LLVMType | t.CPtr:
if pool is None or class_sha1 is None or class_name is None or method_name is None: """从依赖模块的 text.ll 中查找方法返回类型
use_bare: 1=按模块级函数查找(搜索 "{sha1}.{method_name}" define,
用于 new_lexer/tokenize 等模块函数;class_name 忽略)。
"""
if pool is None or class_sha1 is None or method_name is None:
return None return None
if self.TempDir is None or self.TempDirLen == 0: if self.TempDir is None or self.TempDirLen == 0:
return None return None
@@ -2968,16 +3020,24 @@ class CallGenMixin:
else: else:
read_buf[_TEXT_LL_READ_BUF_SIZE - 1] = '\0' read_buf[_TEXT_LL_READ_BUF_SIZE - 1] = '\0'
# 构造搜索模式: "{sha1}.{class_name}.{method_name}" # 构造搜索模式: "{sha1}.{class_name}.{method_name}" 或 bare 模式 "{sha1}.{method_name}"
cls_len: t.CSizeT = string.strlen(class_name) cls_len: t.CSizeT = 0
if class_name is not None:
cls_len = string.strlen(class_name)
meth_len: t.CSizeT = string.strlen(method_name) meth_len: t.CSizeT = string.strlen(method_name)
search_len: t.CSizeT = sha1_len + cls_len + meth_len + 4 search_len: t.CSizeT = sha1_len + meth_len + 2
if use_bare == 0:
search_len = sha1_len + cls_len + meth_len + 4
search_buf: bytes = stdlib.malloc(search_len) search_buf: bytes = stdlib.malloc(search_len)
if search_buf is None: if search_buf is None:
stdlib.free(read_buf) stdlib.free(read_buf)
return None return None
viperlib.snprintf(search_buf, search_len, "%s.%s.%s", if use_bare != 0:
class_sha1, class_name, method_name) viperlib.snprintf(search_buf, search_len, "%s.%s",
class_sha1, method_name)
else:
viperlib.snprintf(search_buf, search_len, "%s.%s.%s",
class_sha1, class_name, method_name)
search_str_len: t.CSizeT = string.strlen(search_buf) search_str_len: t.CSizeT = string.strlen(search_buf)
# 在 text.ll 中搜索 define 行包含 search_buf # 在 text.ll 中搜索 define 行包含 search_buf
@@ -3010,8 +3070,15 @@ class CallGenMixin:
# 检查是否匹配 search_buf # 检查是否匹配 search_buf
if name_start + search_str_len <= ls + ll: if name_start + search_str_len <= ls + ll:
if string.strncmp(read_buf + name_start, search_buf, search_str_len) == 0: if string.strncmp(read_buf + name_start, search_buf, search_str_len) == 0:
# 匹配!提取返回类型: [ls+7, at_pos) # 函数名边界检查:search 后必须是 '"'(带引号名)、'('(参数表)或行尾,
result_ty = self._ParseLLVMRetTyStr(pool, read_buf, ls + 7, at_pos) # 否则 "sha1.func" 会误匹配 "sha1.func_helper" 等前缀同名函数
name_end: t.CSizeT = name_start + search_str_len
end_ch: int = 0
if name_end < ls + ll:
end_ch = read_buf[name_end]
if end_ch == 34 or end_ch == 40 or end_ch == 0:
# 匹配!提取返回类型: [ls+7, at_pos)
result_ty = self._ParseLLVMRetTyStr(pool, read_buf, ls + 7, at_pos)
pos = le + 1 pos = le + 1
@@ -3019,6 +3086,45 @@ class CallGenMixin:
stdlib.free(read_buf) stdlib.free(read_buf)
return result_ty return result_ty
# ============================================================
# _dep_text_ll_exists - 探测依赖模块 text.ll 是否已生成
#
# 用于区分跨模块返回类型精确解析失败的两种场景:
# 1. text.ll 存在但函数未命中 → 真错误(依赖已编译,函数缺失)
# 2. text.ll 不存在 → 依赖模块尚未翻译(Phase A 按文件顺序翻译,
# main.vp 先于其 import 的 mbuddytest.vp),标记重试待下遍
# ============================================================
def _dep_text_ll_exists(self, pool: memhub.MemBuddy | t.CPtr,
sha1: str) -> int:
"""探测 {temp_dir}/{sha1[0:2]}/{sha1}.text.ll 是否存在,1=存在"""
if self.TempDir is None or self.TempDirLen == 0 or sha1 is None:
return 0
sha1_len: t.CSizeT = string.strlen(sha1)
if sha1_len < 2:
return 0
path_len: t.CSizeT = self.TempDirLen + sha1_len + 14
path_buf: bytes = stdlib.malloc(path_len)
subdir_buf: bytes = stdlib.malloc(3)
if path_buf is None or subdir_buf is None:
if path_buf is not None:
stdlib.free(path_buf)
if subdir_buf is not None:
stdlib.free(subdir_buf)
return 0
subdir_buf[0] = sha1[0]
subdir_buf[1] = sha1[1]
subdir_buf[2] = '\0'
viperlib.snprintf(path_buf, path_len, "%s/%s/%s.text.ll",
self.TempDir, subdir_buf, sha1)
stdlib.free(subdir_buf)
tf: fileio.File | t.CPtr = fileio.File(path_buf, fileio.MODE.R)
exists: int = 0
if not tf.closed:
tf.close()
exists = 1
stdlib.free(path_buf)
return exists
# ============================================================ # ============================================================
# 属性方法(@property/@x.setter/@x.deleter)与静态/类方法调用辅助 # 属性方法(@property/@x.setter/@x.deleter)与静态/类方法调用辅助
# #
@@ -5997,6 +6103,71 @@ class CallGenMixin:
re_inferred_ty: llvmlite.LLVMType | t.CPtr = self._infer_external_func_ret_ty(pool, orig_func_name) re_inferred_ty: llvmlite.LLVMType | t.CPtr = self._infer_external_func_ret_ty(pool, orig_func_name)
if re_inferred_ty is not None: if re_inferred_ty is not None:
call_ret_ty = re_inferred_ty call_ret_ty = re_inferred_ty
# ============================================================
# 跨模块 Viper 函数返回类型精确解析(对齐方法路径,fail-fast)
# 根因:_infer_external_func_ret_ty 白名单未覆盖返回指针的模块函数
# (new_lexer→Lexer* / tokenize→Token* / parse_tokens→AST*),兜底 i32
# 导致 64 位指针被截断高 32 位 → inttoptr 无效指针 → 解引用崩溃
# (AstTest 的 _lexer_init 崩溃链)。declare 也可能是错误 i32(_ensure_method_declare
# 曾用兜底类型创建),因此不信任 found_func 的 declare 返回类型。
# 权威来源:text.ll define;次选:vpi stub 返回注解;均未命中立即终止。
# 仅对 mangled("{16位hex}.func")调用生效;C 库/Win32 裸名走原白名单。
# Phase 1a(declare_only)期间依赖 text.ll 未生成,保持原白名单行为不 fail-fast。
# ============================================================
if self._is_sha1_mangled_call(call_name) != 0:
if trans is None or trans._declare_only == 0:
cf_dot: t.CChar | t.CPtr = string.strchr(call_name, '.')
cf_last: t.CChar | t.CPtr = string.strrchr(call_name, '.')
if cf_dot is not None and cf_last is not None:
cf_sha1: t.CChar | t.CPtr = stdlib.malloc(17)
cf_bare_len: t.CSizeT = string.strlen(call_name) - \
(t.CSizeT(t.CUInt64T(cf_last) - t.CUInt64T(call_name))) - 1
cf_bare: t.CChar | t.CPtr = stdlib.malloc(cf_bare_len + 1)
if cf_sha1 is not None and cf_bare is not None:
string.strncpy(cf_sha1, call_name, 16)
cf_sha1[16] = '\0'
string.strcpy(cf_bare, cf_last + 1)
cf_ret: llvmlite.LLVMType | t.CPtr = self._LookupMethodRetTyFromTextLL(
pool, cf_sha1, None, cf_bare, 1)
if cf_ret is not None:
call_ret_ty = cf_ret
else:
cf_vpi: llvmlite.LLVMType | t.CPtr = self._lookup_method_ret_ty_via_vpi(
pool, cf_sha1, None, cf_bare)
if cf_vpi is not None:
call_ret_ty = cf_vpi
else:
# 区分两种失败场景:
# 1. 依赖模块 text.ll 存在但函数未命中 → 真错误,fail-fast
# 2. 依赖模块 text.ll 不存在(模块尚未翻译)→ Phase A 按
# 文件顺序翻译,main.vp 先于其 import 的 mbuddytest.vp
# (同项目多源文件)。此时标记 trans._deps_pending=1,
# 保持白名单类型继续,Phase A 多遍循环下遍重译——
# 届时依赖已生成 text.ll,可精确解析(治本:依赖先翻译)。
if self._dep_text_ll_exists(pool, cf_sha1) != 0:
cf_err: t.CChar | t.CPtr = VLogger.fmt_buf()
if cf_err is not None:
viperlib.snprintf(
cf_err, 1024,
"跨模块函数 '%s' 返回类型无法精确获取(text.ll/vpi 均未命中),"
"默认 i32 会截断指针/i64,请检查依赖模块是否已编译/注册", call_name)
VLogger.error(cf_err, "HandlesExprCall")
HandlesType.fatal_error(
node,
f"跨模块函数返回类型无法精确获取(text.ll/vpi 均未命中),默认 i32 会截断指针: {call_name}",
"TypeError")
else:
if trans is not None:
trans._deps_pending = 1
cf_dbg: t.CChar | t.CPtr = VLogger.fmt_buf()
if cf_dbg is not None:
viperlib.snprintf(
cf_dbg, 1024,
"跨模块函数 '%s' 依赖模块 text.ll 尚未生成(Phase A 顺序),"
"标记重试,本遍保持默认类型", call_name)
VLogger.warning(cf_dbg, "HandlesExprCall")
stdlib.free(cf_sha1)
stdlib.free(cf_bare)
# 跨模块调用:创建 declare 声明,避免 llc 报 undefined value # 跨模块调用:创建 declare 声明,避免 llc 报 undefined value
self._ensure_method_declare(pool, mod, call_name, call_ret_ty, head, can) self._ensure_method_declare(pool, mod, call_name, call_ret_ty, head, can)
# 检查被调用函数是否为变长函数(*args),若是则对变长参数进行 C ABI 提升 # 检查被调用函数是否为变长函数(*args),若是则对变长参数进行 C ABI 提升
@@ -132,6 +132,11 @@ class Translator:
# Phase 1a 声明模式标志:1=只注册 struct/enum/union 不翻译代码体,0=全量翻译 # Phase 1a 声明模式标志:1=只注册 struct/enum/union 不翻译代码体,0=全量翻译
_declare_only: t.CInt _declare_only: t.CInt
# Phase A 依赖未就绪标志:1=本文件翻译时依赖模块的 text.ll 尚未生成
# (同项目源文件按顺序翻译,main.vp 先于其 import 的 mbuddytest.vp),
# 跨模块函数返回类型无法精确解析。Phase A 多遍循环据此下遍重试。
_deps_pending: t.CInt
# 子 Handle 指针(每个 Handle 一个槽,通过 Mixin 回指针访问本结构体) # 子 Handle 指针(每个 Handle 一个槽,通过 Mixin 回指针访问本结构体)
AssignH: HandlesAssign.AssignHandle | t.CPtr AssignH: HandlesAssign.AssignHandle | t.CPtr
ReturnH: HandlesReturn.ReturnHandle | t.CPtr ReturnH: HandlesReturn.ReturnHandle | t.CPtr
@@ -175,6 +180,7 @@ class Translator:
self.GenericTypeParamNames = None self.GenericTypeParamNames = None
self.GenericTypeArgs = None self.GenericTypeArgs = None
self._declare_only = 0 self._declare_only = 0
self._deps_pending = 0
self.AssignH = None self.AssignH = None
self.ReturnH = None self.ReturnH = None
self.ImportsH = None self.ImportsH = None
+127 -72
View File
@@ -415,84 +415,139 @@ def RunMultiFileProject(mb: memhub.MemBuddy | t.CPtr,
if app_deps_buf is not None: if app_deps_buf is not None:
app_deps_buf[0] = '\0' app_deps_buf[0] = '\0'
for i in range(file_count): # Phase A 多遍重试:同项目多源文件时,main.vp 可能先于其 import 的
ea: t.CUInt64T = t.CUInt64T(entries) + i * entry_size # mbuddytest.vp 翻译(文件系统顺序),跨模块函数返回类型精确解析会因
ent: SrcFileEntry | t.CPtr = (SrcFileEntry | t.CPtr)(t.CVoid(ea, t.CPtr)) # 依赖 text.ll 未生成而标记 _deps_pending=1(见 CallGenMixin 跨模块
if ent is None or ent.Path is None: # 返回类型解析段)。下遍重译即可命中(依赖已生成 text.ll)。
continue # 每遍至少完成一个文件,最多 file_count 遍收敛。
fb_loop: t.CChar | t.CPtr = VLogger.fmt_buf() done_flags: bytes = stdlib.malloc(file_count)
if fb_loop is not None: if done_flags is None:
viperlib.snprintf(fb_loop, 1024, "Phase A 开始翻译: %s", ent.Path) VLogger.error("Phase A done_flags 分配失败", "PhaseA")
VLogger.info(fb_loop, "PhaseA-DBG") stdlib.free(entries)
# 计算源文件的包名(相对 source_dir 的目录部分) return 1
src_dir_len_pa: t.CSizeT = string.strlen(source_dir) string.memset(done_flags, 0, file_count)
pkg_pa: str = None PHASE_A_MAX_PASSES: t.CInt = 32
if string.strlen(ent.Path) > src_dir_len_pa + 1: for pass_a in range(PHASE_A_MAX_PASSES):
rel_path_pa: str = ent.Path + src_dir_len_pa + 1 completed_this_pass: int = 0
pkg_pa = HandlesImports.compute_package_from_relpath(mb, rel_path_pa) for i in range(file_count):
tr_a: HandlesTranslator.Translator | t.CPtr = BuildPipeline.TranslateFileGetTrans(mb, ent.Path, ent.Sha1, pkg_pa, 0, temp_dir) if done_flags[i] != 0:
if tr_a is None: continue
continue ea: t.CUInt64T = t.CUInt64T(entries) + i * entry_size
fb_ok: t.CChar | t.CPtr = VLogger.fmt_buf() ent: SrcFileEntry | t.CPtr = (SrcFileEntry | t.CPtr)(t.CVoid(ea, t.CPtr))
if fb_ok is not None: if ent is None or ent.Path is None:
viperlib.snprintf(fb_ok, 1024, "Phase A 翻译完成: %s", ent.Path) done_flags[i] = 1
VLogger.info(fb_ok, "PhaseA-DBG") completed_this_pass += 1
continue
fb_loop: t.CChar | t.CPtr = VLogger.fmt_buf()
if fb_loop is not None:
viperlib.snprintf(fb_loop, 1024, "Phase A 开始翻译: %s", ent.Path)
VLogger.info(fb_loop, "PhaseA-DBG")
# 计算源文件的包名(相对 source_dir 的目录部分)
src_dir_len_pa: t.CSizeT = string.strlen(source_dir)
pkg_pa: str = None
if string.strlen(ent.Path) > src_dir_len_pa + 1:
rel_path_pa: str = ent.Path + src_dir_len_pa + 1
pkg_pa = HandlesImports.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)
if tr_a is None:
# 翻译失败(语法/解析错误):记录待下遍重试,收敛检查统一报错
continue
# 收集 _imported_modules 到 app_deps_buf(供 Phase B+ 按需编译)。
# 依赖在翻译早期已填充,即使 _deps_pending 也收集。
if tr_a._imported_modules is not None and app_deps_buf is not None:
im_len_a: t.CSizeT = string.strlen(tr_a._imported_modules)
if im_len_a > 0 and app_deps_len + im_len_a + 1 < APP_DEPS_BUF_SIZE:
if app_deps_len > 0:
app_deps_buf[app_deps_len] = ' '
app_deps_len = app_deps_len + 1
string.strcpy(app_deps_buf + app_deps_len, tr_a._imported_modules)
app_deps_len = app_deps_len + im_len_a
app_deps_buf[app_deps_len] = '\0'
if tr_a._deps_pending != 0:
# 依赖模块 text.ll 未生成(同项目文件顺序),不写盘,下遍重试
fb_dp: t.CChar | t.CPtr = VLogger.fmt_buf()
if fb_dp is not None:
viperlib.snprintf(fb_dp, 1024,
"Phase A 依赖未就绪,下遍重试: %s", ent.Path)
VLogger.warning(fb_dp, "PhaseA-DBG")
continue
fb_ok: t.CChar | t.CPtr = VLogger.fmt_buf()
if fb_ok is not None:
viperlib.snprintf(fb_ok, 1024, "Phase A 翻译完成: %s", ent.Path)
VLogger.info(fb_ok, "PhaseA-DBG")
# dump stub IR (declarations only) # dump stub IR (declarations only)
stub_buf_a: bytes = stdlib.malloc(PHASE_A_IR_SIZE) stub_buf_a: bytes = stdlib.malloc(PHASE_A_IR_SIZE)
if stub_buf_a is None: if stub_buf_a is None:
continue continue
tr_a.dump_ir(stub_buf_a, PHASE_A_IR_SIZE, llvmlite.OUTPUT_STUB) tr_a.dump_ir(stub_buf_a, PHASE_A_IR_SIZE, llvmlite.OUTPUT_STUB)
stub_len_a: t.CSizeT = string.strlen(stub_buf_a) stub_len_a: t.CSizeT = string.strlen(stub_buf_a)
# save stub.ll (切片路径: temp_dir/{sha1前缀}/{sha1}.stub.ll) # save stub.ll (切片路径: temp_dir/{sha1前缀}/{sha1}.stub.ll)
stub_path_a: str = StubMerger._sliced_path(temp_dir, td_len_pa, ent.Sha1, "stub.ll") stub_path_a: str = StubMerger._sliced_path(temp_dir, td_len_pa, ent.Sha1, "stub.ll")
if stub_path_a is not None: if stub_path_a is not None:
sf_a: fileio.File | t.CPtr = fileio.File(stub_path_a, fileio.MODE.W) sf_a: fileio.File | t.CPtr = fileio.File(stub_path_a, fileio.MODE.W)
if not sf_a.closed: if not sf_a.closed:
sf_a.write(stub_buf_a, stub_len_a) sf_a.write(stub_buf_a, stub_len_a)
sf_a.close() sf_a.close()
stdlib.free(stub_path_a) stdlib.free(stub_path_a)
stdlib.free(stub_buf_a) stdlib.free(stub_buf_a)
# dump text IR (definitions only) # dump text IR (definitions only)
text_buf_a: bytes = stdlib.malloc(PHASE_A_IR_SIZE) text_buf_a: bytes = stdlib.malloc(PHASE_A_IR_SIZE)
if text_buf_a is None: if text_buf_a is None:
continue continue
tr_a.dump_ir(text_buf_a, PHASE_A_IR_SIZE, llvmlite.OUTPUT_TEXT) tr_a.dump_ir(text_buf_a, PHASE_A_IR_SIZE, llvmlite.OUTPUT_TEXT)
text_len_a: t.CSizeT = string.strlen(text_buf_a) text_len_a: t.CSizeT = string.strlen(text_buf_a)
# save text.ll (切片路径) # save text.ll (切片路径)
text_path_a: str = StubMerger._sliced_path(temp_dir, td_len_pa, ent.Sha1, "text.ll") text_path_a: str = StubMerger._sliced_path(temp_dir, td_len_pa, ent.Sha1, "text.ll")
if text_path_a is not None: if text_path_a is not None:
tf_a: fileio.File | t.CPtr = fileio.File(text_path_a, fileio.MODE.W) tf_a: fileio.File | t.CPtr = fileio.File(text_path_a, fileio.MODE.W)
if not tf_a.closed: if not tf_a.closed:
tf_a.write(text_buf_a, text_len_a) tf_a.write(text_buf_a, text_len_a)
tf_a.close() tf_a.close()
stdlib.free(text_path_a) stdlib.free(text_path_a)
stdlib.free(text_buf_a) stdlib.free(text_buf_a)
# save dependencies (_imported_modules) for Phase B (切片路径) # save dependencies (_imported_modules) for Phase B (切片路径)
deps_path_a: str = StubMerger._sliced_path(temp_dir, td_len_pa, ent.Sha1, "deps.txt") deps_path_a: str = StubMerger._sliced_path(temp_dir, td_len_pa, ent.Sha1, "deps.txt")
if deps_path_a is not None: if deps_path_a is not None:
df_a: fileio.File | t.CPtr = fileio.File(deps_path_a, fileio.MODE.W) df_a: fileio.File | t.CPtr = fileio.File(deps_path_a, fileio.MODE.W)
if not df_a.closed: if not df_a.closed:
if tr_a._imported_modules is not None: if tr_a._imported_modules is not None:
dl_a: t.CSizeT = string.strlen(tr_a._imported_modules) dl_a: t.CSizeT = string.strlen(tr_a._imported_modules)
df_a.write(tr_a._imported_modules, dl_a) df_a.write(tr_a._imported_modules, dl_a)
df_a.close() df_a.close()
stdlib.free(deps_path_a) stdlib.free(deps_path_a)
# 收集 _imported_modules 到 app_deps_buf(供 Phase B+ 按需编译) done_flags[i] = 1
if tr_a._imported_modules is not None and app_deps_buf is not None: completed_this_pass += 1
im_len_a: t.CSizeT = string.strlen(tr_a._imported_modules) # 收敛检查:所有文件完成则结束
if im_len_a > 0 and app_deps_len + im_len_a + 1 < APP_DEPS_BUF_SIZE: all_done: int = 1
if app_deps_len > 0: for i_ck in range(file_count):
app_deps_buf[app_deps_len] = ' ' if done_flags[i_ck] == 0:
app_deps_len = app_deps_len + 1 all_done = 0
string.strcpy(app_deps_buf + app_deps_len, tr_a._imported_modules) break
app_deps_len = app_deps_len + im_len_a if all_done != 0:
app_deps_buf[app_deps_len] = '\0' break
if completed_this_pass == 0:
# 无进展:剩余文件无法完成(真实编译错误),终止并列出未完成文件
for i_err in range(file_count):
if done_flags[i_err] == 0:
ea_err: t.CUInt64T = t.CUInt64T(entries) + i_err * entry_size
ent_err: SrcFileEntry | t.CPtr = (SrcFileEntry | t.CPtr)(t.CVoid(ea_err, t.CPtr))
if ent_err is not None and ent_err.Path is not None:
fb_err: t.CChar | t.CPtr = VLogger.fmt_buf()
if fb_err is not None:
viperlib.snprintf(fb_err, 1024,
"Phase A 无法完成翻译: %s(请检查依赖模块是否已编译/注册)", ent_err.Path)
VLogger.error(fb_err, "PhaseA")
stdlib.free(done_flags)
VLogger.error("Phase A 多遍收敛失败,终止编译", "PhaseA")
stdlib.free(entries)
return 1
stdlib.free(done_flags)
if do_phase2 == 0: if do_phase2 == 0:
VLogger.info("Phase A 完成(仅 stub 生成)", "project") VLogger.info("Phase A 完成(仅 stub 生成)", "project")