fix(sizeof,parse): 尺寸链去静默兜底,解析报错可定位,补 C 解析器缺口
StructGen: _get_struct_size/_get_align 去 opaque→8/ptr_size、未知→4 兜底并补 VectorType;LiteralStructType.fields→.elements(llvmlite 无 fields,必然 AttributeError) HandlesClassDef: union 载荷尺寸经不存在的 ir.DataLayout 静默退化成 8 字节;HandlesExprCall/ExprBuiltin: sizeof 各静默 0 分支改 fail-fast,数组元素 sizeof 统一走 _get_struct_size HandlesFor: nonlocal struct/array 堆分配不再退化成 malloc(8) vast/vast_c: 补一元 * / & 解析,装饰器后 extern/export 分派修复,C 侧报错带 token 与行号;StubGen.Converter: 宏展开丢尾随空行修 .pyi 缩进非法 其余为 rawptr 迁移 / fail-fast 口径等历史改动一并入库
This commit is contained in:
1 parent
53a345a59a
commit
c83fc752c6
53 files changed
+6238
-887
No files matched your search
@@ -149,7 +149,7 @@ def main() -> None:
|
||||
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 插桩,并显式链接 clang64 的 ASan 运行库(mingw64 的 clang 没有 x86_64-w64-windows-gnu 目标的 ASan runtime,不能用 -fsanitize=address)')
|
||||
parser.add_argument('--dwarf', action='store_true', help='函数级 DWARF 调试元数据:每模块一份 DICompileUnit/DIFile,每个 define 一份 DISubprogram 并挂 !dbg,函数入口第一条指令挂 DILocation。使 ASan/llvm-symbolizer 能把崩溃地址反查为 "<文件>.vp:函数起始行"(默认关闭;--asan 时自动开启)。开启时强制重译 App 模块(不复用不含 !llvm.dbg.cu 的旧 .ll/.obj),.ll/.obj 体积与编译时间略增')
|
||||
parser.add_argument('--ctrlc-stack', action='store_true', help='Ctrl+C 调用栈打印(仅 Windows/PE):链接完成后用 llvm-dwarfdump 把最终 exe 的 .debug_line/.debug_info 导出为 "<exe>.stackmap" 地址→源行旁文件(小端定长格式),供 exe 内运行时在 Ctrl+C/Ctrl+Break 时把 CRT 到当前的返回地址逐帧翻译为 "<文件>.vp:行/函数"。会自动开启 DWARF(同 --asan,否则行表为空);找不到 llvm-dwarfdump / 调用失败 / 解析不到任何行记录 / 目标不是 Windows PE 时直接报错终止编译')
|
||||
parser.add_argument('--traceback', action='store_true', help='Ctrl+C 调用栈打印(仅 Windows/PE):链接完成后用 llvm-dwarfdump 把最终 exe 的 .debug_line/.debug_info 导出为 "<exe>.stackmap" 地址→源行旁文件(小端定长格式),供 exe 内运行时在 Ctrl+C/Ctrl+Break 时把 CRT 到当前的返回地址逐帧翻译为 "<文件>.vp:行/函数"。会自动开启 DWARF(同 --asan,否则行表为空);找不到 llvm-dwarfdump / 调用失败 / 解析不到任何行记录 / 目标不是 Windows PE 时直接报错终止编译')
|
||||
parser.add_argument('--nowarn-register', action='store_true', help='抑制"外部函数返回类型未登记"类 WARN 输出(透传给自举编译的 TransPyV.exe)')
|
||||
parser.add_argument('--hotspot-sites', nargs='?', const='', default=None, help='调用点热点统计:为每个匹配白名单的直接 call 点插入计数器,运行时 Ctrl+C 各模块按真实次数降序写入 <path>(默认为 hotspot_sites.txt);同时为每个函数定义加计时器,按自身耗时(self)降序写入同目录 hotspot_time.txt,行为 `<函数名> <self> <inc> <进入次数>`(单位 tick,x86_64 上为序列化 lfence; rdtsc);self 已扣除被插桩子调用的 inc,但不含未插桩被调方(间接调用、strcmp/memcpy 等无定义外部函数),它们计入调用者的 self')
|
||||
parser.add_argument('--hotspot-callees', default=None, help='--hotspot-sites 的白名单被调函数基名(逗号分隔),默认内存/字符串族 strcmp,strncmp,strchr,strlen,strstr,memcpy,memset,memmove,strcpy,strcat')
|
||||
@@ -163,20 +163,20 @@ def main() -> None:
|
||||
if args.asan:
|
||||
_vlog().info('ASan 模式已启用(opt -passes=asan + clang64 libclang_rt.asan_dynamic*)', 'asan')
|
||||
|
||||
# Ctrl+C 调用栈(--ctrlc-stack):链接后用 llvm-dwarfdump 导出 <exe>.stackmap。
|
||||
# Ctrl+C 调用栈(--traceback):链接后用 llvm-dwarfdump 导出 <exe>.stackmap。
|
||||
# 不在此处做工具/平台校验——真正的 fail-fast 落在链接成功后的旁文件导出步骤
|
||||
# (那里才有最终 exe 路径可判 PE)。此处仅提示已启用。
|
||||
if args.ctrlc_stack:
|
||||
_vlog().info('Ctrl+C 调用栈模式已启用(链接后用 llvm-dwarfdump 导出 <exe>.stackmap)', 'ctrlc-stack')
|
||||
if args.traceback:
|
||||
_vlog().info('Ctrl+C 调用栈模式已启用(链接后用 llvm-dwarfdump 导出 <exe>.stackmap)', 'traceback')
|
||||
|
||||
# 函数级 DWARF 调试元数据(--dwarf):默认关闭(硬要求——普通构建不承担
|
||||
# 任何风险,生成的 .ll 与改动前逐字节一致)。--asan 时自动开启:ASan 报告
|
||||
# 本身就需要行号才能定位,且 ASan 构建已是显式选择的非默认路径。
|
||||
# --ctrlc-stack 同理自动开启:旁文件的"地址→源行"完全来自 DWARF 行表,
|
||||
# --traceback 同理自动开启:旁文件的"地址→源行"完全来自 DWARF 行表,
|
||||
# 没有 DI 元数据时 .debug_line 为空、旁文件必为空。
|
||||
dwarf_enabled: bool = bool(args.dwarf or args.asan or args.ctrlc_stack)
|
||||
dwarf_enabled: bool = bool(args.dwarf or args.asan or args.traceback)
|
||||
if dwarf_enabled:
|
||||
_auto_src: str = '--asan' if args.asan else ('--ctrlc-stack' if args.ctrlc_stack else '')
|
||||
_auto_src: str = '--asan' if args.asan else ('--traceback' if args.traceback else '')
|
||||
_vlog().info(
|
||||
'函数级 DWARF 调试元数据已启用:每个 .vp 模块一份 DICompileUnit + 每函数 DISubprogram/DILocation'
|
||||
+ (f'(由 {_auto_src} 自动开启)' if _auto_src and not args.dwarf else ''),
|
||||
@@ -349,7 +349,7 @@ def main() -> None:
|
||||
except:
|
||||
pass
|
||||
_vlog().info(f"includes.binary: 已清空 {precompiled_dir} ({cleared} 个文件)")
|
||||
trans: Phase2Translator = Phase2Translator(src, temp_dir, output_dir, compile_cmd=cc_cmd, compile_flags=cc_flags, linker_cmd=linker_cmd, linker_flags=linker_flags, linker_output=linker_output, static_libs=static_libs, dynamic_libs=dynamic_libs, include_dirs=project_include_dirs, target_triple=target_triple, target_datalayout=target_datalayout, force_recompile_includes=force_recompile_includes, asan_mode=args.asan, dwarf_enabled=dwarf_enabled, ctrlc_stack_enabled=args.ctrlc_stack)
|
||||
trans: Phase2Translator = Phase2Translator(src, temp_dir, output_dir, compile_cmd=cc_cmd, compile_flags=cc_flags, linker_cmd=linker_cmd, linker_flags=linker_flags, linker_output=linker_output, static_libs=static_libs, dynamic_libs=dynamic_libs, include_dirs=project_include_dirs, target_triple=target_triple, target_datalayout=target_datalayout, force_recompile_includes=force_recompile_includes, asan_mode=args.asan, dwarf_enabled=dwarf_enabled, traceback_enabled=args.traceback)
|
||||
trans.sha1_map = Load_sha1_map(temp_dir)
|
||||
trans.slice_level = slice_level
|
||||
# 调用点热点统计与每函数计时(--hotspot-sites):None=未启用(零影响)
|
||||
|
||||
@@ -10,7 +10,8 @@ from lib.includes import t
|
||||
from lib.includes.t import CTypeRegistry
|
||||
from lib.core.SymbolUtils import (
|
||||
IsListAnnotation, AnnotationContainsName, IsCPtrSubscriptNode, IsCPtrNode,
|
||||
IsRawPtrSubscriptNode, IsPtrSubscriptNode, ResolveGenericTypeArgStr)
|
||||
IsRawPtrSubscriptNode, IsPtrSubscriptNode, ResolveGenericTypeArgStr,
|
||||
EnumClassIntWidth, ClassHasFuncLevelCVTable)
|
||||
|
||||
|
||||
class DeclarationGenerator:
|
||||
@@ -55,7 +56,9 @@ class DeclarationGenerator:
|
||||
t.configure_platform(self.target_triple)
|
||||
|
||||
def generate(self, pyi_content: str, src_path: str) -> str:
|
||||
tree: ast.Module = ast.parse(pyi_content)
|
||||
# 传 src_path:解析失败时报错/回退诊断才带得上来源(否则 vast 记为 <unknown>,
|
||||
# 既无法定位文件,也无法知道是哪份 .pyi 出问题)
|
||||
tree: ast.Module = ast.parse(pyi_content, src_path)
|
||||
|
||||
self._DefineConstants: dict[str, int | str] = {}
|
||||
self._pyi_tree = tree
|
||||
@@ -255,9 +258,17 @@ class DeclarationGenerator:
|
||||
if isinstance(elt, ast.Subscript) and isinstance(elt.value, ast.Attribute) and isinstance(elt.value.value, ast.Name) and elt.value.value.id == 't' and elt.value.attr == 'Callable':
|
||||
return None
|
||||
VarType: str = self._get_type_str(node.annotation)
|
||||
if VarType.startswith('[0 x ') and node.value and isinstance(node.value, ast.List):
|
||||
if VarType.startswith('[0 x ') and node.value:
|
||||
elem_type: str = VarType[5:-1]
|
||||
actual_count: int = len(node.value.elts)
|
||||
actual_count: int = 0
|
||||
if isinstance(node.value, ast.List):
|
||||
actual_count = len(node.value.elts)
|
||||
elif isinstance(node.value, ast.Constant) and isinstance(node.value.value, str):
|
||||
# 字符串字面量:与 HandlesBase.ParseArrayCount(:731-732) / HandlesAssign
|
||||
# 全局路径同口径(len(str)+1,含结尾 NUL)。此前只修 List 值 → CArray
|
||||
# 字符串全局的 stub 声明停留在 [0 x i8],与定义端 [N x i8] 长度不一致
|
||||
# (跨模块 type mismatch / llc 拒绝)。
|
||||
actual_count = len(node.value.value) + 1
|
||||
if actual_count > 0:
|
||||
VarType = f'[{actual_count} x {elem_type}]'
|
||||
return f'@{var_name} = external global {VarType}'
|
||||
@@ -350,6 +361,10 @@ class DeclarationGenerator:
|
||||
if is_enum:
|
||||
enum_values: dict[str, int] = {}
|
||||
next_value: int = 0
|
||||
# 枚举存储宽度 = 最宽成员类型(与定义端 HandlesClassDef / 跨模块读取端
|
||||
# 同一规则源 EnumClassIntWidth;此处无 SymbolTable,走 t 类名 / builtins /
|
||||
# 库 typedef 的表)。声明端的全局类型必须与定义端运行时常量同宽(ABI)。
|
||||
enum_width: int = EnumClassIntWidth(node.body)
|
||||
for item in node.body:
|
||||
if isinstance(item, ast.AnnAssign) and isinstance(item.target, ast.Name):
|
||||
var_name: str = item.target.id
|
||||
@@ -367,7 +382,7 @@ class DeclarationGenerator:
|
||||
enum_values[var_name] = next_value
|
||||
next_value += 1
|
||||
for var_name, value in enum_values.items():
|
||||
decls.append(f'@__config_{class_name}_{var_name} = external global i32')
|
||||
decls.append(f'@__config_{class_name}_{var_name} = external global i{enum_width}')
|
||||
return decls
|
||||
if is_renum:
|
||||
# REnum 布局: { i32 __tag, <max_variant_payload> }
|
||||
@@ -473,6 +488,14 @@ class DeclarationGenerator:
|
||||
# (@t.NoVTable 标记的类及其子类不使用 vtable,字段展平嵌入)
|
||||
if has_parent_class and not is_cvtable and not is_novtable and not parent_is_novtable:
|
||||
is_cvtable = True
|
||||
# 方法级 virtual(`virtual def m(...)` ⇒ 方法装饰器 @t.CVTable)触发类级 vtable:
|
||||
# 与 HandlesClassDef._resolve_class_flags / TPV _detect_vtable_status 的
|
||||
# `_has_any_func_cvtable` 分支逐条对齐(类级 NoVTable / 无真实父类)。
|
||||
# 必须在此同步:定义模块按 IsCVTable 在 struct 首部放 vptr,若 stub 不同步,
|
||||
# 跨模块使用该类的 struct 布局会比定义模块少一个字段 ⇒ 字段整体错位。
|
||||
if not is_cvtable and ClassHasFuncLevelCVTable(node):
|
||||
if is_novtable or not has_parent_class:
|
||||
is_cvtable = True
|
||||
base_has_vtable: bool = False
|
||||
for base in node.bases:
|
||||
base_name: str | None = None
|
||||
@@ -847,6 +870,7 @@ class DeclarationGenerator:
|
||||
if has_methods and not is_cpython_object:
|
||||
is_cpython_object = True
|
||||
base_has_vtable: bool = False
|
||||
has_real_parent: bool = False
|
||||
for base in node.bases:
|
||||
base_name: str | None = None
|
||||
if isinstance(base, ast.Attribute):
|
||||
@@ -859,6 +883,7 @@ class DeclarationGenerator:
|
||||
elif isinstance(base.value, ast.Name):
|
||||
base_name = base.value.id
|
||||
if base_name and not self._is_marker_base(base_name):
|
||||
has_real_parent = True
|
||||
_base_node: ast.ClassDef | None = None
|
||||
for n in ast.iter_child_nodes(self._pyi_tree):
|
||||
if isinstance(n, ast.ClassDef) and n.name == base_name:
|
||||
@@ -874,6 +899,11 @@ class DeclarationGenerator:
|
||||
base_has_vtable = True
|
||||
if base_has_vtable:
|
||||
break
|
||||
# 方法级 virtual 触发类级 vtable(与上方 _generate_class_decl 同一判据,
|
||||
# 保持在同一个类里两处口径一致;详见该处注释)
|
||||
if not is_cvtable and not base_has_vtable and ClassHasFuncLevelCVTable(node):
|
||||
if is_novtable or not has_real_parent:
|
||||
is_cvtable = True
|
||||
return has_methods and is_cvtable and not base_has_vtable and not is_novtable
|
||||
|
||||
def _FlattenInheritedDecl(self, cls_name: str, seen_cls: set, member_types: list[str],
|
||||
@@ -1156,7 +1186,7 @@ class DeclarationGenerator:
|
||||
return _dg_llvm
|
||||
_dg_node: ast.AST | None = None
|
||||
try:
|
||||
_dg_node = ast.parse(_dg_bv, mode='eval').body
|
||||
_dg_node = ast.parse(_dg_bv, '<DeclarationGenerator:type_expr>', mode='eval').body
|
||||
except Exception:
|
||||
_dg_node = None
|
||||
if _dg_node is not None:
|
||||
@@ -1291,11 +1321,13 @@ class DeclarationGenerator:
|
||||
return f'[{self._get_const_int(annotation.slice)} x i8]'
|
||||
if isinstance(annotation.slice, ast.Constant) and isinstance(annotation.slice.value, int):
|
||||
return f'[{annotation.slice.value} x {base}]'
|
||||
# 处理 t.CArray[elem_type, count] / rawarray[elem_type, count] 注解 → [count x elem_type]
|
||||
# 支持 t.CArray (ast.Attribute)、CArray/rawarray (ast.Name) 三种写法
|
||||
# 处理 t.CArray[elem_type, count] / rawarray[elem_type, count] / array[elem_type, count]
|
||||
# 注解 → [count x elem_type]
|
||||
# 支持 t.CArray (ast.Attribute)、CArray/rawarray/array (ast.Name) 三种写法
|
||||
# (array 为本轮新增的内建软关键字,与 t.CArray 逐点等价)
|
||||
ValueIsCArray: bool = (
|
||||
(isinstance(annotation.value, ast.Attribute) and annotation.value.attr == 'CArray')
|
||||
or (isinstance(annotation.value, ast.Name) and annotation.value.id in ('CArray', 'rawarray'))
|
||||
or (isinstance(annotation.value, ast.Name) and annotation.value.id in ('CArray', 'rawarray', 'array'))
|
||||
)
|
||||
if ValueIsCArray:
|
||||
SliceNode: ast.AST = annotation.slice
|
||||
@@ -1307,9 +1339,13 @@ class DeclarationGenerator:
|
||||
# count 为 None 或 0 → 零长度数组(外部符号)
|
||||
return f'[0 x {ElemType}]'
|
||||
elif isinstance(SliceNode, (ast.Attribute, ast.Name, ast.Subscript)):
|
||||
# 单参数 t.CArray[elem_type] → 指针模式
|
||||
# 单参数 t.CArray[elem_type]:与 t.CArray[elem_type, None] 语义统一为
|
||||
# "长度自动推导数组"。此处无初始化字面量可推导 → 与 ,None 分支同用
|
||||
# [0 x elem] 占位,真实长度由 _generate_global_decl 按初始化字面量
|
||||
# (List 长度 / str 长度+1) 修正。
|
||||
# 参数位置经 _decay_array_to_ptr(:23-28) 退化为 elem*,与旧指针行为一致。
|
||||
ElemType: str = self._get_type_str(SliceNode, embedded=True)
|
||||
return f'{ElemType}*'
|
||||
return f'[0 x {ElemType}]'
|
||||
if isinstance(annotation.value, ast.Name) and annotation.value.id == 'tuple':
|
||||
slice_node: ast.AST = annotation.slice
|
||||
elem_types: list[str]
|
||||
|
||||
@@ -9,6 +9,7 @@ import traceback
|
||||
from lib.Projectrans.Utils import compute_sha1, get_file_dependencies, find_reachable_files_from_entries, parse_python_file
|
||||
from lib.Projectrans.DeclarationGenerator import DeclarationGenerator
|
||||
from lib.core.VLogger import get_logger as _vlog
|
||||
from lib.core.SymbolUtils import ExpandSoftKeywords
|
||||
from lib.constants.config import mode as _ConfigMode
|
||||
from lib.includes import t
|
||||
from StubGen import PythonToStubConverter
|
||||
@@ -383,6 +384,11 @@ class Phase1Generator:
|
||||
|
||||
用于 .stub.ll 缓存依赖校验:当依赖的 include SHA1 变化时,
|
||||
当前 include 的 .stub.ll 缓存应失效(因为类型引用的 SHA1 前缀会变化)。
|
||||
|
||||
签名同时纳入 includes/builtins.vp:它是注解类型解析的隐式输入
|
||||
(不在任何模块的 import 闭包,但其 CTypedef 参与签名/字段类型解析),
|
||||
其内容变化必须让所有 .stub.ll 失效——否则旧声明(如 sizet 曾被解析成
|
||||
%"{mod}.sizet"* 假结构体)会被缓存继续引用,源码修复不生效。
|
||||
"""
|
||||
if not self._include_dep_graph or not self._include_file_map:
|
||||
return ''
|
||||
@@ -400,6 +406,11 @@ class Phase1Generator:
|
||||
if DepSha1:
|
||||
Result.add(DepSha1)
|
||||
Queue.extend(self._include_dep_graph.get(DepMod, set()))
|
||||
BuiltinsInfo: tuple[str, str, str] | None = self._include_file_map.get('builtins')
|
||||
if BuiltinsInfo is not None:
|
||||
BuiltinsSha1: str = self._get_sha1(BuiltinsInfo[0])
|
||||
if BuiltinsSha1:
|
||||
Result.add(BuiltinsSha1)
|
||||
return '|'.join(sorted(Result))
|
||||
|
||||
def _ReadStubDep(self, stub_path: str) -> str:
|
||||
@@ -510,7 +521,9 @@ class Phase1Generator:
|
||||
"""
|
||||
result: dict[str, str] = {}
|
||||
try:
|
||||
tree = ast.parse(content)
|
||||
# `.vp` 源码须先过 Viper 软关键字层(`enum X:` / `fn` 等)再解析,
|
||||
# 否则 SyntaxError 被吞成"无 docstring",与主翻译路径口径不一致。
|
||||
tree = ast.parse(ExpandSoftKeywords(content), '<Phase1Generator:_extract_docstrings>')
|
||||
except SyntaxError:
|
||||
return result
|
||||
for node in tree.body:
|
||||
|
||||
@@ -31,11 +31,11 @@ from lib.core.LLVMCG.IRMetadataFix import SanitizeIRMetadata
|
||||
_FILE_STAT_CACHE: dict[str, bool] = {}
|
||||
|
||||
|
||||
# --ctrlc-stack(Ctrl+C 调用栈)强制纳入编译+链接的 include 模块源文件名。
|
||||
# --traceback(Ctrl+C 调用栈)强制纳入编译+链接的 include 模块源文件名。
|
||||
# 该模块对外只有 Install() 入口,且无任何用户代码 import 它 —— 若不强制纳入,
|
||||
# 产物里既没有 Install 符号、也没有调用它的启动期构造器,功能静默失效。
|
||||
# .vp 优先,.py 仅为向后兼容的回退名。
|
||||
_CTRLC_STACK_MODULE_FILES: tuple[str, ...] = ('dbgstack.vp', 'dbgstack.py')
|
||||
_TRACEBACK_MODULE_FILES: tuple[str, ...] = ('traceback.vp', 'traceback.py')
|
||||
|
||||
|
||||
def _CachedIsFile(path: str) -> bool:
|
||||
@@ -80,9 +80,9 @@ def _FindAsanRuntimeDir() -> str | None:
|
||||
return found[-1]
|
||||
|
||||
|
||||
# ---------- --ctrlc-stack:地址→源行 旁文件(<exe>.stackmap)----------
|
||||
# ---------- --traceback:地址→源行 旁文件(<exe>.stackmap)----------
|
||||
#
|
||||
# 开启 --ctrlc-stack 时,链接成功后用 llvm-dwarfdump 解析最终 PE 的
|
||||
# 开启 --traceback 时,链接成功后用 llvm-dwarfdump 解析最终 PE 的
|
||||
# .debug_line/.debug_info,把已有的"函数级/语句级 DWARF 行标记"导出成
|
||||
# <exe>.stackmap,供 exe 内运行时处理器在 Ctrl+C/Ctrl+Break 时把 CRT 到当前
|
||||
# 的返回地址逐帧翻译回 ".vp:行/函数"。以下函数只在开关开启时被调用;未开启时
|
||||
@@ -95,7 +95,7 @@ def _ReadPEImageLayout(exe_path: str) -> tuple[int, int, dict[str, tuple[int, in
|
||||
"""读取 PE 的 (ImageBase, SizeOfImage, 节表) ;非 PE / 解析失败返回 None。
|
||||
|
||||
节表为 {节名: (节 RVA, 节 VirtualSize)}。写端需要这三样才能把 DWARF 地址严格
|
||||
归一到"运行期 RVA"(运行期 dbgstack.vp 用 `帧地址 − GetModuleHandleA(NULL)`):
|
||||
归一到"运行期 RVA"(运行期 traceback.vp 用 `帧地址 − GetModuleHandleA(NULL)`):
|
||||
* 合法行 ⇔ 地址落在映像 VA 区间 [ImageBase, ImageBase + SizeOfImage);
|
||||
* "运行期可能出现的 RVA 区间"的粗判据 = [.text 节 RVA, SizeOfImage)。
|
||||
(原 _ReadPEImageBase 只返回 ImageBase,不足以做上述判定,故扩展为本函数。)
|
||||
@@ -413,7 +413,7 @@ def _parallel_translate_worker(src_path: str, out_path: str, src_root: str, temp
|
||||
hotspot_sites_file: str | None = None,
|
||||
hotspot_sites_all: bool = False,
|
||||
hotspot_callees: list[str] | None = None,
|
||||
ctrlc_stack_enabled: bool = False,
|
||||
traceback_enabled: bool = False,
|
||||
dwarf_enabled: bool = False) -> tuple[str, str, str]:
|
||||
# 注意:不可跨任务复用 Phase2Translator/共享符号表!_translate_file 编译过程中
|
||||
# 会向共享 SymbolTable 写入符号(import insert/类注册等),复用导致前文件的
|
||||
@@ -437,10 +437,10 @@ def _parallel_translate_worker(src_path: str, out_path: str, src_root: str, temp
|
||||
trans.hotspot_sites_file = hotspot_sites_file
|
||||
trans.hotspot_sites_all = hotspot_sites_all
|
||||
trans.hotspot_callees = hotspot_callees
|
||||
# Ctrl+C 调用栈(--ctrlc-stack):worker 子进程无父进程属性,须显式透传
|
||||
# (False=未启用,零影响);dbgstack 模块据此注入 Install() 启动期构造器
|
||||
trans.ctrlc_stack_enabled = ctrlc_stack_enabled
|
||||
# 函数级 DWARF(--ctrlc-stack 时 include 模块也必须带行表,否则栈里落到
|
||||
# Ctrl+C 调用栈(--traceback):worker 子进程无父进程属性,须显式透传
|
||||
# (False=未启用,零影响);traceback 模块据此注入 Install() 启动期构造器
|
||||
trans.traceback_enabled = traceback_enabled
|
||||
# 函数级 DWARF(--traceback 时 include 模块也必须带行表,否则栈里落到
|
||||
# includes/* 的帧取不到源行):worker 子进程无父进程属性,须显式透传
|
||||
# (False=未启用,零影响),仅用于 _translate_file 的 dwarf_enabled 形参
|
||||
trans.dwarf_enabled = dwarf_enabled
|
||||
@@ -736,7 +736,7 @@ def _BuildPyiCacheFile(temp_dir: str) -> str | None:
|
||||
continue
|
||||
try:
|
||||
with open(p, 'r', encoding='utf-8') as f:
|
||||
cache[p] = ast.parse(f.read())
|
||||
cache[p] = ast.parse(f.read(), p)
|
||||
except Exception:
|
||||
cache[p] = None
|
||||
try:
|
||||
@@ -776,7 +776,7 @@ def _PyiCacheWorkerInitializer(temp_dir: str) -> None:
|
||||
for p in _ScanPyiFiles(temp_dir):
|
||||
try:
|
||||
with open(p, 'r', encoding='utf-8') as f:
|
||||
new_cache[p] = ast.parse(f.read())
|
||||
new_cache[p] = ast.parse(f.read(), p)
|
||||
except Exception:
|
||||
new_cache[p] = None
|
||||
if new_cache:
|
||||
@@ -914,7 +914,7 @@ class Phase2Translator:
|
||||
_include_sha1_cache: dict[str, str] = {}
|
||||
_include_exist_cache: dict[str, bool] = {}
|
||||
|
||||
def __init__(self, src_root: str, temp_dir: str, output_dir: str, compile_cmd: str = 'llc', compile_flags: list[str] | None = None, linker_cmd: str | None = None, linker_flags: list[str] | None = None, linker_output: str | None = None, static_libs: list[str] | None = None, dynamic_libs: list[str] | None = None, include_dirs: list[str] | None = None, entry_files: list[str] | None = None, target_triple: str | None = None, target_datalayout: str | None = None, force_recompile_includes: bool = False, build_pyi_cache: bool = True, asan_mode: bool = False, dwarf_enabled: bool = False, ctrlc_stack_enabled: bool = False) -> None:
|
||||
def __init__(self, src_root: str, temp_dir: str, output_dir: str, compile_cmd: str = 'llc', compile_flags: list[str] | None = None, linker_cmd: str | None = None, linker_flags: list[str] | None = None, linker_output: str | None = None, static_libs: list[str] | None = None, dynamic_libs: list[str] | None = None, include_dirs: list[str] | None = None, entry_files: list[str] | None = None, target_triple: str | None = None, target_datalayout: str | None = None, force_recompile_includes: bool = False, build_pyi_cache: bool = True, asan_mode: bool = False, dwarf_enabled: bool = False, traceback_enabled: bool = False) -> None:
|
||||
self.src_root = os.path.abspath(src_root)
|
||||
self.temp_dir = os.path.abspath(temp_dir)
|
||||
self.output_dir = os.path.abspath(output_dir)
|
||||
@@ -950,12 +950,12 @@ class Phase2Translator:
|
||||
# !llvm.dbg.cu + 每函数 DISubprogram + 入口 DILocation,供 ASan /
|
||||
# llvm-symbolizer 反查 ".vp:函数起始行"。
|
||||
self.dwarf_enabled: bool = dwarf_enabled
|
||||
# Ctrl+C 调用栈(--ctrlc-stack):False=未启用(零影响,不调用任何外部
|
||||
# Ctrl+C 调用栈(--traceback):False=未启用(零影响,不调用任何外部
|
||||
# 工具、不写任何旁文件);True=链接成功后额外用 llvm-dwarfdump 把最终
|
||||
# exe 的 DWARF 行表导出为 "<exe>.stackmap"(地址→源行旁文件,供 exe 内
|
||||
# 运行时在 Ctrl+C/Ctrl+Break 时逐帧翻译返回地址)。要求 --dwarf 已开启
|
||||
# (Config 侧联动,否则行表为空),且目标为 Windows PE。
|
||||
self.ctrlc_stack_enabled: bool = ctrlc_stack_enabled
|
||||
self.traceback_enabled: bool = traceback_enabled
|
||||
# 调用点热点统计(--hotspot-sites):None=未启用(零影响);str=调用点计数
|
||||
# 落盘文件路径(每函数计时落同目录 hotspot_time.txt)。各模块运行时自带
|
||||
# Ctrl+C 处理器、统一以追加方式写该路径;含 main 的模块在 main 入口清空一次
|
||||
@@ -1173,9 +1173,9 @@ class Phase2Translator:
|
||||
# 行为与改动前完全一致(零影响)。
|
||||
# --dwarf(函数级 DWARF)同理:缓存 .ll/.obj 不含 !llvm.dbg.cu,必须一并
|
||||
# 强制重译,否则开启开关后仍复用旧产物、看不到任何 .vp 行号。
|
||||
# --ctrlc-stack 依赖同一份 .debug_line 行表(Config 已联动开启 --dwarf),
|
||||
# --traceback 依赖同一份 .debug_line 行表(Config 已联动开启 --dwarf),
|
||||
# 这里显式纳入,避免将来联动改动后静默复用无 DI 的旧产物。
|
||||
hotspot_on: bool = bool(self.hotspot_sites_file) or bool(self.dwarf_enabled) or bool(self.ctrlc_stack_enabled)
|
||||
hotspot_on: bool = bool(self.hotspot_sites_file) or bool(self.dwarf_enabled) or bool(self.traceback_enabled)
|
||||
for i, src_path in enumerate(py_files, 1):
|
||||
rel = os.path.relpath(src_path, self.src_root)
|
||||
# 复用 parse_python_file 的 AST 缓存(get_file_dependencies 已 parse 过)
|
||||
@@ -1786,7 +1786,7 @@ class Phase2Translator:
|
||||
# `.vp` 源码须先过 Viper 软关键字层(`enum X:` / `fn` 等)再解析:
|
||||
# 否则此处 SyntaxError 会静默 return 原 code(自动注入 import _list/_dict
|
||||
# 失效),与主翻译路径(Shell/TransPyC 已展开)口径不一致。
|
||||
tree = ast.parse(ExpandSoftKeywords(code))
|
||||
tree = ast.parse(ExpandSoftKeywords(code), src_path)
|
||||
except SyntaxError:
|
||||
return code
|
||||
|
||||
@@ -1851,10 +1851,10 @@ class Phase2Translator:
|
||||
"""翻译单个源文件,嵌入相关 .stub.ll 声明
|
||||
|
||||
dwarf_enabled: 函数级 DWARF 调试元数据(--dwarf),默认 False(零影响)。
|
||||
App 源模块在 --dwarf/--asan/--ctrlc-stack 时传 True。include 文件默认
|
||||
App 源模块在 --dwarf/--asan/--traceback 时传 True。include 文件默认
|
||||
传 False(其 .obj 会落进跨项目共享的 includes.binary 预编译缓存,注入 DI
|
||||
会污染该缓存);仅 --ctrlc-stack 时传 True——此时 include 的 .obj 改落
|
||||
隔离目录(includes.binary_ctrlcstack,见 _get_precompiled_obj_path),
|
||||
会污染该缓存);仅 --traceback 时传 True——此时 include 的 .obj 改落
|
||||
隔离目录(includes.binary_traceback,见 _get_precompiled_obj_path),
|
||||
不会污染共享缓存。
|
||||
"""
|
||||
_t0: float = time.time()
|
||||
@@ -1891,15 +1891,15 @@ class Phase2Translator:
|
||||
# 未启用时不设置 → 与改动前逐字节一致(零影响)
|
||||
if dwarf_enabled:
|
||||
trans.translator.DwarfEnabled = True
|
||||
# Ctrl+C 调用栈(--ctrlc-stack):仅当本文件就是 dbgstack 模块时置位——
|
||||
# Ctrl+C 调用栈(--traceback):仅当本文件就是 traceback 模块时置位——
|
||||
# 只有该模块需要注入 @llvm.global_ctors 构造器调用自己的 Install()
|
||||
# (见 BaseGen._RegisterDbgStackInstallViaCtor)。其它模块不设置该属性
|
||||
# → Gen.ctrlc_stack_ctor 保持 False,IR 与改动前逐字节一致(零影响)。
|
||||
_ctrlc_stack_ctor: bool = bool(
|
||||
self.ctrlc_stack_enabled
|
||||
and os.path.basename(src_path) in _CTRLC_STACK_MODULE_FILES)
|
||||
if _ctrlc_stack_ctor:
|
||||
trans.translator.CtrlcStackCtor = True
|
||||
# (见 BaseGen._RegisterTracebackInstallViaCtor)。其它模块不设置该属性
|
||||
# → Gen.traceback_ctor 保持 False,IR 与改动前逐字节一致(零影响)。
|
||||
_traceback_ctor: bool = bool(
|
||||
self.traceback_enabled
|
||||
and os.path.basename(src_path) in _TRACEBACK_MODULE_FILES)
|
||||
if _traceback_ctor:
|
||||
trans.translator.TracebackCtor = True
|
||||
trans.SliceLevel = self.slice_level
|
||||
trans.translator.SliceLevel = self.slice_level
|
||||
trans.translator.SliceCount = 0
|
||||
@@ -1953,7 +1953,7 @@ class Phase2Translator:
|
||||
try:
|
||||
with open(sig_path, 'r', encoding='utf-8') as f:
|
||||
sig_content = f.read()
|
||||
sig_tree = ast.parse(sig_content)
|
||||
sig_tree = ast.parse(sig_content, sig_path)
|
||||
for node in ast.iter_child_nodes(sig_tree):
|
||||
if isinstance(node, ast.FunctionDef):
|
||||
if _check_annotation_for_export(node.returns):
|
||||
@@ -2097,13 +2097,13 @@ class Phase2Translator:
|
||||
out_dir = os.path.dirname(out_path)
|
||||
if out_dir:
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
# --ctrlc-stack:核对注入钩子确实生效——本模块要求注入 @llvm.global_ctors
|
||||
# --traceback:核对注入钩子确实生效——本模块要求注入 @llvm.global_ctors
|
||||
# 构造器(StructGen.finalize 里落成 .ll 文本之前),若产物中没有该全局
|
||||
# 说明钩子不可用(如序列化路径被改动绕过),显式报错终止,不静默产出
|
||||
# 缺注册的产物(那会让整个功能静默失效)。
|
||||
if _ctrlc_stack_ctor and 'llvm.global_ctors' not in result:
|
||||
if _traceback_ctor and 'llvm.global_ctors' not in result:
|
||||
raise RuntimeError(
|
||||
'--ctrlc-stack: dbgstack 模块未注入 @llvm.global_ctors '
|
||||
'--traceback: traceback 模块未注入 @llvm.global_ctors '
|
||||
'(模块级收尾钩子未生效,不静默降级)')
|
||||
# 修复 LLVM 22+: external global 不能有初值,改为 linkonce
|
||||
result = re.sub(r'^(@[\w.]+)\s*=\s*external\s+global\s+(\w[\w*]*)\s+(-?\d+|null|zeroinitializer|true|false)$', r'\1 = linkonce global \2 \3', result, flags=re.MULTILINE)
|
||||
@@ -2218,8 +2218,8 @@ class Phase2Translator:
|
||||
return opt_r
|
||||
compile_input = asan_ll
|
||||
cmd = [self.compile_cmd] + self.compile_flags + ['-o', obj_path, compile_input]
|
||||
if self.ctrlc_stack_enabled:
|
||||
# --ctrlc-stack:让 llc 保留帧指针(-frame-pointer=all)。dbghelp 主路径
|
||||
if self.traceback_enabled:
|
||||
# --traceback:让 llc 保留帧指针(-frame-pointer=all)。dbghelp 主路径
|
||||
# 不依赖帧指针,但符号表不可用时要靠 RBP 链兜底——llc 默认
|
||||
# omit-frame-pointer 会让 RBP 链只走出 1~2 帧(实测"共 2 帧")。
|
||||
# 只在开启该开关时加,关闭时命令与改前逐字一致。
|
||||
@@ -2786,14 +2786,14 @@ class Phase2Translator:
|
||||
|
||||
def _scan_include_libraries(self) -> None:
|
||||
"""扫描 includes 目录,检测被使用的模块对应的库文件和源文件"""
|
||||
# --ctrlc-stack:强制纳入 dbgstack 模块 —— 无任何用户代码 import 它,
|
||||
# --traceback:强制纳入 traceback 模块 —— 无任何用户代码 import 它,
|
||||
# 不纳入就不会被翻译 ⇒ 产物没有 Install 符号可注入启动期构造器。
|
||||
# 直接借用既有的 used_includes 收集路径(下面按同一套 py_index /
|
||||
# 传递依赖逻辑把它加入 extra_py_files,再交给 _compile_include_py_files
|
||||
# 编译并把 .obj 追加进 extra_link_files)。开关关闭时不触碰 used_includes
|
||||
# → 本函数行为与改动前逐字一致(零影响)。
|
||||
if self.ctrlc_stack_enabled:
|
||||
self.used_includes.add('dbgstack')
|
||||
if self.traceback_enabled:
|
||||
self.used_includes.add('traceback')
|
||||
if not self.used_includes:
|
||||
return
|
||||
|
||||
@@ -2845,16 +2845,16 @@ class Phase2Translator:
|
||||
_vlog().info(f"\n[includes] 需要链接的库文件: {len(self.extra_link_files)} 个")
|
||||
if self.extra_compile_files:
|
||||
_vlog().info(f"[includes] 需要编译的源文件: {len(self.extra_compile_files)} 个")
|
||||
# --ctrlc-stack:断言 dbgstack 源文件确实被纳入待编译集合。找不到就必然
|
||||
# --traceback:断言 traceback 源文件确实被纳入待编译集合。找不到就必然
|
||||
# 得不到 Install 符号、构造器无处注入(功能静默失效)——显式报错终止,
|
||||
# 不静默跳过(多半是 project.json 的 includes 未包含仓库的 includes 目录)。
|
||||
if self.ctrlc_stack_enabled:
|
||||
_dbgstack_found: bool = any(
|
||||
os.path.basename(_p) in _CTRLC_STACK_MODULE_FILES
|
||||
if self.traceback_enabled:
|
||||
_traceback_found: bool = any(
|
||||
os.path.basename(_p) in _TRACEBACK_MODULE_FILES
|
||||
for _p in self.extra_py_files)
|
||||
if not _dbgstack_found:
|
||||
if not _traceback_found:
|
||||
raise RuntimeError(
|
||||
'--ctrlc-stack: 在 includes 目录中找不到 dbgstack.vp/.py,'
|
||||
'--traceback: 在 includes 目录中找不到 traceback.vp/.py,'
|
||||
'无法编译并链接 Install()(请确认 project.json 的 includes '
|
||||
f'包含仓库 includes 目录;已扫描: {self.include_dirs})')
|
||||
|
||||
@@ -3104,14 +3104,14 @@ class Phase2Translator:
|
||||
预编译目录位于 include 目录同级,命名为 {basename}.binary/。
|
||||
文件命名为 {sha1}.{triple_safe}.{rel_model}.obj,区分目标平台和重定位模型。
|
||||
|
||||
--ctrlc-stack:include 模块也带 DWARF 行表(栈帧要能落到 includes/* 的源行),
|
||||
--traceback:include 模块也带 DWARF 行表(栈帧要能落到 includes/* 的源行),
|
||||
此类 .obj 不得回写进跨项目共享的 includes.binary(会污染其它项目的预编译
|
||||
缓存),改落**兄弟目录** {basename}.binary + 后缀(不新增层级,形态与
|
||||
ASan 变体目录 includes.binary_asan 一致)。
|
||||
关闭时后缀为空 → 路径与改动前逐字一致(含 --dwarf/--asan 单独开启的情形)。
|
||||
--asan 与 --ctrlc-stack 同开:ASan 插桩也会改变 include obj 内容(见
|
||||
--asan 与 --traceback 同开:ASan 插桩也会改变 include obj 内容(见
|
||||
_llc_compile),两者产物不可互相复用,故再叠加 _asan 后缀区分
|
||||
(优先级:是否隔离由 --ctrlc-stack 决定,--asan 只决定后缀再叠加 _asan)。
|
||||
(优先级:是否隔离由 --traceback 决定,--asan 只决定后缀再叠加 _asan)。
|
||||
"""
|
||||
AbsSrcPath: str = os.path.abspath(src_path)
|
||||
RelModel: str = 'static'
|
||||
@@ -3128,8 +3128,8 @@ class Phase2Translator:
|
||||
inc_parent: str = os.path.dirname(abs_inc)
|
||||
inc_base: str = os.path.basename(abs_inc)
|
||||
_iso_suffix: str = ''
|
||||
if self.ctrlc_stack_enabled:
|
||||
_iso_suffix = '_ctrlcstack_asan' if self.asan_mode else '_ctrlcstack'
|
||||
if self.traceback_enabled:
|
||||
_iso_suffix = '_traceback_asan' if self.asan_mode else '_traceback'
|
||||
precompiled_dir: str = os.path.join(inc_parent, inc_base + '.binary' + _iso_suffix)
|
||||
triple_safe: str = (self.triple or 'default').replace(os.sep, '_').replace('/', '_').replace('\\', '_').replace(':', '_')
|
||||
return os.path.join(precompiled_dir, f"{sha1}.{triple_safe}.{RelModel}.obj")
|
||||
@@ -3155,18 +3155,18 @@ class Phase2Translator:
|
||||
# 本函数行为与改动前逐字节一致(零影响)。
|
||||
hotspot_on: bool = bool(self.hotspot_sites_file)
|
||||
|
||||
# --ctrlc-stack:include 模块也必须带 DWARF 行表(否则运行时 Ctrl+C 的栈里
|
||||
# --traceback:include 模块也必须带 DWARF 行表(否则运行时 Ctrl+C 的栈里
|
||||
# 落在 includes/*(memhub/string/viperlib/w32…)的帧取不到源行,只能落到
|
||||
# 邻行或 "+0x…(unknown)")。为此翻译时给 include 透传 dwarf_enabled=True
|
||||
# (见下方 worker/串行四处的 dwarf_enabled=self.ctrlc_stack_enabled),并把
|
||||
# 旧目标文件(output 目录 {sha1}.obj 及隔离缓存 includes.binary_ctrlcstack
|
||||
# (见下方 worker/串行四处的 dwarf_enabled=self.traceback_enabled),并把
|
||||
# 旧目标文件(output 目录 {sha1}.obj 及隔离缓存 includes.binary_traceback
|
||||
# 内的旧 .obj)一并失效重译——复用上一次无 DI 的产物会让行表静默缺失。
|
||||
# 与 hotspot_on 处理方式一致;同时覆盖 dbgstack.vp(其 .obj 还须含
|
||||
# 与 hotspot_on 处理方式一致;同时覆盖 traceback.vp(其 .obj 还须含
|
||||
# @llvm.global_ctors 启动期构造器)。取舍:开启本开关时 include 目标文件每
|
||||
# 次构建都重译(放弃隔离缓存的复用,换取"必然带 DWARF"的正确性保证),
|
||||
# 仅调试模式可接受。开关关闭时 include_dwarf=False,本函数行为与改动前
|
||||
# 逐字节一致(零影响,含 --dwarf/--asan 单独开启的情形)。
|
||||
include_dwarf: bool = bool(self.ctrlc_stack_enabled)
|
||||
include_dwarf: bool = bool(self.traceback_enabled)
|
||||
|
||||
# 构建 include 文件的模块 SHA1 映射(预计算所有文件,构建完整映射)
|
||||
include_ModuleSha1Map = self._build_current_ModuleSha1Map(None)
|
||||
@@ -3206,7 +3206,7 @@ class Phase2Translator:
|
||||
self._collect_inline_symbols(src_path)
|
||||
continue
|
||||
|
||||
# --hotspot-sites / --ctrlc-stack:include 的 .py 未变时下面两处会因旧
|
||||
# --hotspot-sites / --traceback:include 的 .py 未变时下面两处会因旧
|
||||
# .obj 存在而直接跳过,复用上一次(未插桩 / 不含 DI)的目标文件。
|
||||
# 启用时先让这两类旧 .obj 一并失效,使其必然走入 need_translate 重新
|
||||
# 翻译并嵌入热点计数/计时 IR 或 DWARF 行表。
|
||||
@@ -3351,8 +3351,8 @@ class Phase2Translator:
|
||||
hotspot_sites_file=self.hotspot_sites_file,
|
||||
hotspot_sites_all=self.hotspot_sites_all,
|
||||
hotspot_callees=self.hotspot_callees,
|
||||
ctrlc_stack_enabled=self.ctrlc_stack_enabled,
|
||||
dwarf_enabled=self.ctrlc_stack_enabled
|
||||
traceback_enabled=self.traceback_enabled,
|
||||
dwarf_enabled=self.traceback_enabled
|
||||
)
|
||||
in_progress[future] = item
|
||||
|
||||
@@ -3372,8 +3372,8 @@ class Phase2Translator:
|
||||
hotspot_sites_file=self.hotspot_sites_file,
|
||||
hotspot_sites_all=self.hotspot_sites_all,
|
||||
hotspot_callees=self.hotspot_callees,
|
||||
ctrlc_stack_enabled=self.ctrlc_stack_enabled,
|
||||
dwarf_enabled=self.ctrlc_stack_enabled
|
||||
traceback_enabled=self.traceback_enabled,
|
||||
dwarf_enabled=self.traceback_enabled
|
||||
)
|
||||
in_progress[future] = item
|
||||
remaining.clear()
|
||||
@@ -3417,12 +3417,12 @@ class Phase2Translator:
|
||||
for item in need_translate:
|
||||
src_path, sha1, module_name, ll_path, obj_path, precompiled_obj = item
|
||||
_vlog().info(f" 翻译: {module_name}.py -> {sha1}.ll")
|
||||
self._translate_file(src_path, ll_path, prebuilt_ModuleSha1Map=include_ModuleSha1Map, dwarf_enabled=self.ctrlc_stack_enabled)
|
||||
self._translate_file(src_path, ll_path, prebuilt_ModuleSha1Map=include_ModuleSha1Map, dwarf_enabled=self.traceback_enabled)
|
||||
else:
|
||||
item = need_translate[0]
|
||||
src_path, sha1, module_name, ll_path, obj_path, precompiled_obj = item
|
||||
_vlog().info(f" 翻译: {module_name}.py -> {sha1}.ll")
|
||||
self._translate_file(src_path, ll_path, prebuilt_ModuleSha1Map=include_ModuleSha1Map, dwarf_enabled=self.ctrlc_stack_enabled)
|
||||
self._translate_file(src_path, ll_path, prebuilt_ModuleSha1Map=include_ModuleSha1Map, dwarf_enabled=self.traceback_enabled)
|
||||
|
||||
# ========== 阶段2:串行后处理(dso_local + stub_decls 注入)==========
|
||||
compile_tasks = [] # [(dso_ll_path, obj_path, src_path, sha1, module_name, ll_path, precompiled_obj, ll_content)]
|
||||
@@ -3435,16 +3435,16 @@ class Phase2Translator:
|
||||
with open(ll_path, 'r', encoding='utf-8') as f:
|
||||
ll_content = f.read()
|
||||
|
||||
# --ctrlc-stack 提醒:include 模块若发射了 define 却不带 DI(!llvm.dbg.cu),
|
||||
# --traceback 提醒:include 模块若发射了 define 却不带 DI(!llvm.dbg.cu),
|
||||
# 其 .debug_line 行为空 ⇒ 运行时 Ctrl+C 栈里该 include 帧取不到源行
|
||||
# (会退化为 +0x… (unknown))。
|
||||
# **只警告、不报错**:像 includes/stdarg.vp 这类模块的 define 是"合成"的
|
||||
# (由 extern/sugar 派生、无 .vp 函数体),本就不会有源级 DI —— 按 fail-fast
|
||||
# 报错会把这类模块全部误杀;而"行号缺失"只降低可读性,不是编译正确性问题。
|
||||
if self.ctrlc_stack_enabled and re.search(r'^define ', ll_content, re.MULTILINE) \
|
||||
if self.traceback_enabled and re.search(r'^define ', ll_content, re.MULTILINE) \
|
||||
and '!llvm.dbg.cu' not in ll_content:
|
||||
_vlog().warning(
|
||||
f"--ctrlc-stack: include 模块 {module_name}.py 发射了 define 但 IR 未携带 "
|
||||
f"--traceback: include 模块 {module_name}.py 发射了 define 但 IR 未携带 "
|
||||
f"DWARF (!llvm.dbg.cu)——该模块内联帧在 Ctrl+C 栈里将只显示地址: {ll_path}")
|
||||
|
||||
# dso_local 正则标注(使用 ^ + MULTILINE 避免匹配字符串常量内容中的 define/declare)
|
||||
@@ -3602,8 +3602,8 @@ class Phase2Translator:
|
||||
result = compile_results.get(task)
|
||||
if result and result.returncode == 0:
|
||||
self.extra_link_files.append(obj_path)
|
||||
# 复制到 includes.binary 预编译目录(--ctrlc-stack 时 precompiled_obj
|
||||
# 指向隔离目录 includes.binary_ctrlcstack,不会污染共享缓存)
|
||||
# 复制到 includes.binary 预编译目录(--traceback 时 precompiled_obj
|
||||
# 指向隔离目录 includes.binary_traceback,不会污染共享缓存)
|
||||
if precompiled_obj:
|
||||
precompiled_dir = os.path.dirname(precompiled_obj)
|
||||
try:
|
||||
@@ -3611,12 +3611,12 @@ class Phase2Translator:
|
||||
os.makedirs(precompiled_dir, exist_ok=True)
|
||||
shutil.copy2(obj_path, precompiled_obj)
|
||||
except Exception as _e:
|
||||
# --ctrlc-stack fail-fast:隔离目录创建 / 写回失败必须显式终止,
|
||||
# --traceback fail-fast:隔离目录创建 / 写回失败必须显式终止,
|
||||
# 不静默降级(否则本次构建的 include obj 拿不到、或落到共享缓存
|
||||
# 之外的位置,DWARF 行表与后续复用的前提都被破坏)。
|
||||
if self.ctrlc_stack_enabled:
|
||||
if self.traceback_enabled:
|
||||
raise RuntimeError(
|
||||
f"--ctrlc-stack: include 预编译缓存写入隔离目录失败: "
|
||||
f"--traceback: include 预编译缓存写入隔离目录失败: "
|
||||
f"{precompiled_obj}: {_e}") from _e
|
||||
if _config_mode == "strict":
|
||||
_vlog().warning(f"预编译缓存写入失败: {_e}")
|
||||
@@ -4186,7 +4186,7 @@ class Phase2Translator:
|
||||
break
|
||||
|
||||
def _WriteStackMap(self, exe_path: str) -> None:
|
||||
"""--ctrlc-stack:链接成功后导出"地址→源行"旁文件 <exe>.stackmap。
|
||||
"""--traceback:链接成功后导出"地址→源行"旁文件 <exe>.stackmap。
|
||||
|
||||
用 llvm-dwarfdump 解析最终 PE 的 .debug_line(地址→文件/行)与
|
||||
.debug_info(DISubprogram 的 low/high_pc + DW_AT_name → 函数名),
|
||||
@@ -4198,7 +4198,7 @@ class Phase2Translator:
|
||||
rva(u64) + file_off(u32) + line(u32) + func_off(u32)
|
||||
字符串池 str_bytes 字节(紧接记录区,UTF-8,全部以 '\\0' 结尾)
|
||||
|
||||
口径(必须与运行期严格一致):运行期 dbgstack.vp 取
|
||||
口径(必须与运行期严格一致):运行期 traceback.vp 取
|
||||
`rva = 帧地址 − GetModuleHandleA(NULL)`,即"链接期 ImageBase 之上的偏移",
|
||||
故写端唯一正确口径是 **`RVA = dwarfdump 打印的地址 − PE.ImageBase`**。
|
||||
|
||||
@@ -4214,27 +4214,27 @@ class Phase2Translator:
|
||||
的记录保留(RVA = 地址 − ImageBase);其余(未重定位的 0、越界值)一律**丢弃并
|
||||
统计**(不静默留垃圾条目)。同一 RVA 只保留行号最大的一条。
|
||||
|
||||
写出前自检(任一不满足即 fail-fast 抛异常,遵循 --ctrlc-stack 既有风格):
|
||||
写出前自检(任一不满足即 fail-fast 抛异常,遵循 --traceback 既有风格):
|
||||
记录数 / min~max RVA 与 SizeOfImage 的关系 / 所有 RVA ∈ [0, SizeOfImage) /
|
||||
RVA 严格升序 / 至少若干条落在运行期可能出现的 RVA 区间
|
||||
(粗判据 [.text 节 RVA, SizeOfImage))。
|
||||
|
||||
任何一步失败都直接抛异常终止编译(--ctrlc-stack 的硬要求,无兜底)。
|
||||
任何一步失败都直接抛异常终止编译(--traceback 的硬要求,无兜底)。
|
||||
"""
|
||||
import struct
|
||||
import bisect
|
||||
|
||||
def _Fail(msg: str) -> None:
|
||||
full: str = msg + '(这是 --ctrlc-stack 的要求)'
|
||||
full: str = msg + '(这是 --traceback 的要求)'
|
||||
_vlog().error(full)
|
||||
raise RuntimeError(full)
|
||||
|
||||
layout = _ReadPEImageLayout(exe_path)
|
||||
if layout is None:
|
||||
_Fail(f"--ctrlc-stack 只支持 Windows PE 产物,但 {exe_path} 不是有效 PE")
|
||||
_Fail(f"--traceback 只支持 Windows PE 产物,但 {exe_path} 不是有效 PE")
|
||||
image_base, size_of_image, sections = layout
|
||||
if size_of_image <= 0:
|
||||
_Fail(f"--ctrlc-stack 读到的 PE.SizeOfImage 非正({size_of_image}),无法界定 RVA 域")
|
||||
_Fail(f"--traceback 读到的 PE.SizeOfImage 非正({size_of_image}),无法界定 RVA 域")
|
||||
img_lo: int = image_base
|
||||
img_hi: int = image_base + size_of_image
|
||||
# 运行期可能出现的 RVA 区间的**粗判据**下界:.text 节 RVA(缺失时退化为最小节 RVA)
|
||||
@@ -4245,22 +4245,22 @@ class Phase2Translator:
|
||||
|
||||
dwarfdump: str | None = shutil.which('llvm-dwarfdump') or shutil.which('llvm-dwarfdump.exe')
|
||||
if not dwarfdump:
|
||||
_Fail("--ctrlc-stack 找不到 llvm-dwarfdump(请确认 PATH 内含 LLVM 工具)")
|
||||
_Fail("--traceback 找不到 llvm-dwarfdump(请确认 PATH 内含 LLVM 工具)")
|
||||
|
||||
try:
|
||||
proc = subprocess.run(
|
||||
[dwarfdump, '--debug-line', '--debug-info', exe_path],
|
||||
capture_output=True)
|
||||
except Exception as e:
|
||||
_Fail(f"--ctrlc-stack 调用 llvm-dwarfdump 失败: {e}")
|
||||
_Fail(f"--traceback 调用 llvm-dwarfdump 失败: {e}")
|
||||
if proc.returncode != 0:
|
||||
_Fail(f"--ctrlc-stack 的 llvm-dwarfdump 返回 {proc.returncode}: "
|
||||
_Fail(f"--traceback 的 llvm-dwarfdump 返回 {proc.returncode}: "
|
||||
+ proc.stderr.decode('utf-8', errors='replace').strip())
|
||||
dump_text: str = proc.stdout.decode('utf-8', errors='replace')
|
||||
|
||||
rows: list[tuple[int, str, int]] = _ParseDwarfLineRows(dump_text)
|
||||
if not rows:
|
||||
_Fail(f"--ctrlc-stack 未能从 {exe_path} 解析出任何 DWARF 行记录(.debug_line 为空?)")
|
||||
_Fail(f"--traceback 未能从 {exe_path} 解析出任何 DWARF 行记录(.debug_line 为空?)")
|
||||
|
||||
# ---- 行记录:逐条归一化为"运行期 RVA";不能归一的一律丢弃并统计 ----
|
||||
dropped_rows: int = 0
|
||||
@@ -4278,12 +4278,12 @@ class Phase2Translator:
|
||||
best[rva] = (path, line)
|
||||
if dropped_rows:
|
||||
_vlog().warning(
|
||||
f" [--ctrlc-stack] 丢弃 {dropped_rows} 条无法归一化的 DWARF 行记录"
|
||||
f" [--traceback] 丢弃 {dropped_rows} 条无法归一化的 DWARF 行记录"
|
||||
f"(地址不在映像 VA 区间 [0x{img_lo:x}, 0x{img_hi:x}) 内;判定原因:多为被连接器"
|
||||
f" COMDAT 去重丢弃的泛型副本,其地址恰为 0)示例地址: "
|
||||
+ ", ".join(f"0x{a:x}" for a in dropped_row_samples))
|
||||
if not best:
|
||||
_Fail(f"--ctrlc-stack 从 {exe_path} 得到的行记录全部无法归一到运行期 RVA 域")
|
||||
_Fail(f"--traceback 从 {exe_path} 得到的行记录全部无法归一到运行期 RVA 域")
|
||||
|
||||
# ---- 子程序(函数名):同样逐条归一化;不能归一的丢弃 ----
|
||||
funcs: list[tuple[int, int, str]] = _ParseDwarfSubprograms(dump_text)
|
||||
@@ -4302,7 +4302,7 @@ class Phase2Translator:
|
||||
func_ranges.append((lo_rva, hi_rva, name))
|
||||
if dropped_funcs:
|
||||
_vlog().warning(
|
||||
f" [--ctrlc-stack] 丢弃 {dropped_funcs} 个无法归一化的 DWARF 子程序"
|
||||
f" [--traceback] 丢弃 {dropped_funcs} 个无法归一化的 DWARF 子程序"
|
||||
f"(low_pc 不在映像 VA 区间 [0x{img_lo:x}, 0x{img_hi:x}) 内;同上为去重副本)")
|
||||
func_ranges.sort(key=lambda f: f[0])
|
||||
func_lows: list[int] = [f[0] for f in func_ranges]
|
||||
@@ -4341,19 +4341,19 @@ class Phase2Translator:
|
||||
max_rva: int = rvas[-1]
|
||||
for i in range(1, len(rvas)):
|
||||
if rvas[i] <= rvas[i - 1]:
|
||||
_Fail(f"--ctrlc-stack 自检失败:记录 RVA 非严格升序"
|
||||
_Fail(f"--traceback 自检失败:记录 RVA 非严格升序"
|
||||
f"(第 {i} 条 0x{rvas[i]:x} <= 前一条 0x{rvas[i - 1]:x},会破坏运行期二分查找)")
|
||||
outside: int = sum(1 for r in rvas if not (0 <= r < size_of_image))
|
||||
if outside:
|
||||
_Fail(f"--ctrlc-stack 自检失败:有 {outside} 条记录的 RVA 落在 [0, SizeOfImage=0x{size_of_image:x}) 之外")
|
||||
_Fail(f"--traceback 自检失败:有 {outside} 条记录的 RVA 落在 [0, SizeOfImage=0x{size_of_image:x}) 之外")
|
||||
# 运行期可能出现的 RVA 区间(粗判据):[.text 节 RVA, SizeOfImage)。一条都没有 ⇒ 口径又错了。
|
||||
plausible: int = sum(1 for r in rvas if text_rva <= r < size_of_image)
|
||||
if plausible <= 0:
|
||||
_Fail(f"--ctrlc-stack 自检失败:没有任何记录的 RVA 落在运行期可能出现的区间"
|
||||
_Fail(f"--traceback 自检失败:没有任何记录的 RVA 落在运行期可能出现的区间"
|
||||
f" [0x{text_rva:x}(.text RVA), 0x{size_of_image:x}(SizeOfImage)) 内"
|
||||
f"(RVA 口径疑似又错了)")
|
||||
_vlog().info(
|
||||
f" [--ctrlc-stack] 自检: 记录={len(records)} RVA范围=[0x{min_rva:x}, 0x{max_rva:x}]"
|
||||
f" [--traceback] 自检: 记录={len(records)} RVA范围=[0x{min_rva:x}, 0x{max_rva:x}]"
|
||||
f" PE.SizeOfImage=0x{size_of_image:x}({size_of_image})"
|
||||
f" 各节最高结束 RVA=0x{max_sec_end:x}"
|
||||
f" | 全部∈[0,SizeOfImage)=是 严格升序=是"
|
||||
@@ -4371,9 +4371,9 @@ class Phase2Translator:
|
||||
with open(map_path, 'wb') as f:
|
||||
f.write(out)
|
||||
except Exception as e:
|
||||
_Fail(f"--ctrlc-stack 写出旁文件失败 {map_path}: {e}")
|
||||
_Fail(f"--traceback 写出旁文件失败 {map_path}: {e}")
|
||||
_vlog().success(
|
||||
f" [--ctrlc-stack] 已写出地址→源行旁文件: {map_path}"
|
||||
f" [--traceback] 已写出地址→源行旁文件: {map_path}"
|
||||
f"({len(records)} 条记录,{len(out)} 字节)")
|
||||
|
||||
def _link_obj_files(self, active_sha1s: set[str]) -> None:
|
||||
@@ -4504,13 +4504,13 @@ SECTIONS {
|
||||
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')
|
||||
# Ctrl+C 栈转储(--ctrlc-stack):includes/dbgstack.vp 的主回溯路径用
|
||||
# Ctrl+C 栈转储(--traceback):includes/traceback.vp 的主回溯路径用
|
||||
# StackWalk64 / SymInitialize / SymFunctionTableAccess64 / SymGetModuleBase64
|
||||
# (均在 dbghelp),故开启该开关时必须显式链接 -ldbghelp,否则链接期报未定义符号。
|
||||
# 与 ASan 同理:库要排在目标文件之后 ⇒ 追加到 lib_flags。
|
||||
# (若确实无法链 dbghelp:把 includes/dbgstack.vp 的 DBGSTACK_USE_DBGHELP 置 0,
|
||||
# (若确实无法链 dbghelp:把 includes/traceback.vp 的 TRACEBACK_USE_DBGHELP 置 0,
|
||||
# 退化为纯 RBP 链回溯,此时不该再带本 flag。)
|
||||
if self.ctrlc_stack_enabled:
|
||||
if self.traceback_enabled:
|
||||
lib_flags.append('-ldbghelp')
|
||||
cmd = [self.linker_cmd] + non_lib_flags + ['-o', link_output] + all_link_files + lib_flags
|
||||
_vlog().info(f" 执行: {' '.join(cmd)}")
|
||||
@@ -4564,8 +4564,8 @@ SECTIONS {
|
||||
_vlog().error(f"\n链接异常,立即终止编译。")
|
||||
sys.exit(1)
|
||||
|
||||
# --ctrlc-stack:链接成功后导出"地址→源行"旁文件(<exe>.stackmap)。
|
||||
# --traceback:链接成功后导出"地址→源行"旁文件(<exe>.stackmap)。
|
||||
# 刻意放在上面的链接 try/except 之外,使导出阶段的 fail-fast RuntimeError
|
||||
# 直接向上抛出终止编译,而不被"链接异常兜底"吞掉。
|
||||
if self.ctrlc_stack_enabled:
|
||||
if self.traceback_enabled:
|
||||
self._WriteStackMap(exe_path)
|
||||
@@ -78,10 +78,11 @@ def parse_python_file(filepath: str) -> tuple[str, ast.Module] | tuple[str, None
|
||||
except Exception:
|
||||
return ('', None)
|
||||
try:
|
||||
# 软关键字展开:fn/fun/func/function -> def(行号不变),供 ast.parse 使用。
|
||||
# 软关键字/语法糖展开:fn/fun/func/function -> def、enum 块 -> class X(t.CEnum)
|
||||
# (均同行替换,行号不变),供 ast.parse 使用。
|
||||
# 注意 content 保持原始文本返回(compute_sha1 必须与 Phase1 一致),
|
||||
# 仅 tree 基于展开后的代码解析。
|
||||
tree: ast.Module = ast.parse(ExpandSoftKeywords(content))
|
||||
tree: ast.Module = ast.parse(ExpandSoftKeywords(content), filepath)
|
||||
except SyntaxError:
|
||||
return (content, None)
|
||||
_FILE_AST_CACHE[norm_path] = (mtime, content, tree)
|
||||
|
||||
Reference in new issue
Block a user