1455 lines
81 KiB
Plaintext
1455 lines
81 KiB
Plaintext
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.Handles.HandlesTranslator as HandlesTranslator
|
||
import lib.core.Handles.HandlesExprCall as HandlesExprCall
|
||
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 | t.CPtr
|
||
|
||
# 源代码缓冲区大小(1MB)
|
||
SRC_BUF_SIZE: t.CDefine = 1048576
|
||
|
||
# 最大源文件数(递归扫描子目录后文件数增多:App 现有 65 个 .vp,
|
||
# 2026-08-14 CompilerFactory.vp 加入后超出 64 被截断 → 链接 undefined symbol
|
||
# CompilerFactory.get_perm_pool。增大到 128 留余量)
|
||
MAX_SRC_FILES: t.CDefine = 128
|
||
|
||
# 全局栈金丝雀(避免局部变量改变栈布局)
|
||
_g_canary: t.CUInt32T
|
||
|
||
|
||
@t.NoVTable
|
||
class SrcFileEntry:
|
||
"""源文件条目"""
|
||
Path: str # 完整路径
|
||
Sha1: str # SHA1 前16字符
|
||
|
||
|
||
# ============================================================
|
||
# _ScanDirForPyFiles - 递归扫描目录,收集 .vp/.py 文件到 entries
|
||
#
|
||
# 替代旧的非递归 FindFirstFileA(dir/*.py) 扫描,支持子目录
|
||
# (如 App/lib/core/Handles/)。遇到目录递归扫描,遇到 .vp/.py
|
||
# 文件读取内容、计算 SHA1、加入 entries。
|
||
#
|
||
# Args:
|
||
# mb: 内存池
|
||
# dir_path: 当前扫描目录
|
||
# entries: SrcFileEntry 数组
|
||
# entry_size: 单个条目大小(SrcFileEntry.__sizeof__())
|
||
# file_count: 当前已收集的文件数
|
||
# max_files: 最大文件数
|
||
#
|
||
# Returns:
|
||
# 新的 file_count
|
||
# ============================================================
|
||
def _ScanDirForPyFiles(mb: memhub.MemBuddy | t.CPtr,
|
||
dir_path: str,
|
||
entries: SrcFileEntry | t.CPtr,
|
||
entry_size: t.CSizeT,
|
||
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: 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 = _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 或 .py 文件(.vp 为 Viper 源,.py 向后兼容)
|
||
if fname_len > 3 and Utils.is_vp_or_py_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.vp:1510 对同一批 App 源文件的口径一致;
|
||
# 此前用 Utils.compute_sha1(原始字节)会导致 stub/define
|
||
# 符号前缀不一致 → 链接报 undefined reference。
|
||
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 (sha1=%s)", fname, sha1_val)
|
||
VLogger.info(fb, "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
|
||
|
||
|
||
# ============================================================
|
||
# _PreRegisterAppGenericTemplates - mark2 前预注册 App 源文件泛型类模板
|
||
#
|
||
# 与 Phase1 的 includes 全量预注册同理:泛型模板表(GenericClassNames 等
|
||
# 4 张 + 模板节点)是 pool 懒分配的跨文件持久表,若在 mark2 之后
|
||
# (Phase A-pre 翻译 App 文件时)才首次注册,reset_to(mark2) 会回收表与
|
||
# 节点 → 后续查重/特化遍历悬空内存崩溃。App 文件数量少,mark2 前预注册
|
||
# 产生的 AST 驻留内存可忽略。重复注册由 _register_generic_template 查重跳过。
|
||
# ============================================================
|
||
def _PreRegisterAppGenericTemplates(mb: memhub.MemBuddy | t.CPtr,
|
||
entries: SrcFileEntry | t.CPtr,
|
||
file_count: int,
|
||
entry_size: t.CSizeT) -> 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: t.CUInt64T = t.CUInt64T(entries) + i * entry_size
|
||
ent_pa: SrcFileEntry | t.CPtr = (SrcFileEntry | t.CPtr)(t.CVoid(ea_pa, t.CPtr))
|
||
if ent_pa is None or ent_pa.Path is None or ent_pa.Sha1 is None:
|
||
continue
|
||
# 读取文件
|
||
f_pa: fileio.File | t.CPtr = fileio.File(ent_pa.Path, fileio.MODE.R)
|
||
if f_pa.closed:
|
||
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
|
||
else:
|
||
src_buf_pa[SRC_BUF_SIZE - 1] = 0
|
||
# 解析 AST(只解析,不完整翻译;用永久池使模板节点跨 mark2 reset 存活)
|
||
perm_pa: memhub.MemBuddy | t.CPtr = CompilerFactory.get_perm_pool()
|
||
if perm_pa is None:
|
||
perm_pa = mb
|
||
lx_pa: ast.Lexer | t.CPtr = 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 | t.CPtr = ast.tokenize(lx_pa)
|
||
tree_pa: ast.AST | t.CPtr = 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 | t.CPtr] | t.CPtr = tree_pa.children
|
||
if ch_pa is not None:
|
||
cn_pa: t.CSizeT = len(ch_pa)
|
||
for ci_pa in range(cn_pa):
|
||
child_pa: ast.AST | t.CPtr = ch_pa.get(ci_pa)
|
||
if child_pa is None or child_pa.kind() != ast.ASTKind.ClassDef:
|
||
continue
|
||
cd_pa: ast.ClassDef | t.CPtr = (ast.ClassDef | t.CPtr)(child_pa)
|
||
if cd_pa is None or cd_pa.name is None:
|
||
continue
|
||
tp_pa: list[str] | t.CPtr = 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:
|
||
fb_pg3: str = VLogger.fmt_buf()
|
||
if fb_pg3 is not None:
|
||
viperlib.snprintf(fb_pg3, 1024, "mark2 前预注册 %d 个 App 泛型类模板", handled)
|
||
VLogger.info(fb_pg3, "PhaseA")
|
||
return handled
|
||
|
||
|
||
def RunMultiFileProject(mb: memhub.MemBuddy | t.CPtr,
|
||
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: VLogger.Logger | t.CPtr,
|
||
args: argparse.ParsedArgs | t.CPtr) -> int:
|
||
"""多文件项目编译
|
||
|
||
Returns: 0 成功,非 0 失败
|
||
"""
|
||
if source_dir is None:
|
||
VLogger.error("source_dir 为空", "project")
|
||
return 1
|
||
|
||
# === 栈金丝雀检查(全局变量,不影响栈布局)===
|
||
global _g_canary
|
||
_g_canary = 305419896
|
||
|
||
fb: str = VLogger.fmt_buf()
|
||
if fb is not None:
|
||
viperlib.snprintf(fb, 1024, "多文件项目编译: %s", source_dir)
|
||
VLogger.info(fb, "project")
|
||
|
||
# === 1. 递归扫描 source_dir 下的 .vp/.py 文件(包括子目录)===
|
||
entry_size: t.CSizeT = 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)
|
||
fb: str = VLogger.fmt_buf()
|
||
if fb is not None:
|
||
viperlib.snprintf(fb, 1024, "共 %d 个源文件", file_count)
|
||
VLogger.info(fb, "project")
|
||
|
||
if file_count == 0:
|
||
stdlib.free(entries)
|
||
return 1
|
||
|
||
# 初始化 AST 表
|
||
ast._init_tables(mb)
|
||
|
||
# mark2 之前预注册 App 源文件的泛型类模板:泛型模板表/节点是 pool 懒分配,
|
||
# 必须在 mark2 之前注册才能跨 reset_to(mark2) 存活(与 Phase1 的 includes
|
||
# 全量预注册同理),否则 Phase A-pre 首次注册后 reset 回收 → 后续查重/特化
|
||
# 遍历悬空内存崩溃(1.log FuncTest ASAN access-violation 同类根因)。
|
||
_PreRegisterAppGenericTemplates(mb, entries, file_count, entry_size)
|
||
|
||
# OOM 清理(对齐 Phase1 1b 的 mark1/reset_to 模式):
|
||
# Phase2 的 includes 预注册(Phase A-pre-inc)、App 源文件预注册(Phase A-pre)、
|
||
# App 全量翻译(Phase A)、缺失 includes 翻译(Phase B+)都会产生大量瞬时数据
|
||
# (AST/tokens/lexer/IR/Handler/dump 临时)。无回收时 98 个 includes 的 AST/IR
|
||
# 全量累积 pool → 1GB OOM(NewAstTest 依赖 ast 模块场景)。
|
||
# 此处打 mark2,各处翻译完成后 reset_to(mark2) 回收该文件瞬时数据;
|
||
# 跨文件持久表(struct/enum/docstring/CDefine/函数注册表等)已 stdlib 化
|
||
# 或落在 mark2 之前,reset 不受影响(与 Phase1 1b 同翻译路径,已验证安全)。
|
||
mark2: t.CSizeT = 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 扫描 includes 目录填充 Sha1Store(消除 Phase1 依赖)===
|
||
# Phase2 不依赖 Phase1 的副作用:如果 Sha1Store 为空(Phase1 未运行或提前返回),
|
||
# 自行扫描 includes 目录并填充 Sha1Store,确保后续"编译缺失 includes"逻辑能正常工作
|
||
if includes_dir is not None:
|
||
if StubMergerStore.GetSha1StoreCount() == 0:
|
||
inc_scan_result: IncludesScanner.ScanResult | t.CPtr = IncludesScanner.scan_includes(mb, includes_dir)
|
||
if inc_scan_result is None:
|
||
VLogger.error("includes 扫描失败,无法填充 Sha1Store", "project")
|
||
if inc_scan_result is not None:
|
||
inc_filled: int = StubMergerStore.PopulateSha1MapStore(inc_scan_result)
|
||
fb_inc: str = VLogger.fmt_buf()
|
||
if fb_inc is not None:
|
||
viperlib.snprintf(fb_inc, 1024, "Phase2 自主填充 includes Sha1Store: %d 个", inc_filled)
|
||
VLogger.info(fb_inc, "project")
|
||
|
||
# 追加 App 源文件 SHA1 到 _sha1_map.txt(人类可读输出,程序内部不读取此文件)
|
||
# 格式: {sha1}:{rel_path}\n (保留目录结构,如 lib/core/VLogger.vp)
|
||
# 同步追加到内存存储器(AppendToSha1MapStore,供跨模块 CDefine 查找)
|
||
td_len_am: t.CSizeT = string.strlen(temp_dir)
|
||
src_dir_len_am: t.CSizeT = 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: fileio.File | t.CPtr = 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: t.CUInt64T = t.CUInt64T(entries) + i * entry_size
|
||
ent_am: SrcFileEntry | t.CPtr = (SrcFileEntry | t.CPtr)(t.CVoid(ea_am, t.CPtr))
|
||
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: t.CSizeT = 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:
|
||
fb_apf: str = VLogger.fmt_buf()
|
||
if fb_apf is not None:
|
||
viperlib.snprintf(fb_apf, 1024, "AppendToSha1MapStore 失败: %s", rel_path_am)
|
||
VLogger.error(fb_apf, "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: t.CSizeT = 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()
|
||
fb: str = VLogger.fmt_buf()
|
||
if fb is not None:
|
||
viperlib.snprintf(fb, 1024, "已追加 %d 个 App 文件到 _sha1_map.txt 和内存存储器", app_filled)
|
||
VLogger.info(fb, "project")
|
||
stdlib.free(map_path_am)
|
||
|
||
# 模块 SHA1 映射已由 PopulateSha1MapStore + AppendToSha1MapStore 统一填充到
|
||
# StubMerger 全局存储器,HandlesExprCall._lookup_module_sha1 直接查全局存储器,
|
||
# 不再需要构建独立的 SHA1 映射表副本。
|
||
|
||
|
||
# === Phase A-pre-vpi: 预生成所有 App 源文件的 .vpi stub ===
|
||
#
|
||
# 与 Phase1 1b-pre 同理:依赖模块的 .vpi 必须在依赖方翻译前可用。
|
||
# Phase A 主循环按扫描顺序翻译(非拓扑),若 StubMergerCollect/IR/Store
|
||
# 先于 __init__.vp 翻译,__init__.vpi 尚未生成 → LoadDepVpiIntoModule
|
||
# 找不到 vpi → 跨模块全局变量 declare 未注入 → ExprGenAttr fallback
|
||
# 以 i8* 创建带前缀 global(@b0b568._sha1_store_count = external global i8*)
|
||
# → llc "icmp slt 类型不匹配"(22fc/2e23/daad 根因,见 vpi 时间戳顺序)。
|
||
# 此处先为所有 App 源文件预生成 vpi,保证 Phase A 翻译任意模块时其依赖
|
||
# vpi 已存在(App 源文件自身的 vpi 随后由 TranslateFileGetTrans 覆盖重写,
|
||
# 内容一致,此处仅保证"先于依赖方")。
|
||
td_len_vpa: t.CSizeT = string.strlen(temp_dir)
|
||
PYI_BUF_SIZE_P2: t.CSizeT = 262144
|
||
vpi_failed_p2: int = 0
|
||
for i_vp2 in range(file_count):
|
||
ea_vp2: t.CUInt64T = t.CUInt64T(entries) + i_vp2 * entry_size
|
||
ent_vp2: SrcFileEntry | t.CPtr = (SrcFileEntry | t.CPtr)(t.CVoid(ea_vp2, t.CPtr))
|
||
if ent_vp2 is None or ent_vp2.Path is None or ent_vp2.Sha1 is None:
|
||
vpi_failed_p2 += 1
|
||
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
|
||
continue
|
||
vf_vp2: fileio.File | t.CPtr = 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 传 None:与 TranslateFileGetTrans 后续覆盖写的内容一致)
|
||
f_vp2: fileio.File | t.CPtr = fileio.File(ent_vp2.Path, fileio.MODE.R)
|
||
if f_vp2.closed:
|
||
vpi_failed_p2 += 1
|
||
continue
|
||
src_buf_vp2: bytes = stdlib.malloc(SRC_BUF_SIZE)
|
||
if src_buf_vp2 is None:
|
||
f_vp2.close()
|
||
vpi_failed_p2 += 1
|
||
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
|
||
continue
|
||
if bytes_read_vp2 < SRC_BUF_SIZE:
|
||
src_buf_vp2[bytes_read_vp2] = 0
|
||
else:
|
||
src_buf_vp2[SRC_BUF_SIZE - 1] = 0
|
||
lx_vp2: ast.Lexer | t.CPtr = ast.new_lexer(mb)
|
||
if lx_vp2 is None:
|
||
stdlib.free(src_buf_vp2)
|
||
vpi_failed_p2 += 1
|
||
continue
|
||
ast._lexer_init(lx_vp2, src_buf_vp2, mb)
|
||
tokens_vp2: ast.Token | t.CPtr = ast.tokenize(lx_vp2)
|
||
tree_vp2: ast.AST | t.CPtr = ast.parse_tokens(mb, tokens_vp2)
|
||
if tree_vp2 is None:
|
||
stdlib.free(src_buf_vp2)
|
||
vpi_failed_p2 += 1
|
||
continue
|
||
pyi_buf_vp2: bytes = stdlib.malloc(PYI_BUF_SIZE_P2)
|
||
if pyi_buf_vp2 is not None:
|
||
pyi_pos_vp2: t.CSizeT = StubConverter._GeneratePyiFromAst(
|
||
mb, tree_vp2, None, 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: fileio.File | t.CPtr = 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
|
||
stdlib.free(src_buf_vp2)
|
||
# OOM 清理:回收本文件 AST(vpi 已写盘;mark2 后持久数据已 stdlib 化)
|
||
mb.reset_to(mark2)
|
||
if vpi_failed_p2 > 0:
|
||
fb_vp2: str = VLogger.fmt_buf()
|
||
if fb_vp2 is not None:
|
||
viperlib.snprintf(fb_vp2, 1024, "Phase A-pre-vpi 有 %d 个文件 vpi 预生成失败", vpi_failed_p2)
|
||
VLogger.error(fb_vp2, "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(追加82 同源溢出),8192 会静默丢依赖致 Phase B+ 可达集合不全
|
||
APP_DEPS_BUF_SIZE: t.CSizeT = 65536
|
||
app_deps_buf: bytes = None
|
||
app_deps_len: t.CSizeT = 0
|
||
if do_phase1 != 0:
|
||
if log is not None:
|
||
log.banner("Phase A: 生成 stub + text")
|
||
|
||
# === Phase A-pre-inc: includes 预注册 struct + 依赖填充 ===
|
||
# 必须在 App 源文件预注册之前执行:App 源文件(strict_mode=1)可能继承
|
||
# 或引用 includes 中的 struct(如 ast.AST)。若 includes struct 未注册,
|
||
# App 源文件翻译会失败 → .obj 缺失 → undefined reference。
|
||
# 同时填充 PopulateIncludesDeps,供 Phase B+ 的 _BuildReachableSha1Set 使用。
|
||
# 多遍扫描:解决跨文件继承问题(如 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: t.CInt = 3
|
||
# [修复] 记录预注册失败的 includes:多遍后仍失败 → 报错终止编译。
|
||
# 不允许静默降级——静默失败会让 App 源文件的跨模块类构造退化为
|
||
# 普通函数调用(返回类型默认推断成 i32),调用点报误导性的
|
||
# "跨模块调用 'parser.add_argument' 找不到模块 SHA1"
|
||
# (如 argparse.ArgumentParser 类未注册导致 parser 变量类型崩坏)。
|
||
# declare_only=1 时 import 的 SHA1 检查跳过(容忍依赖未注册),
|
||
# 因此预注册失败只可能是文件自身无法翻译(语法/解析/编译错误)。
|
||
# 注意:Sha1Store 因 AppendToSha1MapStore 追加了 App 源文件 SHA1
|
||
# (供跨模块 CDefine 查找),此处必须跳过 App 文件——App 文件的
|
||
# 预注册由 Phase A-pre(遍历 entries,declare_only=1)负责。
|
||
# App 文件是严格模式(仅本地+import 可见)且预注册阶段依赖未就绪,
|
||
# 若在此预注册必然失败,误报"预注册失败: main.vp"。
|
||
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 + t.CSizeT(si_inc) * 17
|
||
inc_rel_pre: str = store_rel_inc_pre + t.CSizeT(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: t.CUInt64T = t.CUInt64T(entries) + i_app_inc * entry_size
|
||
ent_app_inc: SrcFileEntry | t.CPtr = (SrcFileEntry | t.CPtr)(t.CVoid(ea_app_inc, t.CPtr))
|
||
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: t.CSizeT = string.strlen(inc_rel_pre)
|
||
# 所属 includes 根目录(多目录逐条取自 IncDir 数组,空串回退 includes_dir)
|
||
incdir_pre: str = store_incdir_inc_pre + t.CSizeT(si_inc) * StubMerger.MAX_INC_DIR_LEN
|
||
if incdir_pre is None or incdir_pre[0] == '\0':
|
||
incdir_pre = includes_dir
|
||
incdir_len_pre: t.CSizeT = 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: HandlesTranslator.Translator | t.CPtr = 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:
|
||
# 预注册成功:清除失败标记
|
||
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 + t.CSizeT(si_inc_f) * StubMerger.MAX_REL_PATH_LEN
|
||
fb_inc_f: str = VLogger.fmt_buf()
|
||
if fb_inc_f is not None:
|
||
viperlib.snprintf(fb_inc_f, 1024,
|
||
"includes 预注册失败: %s(文件无法翻译,可能是语法/解析/编译错误),终止编译",
|
||
rel_f)
|
||
VLogger.error(fb_inc_f, "prePhaseA")
|
||
sys.exit(1)
|
||
stdlib.free(inc_fail_flags)
|
||
fb_inc_pre: str = VLogger.fmt_buf()
|
||
if fb_inc_pre is not None:
|
||
viperlib.snprintf(fb_inc_pre, 1024, "includes 预注册 + 依赖填充: %d/%d", deps_filled_pre, store_count_inc_pre)
|
||
VLogger.info(fb_inc_pre, "prePhaseA")
|
||
|
||
# === Phase A-pre: 预注册所有源文件的 struct/enum/union ===
|
||
# 解决循环引用问题:circ_a 翻译时需要知道 circ_b.ClassB 的 struct 定义
|
||
# 仅注册 struct/enum/union(declare_only=1),不翻译方法体
|
||
# 多遍扫描:解决跨文件继承的字母序问题(如 HandlesAnnAssign.vp/.py 在 HandlesBase.vp/.py 之前,
|
||
# AnnAssignHandle 继承 Mixin 时 Mixin 未注册)。每遍注册新类后,下一遍子类可继承。
|
||
VLogger.info("预注册 struct/enum/union...", "PhaseA")
|
||
src_dir_len_pre: t.CSizeT = string.strlen(source_dir)
|
||
PHASE_A_PRE_MAX_PASSES: t.CInt = 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: t.CUInt64T = t.CUInt64T(entries) + i * entry_size
|
||
ent_pre: SrcFileEntry | t.CPtr = (SrcFileEntry | t.CPtr)(t.CVoid(ea_pre, t.CPtr))
|
||
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)
|
||
tr_pre: HandlesTranslator.Translator | t.CPtr = BuildPipeline.TranslateFileGetTrans(mb, ent_pre.Path, ent_pre.Sha1, pkg_pre, 1)
|
||
if tr_pre is None:
|
||
fb: str = VLogger.fmt_buf()
|
||
if fb is not None:
|
||
viperlib.snprintf(fb, 1024, "预注册失败: %s", ent_pre.Path)
|
||
VLogger.error(fb, "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_SIZE: t.CSizeT = 1048576
|
||
td_len_pa: t.CSizeT = 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 翻译(文件系统顺序),跨模块函数返回类型精确解析会因
|
||
# 依赖 text.ll 未生成而标记 _deps_pending=1(见 CallGenMixin 跨模块
|
||
# 返回类型解析段)。下遍重译即可命中(依赖已生成 text.ll)。
|
||
# 每遍至少完成一个文件,最多 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: t.CInt = 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: t.CUInt64T = t.CUInt64T(entries) + i * entry_size
|
||
ent: SrcFileEntry | t.CPtr = (SrcFileEntry | t.CPtr)(t.CVoid(ea, t.CPtr))
|
||
if ent is None or ent.Path is None:
|
||
done_flags[i] = 1
|
||
completed_this_pass += 1
|
||
continue
|
||
# 计算源文件的包名(相对 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 = 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)
|
||
if tr_a is None:
|
||
# 翻译失败(语法/解析错误):记录待下遍重试,收敛检查统一报错
|
||
# OOM 清理:失败翻译也已产生 AST/tokens 瞬时数据,立即回收
|
||
mb.reset_to(mark2)
|
||
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:
|
||
# 逐模块去重拼接:多个 App 文件 import 同一模块(builtins/
|
||
# _list/_dict/stdlib/t/c 等公共依赖)会让 app_deps 无去重
|
||
# 膨胀到接近 64KB,_BuildReachableSha1Set 的初始 worklist
|
||
# 即满 → BFS 展开必报"依赖 worklist 溢出"。
|
||
im_len_a: t.CSizeT = string.strlen(tr_a._imported_modules)
|
||
im_pos_a: t.CSizeT = 0
|
||
while im_pos_a < im_len_a:
|
||
im_start_a: t.CSizeT = 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: t.CSizeT = 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)
|
||
if tr_a._deps_pending != 0:
|
||
# 依赖模块 text.ll 未生成(同项目文件顺序),不写盘,下遍重试
|
||
# 诊断增强:遍历本文件直接依赖,列出 text.ll 仍缺失的模块
|
||
# (含 SHA1)——精确定位是哪个依赖未完成(通常是其自身依赖链
|
||
# 上某个文件先失败,text.ll 从未落盘)。全部直接依赖已就绪仍
|
||
# pending 时,说明缺失在传递依赖/MRO/vpi 侧,据此缩小排查范围。
|
||
fb_dp: str = VLogger.fmt_buf()
|
||
if fb_dp is not None:
|
||
miss_dp: bytes = stdlib.malloc(768)
|
||
if miss_dp is not None:
|
||
miss_dp[0] = '\0'
|
||
md_cnt: int = 0
|
||
if tr_a._imported_modules is not None:
|
||
im_dp_len: t.CSizeT = string.strlen(tr_a._imported_modules)
|
||
im_dp_pos: t.CSizeT = 0
|
||
while im_dp_pos < im_dp_len:
|
||
im_dp_start: t.CSizeT = im_dp_pos
|
||
while im_dp_pos < im_dp_len and tr_a._imported_modules[im_dp_pos] != ' ':
|
||
im_dp_pos += 1
|
||
im_dp_mod_len: t.CSizeT = im_dp_pos - im_dp_start
|
||
if im_dp_pos < im_dp_len:
|
||
im_dp_pos += 1
|
||
if im_dp_mod_len == 0:
|
||
continue
|
||
im_dp_buf: bytes = stdlib.malloc(im_dp_mod_len + 1)
|
||
if im_dp_buf is None:
|
||
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 不命中)
|
||
im_dp_sha1: str = StubMergerStore.LookupSha1ByModName(im_dp_buf)
|
||
if im_dp_sha1 is None:
|
||
im_dp_sha1 = StubMergerStore.LookupSha1ByModNameSuffix(im_dp_buf)
|
||
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_resolved: int = 0
|
||
im_dp_missing: int = 0
|
||
if im_dp_sha1 is not None:
|
||
im_dp_resolved = 1
|
||
im_dp_txt: str = StubMergerCollect._sliced_path(
|
||
temp_dir, td_len_pa, im_dp_sha1, "text.ll")
|
||
if im_dp_txt is not None:
|
||
im_dp_f: fileio.File | t.CPtr = fileio.File(im_dp_txt, fileio.MODE.R)
|
||
if im_dp_f.closed:
|
||
im_dp_missing = 1
|
||
else:
|
||
im_dp_f.close()
|
||
stdlib.free(im_dp_txt)
|
||
else:
|
||
im_dp_missing = 1
|
||
else:
|
||
# 内置模块(t/c)不生成 text.ll,解析不到不算缺失
|
||
if string.strcmp(im_dp_buf, "t") == 0 or string.strcmp(im_dp_buf, "c") == 0:
|
||
im_dp_resolved = 2
|
||
if im_dp_missing != 0 or im_dp_resolved == 0:
|
||
if md_cnt > 0 and string.strlen(miss_dp) < 700:
|
||
string.strcat(miss_dp, ", ")
|
||
if string.strlen(miss_dp) < 700:
|
||
if im_dp_missing != 0:
|
||
viperlib.snprintf(miss_dp + string.strlen(miss_dp),
|
||
768 - string.strlen(miss_dp),
|
||
"%s(%s)缺text.ll", im_dp_buf, im_dp_sha1)
|
||
else:
|
||
viperlib.snprintf(miss_dp + string.strlen(miss_dp),
|
||
768 - string.strlen(miss_dp),
|
||
"%s(未解析-改名别名?)", im_dp_buf)
|
||
md_cnt += 1
|
||
stdlib.free(im_dp_buf)
|
||
if md_cnt > 0:
|
||
viperlib.snprintf(fb_dp, 1024,
|
||
"Phase A 依赖未就绪,下遍重试: %s | 依赖探测明细: %s",
|
||
ent.Path, miss_dp)
|
||
else:
|
||
viperlib.snprintf(fb_dp, 1024,
|
||
"Phase A 依赖未就绪,下遍重试: %s | 直接依赖 text.ll 均已生成"
|
||
"(缺失可能在传递依赖/MRO/vpi 侧)", ent.Path)
|
||
stdlib.free(miss_dp)
|
||
else:
|
||
viperlib.snprintf(fb_dp, 1024,
|
||
"Phase A 依赖未就绪,下遍重试: %s", ent.Path)
|
||
# --nowarn-register 亦抑制此类 Phase A 依赖重试 WARN(刷屏收敛)
|
||
if VLogger.nowarn_register() == 0:
|
||
VLogger.warning(fb_dp, "PhaseA-DBG")
|
||
# 回滚本文件已注册类的 MethodsEmitted:第 1 遍 _translate_oop_methods
|
||
# 发射方法体前已置 MethodsEmitted=1(防重入),但方法体内跨模块调用
|
||
# 依赖 text.ll 未生成触发 _deps_pending → 本遍作废;若不回滚,
|
||
# 下遍重译 existing 分支被 MethodsEmitted==1 防重复检查拦截 → 类方法
|
||
# 永不发射 → 链接 undefined symbol(追加87 根因)。
|
||
# 必须先于 mb.reset_to(mark2)(MethodsEmitted 在 perm_pool 上跨 reset 存活)。
|
||
if ent.Sha1 is not None:
|
||
GenApi.reset_module_methods_emitted(ent.Sha1)
|
||
# OOM 清理:本文件翻译已产生瞬时数据(下遍会重新翻译),立即回收
|
||
mb.reset_to(mark2)
|
||
continue
|
||
|
||
# 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: t.CSizeT = 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: fileio.File | t.CPtr = 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: t.CSizeT = 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: fileio.File | t.CPtr = 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: fileio.File | t.CPtr = fileio.File(deps_path_a, fileio.MODE.W)
|
||
if not df_a.closed:
|
||
if tr_a._imported_modules is not None:
|
||
dl_a: t.CSizeT = string.strlen(tr_a._imported_modules)
|
||
df_a.write(tr_a._imported_modules, dl_a)
|
||
df_a.close()
|
||
stdlib.free(deps_path_a)
|
||
|
||
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:
|
||
# 无进展:剩余文件无法完成(真实编译错误),终止并列出未完成文件
|
||
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: str = VLogger.fmt_buf()
|
||
if fb_err is not None:
|
||
viperlib.snprintf(fb_err, 1024,
|
||
"Phase A 无法完成翻译: %s(请检查依赖模块是否已编译/注册;"
|
||
"缺失依赖明细见上方 PhaseA-DBG 警告,若被 --nowarn-register 抑制请去掉该参数重跑)",
|
||
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:
|
||
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:
|
||
if log is not None:
|
||
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: t.CSizeT = 65536
|
||
obj_paths: bytes = stdlib.malloc(OBJ_PATHS_SIZE)
|
||
if obj_paths is None:
|
||
return 1
|
||
obj_paths[0] = '\0'
|
||
obj_pos: t.CSizeT = 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: t.CSizeT = 4194304
|
||
|
||
# llc 任务表(stdlib.malloc,跨线程存活):连续 LlcTask 记录数组,
|
||
# 循环内只写 .ll + 构造任务,循环后 run_llc_parallel 分批并行跑 llc
|
||
llc_tasks: bytes = stdlib.malloc(t.CSizeT(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: t.CUInt64T = t.CUInt64T(entries) + i * entry_size
|
||
ent: SrcFileEntry | t.CPtr = (SrcFileEntry | t.CPtr)(t.CVoid(ea, t.CPtr))
|
||
if ent is None or ent.Path is None or ent.Sha1 is None:
|
||
continue
|
||
# 组合本地 stub + 所有依赖 stub + 本地 text → 完整 IR
|
||
combined_ir: bytes = stdlib.malloc(COMBINED_IR_SIZE)
|
||
if combined_ir is None:
|
||
fb: str = VLogger.fmt_buf()
|
||
if fb is not None:
|
||
viperlib.snprintf(fb, 1024, "combined_ir 分配失败: %s", ent.Path)
|
||
VLogger.error(fb, "PhaseB")
|
||
continue
|
||
combined_len: t.CSizeT = StubMergerBuild.BuildCombinedIR(temp_dir, ent.Sha1, combined_ir, COMBINED_IR_SIZE)
|
||
if combined_len == 0:
|
||
fb: str = VLogger.fmt_buf()
|
||
if fb is not None:
|
||
viperlib.snprintf(fb, 1024, "BuildCombinedIR 失败: %s", ent.Path)
|
||
VLogger.error(fb, "PhaseB")
|
||
stdlib.free(combined_ir)
|
||
continue
|
||
|
||
# 写 .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:
|
||
fb: str = VLogger.fmt_buf()
|
||
if fb is not None:
|
||
viperlib.snprintf(fb, 1024, "写 .ll 失败: %s", ent.Path)
|
||
VLogger.error(fb, "PhaseB")
|
||
sys.exit(1)
|
||
|
||
# 构造 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: BuildPipeline.LlcTask | t.CPtr = (BuildPipeline.LlcTask | t.CPtr)(t.CVoid(
|
||
t.CUInt64T(llc_tasks) + t.CSizeT(llc_task_count) * BuildPipeline.LlcTask.__sizeof__(), t.CPtr))
|
||
if BuildPipeline.init_llc_task(tslot, llc_ir_path, output_dir, ent.Sha1,
|
||
cc_cmd, cc_flags) != 0:
|
||
fb: str = VLogger.fmt_buf()
|
||
if fb is not None:
|
||
viperlib.snprintf(fb, 1024, "llc 任务构造失败: %s", ent.Path)
|
||
VLogger.error(fb, "PhaseB")
|
||
sys.exit(1)
|
||
llc_task_count += 1
|
||
|
||
compiled_count += 1
|
||
# 显示已提交 llc 的文件
|
||
fb_comp: str = VLogger.fmt_buf()
|
||
if fb_comp is not None:
|
||
viperlib.snprintf(fb_comp, 1024, "[%d/%d] llc 已提交: %s", i + 1, file_count, ent.Path)
|
||
VLogger.info(fb_comp, "PhaseB")
|
||
# 构造 .obj 路径,检测是否是 main 模块(test_main.vp/.py 或 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:
|
||
is_main_mod = 1
|
||
elif string.strstr(ent.Path, "main.vp") is not None or string.strstr(ent.Path, "main.py") 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: t.CSizeT = 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: BuildPipeline.LlcTask | t.CPtr = (BuildPipeline.LlcTask | t.CPtr)(t.CVoid(
|
||
t.CUInt64T(llc_tasks) + t.CSizeT(ti) * BuildPipeline.LlcTask.__sizeof__(), t.CPtr))
|
||
BuildPipeline.free_llc_task(tfree)
|
||
ti += 1
|
||
stdlib.free(llc_tasks)
|
||
|
||
fb: str = VLogger.fmt_buf()
|
||
if fb is not None:
|
||
viperlib.snprintf(fb, 1024, "编译完成: %d/%d", compiled_count, file_count)
|
||
VLogger.success(fb, "PhaseB")
|
||
|
||
if compiled_count == 0:
|
||
VLogger.error("无成功编译的文件", "PhaseB")
|
||
stdlib.free(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: t.CSizeT = string.strlen(temp_dir)
|
||
|
||
# 诊断日志:确认 Phase B+ 入口和 Sha1Store 状态
|
||
fb_pbp_enter: str = VLogger.fmt_buf()
|
||
if fb_pbp_enter is not None:
|
||
viperlib.snprintf(fb_pbp_enter, 1024,
|
||
"PhaseB+ 入口: store_count=%d includes_binary_dir=%s",
|
||
StubMergerStore.GetSha1StoreCount(), includes_binary_dir)
|
||
VLogger.info(fb_pbp_enter, "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: t.CSizeT = t.CSizeT(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)
|
||
fb_reach: str = VLogger.fmt_buf()
|
||
if fb_reach is not None:
|
||
viperlib.snprintf(fb_reach, 1024, "可达 includes SHA1: %d 个", reachable_count)
|
||
VLogger.info(fb_reach, "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)跨函数传参/索引的地址异常
|
||
# (与 _BuildReachableSha1Set 的 BFS 一致)。
|
||
# rel/incdir 仅本函数内按索引读取(不跨函数传参),保留 store 指针。
|
||
td_len_pb: t.CSizeT = 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: t.CSizeT | t.CPtr = (t.CSizeT | t.CPtr)(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 + t.CSizeT(ti) * 17
|
||
inc_rel_mi: str = store_rel_arr_mi + t.CSizeT(ti) * StubMerger.MAX_REL_PATH_LEN
|
||
# 检查 .obj 是否已存在于 includes.binary(切片子目录)
|
||
ibd_len_mi: t.CSizeT = 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: t.CSizeT = string.strlen(inc_rel_mi)
|
||
incdir_mi: str = store_incdir_arr_mi + t.CSizeT(ti) * StubMerger.MAX_INC_DIR_LEN
|
||
if incdir_mi is None or incdir_mi[0] == '\0':
|
||
incdir_mi = includes_dir
|
||
inc_dir_len_mi: t.CSizeT = 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: fileio.File | t.CPtr = 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: t.CInt64T = 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'
|
||
else:
|
||
tbuf_mi[StubMerger.STUB_READ_BUF_SIZE - 1] = '\0'
|
||
# 逐行扫描: 找 define 行即为实现文件
|
||
# (不再要求行中含 @",因为 memhub 等模块的函数名
|
||
# 如 @ca40915f11b8b6ad._align_up 不带引号但仍是实现文件)
|
||
tpos_mi: t.CSizeT = 0
|
||
while tpos_mi < tbr_mi:
|
||
tls_mi: t.CSizeT = tpos_mi
|
||
while tpos_mi < tbr_mi:
|
||
if tbuf_mi[tpos_mi] == '\n':
|
||
break
|
||
tpos_mi += 1
|
||
tll_mi: t.CSizeT = 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: t.CSizeT = 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: HandlesTranslator.Translator | t.CPtr = BuildPipeline.TranslateFileGetTrans(mb, src_fp_mi, inc_sha1_mi, inc_pkg_mi)
|
||
if tr_mi is not None:
|
||
PBP_IR_SIZE: t.CSizeT = 1048576
|
||
# 保存 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: t.CSizeT = 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: fileio.File | t.CPtr = 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: t.CSizeT = 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: fileio.File | t.CPtr = 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: fileio.File | t.CPtr = 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: fileio.File | t.CPtr = 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)
|
||
fb: str = VLogger.fmt_buf()
|
||
if fb is not None:
|
||
viperlib.snprintf(fb, 1024,
|
||
"BuildCombinedIR 失败: %s (stub=%d text=%d is_decl=%d)",
|
||
src_fp_mi, diag_stub_ex, diag_text_ex, is_decl_mi)
|
||
VLogger.error(fb, "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:
|
||
fb: str = VLogger.fmt_buf()
|
||
if fb is not None:
|
||
viperlib.snprintf(fb, 1024, "llc 编译失败,终止编译: %s", src_fp_mi)
|
||
VLogger.error(fb, "PhaseB+")
|
||
sys.exit(1)
|
||
|
||
inc_compiled += 1
|
||
# 显示编译完成的 includes 文件
|
||
fb_inc_comp: str = VLogger.fmt_buf()
|
||
if fb_inc_comp is not None:
|
||
viperlib.snprintf(fb_inc_comp, 1024, "[includes %d] 编译完成: %s", inc_compiled, src_fp_mi)
|
||
VLogger.info(fb_inc_comp, "PhaseB+")
|
||
|
||
# 添加到 obj_paths (切片路径,从 includes_binary_dir 取)
|
||
ibd_len_mi2: t.CSizeT = 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: t.CSizeT = 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)
|
||
|
||
fb: str = VLogger.fmt_buf()
|
||
if fb is not None:
|
||
viperlib.snprintf(fb, 1024, "编译缺失 includes: %d 个", inc_compiled)
|
||
VLogger.info(fb, "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 ===
|
||
if log is not None:
|
||
log.banner("Phase C: 链接")
|
||
|
||
od_len2: t.CSizeT = string.strlen(output_dir)
|
||
lo_len: t.CSizeT = 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: t.CSizeT = 0
|
||
# main_obj_path 放在最前面(确保 --allow-multiple-definition 选择用户 main)
|
||
if main_obj_path[0] != '\0':
|
||
mlen: t.CSizeT = 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 依赖文件
|
||
# (main import 的 syslibtest_impl/os_backend 等)的 .obj 从未入链——追加28
|
||
# 前靠 reachable 误含 App 时从 includes.binary 的重复 obj 兜底(undefined
|
||
# symbol: ae2038ced7b4634f.run 根因)。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: t.CSizeT = string.strlen(includes_binary_dir)
|
||
td_len_pc: t.CSizeT = string.strlen(temp_dir)
|
||
ri_pc: int
|
||
for ri_pc in range(reachable_count):
|
||
rsha1_pc: str = reachable_set + t.CSizeT(ri_pc) * 17
|
||
if rsha1_pc[0] == '\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: fileio.File | t.CPtr = fileio.File(obj_path_pc, fileio.MODE.R)
|
||
if not cf_pc.closed:
|
||
cf_pc.close()
|
||
# .obj 存在,追加到 final_obj_paths
|
||
op_len_pc: t.CSizeT = 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)
|
||
if StubMergerIR.IsDeclOnlyModule(temp_dir, td_len_pc, rsha1_pc) != 1:
|
||
fb_pc: str = VLogger.fmt_buf()
|
||
if fb_pc is not None:
|
||
viperlib.snprintf(fb_pc, 1024, "可达模块 .obj 缺失: %s", obj_path_pc)
|
||
VLogger.error(fb_pc, "PhaseC")
|
||
stdlib.free(obj_path_pc)
|
||
|
||
final_obj_paths[fop_pos] = '\0'
|
||
obj_paths_len: t.CSizeT = 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)
|
||
|
||
if lret == 0:
|
||
fb: str = VLogger.fmt_buf()
|
||
if fb is not None:
|
||
viperlib.snprintf(fb, 1024, "输出: %s", exe_path)
|
||
VLogger.success(fb, "PhaseC")
|
||
if args.get_bool("run"):
|
||
fb: str = VLogger.fmt_buf()
|
||
if fb is not None:
|
||
viperlib.snprintf(fb, 1024, "执行: %s", exe_path)
|
||
VLogger.info(fb, "run")
|
||
rp: subprocess.CompletedProcess | t.CPtr = subprocess.run(exe_path, False, False)
|
||
if rp is not None:
|
||
fb: str = VLogger.fmt_buf()
|
||
if fb is not None:
|
||
viperlib.snprintf(fb, 1024, "退出码: %d", rp.returncode)
|
||
VLogger.info(fb, "run")
|
||
else:
|
||
VLogger.error("链接失败", "PhaseC")
|
||
stdlib.free(entries)
|
||
return 1
|
||
|
||
stdlib.free(entries)
|
||
return 0
|