Files
ViperC/App/lib/Phase/Phase2/__init__.vp
T
TermiNexus 981af07d5c fix(phase): App 预注册期注入依赖 vpi,修 list[ast.ClassDef*] 票名前缀分裂
Phase A-pre(App 预注册)此前不做导入处理、不注入依赖 .vpi,导致包重导出
类的字段注解在预注册期只能保留「包前缀」(1abcd…),并被冻结进结构体布局;
阶段 A 构造函数侧却能借包别名 entry 校正为真定义前缀(e70bd…),两处票名
分裂 ⇒「非基本类型指针不可隐式转换」(现场 ClassGenGeneric.vp:92)。

改动(只放开 App 预注册,不动 includes 预注册那条防依赖环自锁的约束):
- Translator 新增 _pre_allow_dep_inject 开关,并在导入处理门拆成两道:
  ①导入处理 + 自动容器可见性 + LoadDepVpiIntoModule —— 全量翻译与 App 预注册共用;
  ②模块级变量预注册 + 函数前向声明 —— 仍只在全量翻译做。
- BuildPipeline.TranslateFileGetTrans 新增 pre_allow_dep_inject 参数并写入 Translator。
- Phase2 App 预注册调用点传 1;includes 预注册保持默认 0。
2026-10-02 01:03:31 +08:00

