1727 lines
92 KiB
Plaintext
1727 lines
92 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.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: rawptr[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: rawptr[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: rawptr[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: rawptr[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: rawptr[memhub.MemBuddy] = CompilerFactory.factory.PermPool
|
||
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: sizet = 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:
|
||
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: rawptr[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: VLogger.Logger | t.CPtr,
|
||
args: argparse.ParsedArgs | t.CPtr,
|
||
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 传 None:与 TranslateFileGetTrans 后续覆盖写的内容一致)
|
||
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 | t.CPtr = 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 | 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
|
||
VLogger.error(
|
||
f"[Phase A-pre-vpi] {ent_vp2.Path} | AST 解析失败"
|
||
f"(第 {ast.get_parse_err_line()} 行第 {ast.get_parse_err_col()} 列: "
|
||
f"{ast.get_parse_err_msg()})", "PhaseA")
|
||
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, 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: 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: 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:
|
||
# 记录本文件依赖(供收敛失败依赖图诊断;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)
|
||
tr_pre: HandlesTranslator.Translator | t.CPtr = BuildPipeline.TranslateFileGetTrans(mb, ent_pre.Path, ent_pre.Sha1, pkg_pre, 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: HandlesTranslator.Translator | t.CPtr = 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: BuildPipeline.LlcTask | t.CPtr = (BuildPipeline.LlcTask | t.CPtr)(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: BuildPipeline.LlcTask | t.CPtr = (BuildPipeline.LlcTask | t.CPtr)(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()
|
||
# 诊断:_BuildIncludesSha1Map 复制的条目数(PhaseB+ 循环执行前提)
|
||
VLogger.debug(f"诊断 store_count_mi={store_count_mi}", "PhaseB+")
|
||
if store_count_mi > 0:
|
||
|
||
# 拓扑排序可达 includes:被依赖模块先完整翻译。跨模块 vtable 生成
|
||
# 需要依赖模块的函数 TypeStr 已注册到全局表(如 astaux 继承 base 的 AST)
|
||
topo_idx: sizet | t.CPtr = (sizet | 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)
|
||
|
||
# 诊断:拓扑排序条数(0 则循环不执行)
|
||
VLogger.debug(f"诊断 topo_count={topo_count} reachable_count={reachable_count}", "PhaseB+")
|
||
|
||
# 诊断:dump reachable_set 内容(验证 BFS 写入的 sha1 是否有效)
|
||
ri_dbg: int = 0
|
||
for ri_dbg in range(reachable_count):
|
||
rsha1_dbg: str = reachable_set + sizet(ri_dbg) * 17
|
||
VLogger.debug(f"诊断 reachable[{ri_dbg}]={rsha1_dbg}", "PhaseB+")
|
||
|
||
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 不存在,进入 is_decl 判定
|
||
VLogger.debug(f"诊断 循环 si={si_mi} ti={ti} sha1={inc_sha1_mi} obj_exists={obj_exists_mi}", "PhaseB+")
|
||
|
||
# .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 判定结果
|
||
VLogger.debug(f"诊断 is_decl={is_decl_mi} src={src_fp_mi}", "PhaseB+")
|
||
# 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: HandlesTranslator.Translator | t.CPtr = 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")
|
||
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: subprocess.CompletedProcess | t.CPtr = 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
|