feat(tpc): includes 只认项目配置;ASan 改为显式链接 clang64 运行库

includes 目录不再回退仓库硬编码 TransPyCRoot/includes:Phase2Translator 新增 PROJECT_INCLUDE_DIRS(INCLUDE_DIRS 置空以兼容旧引用),Config.py 从 project.vpj 的 includes 同步;HandlesImports 的模块搜索、TypeAnnotationResolver._builtins_include_dirs 一律只读项目目录。

ASan:mingw64 clang 没有 x86_64-w64-windows-gnu 目标的 ASan runtime,新增 _FindAsanRuntimeDir 定位 clang64 运行库并按 -l: 显式链接(asan_dynamic-x86_64.dll.a + runtime_thunk),定位失败即报错退出。HandlesExprFormat:i32 不再写 'l' 长度修饰符(LP64 下 printf 会按 64 位读实参)。
This commit is contained in:
TermiNexus committed 2026-09-11 22:47:21 +08:00
1 parent ab3873b038
commit cc2f5d34eb
122 files changed
+89 -31

No files matched your search

+12 -10
View File
@@ -147,16 +147,16 @@ def main() -> None:
parser.add_argument('--run', action='store_true', help='编译成功后立即执行生成的可执行文件')
parser.add_argument('--rebuild-includes', action='store_true', help='删除 includes.binary 预编译缓存并重新编译所有 includes')
parser.add_argument('--clear-cache', action='store_true', help='清除 .transpyc_cache 全局缓存(.vpi/.stub.ll/.doc.json)')
parser.add_argument('--asan', action='store_true', help='ASan 模式:对全部 .ll 做 opt -passes=asan 插桩并链接 -fsanitize=address(需 clang64 环境)')
parser.add_argument('--asan', action='store_true', help='ASan 模式:对全部 .ll 做 opt -passes=asan 插桩,并显式链接 clang64 的 ASan 运行库(mingw64 的 clang 没有 x86_64-w64-windows-gnu 目标的 ASan runtime,不能用 -fsanitize=address)')
parser.add_argument('--nowarn-register', action='store_true', help='抑制"外部函数返回类型未登记"类 WARN 输出(透传给自举编译的 TransPyV.exe)')
args: argparse.Namespace = parser.parse_args()
# ASan 模式(--asan):直接经 args 传给 Phase2Translator(compile_ll_to_obj
# 先 opt -passes=asan 插桩,链接追加 -fsanitize=address)。不再用 TPC_ASAN
# 环境变量透传——环境变量在 worker 子进程/外部调用链下不可靠。
# 先 opt -passes=asan 插桩,链接显式 -l: clang64 的 libclang_rt.asan_dynamic*)。
# 不再用 TPC_ASAN 环境变量透传——环境变量在 worker 子进程/外部调用链下不可靠。
if args.asan:
_vlog().info('ASan 模式已启用(opt -passes=asan + -fsanitize=address)', 'asan')
_vlog().info('ASan 模式已启用(opt -passes=asan + clang64 libclang_rt.asan_dynamic*)', 'asan')
project_file: str | None = args.project
if not project_file:
@@ -184,6 +184,13 @@ def main() -> None:
cc_flags: list[str] = resolved.get('compiler', {}).get('flags', ['-filetype=obj'])
slice_level: int = resolved.get('options', {}).get('slice_level', 3)
project_include_dirs: list[str] = resolved.get('includes', [])
# includes 只认项目配置:同步到 Phase2Translator.PROJECT_INCLUDE_DIRS,
# 供 builtins.vp 扫描等静态路径在 phase1/2 全程使用(禁止仓库硬编码回退)
try:
from lib.Projectrans.Phase2Translator import Phase2Translator
Phase2Translator.PROJECT_INCLUDE_DIRS = [os.path.abspath(d) for d in project_include_dirs if os.path.isdir(os.path.join(project_dir, d)) or os.path.isdir(d)]
except Exception:
pass
target_triple: str | None = resolved.get('target', {}).get('triple')
target_datalayout: str | None = resolved.get('target', {}).get('datalayout')
@@ -250,11 +257,6 @@ def main() -> None:
_vlog().error("阶段一需要指定源目录,不支持文件列表")
sys.exit(1)
all_include_dirs: list[str] = list(project_include_dirs) if project_include_dirs else []
for d in Phase2Translator.INCLUDE_DIRS:
if os.path.isdir(d):
d = os.path.abspath(d)
if d not in [os.path.abspath(x) for x in all_include_dirs]:
all_include_dirs.append(d)
gen: Phase1Generator = Phase1Generator(src, temp_dir, include_dirs=all_include_dirs, target_triple=target_triple, target_datalayout=target_datalayout)
gen.run()
save_sha1_map(temp_dir, gen.sha1_map)
@@ -286,7 +288,7 @@ def main() -> None:
force_recompile_includes: bool = bool(resolved.get('options', {}).get('force_recompile_includes', False))
if args.rebuild_includes:
force_recompile_includes = True
all_inc_dirs: list[str] = list(project_include_dirs) + list(Phase2Translator.INCLUDE_DIRS)
all_inc_dirs: list[str] = list(project_include_dirs)
seen_dirs: set[str] = set()
for inc_dir in all_inc_dirs:
abs_inc: str = os.path.abspath(inc_dir)
+66 -8
View File
@@ -37,6 +37,40 @@ def _CachedIsFile(path: str) -> bool:
return r
def _FindAsanRuntimeDir() -> str | None:
"""定位 clang64 的 ASan 运行库目录(按 LLVM 大版本取最新的那个)
mingw64 的 clang 只为 x86_64-w64-windows-gnu 目标提供 builtins / fuzzer /
profile / orc 运行库,**没有 ASan runtime**(<resource-dir>/lib/
x86_64-w64-windows-gnu/ 是空目录),所以不能靠 `-fsanitize=address` 让驱动
自动去找 —— 那只会得到
"could not open .../libclang_rt.asan_dynamic.dll.a: No such file or directory"。
clang64(MSVC ABI) 那边才有 libclang_rt.asan_dynamic-x86_64.dll.a 与
libclang_rt.asan_dynamic_runtime_thunk-x86_64.a,只能借用这两份。
Returns:
形如 D:\\msys64\\clang64\\lib\\clang\\22\\lib\\windows 的目录;找不到返回 None
"""
root = r'D:\msys64\clang64\lib\clang'
if not os.path.isdir(root):
return None
found: list[str] = []
for name in os.listdir(root):
cand = os.path.join(root, name, 'lib', 'windows')
if os.path.isfile(os.path.join(cand, 'libclang_rt.asan_dynamic-x86_64.dll.a')):
found.append(cand)
if not found:
return None
def _Ver(path: str) -> int:
# 目录名是 LLVM 大版本号(如 "22")——按数值比,否则 "9" 会排在 "22" 之后
v = path.split(os.sep)[-3]
return int(v) if v.isdigit() else 0
found.sort(key=_Ver)
return found[-1]
def _PickWorkerCount(n_files: int, per_worker_mb: int = 350) -> int:
"""按可用物理内存动态限制并行 worker 数,避免多进程编译内存峰值 OOM。
@@ -565,9 +599,10 @@ class Phase2Translator:
INCLUDE_LIB_EXTENSIONS: tuple[str, ...] = ('.dll', '.ll', '.so', '.o', '.a', '.lib')
INCLUDE_SRC_EXTENSIONS: tuple[str, ...] = ('.c', '.cpp', '.cc', '.cxx', '.m', '.mm')
INCLUDE_DIRS: list[str] = [
os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), 'includes'),
]
# includes 只认项目配置(project.vpj includes),禁止仓库硬编码回退。
# INCLUDE_DIRS 保留为空以兼容旧引用;实际目录由 __init__ 写入 PROJECT_INCLUDE_DIRS。
INCLUDE_DIRS: list[str] = []
PROJECT_INCLUDE_DIRS: list[str] = []
# include 源文件 sha1/存在性缓存(_build_current_ModuleSha1Map):
# 编译期间 include .vp/.py 源文件不变,避免每文件重复 isfile + open +
# compute_sha1(PROF 显示 9306 isfile + 4653 open + TextIOWrapper.read
@@ -595,13 +630,16 @@ class Phase2Translator:
# ASan 模式:由 Config 的 --asan 参数直传(不再用 TPC_ASAN 环境变量,
# 环境变量在 worker 子进程/外部调用链下不可靠)。
# 检测 stdlib 堆/栈/全局越界与垃圾指针(MemBuddy pool 内部由 canary 覆盖)。
# 注意:mingw64 的 clang 不提供 ASan runtime(libclang_rt.asan 缺失),
# 必须使用 clang64 环境(自带 asan_dynamic runtime),PATH 前置 clang64/bin。
# 注意:mingw64 的 clang 不提供 x86_64-w64-windows-gnu 目标的 ASan runtime,
# 只能借用 clang64(MSVC ABI) 的那两份:PATH 前置 clang64/bin(运行期 DLL),
# 链接时显式 -l: 那两份 .a/.dll.a(见 _FindAsanRuntimeDir 的说明)。
self.asan_mode = asan_mode
self.asan_rt_dir: str | None = None
if self.asan_mode:
_clang64_bin = r'D:\msys64\clang64\bin'
if os.path.isdir(_clang64_bin):
os.environ['PATH'] = _clang64_bin + os.pathsep + os.environ.get('PATH', '')
self.asan_rt_dir = _FindAsanRuntimeDir()
self.slice_level = 3
self.sha1_map: dict[str, str] = {}
self.sig_files: dict[str, str] = {}
@@ -652,7 +690,7 @@ class Phase2Translator:
self._shared_cross_module_vtable: set[str] = set()
self._shared_class_methods: dict[str, list[str]] = {}
self._shared_cross_module_novtable: set[str] = set()
default_dirs = [d for d in self.INCLUDE_DIRS if os.path.isdir(d)]
default_dirs: list[str] = []
if include_dirs:
seen = set()
self.include_dirs = []
@@ -663,6 +701,8 @@ class Phase2Translator:
seen.add(d)
else:
self.include_dirs = default_dirs
# 严格:只认项目 includes;供 builtins.vp 扫描等静态路径使用
Phase2Translator.PROJECT_INCLUDE_DIRS = list(self.include_dirs)
self._LoadSha1Map()
def _LoadSha1Map(self) -> None:
@@ -1486,6 +1526,10 @@ class Phase2Translator:
trans.translator._global_function_default_args = self.function_default_args
trans.translator._global_function_param_names = self.function_param_names
trans.translator._temp_dir = self.temp_dir
# includes 只认 project.vpj 配置的 include 目录(不再回退仓库 TransPyCRoot/includes):
# 目录重组/缺失时避免声明解析从仓库或 sig 回退路径产生错误时序(memhub 未注册→i32)
_proj_inc = [os.path.abspath(d) for d in self.include_dirs if os.path.isdir(d)]
trans.translator.LibraryPaths = _proj_inc if _proj_inc else ['.']
trans.translator._shared_doc_meta = self._shared_doc_meta
# 传递共享 vtable 状态
trans.translator._shared_class_vtable = self._shared_class_vtable
@@ -3655,9 +3699,23 @@ SECTIONS {
# 其他所有标志(包括 -Wl, 选项)放在目标文件之前
non_lib_flags.append(flag)
i += 1
# ASan 模式:链接 libasan(配合各 .ll 的 opt -passes=asan 插桩)
# ASan 模式:链接 ASan 运行库(配合各 .ll 的 opt -passes=asan 插桩)
# 不能传 -fsanitize=address:那会让 clang++ 驱动去找
# <resource-dir>/lib/x86_64-w64-windows-gnu/libclang_rt.asan_dynamic.dll.a,
# 而该目录是空的(上游 LLVM 不为这个 triple 构建 ASan runtime),
# 直接报 "could not open ... No such file or directory"。
# 改为显式链接 clang64 的那两份(与 TPV / README §8.1 同口径)。
# 它们是库,必须排在目标文件之后,所以追加到 lib_flags 而不是 non_lib_flags。
if self.asan_mode:
non_lib_flags.append('-fsanitize=address')
if self.asan_rt_dir is None:
_vlog().error(
"ASan 模式找不到 clang64 的 ASan 运行库目录 "
r"(D:\msys64\clang64\lib\clang\<ver>\lib\windows)\n"
" 请确认 msys2 的 mingw-w64-clang-x86_64-compiler-rt 已安装。")
sys.exit(1)
lib_flags.append('-L' + self.asan_rt_dir)
lib_flags.append('-l:libclang_rt.asan_dynamic-x86_64.dll.a')
lib_flags.append('-l:libclang_rt.asan_dynamic_runtime_thunk-x86_64.a')
cmd = [self.linker_cmd] + non_lib_flags + ['-o', link_output] + all_link_files + lib_flags
_vlog().info(f" 执行: {' '.join(cmd)}")
result = subprocess.run(
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.