1724 lines
92 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import lib.core.Gen.GenApi as GenApi
import t, c
from stdint import *
import stdio
import string
import stdlib
import memhub
import viperlib
import w32.fileio as fileio
import w32.win32file
import w32.win32base
import sys
import ast
import llvmlite
import subprocess
import argparse
import lib.core.VLogger as VLogger
import lib.core.Gen.Translator as HandlesTranslator
import lib.Phase.BuildPipeline as BuildPipeline
import lib.core.IncludesScanner as IncludesScanner
import lib.core.CompilerFactory as CompilerFactory
import lib.Stub.StubMerger as StubMerger
import lib.Projectrans.Utils as Utils
import lib.Projectrans.Config as Config
import lib.Stub.StubMerger.StubMergerBuild as StubMergerBuild
import lib.Stub.StubMerger.StubMergerCollect as StubMergerCollect
import lib.Stub.StubMerger.StubMergerIR as StubMergerIR
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
import lib.Stub.StubMerger.StubMergerTopo as StubMergerTopo
import lib.Stub.StubGen.Converter as StubConverter
# 全局 mbuddy 指针
_mbuddy: memhub.MemBuddy*
# 源代码缓冲区大小(1MB)
SRC_BUF_SIZE: t.CDefine = 1048576
# 最大源文件数(递归扫描子目录后文件数增多;过小会截断文件列表 → 链接 undefined symbol,故留余量)
MAX_SRC_FILES: t.CDefine = 128
@t.NoVTable
class SrcFileEntry:
"""源文件条目"""
Path: str # 完整路径
Sha1: str # SHA1 前16字符
# ============================================================
# _ScanDirForPyFiles - 递归扫描目录,收集 .vp/.py 文件到 entries(遇目录递归,遇文件读取内容、计算 SHA1、加入 entries)
#
# Args: mb 内存池;dir_path 当前扫描目录;entries SrcFileEntry 数组;entry_size 单个条目大小(SrcFileEntry.__sizeof__());file_count 当前已收集的文件数;max_files 最大文件数
# Returns: 新的 file_count
# ============================================================
def _ScanDirForPyFiles(mb: memhub.MemBuddy*,
dir_path: str,
entries: SrcFileEntry | t.CPtr,
entry_size: sizet,
file_count: int, max_files: int) -> int:
"""递归扫描目录,收集 .vp/.py 文件到 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: sizet = 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: sizet = 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: sizet = 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 = _ScanDirForPyFiles(mb, sub_dir, entries, entry_size, file_count, max_files)
stdlib.free(sub_dir)
if w32.win32file.FindNextFileA(handle, find_data) == 0:
break
continue
# 检查是否是 .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:
sbuf: bytes = Utils.read_file_to_buf(full_path, SRC_BUF_SIZE)
if sbuf is None:
if w32.win32file.FindNextFileA(handle, find_data) == 0:
break
continue
# 统一用 IncludesScanner.compute_file_sha1(CRLF→LF 归一化),与 StubMerger 对同一批 App 源文件的口径一致;
# 用原始字节口径会导致 stub/define 符号前缀不一致 → 链接 undefined reference。
sha1_val: str = IncludesScanner.compute_file_sha1(mb, full_path)
if sha1_val is not None:
ea: uint64 = uint64(entries) + file_count * entry_size
ent: SrcFileEntry | t.CPtr = (SrcFileEntry | t.CPtr)(ea)
if ent is not None:
ent.Path = full_path
ent.Sha1 = sha1_val
file_count += 1
VLogger.info(f"{fname} (sha1={sha1_val})", "project")
stdlib.free(sbuf)
# 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
# ============================================================
# _ScanDirForAsmFiles - 递归扫描目录,收集 .s 汇编文件
#
# 与 _ScanDirForPyFiles 同构:VKernel 等裸机项目含 .s(如
# Kernel/intr/isr.s 定义 isr0/syscall_entry/_per_cpu 等符号)。
# TPV 不对 .s 翻译,直接用 llvm-mc 汇编为 .obj 参与链接。
# ============================================================
def _ScanDirForAsmFiles(mb: memhub.MemBuddy*,
dir_path: str,
entries: SrcFileEntry | t.CPtr,
entry_size: sizet,
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: sizet = 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: sizet = 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: sizet = 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: uint64 = uint64(entries) + file_count * entry_size
ent: SrcFileEntry | t.CPtr = (SrcFileEntry | t.CPtr)(ea)
if ent is not None:
ent.Path = full_path
ent.Sha1 = sha1_val
file_count += 1
VLogger.info(f"{fname} (asm sha1={sha1_val})", "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 源文件泛型类模板
#
# 泛型模板表(GenericClassNames 等 4 张 + 模板节点)是 pool 懒分配的跨文件持久表,若在 mark2 之后(Phase A-pre 翻译 App 文件时)
# 才首次注册,reset_to(mark2) 会回收表与节点 → 后续查重/特化遍历悬空内存崩溃;App 文件数量少,mark2 前预注册的 AST 驻留内存可忽略。
# 重复注册由 _register_generic_template 查重跳过。
# ============================================================
def _PreRegisterAppGenericTemplates(mb: memhub.MemBuddy*,
entries: SrcFileEntry | t.CPtr,
file_count: int,
entry_size: sizet) -> int:
"""mark2 前预注册 App 源文件的泛型类模板,返回处理到的泛型类数量"""
if mb is None or entries is None or file_count <= 0:
return 0
handled: int = 0
for i in range(file_count):
ea_pa: uint64 = uint64(entries) + i * entry_size
ent_pa: SrcFileEntry | t.CPtr = (SrcFileEntry | t.CPtr)(ea_pa)
if ent_pa is None or ent_pa.Path is None or ent_pa.Sha1 is None:
continue
# 读取文件
f_pa: rawptr[fileio.File] = fileio.File(ent_pa.Path, fileio.MODE.R)
if f_pa.closed:
continue
# read_all 的容量参数含终止符(最多读 cap-1 字节), 文件更长会被静默截断
if f_pa.size() >= SRC_BUF_SIZE:
VLogger.error(f"源文件超过 {SRC_BUF_SIZE} 字节上限, 拒绝读取: {ent_pa.Path}", "PhaseA")
f_pa.close()
continue
src_buf_pa: bytes = stdlib.malloc(SRC_BUF_SIZE)
if src_buf_pa is None:
f_pa.close()
continue
bytes_read_pa: LONG = f_pa.read_all(src_buf_pa, SRC_BUF_SIZE)
f_pa.close()
if bytes_read_pa <= 0:
stdlib.free(src_buf_pa)
continue
if bytes_read_pa < SRC_BUF_SIZE:
src_buf_pa[bytes_read_pa] = 0
# 解析 AST(只解析,不完整翻译;用永久池使模板节点跨 mark2 reset 存活)
perm_pa: memhub.MemBuddy* = CompilerFactory.factory.PermPool
if perm_pa is None:
perm_pa = mb
lx_pa: ast.Lexer* = ast.new_lexer(perm_pa)
if lx_pa is None:
stdlib.free(src_buf_pa)
continue
ast._lexer_init(lx_pa, src_buf_pa, perm_pa)
tokens_pa: ast.Token* = ast.tokenize(lx_pa)
tree_pa: ast.AST* = ast.parse_tokens(perm_pa, tokens_pa)
if tree_pa is None:
stdlib.free(src_buf_pa)
continue
# 遍历顶层 ClassDef,注册泛型模板(type_params 非 None)
ch_pa: list[ast.AST*]* = tree_pa.children
if ch_pa is not None:
cn_pa: sizet = len(ch_pa)
for ci_pa in range(cn_pa):
child_pa: ast.AST* = ch_pa.get(ci_pa)
if child_pa is None or child_pa.kind() != ast.ASTKind.ClassDef:
continue
cd_pa: ast.ClassDef* = ast.ClassDef*(child_pa)
if cd_pa is None or cd_pa.name is None:
continue
tp_pa: list[str]* = cd_pa.type_params
if tp_pa is None:
continue
GenApi.set_current_file_type(ent_pa.Path)
GenApi._register_generic_template(perm_pa, cd_pa, ent_pa.Sha1)
handled += 1
stdlib.free(src_buf_pa)
if handled > 0:
VLogger.info(f"mark2 前预注册 {handled} 个 App 泛型类模板", "PhaseA")
return handled
# ============================================================
# Phase A 收敛失败依赖图诊断
#
# 多遍收敛失败时仅报 "无法完成翻译: <路径>" 无法定位「直接依赖均已生成但缺失在传递依赖/MRO/vpi 侧」。
# 故翻译过程中把每个待译文件的依赖模块名与缺失明细持久化到 stdlib 全局表(跨 mb.reset_to(mark2) 存活),
# 收敛失败时按文件输出依赖树,用 [MISS]/[OK] 标出缺 text.ll 的节点,并回显 pending 明细。
# ============================================================
PA_DEPS_MAX_STR: sizet = 1024
PA_MISS_MAX_STR: sizet = 768
PA_GRAPH_DEPTH_MAX: int = 3 # 缺失链下钻深度(第 1 层开始计数)
PA_GRAPH_OK_PROBE: int = 2 # 就绪节点仅再探测 1 层(找隐藏缺失)
_pa_diag_max: sizet = 0
_pa_diag_count: int = 0
_pa_diag_sha1: bytes = None
_pa_diag_deps: bytes = None
_pa_diag_miss: bytes = None
def _PhaseADiagInit(max_count: sizet) -> None:
"""分配 Phase A 收敛失败诊断持久表(stdlib,跨 reset 存活)"""
global _pa_diag_max, _pa_diag_count, _pa_diag_sha1, _pa_diag_deps, _pa_diag_miss
_pa_diag_max = max_count
_pa_diag_count = 0
_pa_diag_sha1 = stdlib.malloc(max_count * 17)
_pa_diag_deps = stdlib.malloc(max_count * PA_DEPS_MAX_STR)
_pa_diag_miss = stdlib.malloc(max_count * PA_MISS_MAX_STR)
if _pa_diag_sha1 is not None:
string.memset(_pa_diag_sha1, 0, max_count * 17)
if _pa_diag_deps is not None:
string.memset(_pa_diag_deps, 0, max_count * PA_DEPS_MAX_STR)
if _pa_diag_miss is not None:
string.memset(_pa_diag_miss, 0, max_count * PA_MISS_MAX_STR)
def _PhaseADiagFree() -> None:
"""释放诊断持久表"""
global _pa_diag_max, _pa_diag_count, _pa_diag_sha1, _pa_diag_deps, _pa_diag_miss
if _pa_diag_sha1 is not None:
stdlib.free(_pa_diag_sha1)
_pa_diag_sha1 = None
if _pa_diag_deps is not None:
stdlib.free(_pa_diag_deps)
_pa_diag_deps = None
if _pa_diag_miss is not None:
stdlib.free(_pa_diag_miss)
_pa_diag_miss = None
_pa_diag_count = 0
_pa_diag_max = 0
def _PhaseADiagRecord(sha1: str, deps: str, miss: str) -> None:
"""记录/更新单文件诊断:sha1 → 依赖串 + 缺失明细(None 表示不更新该字段)"""
global _pa_diag_count
if _pa_diag_sha1 is None or _pa_diag_deps is None or sha1 is None:
return
if string.strlen(sha1) < 16:
return
for i in range(_pa_diag_count):
es: bytes = _pa_diag_sha1 + sizet(i) * 17
if string.strcmp(es, sha1) == 0:
if miss is not None and _pa_diag_miss is not None:
maddr: bytes = _pa_diag_miss + sizet(i) * PA_MISS_MAX_STR
string.strncpy(maddr, miss, PA_MISS_MAX_STR - 1)
maddr[PA_MISS_MAX_STR - 1] = 0
return
if _pa_diag_count >= _pa_diag_max:
return
saddr: bytes = _pa_diag_sha1 + sizet(_pa_diag_count) * 17
string.strncpy(saddr, sha1, 16)
saddr[16] = 0
daddr: bytes = _pa_diag_deps + sizet(_pa_diag_count) * PA_DEPS_MAX_STR
if deps is not None:
string.strncpy(daddr, deps, PA_DEPS_MAX_STR - 1)
daddr[PA_DEPS_MAX_STR - 1] = 0
if miss is not None and _pa_diag_miss is not None:
maddr2: bytes = _pa_diag_miss + sizet(_pa_diag_count) * PA_MISS_MAX_STR
string.strncpy(maddr2, miss, PA_MISS_MAX_STR - 1)
maddr2[PA_MISS_MAX_STR - 1] = 0
_pa_diag_count += 1
def _PhaseADiagDepsLookup(sha1: str) -> str:
"""按 sha1 查持久化依赖串(stdlib.malloc 副本,调用者 free;未命中 None)"""
if _pa_diag_sha1 is None or _pa_diag_deps is None or sha1 is None:
return None
for i in range(_pa_diag_count):
es: bytes = _pa_diag_sha1 + sizet(i) * 17
if string.strcmp(es, sha1) == 0:
src: bytes = _pa_diag_deps + sizet(i) * PA_DEPS_MAX_STR
ln: sizet = string.strlen(src)
out: bytes = stdlib.malloc(ln + 1)
if out is None:
return None
string.strcpy(out, src)
return out
return None
def _PhaseADiagMissLookup(sha1: str) -> str:
"""按 sha1 查持久化缺失明细(stdlib.malloc 副本,调用者 free;未命中/空 None)"""
if _pa_diag_sha1 is None or _pa_diag_miss is None or sha1 is None:
return None
for i in range(_pa_diag_count):
es: bytes = _pa_diag_sha1 + sizet(i) * 17
if string.strcmp(es, sha1) == 0:
src: bytes = _pa_diag_miss + sizet(i) * PA_MISS_MAX_STR
ln: sizet = string.strlen(src)
if ln == 0:
return None
out: bytes = stdlib.malloc(ln + 1)
if out is None:
return None
string.strcpy(out, src)
return out
return None
def _PhaseATextLLExists(temp_dir: str, td_len: sizet, sha1: str) -> int:
"""探测 {temp_dir}/{sha1前2位}/{sha1}.text.ll 是否存在,1=存在"""
if temp_dir is None or td_len == 0 or sha1 is None or string.strlen(sha1) < 2:
return 0
tpath: str = StubMergerCollect._sliced_path(temp_dir, td_len, sha1, "text.ll")
if tpath is None:
return 0
tf: rawptr[fileio.File] = fileio.File(tpath, fileio.MODE.R)
exists: int = 0
if not tf.closed:
tf.close()
exists = 1
stdlib.free(tpath)
return exists
def _PhaseAResolveDepSha1(mod_name: str) -> str:
"""依赖模块名 → SHA1(精确 → 后缀兜底),未命中 None"""
if mod_name is None:
return None
s1: str = StubMergerStore.LookupSha1ByModName(mod_name)
if s1 is not None:
return s1
return StubMergerStore.LookupSha1ByModNameSuffix(mod_name)
def _PhaseADumpNode(temp_dir: str, td_len: sizet, sha1: str,
depth: int, prefix: bytes) -> None:
"""递归打印依赖子图;[MISS]=缺 text.ll(下钻整链),[OK]=就绪(仅探测 1 层)"""
deps: bytes = None
if sha1 is not None:
deps = _PhaseADiagDepsLookup(sha1)
if deps is None and sha1 is not None:
# 回退:读取 {temp_dir}/{前缀}/{sha1}.deps.txt(Phase1 已为 includes 写盘)
dpath: str = StubMergerCollect._sliced_path(temp_dir, td_len, sha1, "deps.txt")
if dpath is not None:
df: rawptr[fileio.File] = fileio.File(dpath, fileio.MODE.R)
if not df.closed:
deps = stdlib.malloc(PA_DEPS_MAX_STR)
if deps is not None:
rl: int64 = df.read_all(deps, PA_DEPS_MAX_STR)
if rl < 0:
rl = 0
# read_all 的容量参数即缓冲区容量(含终止符):传真实容量 MAX 时最多读 MAX-1 字节,故 rl 恒 <= MAX-1,在界内直接写终止符即可。
deps[rl] = 0
df.close()
stdlib.free(dpath)
if deps is None or deps[0] == 0:
if deps is not None:
stdlib.free(deps)
return
dl: sizet = string.strlen(deps)
token_count: int = 0
pos: sizet = 0
while pos < dl:
while pos < dl and deps[pos] == ' ':
pos += 1
if pos >= dl:
break
token_count += 1
while pos < dl and deps[pos] != ' ':
pos += 1
pos = 0
idx: int = 0
while pos < dl:
while pos < dl and deps[pos] == ' ':
pos += 1
if pos >= dl:
break
ts: sizet = pos
while pos < dl and deps[pos] != ' ':
pos += 1
tl: sizet = pos - ts
nm: bytes = stdlib.malloc(tl + 1)
if nm is not None:
string.strncpy(nm, deps + ts, tl)
nm[tl] = 0
is_last: int = 0
if idx + 1 >= token_count:
is_last = 1
builtin: int = 0
if string.strcmp(nm, "t") == 0 or string.strcmp(nm, "c") == 0:
builtin = 1
dep_sha1: str = _PhaseAResolveDepSha1(nm)
txt_ok: int = 0
if dep_sha1 is not None:
txt_ok = _PhaseATextLLExists(temp_dir, td_len, dep_sha1)
# 分支字符内联(不返回字符串字面量:避免 TPV 对字面量返回的
# 栈临时指针悬垂 → printf %s 崩溃,图输出被吞)
br_1: str = "|-- "
br_2: str = "`-- "
if builtin != 0:
if is_last != 0:
stdio.printf("%s%s%s (builtin)\n", prefix, br_2, nm)
else:
stdio.printf("%s%s%s (builtin)\n", prefix, br_1, nm)
elif dep_sha1 is None:
if is_last != 0:
stdio.printf("%s%s%s (unresolved)\n", prefix, br_2, nm)
else:
stdio.printf("%s%s%s (unresolved)\n", prefix, br_1, nm)
elif txt_ok != 0:
if is_last != 0:
stdio.printf("%s%s%s [OK]\n", prefix, br_2, nm)
else:
stdio.printf("%s%s%s [OK]\n", prefix, br_1, nm)
else:
if is_last != 0:
stdio.printf("%s%s%s [MISS] text.ll=%s\n", prefix, br_2, nm, dep_sha1)
else:
stdio.printf("%s%s%s [MISS] text.ll=%s\n", prefix, br_1, nm, dep_sha1)
# 下钻:MISS 全链(至 PA_GRAPH_DEPTH_MAX),OK 仅探测 1 层(PA_GRAPH_OK_PROBE)
can_expand: int = 0
if dep_sha1 is not None and builtin == 0:
if txt_ok == 0:
if depth + 1 < PA_GRAPH_DEPTH_MAX:
can_expand = 1
else:
if depth + 1 < PA_GRAPH_OK_PROBE:
can_expand = 1
if can_expand != 0:
# +5:strcat 追加 "| "/" "(4 字符)+ NUL;strlen+4 会溢出 1 字节
# 破坏堆元数据 → 后续 malloc/free 崩溃,图打印中断且 stdout 缓冲丢失
npl: sizet = string.strlen(prefix) + 5
new_prefix: bytes = stdlib.malloc(npl)
if new_prefix is not None:
string.strcpy(new_prefix, prefix)
if is_last != 0:
string.strcat(new_prefix, " ")
else:
string.strcat(new_prefix, "| ")
_PhaseADumpNode(temp_dir, td_len, dep_sha1, depth + 1, new_prefix)
stdlib.free(new_prefix)
stdlib.free(nm)
idx += 1
stdlib.free(deps)
def _PhaseADumpFailGraph(temp_dir: str, td_len: sizet, path: str, sha1: str) -> None:
"""收敛失败文件诊断:pending 明细 + 依赖树(标出缺 text.ll 节点)
每步 printf 后立即 fflush:管道捕获下 stdout 全缓冲,若后续崩溃未刷出
则整段诊断丢失("ERROR 可见但依赖图无输出"即此类)。
"""
stdio.printf("[PhaseA-Fail] ==============================================================\n")
stdio.fflush(None)
stdio.printf("[PhaseA-Fail] FAIL TO TRANSLATE: %s [sha1=%s]\n", path, sha1)
stdio.fflush(None)
miss: bytes = _PhaseADiagMissLookup(sha1)
if miss is not None:
if miss[0] != 0:
stdio.printf("[PhaseA-Fail] pending reason: %s\n", miss)
stdlib.free(miss)
stdio.fflush(None)
stdio.printf("[PhaseA-Fail] dep graph ([MISS]=missing text.ll, [OK]=ready, depth<=%d):\n",
PA_GRAPH_DEPTH_MAX)
stdio.fflush(None)
root_prefix: bytes = stdlib.malloc(1)
if root_prefix is not None:
root_prefix[0] = 0
_PhaseADumpNode(temp_dir, td_len, sha1, 0, root_prefix)
stdlib.free(root_prefix)
stdio.fflush(None)
def RunMultiFileProject(mb: memhub.MemBuddy*,
source_dir: str, temp_dir: str, output_dir: str,
cc_cmd: str, cc_flags: str,
linker_cmd: str, linker_flags: str, linker_output: str,
includes_binary_dir: str,
includes_dir: str,
do_phase1: int, do_phase2: int,
log: rawptr[VLogger.Logger],
args: rawptr[argparse.ParsedArgs],
linker_static_libs: str = None,
linker_dynamic_libs: str = None) -> int:
"""多文件项目编译
Returns: 0 成功,非 0 失败
"""
if source_dir is None:
VLogger.error("source_dir 为空", "project")
return 1
VLogger.info(f"多文件项目编译: {source_dir}", "project")
# === 1. 递归扫描 source_dir 下的 .vp/.py 文件(包括子目录)===
entry_size: sizet = SrcFileEntry.__sizeof__()
entries: SrcFileEntry | t.CPtr = stdlib.malloc(MAX_SRC_FILES * entry_size)
if entries is None:
return 1
string.memset(entries, 0, MAX_SRC_FILES * entry_size)
file_count: int = _ScanDirForPyFiles(mb, source_dir, entries, entry_size, 0, MAX_SRC_FILES)
VLogger.info(f"共 {file_count} 个源文件", "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:
VLogger.info(f"汇编文件 {asm_file_count} 个(llvm-mc 汇编参与链接)", "project")
if file_count == 0:
stdlib.free(entries)
stdlib.free(asm_entries)
return 1
# 初始化 AST 表
ast._init_tables(mb)
# mark2 之前预注册 App 源文件的泛型类模板:模板表/节点是 pool 懒分配,必须在 mark2 之前注册才能跨 reset_to(mark2) 存活,
# 否则 Phase A-pre 首次注册后即被 reset 回收 → 后续查重/特化遍历悬空内存崩溃。
_PreRegisterAppGenericTemplates(mb, entries, file_count, entry_size)
# OOM 清理(对齐 Phase1 1b 的 mark1/reset_to 模式):无回收时 98 个 includes 的 AST/IR 会全量累积 pool → 1GB OOM。
# 此处打 mark2,各处翻译完成后 reset_to(mark2) 回收该文件瞬时数据(AST/tokens/lexer/IR/Handler/dump 临时);
# 跨文件持久表(struct/enum/docstring/CDefine/函数注册表等)已 stdlib 化或落在 mark2 之前,reset 不受影响。
mark2: sizet = mb.mark()
# mark2 之后新注册的 struct/enum 条目改 stdlib 分配(跨 reset 存活,对齐 Phase1)
GenApi.set_stdlib_alloc(1)
# 确保 temp/output 目录存在
BuildPipeline.ensure_dir(temp_dir)
BuildPipeline.ensure_dir(output_dir)
# === 0.5 依赖图按需填充 Sha1Store(消除 Phase1 依赖)===
# Phase2 不依赖 Phase1 的副作用:如果 Sha1Store 为空(Phase1 未运行或提前返回),
# 自行按依赖图扫描(App 优先 BFS)并填充 Sha1Store,确保后续"编译缺失 includes"
# 逻辑能正常工作。未引用的 includes 根本不扫描(对齐 Phase1 依赖图模型)。
if includes_dir is not None:
if StubMergerStore.GetSha1StoreCount() == 0:
rs_05: bytes = stdlib.malloc(StubMerger.MAX_INCLUDES_SHA1 * 17)
if rs_05 is None:
VLogger.error("reachable_set 分配失败,无法填充 Sha1Store", "project")
else:
string.memset(rs_05, 0, StubMerger.MAX_INCLUDES_SHA1 * 17)
rc_05: int = StubMergerBuild._BuildReachableSha1Set(
mb, source_dir, temp_dir, rs_05, None, includes_dir)
if rc_05 < 0:
VLogger.error("依赖图扫描失败,无法填充 Sha1Store", "project")
elif rc_05 > 0:
VLogger.info(f"Phase2 依赖图按需填充 Sha1Store: {rc_05} 个", "project")
stdlib.free(rs_05)
# 追加 App 源文件 SHA1 到 _sha1_map.txt(人类可读输出,程序内部不读取此文件)
# 格式: {sha1}:{rel_path}\n (保留目录结构,如 lib/core/VLogger.vp)
# 同步追加到内存存储器(AppendToSha1MapStore,供跨模块 CDefine 查找)
td_len_am: sizet = string.strlen(temp_dir)
src_dir_len_am: sizet = string.strlen(source_dir)
map_path_am: bytes = stdlib.malloc(td_len_am + 32)
if map_path_am is not None:
viperlib.snprintf(map_path_am, td_len_am + 32, "%s/_sha1_map.txt", temp_dir)
mf: rawptr[fileio.File] = fileio.File(map_path_am, fileio.MODE.A)
line_am: bytes = stdlib.malloc(512)
# 先遍历一次填充内存存储器(不依赖文件 I/O),再写入 _sha1_map.txt
app_filled: int = 0
for i in range(file_count):
ea_am: uint64 = uint64(entries) + i * entry_size
ent_am: SrcFileEntry | t.CPtr = (SrcFileEntry | t.CPtr)(ea_am)
if ent_am is None or ent_am.Path is None or ent_am.Sha1 is None:
continue
# 计算相对路径(去除 source_dir 前缀,保留目录结构)
p_str: str = ent_am.Path
p_len: sizet = string.strlen(p_str)
rel_path_am: str = p_str
if p_len > src_dir_len_am + 1:
if string.strncmp(p_str, source_dir, src_dir_len_am) == 0:
rel_path_am = p_str + src_dir_len_am + 1
# 同步追加到内存存储器(供跨模块 CDefine 查找)
# rel_path_am 格式如 "lib/core/StubMerger.vp",与 _sha1_map.txt 一致
if StubMergerStore.AppendToSha1MapStore(ent_am.Sha1, rel_path_am) != 0:
VLogger.error(f"AppendToSha1MapStore 失败: {rel_path_am}", "project")
app_filled += 1
# 写入 _sha1_map.txt(人类可读输出)
if not mf.closed and line_am is not None:
viperlib.snprintf(line_am, 512, "%s:%s\n", ent_am.Sha1, rel_path_am)
ll_am: sizet = string.strlen(line_am)
mf.write(line_am, ll_am)
if line_am is not None:
stdlib.free(line_am)
if not mf.closed:
mf.close()
VLogger.info(f"已追加 {app_filled} 个 App 文件到 _sha1_map.txt 和内存存储器", "project")
stdlib.free(map_path_am)
# 模块 SHA1 映射已由 PopulateSha1MapStore + AppendToSha1MapStore 统一填充到
# StubMerger 全局存储器,HandlesExprCall._lookup_module_sha1 直接查全局存储器,
# 不再需要构建独立的 SHA1 映射表副本。
# === Phase A-pre-vpi: 预生成所有 App 源文件的 .vpi stub ===
#
# 依赖模块的 .vpi 必须在依赖方翻译前可用:Phase A 主循环按扫描顺序(非拓扑)翻译,若依赖模块后翻译则 LoadDepVpiIntoModule 找不到 vpi
# → 跨模块全局变量 declare 未注入 → ExprGenAttr fallback 以 i8* 创建带前缀 global → llc "icmp slt 类型不匹配"。
# 此处先为所有 App 源文件预生成 vpi,保证 Phase A 翻译任意模块时其依赖 vpi 已存在(App 源文件自身 vpi 随后由 TranslateFileGetTrans 覆盖重写,内容一致)。
td_len_vpa: sizet = string.strlen(temp_dir)
PYI_BUF_SIZE_P2: sizet = 262144
vpi_failed_p2: int = 0
for i_vp2 in range(file_count):
ea_vp2: uint64 = uint64(entries) + i_vp2 * entry_size
ent_vp2: SrcFileEntry | t.CPtr = (SrcFileEntry | t.CPtr)(ea_vp2)
if ent_vp2 is None or ent_vp2.Path is None or ent_vp2.Sha1 is None:
vpi_failed_p2 += 1
VLogger.error("[Phase A-pre-vpi] 源文件条目无效(Path/Sha1 缺失),跳过", "PhaseA")
continue
# vpi 已存在则跳过(sha1 基于内容,同名 vpi 必然有效;跨次编译缓存)
vpi_path_vp2: str = StubMergerCollect._sliced_path(temp_dir, td_len_vpa, ent_vp2.Sha1, "vpi")
if vpi_path_vp2 is None:
vpi_failed_p2 += 1
VLogger.error(f"[Phase A-pre-vpi] {ent_vp2.Path} | vpi 路径生成失败", "PhaseA")
continue
vf_vp2: rawptr[fileio.File] = fileio.File(vpi_path_vp2, fileio.MODE.R)
if not vf_vp2.closed:
vf_vp2.close()
stdlib.free(vpi_path_vp2)
continue
stdlib.free(vpi_path_vp2)
# 读取源文件 → 解析 AST → 生成 vpi
# (rel_path 传 ent_vp2.Path:vpi 内容不依赖它——与 TranslateFileGetTrans
# 后续覆盖写的内容一致;此处仅用于 fatal_error 的报错归因,指向本源文件)
f_vp2: rawptr[fileio.File] = fileio.File(ent_vp2.Path, fileio.MODE.R)
if f_vp2.closed:
vpi_failed_p2 += 1
VLogger.error(f"[Phase A-pre-vpi] {ent_vp2.Path} | 无法打开源文件", "PhaseA")
continue
# read_all 的容量参数含终止符(最多读 cap-1 字节), 文件更长会被静默截断
if f_vp2.size() >= SRC_BUF_SIZE:
vpi_failed_p2 += 1
VLogger.error(f"[Phase A-pre-vpi] {ent_vp2.Path} | 源文件超过 {SRC_BUF_SIZE} 字节上限", "PhaseA")
f_vp2.close()
continue
src_buf_vp2: bytes = stdlib.malloc(SRC_BUF_SIZE)
if src_buf_vp2 is None:
f_vp2.close()
vpi_failed_p2 += 1
VLogger.error(f"[Phase A-pre-vpi] {ent_vp2.Path} | 源文件缓冲分配失败(OOM)", "PhaseA")
continue
bytes_read_vp2: LONG = f_vp2.read_all(src_buf_vp2, SRC_BUF_SIZE)
f_vp2.close()
if bytes_read_vp2 <= 0:
stdlib.free(src_buf_vp2)
vpi_failed_p2 += 1
VLogger.error(f"[Phase A-pre-vpi] {ent_vp2.Path} | 文件读取失败或为空", "PhaseA")
continue
if bytes_read_vp2 < SRC_BUF_SIZE:
src_buf_vp2[bytes_read_vp2] = 0
lx_vp2: ast.Lexer* = ast.new_lexer(mb)
if lx_vp2 is None:
stdlib.free(src_buf_vp2)
vpi_failed_p2 += 1
VLogger.error(f"[Phase A-pre-vpi] {ent_vp2.Path} | 词法器分配失败(OOM)", "PhaseA")
continue
ast._lexer_init(lx_vp2, src_buf_vp2, mb)
tokens_vp2: ast.Token* = ast.tokenize(lx_vp2)
tree_vp2: ast.AST* = ast.parse_tokens(mb, tokens_vp2)
if tree_vp2 is None:
stdlib.free(src_buf_vp2)
vpi_failed_p2 += 1
# 解析失败:**日志行内联 行/列 + 原因**(与下方 dump_parse_traceback 的完整
# Python 风格 traceback 同源:Traceback + File/行 + 源码行 + caret +
# SyntaxError: 消息;消息含 token 类型与前/后 token 附注,见
# includes/ast/parser.vp 的 _record_parse_err_ctx / parse_tokens 兜底补记)。
# 只打 "AST 解析失败" 时用户看不到任何定位与原因(traceback 未落到终端时更甚)。
VLogger.error(f"[Phase A-pre-vpi] {ent_vp2.Path} | AST 解析失败(第 {ast.get_parse_err_line()} 行第 {ast.get_parse_err_col()} 列: {ast.get_parse_err_msg()})", "PhaseA")
GenApi.dump_parse_traceback(ent_vp2.Path)
continue
pyi_buf_vp2: bytes = stdlib.malloc(PYI_BUF_SIZE_P2)
if pyi_buf_vp2 is not None:
pyi_pos_vp2: sizet = StubConverter._GeneratePyiFromAst(
mb, tree_vp2, ent_vp2.Path, pyi_buf_vp2, PYI_BUF_SIZE_P2)
if pyi_pos_vp2 > 0:
pyi_path_vp2: str = StubMergerCollect._sliced_path(temp_dir, td_len_vpa, ent_vp2.Sha1, "vpi")
if pyi_path_vp2 is not None:
pf_vp2: rawptr[fileio.File] = fileio.File(pyi_path_vp2, fileio.MODE.W)
if not pf_vp2.closed:
pf_vp2.write(pyi_buf_vp2, pyi_pos_vp2)
pf_vp2.close()
stdlib.free(pyi_path_vp2)
stdlib.free(pyi_buf_vp2)
else:
vpi_failed_p2 += 1
VLogger.error(f"[Phase A-pre-vpi] {ent_vp2.Path} | pyi 缓冲分配失败(OOM)", "PhaseA")
stdlib.free(src_buf_vp2)
# OOM 清理:回收本文件 AST(vpi 已写盘;mark2 后持久数据已 stdlib 化)
mb.reset_to(mark2)
if vpi_failed_p2 > 0:
VLogger.error(f"Phase A-pre-vpi 有 {vpi_failed_p2} 个文件 vpi 预生成失败(失败文件与原因见上方逐条 [Phase A-pre-vpi] 日志)", "PhaseA")
return 1
# === 2. Phase A: 为每个文件生成 stub + text ===
# app_deps_buf: 收集所有 App 源文件的直接依赖模块名(空格分隔),供 Phase B+ 的 _BuildReachableSha1Set 作为初始 worklist;
# 声明在 if 块外确保 Phase B+ 和清理代码能访问。容量 65536 与 StubMerger.REACH_WORKLIST_BUF 对齐:全量 App imports 拼接超 8192,
# 过小会静默丢依赖致 Phase B+ 可达集合不全。
APP_DEPS_BUF_SIZE: sizet = 65536
app_deps_buf: bytes = None
app_deps_len: sizet = 0
if do_phase1 != 0:
log.banner("Phase A: 生成 stub + text")
# Phase A 收敛失败依赖图诊断表初始化(stdlib,跨 mark2 reset 存活)
_PhaseADiagInit(sizet(file_count) + StubMerger.MAX_INCLUDES)
# === Phase A-pre-inc: includes 预注册 struct + 依赖填充 ===
# 必须在 App 源文件预注册之前执行:App 源文件(strict_mode=1)可能继承或引用 includes 中的 struct(如 ast.AST),
# includes struct 未注册会使 App 翻译失败 → .obj 缺失 → undefined reference。同时填充 PopulateIncludesDeps 供 Phase B+ 使用。
# 多遍扫描解决跨文件继承问题(如 Constant(AST) 在 AST 未注册时被跳过)。
if includes_dir is not None and includes_binary_dir is not None:
store_count_inc_pre: int = StubMergerStore.GetSha1StoreCount()
if store_count_inc_pre > 0:
store_sha1_inc_pre: bytes = StubMergerStore.GetSha1StoreArrPtr()
store_rel_inc_pre: bytes = StubMergerStore.GetSha1StoreRelArrPtr()
store_incdir_inc_pre: bytes = StubMergerStore.GetSha1StoreIncdirArrPtr()
deps_filled_pre: int = 0
PHASE_A_INC_MAX_PASSES: int = 3
# 记录预注册失败的 includes:多遍后仍失败 → 报错终止编译,不允许静默降级(静默失败会让 App 的跨模块类构造退化为普通函数调用,
# 返回类型默认推断成 i32,调用点报误导性的「找不到模块 SHA1」)。declare_only=1 时 import 的 SHA1 检查跳过,故预注册失败只可能是文件自身无法翻译。
# 必须跳过 App 文件:Sha1Store 里含 App 源文件 SHA1,但其预注册由 Phase A-pre 负责(App 是严格模式且此阶段依赖未就绪,在此预注册必然失败误报)。
inc_fail_flags: bytes = stdlib.malloc(store_count_inc_pre)
if inc_fail_flags is not None:
string.memset(inc_fail_flags, 1, store_count_inc_pre)
for pass_inc_i in range(PHASE_A_INC_MAX_PASSES):
struct_count_before_inc: int = GenApi.get_struct_count()
for si_inc in range(store_count_inc_pre):
inc_sha1_pre: str = store_sha1_inc_pre + sizet(si_inc) * 17
inc_rel_pre: str = store_rel_inc_pre + sizet(si_inc) * StubMerger.MAX_REL_PATH_LEN
if inc_rel_pre is None or inc_rel_pre[0] == '\0':
continue
# 跳过 App 源文件(main.vp 等):由 Phase A-pre 预注册,不算失败
is_app_file_inc: int = 0
for i_app_inc in range(file_count):
ea_app_inc: uint64 = uint64(entries) + i_app_inc * entry_size
ent_app_inc: SrcFileEntry | t.CPtr = (SrcFileEntry | t.CPtr)(ea_app_inc)
if ent_app_inc is not None and ent_app_inc.Sha1 is not None \
and string.strcmp(ent_app_inc.Sha1, inc_sha1_pre) == 0:
is_app_file_inc = 1
break
if is_app_file_inc != 0 and inc_fail_flags is not None:
inc_fail_flags[si_inc] = 0
continue
rel_len_pre: sizet = string.strlen(inc_rel_pre)
# 所属 includes 根目录(多目录逐条取自 IncDir 数组,空串回退 includes_dir)
incdir_pre: str = store_incdir_inc_pre + sizet(si_inc) * StubMerger.MAX_INC_DIR_LEN
if incdir_pre is None or incdir_pre[0] == '\0':
incdir_pre = includes_dir
incdir_len_pre: sizet = string.strlen(incdir_pre)
full_path_pre: bytes = stdlib.malloc(incdir_len_pre + rel_len_pre + 2)
if full_path_pre is None:
continue
viperlib.snprintf(full_path_pre, incdir_len_pre + rel_len_pre + 2,
"%s/%s", incdir_pre, inc_rel_pre)
pkg_inc_pre: str = GenApi.compute_package_from_relpath(mb, inc_rel_pre)
tr_inc_pre: rawptr[HandlesTranslator.Translator] = BuildPipeline.TranslateFileGetTrans(
mb, full_path_pre, inc_sha1_pre, pkg_inc_pre, 1)
stdlib.free(full_path_pre)
if tr_inc_pre is not None:
# 记录本文件依赖(供收敛失败依赖图诊断;stdlib 表跨 reset 存活)
_PhaseADiagRecord(inc_sha1_pre, tr_inc_pre._imported_modules, None)
# 预注册成功:清除失败标记
if inc_fail_flags is not None:
inc_fail_flags[si_inc] = 0
# 依赖填充只在第一遍执行(PopulateIncludesDeps 内部有去重)
# 注意:必须在 reset_to(mark2) 之前调用——内部深拷贝到 stdlib
if pass_inc_i == 0 and tr_inc_pre._imported_modules is not None \
and StubMergerStore.PopulateIncludesDeps(
inc_sha1_pre, tr_inc_pre._imported_modules) == 0:
deps_filled_pre = deps_filled_pre + 1
# OOM 清理:回收本 includes 文件预注册翻译的瞬时数据
# (AST/tokens/lexer/IR/Handler);struct 注册表条目 stdlib 分配不受影响
mb.reset_to(mark2)
struct_count_after_inc: int = GenApi.get_struct_count()
# 收敛检查:本遍没有新结构体注册 → 所有类已注册
if struct_count_after_inc == struct_count_before_inc:
break
# 多遍后仍预注册失败的 includes → 报错终止(不允许降级)
if inc_fail_flags is not None:
for si_inc_f in range(store_count_inc_pre):
if inc_fail_flags[si_inc_f] != 0:
rel_f: str = store_rel_inc_pre + sizet(si_inc_f) * StubMerger.MAX_REL_PATH_LEN
VLogger.error(f"includes 预注册失败: {rel_f}(文件无法翻译,可能是语法/解析/编译错误),终止编译", "prePhaseA")
stdlib.free(inc_fail_flags)
VLogger.info(f"includes 预注册 + 依赖填充: {deps_filled_pre}/{store_count_inc_pre}", "prePhaseA")
# === Phase A-pre: 预注册所有源文件的 struct/enum/union ===
# 解决循环引用问题(circ_a 翻译时需知道 circ_b.ClassB 的 struct 定义):仅注册 struct/enum/union(declare_only=1),不翻译方法体;
# 多遍扫描解决跨文件继承的字母序问题(如 AnnAssignHandle 继承 Mixin 时 Mixin 未注册),每遍注册新类后下一遍子类可继承。
VLogger.info("预注册 struct/enum/union...", "PhaseA")
src_dir_len_pre: sizet = string.strlen(source_dir)
PHASE_A_PRE_MAX_PASSES: int = 3
for pass_i in range(PHASE_A_PRE_MAX_PASSES):
struct_count_before: int = GenApi.get_struct_count()
for i in range(file_count):
ea_pre: uint64 = uint64(entries) + i * entry_size
ent_pre: SrcFileEntry | t.CPtr = (SrcFileEntry | t.CPtr)(ea_pre)
if ent_pre is None or ent_pre.Path is None:
continue
pkg_pre: str = None
if string.strlen(ent_pre.Path) > src_dir_len_pre + 1:
rel_path_pre: str = ent_pre.Path + src_dir_len_pre + 1
pkg_pre = GenApi.compute_package_from_relpath(mb, rel_path_pre)
# App 预注册传 pre_allow_dep_inject=1:声明期也做导入处理 + 依赖 vpi 注入,
# 使类字段注解在预注册期即与阶段 A 同上下文(否则包重导出类
# list[ast.ClassDef*] 的票名前缀会在预注册期被冻结成包前缀,与阶段 A
# 解析出的真定义前缀分裂 → 结构体布局不兼容,见 Translator 字段说明)。
tr_pre: rawptr[HandlesTranslator.Translator] = BuildPipeline.TranslateFileGetTrans(
mb, ent_pre.Path, ent_pre.Sha1, pkg_pre, 1, None, 1)
if tr_pre is None:
VLogger.error(f"预注册失败: {ent_pre.Path}", "PhaseA")
# OOM 清理:回收本 App 文件预注册翻译的瞬时数据
mb.reset_to(mark2)
struct_count_after: int = GenApi.get_struct_count()
# 收敛检查:本遍没有新结构体注册 → 所有类已注册
if struct_count_after == struct_count_before:
break
VLogger.info("预注册完成", "PhaseA")
# Phase A IR 缓冲:1MB 不够,ast/parser.vp 的 text.ll 超 1MB 被截断 →
# 合并 IR 尾部丢字(opt: "end of file in global variable name")。
PHASE_A_IR_SIZE: sizet = 8388608
td_len_pa: sizet = string.strlen(temp_dir)
# 设置 temp 目录路径,使 HandlesExprCall 能读取依赖模块的 text.ll
# 用于跨模块方法返回类型查找(避免指针返回值被截断为 i32)
GenApi.SetTempDir(temp_dir)
# app_deps_buf: 收集所有 App 源文件的直接依赖模块名(空格分隔)
# 供 Phase B+ 的 _BuildReachableSha1Set 作为初始 worklist 使用
# 声明已在 if 块外,此处仅赋值
app_deps_buf = stdlib.malloc(APP_DEPS_BUF_SIZE)
app_deps_len = 0
if app_deps_buf is not None:
app_deps_buf[0] = '\0'
# Phase A 多遍重试:同项目多源文件时,main.vp 可能先于其 import 的
# mbuddytest.vp 翻译(文件系统顺序);翻译失败(语法/解析错误)的文件
# 记录待下遍重试。每遍至少完成一个文件,最多 file_count 遍收敛。
done_flags: bytes = stdlib.malloc(file_count)
if done_flags is None:
VLogger.error("Phase A done_flags 分配失败", "PhaseA")
stdlib.free(entries)
return 1
string.memset(done_flags, 0, file_count)
PHASE_A_MAX_PASSES: int = 32
for pass_a in range(PHASE_A_MAX_PASSES):
completed_this_pass: int = 0
for i in range(file_count):
if done_flags[i] != 0:
continue
ea: uint64 = uint64(entries) + i * entry_size
ent: SrcFileEntry | t.CPtr = (SrcFileEntry | t.CPtr)(ea)
# 计时点 0:本文件处理开始(translate 段起点)
t_pa0: uint64 = w32.win32base.GetTickCount64()
if ent is None or ent.Path is None:
done_flags[i] = 1
completed_this_pass += 1
continue
# 计算源文件的包名(相对 source_dir 的目录部分)
src_dir_len_pa: sizet = 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 = GenApi.compute_package_from_relpath(mb, rel_path_pa)
tr_a: rawptr[HandlesTranslator.Translator] = BuildPipeline.TranslateFileGetTrans(mb, ent.Path, ent.Sha1, pkg_pa, 0, temp_dir)
# 计时点 1:translate 返回
t_pa1: uint64 = w32.win32base.GetTickCount64()
if tr_a is None:
# 翻译失败(语法/解析错误):记录待下遍重试,收敛检查统一报错
# OOM 清理:失败翻译也已产生 AST/tokens 瞬时数据,立即回收
mb.reset_to(mark2)
continue
# 记录本文件依赖(供收敛失败依赖图诊断;stdlib 表跨 reset 存活)
_PhaseADiagRecord(ent.Sha1, tr_a._imported_modules, None)
# 收集 _imported_modules 到 app_deps_buf(供 Phase B+ 按需编译)。
# 依赖在翻译早期已填充。
if tr_a._imported_modules is not None and app_deps_buf is not None:
# 逐模块去重拼接:多个 App 文件 import 同一模块(builtins/_list/_dict/stdlib/t/c 等公共依赖)会让 app_deps 无去重膨胀到接近 64KB,
# _BuildReachableSha1Set 的初始 worklist 即满 → BFS 展开必报「依赖 worklist 溢出」。
im_len_a: sizet = string.strlen(tr_a._imported_modules)
im_pos_a: sizet = 0
while im_pos_a < im_len_a:
im_start_a: sizet = im_pos_a
while im_pos_a < im_len_a and tr_a._imported_modules[im_pos_a] != ' ':
im_pos_a += 1
im_mod_len: sizet = im_pos_a - im_start_a
if im_pos_a < im_len_a:
im_pos_a += 1
if im_mod_len == 0:
continue
im_mod_buf: bytes = stdlib.malloc(im_mod_len + 1)
if im_mod_buf is None:
continue
string.strncpy(im_mod_buf, tr_a._imported_modules + im_start_a, im_mod_len)
im_mod_buf[im_mod_len] = '\0'
# 已在 app_deps 中则跳过(词边界精确匹配)
if app_deps_len > 0 and GenApi.is_module_imported(app_deps_buf, im_mod_buf) != 0:
stdlib.free(im_mod_buf)
continue
if app_deps_len + im_mod_len + 2 < 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, im_mod_buf)
app_deps_len = app_deps_len + im_mod_len
app_deps_buf[app_deps_len] = '\0'
stdlib.free(im_mod_buf)
# dump stub IR (declarations only)
stub_buf_a: bytes = stdlib.malloc(PHASE_A_IR_SIZE)
if stub_buf_a is None:
VLogger.error("stub 缓冲区分配失败", "PhaseA")
tr_a.dump_ir(stub_buf_a, PHASE_A_IR_SIZE, llvmlite.OUTPUT_STUB)
stub_len_a: sizet = string.strlen(stub_buf_a)
# save stub.ll (切片路径: temp_dir/{sha1前缀}/{sha1}.stub.ll)
stub_path_a: str = StubMergerCollect._sliced_path(temp_dir, td_len_pa, ent.Sha1, "stub.ll")
if stub_path_a is not None:
sf_a: rawptr[fileio.File] = fileio.File(stub_path_a, fileio.MODE.W)
if not sf_a.closed:
sf_a.write(stub_buf_a, stub_len_a)
sf_a.close()
stdlib.free(stub_path_a)
stdlib.free(stub_buf_a)
# dump text IR (definitions only)
text_buf_a: bytes = stdlib.malloc(PHASE_A_IR_SIZE)
if text_buf_a is None:
VLogger.error("text 缓冲区分配失败", "PhaseA")
tr_a.dump_ir(text_buf_a, PHASE_A_IR_SIZE, llvmlite.OUTPUT_TEXT)
text_len_a: sizet = string.strlen(text_buf_a)
# save text.ll (切片路径)
text_path_a: str = StubMergerCollect._sliced_path(temp_dir, td_len_pa, ent.Sha1, "text.ll")
if text_path_a is not None:
tf_a: rawptr[fileio.File] = fileio.File(text_path_a, fileio.MODE.W)
if not tf_a.closed:
tf_a.write(text_buf_a, text_len_a)
tf_a.close()
stdlib.free(text_path_a)
stdlib.free(text_buf_a)
# save dependencies (_imported_modules) for Phase B (切片路径)
deps_path_a: str = StubMergerCollect._sliced_path(temp_dir, td_len_pa, ent.Sha1, "deps.txt")
if deps_path_a is not None:
df_a: rawptr[fileio.File] = fileio.File(deps_path_a, fileio.MODE.W)
if not df_a.closed:
if tr_a._imported_modules is not None:
dl_a: sizet = string.strlen(tr_a._imported_modules)
df_a.write(tr_a._imported_modules, dl_a)
df_a.close()
stdlib.free(deps_path_a)
# 计时点 2:stub+text dump 及写盘完成;输出逐文件进度(对齐 Phase1 1b)
t_pa2: uint64 = w32.win32base.GetTickCount64()
VLogger.info(f"[{i+1}/{file_count}] PhaseA: {ent.Path} (translate={t_pa1-t_pa0} ms, dump={t_pa2-t_pa1} ms, total={t_pa2-t_pa0} ms)", "PhaseA")
done_flags[i] = 1
completed_this_pass += 1
# OOM 清理:stub/text/deps 已写盘,回收本文件翻译瞬时数据
# (AST/tokens/lexer/IR/Handler);app_deps_buf 已深拷贝依赖,不受影响
mb.reset_to(mark2)
# 收敛检查:所有文件完成则结束
all_done: int = 1
for i_ck in range(file_count):
if done_flags[i_ck] == 0:
all_done = 0
break
if all_done != 0:
break
if completed_this_pass == 0:
# 无进展:剩余文件无法完成(真实编译错误),终止并列出未完成文件。每个失败文件附带依赖图诊断(_PhaseADumpFailGraph):pending 明细 + 依赖树。
# VLogger.error 内部 sys.exit(1),必须放在所有图形输出之后(否则进程在 ERROR 处即退出,图永远打不出)。
td_len_fail: sizet = string.strlen(temp_dir)
for i_err in range(file_count):
if done_flags[i_err] == 0:
ea_err: uint64 = uint64(entries) + i_err * entry_size
ent_err: SrcFileEntry | t.CPtr = (SrcFileEntry | t.CPtr)(ea_err)
if ent_err is not None and ent_err.Path is not None:
if ent_err.Sha1 is not None:
_PhaseADumpFailGraph(temp_dir, td_len_fail, ent_err.Path, ent_err.Sha1)
stdlib.free(done_flags)
_PhaseADiagFree()
VLogger.error("Phase A 多遍收敛失败,终止编译", "PhaseA")
stdlib.free(entries)
return 1
stdlib.free(done_flags)
# Phase A 成功收敛:释放依赖图诊断表(仅失败路径需要,成功后无用途)
_PhaseADiagFree()
if do_phase2 == 0:
VLogger.info("Phase A 完成(仅 stub 生成)", "project")
if app_deps_buf is not None:
stdlib.free(app_deps_buf)
stdlib.free(entries)
return 0
# === 3. Phase B: 编译每个文件为 .obj ===
if do_phase2 != 0:
log.banner("Phase B: 编译 .obj")
# 收集 .obj 路径(增大到 64KB,避免 includes .obj 路径溢出导致链接丢失符号)
# reachable_set/reachable_count: Phase B+ 填充,Phase C 用于按需链接
reachable_set: bytes = None
reachable_count: int = 0
OBJ_PATHS_SIZE: sizet = 65536
obj_paths: bytes = stdlib.malloc(OBJ_PATHS_SIZE)
if obj_paths is None:
return 1
obj_paths[0] = '\0'
obj_pos: sizet = 0
# main_obj_path: 存放定义了用户 main 的 .obj 路径(链接时放在最前面,
# 避免 --allow-multiple-definition 选择了其他模块的 wrapper main)
main_obj_path: bytes = stdlib.malloc(512)
if main_obj_path is None:
return 1
main_obj_path[0] = '\0'
compiled_count: int = 0
# 组合 IR 缓冲区大小(4MB,足够容纳本地 stub + 所有依赖 stub + 本地 text)
COMBINED_IR_SIZE: sizet = 4194304
# llc 任务表(stdlib.malloc,跨线程存活):连续 LlcTask 记录数组,
# 循环内只写 .ll + 构造任务,循环后 run_llc_parallel 分批并行跑 llc
llc_tasks: bytes = stdlib.malloc(sizet(file_count) * BuildPipeline.LlcTask.__sizeof__())
if llc_tasks is None:
VLogger.error("llc 任务表分配失败", "PhaseB")
return 1
llc_task_count: int = 0
for i in range(file_count):
ea: uint64 = uint64(entries) + i * entry_size
ent: SrcFileEntry | t.CPtr = (SrcFileEntry | t.CPtr)(ea)
if ent is None or ent.Path is None or ent.Sha1 is None:
continue
# 单文件三步计时起点(GetTickCount64,ms)
t_pb0: uint64 = w32.win32base.GetTickCount64()
# 组合本地 stub + 所有依赖 stub + 本地 text → 完整 IR
combined_ir: bytes = stdlib.malloc(COMBINED_IR_SIZE)
if combined_ir is None:
VLogger.error(f"combined_ir 分配失败: {ent.Path}", "PhaseB")
continue
combined_len: sizet = StubMergerBuild.BuildCombinedIR(temp_dir, ent.Sha1, combined_ir, COMBINED_IR_SIZE)
if combined_len == 0:
VLogger.error(f"BuildCombinedIR 失败: {ent.Path}", "PhaseB")
stdlib.free(combined_ir)
continue
# 计时点 1:BuildCombinedIR 完成
t_pb1: uint64 = w32.win32base.GetTickCount64()
# 写 .ll(llc 线程只读此文件);llc 本身改为循环后批量并行
ret_w: int = BuildPipeline.write_ir_to_file(combined_ir, combined_len, temp_dir, ent.Sha1)
stdlib.free(combined_ir)
if ret_w != 0:
VLogger.error(f"写 .ll 失败: {ent.Path}", "PhaseB")
sys.exit(1)
# 计时点 2:写 .ll 完成
t_pb2: uint64 = w32.win32base.GetTickCount64()
# 构造 llc 任务(命令串 stdlib.malloc,线程运行期不被 reset 回收)
llc_ir_path: str = BuildPipeline.build_sliced_path(temp_dir, ent.Sha1, "ll")
if llc_ir_path is None:
VLogger.error("llc 输入路径构造失败", "PhaseB")
sys.exit(1)
tslot: rawptr[BuildPipeline.LlcTask] = rawptr[BuildPipeline.LlcTask](uint64(llc_tasks) + sizet(llc_task_count) * BuildPipeline.LlcTask.__sizeof__())
if BuildPipeline.init_llc_task(tslot, llc_ir_path, output_dir, ent.Sha1,
cc_cmd, cc_flags) != 0:
VLogger.error(f"llc 任务构造失败: {ent.Path}", "PhaseB")
sys.exit(1)
# 计时点 3:llc 任务构造完成
t_pb3: uint64 = w32.win32base.GetTickCount64()
llc_task_count += 1
compiled_count += 1
# 显示已提交 llc 的文件(含三步计时:合并 stub / 写 .ll / 构造任务)
VLogger.info(f"[{i+1}/{file_count}] llc 已提交: {ent.Path} (merge={t_pb1-t_pb0} ms, write={t_pb2-t_pb1} ms, task={t_pb3-t_pb2} ms, total={t_pb3-t_pb0} ms)", "PhaseB")
# 构造 .obj 路径,检测是否是 main 模块(test_main.vp 或 main.vp,不再识别 .py)
od_len: sizet = string.strlen(output_dir)
is_main_mod: int = 0
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:
is_main_mod = 1
# 切片路径: output_dir/{sha1前缀}/{sha1}.obj
obj_path_sliced: str = StubMergerCollect._sliced_path(output_dir, od_len, ent.Sha1, "obj")
if obj_path_sliced is not None:
op_sliced_len: sizet = string.strlen(obj_path_sliced)
if is_main_mod != 0:
# main 模块: 复制到 main_obj_path(确保链接时 main 在最前)
if op_sliced_len < 512:
string.strcpy(main_obj_path, obj_path_sliced)
else:
VLogger.error("main_obj_path 缓冲区不足,终止编译", "PhaseB")
else:
# 其他模块: 追加到 obj_paths
if obj_pos + op_sliced_len + 2 < OBJ_PATHS_SIZE:
if obj_pos > 0:
obj_paths[obj_pos] = ' '
obj_pos += 1
string.strcpy(obj_paths + obj_pos, obj_path_sliced)
obj_pos += op_sliced_len
obj_paths[obj_pos] = '\0'
else:
VLogger.error(".obj 路径缓冲区不足", "PhaseB")
stdlib.free(obj_path_sliced)
# 并行执行全部 llc(分批 LLC_MAX_THREADS 线程)
ret_p: int = BuildPipeline.run_llc_parallel(llc_tasks, llc_task_count,
BuildPipeline.LLC_MAX_THREADS)
if ret_p != 0:
VLogger.error("llc 编译失败,终止编译", "PhaseB")
sys.exit(1)
# 释放 llc 任务(命令串 + 记录)
ti: int = 0
while ti < llc_task_count:
tfree: rawptr[BuildPipeline.LlcTask] = rawptr[BuildPipeline.LlcTask](uint64(llc_tasks) + sizet(ti) * BuildPipeline.LlcTask.__sizeof__())
BuildPipeline.free_llc_task(tfree)
ti += 1
stdlib.free(llc_tasks)
# 汇编 .s 文件:llvm-mc → .obj(isr.s 等,符号为裸名,与 .vp 的
# CExport/State stub 声明匹配)。obj 加入 obj_paths 参与 Phase C 链接。
od_len_as: sizet = string.strlen(output_dir)
asm_compiled: int = 0
for ai_as in range(asm_file_count):
ea_as: uint64 = uint64(asm_entries) + ai_as * entry_size
ent_as: SrcFileEntry | t.CPtr = (SrcFileEntry | t.CPtr)(ea_as)
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:
VLogger.error(f"llvm-mc 汇编失败,终止编译: {ent_as.Path}", "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: sizet = 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
VLogger.info(f"[asm {asm_compiled}] 汇编完成: {ent_as.Path}", "PhaseB")
VLogger.success(f"编译完成: {compiled_count}/{file_count}", "PhaseB")
if compiled_count == 0:
VLogger.error("无成功编译的文件", "PhaseB")
stdlib.free(entries)
stdlib.free(asm_entries)
return 1
# === 3.5 编译缺失的 includes 文件(按依赖图按需编译)===
# 只编译被 App 源文件直接/间接引用的 includes 文件(可达集合),
# 而非遍历整个 includes 目录。通过 _BuildReachableSha1Set 构建依赖图。
if includes_dir is not None and includes_binary_dir is not None:
inc_compiled: int = 0
td_len_mi: sizet = string.strlen(temp_dir)
# 诊断日志:确认 Phase B+ 入口和 Sha1Store 状态
VLogger.info(f"PhaseB+ 入口: store_count={StubMergerStore.GetSha1StoreCount()} includes_binary_dir={includes_binary_dir}", "PhaseB+")
# 创建 includes_binary_dir 目录(确保 .obj 能写入和 collect_obj_files 能扫描)
BuildPipeline.ensure_dir(includes_binary_dir)
# declare_only 预处理已移至 Phase A 之前(Phase A-pre-inc),
# 确保 Phase A 翻译 App 源文件时 includes struct 已注册。
# PopulateIncludesDeps 已在 Phase A-pre-inc 中填充。
# 构建可达 SHA1 集合(依赖图遍历)
# app_deps_buf 为 None 时回退到扫描 source_dir(非递归)
REACHABLE_SET_SIZE: sizet = sizet(StubMerger.MAX_INCLUDES_SHA1) * 17
reachable_set: bytes = stdlib.malloc(REACHABLE_SET_SIZE)
reachable_count: int = 0
if reachable_set is not None:
string.memset(reachable_set, 0, REACHABLE_SET_SIZE)
reachable_count = StubMergerBuild._BuildReachableSha1Set(
mb, source_dir, temp_dir, reachable_set, app_deps_buf, includes_dir)
VLogger.info(f"可达 includes SHA1: {reachable_count} 个", "PhaseB+")
# fast-fail: 可达集合构建失败(依赖未找到),永不回退,直接终止
if reachable_count < 0:
VLogger.error("可达集合构建失败(fast-fail),终止编译", "PhaseB+")
return 1
else:
VLogger.error("reachable_set 分配失败,终止编译", "PhaseB+")
return 1
# 释放 app_deps_buf(已构建完 reachable_set,不再需要)
if app_deps_buf is not None:
stdlib.free(app_deps_buf)
app_deps_buf = None
# 从全局存储器获取 SHA1/模块名 数组:复制局部副本(bytes),避免 store 联合返回值(bytes|t.CPtr)跨函数传参/索引的地址异常。
# rel/incdir 仅本函数内按索引读取(不跨函数传参),保留 store 指针。
td_len_pb: sizet = string.strlen(temp_dir)
store_sha1_arr_mi: bytes = stdlib.malloc(StubMerger.MAX_INCLUDES * 17)
store_mod_arr_mi: bytes = stdlib.malloc(StubMerger.MAX_INCLUDES * 64)
store_count_mi: int = 0
if store_sha1_arr_mi is not None and store_mod_arr_mi is not None:
store_count_mi = StubMergerIR._BuildIncludesSha1Map(
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()
if store_count_mi > 0:
# 拓扑排序可达 includes:被依赖模块先完整翻译。跨模块 vtable 生成
# 需要依赖模块的函数 TypeStr 已注册到全局表(如 astaux 继承 base 的 AST)
topo_idx: sizet* = (sizet*)(stdlib.malloc(8 * (store_count_mi + 1)))
topo_count: int = 0
if topo_idx is None:
VLogger.error("拓扑排序索引分配失败,终止编译", "PhaseB+")
topo_count = 0
else:
topo_count = StubMergerTopo.BuildTopoSortedIndices(
store_count_mi, store_sha1_arr_mi, store_mod_arr_mi,
reachable_set, reachable_count, topo_idx)
for si_mi in range(topo_count):
# 当前条目(拓扑序索引,仅含可达模块)
ti: int = topo_idx[si_mi]
# 获取当前条目的 SHA1 和 rel_path
inc_sha1_mi: str = store_sha1_arr_mi + sizet(ti) * 17
inc_rel_mi: str = store_rel_arr_mi + sizet(ti) * StubMerger.MAX_REL_PATH_LEN
# 跳过 App 源文件:App 文件可能同时被 includes 扫描且在可达集合中,若 Phase B+ 再编译一份 .obj 到 includes_binary,
# 会与 Phase A/B 编译到 output/ 的 .obj 同 sha1 同符号 → 链接 duplicate symbol。
app_skip_mi: int = 0
for ai_mi in range(file_count):
ea_ap: uint64 = uint64(entries) + ai_mi * entry_size
ent_ap: SrcFileEntry | t.CPtr = (SrcFileEntry | t.CPtr)(ea_ap)
if ent_ap is not None and ent_ap.Sha1 is not None:
if string.strcmp(ent_ap.Sha1, inc_sha1_mi) == 0:
app_skip_mi = 1
break
if app_skip_mi != 0:
continue
# 检查 .obj 是否已存在于 includes.binary(切片子目录)
ibd_len_mi: sizet = string.strlen(includes_binary_dir)
check_pat_mi: str = StubMergerCollect._sliced_path(includes_binary_dir, ibd_len_mi, inc_sha1_mi, "obj")
check_fd_mi: w32.win32file.WIN32_FIND_DATAA | t.CPtr = stdlib.malloc(w32.win32file.WIN32_FIND_DATAA.__sizeof__())
obj_exists_mi: int = 0
if check_pat_mi is not None and check_fd_mi is not None:
string.memset(check_fd_mi, 0, w32.win32file.WIN32_FIND_DATAA.__sizeof__())
check_h_mi: w32.win32base.HANDLE = w32.win32file.FindFirstFileA(check_pat_mi, check_fd_mi)
if check_h_mi != w32.win32base.INVALID_HANDLE_VALUE:
w32.win32file.FindClose(check_h_mi)
obj_exists_mi = 1
if obj_exists_mi != 0:
if check_pat_mi is not None:
stdlib.free(check_pat_mi)
stdlib.free(check_fd_mi)
continue
# .obj 不存在,需要编译
# 构造源文件路径: {所属 includes 根目录}/{rel_path}(多目录逐条取 IncDir,空串回退 includes_dir)
rel_path_len_mi: sizet = string.strlen(inc_rel_mi)
incdir_mi: str = store_incdir_arr_mi + sizet(ti) * StubMerger.MAX_INC_DIR_LEN
if incdir_mi is None or incdir_mi[0] == '\0':
incdir_mi = includes_dir
inc_dir_len_mi: sizet = string.strlen(incdir_mi)
src_fp_mi: bytes = stdlib.malloc(inc_dir_len_mi + 1 + rel_path_len_mi + 1)
if src_fp_mi is None:
stdlib.free(check_pat_mi)
if check_fd_mi is not None:
stdlib.free(check_fd_mi)
VLogger.error("includes 源路径分配失败,终止编译", "PhaseB+")
viperlib.snprintf(src_fp_mi, inc_dir_len_mi + 1 + rel_path_len_mi + 1, "%s/%s", incdir_mi, inc_rel_mi)
# 检查是否为声明文件(只有 declare 没有实质 define)
# 判断方法: text.ll 中若有混淆函数 define(含 @")则为实现文件
is_decl_mi: int = -1
tpath_mi: str = StubMergerCollect._sliced_path(temp_dir, td_len_mi, inc_sha1_mi, "text.ll")
if tpath_mi is not None:
tf_mi: rawptr[fileio.File] = fileio.File(tpath_mi, fileio.MODE.R)
if not tf_mi.closed:
is_decl_mi = 1
tbuf_mi: bytes = stdlib.malloc(StubMerger.STUB_READ_BUF_SIZE)
tbr_mi: int64 = 0
if tbuf_mi is not None:
tbr_mi = tf_mi.read_all(tbuf_mi, StubMerger.STUB_READ_BUF_SIZE)
if tbuf_mi is not None and tbr_mi > 0:
if tbr_mi < StubMerger.STUB_READ_BUF_SIZE:
tbuf_mi[tbr_mi] = '\0'
# 逐行扫描: 找 define 行即为实现文件
# (不再要求行中含 @",因为 memhub 等模块的函数名
# 如 @ca40915f11b8b6ad._align_up 不带引号但仍是实现文件)
tpos_mi: sizet = 0
while tpos_mi < tbr_mi:
tls_mi: sizet = tpos_mi
while tpos_mi < tbr_mi:
if tbuf_mi[tpos_mi] == '\n':
break
tpos_mi += 1
tll_mi: sizet = tpos_mi - tls_mi
if tpos_mi < tbr_mi:
tpos_mi += 1
if tll_mi >= 7 and string.strncmp(tbuf_mi + tls_mi, "define ", 7) == 0:
is_decl_mi = 0
break
stdlib.free(tbuf_mi)
tf_mi.close()
stdlib.free(tpath_mi)
# is_decl_mi == -1: text.ll 不存在(Phase1 未翻译此文件)
# is_decl_mi == 1: 声明文件(仅 declare 无 define)
# is_decl_mi == 0: 实现文件(有 define)
# 只跳过声明文件(is_decl_mi == 1);text.ll 不存在时继续,让后续翻译逻辑处理
if is_decl_mi == 1:
stdlib.free(check_pat_mi)
if check_fd_mi is not None:
stdlib.free(check_fd_mi)
stdlib.free(src_fp_mi)
continue
# 尝试 BuildCombinedIR(stub/text 应已由 Phase1 生成)
inc_combined_mi: bytes = stdlib.malloc(COMBINED_IR_SIZE)
inc_combined_len_mi: sizet = 0
if inc_combined_mi is not None:
inc_combined_len_mi = StubMergerBuild.BuildCombinedIR(temp_dir, inc_sha1_mi, inc_combined_mi, COMBINED_IR_SIZE)
# 如果 stub/text 不存在,翻译源文件并保存 stub + text,然后重试
if inc_combined_len_mi == 0 and inc_combined_mi is not None:
# 计算 includes 文件的包名(相对 includes_dir 的目录部分)
inc_pkg_mi: str = None
if string.strlen(src_fp_mi) > inc_dir_len_mi + 1:
inc_rel_mi2: str = src_fp_mi + inc_dir_len_mi + 1
inc_pkg_mi = GenApi.compute_package_from_relpath(mb, inc_rel_mi2)
tr_mi: rawptr[HandlesTranslator.Translator] = BuildPipeline.TranslateFileGetTrans(mb, src_fp_mi, inc_sha1_mi, inc_pkg_mi)
if tr_mi is not None:
# PBP IR 缓冲:同 PHASE_A_IR_SIZE(parser.vp text.ll >1MB 截断根因)
PBP_IR_SIZE: sizet = 8388608
# 保存 stub.ll (切片路径)
inc_stub_buf: bytes = stdlib.malloc(PBP_IR_SIZE)
if inc_stub_buf is not None:
tr_mi.dump_ir(inc_stub_buf, PBP_IR_SIZE, llvmlite.OUTPUT_STUB)
inc_stub_len: sizet = string.strlen(inc_stub_buf)
inc_stub_path: str = StubMergerCollect._sliced_path(temp_dir, td_len_mi, inc_sha1_mi, "stub.ll")
if inc_stub_path is not None:
isf: rawptr[fileio.File] = fileio.File(inc_stub_path, fileio.MODE.W)
if not isf.closed:
isf.write(inc_stub_buf, inc_stub_len)
isf.close()
stdlib.free(inc_stub_path)
stdlib.free(inc_stub_buf)
# 保存 text.ll (切片路径)
inc_text_buf: bytes = stdlib.malloc(PBP_IR_SIZE)
if inc_text_buf is not None:
tr_mi.dump_ir(inc_text_buf, PBP_IR_SIZE, llvmlite.OUTPUT_TEXT)
inc_text_len: sizet = string.strlen(inc_text_buf)
inc_text_path: str = StubMergerCollect._sliced_path(temp_dir, td_len_mi, inc_sha1_mi, "text.ll")
if inc_text_path is not None:
itf: rawptr[fileio.File] = fileio.File(inc_text_path, fileio.MODE.W)
if not itf.closed:
itf.write(inc_text_buf, inc_text_len)
itf.close()
stdlib.free(inc_text_path)
stdlib.free(inc_text_buf)
# 重试 BuildCombinedIR
inc_combined_len_mi = StubMergerBuild.BuildCombinedIR(temp_dir, inc_sha1_mi, inc_combined_mi, COMBINED_IR_SIZE)
# OOM 清理:stub/text 已写盘,回收本 includes 文件翻译瞬时数据
# (AST/tokens/lexer/IR/Handler);BuildCombinedIR 只读文件,不受影响
mb.reset_to(mark2)
if inc_combined_len_mi == 0:
# 诊断:检查 stub.ll/text.ll 是否存在
diag_stub: str = StubMergerCollect._sliced_path(temp_dir, td_len_mi, inc_sha1_mi, "stub.ll")
diag_text: str = StubMergerCollect._sliced_path(temp_dir, td_len_mi, inc_sha1_mi, "text.ll")
diag_stub_ex: int = 0
diag_text_ex: int = 0
if diag_stub is not None:
df_diag_s: rawptr[fileio.File] = fileio.File(diag_stub, fileio.MODE.R)
if not df_diag_s.closed:
diag_stub_ex = 1
df_diag_s.close()
stdlib.free(diag_stub)
if diag_text is not None:
df_diag_t: rawptr[fileio.File] = fileio.File(diag_text, fileio.MODE.R)
if not df_diag_t.closed:
diag_text_ex = 1
df_diag_t.close()
stdlib.free(diag_text)
VLogger.error(f"BuildCombinedIR 失败: {src_fp_mi} (stub={diag_stub_ex} text={diag_text_ex} is_decl={is_decl_mi})", "PhaseB+")
if inc_combined_mi is not None:
stdlib.free(inc_combined_mi)
stdlib.free(check_pat_mi)
if check_fd_mi is not None:
stdlib.free(check_fd_mi)
stdlib.free(src_fp_mi)
continue
# 编译为 .obj(输出到 includes_binary_dir,与存在性检查和链接收集一致)
inc_cret_mi: int = BuildPipeline.compile_module_to_obj(
inc_combined_mi, inc_combined_len_mi, temp_dir, includes_binary_dir, inc_sha1_mi,
cc_cmd, cc_flags)
stdlib.free(inc_combined_mi)
if inc_cret_mi != 0:
VLogger.error(f"llc 编译失败,终止编译: {src_fp_mi}", "PhaseB+")
sys.exit(1)
inc_compiled += 1
# 显示编译完成的 includes 文件
VLogger.info(f"[includes {inc_compiled}] 编译完成: {src_fp_mi}", "PhaseB+")
# 添加到 obj_paths (切片路径,从 includes_binary_dir 取)
ibd_len_mi2: sizet = string.strlen(includes_binary_dir)
inc_obj_sliced: str = StubMergerCollect._sliced_path(includes_binary_dir, ibd_len_mi2, inc_sha1_mi, "obj")
if inc_obj_sliced is not None:
inc_obj_len: sizet = string.strlen(inc_obj_sliced)
if obj_pos + inc_obj_len + 2 < OBJ_PATHS_SIZE:
if obj_pos > 0:
obj_paths[obj_pos] = ' '
obj_pos += 1
string.strcpy(obj_paths + obj_pos, inc_obj_sliced)
obj_pos += inc_obj_len
obj_paths[obj_pos] = '\0'
else:
VLogger.error("includes .obj 路径缓冲区溢出,终止编译", "PhaseB+")
stdlib.free(inc_obj_sliced)
# 释放本次迭代的临时内存
if check_pat_mi is not None:
stdlib.free(check_pat_mi)
if check_fd_mi is not None:
stdlib.free(check_fd_mi)
stdlib.free(src_fp_mi)
# 释放拓扑排序索引与局部映射副本
if topo_idx is not None:
stdlib.free(topo_idx)
if store_sha1_arr_mi is not None:
stdlib.free(store_sha1_arr_mi)
if store_mod_arr_mi is not None:
stdlib.free(store_mod_arr_mi)
VLogger.info(f"编译缺失 includes: {inc_compiled} 个", "PhaseB+")
# 释放 app_deps_buf(Phase B+ 块未执行时此处兜底释放)
if app_deps_buf is not None:
stdlib.free(app_deps_buf)
app_deps_buf = None
# === 4. Phase C: 链接所有 .obj → .exe ===
log.banner("Phase C: 链接")
od_len2: sizet = string.strlen(output_dir)
lo_len: sizet = string.strlen(linker_output)
exe_path: bytes = stdlib.malloc(od_len2 + lo_len + 2)
if exe_path is not None:
viperlib.snprintf(exe_path, od_len2 + lo_len + 2, "%s/%s", output_dir, linker_output)
else:
exe_path = linker_output
VLogger.error("exe_path 分配失败,终止编译", "PhaseC")
# 构造最终 .obj 路径列表:main_obj_path 在前,reachable .obj 在后
final_obj_paths: bytes = stdlib.malloc(OBJ_PATHS_SIZE + 512)
if final_obj_paths is None:
stdlib.free(entries)
return 1
final_obj_paths[0] = '\0'
fop_pos: sizet = 0
# main_obj_path 放在最前面(确保 --allow-multiple-definition 选择用户 main)
if main_obj_path[0] != '\0':
mlen: sizet = string.strlen(main_obj_path)
string.strcpy(final_obj_paths, main_obj_path)
fop_pos = mlen
# 合并 Phase B/B+ 编译的 App 非 main 模块 .obj(obj_paths,空格分隔)。
# final_obj_paths 需含 main_obj + reachable includes + App 依赖文件的 .obj,否则 App import 的模块不入链 → undefined symbol。
# obj_paths 同时含 Phase B+ 本次新编译的 includes obj(与下方 reachable 收集重复,--allow-multiple-definition 容忍,仅本次编译的少量文件)。
if obj_pos > 0:
if fop_pos + obj_pos + 1 < OBJ_PATHS_SIZE + 512:
if fop_pos > 0:
final_obj_paths[fop_pos] = ' '
fop_pos += 1
string.strcpy(final_obj_paths + fop_pos, obj_paths)
fop_pos += obj_pos
final_obj_paths[fop_pos] = '\0'
# 按图链接:遍历 reachable_set,只收集可达 includes 的 .obj
# 不再全量收集 includes.binary 下的所有 .obj
if reachable_set is not None and reachable_count > 0:
ibd_len_pc: sizet = string.strlen(includes_binary_dir)
td_len_pc: sizet = string.strlen(temp_dir)
ri_pc: int
for ri_pc in range(reachable_count):
rsha1_pc: str = reachable_set + sizet(ri_pc) * 17
if rsha1_pc[0] == '\0':
continue
# 跳过 App 源文件:其 .obj 已入 final_obj_paths,若 includes_binary 残留同 sha1 的 .obj 被收集进来会与 output obj 同符号 → 链接 duplicate symbol。
app_skip_pc: int = 0
for ai_pc in range(file_count):
ea_pc: uint64 = uint64(entries) + ai_pc * entry_size
ent_pc: SrcFileEntry | t.CPtr = (SrcFileEntry | t.CPtr)(ea_pc)
if ent_pc is not None and ent_pc.Sha1 is not None:
if string.strcmp(ent_pc.Sha1, rsha1_pc) == 0:
app_skip_pc = 1
break
if app_skip_pc != 0:
continue
obj_path_pc: str = StubMergerCollect._sliced_path(includes_binary_dir, ibd_len_pc, rsha1_pc, "obj")
if obj_path_pc is not None:
# 检查 .obj 文件是否存在
cf_pc: rawptr[fileio.File] = fileio.File(obj_path_pc, fileio.MODE.R)
if not cf_pc.closed:
cf_pc.close()
# .obj 存在,追加到 final_obj_paths
op_len_pc: sizet = string.strlen(obj_path_pc)
if fop_pos + op_len_pc + 2 < OBJ_PATHS_SIZE + 512:
if fop_pos > 0:
final_obj_paths[fop_pos] = ' '
fop_pos += 1
string.strcpy(final_obj_paths + fop_pos, obj_path_pc)
fop_pos += op_len_pc
final_obj_paths[fop_pos] = '\0'
else:
# .obj 缺失:纯声明模块(text.ll 无 define 行,如 stdio.vp 全 pass)
# 无需 .obj,静默跳过;实现文件缺失才是编译产物异常(fail-fast)
# phase2command 模式:.obj 尚未生成(由手工执行 bat 产生),
# 缺失属预期——路径照常加入链接命令,跳过缺失报错。
if BuildPipeline._phase2_cmd_path is not None:
op_len_p2: sizet = string.strlen(obj_path_pc)
if fop_pos + op_len_p2 + 2 < OBJ_PATHS_SIZE + 512:
if fop_pos > 0:
final_obj_paths[fop_pos] = ' '
fop_pos += 1
string.strcpy(final_obj_paths + fop_pos, obj_path_pc)
fop_pos += op_len_p2
final_obj_paths[fop_pos] = '\0'
elif StubMergerIR.IsDeclOnlyModule(temp_dir, td_len_pc, rsha1_pc) != 1:
VLogger.error(f"可达模块 .obj 缺失: {obj_path_pc}", "PhaseC")
stdlib.free(obj_path_pc)
final_obj_paths[fop_pos] = '\0'
obj_paths_len: sizet = fop_pos
# 释放 reachable_set(Phase C 已使用完毕)
if reachable_set is not None:
stdlib.free(reachable_set)
reachable_set = None
# 按图链接:不传 includes_binary_dir(传 None),避免全量收集
lret: int = BuildPipeline.link_objs_to_exe(
final_obj_paths, obj_paths_len,
linker_cmd, linker_flags, exe_path,
None, linker_static_libs, linker_dynamic_libs)
if lret == 0:
VLogger.success(f"输出: {exe_path}", "PhaseC")
# --traceback: export <exe>.stackmap (skipped under --phase2command,
# where nothing was actually linked).
if BuildPipeline._traceback_mode != 0 and BuildPipeline._phase2_cmd_path is None:
BuildPipeline.WriteStackMap(mb, exe_path)
if args.get_bool("run") and BuildPipeline._phase2_cmd_path is not None:
# phase2command 模式:exe 尚未链接(命令在 bat 中),跳过 --run
VLogger.info("phase2command 模式:跳过 --run(exe 尚未链接)", "phase2cmd")
elif args.get_bool("run"):
VLogger.info(f"执行: {exe_path}", "run")
rp: rawptr[subprocess.CompletedProcess] = subprocess.run(exe_path, False, False)
if rp is not None:
VLogger.info(f"退出码: {rp.returncode}", "run")
else:
VLogger.error("链接失败", "PhaseC")
stdlib.free(entries)
stdlib.free(asm_entries)
return 1
stdlib.free(entries)
stdlib.free(asm_entries)
return 0