feat(ctrlc-stack): 新增 --ctrlc-stack 编译期开关(按键打印 .vp 调用栈)
Config/Phase2Translator: --ctrlc-stack 自动开 DWARF;缓存失效;链接后 llvm-dwarfdump 导出 <exe>.stackmap(逐条归一化为 RVA——旧的整体判据会被 lld 去重产生的 0 地址行带偏,导致全表差一个 ImageBase);链接参数追加 -ldbghelp;include 也生成 DWARF 并隔离到 includes.binary_ctrlcstack(_asan);llc 追加 -frame-pointer=all。 BaseGen._RegisterDbgStackInstallViaCtor + StructGen.finalize 钩子: 注入 llvm.global_ctors(priority 65535) 在启动期调用 dbgstack.Install(),写盘前核对 global_ctors 否则 fail-fast。 FuncGen._adjust_args: 指针到整数的 fail-fast 补逐项类型诊断。
This commit is contained in:
1 parent
f7ace9876d
commit
53a345a59a
6 files changed
+1351
-123
No files matched your search
@@ -149,6 +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('--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')
|
||||
@@ -162,14 +163,23 @@ 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。
|
||||
# 不在此处做工具/平台校验——真正的 fail-fast 落在链接成功后的旁文件导出步骤
|
||||
# (那里才有最终 exe 路径可判 PE)。此处仅提示已启用。
|
||||
if args.ctrlc_stack:
|
||||
_vlog().info('Ctrl+C 调用栈模式已启用(链接后用 llvm-dwarfdump 导出 <exe>.stackmap)', 'ctrlc-stack')
|
||||
|
||||
# 函数级 DWARF 调试元数据(--dwarf):默认关闭(硬要求——普通构建不承担
|
||||
# 任何风险,生成的 .ll 与改动前逐字节一致)。--asan 时自动开启:ASan 报告
|
||||
# 本身就需要行号才能定位,且 ASan 构建已是显式选择的非默认路径。
|
||||
dwarf_enabled: bool = bool(args.dwarf or args.asan)
|
||||
# --ctrlc-stack 同理自动开启:旁文件的"地址→源行"完全来自 DWARF 行表,
|
||||
# 没有 DI 元数据时 .debug_line 为空、旁文件必为空。
|
||||
dwarf_enabled: bool = bool(args.dwarf or args.asan or args.ctrlc_stack)
|
||||
if dwarf_enabled:
|
||||
_auto_src: str = '--asan' if args.asan else ('--ctrlc-stack' if args.ctrlc_stack else '')
|
||||
_vlog().info(
|
||||
'函数级 DWARF 调试元数据已启用:每个 .vp 模块一份 DICompileUnit + 每函数 DISubprogram/DILocation'
|
||||
+ ('(由 --asan 自动开启)' if not args.dwarf else ''),
|
||||
+ (f'(由 {_auto_src} 自动开启)' if _auto_src and not args.dwarf else ''),
|
||||
'dwarf')
|
||||
|
||||
project_file: str | None = args.project
|
||||
@@ -339,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)
|
||||
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.sha1_map = Load_sha1_map(temp_dir)
|
||||
trans.slice_level = slice_level
|
||||
# 调用点热点统计与每函数计时(--hotspot-sites):None=未启用(零影响)
|
||||
|
||||
@@ -16,7 +16,8 @@ from lib.core.VLogger import get_logger as _vlog
|
||||
from lib.constants.config import mode as _config_mode
|
||||
from lib.core.Handles.HandlesBase import CTypeInfo
|
||||
from lib.includes import t
|
||||
from lib.core.SymbolUtils import AnnotationContainsName
|
||||
from lib.core.SymbolUtils import AnnotationContainsName, ExpandSoftKeywords
|
||||
from lib.core.SymbolUtils import ClassHasFuncLevelCVTable, IsVtableSlotMethod
|
||||
from lib.Projectrans.Utils import compute_sha1, _check_annotation_for_export, find_reachable_files_from_entries, topological_sort_files, parse_python_file
|
||||
from lib.Projectrans.Phase1Generator import _TryGlobalCache, _SaveToGlobalCache, _GLOBAL_CACHE_DIR
|
||||
import TransPyC
|
||||
@@ -30,6 +31,13 @@ from lib.core.LLVMCG.IRMetadataFix import SanitizeIRMetadata
|
||||
_FILE_STAT_CACHE: dict[str, bool] = {}
|
||||
|
||||
|
||||
# --ctrlc-stack(Ctrl+C 调用栈)强制纳入编译+链接的 include 模块源文件名。
|
||||
# 该模块对外只有 Install() 入口,且无任何用户代码 import 它 —— 若不强制纳入,
|
||||
# 产物里既没有 Install 符号、也没有调用它的启动期构造器,功能静默失效。
|
||||
# .vp 优先,.py 仅为向后兼容的回退名。
|
||||
_CTRLC_STACK_MODULE_FILES: tuple[str, ...] = ('dbgstack.vp', 'dbgstack.py')
|
||||
|
||||
|
||||
def _CachedIsFile(path: str) -> bool:
|
||||
r: bool | None = _FILE_STAT_CACHE.get(path)
|
||||
if r is None:
|
||||
@@ -72,6 +80,245 @@ def _FindAsanRuntimeDir() -> str | None:
|
||||
return found[-1]
|
||||
|
||||
|
||||
# ---------- --ctrlc-stack:地址→源行 旁文件(<exe>.stackmap)----------
|
||||
#
|
||||
# 开启 --ctrlc-stack 时,链接成功后用 llvm-dwarfdump 解析最终 PE 的
|
||||
# .debug_line/.debug_info,把已有的"函数级/语句级 DWARF 行标记"导出成
|
||||
# <exe>.stackmap,供 exe 内运行时处理器在 Ctrl+C/Ctrl+Break 时把 CRT 到当前
|
||||
# 的返回地址逐帧翻译回 ".vp:行/函数"。以下函数只在开关开启时被调用;未开启时
|
||||
# 不引入任何新依赖、任何新行为。
|
||||
|
||||
_PE_MAGIC: bytes = b'PE\0\0'
|
||||
|
||||
|
||||
def _ReadPEImageLayout(exe_path: str) -> tuple[int, int, dict[str, tuple[int, int]]] | None:
|
||||
"""读取 PE 的 (ImageBase, SizeOfImage, 节表) ;非 PE / 解析失败返回 None。
|
||||
|
||||
节表为 {节名: (节 RVA, 节 VirtualSize)}。写端需要这三样才能把 DWARF 地址严格
|
||||
归一到"运行期 RVA"(运行期 dbgstack.vp 用 `帧地址 − GetModuleHandleA(NULL)`):
|
||||
* 合法行 ⇔ 地址落在映像 VA 区间 [ImageBase, ImageBase + SizeOfImage);
|
||||
* "运行期可能出现的 RVA 区间"的粗判据 = [.text 节 RVA, SizeOfImage)。
|
||||
(原 _ReadPEImageBase 只返回 ImageBase,不足以做上述判定,故扩展为本函数。)
|
||||
"""
|
||||
import struct
|
||||
try:
|
||||
with open(exe_path, 'rb') as f:
|
||||
head: bytes = f.read(0x1000)
|
||||
except Exception:
|
||||
return None
|
||||
if len(head) < 0x40 or head[:2] != b'MZ':
|
||||
return None
|
||||
e_lfanew: int = struct.unpack_from('<I', head, 0x3C)[0]
|
||||
if e_lfanew + 24 > len(head) or head[e_lfanew:e_lfanew + 4] != _PE_MAGIC:
|
||||
return None
|
||||
coff_off: int = e_lfanew + 4
|
||||
n_sections: int = struct.unpack_from('<H', head, coff_off + 2)[0]
|
||||
opt_size: int = struct.unpack_from('<H', head, coff_off + 16)[0]
|
||||
opt_off: int = coff_off + 20 # COFF 文件头固定 20 字节
|
||||
if opt_off + 32 > len(head):
|
||||
return None
|
||||
magic: int = struct.unpack_from('<H', head, opt_off)[0]
|
||||
if magic == 0x20B: # PE32+
|
||||
image_base: int = struct.unpack_from('<Q', head, opt_off + 24)[0]
|
||||
elif magic == 0x10B: # PE32
|
||||
image_base = struct.unpack_from('<I', head, opt_off + 28)[0]
|
||||
else:
|
||||
return None
|
||||
# SizeOfImage 在两种可选头里都在 +56(PE32 多出的 BaseOfData 在 ImageBase 之前)
|
||||
size_of_image: int = struct.unpack_from('<I', head, opt_off + 56)[0]
|
||||
sections: dict[str, tuple[int, int]] = {}
|
||||
sec_off: int = opt_off + opt_size
|
||||
for i in range(n_sections):
|
||||
base: int = sec_off + i * 40
|
||||
if base + 40 > len(head):
|
||||
break
|
||||
name: str = head[base:base + 8].split(b'\x00', 1)[0].decode('ascii', 'replace')
|
||||
sections[name] = (struct.unpack_from('<I', head, base + 12)[0], # VirtualAddress = 节 RVA
|
||||
struct.unpack_from('<I', head, base + 8)[0]) # VirtualSize
|
||||
return image_base, size_of_image, sections
|
||||
|
||||
|
||||
# llvm-dwarfdump --debug-line 行表;行记录列形如
|
||||
# Address Line Column File ISA Discriminator Flags
|
||||
# 0x0000000140001000 12 0 1 0 0 is_stmt
|
||||
_DWARF_LINE_HEADER_RE = re.compile(r'^Address\s+Line\s+Column\s+File\b')
|
||||
_DWARF_LINE_ROW_RE = re.compile(r'^(0x[0-9A-Fa-f]+)\s+(\d+)\s+(\d+)\s+(\d+)\b')
|
||||
_DWARF_FILENAMES_RE = re.compile(r'^\s*file_names\[\s*(\d+)\]\s*:')
|
||||
_DWARF_DIRNAMES_RE = re.compile(r'^\s*(?:directory_names|include_directories)\[\s*(\d+)\]\s*[:=]\s*(.*)$')
|
||||
_DWARF_NAME_RE = re.compile(r'^\s*name:\s*(.*)$')
|
||||
_DWARF_DIRINDEX_RE = re.compile(r'^\s*dir_index:\s*(\d+)\s*$')
|
||||
# llvm-dwarfdump --debug-info 里的 DISubprogram(DW_AT_low_pc/high_pc/name)
|
||||
_DWARF_TAG_ANY_RE = re.compile(r'DW_TAG_\w+')
|
||||
_DWARF_TAG_SUBPROGRAM_RE = re.compile(r'DW_TAG_subprogram\b')
|
||||
_DWARF_LOWPC_RE = re.compile(r'DW_AT_low_pc\s*\(\s*(0x[0-9A-Fa-f]+)\s*\)')
|
||||
_DWARF_HIGHPC_RE = re.compile(r'DW_AT_high_pc\s*\(\s*(0x[0-9A-Fa-f]+)\s*\)')
|
||||
_DWARF_ATNAME_RE = re.compile(r'DW_AT_name\s*\("((?:[^"\\]|\\.)*)"\)')
|
||||
|
||||
|
||||
def _ParseDwarfLineRows(dump_text: str) -> list[tuple[int, str, int]]:
|
||||
"""从 llvm-dwarfdump 输出解析出 [(地址, 文件绝对路径, 行号)]。
|
||||
|
||||
逐个 CU(debug_line[0x..])重建目录/文件表;行记录过滤掉 line==0 的
|
||||
end_sequence 行。文件路径优先用文件表里的绝对 name;name 为相对时按 dir_index
|
||||
指向的目录拼接(DWARF v5 取 directory_names,v4 取 include_directories)。
|
||||
|
||||
⚠ 地址口径:**原样返回 dwarfdump 打印的地址**(本例为含 ImageBase 的 VA
|
||||
0x14000xxxx,但被链接器按 COMDAT 去重丢弃的泛型副本其地址恰为 0)。
|
||||
本函数**不**做"整表 VA/RVA"的判定——那正是旧实现失真的根因(全局最小值一旦为
|
||||
0,整表都不减基址)。归一化(逐条 `地址 − ImageBase`,并丢弃不能归一的条目)
|
||||
由调用方 `_WriteStackMap` 依 PE.ImageBase/SizeOfImage 完成。
|
||||
"""
|
||||
rows: list[tuple[int, str, int]] = []
|
||||
dirs: dict[int, str] = {}
|
||||
files: dict[int, tuple[str, int]] = {}
|
||||
pending_kind: str | None = None
|
||||
pending_idx: int = -1
|
||||
pending_name: str | None = None
|
||||
pending_dir_index: int = 0
|
||||
in_line_section: bool = False
|
||||
|
||||
def _Unquote(text: str) -> str:
|
||||
text = text.strip()
|
||||
if len(text) >= 2 and text[0] == '"' and text[-1] == '"':
|
||||
text = text[1:-1]
|
||||
return text
|
||||
|
||||
def _Commit() -> None:
|
||||
nonlocal pending_kind, pending_name, pending_dir_index
|
||||
if pending_kind == 'file' and pending_name is not None:
|
||||
files[pending_idx] = (pending_name, pending_dir_index)
|
||||
pending_kind = None
|
||||
pending_name = None
|
||||
pending_dir_index = 0
|
||||
|
||||
def _Resolve(idx: int) -> str:
|
||||
entry = files.get(idx)
|
||||
if entry is None:
|
||||
return '??'
|
||||
name, dir_index = entry
|
||||
if os.path.isabs(name):
|
||||
return name
|
||||
base: str = dirs.get(dir_index, '')
|
||||
if base and not os.path.isabs(base):
|
||||
root: str = dirs.get(0, '')
|
||||
if root and os.path.isabs(root):
|
||||
base = os.path.join(root, base)
|
||||
return os.path.normpath(os.path.join(base, name)) if base else name
|
||||
|
||||
for line in dump_text.splitlines():
|
||||
stripped: str = line.strip()
|
||||
if not stripped:
|
||||
continue
|
||||
if 'debug_line contents' in stripped:
|
||||
in_line_section = True
|
||||
continue
|
||||
if 'contents:' in stripped and 'debug_' in stripped:
|
||||
in_line_section = False
|
||||
continue
|
||||
if not in_line_section:
|
||||
continue
|
||||
if stripped.startswith('debug_line['):
|
||||
_Commit()
|
||||
dirs = {}
|
||||
files = {}
|
||||
continue
|
||||
m = _DWARF_FILENAMES_RE.match(line)
|
||||
if m:
|
||||
_Commit()
|
||||
pending_kind = 'file'
|
||||
pending_idx = int(m.group(1))
|
||||
continue
|
||||
m = _DWARF_DIRNAMES_RE.match(line)
|
||||
if m:
|
||||
_Commit()
|
||||
value: str = _Unquote(m.group(2))
|
||||
if value:
|
||||
dirs[int(m.group(1))] = value
|
||||
else:
|
||||
pending_kind = 'dir'
|
||||
pending_idx = int(m.group(1))
|
||||
continue
|
||||
m = _DWARF_NAME_RE.match(line)
|
||||
if m and pending_kind is not None:
|
||||
name: str = _Unquote(m.group(1))
|
||||
if pending_kind == 'file':
|
||||
pending_name = name
|
||||
else:
|
||||
dirs[pending_idx] = name
|
||||
pending_kind = None
|
||||
continue
|
||||
m = _DWARF_DIRINDEX_RE.match(line)
|
||||
if m and pending_kind == 'file':
|
||||
pending_dir_index = int(m.group(1))
|
||||
continue
|
||||
if _DWARF_LINE_HEADER_RE.match(line):
|
||||
_Commit()
|
||||
continue
|
||||
m = _DWARF_LINE_ROW_RE.match(line)
|
||||
if m:
|
||||
_Commit()
|
||||
row_line: int = int(m.group(2))
|
||||
if row_line > 0:
|
||||
rows.append((int(m.group(1), 16), _Resolve(int(m.group(4))), row_line))
|
||||
return rows
|
||||
|
||||
|
||||
def _ParseDwarfSubprograms(dump_text: str) -> list[tuple[int, int, str]]:
|
||||
"""从 llvm-dwarfdump --debug-info 输出解析 [(low_pc, high_pc, 函数名)]。
|
||||
|
||||
DW_AT_high_pc 有两种形态且 dwarfdump 都打成 `(0x…)`:`DW_FORM_addr`(绝对
|
||||
结束地址,值 > low_pc)与常量形式(相对 low_pc 的**长度**,值通常 < low_pc)。
|
||||
本函数以 `high_pc <= low_pc` 判定后者并按 `low_pc + 长度` 还原;缺 high_pc 时
|
||||
退化为 [low_pc, low_pc + 1)。
|
||||
|
||||
⚠ 地址口径同 `_ParseDwarfLineRows`:**原样返回**,不做整表 VA/RVA 判定;被
|
||||
链接器 COMDAT 去重丢弃的泛型副本其 low_pc 恰为 0(实测 TransPyV.exe 有 756 个),
|
||||
由调用方 `_WriteStackMap` 逐条归一化并丢弃。
|
||||
"""
|
||||
result: list[tuple[int, int, str]] = []
|
||||
cur_low: int | None = None
|
||||
cur_high: int | None = None
|
||||
cur_name: str | None = None
|
||||
in_prog: bool = False
|
||||
|
||||
def _Flush() -> None:
|
||||
nonlocal cur_low, cur_high, cur_name, in_prog
|
||||
if in_prog and cur_low is not None and cur_name:
|
||||
hi: int = cur_high if cur_high is not None else cur_low + 1
|
||||
if hi <= cur_low:
|
||||
hi = cur_low + cur_high # high_pc 为 offset 形式
|
||||
if hi > cur_low:
|
||||
result.append((cur_low, hi, cur_name))
|
||||
cur_low = None
|
||||
cur_high = None
|
||||
cur_name = None
|
||||
in_prog = False
|
||||
|
||||
for line in dump_text.splitlines():
|
||||
if _DWARF_TAG_ANY_RE.search(line):
|
||||
if _DWARF_TAG_SUBPROGRAM_RE.search(line):
|
||||
_Flush()
|
||||
in_prog = True
|
||||
else:
|
||||
_Flush()
|
||||
continue
|
||||
if not in_prog:
|
||||
continue
|
||||
m = _DWARF_LOWPC_RE.search(line)
|
||||
if m:
|
||||
cur_low = int(m.group(1), 16)
|
||||
continue
|
||||
m = _DWARF_HIGHPC_RE.search(line)
|
||||
if m:
|
||||
cur_high = int(m.group(1), 16)
|
||||
continue
|
||||
m = _DWARF_ATNAME_RE.search(line)
|
||||
if m and cur_name is None:
|
||||
cur_name = m.group(1)
|
||||
_Flush()
|
||||
return result
|
||||
|
||||
|
||||
def _PickWorkerCount(n_files: int, per_worker_mb: int = 350) -> int:
|
||||
"""按可用物理内存动态限制并行 worker 数,避免多进程编译内存峰值 OOM。
|
||||
|
||||
@@ -165,7 +412,9 @@ def _parallel_translate_worker(src_path: str, out_path: str, src_root: str, temp
|
||||
prebuilt_ModuleSha1Map: dict[str, str] | None = None,
|
||||
hotspot_sites_file: str | None = None,
|
||||
hotspot_sites_all: bool = False,
|
||||
hotspot_callees: list[str] | None = None) -> tuple[str, str, str]:
|
||||
hotspot_callees: list[str] | None = None,
|
||||
ctrlc_stack_enabled: bool = False,
|
||||
dwarf_enabled: bool = False) -> tuple[str, str, str]:
|
||||
# 注意:不可跨任务复用 Phase2Translator/共享符号表!_translate_file 编译过程中
|
||||
# 会向共享 SymbolTable 写入符号(import insert/类注册等),复用导致前文件的
|
||||
# 符号污染后文件 → 跨模块 struct 类型前缀错位(如 7cadfe07e983fd90.CompilerFactory
|
||||
@@ -188,6 +437,13 @@ 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 模块也必须带行表,否则栈里落到
|
||||
# includes/* 的帧取不到源行):worker 子进程无父进程属性,须显式透传
|
||||
# (False=未启用,零影响),仅用于 _translate_file 的 dwarf_enabled 形参
|
||||
trans.dwarf_enabled = dwarf_enabled
|
||||
|
||||
if shared_sym_pickle_path and os.path.exists(shared_sym_pickle_path):
|
||||
# 使用 pickle.load(f) 流式加载,避免 pickle.loads(f.read()) 一次性
|
||||
@@ -215,7 +471,7 @@ def _parallel_translate_worker(src_path: str, out_path: str, src_root: str, temp
|
||||
trans._precollect_inline_symbols()
|
||||
trans._build_shared_symbol_data()
|
||||
|
||||
trans._translate_file(src_path, out_path, prebuilt_ModuleSha1Map=prebuilt_ModuleSha1Map)
|
||||
trans._translate_file(src_path, out_path, prebuilt_ModuleSha1Map=prebuilt_ModuleSha1Map, dwarf_enabled=dwarf_enabled)
|
||||
return ('ok', src_path, '')
|
||||
except Exception as e:
|
||||
# fail-fast(2026-08-18):include 文件编译出错立即终止整个编译进程
|
||||
@@ -552,6 +808,18 @@ def _count_braces_outside_strings(text: str) -> int:
|
||||
return count
|
||||
|
||||
|
||||
# LLVM IR "无引号标识符"的精确规则(LangRef "Identifiers";实现即
|
||||
# LLLexer::ReadVarName):首字符 ∈ [-a-zA-Z$._],后续字符 ∈ [-a-zA-Z$._0-9]。
|
||||
# 首字符**不允许是数字**:`$884f3d94ea4d0123.md5_F` 会让 LLLexer::LexDollar
|
||||
# 走到 ReadVarName() 返回 false ⇒ 产出 error token ⇒ 顶层解析报
|
||||
# "expected top-level entity"。故 comdat 名必须以同一规则判定是否加引号
|
||||
# (与 LLVM 自身 AsmWriter 的输出形态一致:`$"884f3d94ea4d0123.md5_F"`)。
|
||||
# 另:引号名 @"..." 不支持转义,名字含 `"`/`\` 时两种形态都非法 ⇒ 见
|
||||
# _AddWeakComdat 内的 fail-fast(不静默替换、不跳过)。
|
||||
_LLVM_UNQUOTED_NAME_RE: re.Pattern[str] = re.compile(
|
||||
r'[-a-zA-Z$._][-a-zA-Z$._0-9]*\Z')
|
||||
|
||||
|
||||
def _AddWeakComdat(ll_content: str) -> str:
|
||||
"""为 weak/linkonce 系列 函数/全局 定义补 COMDAT(COFF/PE 必需)。
|
||||
|
||||
@@ -604,7 +872,17 @@ def _AddWeakComdat(ll_content: str) -> str:
|
||||
return ll_content
|
||||
decls: list[str] = []
|
||||
for name in dict.fromkeys(comdat_names):
|
||||
if re.match(r'^[0-9A-Za-z_$.-]+$', name):
|
||||
# fail-fast:LLVM 的引号标识符 @"..." **不支持转义**(LLLexer 读到 `"` 即认为名字
|
||||
# 结束),故名字里含 `"` / `\` / 换行时,加不加引号都产不出合法 IR。此处不静默
|
||||
# 跳过、也不做字符替换(那会把符号名改得与源码/其它模块不一致,链接期更难查),
|
||||
# 直接指名报错。名字来自弱符号命名 `<sha1>.<标识符>`,出现这些字符说明上游符号名
|
||||
# 构造有误,应在上游修正。
|
||||
if '"' in name or '\\' in name or '\n' in name or '\r' in name:
|
||||
raise RuntimeError(
|
||||
f'comdat 名无法表达为 LLVM 标识符(含双引号/反斜杠/换行): {name!r};'
|
||||
'LLVM 引号名 @"..." 不支持转义,产出即非法 IR(opt 解析报 '
|
||||
'"expected top-level entity")。请在上游符号名构造处修正。')
|
||||
if _LLVM_UNQUOTED_NAME_RE.match(name):
|
||||
decls.append(f'${name} = comdat any')
|
||||
else:
|
||||
decls.append(f'$"{name}" = comdat any')
|
||||
@@ -636,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) -> 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, ctrlc_stack_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)
|
||||
@@ -671,7 +949,13 @@ class Phase2Translator:
|
||||
# (零影响,不构造任何 DI 元数据);True=App 源模块的 .ll 携带
|
||||
# !llvm.dbg.cu + 每函数 DISubprogram + 入口 DILocation,供 ASan /
|
||||
# llvm-symbolizer 反查 ".vp:函数起始行"。
|
||||
self.dwarf_enabled: bool = dwarf_enabled
|
||||
self.dwarf_enabled: bool = dwarf_enabled
|
||||
# Ctrl+C 调用栈(--ctrlc-stack):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
|
||||
# 调用点热点统计(--hotspot-sites):None=未启用(零影响);str=调用点计数
|
||||
# 落盘文件路径(每函数计时落同目录 hotspot_time.txt)。各模块运行时自带
|
||||
# Ctrl+C 处理器、统一以追加方式写该路径;含 main 的模块在 main 入口清空一次
|
||||
@@ -889,7 +1173,9 @@ class Phase2Translator:
|
||||
# 行为与改动前完全一致(零影响)。
|
||||
# --dwarf(函数级 DWARF)同理:缓存 .ll/.obj 不含 !llvm.dbg.cu,必须一并
|
||||
# 强制重译,否则开启开关后仍复用旧产物、看不到任何 .vp 行号。
|
||||
hotspot_on: bool = bool(self.hotspot_sites_file) or bool(self.dwarf_enabled)
|
||||
# --ctrlc-stack 依赖同一份 .debug_line 行表(Config 已联动开启 --dwarf),
|
||||
# 这里显式纳入,避免将来联动改动后静默复用无 DI 的旧产物。
|
||||
hotspot_on: bool = bool(self.hotspot_sites_file) or bool(self.dwarf_enabled) or bool(self.ctrlc_stack_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 过)
|
||||
@@ -1497,7 +1783,10 @@ class Phase2Translator:
|
||||
- list(...) / dict(...) 作为构造函数调用 (ast.Call, func=Name)
|
||||
"""
|
||||
try:
|
||||
tree = ast.parse(code)
|
||||
# `.vp` 源码须先过 Viper 软关键字层(`enum X:` / `fn` 等)再解析:
|
||||
# 否则此处 SyntaxError 会静默 return 原 code(自动注入 import _list/_dict
|
||||
# 失效),与主翻译路径(Shell/TransPyC 已展开)口径不一致。
|
||||
tree = ast.parse(ExpandSoftKeywords(code))
|
||||
except SyntaxError:
|
||||
return code
|
||||
|
||||
@@ -1562,8 +1851,11 @@ class Phase2Translator:
|
||||
"""翻译单个源文件,嵌入相关 .stub.ll 声明
|
||||
|
||||
dwarf_enabled: 函数级 DWARF 调试元数据(--dwarf),默认 False(零影响)。
|
||||
仅 App 源模块传 True;include 文件的 .obj 会落进跨项目共享的
|
||||
includes.binary 预编译缓存,注入 DI 会污染该缓存,故一律保持 False。
|
||||
App 源模块在 --dwarf/--asan/--ctrlc-stack 时传 True。include 文件默认
|
||||
传 False(其 .obj 会落进跨项目共享的 includes.binary 预编译缓存,注入 DI
|
||||
会污染该缓存);仅 --ctrlc-stack 时传 True——此时 include 的 .obj 改落
|
||||
隔离目录(includes.binary_ctrlcstack,见 _get_precompiled_obj_path),
|
||||
不会污染共享缓存。
|
||||
"""
|
||||
_t0: float = time.time()
|
||||
with open(src_path, 'r', encoding='utf-8') as f:
|
||||
@@ -1582,7 +1874,11 @@ class Phase2Translator:
|
||||
f"仍按主流程 sha1 生成前缀以保证链接一致;下次构建将采用新 sha1", "SHA1")
|
||||
|
||||
# 自动导入: 检测 list 容器使用,自动注入 import _list
|
||||
# 注入行会让 AST 节点 lineno 整体下移 N(磁盘真实行号 = AST 行号 - N);
|
||||
# 记下 N 供报错渲染还原磁盘行号(如 _RaiseUnresolvedExpr)。
|
||||
_raw_newlines: int = code.count('\n')
|
||||
code = self._inject_auto_imports(code, src_path)
|
||||
_line_offset: int = code.count('\n') - _raw_newlines
|
||||
|
||||
if prebuilt_ModuleSha1Map is not None:
|
||||
ModuleSha1Map = prebuilt_ModuleSha1Map
|
||||
@@ -1595,12 +1891,22 @@ class Phase2Translator:
|
||||
# 未启用时不设置 → 与改动前逐字节一致(零影响)
|
||||
if dwarf_enabled:
|
||||
trans.translator.DwarfEnabled = True
|
||||
# Ctrl+C 调用栈(--ctrlc-stack):仅当本文件就是 dbgstack 模块时置位——
|
||||
# 只有该模块需要注入 @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
|
||||
trans.SliceLevel = self.slice_level
|
||||
trans.translator.SliceLevel = self.slice_level
|
||||
trans.translator.SliceCount = 0
|
||||
trans.translator.SliceInfos = []
|
||||
trans.translator.LlvmGen = None
|
||||
trans.translator._module_sha1 = sha1
|
||||
trans.translator._module_sha1 = sha1
|
||||
trans.translator._line_offset = _line_offset
|
||||
trans.translator._ModuleSha1Map = ModuleSha1Map
|
||||
trans.translator._struct_sha1_map = self.struct_sha1_map
|
||||
trans.translator._global_function_default_args = self.function_default_args
|
||||
@@ -1791,6 +2097,14 @@ class Phase2Translator:
|
||||
out_dir = os.path.dirname(out_path)
|
||||
if out_dir:
|
||||
os.makedirs(out_dir, exist_ok=True)
|
||||
# --ctrlc-stack:核对注入钩子确实生效——本模块要求注入 @llvm.global_ctors
|
||||
# 构造器(StructGen.finalize 里落成 .ll 文本之前),若产物中没有该全局
|
||||
# 说明钩子不可用(如序列化路径被改动绕过),显式报错终止,不静默产出
|
||||
# 缺注册的产物(那会让整个功能静默失效)。
|
||||
if _ctrlc_stack_ctor and 'llvm.global_ctors' not in result:
|
||||
raise RuntimeError(
|
||||
'--ctrlc-stack: dbgstack 模块未注入 @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)
|
||||
# 后处理:添加 llvmlite 不支持的 LLVM 属性(如 willreturn, mustprogress)
|
||||
@@ -1904,6 +2218,13 @@ 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 主路径
|
||||
# 不依赖帧指针,但符号表不可用时要靠 RBP 链兜底——llc 默认
|
||||
# omit-frame-pointer 会让 RBP 链只走出 1~2 帧(实测"共 2 帧")。
|
||||
# 只在开启该开关时加,关闭时命令与改前逐字一致。
|
||||
cmd = [self.compile_cmd] + self.compile_flags \
|
||||
+ ['-frame-pointer=all', '-o', obj_path, compile_input]
|
||||
return subprocess.run(cmd, capture_output=True, text=True, cwd=cwd)
|
||||
|
||||
def _compile_ll_files(self, active_sha1s: set[str]) -> None:
|
||||
@@ -2379,8 +2700,12 @@ class Phase2Translator:
|
||||
continue
|
||||
py_path: str = os.path.join(root, fname)
|
||||
code, tree = parse_python_file(py_path)
|
||||
# 字符串预过滤:不含 CVTable 的文件跳过 AST 遍历
|
||||
if tree is None or 'CVTable' not in code:
|
||||
# 字符串预过滤:不含 CVTable 的文件跳过 AST 遍历。
|
||||
# `virtual class` / `virtual def` 修饰符在源码里写的是 `virtual`
|
||||
# (AST 上才变成 @t.CVTable 装饰器),故预过滤必须同时放行
|
||||
# `virtual`/`novirtual`,否则含修饰符的 includes 类会被整段跳过——
|
||||
# 静默不预收集虚表状态(与本题"不得静默丢弃修饰符"同族问题)。
|
||||
if tree is None or ('CVTable' not in code and 'virtual' not in code):
|
||||
continue
|
||||
try:
|
||||
for node in ast.iter_child_nodes(tree):
|
||||
@@ -2394,6 +2719,25 @@ class Phase2Translator:
|
||||
elif isinstance(dec, ast.Name):
|
||||
if dec.id == 'CVTable':
|
||||
has_cvtable = True
|
||||
# 方法级 virtual 触发类级 vtable(与
|
||||
# HandlesClassDef._resolve_class_flags 同口径:类级
|
||||
# NoVTable / 无真实父类两种可判定形态)。
|
||||
# 记录"类级 CVTable"单独一份:它是 vtable 槽筛选的
|
||||
# VtableByClassLevel(忽略方法级标记),两者不可混用。
|
||||
class_level_cvtable: bool = has_cvtable
|
||||
if not has_cvtable:
|
||||
_has_real_parent: bool = False
|
||||
for _b in node.bases:
|
||||
_bn: str = None
|
||||
if hasattr(_b, 'id'):
|
||||
_bn = _b.id
|
||||
elif hasattr(_b, 'attr'):
|
||||
_bn = _b.attr
|
||||
if _bn and _bn not in ('Object', 'CVTable', 'NoVTable', 'CStruct', 'CUnion', 'CEnum', 'REnum', 'Exception', 'Enum'):
|
||||
_has_real_parent = True
|
||||
break
|
||||
if ClassHasFuncLevelCVTable(node) and not _has_real_parent:
|
||||
has_cvtable = True
|
||||
if not has_cvtable:
|
||||
continue
|
||||
class_name: str = node.name
|
||||
@@ -2407,6 +2751,11 @@ class Phase2Translator:
|
||||
# 构造函数不放入 vtable(避免子类与基类 vtable 大小不一致破坏多态分派)
|
||||
if method_name in ('__new__', '__init__', '__before_init__'):
|
||||
continue
|
||||
# 方法级修饰符:`novirtual def`(⇒ @t.NoVTable)不进槽;
|
||||
# 判据唯一源 = SymbolUtils.IsVtableSlotMethod,与
|
||||
# HandlesClassDef / TPV _should_method_be_virtual 同口径。
|
||||
if not IsVtableSlotMethod(item, class_level_cvtable):
|
||||
continue
|
||||
if method_name == '__init__':
|
||||
func_full_name: str = f"{class_name}.__init__"
|
||||
elif method_name == '__call__':
|
||||
@@ -2437,6 +2786,14 @@ class Phase2Translator:
|
||||
|
||||
def _scan_include_libraries(self) -> None:
|
||||
"""扫描 includes 目录,检测被使用的模块对应的库文件和源文件"""
|
||||
# --ctrlc-stack:强制纳入 dbgstack 模块 —— 无任何用户代码 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 not self.used_includes:
|
||||
return
|
||||
|
||||
@@ -2488,6 +2845,18 @@ 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 源文件确实被纳入待编译集合。找不到就必然
|
||||
# 得不到 Install 符号、构造器无处注入(功能静默失效)——显式报错终止,
|
||||
# 不静默跳过(多半是 project.json 的 includes 未包含仓库的 includes 目录)。
|
||||
if self.ctrlc_stack_enabled:
|
||||
_dbgstack_found: bool = any(
|
||||
os.path.basename(_p) in _CTRLC_STACK_MODULE_FILES
|
||||
for _p in self.extra_py_files)
|
||||
if not _dbgstack_found:
|
||||
raise RuntimeError(
|
||||
'--ctrlc-stack: 在 includes 目录中找不到 dbgstack.vp/.py,'
|
||||
'无法编译并链接 Install()(请确认 project.json 的 includes '
|
||||
f'包含仓库 includes 目录;已扫描: {self.include_dirs})')
|
||||
|
||||
def _scan_dir_for_libs(self, directory: str, module_name: str) -> None:
|
||||
"""递归扫描目录中的库文件和源文件
|
||||
@@ -2734,6 +3103,15 @@ class Phase2Translator:
|
||||
|
||||
预编译目录位于 include 目录同级,命名为 {basename}.binary/。
|
||||
文件命名为 {sha1}.{triple_safe}.{rel_model}.obj,区分目标平台和重定位模型。
|
||||
|
||||
--ctrlc-stack:include 模块也带 DWARF 行表(栈帧要能落到 includes/* 的源行),
|
||||
此类 .obj 不得回写进跨项目共享的 includes.binary(会污染其它项目的预编译
|
||||
缓存),改落**兄弟目录** {basename}.binary + 后缀(不新增层级,形态与
|
||||
ASan 变体目录 includes.binary_asan 一致)。
|
||||
关闭时后缀为空 → 路径与改动前逐字一致(含 --dwarf/--asan 单独开启的情形)。
|
||||
--asan 与 --ctrlc-stack 同开:ASan 插桩也会改变 include obj 内容(见
|
||||
_llc_compile),两者产物不可互相复用,故再叠加 _asan 后缀区分
|
||||
(优先级:是否隔离由 --ctrlc-stack 决定,--asan 只决定后缀再叠加 _asan)。
|
||||
"""
|
||||
AbsSrcPath: str = os.path.abspath(src_path)
|
||||
RelModel: str = 'static'
|
||||
@@ -2749,7 +3127,10 @@ class Phase2Translator:
|
||||
continue
|
||||
inc_parent: str = os.path.dirname(abs_inc)
|
||||
inc_base: str = os.path.basename(abs_inc)
|
||||
precompiled_dir: str = os.path.join(inc_parent, inc_base + '.binary')
|
||||
_iso_suffix: str = ''
|
||||
if self.ctrlc_stack_enabled:
|
||||
_iso_suffix = '_ctrlcstack_asan' if self.asan_mode else '_ctrlcstack'
|
||||
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")
|
||||
except ValueError:
|
||||
@@ -2774,6 +3155,19 @@ class Phase2Translator:
|
||||
# 本函数行为与改动前逐字节一致(零影响)。
|
||||
hotspot_on: bool = bool(self.hotspot_sites_file)
|
||||
|
||||
# --ctrlc-stack: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
|
||||
# 内的旧 .obj)一并失效重译——复用上一次无 DI 的产物会让行表静默缺失。
|
||||
# 与 hotspot_on 处理方式一致;同时覆盖 dbgstack.vp(其 .obj 还须含
|
||||
# @llvm.global_ctors 启动期构造器)。取舍:开启本开关时 include 目标文件每
|
||||
# 次构建都重译(放弃隔离缓存的复用,换取"必然带 DWARF"的正确性保证),
|
||||
# 仅调试模式可接受。开关关闭时 include_dwarf=False,本函数行为与改动前
|
||||
# 逐字节一致(零影响,含 --dwarf/--asan 单独开启的情形)。
|
||||
include_dwarf: bool = bool(self.ctrlc_stack_enabled)
|
||||
|
||||
# 构建 include 文件的模块 SHA1 映射(预计算所有文件,构建完整映射)
|
||||
include_ModuleSha1Map = self._build_current_ModuleSha1Map(None)
|
||||
need_translate = [] # [(src_path, sha1, module_name, ll_path, obj_path, precompiled_obj)]
|
||||
@@ -2812,18 +3206,20 @@ class Phase2Translator:
|
||||
self._collect_inline_symbols(src_path)
|
||||
continue
|
||||
|
||||
# --hotspot-sites:include 的 .py 未变时下面两处会因旧 .obj 存在而直接跳过,
|
||||
# 复用上一次可能未插桩的目标文件。启用热点时先让这两类旧 .obj 一并失效,
|
||||
# 使其必然走入 need_translate 重新翻译并嵌入热点计数/计时 IR。
|
||||
# --hotspot-sites / --ctrlc-stack:include 的 .py 未变时下面两处会因旧
|
||||
# .obj 存在而直接跳过,复用上一次(未插桩 / 不含 DI)的目标文件。
|
||||
# 启用时先让这两类旧 .obj 一并失效,使其必然走入 need_translate 重新
|
||||
# 翻译并嵌入热点计数/计时 IR 或 DWARF 行表。
|
||||
# 用 os.path.isfile 而非 _CachedIsFile:避免把"已存在"写入存在性缓存后又被
|
||||
# 本处删除,使下面两处判定读到过期缓存而漏编(与 App 主循环同一写法)。
|
||||
if hotspot_on:
|
||||
if hotspot_on or include_dwarf:
|
||||
for _stale_obj in (precompiled_obj, obj_path):
|
||||
if _stale_obj and os.path.isfile(_stale_obj):
|
||||
try:
|
||||
os.remove(_stale_obj)
|
||||
except OSError as e:
|
||||
# 不中断构建,但真实错误(如权限不足)必须打印告警,不静默吞掉
|
||||
# (报文保持原样:关闭开关时与改动前逐字一致)
|
||||
_vlog().warning(
|
||||
f"Phase2Translator: 删除热点插桩 include 模块的旧目标文件失败: {_stale_obj}: {e}")
|
||||
|
||||
@@ -2954,7 +3350,9 @@ class Phase2Translator:
|
||||
include_ModuleSha1Map,
|
||||
hotspot_sites_file=self.hotspot_sites_file,
|
||||
hotspot_sites_all=self.hotspot_sites_all,
|
||||
hotspot_callees=self.hotspot_callees
|
||||
hotspot_callees=self.hotspot_callees,
|
||||
ctrlc_stack_enabled=self.ctrlc_stack_enabled,
|
||||
dwarf_enabled=self.ctrlc_stack_enabled
|
||||
)
|
||||
in_progress[future] = item
|
||||
|
||||
@@ -2973,7 +3371,9 @@ class Phase2Translator:
|
||||
include_ModuleSha1Map,
|
||||
hotspot_sites_file=self.hotspot_sites_file,
|
||||
hotspot_sites_all=self.hotspot_sites_all,
|
||||
hotspot_callees=self.hotspot_callees
|
||||
hotspot_callees=self.hotspot_callees,
|
||||
ctrlc_stack_enabled=self.ctrlc_stack_enabled,
|
||||
dwarf_enabled=self.ctrlc_stack_enabled
|
||||
)
|
||||
in_progress[future] = item
|
||||
remaining.clear()
|
||||
@@ -3017,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)
|
||||
self._translate_file(src_path, ll_path, prebuilt_ModuleSha1Map=include_ModuleSha1Map, dwarf_enabled=self.ctrlc_stack_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)
|
||||
self._translate_file(src_path, ll_path, prebuilt_ModuleSha1Map=include_ModuleSha1Map, dwarf_enabled=self.ctrlc_stack_enabled)
|
||||
|
||||
# ========== 阶段2:串行后处理(dso_local + stub_decls 注入)==========
|
||||
compile_tasks = [] # [(dso_ll_path, obj_path, src_path, sha1, module_name, ll_path, precompiled_obj, ll_content)]
|
||||
@@ -3035,6 +3435,18 @@ class Phase2Translator:
|
||||
with open(ll_path, 'r', encoding='utf-8') as f:
|
||||
ll_content = f.read()
|
||||
|
||||
# --ctrlc-stack 提醒: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) \
|
||||
and '!llvm.dbg.cu' not in ll_content:
|
||||
_vlog().warning(
|
||||
f"--ctrlc-stack: include 模块 {module_name}.py 发射了 define 但 IR 未携带 "
|
||||
f"DWARF (!llvm.dbg.cu)——该模块内联帧在 Ctrl+C 栈里将只显示地址: {ll_path}")
|
||||
|
||||
# dso_local 正则标注(使用 ^ + MULTILINE 避免匹配字符串常量内容中的 define/declare)
|
||||
ll_content = re.sub(r'^(define|declare)\s+(private|internal|available_externally|linkonce|weak|common|appending|extern_weak|linkonce_odr|weak_odr|external)\s+', r'\1 \2 dso_local ', ll_content, flags=re.MULTILINE)
|
||||
ll_content = re.sub(r'^(define|declare)\s+(?!private|internal|available_externally|linkonce|weak|common|appending|extern_weak|linkonce_odr|weak_odr|external|dso_local)', r'\1 dso_local ', ll_content, flags=re.MULTILINE)
|
||||
@@ -3190,7 +3602,8 @@ class Phase2Translator:
|
||||
result = compile_results.get(task)
|
||||
if result and result.returncode == 0:
|
||||
self.extra_link_files.append(obj_path)
|
||||
# 复制到 includes.binary 预编译目录
|
||||
# 复制到 includes.binary 预编译目录(--ctrlc-stack 时 precompiled_obj
|
||||
# 指向隔离目录 includes.binary_ctrlcstack,不会污染共享缓存)
|
||||
if precompiled_obj:
|
||||
precompiled_dir = os.path.dirname(precompiled_obj)
|
||||
try:
|
||||
@@ -3198,6 +3611,13 @@ class Phase2Translator:
|
||||
os.makedirs(precompiled_dir, exist_ok=True)
|
||||
shutil.copy2(obj_path, precompiled_obj)
|
||||
except Exception as _e:
|
||||
# --ctrlc-stack fail-fast:隔离目录创建 / 写回失败必须显式终止,
|
||||
# 不静默降级(否则本次构建的 include obj 拿不到、或落到共享缓存
|
||||
# 之外的位置,DWARF 行表与后续复用的前提都被破坏)。
|
||||
if self.ctrlc_stack_enabled:
|
||||
raise RuntimeError(
|
||||
f"--ctrlc-stack: include 预编译缓存写入隔离目录失败: "
|
||||
f"{precompiled_obj}: {_e}") from _e
|
||||
if _config_mode == "strict":
|
||||
_vlog().warning(f"预编译缓存写入失败: {_e}")
|
||||
# 注册符号
|
||||
@@ -3220,33 +3640,49 @@ class Phase2Translator:
|
||||
self._extract_include_param_names(sorted_files)
|
||||
|
||||
def _extract_include_param_names_from_dirs(self) -> None:
|
||||
"""从 include 目录的 Python 源码中提取函数参数名,存入 function_param_names
|
||||
"""从 include 目录的 Python 源码中提取函数参数名与默认值。
|
||||
|
||||
优化:单次递归遍历 AST,维护当前类上下文,O(N) 复杂度。
|
||||
复用 parse_python_file 的 AST 缓存。
|
||||
顶层 .vp/.py 直接解析;包子目录(如 includes/json)只在该包被
|
||||
used_includes 引用时递归解析——避免为每个工程全量递归 includes
|
||||
(fltk/vqt6/openssl 等上百个文件)带来的解析开销。
|
||||
默认值一并注册(跨模块缺参调用点据此填充,见 HandlesExprCall
|
||||
._HandleClosureCallLlvm),此前只注册参数名,导致 include 模块的
|
||||
尾随默认参数在调用点被零值填充。
|
||||
"""
|
||||
for includes_dir in self.include_dirs:
|
||||
if not os.path.isdir(includes_dir):
|
||||
continue
|
||||
for filename in os.listdir(includes_dir):
|
||||
if not (filename.endswith('.vp') or filename.endswith('.py')):
|
||||
continue
|
||||
src_path = os.path.join(includes_dir, filename)
|
||||
try:
|
||||
source, tree = parse_python_file(src_path)
|
||||
if tree is None:
|
||||
for entry in os.listdir(includes_dir):
|
||||
entry_path = os.path.join(includes_dir, entry)
|
||||
targets: list[str] = []
|
||||
if os.path.isdir(entry_path):
|
||||
if not self.used_includes or entry not in self.used_includes:
|
||||
continue
|
||||
sha1 = compute_sha1(source)
|
||||
self._collect_function_params(tree, sha1)
|
||||
except Exception as e:
|
||||
_vlog().warning(f" 提取参数名失败 {src_path}: {e}")
|
||||
for root, dirs, files in os.walk(entry_path):
|
||||
dirs[:] = [d for d in dirs if not d.startswith('.') and d != '__pycache__']
|
||||
targets.extend(os.path.join(root, f) for f in files
|
||||
if f.endswith('.vp') or f.endswith('.py'))
|
||||
elif entry.endswith('.vp') or entry.endswith('.py'):
|
||||
targets.append(entry_path)
|
||||
for src_path in targets:
|
||||
try:
|
||||
source, tree = parse_python_file(src_path)
|
||||
if tree is None:
|
||||
continue
|
||||
sha1 = compute_sha1(source)
|
||||
self._collect_function_params(tree, sha1)
|
||||
except Exception as e:
|
||||
_vlog().warning(f" 提取参数名失败 {src_path}: {e}")
|
||||
|
||||
def _collect_function_params(self, tree: ast.AST, sha1: str | None) -> None:
|
||||
"""单次递归遍历 AST 收集函数参数名(含类方法),O(N) 复杂度
|
||||
"""单次递归遍历 AST 收集函数参数名(含类方法)与默认值,O(N) 复杂度
|
||||
|
||||
遍历时维护 current_class 上下文:遇到 ClassDef 进入类作用域,
|
||||
遇到 FunctionDef 记录参数名并标注所属类,函数内部的嵌套定义
|
||||
不再属于外层类。
|
||||
|
||||
默认值只登记非方法(模块级函数)的裸名键,减少与其它模块同名函数的
|
||||
误配;带 sha1 / 类名限定的键始终登记,查找时精确键优先。
|
||||
"""
|
||||
def visit(node: ast.AST, current_class: str | None) -> None:
|
||||
for child in ast.iter_child_nodes(node):
|
||||
@@ -3263,6 +3699,22 @@ class Phase2Translator:
|
||||
self.function_param_names[f'{current_class}.{func_name}'] = param_names
|
||||
if sha1:
|
||||
self.function_param_names[f'{sha1}.{current_class}.{func_name}'] = param_names
|
||||
defaults = getattr(child.args, 'defaults', []) or []
|
||||
if defaults:
|
||||
default_vals = []
|
||||
for d in defaults:
|
||||
if isinstance(d, ast.Constant):
|
||||
default_vals.append(d.value)
|
||||
else:
|
||||
default_vals.append(None)
|
||||
if sha1:
|
||||
self.function_default_args[f'{sha1}.{func_name}'] = default_vals
|
||||
if current_class:
|
||||
self.function_default_args[f'{current_class}.{func_name}'] = default_vals
|
||||
if sha1:
|
||||
self.function_default_args[f'{sha1}.{current_class}.{func_name}'] = default_vals
|
||||
else:
|
||||
self.function_default_args[func_name] = default_vals
|
||||
# 函数内部的嵌套定义不属于外层类
|
||||
visit(child, None)
|
||||
else:
|
||||
@@ -3733,6 +4185,197 @@ class Phase2Translator:
|
||||
_vlog().warning(f"剥离可执行文件调试段失败: {_e}", "Exception")
|
||||
break
|
||||
|
||||
def _WriteStackMap(self, exe_path: str) -> None:
|
||||
"""--ctrlc-stack:链接成功后导出"地址→源行"旁文件 <exe>.stackmap。
|
||||
|
||||
用 llvm-dwarfdump 解析最终 PE 的 .debug_line(地址→文件/行)与
|
||||
.debug_info(DISubprogram 的 low/high_pc + DW_AT_name → 函数名),
|
||||
换算为 RVA 后落盘。旁文件规格(全部小端 little-endian):
|
||||
|
||||
头部 20 字节 = magic(4)="TPCS" + version(u32=1) + entry_count(u32)
|
||||
+ str_count(u32) + str_bytes(u32)
|
||||
记录 entry_count × 20 字节(按 rva 升序):
|
||||
rva(u64) + file_off(u32) + line(u32) + func_off(u32)
|
||||
字符串池 str_bytes 字节(紧接记录区,UTF-8,全部以 '\\0' 结尾)
|
||||
|
||||
口径(必须与运行期严格一致):运行期 dbgstack.vp 取
|
||||
`rva = 帧地址 − GetModuleHandleA(NULL)`,即"链接期 ImageBase 之上的偏移",
|
||||
故写端唯一正确口径是 **`RVA = dwarfdump 打印的地址 − PE.ImageBase`**。
|
||||
|
||||
⚠ 实测 TransPyV.exe 的 DWARF 里同时存在两个地址域:
|
||||
* 绝大多数行/子程序地址是**已重定位的 VA**(0x14000xxxx,含 ImageBase);
|
||||
* 另有 22638 条行记录 / 756 个 subprogram 的地址**恰为 0**——它们是**被
|
||||
链接器按 COMDAT 去重丢弃的泛型实例副本**(实测 36 个 `list[T].*` 名字,
|
||||
每个都有 1 份 VA 副本 + 15~21 份 0 地址副本),并非解析错误。
|
||||
旧实现按"整段最小地址是否 >= ImageBase"**整表**判定:只要有一行 0,就整表都不
|
||||
减基址 ⇒ 全部 VA 行留在 0x140000000+ 域、与运行期 RVA 域相差一个 ImageBase,
|
||||
运行期下界查找对任意帧都落到同一条记录上(用户实测所有帧都显示同一行)。
|
||||
现改为**逐条**归一化:地址落在映像 VA 区间 [ImageBase, ImageBase+SizeOfImage)
|
||||
的记录保留(RVA = 地址 − ImageBase);其余(未重定位的 0、越界值)一律**丢弃并
|
||||
统计**(不静默留垃圾条目)。同一 RVA 只保留行号最大的一条。
|
||||
|
||||
写出前自检(任一不满足即 fail-fast 抛异常,遵循 --ctrlc-stack 既有风格):
|
||||
记录数 / min~max RVA 与 SizeOfImage 的关系 / 所有 RVA ∈ [0, SizeOfImage) /
|
||||
RVA 严格升序 / 至少若干条落在运行期可能出现的 RVA 区间
|
||||
(粗判据 [.text 节 RVA, SizeOfImage))。
|
||||
|
||||
任何一步失败都直接抛异常终止编译(--ctrlc-stack 的硬要求,无兜底)。
|
||||
"""
|
||||
import struct
|
||||
import bisect
|
||||
|
||||
def _Fail(msg: str) -> None:
|
||||
full: str = msg + '(这是 --ctrlc-stack 的要求)'
|
||||
_vlog().error(full)
|
||||
raise RuntimeError(full)
|
||||
|
||||
layout = _ReadPEImageLayout(exe_path)
|
||||
if layout is None:
|
||||
_Fail(f"--ctrlc-stack 只支持 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 域")
|
||||
img_lo: int = image_base
|
||||
img_hi: int = image_base + size_of_image
|
||||
# 运行期可能出现的 RVA 区间的**粗判据**下界:.text 节 RVA(缺失时退化为最小节 RVA)
|
||||
text_rva: int = min((v[0] for v in sections.values()), default=0x1000)
|
||||
if '.text' in sections:
|
||||
text_rva = sections['.text'][0]
|
||||
max_sec_end: int = max((v[0] + v[1] for v in sections.values()), default=size_of_image)
|
||||
|
||||
dwarfdump: str | None = shutil.which('llvm-dwarfdump') or shutil.which('llvm-dwarfdump.exe')
|
||||
if not dwarfdump:
|
||||
_Fail("--ctrlc-stack 找不到 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}")
|
||||
if proc.returncode != 0:
|
||||
_Fail(f"--ctrlc-stack 的 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 为空?)")
|
||||
|
||||
# ---- 行记录:逐条归一化为"运行期 RVA";不能归一的一律丢弃并统计 ----
|
||||
dropped_rows: int = 0
|
||||
dropped_row_samples: list[int] = []
|
||||
best: dict[int, tuple[str, int]] = {}
|
||||
for addr, path, line in rows:
|
||||
if not (img_lo <= addr < img_hi):
|
||||
dropped_rows += 1
|
||||
if len(dropped_row_samples) < 5:
|
||||
dropped_row_samples.append(addr)
|
||||
continue
|
||||
rva: int = addr - image_base
|
||||
cur = best.get(rva)
|
||||
if cur is None or line > cur[1]:
|
||||
best[rva] = (path, line)
|
||||
if dropped_rows:
|
||||
_vlog().warning(
|
||||
f" [--ctrlc-stack] 丢弃 {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 域")
|
||||
|
||||
# ---- 子程序(函数名):同样逐条归一化;不能归一的丢弃 ----
|
||||
funcs: list[tuple[int, int, str]] = _ParseDwarfSubprograms(dump_text)
|
||||
func_ranges: list[tuple[int, int, str]] = []
|
||||
dropped_funcs: int = 0
|
||||
for low, high, name in funcs:
|
||||
if not (img_lo <= low < img_hi):
|
||||
dropped_funcs += 1
|
||||
continue
|
||||
lo_rva: int = low - image_base
|
||||
hi_rva: int = high - image_base
|
||||
if hi_rva <= lo_rva:
|
||||
hi_rva = lo_rva + 1
|
||||
if hi_rva > size_of_image:
|
||||
hi_rva = size_of_image
|
||||
func_ranges.append((lo_rva, hi_rva, name))
|
||||
if dropped_funcs:
|
||||
_vlog().warning(
|
||||
f" [--ctrlc-stack] 丢弃 {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]
|
||||
|
||||
def _FuncNameFor(rva: int) -> str | None:
|
||||
i: int = bisect.bisect_right(func_lows, rva) - 1
|
||||
if i >= 0:
|
||||
low, high, name = func_ranges[i]
|
||||
if low <= rva < high:
|
||||
return name
|
||||
return None
|
||||
|
||||
# 字符串池:偏移 0 固定为一个空串('\0')哨兵,使 func_off=0 可无歧义表示
|
||||
# "无函数名";真实串偏移恒 >= 1。
|
||||
pool: bytearray = bytearray(b'\x00')
|
||||
str_offsets: dict[str, int] = {'': 0}
|
||||
|
||||
def _Intern(s: str) -> int:
|
||||
off: int | None = str_offsets.get(s)
|
||||
if off is None:
|
||||
off = len(pool)
|
||||
pool.extend(s.encode('utf-8'))
|
||||
pool.append(0)
|
||||
str_offsets[s] = off
|
||||
return off
|
||||
|
||||
records: list[tuple[int, int, int, int]] = []
|
||||
for rva in sorted(best.keys()):
|
||||
path, line = best[rva]
|
||||
name = _FuncNameFor(rva)
|
||||
records.append((rva, _Intern(path), line, _Intern(name) if name else 0))
|
||||
|
||||
# ---- 写端自检(fail-fast;与运行期 LoadMapFile 的校验口径一一对应)----
|
||||
rvas: list[int] = [r[0] for r in records]
|
||||
min_rva: int = rvas[0]
|
||||
max_rva: int = rvas[-1]
|
||||
for i in range(1, len(rvas)):
|
||||
if rvas[i] <= rvas[i - 1]:
|
||||
_Fail(f"--ctrlc-stack 自检失败:记录 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}) 之外")
|
||||
# 运行期可能出现的 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 落在运行期可能出现的区间"
|
||||
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" PE.SizeOfImage=0x{size_of_image:x}({size_of_image})"
|
||||
f" 各节最高结束 RVA=0x{max_sec_end:x}"
|
||||
f" | 全部∈[0,SizeOfImage)=是 严格升序=是"
|
||||
f" | 落在[.text 0x{text_rva:x},SizeOfImage)内={plausible} 条")
|
||||
|
||||
out: bytearray = bytearray()
|
||||
out.extend(b'TPCS')
|
||||
out.extend(struct.pack('<IIII', 1, len(records), len(str_offsets), len(pool)))
|
||||
for rva, file_off, line, func_off in records:
|
||||
out.extend(struct.pack('<QIII', rva, file_off, line, func_off))
|
||||
out.extend(pool)
|
||||
|
||||
map_path: str = exe_path + '.stackmap'
|
||||
try:
|
||||
with open(map_path, 'wb') as f:
|
||||
f.write(out)
|
||||
except Exception as e:
|
||||
_Fail(f"--ctrlc-stack 写出旁文件失败 {map_path}: {e}")
|
||||
_vlog().success(
|
||||
f" [--ctrlc-stack] 已写出地址→源行旁文件: {map_path}"
|
||||
f"({len(records)} 条记录,{len(out)} 字节)")
|
||||
|
||||
def _link_obj_files(self, active_sha1s: set[str]) -> None:
|
||||
"""将 active_sha1s 对应的 .obj 文件和 includes 库文件链接为可执行文件"""
|
||||
obj_files = []
|
||||
@@ -3861,6 +4504,14 @@ 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 的主回溯路径用
|
||||
# StackWalk64 / SymInitialize / SymFunctionTableAccess64 / SymGetModuleBase64
|
||||
# (均在 dbghelp),故开启该开关时必须显式链接 -ldbghelp,否则链接期报未定义符号。
|
||||
# 与 ASan 同理:库要排在目标文件之后 ⇒ 追加到 lib_flags。
|
||||
# (若确实无法链 dbghelp:把 includes/dbgstack.vp 的 DBGSTACK_USE_DBGHELP 置 0,
|
||||
# 退化为纯 RBP 链回溯,此时不该再带本 flag。)
|
||||
if self.ctrlc_stack_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)}")
|
||||
result = subprocess.run(
|
||||
@@ -3912,3 +4563,9 @@ SECTIONS {
|
||||
_vlog().error(f" {e}")
|
||||
_vlog().error(f"\n链接异常,立即终止编译。")
|
||||
sys.exit(1)
|
||||
|
||||
# --ctrlc-stack:链接成功后导出"地址→源行"旁文件(<exe>.stackmap)。
|
||||
# 刻意放在上面的链接 try/except 之外,使导出阶段的 fail-fast RuntimeError
|
||||
# 直接向上抛出终止编译,而不被"链接异常兜底"吞掉。
|
||||
if self.ctrlc_stack_enabled:
|
||||
self._WriteStackMap(exe_path)
|
||||
+387
-3
@@ -6,6 +6,7 @@ import llvmlite.ir as ir
|
||||
from lib.includes import t as _t
|
||||
from lib.includes.t import CTypeRegistry
|
||||
from lib.core.Handles.HandlesBase import CTypeInfo as _CTypeInfo, BuiltinTypeMap
|
||||
from lib.core.SymbolUtils import EnumIntWidth, _ENUM_DEFAULT_INT_WIDTH
|
||||
from lib.constants.config import DEFAULT_HOTSPOT_CALLEES
|
||||
|
||||
|
||||
@@ -155,6 +156,33 @@ class BaseGenMixin:
|
||||
self.global_struct_class: dict[str, str] = {}
|
||||
self.var_type_info: dict[str, Any] = {}
|
||||
self.var_const_flags: dict[str, bool] = {}
|
||||
# ---- const / volatile 登记(与 TransPyV 侧 VarEntry/FieldEntry 逐字段对应,
|
||||
# 对照表见 Debugger/type-descriptors.md「两侧语义对照表」)----
|
||||
# 分两层,因为 TPC 的函数内语句与模块级语句走不同生命周期:
|
||||
# 函数作用域层(随函数出栈清空,清零点 HandlesFunctions 函数出口):
|
||||
# var_const_flags 变量(局部/形参)自身 const
|
||||
# var_volatile_flags 变量自身 volatile
|
||||
# var_pointee_volatile_flags 变量所指物 volatile(rawptr[t.CVolatile|T])
|
||||
# 模块级层(模块存活期持久):
|
||||
# global_* 模块级同名三类
|
||||
# 类字段层(模块存活期持久):
|
||||
# field_const_flags '类名.字段名' -> True
|
||||
# field_volatile_flags '类名.字段名' -> True
|
||||
# field_*_names 裸字段名集合(IR 侧按 GEP 指令名回查,
|
||||
# GEP 以字段名命名,见 HandlesExprAttr:36 等)
|
||||
self.var_volatile_flags: dict[str, bool] = {}
|
||||
self.var_pointee_volatile_flags: dict[str, bool] = {}
|
||||
self.global_const_flags: dict[str, bool] = {}
|
||||
self.global_volatile_flags: dict[str, bool] = {}
|
||||
self.global_pointee_volatile_flags: dict[str, bool] = {}
|
||||
self.field_const_flags: dict[str, bool] = {}
|
||||
self.field_const_names: set[str] = set()
|
||||
self.field_volatile_flags: dict[str, bool] = {}
|
||||
self.field_volatile_names: set[str] = set()
|
||||
self.field_pointee_volatile_flags: dict[str, bool] = {}
|
||||
self.field_pointee_volatile_names: set[str] = set()
|
||||
# const 变量名 -> 声明处类型文本(仅用于报文的「声明处类型(含 const)」)
|
||||
self.const_decl_types: dict[str, str] = {}
|
||||
self._current_source_file: str | None = None
|
||||
self._current_source_lines: list[str] = []
|
||||
self._ns_cache: dict[str, str] = {}
|
||||
@@ -191,6 +219,18 @@ class BaseGenMixin:
|
||||
# _generate_structs 用不完整 class_members 调用 set_body(set_body 只能调用一次)
|
||||
# 用集合而非单值,因为嵌套特化类 _EmitClassLlvm 会清除单值,导致外层类被错误设置 body
|
||||
self._collecting_members_set: set[str] = set()
|
||||
# 已按父类展平的类成员表登记(HandlesImports._EmitExternalClassDeclLlvm /
|
||||
# HandlesClassDef._EmitClassLlvm 在展平完成时登记)。两处用途:
|
||||
# ① _TryLoadClassMembersFromPyi 的 guard 只在"已展平"时短路 ——
|
||||
# 停在自有字段的冻结表允许后续补齐(展平与加载顺序无关);
|
||||
# ② FuncGen._member_base 查询前据此判定是否需要重跑展平。
|
||||
self._members_flattened: set[str] = set()
|
||||
# 展平补齐重入保护:避免 _member_base → _TryLoadClassMembersFromPyi →
|
||||
# _EmitExternalClassDeclLlvm → _member_base 的同类递归。
|
||||
self._members_flatten_in_progress: set[str] = set()
|
||||
# 展平补齐的"依赖状态签名"(类名 → (struct 元素数, 成员表长度)):
|
||||
# 同一签名下补齐无效时不再重复尝试(HandlesImports.EnsureClassMembersFlattened)。
|
||||
self._members_flatten_repair_sig: dict[str, tuple[int, int]] = {}
|
||||
self._last_var_name: str | None = None
|
||||
self._variadic_info: dict | None = None
|
||||
self._vtable_copy_counter: int = 0
|
||||
@@ -217,6 +257,10 @@ class BaseGenMixin:
|
||||
# True=每个 define 发 DISubprogram + 入口首条指令发 DILocation。
|
||||
self.dwarf_enabled: bool = False
|
||||
self.DebugInfo: Any = None
|
||||
# Ctrl+C 调用栈(--ctrlc-stack):仅在翻译 includes/dbgstack.vp 这一个模块时
|
||||
# 置 True —— 落成 .ll 文本前为本模块注入 @llvm.global_ctors 构造器调用其
|
||||
# Install()(见 _RegisterDbgStackInstallViaCtor)。默认 False(零影响)。
|
||||
self.ctrlc_stack_ctor: bool = False
|
||||
pi: dict[str, bool | int] = self._platform_info
|
||||
# 根据目标平台设置宏(声明式,从三元组推导)
|
||||
if pi['is_windows']:
|
||||
@@ -280,6 +324,30 @@ class BaseGenMixin:
|
||||
return prefix
|
||||
return ''
|
||||
|
||||
def _FindStructByIrName(self, ns_name: str) -> ir.IdentifiedStructType | None:
|
||||
"""按 **IR 类型名**(`IdentifiedStructType.name`)查已注册结构体。
|
||||
|
||||
必要性:`self.structs` 的键有"裸短名 / sha1 前缀全名 / import 别名"多种,
|
||||
同一个 IR 名字可被多个键指向。若只按"键"判存在性,对同一 IR 名字会再建
|
||||
第二个 `IdentifiedStructType` 对象 ⇒ 单模块 .ll 里出现**两条同名 type 定义**
|
||||
(Body 逐字相同)。`llc` 容忍这种重复,但 `llvm-link` 报
|
||||
"error: redefinition of type" ⇒ 跨模块 alwaysinline 合并整体回退
|
||||
(现场:includes/ast/parser.vp 的 8312ce86a2fd82c0.Keyword —— 裸名
|
||||
'Keyword' 先被 .tokens 的导入占用,astaux 的同名类只剩 'KwNode' 键,
|
||||
随后按全名 '8312ce86a2fd82c0.Keyword' 请求时又建了一个同名新对象)。
|
||||
|
||||
IR 里类型的身份**就是名字**(不是 Python 对象),故存在性必须按名字判定:
|
||||
命中即复用同一对象(需要 body 时由既有 set_body 流程补,与"opaque 占位 →
|
||||
set_body"同语义)。名字不同的同名类冲突由 sha1 前缀命名负责区分,不靠
|
||||
"同名两个对象"。
|
||||
"""
|
||||
if not ns_name:
|
||||
return None
|
||||
for _st in self.structs.values():
|
||||
if isinstance(_st, ir.IdentifiedStructType) and _st.name == ns_name:
|
||||
return _st
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _CleanStructName(name: str) -> str:
|
||||
"""结构体名清洗:去掉指针标记/空格/修饰符(clean_name 既有行为)。
|
||||
@@ -299,6 +367,23 @@ class BaseGenMixin:
|
||||
.replace('const', 'const_').replace('volatile', 'volatile_'))
|
||||
return head + tail
|
||||
|
||||
def _enum_int_type(self, EnumName: str | None, MemberInfo: Any = None) -> ir.IntType:
|
||||
"""枚举运行时常量 / 枚举类型注解的整数位宽类型(两侧 ABI 同点)。
|
||||
|
||||
宽度 = "最宽成员类型",在枚举定义处推导并登记
|
||||
(HandlesClassDef._RegisterEnumMembers → enum_width),此处读取。
|
||||
优先用调用点手头的成员条目;退回按枚举名查符号表;再退回默认 i32
|
||||
(跨模块 stub 序列化不携带 enum_width、旧产物 → 默认 32,与改前逐字一致)。
|
||||
"""
|
||||
Width: int = EnumIntWidth(MemberInfo, 0)
|
||||
if Width > 0:
|
||||
return ir.IntType(Width)
|
||||
if EnumName:
|
||||
Table: Any = getattr(self, 'SymbolTable', None)
|
||||
if Table is not None:
|
||||
return ir.IntType(EnumIntWidth(Table.lookup(EnumName)))
|
||||
return ir.IntType(_ENUM_DEFAULT_INT_WIDTH)
|
||||
|
||||
def _get_or_create_struct(self, name: str, source_sha1: str | None = None, packed: bool = False) -> ir.IdentifiedStructType:
|
||||
clean_name: str = self._CleanStructName(name)
|
||||
# 如果名称包含模块前缀(如 "a9fa0f6200c09e65.zbit_writer"),
|
||||
@@ -319,6 +404,16 @@ class BaseGenMixin:
|
||||
if _existing_sha1 and _existing_sha1 != parts[0]:
|
||||
if clean_name in self.structs:
|
||||
return self.structs[clean_name]
|
||||
# 按 IR 名去重:该 IR 名字的 struct 可能已由**别的键**
|
||||
# (裸名/别名)注册 ⇒ 复用,绝不建第二个同名对象
|
||||
# (否则单模块 .ll 出现两条同名 type 定义 ⇒ llvm-link
|
||||
# "redefinition of type" ⇒ 跨模块内联整体回退)。
|
||||
_st_same: ir.IdentifiedStructType | None = self._FindStructByIrName(clean_name)
|
||||
if _st_same is not None:
|
||||
self.structs[clean_name] = _st_same
|
||||
if packed and not _st_same.packed:
|
||||
_st_same.packed = True
|
||||
return _st_same
|
||||
st_new: ir.IdentifiedStructType = ir.IdentifiedStructType(self.module, name=clean_name, packed=packed)
|
||||
self.structs[clean_name] = st_new
|
||||
return st_new
|
||||
@@ -354,6 +449,14 @@ class BaseGenMixin:
|
||||
st_full.packed = True
|
||||
return st_full
|
||||
ns_name: str = f"{source_sha1}.{clean_name}"
|
||||
# 按 IR 名去重(见 _FindStructByIrName):同一 IR 名字已有对象
|
||||
# ⇒ 复用同一对象,只补注册本键,绝不建第二个同名对象。
|
||||
_st_same2: ir.IdentifiedStructType | None = self._FindStructByIrName(ns_name)
|
||||
if _st_same2 is not None:
|
||||
self.structs[full_key] = _st_same2
|
||||
if packed and not _st_same2.packed:
|
||||
_st_same2.packed = True
|
||||
return _st_same2
|
||||
st_new: ir.IdentifiedStructType = ir.IdentifiedStructType(self.module, name=ns_name, packed=packed)
|
||||
self.structs[full_key] = st_new
|
||||
return st_new
|
||||
@@ -397,7 +500,10 @@ class BaseGenMixin:
|
||||
# 这对自引用 REnum(如 LLVMType 包含 LLVMType*)至关重要:
|
||||
# _parse_llvm_type 先通过此路径创建 opaque 结构体,_TryLoadStructFromStub 再 set_body。
|
||||
elif Entry and Entry.IsEnum and clean_name not in self.class_methods:
|
||||
return ir.IntType(32)
|
||||
# CEnum 存储宽度 = 最宽成员类型(登记在 Entry.enum_width;未登记
|
||||
# → 默认 i32)。此前此处恒 i32,与同文件 _enum_int_type 的常量/
|
||||
# 注解宽度分裂(宽成员枚举的类型与常量不同宽)。
|
||||
return self._enum_int_type(clean_name, Entry)
|
||||
# 治本修复:当 clean_name 是已注册的类(在 class_methods 或 class_members 中)时,
|
||||
# 不应走 IsExceptionClass/IsFunction/IsVariable 分支返回非结构体类型。
|
||||
# 否则 _get_or_create_struct 返回指针类型(如 _TypedPointerType)而非 IdentifiedStructType,
|
||||
@@ -412,9 +518,9 @@ class BaseGenMixin:
|
||||
return ir.PointerType(ir.IntType(8))
|
||||
if Entry and Entry.BaseType:
|
||||
if isinstance(Entry.BaseType, type) and issubclass(Entry.BaseType, _t.CEnum):
|
||||
return ir.IntType(32)
|
||||
return self._enum_int_type(clean_name, Entry)
|
||||
if Entry.BaseType is _t.CEnum:
|
||||
return ir.IntType(32)
|
||||
return self._enum_int_type(clean_name, Entry)
|
||||
ns_name: str
|
||||
if source_sha1:
|
||||
ns_name = f"{source_sha1}.{clean_name}"
|
||||
@@ -422,6 +528,17 @@ class BaseGenMixin:
|
||||
ns_name = f"{self._struct_sha1_map[clean_name]}.{clean_name}"
|
||||
else:
|
||||
ns_name = self._mangle_name(clean_name)
|
||||
# 按 IR 名去重(见 _FindStructByIrName):该 IR 名字的对象可能已由**别的键**
|
||||
# (裸名 / sha1 全名 / import 别名)注册 ⇒ 复用同一对象并补注册本键。
|
||||
# 否则单模块 .ll 会出现两条同名 type 定义 ⇒ llvm-link
|
||||
# "error: redefinition of type" ⇒ 跨模块 alwaysinline 合并整体回退
|
||||
# (现场:includes/ast/parser.vp 的 8312ce86a2fd82c0.Keyword 定义两次)。
|
||||
_st_same3: ir.IdentifiedStructType | None = self._FindStructByIrName(ns_name)
|
||||
if _st_same3 is not None:
|
||||
self.structs[clean_name] = _st_same3
|
||||
if packed and not _st_same3.packed:
|
||||
_st_same3.packed = True
|
||||
return _st_same3
|
||||
st: ir.IdentifiedStructType = ir.IdentifiedStructType(self.module, name=ns_name, packed=packed)
|
||||
self.structs[clean_name] = st
|
||||
# 尝试解析 typedef 的底层整数位宽
|
||||
@@ -843,6 +960,223 @@ class BaseGenMixin:
|
||||
info += f"\n → {source_line}"
|
||||
return info
|
||||
|
||||
# ========== const / volatile:判定、登记与报文(两侧同口径) ==========
|
||||
# 语义唯一来源见 Debugger/type-descriptors.md。TPC 与 TransPyV 必须**同判据、
|
||||
# 同报文**;任何未消除的差异都列在该文档的「已知差异」一节。
|
||||
|
||||
@staticmethod
|
||||
def _IsConstObject(Info: Any) -> bool:
|
||||
"""注解解析结果是否为「变量自身 const」的对象(C 语义下的 const 对象)。
|
||||
|
||||
判据(两侧统一):`(DataConst or VarConst)` 且 `PtrCount == 0`。
|
||||
`t.CConst` 在本仓库解析为 **DataConst**(t 模块分支:HandlesTypeMerge.py:192-196、
|
||||
TypeAnnotationResolver.py:382-389),其字段语义是「指向的数据 const」
|
||||
(HandlesBase.py:76-77)。因此:
|
||||
- 非指针类型(PtrCount==0):该限定退化为「该对象自身 const」⇒
|
||||
`a: const int` / `a: t.CConst | int` 的 a 禁止赋值/增量赋值/元素赋值;
|
||||
- 指针类型(PtrCount>=1):限定只作用于 pointee ⇒ `const T *p` 的 p
|
||||
仍可重新赋值(C 语义),**不**登记为 const 变量。
|
||||
后者就是全仓既有两处 `t.CConst | T | t.CPtr` 局部变量(fat32_types.vp:1591
|
||||
`buf_ptr`、elf.vp:509 `cmp_name`)在其作用域内被重新赋值却合法的原因。
|
||||
"""
|
||||
if Info is None:
|
||||
return False
|
||||
if not (getattr(Info, 'DataConst', False) or getattr(Info, 'VarConst', False)):
|
||||
return False
|
||||
return getattr(Info, 'PtrCount', 0) == 0
|
||||
|
||||
@staticmethod
|
||||
def _AnnotationDeclText(Annotation: Any) -> str:
|
||||
"""注解节点的源码文本(仅用于报文;取不到时给占位,不伪造)。"""
|
||||
if Annotation is None:
|
||||
return '(无注解)'
|
||||
try:
|
||||
return ast.unparse(Annotation)
|
||||
except Exception:
|
||||
return '<无法还原注解文本>'
|
||||
|
||||
@staticmethod
|
||||
def _AnnotationHasQualifier(Annotation: Any, AttrName: str) -> bool:
|
||||
"""注解子树里是否出现 `t.<AttrName>`(与 TransPyV TypeGenErr.has_decorator_marker 同口径)。"""
|
||||
if Annotation is None:
|
||||
return False
|
||||
for n in ast.walk(Annotation):
|
||||
if isinstance(n, ast.Attribute) and n.attr == AttrName:
|
||||
return True
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _AnnotationHasPointeeVolatile(Annotation: Any) -> bool:
|
||||
"""注解是否为 `rawptr[t.CVolatile | T]`(volatile 指针指向物,等价 C 的 volatile T*)。
|
||||
|
||||
与 TransPyV TypeGenErr.has_pointee_volatile_marker 同口径:只看 `rawptr[...]`
|
||||
的下标实参子树里有没有 CVolatile;多级指针(slice 仍是 Subscript)明确不支持。
|
||||
"""
|
||||
if not isinstance(Annotation, ast.Subscript):
|
||||
return False
|
||||
base: Any = Annotation.value
|
||||
if not isinstance(base, ast.Name) or base.id != 'rawptr':
|
||||
return False
|
||||
return BaseGenMixin._AnnotationHasQualifier(Annotation.slice, 'CVolatile')
|
||||
|
||||
def RegisterVarQualifiers(self, Name: str, Info: Any, Annotation: Any,
|
||||
IsGlobal: bool = False, IsParam: bool = False) -> None:
|
||||
"""登记一个变量(局部/形参/模块级全局)的 const / volatile 限定。
|
||||
|
||||
统一的登记入口(TPC 的形参、局部变量、模块级全局三处声明点都调本函数),
|
||||
保证「登记点」只有一份判据。
|
||||
|
||||
IsParam=True 时 **const 判据沿用形参的历史条件** `DataConst or VarConst`
|
||||
(不看指针层数):这是「形参保持现状 + 不得放宽任何既有 fail-fast」两条
|
||||
硬约束下的唯一选择——把形参也改成指针感知判据虽然更合 C 语义,但会**放宽**
|
||||
既有形参检查(`fmt: t.CConst | str` 这类指针形参不再登记),从约束上不允许。
|
||||
该历史条件对 `const T*` 形参过严,但全仓 462 个 .vp 普查(见
|
||||
Debugger/type-descriptors.md)表明**零个形参在其函数体内被重新赋值**,
|
||||
故这处差异在既有代码上不可观测。非形参的三种声明一律用指针感知判据
|
||||
`_IsConstObject`。
|
||||
"""
|
||||
if not Name:
|
||||
return
|
||||
if IsParam:
|
||||
IsConst: bool = bool(Info) and (
|
||||
getattr(Info, 'DataConst', False) or getattr(Info, 'VarConst', False))
|
||||
else:
|
||||
IsConst = self._IsConstObject(Info)
|
||||
if IsConst:
|
||||
if IsGlobal:
|
||||
self.global_const_flags[Name] = True
|
||||
else:
|
||||
self.var_const_flags[Name] = True
|
||||
self.const_decl_types[Name] = self._AnnotationDeclText(Annotation)
|
||||
if self._AnnotationHasQualifier(Annotation, 'CVolatile'):
|
||||
if IsGlobal:
|
||||
self.global_volatile_flags[Name] = True
|
||||
else:
|
||||
self.var_volatile_flags[Name] = True
|
||||
if self._AnnotationHasPointeeVolatile(Annotation):
|
||||
if IsGlobal:
|
||||
self.global_pointee_volatile_flags[Name] = True
|
||||
else:
|
||||
self.var_pointee_volatile_flags[Name] = True
|
||||
|
||||
def RegisterFieldQualifiers(self, ClassName: str | None, FieldName: str,
|
||||
Info: Any, Annotation: Any) -> None:
|
||||
"""登记一个类字段的 const / volatile 限定(键 `类名.字段名` + 裸字段名)。"""
|
||||
if not FieldName:
|
||||
return
|
||||
if self._IsConstObject(Info):
|
||||
if ClassName:
|
||||
self.field_const_flags[f'{ClassName}.{FieldName}'] = True
|
||||
self.field_const_names.add(FieldName)
|
||||
self.const_decl_types[FieldName] = self._AnnotationDeclText(Annotation)
|
||||
if self._AnnotationHasQualifier(Annotation, 'CVolatile'):
|
||||
if ClassName:
|
||||
self.field_volatile_flags[f'{ClassName}.{FieldName}'] = True
|
||||
self.field_volatile_names.add(FieldName)
|
||||
if self._AnnotationHasPointeeVolatile(Annotation):
|
||||
if ClassName:
|
||||
self.field_pointee_volatile_flags[f'{ClassName}.{FieldName}'] = True
|
||||
self.field_pointee_volatile_names.add(FieldName)
|
||||
|
||||
def _IsVarConst(self, Name: str) -> bool:
|
||||
"""变量(局部/形参/模块级全局)自身是否 const。"""
|
||||
return bool(Name) and (Name in self.var_const_flags or Name in self.global_const_flags)
|
||||
|
||||
def _IsVarVolatile(self, Name: str) -> bool:
|
||||
"""变量自身是否 volatile。"""
|
||||
return bool(Name) and (Name in self.var_volatile_flags or Name in self.global_volatile_flags)
|
||||
|
||||
def _IsVarPointeeVolatile(self, Name: str) -> bool:
|
||||
"""变量所指物是否 volatile(等价 C 的 volatile T* 解引用)。"""
|
||||
return bool(Name) and (Name in self.var_pointee_volatile_flags
|
||||
or Name in self.global_pointee_volatile_flags)
|
||||
|
||||
def _IsFieldConst(self, ClassName: str | None, FieldName: str) -> bool:
|
||||
"""类字段自身是否 const。
|
||||
|
||||
类名可解析时按精确键 `类名.字段名`(**不**回退裸名,避免"另一个同名
|
||||
const 字段"造成跨类误伤);类名解析不到时退化为裸名集合(保守报错)。
|
||||
"""
|
||||
if not FieldName:
|
||||
return False
|
||||
if ClassName:
|
||||
return f'{ClassName}.{FieldName}' in self.field_const_flags
|
||||
return FieldName in self.field_const_names
|
||||
|
||||
def _IsFieldVolatile(self, FieldName: str) -> bool:
|
||||
return bool(FieldName) and FieldName in self.field_volatile_names
|
||||
|
||||
def _IsFieldPointeeVolatile(self, FieldName: str) -> bool:
|
||||
return bool(FieldName) and FieldName in self.field_pointee_volatile_names
|
||||
|
||||
@staticmethod
|
||||
def _StripDedupSuffix(Name: str) -> list[str]:
|
||||
"""IR 名 -> 候选登记名(llvmlite 去重把重复名改成 `名.1` / `名.2`)。"""
|
||||
Cands: list[str] = [Name]
|
||||
Head, _, Tail = Name.rpartition('.')
|
||||
if Head and Tail.isdigit():
|
||||
Cands.append(Head)
|
||||
return Cands
|
||||
|
||||
def _IsPtrVolatile(self, ptr: Any) -> bool:
|
||||
"""按 IR 指针对应的**登记名**判定该次访问是否带 volatile。
|
||||
|
||||
alloca / GlobalVariable 均以变量名命名,GEP 以字段名命名
|
||||
(见 HandlesExprAttr 的 `gep(..., name=AttrName)`),故变量自身 volatile、
|
||||
变量所指物 volatile 与字段 volatile 都在此命中。名字取不到时返回 False
|
||||
(不做任何猜测/兜底)。
|
||||
|
||||
下标 GEP 额外带基址名(`subscript_<基址名>` / `subscript_ptr_<基址名>`,
|
||||
见 HandlesExprAttr/HandlesAssign 的下标改写):基址是 pointee-volatile
|
||||
变量/字段时,经该下标的 load/store 也要带 volatile(等价 C 的
|
||||
`volatile T* p` 的 `p[i]`)。**基址名后缀为空时名字与改造前逐字相同**
|
||||
⇒ 无 pointee-volatile 的代码 IR 逐字节不变。
|
||||
"""
|
||||
nm: Any = getattr(ptr, 'name', None)
|
||||
if not nm or not isinstance(nm, str):
|
||||
return False
|
||||
for C in self._StripDedupSuffix(nm):
|
||||
if self._IsVarVolatile(C) or self._IsVarPointeeVolatile(C):
|
||||
return True
|
||||
if C in self.field_volatile_names or C in self.field_pointee_volatile_names:
|
||||
return True
|
||||
BaseNames: list[str] = list(self.var_pointee_volatile_flags)
|
||||
BaseNames += list(self.global_pointee_volatile_flags)
|
||||
BaseNames += list(self.field_pointee_volatile_names)
|
||||
for B in BaseNames:
|
||||
if (nm == f'subscript_{B}' or nm.startswith(f'subscript_{B}.')
|
||||
or nm == f'subscript_ptr_{B}' or nm.startswith(f'subscript_ptr_{B}.')):
|
||||
return True
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _SubscriptSuffix(BaseNode: Any) -> str:
|
||||
"""下标基址名后缀(供 pointee-volatile 按 IR 名回查,见 `_IsPtrVolatile`)。
|
||||
|
||||
基址是 `Name`(变量)→ `_变量名`;是 `obj.字段` → `_字段名`;其余形态返回
|
||||
`''`。**返回 `''` 时下标 GEP 名与改造前逐字相同**(既有代码 IR 零变更)。
|
||||
"""
|
||||
if isinstance(BaseNode, ast.Name):
|
||||
return f'_{BaseNode.id}' if BaseNode.id else ''
|
||||
if isinstance(BaseNode, ast.Attribute):
|
||||
return f'_{BaseNode.attr}' if BaseNode.attr else ''
|
||||
return ''
|
||||
|
||||
def _ConstAssignMessage(self, Kind: str, Name: str) -> str:
|
||||
"""const 变量被赋值时的 fail-fast 报文(两侧同口径)。
|
||||
|
||||
含:文件/行/列 + 源码片段(`_get_node_info`)+ 变量名 + 声明处类型(含 const)
|
||||
+ 修法。
|
||||
"""
|
||||
DeclType: str = self.const_decl_types.get(Name, '(声明处类型未登记)')
|
||||
return (
|
||||
f"不能对 const 变量 '{Name}' {Kind}{self._get_node_info()}"
|
||||
f"(声明处类型(含 const):{DeclType};"
|
||||
f"修法:删掉该声明里的 `const` 描述符 / `t.CConst`,或改为声明时一次赋值、"
|
||||
f"之后只用非 const 的副本/中间量;const 对象在其作用域内禁止赋值、"
|
||||
f"增量赋值与元素赋值,且不会降级为警告)"
|
||||
)
|
||||
|
||||
# ========== 统一工具方法 ==========
|
||||
|
||||
def _create_string_global(self, s: str, name: str | None = None) -> ir.Constant:
|
||||
@@ -1766,6 +2100,56 @@ class BaseGenMixin:
|
||||
gv.linkage = 'appending'
|
||||
gv.initializer = ir.Constant(arr_ty, [entry_val])
|
||||
|
||||
def _RegisterDbgStackInstallViaCtor(self) -> None:
|
||||
"""用 @llvm.global_ctors 在进程启动期调用本模块的 Install()(--ctrlc-stack)。
|
||||
|
||||
仅在"本翻译单元就是 includes/dbgstack.vp"时由 StructGen.finalize() 调用
|
||||
(ctrlc_stack_ctor 由 Phase2Translator._translate_file 按源文件名置位)。
|
||||
生成 internal void @__dbgstack_install_ctor()(entry 块直接 call 本模块的
|
||||
Install() 并忽略其 int 返回值),再写入 appending linkage 的
|
||||
@llvm.global_ctors —— 每模块一份,链接器按 appending 语义拼进 .init_array,
|
||||
进程启动时执行一次。
|
||||
|
||||
为什么用构造器而不是"在 main 入口调用":适配 Projectrans 的"每源文件独立
|
||||
翻译单元"模型(不依赖哪个模块含 main),且 CRT 初始化期即执行,能覆盖
|
||||
"main 之前就卡死"的场景 —— 这正是本功能的用途。
|
||||
|
||||
同模块内取 Install 符号(不走跨模块声明),避免跨模块符号名/mangling 耦合。
|
||||
fail-fast:找不到 Install 符号、或模块内已存在 @llvm.global_ctors 时显式
|
||||
报错(不静默降级 —— 否则产物缺注册、功能静默失效)。
|
||||
"""
|
||||
install_fn: ir.Function | None = self._find_function('Install')
|
||||
if install_fn is None:
|
||||
raise RuntimeError(
|
||||
'--ctrlc-stack: 在 dbgstack 模块内找不到 Install() 函数,'
|
||||
'无法注入启动期构造器(不静默降级)')
|
||||
i32: ir.IntType = ir.IntType(32)
|
||||
i8p: ir.PointerType = ir.PointerType(ir.IntType(8))
|
||||
void_fnptr: ir.PointerType = ir.PointerType(ir.FunctionType(ir.VoidType(), []))
|
||||
ctor: ir.Function = ir.Function(
|
||||
self.module, ir.FunctionType(ir.VoidType(), []),
|
||||
name=self._UniqueGlobalName('__dbgstack_install_ctor'))
|
||||
ctor.linkage = 'internal'
|
||||
cb: ir.IRBuilder = ir.IRBuilder(ctor.append_basic_block(name='entry'))
|
||||
cb.call(install_fn, []) # Install() 无参;返回值(int)有意忽略
|
||||
cb.ret_void()
|
||||
if 'llvm.global_ctors' in self.module.globals:
|
||||
raise RuntimeError(
|
||||
'--ctrlc-stack: 模块内已存在 @llvm.global_ctors,'
|
||||
'无法注入 dbgstack 启动期构造器(不静默降级)')
|
||||
elem_ty: ir.LiteralStructType = ir.LiteralStructType([i32, void_fnptr, i8p])
|
||||
arr_ty: ir.ArrayType = ir.ArrayType(elem_ty, 1)
|
||||
entry_val: ir.Constant = ir.Constant(elem_ty, [
|
||||
ir.Constant(i32, 65535),
|
||||
# 传函数引用字符串(非 Function 对象):与 _RegisterHotspotSitesViaCtor 同法,
|
||||
# 否则 llvmlite 会以 str(Function) 渲染整个 define 体
|
||||
ir.Constant(void_fnptr, ctor.get_reference()),
|
||||
ir.Constant(i8p, None),
|
||||
])
|
||||
gv: ir.GlobalVariable = ir.GlobalVariable(self.module, arr_ty, name='llvm.global_ctors')
|
||||
gv.linkage = 'appending'
|
||||
gv.initializer = ir.Constant(arr_ty, [entry_val])
|
||||
|
||||
def _InstallHotspotSites(self) -> None:
|
||||
"""安装调用点热点统计(--hotspot-sites):种子 CLI 单模块路径。
|
||||
|
||||
|
||||
+220
-77
@@ -10,6 +10,15 @@ from lib.core.VLogger import get_logger as _vlog
|
||||
from lib.constants.config import mode as _config_mode
|
||||
|
||||
|
||||
class FailFastError(Exception):
|
||||
"""专用 fail-fast 异常:编译器主动抛出的编译错误(不可隐式转换/不可恢复)。
|
||||
|
||||
刻意与 llvmlite 自身抛出的 TypeError/ValueError 区分开 —— 后者属于"此处可以尝试
|
||||
简化回退"的类型调整失败,本异常则是必须上抛、禁止被兜底 `except` 吞掉的信号
|
||||
(例如 `_adjust_args` 的 L120/L181 两条 fail-fast)。
|
||||
"""
|
||||
|
||||
|
||||
class FuncGenMixin:
|
||||
|
||||
def setup_from_symbol_table(self, SymbolTable: Any) -> None:
|
||||
@@ -42,7 +51,14 @@ class FuncGenMixin:
|
||||
self._generate_structs()
|
||||
self._create_Vtable_globals()
|
||||
|
||||
def register_method(self, ClassName: str, MethodName: str) -> None:
|
||||
def register_method(self, ClassName: str, MethodName: str, IsVtableSlot: bool = True) -> None:
|
||||
"""登记类方法。
|
||||
|
||||
IsVtableSlot:该方法是否占用虚表槽(class_methods 即 vtable 槽序唯一真源)。
|
||||
默认 True 与既有语义逐字一致;False 用于方法级 `novirtual def`(⇒ 方法装饰器
|
||||
@t.NoVTable)——此时只保证类的方法容器存在,**不**把它排进槽序,使 TPC 与
|
||||
TPV(ClassGenBase._should_method_be_virtual 排除该项)的虚表布局逐槽相同。
|
||||
"""
|
||||
if ClassName not in self.class_methods:
|
||||
self.class_methods[ClassName] = []
|
||||
if ClassName not in self.class_members:
|
||||
@@ -54,6 +70,8 @@ class FuncGenMixin:
|
||||
_short_name: str = MethodName.split('.')[-1] if '.' in MethodName else MethodName
|
||||
if _short_name in ('__new__', '__init__', '__before_init__'):
|
||||
return
|
||||
if not IsVtableSlot:
|
||||
return
|
||||
if MethodName not in self.class_methods[ClassName]:
|
||||
self.class_methods[ClassName].append(MethodName)
|
||||
|
||||
@@ -117,9 +135,17 @@ class FuncGenMixin:
|
||||
elif isinstance(arg.type, ir.PointerType) and isinstance(param.type, ir.IntType):
|
||||
# fail-fast:指针→整数隐式转换(跨类型层级,有损)禁止,
|
||||
# 须显式转换(用户隐式转换规则:同层指针可转,指针→整数不允许)。
|
||||
raise TypeError(
|
||||
# 诊断增强(只在即将终止时拼串,零行为影响):把 callee 名、
|
||||
# 实参/形参个数与**逐项类型**一并报出——只给单个类型对无法判断
|
||||
# 「形参表整体错位一位」(如 self/pool 约定不一致)这类根因。
|
||||
_diag: str = "; ".join(
|
||||
f"[{k}] arg={a.type} param={p.type}"
|
||||
for k, (a, p) in enumerate(zip(args, func.args)))
|
||||
raise FailFastError(
|
||||
f"调用参数类型不匹配: 指针类型 {arg.type} 不能隐式转换为"
|
||||
f"整数参数 {param.type};如需转换请显式 cast")
|
||||
f"整数参数 {param.type};如需转换请显式 cast"
|
||||
f"(callee={func.name} 实参{len(args)}个/形参{len(func.args)}个;"
|
||||
f"逐项: {_diag})")
|
||||
elif isinstance(arg.type, ir.PointerType) and isinstance(param.type, ir.PointerType):
|
||||
if isinstance(arg.type.pointee, ir.PointerType) and isinstance(param.type.pointee, ir.IntType):
|
||||
Loaded: ir.Value = self._load(arg, name=f"Load_arg_{i}")
|
||||
@@ -178,7 +204,7 @@ class FuncGenMixin:
|
||||
elif isinstance(arg.type, (ir.FloatType, ir.DoubleType)) and isinstance(param.type, ir.IntType):
|
||||
# fail-fast:浮点→整数隐式转换(有损)禁止,须显式转换
|
||||
# (用户隐式转换规则:浮点→整数不允许)。
|
||||
raise TypeError(
|
||||
raise FailFastError(
|
||||
f"调用参数类型不匹配: 浮点类型 {arg.type} 不能隐式转换为"
|
||||
f"整数参数 {param.type};如需转换请显式 cast")
|
||||
elif isinstance(arg.type, ir.IntType) and isinstance(param.type, (ir.FloatType, ir.DoubleType)):
|
||||
@@ -201,7 +227,10 @@ class FuncGenMixin:
|
||||
adjusted.append(i64_val)
|
||||
else:
|
||||
adjusted.append(self.builder.bitcast(arg, param.type, name=f"cast_arg_{i}"))
|
||||
except Exception: # 参数类型调整失败时使用简化回退逻辑
|
||||
# 只收口 llvmlite 自身"可回退"的类型调整失败(builder/IR 构造抛出的
|
||||
# TypeError/ValueError);FailFastError 与其余异常一律上抛,
|
||||
# 避免编译器主动抛出的真因被伪装成 llvmlite 的下游报文。
|
||||
except (TypeError, ValueError): # 参数类型调整失败时使用简化回退逻辑
|
||||
if isinstance(arg.type, ir.PointerType) and isinstance(param.type, (ir.IdentifiedStructType, ir.LiteralStructType, ir.ArrayType)):
|
||||
if arg.type.pointee == param.type or (hasattr(arg.type.pointee, 'name') and hasattr(param.type, 'name') and arg.type.pointee.name == param.type.name):
|
||||
adjusted.append(self._load(arg, name=f"Load_arg_{i}"))
|
||||
@@ -238,39 +267,135 @@ class FuncGenMixin:
|
||||
adjusted.append(arg)
|
||||
return adjusted
|
||||
|
||||
def _class_has_vtable(self, ClassName: str | None) -> bool:
|
||||
"""类(或其任一祖先)是否带 vtable 槽。
|
||||
|
||||
与 StructGen.py:442-450 的父链传播同源——vtable 沿继承链携带,子类 struct 布局为
|
||||
[vtable?] + [基类字段…] + [自身字段…]。只认 class_vtable /
|
||||
_cross_module_vtable_classes(装饰器集合)会漏掉源码未写 @t.CVTable 的跨模块
|
||||
AST 节点类(ast.Try / ast.Match / ast.ExceptHandler…):它们有方法、继承自
|
||||
@t.CVTable 的 AST,struct 首元素确实是 vtable 指针,但 StructGen.py:415 在
|
||||
"struct body 已从 stub 完整加载"时直接 continue,永不把它们登记进集合
|
||||
(实测 TransPyV/temp/_shared_sym.json 的 class_vtable/cross_module_vtable 仅有
|
||||
{AST, MemManager, Module})。此时 base 静默取 0 → 该类字段偏移整体前移 1
|
||||
(ast.ExceptHandler.children 落到 end_col_offset,i32)。
|
||||
"""
|
||||
if not ClassName:
|
||||
return False
|
||||
_Seen: set[str] = set()
|
||||
_Cur: str | None = ClassName
|
||||
while _Cur and _Cur not in _Seen:
|
||||
_Seen.add(_Cur)
|
||||
if _Cur in self.class_vtable or _Cur in self._cross_module_vtable_classes:
|
||||
return True
|
||||
_Cur = self.class_parent.get(_Cur)
|
||||
return False
|
||||
|
||||
def _NormalizeMemberClass(self, ClassName: str | None) -> str | None:
|
||||
"""成员查询用的类键归一(短名 + 指针包装展开)。
|
||||
|
||||
- 统一短名解析(治本修复 2026-08-10):struct/class_members/class_vtable/
|
||||
class_parent 均以短名注册(跨模块加载 HandlesImports L1604),而调用方
|
||||
可能传入带 sha1 前缀的全名(如 "f47f88ae6545965a.Import")。
|
||||
- rawptr[X] / t.CPtr[X] 指针包装归一:rawptr[X] 是内建裸指针类型
|
||||
(X*),非真实类——接收者注解/类型名为 'rawptr[AST]' 时字段访问
|
||||
(child.parent)应作用于内层 struct X,否则查 class_members 全 miss
|
||||
→ fast-fail "属性 'parent' 在类 'rawptr[AST]' 中找不到(struct=opaque)"
|
||||
(includes/ast/base.vp _set_parent_list bootstrap 根因)。
|
||||
"""
|
||||
if not ClassName:
|
||||
return ClassName
|
||||
_Cls: str = self._extract_short_name(ClassName)
|
||||
if _Cls.startswith('rawptr[') or _Cls.startswith('t.CPtr['):
|
||||
_lb: int = _Cls.find('[')
|
||||
if _Cls.endswith(']') and _lb > 0:
|
||||
_inner: str = _Cls[_lb + 1:-1].strip()
|
||||
_short_inner: str = _inner.rsplit('.', 1)[-1] if '.' in _inner else _inner
|
||||
_hit: str | None = None
|
||||
if _short_inner in self.structs:
|
||||
_hit = _short_inner
|
||||
else:
|
||||
for _k in self.structs:
|
||||
if self._extract_short_name(_k) == _short_inner:
|
||||
_hit = _k
|
||||
break
|
||||
if _hit:
|
||||
_Cls = _hit
|
||||
return _Cls
|
||||
|
||||
def _member_base(self, ClassName: str | None) -> int:
|
||||
"""类 struct 的成员偏移基址(vtable 占位槽数)——**全仓唯一真源**。
|
||||
|
||||
`_get_member_offset` 与所有"自算 base 再 GEP 字段"的消费点(构造/初始化、
|
||||
字段默认值、继承方法 this 调整等)都必须走本方法,禁止各自直查
|
||||
`class_vtable`/`_cross_module_vtable_classes`——旧实现只认这两个装饰器集合,
|
||||
跨模块 AST 节点类(ast.Try/ast.Match/ast.ExceptHandler…)源码未写
|
||||
@t.CVTable 且 StructGen 在"struct body 已从 stub 加载"时提前 continue,
|
||||
永不把它们登记进集合,于是 base 只能靠"成员表长度 == struct 元素数 - 1"
|
||||
这个长度启发式救回;成员表未展平/尚未加载时启发式失效 ⇒ base 静默取 0
|
||||
⇒ 该类所有字段偏移整体前移 1(ast.ExceptHandler.children 落到
|
||||
end_col_offset(i32),len() 分派拿到 i32 ⇒ Type of #1 arg mismatch)。
|
||||
|
||||
判定规则(与 `StructGen._generate_structs` 生成 struct 的规则同源):
|
||||
- vtable 归属沿 class_parent 链判定(见 `_class_has_vtable`);
|
||||
- 再用 struct 实际元素数校准:
|
||||
delta(=struct 元素数 - 成员表长度) == 0 ⇒ 该 struct 无 vtable 槽 → base=0;
|
||||
delta == 1 ⇒ 恰多一个槽:首元素为指针 / 无父类 / 继承链归属成立 → base=1;
|
||||
delta >= 2 ⇒ 长度启发式失效,保留"继承链 vtable 归属"结果(不再静默取 0)。
|
||||
|
||||
查询前会调用 `HandlesImports.EnsureClassMembersFlattened`(幂等)把"停在自有
|
||||
字段"的未展平表补齐——未展平表会让下列 delta 校准失真,也会让按表内下标
|
||||
取字段的消费点漏掉继承前缀。
|
||||
"""
|
||||
_Cls: str | None = self._NormalizeMemberClass(ClassName)
|
||||
if not _Cls:
|
||||
return 0
|
||||
# 查询前确保成员表已按父类展平(幂等、顺序无关)。
|
||||
# 未展平的表(停在自有字段)会让本方法的 delta 校准失真,也会让所有
|
||||
# "按成员表下标 base+i 取字段"的消费点漏掉继承前缀 —— 见
|
||||
# HandlesImports.EnsureClassMembersFlattened(内部有纯内存前置判断与
|
||||
# 重入/重复尝试保护,对已展平类零额外开销)。
|
||||
_EnsureIH: Any = getattr(self, '_import_handler_ref', None)
|
||||
if _EnsureIH is not None and hasattr(_EnsureIH, 'EnsureClassMembersFlattened'):
|
||||
try:
|
||||
_EnsureIH.EnsureClassMembersFlattened(_Cls, self)
|
||||
except Exception as _e:
|
||||
if _config_mode == "strict":
|
||||
raise
|
||||
_vlog().warning(f"补齐类成员展平失败: {_e}", "Exception")
|
||||
_ByInheritVtable: bool = self._class_has_vtable(_Cls)
|
||||
base: int = 1 if _ByInheritVtable else 0
|
||||
_StructBase: ir.IdentifiedStructType | None = self.structs.get(_Cls)
|
||||
if (isinstance(_StructBase, ir.IdentifiedStructType)
|
||||
and _StructBase.elements is not None and len(_StructBase.elements) > 0):
|
||||
_NumElems: int = len(_StructBase.elements)
|
||||
_NumMembers: int = len(self.class_members.get(_Cls, []))
|
||||
_Delta: int = _NumElems - _NumMembers
|
||||
_FirstElem: ir.Type = _StructBase.elements[0]
|
||||
_IsVtablePtr: bool = (isinstance(_FirstElem, ir.PointerType)
|
||||
and isinstance(_FirstElem.pointee, ir.IntType)
|
||||
and _FirstElem.pointee.width == 8)
|
||||
if _Delta == 0:
|
||||
base = 0
|
||||
elif _Delta == 1:
|
||||
_HasParent: bool = self.class_parent.get(_Cls) is not None
|
||||
base = 1 if (_IsVtablePtr or not _HasParent or _ByInheritVtable) else 0
|
||||
return base
|
||||
|
||||
def _get_member_offset(self, field_name: str, ClassName: str | None = None) -> int | None:
|
||||
# base 判定用未归一的原始类名:_member_base 内部自行做同一套归一
|
||||
# (_NormalizeMemberClass),二者结果逐字一致(避免二次归一在
|
||||
# "rawptr[X] 展开命中 sha1 前缀 struct 键"这一角落改变取值)。
|
||||
_MemberBaseKey: str | None = ClassName
|
||||
# 治本修复:同名类 sha1 冲突检测。
|
||||
# 当 class_sha1_map[ClassName] 的 sha1 与 Gen.structs[ClassName] 的 sha1 不一致时,
|
||||
# class_members[ClassName] 可能存储了错误模块的同名类成员。
|
||||
# 此处检测冲突,加载正确模块的 class_members 并以 sha1 前缀全名缓存。
|
||||
_ResolvedClass: str = ClassName if ClassName else ''
|
||||
if ClassName:
|
||||
# 统一短名解析(治本修复 2026-08-10):struct/class_members/class_vtable/
|
||||
# class_parent 均以短名注册(跨模块加载 HandlesImports L1604),而调用方
|
||||
# 可能传入带 sha1 前缀的全名(如 "f47f88ae6545965a.Import"),导致下方
|
||||
# 查表全部 miss 返回 None → _gep_and_Load_member 静默返回 None →
|
||||
# AnnAssign InitValue 为 None → 变量 alloca 未初始化 → 运行期读垃圾指针。
|
||||
ClassName = self._extract_short_name(ClassName)
|
||||
# rawptr[X] / t.CPtr[X] 指针包装归一:rawptr[X] 是内建裸指针类型
|
||||
# (X*),非真实类——接收者注解/类型名为 'rawptr[AST]' 时字段访问
|
||||
# (child.parent)应作用于内层 struct X,否则查 class_members 全 miss
|
||||
# → fast-fail "属性 'parent' 在类 'rawptr[AST]' 中找不到(struct=opaque)"
|
||||
# (includes/ast/base.vp _set_parent_list bootstrap 根因)。
|
||||
if ClassName.startswith('rawptr[') or ClassName.startswith('t.CPtr['):
|
||||
_lb_rc: int = ClassName.find('[')
|
||||
if ClassName.endswith(']') and _lb_rc > 0:
|
||||
_inner_rc: str = ClassName[_lb_rc + 1:-1].strip()
|
||||
_short_rc: str = _inner_rc.rsplit('.', 1)[-1] if '.' in _inner_rc else _inner_rc
|
||||
_hit_rc: str | None = None
|
||||
if _short_rc in self.structs:
|
||||
_hit_rc = _short_rc
|
||||
else:
|
||||
for _k_rc in self.structs:
|
||||
if self._extract_short_name(_k_rc) == _short_rc:
|
||||
_hit_rc = _k_rc
|
||||
break
|
||||
if _hit_rc:
|
||||
ClassName = _hit_rc
|
||||
# 类键归一(短名 + rawptr[X]/t.CPtr[X] 展开)统一走 _NormalizeMemberClass,
|
||||
# 与 _member_base 的 base 判定共用同一归一,禁止各写一份。
|
||||
ClassName = self._NormalizeMemberClass(ClassName)
|
||||
_ResolvedClass = ClassName
|
||||
_ClassSha1Map: dict[str, str] = getattr(self, 'class_sha1_map', {})
|
||||
_ExpectedSha1: str | None = _ClassSha1Map.get(ClassName)
|
||||
@@ -303,27 +428,19 @@ class FuncGenMixin:
|
||||
self.class_members[ClassName] = _OldMembers
|
||||
else:
|
||||
self.class_members.pop(ClassName, None)
|
||||
has_vtable: bool = ClassName and (ClassName in self.class_vtable or ClassName in self._cross_module_vtable_classes)
|
||||
base: int = 1 if has_vtable else 0
|
||||
if ClassName and ClassName in self.structs:
|
||||
struct_type: ir.IdentifiedStructType = self.structs[ClassName]
|
||||
if isinstance(struct_type, ir.IdentifiedStructType) and struct_type.elements is not None and len(struct_type.elements) > 0:
|
||||
if ClassName in self.class_members:
|
||||
expected_fields: int = len(self.class_members[ClassName])
|
||||
actual_elements: int = len(struct_type.elements)
|
||||
if base > 0 and actual_elements == expected_fields:
|
||||
base = 0
|
||||
elif base == 0 and actual_elements == expected_fields + 1:
|
||||
# 跨模块类可能未被检测为 CVTable(如 Name 继承 AST@t.CVTable),
|
||||
# 但实际 struct 首元素是 vtable ptr → 需 base=1。
|
||||
# 但若类有继承(class_parent 有值),多出的 1 可能是继承字段而非 vtable,
|
||||
# 需检查 struct 首元素类型:i8*/i8** 为 vtable,其他为继承字段。
|
||||
_first_elem = struct_type.elements[0] if actual_elements > 0 else None
|
||||
_has_parent = self.class_parent.get(ClassName) is not None
|
||||
_is_vtable_ptr = (isinstance(_first_elem, ir.PointerType) and
|
||||
isinstance(_first_elem.pointee, ir.IntType) and _first_elem.pointee.width == 8)
|
||||
if not _has_parent or _is_vtable_ptr:
|
||||
base = 1
|
||||
# base(vtable 占位槽数):唯一真源 = _member_base(规则与文档见该方法)。
|
||||
# 本函数在同一次调用内还会在下方多处重载成员表(懒加载 425-436、模块重载
|
||||
# 482-508、字段预注册 558-578、force 重建 587-643),每次都可能改写
|
||||
# _member_base 的三个输入(class_parent 父链、structs 布局、class_members
|
||||
# 长度)。因此 base 与"即将返回的下标 i"必须取自**同一份最新已加载状态**:
|
||||
# 所有 return 一律用 _BaseNow() 现算,禁止缓存早先算出的 base——缓存会让
|
||||
# 偏移整体差 1(ast.ExceptHandler.children 落到 end_col_offset(i32),
|
||||
# len 分派拿到 i32)。
|
||||
def _BaseNow() -> int:
|
||||
return self._member_base(_MemberBaseKey)
|
||||
# 预热(与原行为逐字一致):先触发一次 EnsureClassMembersFlattened,
|
||||
# 再决定下面的懒加载。
|
||||
_BaseNow()
|
||||
if ClassName and ClassName not in self.class_members and self._Trans and hasattr(self._Trans, 'ImportHandler'):
|
||||
# 跨模块 class_members 懒加载:struct 已从 stub 加载但 class_members
|
||||
# 可能尚未从 .vpi 填充(vpi/stub 就绪时序不一致),主动按类 sha1 加载。
|
||||
@@ -336,10 +453,13 @@ class FuncGenMixin:
|
||||
continue
|
||||
if ClassName in self.class_members and len(self.class_members[ClassName]) > 0:
|
||||
break
|
||||
# 依赖补齐(上面的懒加载)后再确认一次展平(幂等);偏移基址由 _BaseNow()
|
||||
# 在真正 return 处现算,不在此缓存。
|
||||
_BaseNow()
|
||||
if _ResolvedClass and _ResolvedClass in self.class_members:
|
||||
for i, (name, _) in enumerate(self.class_members[_ResolvedClass]):
|
||||
if name == field_name:
|
||||
return base + i
|
||||
return _BaseNow() + i
|
||||
# 继承链回退:当字段不在当前类的 class_members 中时(常见于跨模块导入
|
||||
# 父类未加载,字段被填充为 __inherited_N 占位名),沿 class_parent 链向上查找。
|
||||
# 子类 struct 布局为 [vtable?] + [基类字段...] + [子类字段...],
|
||||
@@ -348,33 +468,56 @@ class FuncGenMixin:
|
||||
if ClassName and field_name:
|
||||
_parent: str | None = self.class_parent.get(ClassName)
|
||||
_visited: set = {ClassName} if ClassName else set()
|
||||
_cls_short: str = ClassName.split('.')[-1] if ClassName else ''
|
||||
while _parent and _parent not in _visited:
|
||||
_visited.add(_parent)
|
||||
# 父类候选键:class_parent 只登记**短名**(含泛型基类时是模板名,
|
||||
# 如 'GSListNode'),而 class_members 里真实键可能是**实例化名**
|
||||
# ('GSListNode[GlobalVariable]',见 includes/llvmlite/__module.vp
|
||||
# `class GlobalVariable(GSListNode[GlobalVariable])`)或模板名。
|
||||
# 少这一步 ⇒ 泛型基类字段(Next/Prev 等)沿链永远查不到 → fast-fail
|
||||
# ("属性 'Next' 在类 'GlobalVariable' 中找不到")。
|
||||
_p_tmpl: str = _parent.split('[')[0]
|
||||
_cand_keys: list = []
|
||||
for _k in (_parent, _p_tmpl, f"{_p_tmpl}[{_cls_short}]" if _cls_short else _p_tmpl):
|
||||
if _k and _k not in _cand_keys:
|
||||
_cand_keys.append(_k)
|
||||
# 跳过 __inherited_N 占位条目,只在真实字段名中查找
|
||||
if _parent in self.class_members:
|
||||
_members: list = self.class_members[_parent]
|
||||
_has_placeholder: bool = any(n.startswith('__inherited_') for n, _ in _members)
|
||||
if not _has_placeholder:
|
||||
for i, (name, _) in enumerate(_members):
|
||||
if name == field_name:
|
||||
return base + i
|
||||
_hit_idx: int = -1
|
||||
for _k in _cand_keys:
|
||||
if _k in self.class_members:
|
||||
_members: list = self.class_members[_k]
|
||||
_has_placeholder: bool = any(n.startswith('__inherited_') for n, _ in _members)
|
||||
if not _has_placeholder:
|
||||
for i, (name, _) in enumerate(_members):
|
||||
if name == field_name:
|
||||
_hit_idx = i
|
||||
break
|
||||
if _hit_idx >= 0:
|
||||
break
|
||||
if _hit_idx >= 0:
|
||||
# 基类字段位于子类 struct 的 base 起始处,索引 i 即基类内偏移
|
||||
return _BaseNow() + _hit_idx
|
||||
# 父类 class_members 未加载或含占位条目,尝试从 stub 加载真实字段
|
||||
if self._Trans and hasattr(self._Trans, 'ImportHandler'):
|
||||
sha1_map: dict[str, str] = getattr(self, 'ModuleSha1Map', {})
|
||||
for mod_name, mod_sha1 in sha1_map.items():
|
||||
stub_name: str = f"{mod_sha1}.stub.ll"
|
||||
self._Trans.ImportHandler._TryLoadClassMembersFromPyi(_parent, stub_name, self)
|
||||
if _parent in self.class_members and not any(n.startswith('__inherited_') for n, _ in self.class_members[_parent]):
|
||||
for i, (name, _) in enumerate(self.class_members[_parent]):
|
||||
if name == field_name:
|
||||
return base + i
|
||||
for _k in _cand_keys:
|
||||
for mod_name, mod_sha1 in sha1_map.items():
|
||||
stub_name: str = f"{mod_sha1}.stub.ll"
|
||||
self._Trans.ImportHandler._TryLoadClassMembersFromPyi(_k, stub_name, self)
|
||||
if _k in self.class_members and not any(n.startswith('__inherited_') for n, _ in self.class_members[_k]):
|
||||
for i, (name, _) in enumerate(self.class_members[_k]):
|
||||
if name == field_name:
|
||||
return _BaseNow() + i
|
||||
break
|
||||
if _k in self.class_members and any(n == field_name for n, _ in self.class_members.get(_k, [])):
|
||||
break
|
||||
_parent = self.class_parent.get(_parent)
|
||||
short_name: str | None = ClassName.split('.')[-1] if ClassName and '.' in ClassName else None
|
||||
if short_name and short_name in self.class_members:
|
||||
for i, (name, _) in enumerate(self.class_members[short_name]):
|
||||
if name == field_name:
|
||||
return base + i
|
||||
return _BaseNow() + i
|
||||
if ClassName and field_name:
|
||||
if ClassName not in self.class_members or field_name not in [n for n, _ in self.class_members.get(ClassName, [])]:
|
||||
if self._Trans and hasattr(self._Trans, 'ImportHandler'):
|
||||
@@ -385,7 +528,7 @@ class FuncGenMixin:
|
||||
if ClassName in self.class_members and len(self.class_members[ClassName]) > 0:
|
||||
for i, (name, _) in enumerate(self.class_members[ClassName]):
|
||||
if name == field_name:
|
||||
return base + i
|
||||
return _BaseNow() + i
|
||||
break
|
||||
if short_name:
|
||||
for mod_name, mod_sha1 in sha1_map.items():
|
||||
@@ -394,14 +537,14 @@ class FuncGenMixin:
|
||||
if short_name in self.class_members and len(self.class_members[short_name]) > 0:
|
||||
for i, (name, _) in enumerate(self.class_members[short_name]):
|
||||
if name == field_name:
|
||||
return base + i
|
||||
return _BaseNow() + i
|
||||
break
|
||||
if ClassName and field_name:
|
||||
for cn_key in self.class_members:
|
||||
if cn_key == ClassName or cn_key.endswith(f'.{ClassName}') or (short_name and (cn_key == short_name or cn_key.endswith(f'.{short_name}'))):
|
||||
for i, (name, _) in enumerate(self.class_members[cn_key]):
|
||||
if name == field_name:
|
||||
return base + i
|
||||
return _BaseNow() + i
|
||||
# REnum 变体搜索: ClassName 是 REnum(如 MetaType),成员注册在变体结构
|
||||
# (MetaType_Var, MetaType_Concrete, MetaType_App)中。变体与 REnum 主结构
|
||||
# 共享 max_variant_struct 布局,偏移一致(__tag 在偏移 0,字段从偏移 1 开始)
|
||||
@@ -449,7 +592,7 @@ class FuncGenMixin:
|
||||
if short_cn in self.class_members and len(self.class_members[short_cn]) > 0:
|
||||
for i, (name, _) in enumerate(self.class_members[short_cn]):
|
||||
if name == field_name:
|
||||
return base + i
|
||||
return _BaseNow() + i
|
||||
break
|
||||
if ClassName and ClassName in self.structs and (ClassName not in self.class_members or len(self.class_members.get(ClassName, [])) == 0):
|
||||
struct_type: ir.IdentifiedStructType = self.structs[ClassName]
|
||||
@@ -466,11 +609,11 @@ class FuncGenMixin:
|
||||
if ClassName in self.class_members:
|
||||
for i, (name, _) in enumerate(self.class_members[ClassName]):
|
||||
if name == field_name:
|
||||
return base + i
|
||||
return _BaseNow() + i
|
||||
if short_name and short_name in self.class_members:
|
||||
for i, (name, _) in enumerate(self.class_members[short_name]):
|
||||
if name == field_name:
|
||||
return base + i
|
||||
return _BaseNow() + i
|
||||
break
|
||||
# 最后回退:当继承链回退失败但 struct body 已加载时,
|
||||
# 从 struct elements 数和 class_members 数推断继承字段偏移。
|
||||
@@ -521,8 +664,8 @@ class FuncGenMixin:
|
||||
_num_members_final = len(_members_final)
|
||||
for _Ri, (_Rn, _Rt) in enumerate(_members_final):
|
||||
if _Rn == field_name:
|
||||
return base + _Ri
|
||||
_num_inherited_final: int = _num_elements_final - _num_members_final - base
|
||||
return _BaseNow() + _Ri
|
||||
_num_inherited_final: int = _num_elements_final - _num_members_final - _BaseNow()
|
||||
if _num_inherited_final == 1:
|
||||
# 推断(缺失槽位 = 位于 base+0 的未展平继承字段)仅在成员表
|
||||
# 尚未含展平继承字段时成立:若成员表首项本身就是任一已加载
|
||||
@@ -536,7 +679,7 @@ class FuncGenMixin:
|
||||
pass # 已含展平继承字段 → 落入下方 fast-fail 暴露真实缺失
|
||||
else:
|
||||
# 单继承字段,偏移 = base(vtable 之后第一个位置)
|
||||
return base + 0
|
||||
return _BaseNow() + 0
|
||||
# fast-fail(治本修复 2026-08-10):字段未找到不再静默返回 None。
|
||||
# 静默 None 会让调用方(_gep_and_Load_member L26-27)直接 return None,
|
||||
# AnnAssign InitValue 变 None → 变量 alloca 未初始化 → 运行期读垃圾指针
|
||||
|
||||
@@ -140,6 +140,12 @@ class StructGenMixin:
|
||||
_DbgInfo = self._GetDebugInfo()
|
||||
if _DbgInfo is not None:
|
||||
_DbgInfo.Finalize()
|
||||
# Ctrl+C 调用栈(--ctrlc-stack):同样是"落成 .ll 文本之前"的模块级收尾——
|
||||
# 当本模块就是 includes/dbgstack.vp 时,注入 @llvm.global_ctors 构造器调用
|
||||
# 其 Install()(见 BaseGen._RegisterDbgStackInstallViaCtor)。未启用/非该模块
|
||||
# 时 ctrlc_stack_ctor=False,不做任何事(零影响)。
|
||||
if self.ctrlc_stack_ctor:
|
||||
self._RegisterDbgStackInstallViaCtor()
|
||||
ir_text: str
|
||||
try:
|
||||
ir_text = str(self.module)
|
||||
|
||||
@@ -11,7 +11,9 @@ from lib.includes import t
|
||||
from lib.constants.config import mode as _config_mode
|
||||
from lib.core.VLogger import get_logger as _vlog
|
||||
from lib.core.DecoratorPass import run as _run_decorator_pass
|
||||
from lib.core.SymbolUtils import IsTModule, AnnotationContainsName
|
||||
from lib.core.SymbolUtils import (
|
||||
IsTModule, AnnotationContainsName, RequireDefineConstStorable,
|
||||
)
|
||||
# 治本修复:导入 ConstEvaluator 用于预扫描 CDefine 常量的复杂表达式求值
|
||||
from lib.core.ConstEvaluator import ConstEvaluator
|
||||
|
||||
@@ -87,6 +89,11 @@ class LlvmGeneratorMixin:
|
||||
return
|
||||
val: int | str | float | None = _extract_const_value(Value)
|
||||
if val is not None:
|
||||
# fail-fast:常量值必须能落进声明类型(define 带类型形态)/ 64 位存储
|
||||
# (自动推导形态,本语言无 64 位以上整数类型)——此前超大值会被静默
|
||||
# 截断/生成非法 IR 文本。唯一判据见 SymbolUtils.RequireDefineIntStorable。
|
||||
RequireDefineConstStorable(Gen, self, NameNode, TypeInfo.BaseType,
|
||||
Annotation, val, NameNode.id)
|
||||
if not hasattr(Gen, '_define_constants'):
|
||||
Gen._define_constants: dict[str, int | str | float | bool] = {}
|
||||
Gen._define_constants[NameNode.id] = val
|
||||
@@ -162,6 +169,12 @@ class LlvmGeneratorMixin:
|
||||
if isinstance(e, ImportError):
|
||||
raise
|
||||
lineno: int = getattr(Node, 'lineno', 0)
|
||||
# 注入行偏移还原:AST 行号含 _inject_auto_imports 在文件头注入的 N 行,
|
||||
# 先按精确偏移减回磁盘真实行号(N 由 Phase2Translator 计算并挂到 translator);
|
||||
# 报文行号与源码行必须同用还原后的 lineno,二者一致(否则会显示下一行源码)。
|
||||
_line_offset: int = int(getattr(self, '_line_offset', 0) or 0)
|
||||
if _line_offset > 0 and lineno > _line_offset:
|
||||
lineno -= _line_offset
|
||||
source_line: str | None = Gen._get_source_line(lineno)
|
||||
src_file: str = getattr(Gen, '_current_source_file', '')
|
||||
src_path: str = src_file if src_file else 'unknown'
|
||||
@@ -225,6 +238,12 @@ class LlvmGeneratorMixin:
|
||||
self._HandleModuleLevelExprLlvm(Node, Gen)
|
||||
except Exception as e:
|
||||
lineno: int = getattr(Node, 'lineno', 0)
|
||||
# 注入行偏移还原:AST 行号含 _inject_auto_imports 在文件头注入的 N 行,
|
||||
# 先按精确偏移减回磁盘真实行号(N 由 Phase2Translator 计算并挂到 translator);
|
||||
# 报文行号与源码行必须同用还原后的 lineno,二者一致(否则会显示下一行源码)。
|
||||
_line_offset: int = int(getattr(self, '_line_offset', 0) or 0)
|
||||
if _line_offset > 0 and lineno > _line_offset:
|
||||
lineno -= _line_offset
|
||||
source_line: str | None = Gen._get_source_line(lineno)
|
||||
src_file: str = getattr(Gen, '_current_source_file', '')
|
||||
src_path: str = src_file if src_file else 'unknown'
|
||||
@@ -252,6 +271,11 @@ class LlvmGeneratorMixin:
|
||||
if not isinstance(Node.value, ast.Call):
|
||||
return
|
||||
call: ast.Call = Node.value
|
||||
if isinstance(call.func, ast.Name):
|
||||
# 模块级裸名 __llvmir__(...) 等价 c.LLVMIR(...)
|
||||
if call.func.id == '__llvmir__':
|
||||
self._HandleModuleLevelLLVMIR(call, Gen)
|
||||
return
|
||||
if not isinstance(call.func, ast.Attribute):
|
||||
return
|
||||
if not isinstance(call.func.value, ast.Name) or call.func.value.id != 'c':
|
||||
@@ -676,7 +700,11 @@ class LlvmGeneratorMixin:
|
||||
# 函数级 DWARF 调试元数据(--dwarf / --asan):透传开关给本模块的
|
||||
# LlvmCodeGenerator;未启用时 dwarf_enabled=False → 不构造 DI、不挂 !dbg,
|
||||
# 生成的 .ll 与改动前逐字节一致(零影响)
|
||||
self.LlvmGen.dwarf_enabled = bool(getattr(self, 'DwarfEnabled', False))
|
||||
self.LlvmGen.dwarf_enabled = bool(getattr(self, 'DwarfEnabled', False))
|
||||
# Ctrl+C 调用栈(--ctrlc-stack):透传"本模块需注入 Install() 启动期构造器"
|
||||
# 标记(仅 includes/dbgstack.vp 的翻译上下文为 True);未启用时为 False
|
||||
# → finalize() 不做任何事,生成的 .ll 与改动前逐字节一致(零影响)
|
||||
self.LlvmGen.ctrlc_stack_ctor = bool(getattr(self, 'CtrlcStackCtor', False))
|
||||
|
||||
# --- 修复:共享 vtable 状态跨模块 ---
|
||||
# 根因:LlvmGen 每模块重新创建,class_vtable/class_methods 等是实例级属性,
|
||||
|
||||
Reference in new issue
Block a user