Files
TransPyV/App/lib/core/Phase2.vp

944 lines
50 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 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.core.Handles.HandlesImports as HandlesImports
import lib.core.Handles.HandlesStruct as HandlesStruct
import lib.core.BuildPipeline as BuildPipeline
import lib.core.IncludesScanner as IncludesScanner
import lib.core.StubMerger as StubMerger
import lib.Projectrans.Utils as Utils
import lib.Projectrans.Config as Config
# 全局 mbuddy 指针
_mbuddy: memhub.MemBuddy | t.CPtr
# 源代码缓冲区大小1MB
SRC_BUF_SIZE: t.CDefine = 1048576
# 最大源文件数(递归扫描子目录后文件数增多,增大到 64
MAX_SRC_FILES: t.CDefine = 64
# 全局栈金丝雀(避免局部变量改变栈布局)
_g_canary: t.CUInt32T
@t.NoVTable
class SrcFileEntry:
"""源文件条目"""
Path: str # 完整路径
Sha1: str # SHA1 前16字符
# ============================================================
# _ScanDirForPyFiles - 递归扫描目录,收集 .py 文件到 entries
#
# 替代旧的非递归 FindFirstFileA(dir/*.py) 扫描,支持子目录
# (如 App/lib/core/Handles/)。遇到目录递归扫描,遇到 .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:
"""递归扫描目录,收集 .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 not None:
fname_len: t.CSizeT = string.strlen(fname)
if fname_len > 0:
# 跳过 . 和 ..
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:
# 检查是否是目录
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 = stdlib.malloc(dir_len + fname_len + 2)
if sub_dir is not None:
viperlib.snprintf(sub_dir, dir_len + fname_len + 2, "%s/%s", dir_path, fname)
file_count = _ScanDirForPyFiles(mb, sub_dir, entries, entry_size, file_count, max_files)
stdlib.free(sub_dir)
else:
# 检查是否是 .vp 或 .py 文件(.vp 为 Viper 源,.py 向后兼容)
if fname_len > 3:
if (fname[fname_len - 3] == '.' and fname[fname_len - 2] == 'v' and fname[fname_len - 1] == 'p') or (fname[fname_len - 3] == '.' and fname[fname_len - 2] == 'p' and fname[fname_len - 1] == 'y'):
if file_count < max_files:
full_path: bytes = stdlib.malloc(dir_len + fname_len + 2)
if full_path is not None:
viperlib.snprintf(full_path, dir_len + fname_len + 2, "%s/%s", dir_path, fname)
sf: fileio.File | t.CPtr = fileio.File(full_path, fileio.MODE.R)
if not sf.closed:
sbuf: bytes = stdlib.malloc(SRC_BUF_SIZE)
if sbuf is not None:
br: LONG = sf.read_all(sbuf, SRC_BUF_SIZE)
sf.close()
if br > 0:
if br < SRC_BUF_SIZE:
sbuf[br] = 0
else:
sbuf[SRC_BUF_SIZE - 1] = 0
sha1_val: str = Utils.compute_sha1(mb, sbuf)
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: t.CChar | t.CPtr = 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
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
# === 栈金丝雀检查(全局变量,不影响栈布局)===
_g_canary = 305419896
fb: t.CChar | t.CPtr = VLogger.fmt_buf()
if fb is not None:
viperlib.snprintf(fb, 1024, "多文件项目编译: %s", source_dir)
VLogger.info(fb, "project")
# === 1. 递归扫描 source_dir 下的 .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: t.CChar | t.CPtr = 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)
# 确保 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 StubMerger.GetSha1StoreCount() == 0:
inc_scan_result: IncludesScanner.ScanResult | t.CPtr = IncludesScanner.scan_includes(mb, includes_dir)
if inc_scan_result is not None:
inc_filled: int = StubMerger.PopulateSha1MapStore(inc_scan_result)
fb_inc: t.CChar | t.CPtr = 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.py
# 同步追加到内存存储器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.py",与 _sha1_map.txt 一致
if StubMerger.AppendToSha1MapStore(ent_am.Sha1, rel_path_am) == 0:
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: t.CChar | t.CPtr = 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 映射(供跨模块函数调用名混淆使用)
td_len_pb: t.CSizeT = string.strlen(temp_dir)
pb_sha1_arr: bytes = stdlib.malloc(StubMerger.MAX_INCLUDES * 17)
pb_mod_arr: bytes = stdlib.malloc(StubMerger.MAX_INCLUDES * 64)
pb_inc_count: int = 0
if pb_sha1_arr is not None and pb_mod_arr is not None:
pb_inc_count = StubMerger._BuildIncludesSha1Map(temp_dir, td_len_pb, pb_sha1_arr, pb_mod_arr)
# 追加用户源文件的 (模块名, SHA1) 到全局映射
# 使跨模块方法调用能通过 from_imports + _lookup_module_sha1 找到正确的 SHA1
if pb_sha1_arr is not None and pb_mod_arr is not None:
src_dir_len_us: t.CSizeT = string.strlen(source_dir)
for i in range(file_count):
ea_us: t.CUInt64T = t.CUInt64T(entries) + i * entry_size
ent_us: SrcFileEntry | t.CPtr = (SrcFileEntry | t.CPtr)(t.CVoid(ea_us, t.CPtr))
if ent_us is None or ent_us.Path is None or ent_us.Sha1 is None:
continue
if pb_inc_count >= StubMerger.MAX_INCLUDES:
break
# 计算相对路径并转换为点分模块名(保留完整包路径)
path_str: str = ent_us.Path
path_len_us: t.CSizeT = string.strlen(path_str)
rel_path_us: str = path_str
if path_len_us > src_dir_len_us + 1:
if string.strncmp(path_str, source_dir, src_dir_len_us) == 0:
rel_path_us = path_str + src_dir_len_us + 1
# 使用 _PathToModuleName 转换为点分模块名(处理 __init__.py → 包名)
mod_name_us: str = StubMerger._PathToModuleName(rel_path_us)
if mod_name_us is None:
continue
# 写入模块名
mod_idx: t.CSizeT = t.CSizeT(pb_inc_count) * 64
mn_len_us: t.CSizeT = string.strlen(mod_name_us)
if mn_len_us >= 64:
string.strncpy(pb_mod_arr + mod_idx, mod_name_us, 63)
pb_mod_arr[mod_idx + 63] = '\0'
else:
string.strcpy(pb_mod_arr + mod_idx, mod_name_us)
stdlib.free(mod_name_us)
# 写入 SHA1
sha1_idx: t.CSizeT = t.CSizeT(pb_inc_count) * 17
string.strcpy(pb_sha1_arr + sha1_idx, ent_us.Sha1)
pb_inc_count += 1
HandlesExprCall.set_module_sha1_map(pb_sha1_arr, pb_mod_arr, pb_inc_count)
# === 2. Phase A: 为每个文件生成 stub + text ===
# app_deps_buf: 收集所有 App 源文件的直接依赖模块名(空格分隔)
# 供 Phase B+ 的 _BuildReachableSha1Set 作为初始 worklist实现按需编译
# 声明在 if 块外,确保 Phase B+ 和清理代码能访问
APP_DEPS_BUF_SIZE: t.CSizeT = 8192
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 = StubMerger.GetSha1StoreCount()
if store_count_inc_pre > 0:
store_sha1_inc_pre: bytes | t.CPtr = StubMerger.GetSha1StoreArrPtr()
store_rel_inc_pre: bytes | t.CPtr = StubMerger.GetSha1StoreRelArrPtr()
inc_dir_len_pre: t.CSizeT = string.strlen(includes_dir)
deps_filled_pre: int = 0
PHASE_A_INC_MAX_PASSES: t.CInt = 3
for pass_inc_i in range(PHASE_A_INC_MAX_PASSES):
struct_count_before_inc: int = HandlesStruct.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
rel_len_pre: t.CSizeT = string.strlen(inc_rel_pre)
full_path_pre: bytes = stdlib.malloc(inc_dir_len_pre + rel_len_pre + 2)
if full_path_pre is None:
continue
viperlib.snprintf(full_path_pre, inc_dir_len_pre + rel_len_pre + 2,
"%s/%s", includes_dir, inc_rel_pre)
pkg_inc_pre: str = HandlesImports.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:
# 依赖填充只在第一遍执行PopulateIncludesDeps 内部有去重)
if pass_inc_i == 0:
if tr_inc_pre._imported_modules is not None:
if StubMerger.PopulateIncludesDeps(inc_sha1_pre, tr_inc_pre._imported_modules) == 0:
deps_filled_pre = deps_filled_pre + 1
struct_count_after_inc: int = HandlesStruct.get_struct_count()
# 收敛检查:本遍没有新结构体注册 → 所有类已注册
if struct_count_after_inc == struct_count_before_inc:
break
fb_inc_pre: t.CChar | t.CPtr = 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/uniondeclare_only=1不翻译方法体
# 多遍扫描:解决跨文件继承的字母序问题(如 HandlesAnnAssign.py 在 HandlesBase.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 = HandlesStruct.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 = HandlesImports.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: t.CChar | t.CPtr = VLogger.fmt_buf()
if fb is not None:
viperlib.snprintf(fb, 1024, "预注册失败: %s", ent_pre.Path)
VLogger.warning(fb, "PhaseA")
struct_count_after: int = HandlesStruct.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
HandlesExprCall.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'
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:
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 = HandlesImports.compute_package_from_relpath(mb, rel_path_pa)
tr_a: HandlesTranslator.Translator | t.CPtr = BuildPipeline.TranslateFileGetTrans(mb, ent.Path, ent.Sha1, pkg_pa)
if tr_a is None:
continue
# dump stub IR (declarations only)
stub_buf_a: bytes = stdlib.malloc(PHASE_A_IR_SIZE)
if stub_buf_a is None:
continue
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 = StubMerger._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:
continue
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 = StubMerger._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 = StubMerger._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)
# 收集 _imported_modules 到 app_deps_buf供 Phase B+ 按需编译)
if tr_a._imported_modules is not None and app_deps_buf is not None:
im_len_a: t.CSizeT = string.strlen(tr_a._imported_modules)
if im_len_a > 0 and app_deps_len + im_len_a + 1 < APP_DEPS_BUF_SIZE:
if app_deps_len > 0:
app_deps_buf[app_deps_len] = ' '
app_deps_len = app_deps_len + 1
string.strcpy(app_deps_buf + app_deps_len, tr_a._imported_modules)
app_deps_len = app_deps_len + im_len_a
app_deps_buf[app_deps_len] = '\0'
if 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
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: t.CChar | t.CPtr = 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 = StubMerger.BuildCombinedIR(temp_dir, ent.Sha1, combined_ir, COMBINED_IR_SIZE)
if combined_len == 0:
fb: t.CChar | t.CPtr = 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
# 编译为 .obj
cret: int = BuildPipeline.compile_module_to_obj(
combined_ir, combined_len, temp_dir, output_dir, ent.Sha1,
cc_cmd, cc_flags)
stdlib.free(combined_ir)
if cret != 0:
fb: t.CChar | t.CPtr = VLogger.fmt_buf()
if fb is not None:
viperlib.snprintf(fb, 1024, "llc 编译失败,终止编译: %s", ent.Path)
VLogger.error(fb, "PhaseB")
sys.exit(1)
compiled_count += 1
# 显示编译完成的文件
fb_comp: t.CChar | t.CPtr = VLogger.fmt_buf()
if fb_comp is not None:
viperlib.snprintf(fb_comp, 1024, "[%d/%d] 编译完成: %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 = StubMerger._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.warning("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.warning(".obj 路径缓冲区不足", "PhaseB")
stdlib.free(obj_path_sliced)
fb: t.CChar | t.CPtr = 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: t.CChar | t.CPtr = VLogger.fmt_buf()
if fb_pbp_enter is not None:
viperlib.snprintf(fb_pbp_enter, 1024,
"PhaseB+ 入口: store_count=%d includes_binary_dir=%s",
StubMerger.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 = StubMerger._BuildReachableSha1Set(
mb, source_dir, temp_dir, reachable_set, app_deps_buf)
fb_reach: t.CChar | t.CPtr = 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/模块名/rel_path 数组(直接访问器,避免 box 解引用问题)
store_count_mi: int = StubMerger.GetSha1StoreCount()
if store_count_mi > 0:
store_sha1_arr_mi: bytes | t.CPtr = StubMerger.GetSha1StoreArrPtr()
store_mod_arr_mi: bytes | t.CPtr = StubMerger.GetSha1StoreModArrPtr()
store_rel_arr_mi: bytes | t.CPtr = StubMerger.GetSha1StoreRelArrPtr()
for si_mi in range(store_count_mi):
# 获取当前条目的 SHA1 和 rel_path
inc_sha1_mi: str = store_sha1_arr_mi + t.CSizeT(si_mi) * 17
inc_rel_mi: str = store_rel_arr_mi + t.CSizeT(si_mi) * StubMerger.MAX_REL_PATH_LEN
# 按需编译过滤:只处理 reachable_set 中的 includes
# 永不回退reachable_count > 0 时严格按可达集合过滤
if reachable_count > 0:
if StubMerger._is_in_sha1_set(inc_sha1_mi, reachable_set, reachable_count) == 0:
continue
# 检查 .obj 是否已存在于 includes.binary切片子目录
ibd_len_mi: t.CSizeT = string.strlen(includes_binary_dir)
check_pat_mi: str = StubMerger._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_dir}/{rel_path}
rel_path_len_mi: t.CSizeT = string.strlen(inc_rel_mi)
inc_dir_len_mi: t.CSizeT = string.strlen(includes_dir)
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)
continue
viperlib.snprintf(src_fp_mi, inc_dir_len_mi + 1 + rel_path_len_mi + 1,
"%s/%s", includes_dir, inc_rel_mi)
# 检查是否为声明文件(只有 declare 没有实质 define
# 判断方法: text.ll 中若有混淆函数 define含 @")则为实现文件
is_decl_mi: int = -1
tpath_mi: str = StubMerger._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)
if tbuf_mi is not None:
tbr_mi: t.CInt64T = tf_mi.read_all(tbuf_mi, StubMerger.STUB_READ_BUF_SIZE)
if 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 == 1text.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
# 尝试 BuildCombinedIRstub/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 = StubMerger.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 = HandlesImports.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 = StubMerger._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 = StubMerger._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 = StubMerger.BuildCombinedIR(temp_dir, inc_sha1_mi, inc_combined_mi, COMBINED_IR_SIZE)
if inc_combined_len_mi == 0:
# 诊断:检查 stub.ll/text.ll 是否存在
diag_stub: str = StubMerger._sliced_path(temp_dir, td_len_mi, inc_sha1_mi, "stub.ll")
diag_text: str = StubMerger._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: t.CChar | t.CPtr = 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: t.CChar | t.CPtr = 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: t.CChar | t.CPtr = 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 = StubMerger._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'
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)
fb: t.CChar | t.CPtr = VLogger.fmt_buf()
if fb is not None:
viperlib.snprintf(fb, 1024, "编译缺失 includes: %d 个", inc_compiled)
VLogger.info(fb, "PhaseB+")
# 释放 app_deps_bufPhase 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
# 构造最终 .obj 路径列表main_obj_path 在前,其他 .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
if obj_paths[0] != '\0':
final_obj_paths[fop_pos] = ' '
fop_pos += 1
# 追加其他 .obj
if obj_paths[0] != '\0':
string.strcpy(final_obj_paths + fop_pos, obj_paths)
fop_pos += string.strlen(obj_paths)
final_obj_paths[fop_pos] = '\0'
obj_paths_len: t.CSizeT = fop_pos
# 暂时禁用按图链接:始终全量链接 includes.binary 中的 .obj
# 按图链接在 --clean 后 deps.txt 缺失时无法正确工作(跨包传递依赖丢失)
# 按图编译Phase B+)仍保留:只编译 reachable_set 中的缺失 includes
# TODO: 待 deps.txt 生成机制完善后(如 Phase A 翻译所有 includes 的 imports重新启用按图链接
# 释放 reachable_setPhase B+ 已使用完毕)
if reachable_set is not None:
stdlib.free(reachable_set)
reachable_set = None
# 始终传 includes_binary_dir 给 link_objs_to_exe由其全量收集 .obj
lret: int = BuildPipeline.link_objs_to_exe(
final_obj_paths, obj_paths_len,
linker_cmd, linker_flags, exe_path,
includes_binary_dir)
if lret == 0:
fb: t.CChar | t.CPtr = 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: t.CChar | t.CPtr = 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: t.CChar | t.CPtr = 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