StructGen: _get_struct_size/_get_align 去 opaque→8/ptr_size、未知→4 兜底并补 VectorType;LiteralStructType.fields→.elements(llvmlite 无 fields,必然 AttributeError) HandlesClassDef: union 载荷尺寸经不存在的 ir.DataLayout 静默退化成 8 字节;HandlesExprCall/ExprBuiltin: sizeof 各静默 0 分支改 fail-fast,数组元素 sizeof 统一走 _get_struct_size HandlesFor: nonlocal struct/array 堆分配不再退化成 malloc(8) vast/vast_c: 补一元 * / & 解析,装饰器后 extern/export 分派修复,C 侧报错带 token 与行号;StubGen.Converter: 宏展开丢尾随空行修 .pyi 缩进非法 其余为 rawptr 迁移 / fail-fast 口径等历史改动一并入库
2214 lines
124 KiB
Python
2214 lines
124 KiB
Python
from __future__ import annotations
|
||
from typing import Any
|
||
import vast as ast
|
||
import os
|
||
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
|
||
|
||
|
||
class VaArgInstruction(ir.Instruction):
|
||
def descr(self, buf: list[str]) -> None:
|
||
ptr: ir.Value = self.operands[0]
|
||
buf.append("va_arg {0} {1}, {2}\n".format(
|
||
ptr.type, ptr.get_reference(), self.type))
|
||
|
||
|
||
class _HotspotSiteBuilder(ir.IRBuilder):
|
||
"""--hotspot-sites 专用 IRBuilder:为每条新生成的 call 指令挂源码位置。
|
||
|
||
仅当调用点热点统计开关打开时才被实例化(见 BaseGenMixin._NewIrBuilder);
|
||
未启用时一律使用原生 ir.IRBuilder,不产生任何额外属性、行为与既有完全一致。
|
||
|
||
重写 call() 只在返回的 ir.CallInstr 对象上追加两个普通 Python 属性
|
||
(_src_file / _src_line),不改变指令语义、操作数,也不参与 IR 渲染
|
||
(llvmlite 仅按指令自身类型输出文本,忽略这些自定义属性)。取不到位置时
|
||
留空(None / 0),不伪造。
|
||
"""
|
||
|
||
def __init__(self, block: ir.Block | None, gen: Any) -> None:
|
||
super().__init__(block)
|
||
self._HotspotGen: Any = gen
|
||
|
||
def call(self, fn: Any, args: Any, name: str = '', cconv: Any = None,
|
||
tail: Any = False, fastmath: Any = (), attrs: Any = (),
|
||
arg_attrs: Any = None) -> ir.CallInstr:
|
||
instr: ir.CallInstr = super().call(
|
||
fn, args, name=name, cconv=cconv, tail=tail,
|
||
fastmath=fastmath, attrs=attrs, arg_attrs=arg_attrs)
|
||
gen: Any = self._HotspotGen
|
||
# 记录生成期当前源码位置(语句级 lineno + 当前源文件名);取不到则留空
|
||
instr._src_file = getattr(gen, '_current_source_file', None)
|
||
instr._src_line = getattr(gen, '_current_lineno', 0) or 0
|
||
return instr
|
||
|
||
|
||
class BaseGenMixin:
|
||
|
||
@staticmethod
|
||
def _parse_triple(triple: str | None) -> dict[str, bool | int]:
|
||
"""解析目标三元组,返回平台信息字典"""
|
||
info: dict[str, bool | int] = {
|
||
'is_windows': False, 'is_linux': False, 'is_macos': False,
|
||
'is_x86_64': False, 'is_arm64': False, 'is_32bit': False,
|
||
'ptr_size': 8, 'long_size': 8, 'wchar_t_size': 32,
|
||
}
|
||
if not triple:
|
||
return info
|
||
triple_lower: str = triple.lower()
|
||
# 检测操作系统
|
||
if 'windows' in triple_lower or 'win32' in triple_lower or 'mingw' in triple_lower or 'msvc' in triple_lower or 'cygwin' in triple_lower:
|
||
info['is_windows'] = True
|
||
# Windows LLP64: long = 32 位, wchar_t = 16 位
|
||
info['long_size'] = 32
|
||
info['wchar_t_size'] = 16
|
||
elif 'linux' in triple_lower:
|
||
info['is_linux'] = True
|
||
elif 'darwin' in triple_lower or 'macos' in triple_lower or 'apple' in triple_lower:
|
||
info['is_macos'] = True
|
||
# 检测架构
|
||
if 'x86_64' in triple_lower or 'amd64' in triple_lower or 'x64' in triple_lower:
|
||
info['is_x86_64'] = True
|
||
elif 'aarch64' in triple_lower or 'arm64' in triple_lower:
|
||
info['is_arm64'] = True
|
||
elif 'i386' in triple_lower or 'i686' in triple_lower or 'arm' in triple_lower:
|
||
info['is_32bit'] = True
|
||
info['ptr_size'] = 4
|
||
info['long_size'] = 32
|
||
if info['is_windows']:
|
||
info['wchar_t_size'] = 16
|
||
else:
|
||
info['wchar_t_size'] = 32
|
||
return info
|
||
|
||
def __init__(self, triple: str | None = None, datalayout: str | None = None) -> None:
|
||
self.module: ir.Module = ir.Module(name="transpyc_module")
|
||
if triple:
|
||
self.module.triple = triple
|
||
else:
|
||
self.module.triple = "x86_64-none-elf"
|
||
if datalayout:
|
||
self.module.data_layout = datalayout
|
||
else:
|
||
self.module.data_layout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
|
||
|
||
# 解析目标平台信息
|
||
self._platform_info: dict[str, bool | int] = self._parse_triple(self.module.triple)
|
||
self.ptr_size: int = self._platform_info['ptr_size']
|
||
# 根据目标平台配置 t 模块中的平台相关类型大小
|
||
_t.configure_platform(self.module.triple)
|
||
self.builder: ir.IRBuilder | None = None
|
||
self.func: ir.Function | None = None
|
||
self.functions: dict[str, ir.Function] = {}
|
||
self.variables: dict[str, ir.Value | None] = {}
|
||
self._direct_values: dict[str, ir.Value] = {}
|
||
self._reg_values: dict[str, ir.Value] = {}
|
||
self.var_signedness: dict[str, bool] = {}
|
||
self._unsigned_results: set[int] = {}
|
||
self.string_const_counter: int = 0
|
||
self.Vtables: dict[str, ir.GlobalVariable] = {}
|
||
self.class_methods: dict[str, list[str]] = {}
|
||
self.class_members: dict[str, list[tuple[str, ir.Type]]] = {}
|
||
self.class_member_defaults: dict[str, dict[str, Any]] = {}
|
||
self.class_member_signeds: dict[str, dict[str, bool]] = {}
|
||
self.class_member_bitfields: dict[str, dict[str, int]] = {}
|
||
self.class_member_byteorders: dict[str, dict[str, str]] = {}
|
||
self.local_var_byteorders: dict[str, str] = {}
|
||
self.class_member_element_class: dict[str, dict[str, str]] = {}
|
||
self.class_member_bitoffsets: dict[str, dict[str, int]] = {}
|
||
self.structs: dict[str, ir.IdentifiedStructType] = {}
|
||
self.typedef_int_widths: dict[str, int] = {} # typedef名称 -> 底层整数位宽 (如 'UINT' -> 32, 'BYTE' -> 8)
|
||
self._cross_module_vtable_classes: set[str] = set()
|
||
self._cross_module_novtable: set[str] = set()
|
||
self.class_vtable: set[str] = set()
|
||
self._class_graph: dict[str, Any] = {}
|
||
self.class_parent: dict[str, str] = {}
|
||
# 多继承:父类名数组(声明顺序)。class_parent 兼容保留为第一个父类。
|
||
self.class_parents: dict[str, list[str]] = {}
|
||
# Store member annotations for deferred type resolution (cross-module class references)
|
||
self.class_member_annotations: dict[str, dict[str, Any]] = {}
|
||
self.loop_break_targets: list[ir.Block] = []
|
||
self.loop_continue_targets: list[ir.Block] = []
|
||
self.eh_jmp_buf_stack: list[ir.Value] = []
|
||
self.eh_except_block_stack: list[ir.Block] = []
|
||
self.eh_finally_stack: list[Any] = []
|
||
self.global_vars: set[str] = set()
|
||
self.nonlocal_params: dict[str, Any] = {}
|
||
self._variable_scope_stack: list[dict[str, Any]] = [] # Stack of outer scope variables for nested function nonlocal lookup
|
||
self.var_struct_class: dict[str, str] = {}
|
||
self.var_ptr_element: dict[str, bool] = {}
|
||
# 全局变量的指针元素标志(持久,不受函数重置影响)
|
||
# 用于全局变量 _argv: bytes | t.CPtr 等,使 _argv[index] 使用 8 字节指针步长
|
||
self.global_var_ptr_element: dict[str, bool] = {}
|
||
self.class_provides: dict[str, list[str]] = {}
|
||
self.class_requires: dict[str, list[str]] = {}
|
||
self.class_require_must: dict[str, list[str]] = {}
|
||
# 编译期 with 上下文栈:记录当前嵌套 with 链中所有可用的 provides 字段名
|
||
# 用于 __require_must__ 的纯编译期静态可达性检查
|
||
self._with_provides_stack: list[list[str]] = []
|
||
# 函数参数自动取地址模式: func_name -> [None, None, 'need', 'always', ...]
|
||
# 'need': t.CNeedPtr — 仅对非指针值取地址
|
||
# 'always': t.CAutoPtr — 无条件取地址
|
||
self._auto_addr_params: dict[str, list[str | None]] = {}
|
||
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] = {}
|
||
self._type_cache: dict[tuple[str, bool], ir.Type] = {}
|
||
self._typedef_cache: dict[str, Any] = {}
|
||
self._func_sig_cache: dict[str, Any] = {}
|
||
self.var_type_assignments: dict[str, Any] = {}
|
||
self._temp_struct_ptrs: list[ir.Value] = []
|
||
self._stop_iter_flag_param: ir.Value | None = None
|
||
self._local_heap_ptrs: list[tuple[ir.Value, str, str | None]] = []
|
||
self._var_to_heap_ptr: dict[str, ir.Value] = {}
|
||
self.known_return_types: dict[str, ir.Type] = {}
|
||
self._current_lineno: int = 0
|
||
self._current_node_info: str = ""
|
||
self.module_sha1: str | None = None
|
||
self.ModuleSha1Map: dict[str, str] = {}
|
||
self._temp_dir: str | None = None
|
||
self._export_funcs: set[str] = set()
|
||
self._export_extern_funcs: dict[str, str] = {}
|
||
self._current_module_func_names: set[str] = set()
|
||
# __doc__ 魔法字符串注册表:symbol_name -> docstring 文本(None 表示空/优化剥离)
|
||
self._doc_strings: dict[str, str | None] = {}
|
||
self.class_packed: set[str] = set()
|
||
self._define_constants: dict[str, int] = {}
|
||
# 以下属性原先在外部懒注入,此处显式初始化以避免 hasattr/getattr 动态反射
|
||
self._struct_sha1_map: dict[str, str] = {}
|
||
self.SymbolTable: Any = None
|
||
self._Trans: Any = None
|
||
self.class_sha1_map: dict[str, str] = {}
|
||
# 以下属性原先懒注入,此处显式初始化以消除 hasattr/getattr 动态反射
|
||
self._import_handler_ref: Any = None
|
||
self._classes_in_progress: set[str] = set()
|
||
# 字段收集期间添加当前类名,阻止 CollectAnnAssignMember 触发的嵌套
|
||
# _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
|
||
self._decorated_funcs: dict = {}
|
||
self._current_tree: Any = None
|
||
self._DefineConstants: dict = {}
|
||
self._all_define_constants: dict = {}
|
||
# 闭包追踪:记录返回闭包的函数和变量
|
||
# _closure_return_types: func_name -> (fn_return_type, nonlocal_arg_types)
|
||
self._closure_return_types: dict[str, tuple[Any, list[Any]]] = {}
|
||
# _closure_var_types: var_name -> (fn_return_type, nonlocal_arg_types)
|
||
self._closure_var_types: dict[str, tuple[Any, list[Any]]] = {}
|
||
# 调用点热点统计(--hotspot-sites):None=未启用(零影响);str=调用点计数落盘文件路径
|
||
self.hotspot_sites_file: str | None = None
|
||
# 是否对全部直接 call 点插桩(忽略白名单;体积/开销大)
|
||
self.hotspot_sites_all: bool = False
|
||
# 白名单被调函数基名列表;None=使用默认内存/字符串族白名单
|
||
self.hotspot_callees: list[str] | None = None
|
||
# 注册策略:False=种子 CLI 单模块(在 main 入口注册);True=Projectrans
|
||
# 多模块(每模块用 @llvm.global_ctors 自注册,不依赖 main 所在模块)
|
||
self.hotspot_sites_module_local: bool = False
|
||
# 函数级 DWARF 调试元数据(--dwarf / --asan 自动开启):
|
||
# False=未启用(零影响,不构造任何 DI 元数据、不挂 !dbg);
|
||
# True=每个 define 发 DISubprogram + 入口首条指令发 DILocation。
|
||
self.dwarf_enabled: bool = False
|
||
self.DebugInfo: Any = None
|
||
# Ctrl+C 调用栈(--traceback):仅在翻译 includes/traceback.vp 这一个模块时
|
||
# 置 True —— 落成 .ll 文本前为本模块注入 @llvm.global_ctors 构造器调用其
|
||
# Install()(见 _RegisterTracebackInstallViaCtor)。默认 False(零影响)。
|
||
self.traceback_ctor: bool = False
|
||
pi: dict[str, bool | int] = self._platform_info
|
||
# 根据目标平台设置宏(声明式,从三元组推导)
|
||
if pi['is_windows']:
|
||
self._define_constants['_WIN32'] = 1
|
||
if not pi['is_32bit']:
|
||
self._define_constants['_WIN64'] = 1
|
||
self._define_constants['WIN64'] = 1
|
||
self._define_constants['WIN32'] = 1
|
||
if pi['is_linux']:
|
||
self._define_constants['__linux__'] = 1
|
||
if pi['is_macos']:
|
||
self._define_constants['__APPLE__'] = 1
|
||
self._define_constants['__MACH__'] = 1
|
||
if pi['is_x86_64']:
|
||
self._define_constants['__x86_64__'] = 1
|
||
self._define_constants['__x86_64'] = 1
|
||
self._define_constants['_M_X64'] = 1
|
||
elif pi['is_arm64']:
|
||
self._define_constants['__aarch64__'] = 1
|
||
self._define_constants['_M_ARM64'] = 1
|
||
if not pi['is_32bit'] and pi['is_linux']:
|
||
self._define_constants['__LP64__'] = 1
|
||
elif pi['is_32bit']:
|
||
self._define_constants['_ILP32'] = 1
|
||
self._define_constants['SIZEOF_VOID_P'] = pi['ptr_size']
|
||
self._define_constants['__SIZEOF_POINTER__'] = pi['ptr_size']
|
||
|
||
def find_struct_by_pointee(self, pointee: ir.Type) -> tuple[str, ir.IdentifiedStructType] | None:
|
||
"""根据 pointee 类型查找匹配的结构体,返回 (class_name, struct_type) 或 None"""
|
||
for class_name, struct_type in self.structs.items():
|
||
if pointee == struct_type:
|
||
return (class_name, struct_type)
|
||
return None
|
||
|
||
@staticmethod
|
||
def _extract_short_name(name: str) -> str:
|
||
"""从结构体类型名中提取短名(去掉 sha1 前缀)。
|
||
如 "6bc91356c652f8ca.Function" → "Function"
|
||
如 "Function" → "Function"(无 sha1 前缀时原样返回)
|
||
"""
|
||
if '.' in name:
|
||
prefix: str = name.split('.', 1)[0]
|
||
if len(prefix) == 16 and all(c in '0123456789abcdef' for c in prefix):
|
||
return name.split('.', 1)[1]
|
||
return name
|
||
|
||
@staticmethod
|
||
def _extract_struct_sha1(st: ir.IdentifiedStructType) -> str:
|
||
"""从 IdentifiedStructType 的 name 中提取 sha1 前缀(16 位十六进制)。
|
||
返回空字符串表示无 sha1 前缀。用于同名类冲突检测。
|
||
"""
|
||
if not isinstance(st, ir.IdentifiedStructType):
|
||
return ''
|
||
try:
|
||
name: str = st.name
|
||
except (AssertionError, AttributeError):
|
||
return ''
|
||
if '.' in name:
|
||
prefix: str = name.split('.', 1)[0]
|
||
if len(prefix) == 16 and all(c in '0123456789abcdef' for c in prefix):
|
||
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 既有行为)。
|
||
|
||
但**保留**泛型特化名 '[...]' 段内的原样文本:特化实参里的 '*' 是类型
|
||
身份的一部分('list[<sha1>.AST*]' 表示元素为指向 AST 的指针),若一并
|
||
移除会让声明端引用名('list[<sha1>.AST*]')与定义端注册名
|
||
('list[<sha1>.AST]')分裂成两个 struct。头部(首个 '[' 之前)行为不变;
|
||
'[' 开头(LLVM 数组串 '[4 x i8*]')走原清洗,保持既有语义。
|
||
"""
|
||
lb: int = name.find('[')
|
||
if lb > 0:
|
||
head, tail = name[:lb], name[lb:]
|
||
else:
|
||
head, tail = name, ''
|
||
head = (head.replace(' *', '').replace('*', '').replace(' ', '_')
|
||
.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"),
|
||
# 先尝试用短名称(如 "zbit_writer")查找已注册的 struct,
|
||
# 避免为同一 struct 创建多个不同的类型对象导致跨模块引用类型不一致
|
||
if '.' in clean_name and clean_name not in self.structs:
|
||
parts: list[str] = clean_name.split('.', 1)
|
||
if len(parts[0]) == 16 and all(c in '0123456789abcdef' for c in parts[0]):
|
||
short_name: str = parts[1]
|
||
if short_name in self.structs:
|
||
existing_short: ir.IdentifiedStructType = self.structs[short_name]
|
||
# 治本修复:同名类 sha1 冲突检测。
|
||
# 当请求的 sha1 前缀与现有 struct 的 sha1 前缀不一致时
|
||
# (如 stmts.py:Module 与 __module.py:Module 同名冲突),
|
||
# 不能复用已有 struct(set_body 已锁定为错误布局),
|
||
# 需创建独立 struct 并以 sha1 前缀全名存储。
|
||
_existing_sha1: str = self._extract_struct_sha1(existing_short)
|
||
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
|
||
return existing_short
|
||
# 优先解析 typedef 整数类型(如 INT/UINT/BYTE),避免将其注册为 opaque struct。
|
||
# 必须在 `if clean_name in self.structs` 缓存检查之前执行,
|
||
# 否则首次调用(SymbolTable 未就绪时)会将 typedef 名缓存为 opaque struct,
|
||
# 后续调用即使 SymbolTable 就绪也只会返回缓存的错误类型。
|
||
# 此修复同时解决两个问题:
|
||
# 1. alloca unsized type:变量声明 `argc: INT = 6` 不再生成 `alloca %"...INT"`
|
||
# 2. 下标访问:FindStructNameInAnnotation 不再匹配 typedef 名(因不在 Gen.structs)
|
||
td_width: int | None = self._resolve_typedef_int_width(clean_name)
|
||
if td_width is not None:
|
||
if td_width > 0:
|
||
self.typedef_int_widths[clean_name] = td_width
|
||
return ir.IntType(td_width)
|
||
# td_width == 0: 特殊 typedef 名称(str/bytes/list 等),返回 i8
|
||
self.typedef_int_widths[clean_name] = 0
|
||
return ir.IntType(8)
|
||
if clean_name in self.structs:
|
||
existing: ir.IdentifiedStructType = self.structs[clean_name]
|
||
# 治本修复:同名类 sha1 冲突检测(短名调用路径)。
|
||
# 当 source_sha1 与现有 struct 的 sha1 前缀不一致时(跨模块同名类冲突),
|
||
# 不能复用已有 struct(set_body 已锁定为错误布局),
|
||
# 需创建独立 struct 并以 sha1 前缀全名存储,不覆盖短名条目。
|
||
if source_sha1:
|
||
_existing_sha1: str = self._extract_struct_sha1(existing)
|
||
if _existing_sha1 and _existing_sha1 != source_sha1:
|
||
full_key: str = f"{source_sha1}.{clean_name}"
|
||
if full_key in self.structs:
|
||
st_full: ir.IdentifiedStructType = self.structs[full_key]
|
||
if packed and isinstance(st_full, ir.IdentifiedStructType) and not st_full.packed:
|
||
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
|
||
if packed and isinstance(existing, ir.IdentifiedStructType) and not existing.packed:
|
||
existing.packed = True
|
||
return existing
|
||
if clean_name in {'typedef', 'CType', 'CVolatile', 'CEnum', 'CUnion', 'CStruct', 'enum', 'REnum', 'renum', 'Void', 'void'}:
|
||
return ir.IntType(8)
|
||
# typing.Any 是"任意类型"标记,应解析为 i8*(通用指针),
|
||
# 而非创建为 opaque 结构体(否则包含 Any 字段的结构体会变成 unsized)。
|
||
if clean_name == 'Any':
|
||
return ir.IntType(8).as_pointer()
|
||
# t.CType 的基本类型子类(CInt/CChar/CSizeT/CLong/CFloat/CDouble 等,
|
||
# position 含 BASE 且 Size>0)有具体 LLVM 类型,应直接返回对应的
|
||
# ir.IntType/ir.FloatType/ir.DoubleType,而非创建结构体。
|
||
# _is_ctype_marker_name 对这些类型返回 False(不跳过),需要在此处拦截。
|
||
_basic_llvm_type: ir.Type | None = self._try_resolve_ctype_basic(clean_name)
|
||
if _basic_llvm_type is not None:
|
||
return _basic_llvm_type
|
||
# t.CType 及其子类是"类型强转"标记,不应被编译为真实结构体。
|
||
# 动态检查 t 模块中所有 CType 子类名(排除 CStruct/CEnum/CUnion/REnum 子类,
|
||
# 因为用户定义的结构体类继承自它们,需要被创建为结构体)。
|
||
if self._is_ctype_marker_name(clean_name):
|
||
return ir.IntType(8)
|
||
if self.SymbolTable:
|
||
Entry: _CTypeInfo | None = self.SymbolTable.lookup(clean_name)
|
||
if Entry and Entry.IsTypedef:
|
||
resolved_str: str = self._resolve_typedef(clean_name)
|
||
if resolved_str != clean_name:
|
||
return self._type_str_to_llvm(resolved_str)
|
||
if Entry.BaseType:
|
||
if not isinstance(Entry.BaseType, (_t._CTypedef,)) or Entry.PtrCount > 0:
|
||
resolved: str = self._resolve_ctype_to_str(Entry)
|
||
if resolved:
|
||
return self._type_str_to_llvm(resolved)
|
||
if Entry and Entry.IsRenum:
|
||
if clean_name in self.structs:
|
||
return self.structs[clean_name]
|
||
# REnum 结构体尚未创建时不能 fall through 到 IsEnum(会返回 i32),
|
||
# 需要继续到下方正常结构体创建路径,创建 opaque 结构体。
|
||
# 这对自引用 REnum(如 LLVMType 包含 LLVMType*)至关重要:
|
||
# _parse_llvm_type 先通过此路径创建 opaque 结构体,_TryLoadStructFromStub 再 set_body。
|
||
elif Entry and Entry.IsEnum and clean_name not in self.class_methods:
|
||
# 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,
|
||
# 导致 _TryLoadStructFromStub 的 set_body 被跳过(isinstance 检查失败),
|
||
# struct 保持不完整字段数,引发跨模块 GEP 越界崩溃。
|
||
_is_class_name: bool = clean_name in self.class_methods or clean_name in self.class_members
|
||
if Entry and Entry.IsExceptionClass and not _is_class_name:
|
||
return ir.IntType(32)
|
||
if Entry and Entry.IsFunction and not _is_class_name:
|
||
return ir.PointerType(ir.IntType(8))
|
||
if Entry and Entry.IsVariable and not _is_class_name:
|
||
return ir.PointerType(ir.IntType(8))
|
||
if Entry and Entry.BaseType:
|
||
if isinstance(Entry.BaseType, type) and issubclass(Entry.BaseType, _t.CEnum):
|
||
return self._enum_int_type(clean_name, Entry)
|
||
if Entry.BaseType is _t.CEnum:
|
||
return self._enum_int_type(clean_name, Entry)
|
||
ns_name: str
|
||
if source_sha1:
|
||
ns_name = f"{source_sha1}.{clean_name}"
|
||
elif clean_name in self._struct_sha1_map:
|
||
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 的底层整数位宽
|
||
width: int | None = self._resolve_typedef_int_width(clean_name)
|
||
if width is not None:
|
||
self.typedef_int_widths[clean_name] = width
|
||
return st
|
||
|
||
def _resolve_typedef_int_width(self, name: str) -> int | None:
|
||
"""解析 typedef 名称的底层整数位宽
|
||
|
||
通过 CTypeRegistry 和 BuiltinTypeMap 动态解析,
|
||
替代硬编码的 TYPEDEF_NAMES 映射表。
|
||
返回 int 位宽或 None(无法解析)。
|
||
"""
|
||
# 1. 先查 BuiltinTypeMap(覆盖 BYTEPTR 等指针别名)
|
||
bm: Any = BuiltinTypeMap.Get(name)
|
||
if bm:
|
||
ctype_cls: Any
|
||
ptr_count: int
|
||
ctype_cls, ptr_count = bm
|
||
if ptr_count > 0:
|
||
return None # 指针类型,不是整数 typedef
|
||
llvm_str: str = CTypeRegistry.CTypeToLLVM(ctype_cls)
|
||
if llvm_str and llvm_str.startswith('i') and llvm_str[1:].isdigit():
|
||
return int(llvm_str[1:])
|
||
|
||
# 2. 再查 SymbolTable(用户定义的 typedef)
|
||
if self.SymbolTable:
|
||
Entry: _CTypeInfo | None = self.SymbolTable.lookup(name)
|
||
if Entry and Entry.IsTypedef:
|
||
if Entry.BaseType:
|
||
if not isinstance(Entry.BaseType, (_t._CTypedef,)) and Entry.PtrCount == 0:
|
||
llvm_str: str = CTypeRegistry.CTypeToLLVM(type(Entry.BaseType) if isinstance(Entry.BaseType, _t.CType) else Entry.BaseType)
|
||
if llvm_str and llvm_str.startswith('i') and llvm_str[1:].isdigit():
|
||
return int(llvm_str[1:])
|
||
if Entry.OriginalType:
|
||
if isinstance(Entry.OriginalType, _CTypeInfo) and Entry.OriginalType.BaseType:
|
||
if not isinstance(Entry.OriginalType.BaseType, (_t._CTypedef,)) and Entry.OriginalType.PtrCount == 0:
|
||
llvm_str: str = CTypeRegistry.CTypeToLLVM(type(Entry.OriginalType.BaseType) if isinstance(Entry.OriginalType.BaseType, _t.CType) else Entry.OriginalType.BaseType)
|
||
if llvm_str and llvm_str.startswith('i') and llvm_str[1:].isdigit():
|
||
return int(llvm_str[1:])
|
||
elif isinstance(Entry.OriginalType, str):
|
||
# OriginalType 是类型名字符串(如 'CInt', 'CUnsignedInt'),
|
||
# 通过 CTypeRegistry 解析为整数位宽
|
||
cls: Any = CTypeRegistry.GetClassByName(Entry.OriginalType)
|
||
if cls:
|
||
llvm_str: str = CTypeRegistry.CTypeToLLVM(cls)
|
||
if llvm_str and llvm_str.startswith('i') and llvm_str[1:].isdigit():
|
||
return int(llvm_str[1:])
|
||
|
||
# 3. 尝试 CTypeRegistry 直接按名称查找
|
||
ctype_cls: Any = CTypeRegistry.GetClassByName(name)
|
||
if ctype_cls:
|
||
llvm_str: str = CTypeRegistry.CTypeToLLVM(ctype_cls)
|
||
if llvm_str and llvm_str.startswith('i') and llvm_str[1:].isdigit():
|
||
return int(llvm_str[1:])
|
||
|
||
# 4. 特殊名称(非整数类型,但需要标记为 typedef 以跳过 opaque 定义)
|
||
# 注意:如果 name 已注册为 struct(用户定义了同名 class,如 includes/_dict.py 中的 dict),
|
||
# 则不应视为 typedef,应返回 None 让调用方走 struct 创建路径。
|
||
# 解决时序问题:_parse_llvm_declare 从 stub.ll 解析时 dict 可能尚未注册到 Gen.structs,
|
||
# 但 SymbolTable 在 .pyi 解析阶段已就绪,可通过 is_struct 检查区分 class 和 typedef。
|
||
_SPECIAL_TYPEDEFS: set[str] = {
|
||
'typedef', 'CType', 'CVolatile', 'CEnum', 'CUnion',
|
||
'CStruct', 'enum', 'REnum', 'renum',
|
||
'list', 'dict', 'tuple', 'set', 'str', 'bytes', 'array', 'type', 'CArray',
|
||
}
|
||
if name in _SPECIAL_TYPEDEFS:
|
||
if name in self.structs:
|
||
return None # 已注册为 struct,不视为 typedef
|
||
if self.SymbolTable is not None:
|
||
try:
|
||
if self.SymbolTable.is_struct(name):
|
||
return None # SymbolTable 中标记为 struct,不视为 typedef
|
||
except Exception:
|
||
pass
|
||
return 0 # 标记为 typedef,但位宽为 0(非整数)
|
||
|
||
# t.CType 及其子类是"类型强转"标记,不应被创建为结构体。
|
||
# 动态检查 t 模块中所有 CType 子类名(排除 CStruct/CEnum/CUnion/REnum 子类)。
|
||
# 返回 0 标记为 typedef,使 _get_or_create_struct 走 typedef 路径返回 i8。
|
||
if self._is_ctype_marker_name(name):
|
||
return 0
|
||
|
||
return None
|
||
|
||
@staticmethod
|
||
def _is_ctype_marker_name(name: str) -> bool:
|
||
"""检查名称是否是 t.CType 的"类型强转"标记子类名。
|
||
|
||
只有 position 不含 BASE 的纯修饰符/标记类型(CPtr/CVolatile/CConst/
|
||
CInline/CStatic/CExtern/CExport/CRegister/CAuto/_CTypedef/CTypeDefault 等)
|
||
才应该跳过结构体创建。有 BASE position 的基本类型(CInt/CChar/CSizeT/
|
||
CLong/CFloat/CDouble 等)有具体大小,应被正常解析为对应宽度的整数/浮点。
|
||
|
||
CStruct/CEnum/CUnion/REnum 的用户子类是合法结构体,需要被创建。
|
||
|
||
注意:CTypeRegistry.GetClassByName 会跳过 CType 本身和 CTypeDefault,
|
||
所以这里直接用 getattr(t, name) 检查 t 模块属性。
|
||
|
||
Returns:
|
||
True 如果 name 是 CType 标记子类名(应跳过结构体创建)
|
||
"""
|
||
if not name:
|
||
return False
|
||
# 去除可能的 SHA1 前缀(如 "abc123.CPtr" -> "CPtr")
|
||
short_name: str = name.split('.')[-1] if '.' in name else name
|
||
# 直接查 t 模块属性(CTypeRegistry._build 会跳过 CType/CTypeDefault/BigEndian/LittleEndian)
|
||
t_cls: Any = getattr(_t, short_name, None)
|
||
if not isinstance(t_cls, type) or not issubclass(t_cls, _t.CType):
|
||
return False
|
||
# 排除 CStruct/CEnum/CUnion/REnum 子类(它们是命名类型的基类,
|
||
# 用户定义的结构体继承自它们,需要被创建为结构体)
|
||
if issubclass(t_cls, (_t.CStruct, _t.CEnum, _t.REnum, _t.CUnion)):
|
||
return False
|
||
# 基本类型(position 含 BASE 且 Size > 0)有具体大小,不应跳过
|
||
# (CInt→i32, CChar→i8, CSizeT→i64, CLong→i64, CFloat→f32 等)
|
||
# CVoid 虽含 BASE 但 Size=0,CType Size=None,仍需跳过
|
||
if _t.CType.BASE in t_cls.position:
|
||
# 检查实例的 Size(类属性可能未设置,用实例获取)
|
||
try:
|
||
inst = t_cls()
|
||
size_val = getattr(inst, 'Size', None)
|
||
if size_val is not None and size_val > 0:
|
||
return False
|
||
except Exception:
|
||
pass
|
||
return True
|
||
|
||
def _try_resolve_ctype_basic(self, name: str) -> ir.Type | None:
|
||
"""尝试将 t.CType 的基本类型子类名解析为对应的 LLVM 基本类型。
|
||
|
||
仅对 t 模块内的 CType 基本类型子类(CInt/CChar/CSizeT/CLong/CFloat/
|
||
CDouble 等,即 position 含 BASE 且 Size>0)生效,返回对应的
|
||
ir.IntType/ir.FloatType/ir.DoubleType。
|
||
|
||
CStruct/CEnum/CUnion/REnum 子类不在此处理(用户自定义结构体需要被
|
||
创建为结构体)。CVoid/CType/CPtr 等无 Size 或 Size=0 的类型也不在此
|
||
处理(应通过 _is_ctype_marker_name 跳过结构体创建)。
|
||
|
||
Returns:
|
||
对应的 ir.Type,或 None 表示不是基本类型,需要继续走结构体创建路径。
|
||
"""
|
||
if not name:
|
||
return None
|
||
short_name: str = name.split('.')[-1] if '.' in name else name
|
||
cls: Any = getattr(_t, short_name, None)
|
||
if not isinstance(cls, type) or not issubclass(cls, _t.CType):
|
||
return None
|
||
if issubclass(cls, (_t.CStruct, _t.CEnum, _t.REnum, _t.CUnion)):
|
||
return None
|
||
if _t.CType.BASE not in getattr(cls, 'position', frozenset()):
|
||
return None
|
||
try:
|
||
inst = cls()
|
||
size_val = getattr(inst, 'Size', None)
|
||
if size_val is None or size_val <= 0:
|
||
return None
|
||
except Exception:
|
||
return None
|
||
llvm_str: str | None = CTypeRegistry.NameToLLVM(short_name)
|
||
if not llvm_str:
|
||
return None
|
||
return self._type_str_to_llvm(llvm_str)
|
||
|
||
def _set_node_info(self, node: ast.AST, extra_info: str = "") -> None:
|
||
if hasattr(node, 'lineno'):
|
||
self._current_lineno = node.lineno
|
||
else:
|
||
self._current_lineno = 0
|
||
if hasattr(node, 'col_offset'):
|
||
self._current_node_info = f"line {self._current_lineno}, col {node.col_offset}"
|
||
else:
|
||
self._current_node_info = f"line {self._current_lineno}"
|
||
if extra_info:
|
||
self._current_node_info += f" ({extra_info})"
|
||
# 语句/指令级 DWARF 位置(--dwarf / --asan):每进入一条语句就把当前
|
||
# builder 的默认调试位置切到该 AST 节点的行/列(col_offset 是 0-based,
|
||
# +1 转 1-based;无位置时为 0 → 回退函数入口 DILocation)。此后该语句生成
|
||
# 的所有指令(含 call)自动带本函数 scope 的 !dbg,llc 不再因缺 !dbg 丢弃
|
||
# 整份 DI。未启用时 dwarf_enabled=False,一次都不触碰 builder.debug_metadata
|
||
# (零影响:_GetDebugInfo() 也不会被调用)。
|
||
if self.dwarf_enabled and self.builder is not None:
|
||
_DbgInfo = self._GetDebugInfo()
|
||
if _DbgInfo is not None:
|
||
_DbgInfo.SetStatement(
|
||
self.builder, self._current_lineno,
|
||
getattr(node, 'col_offset', -1) + 1)
|
||
|
||
def _set_source_info(self, filepath: str) -> None:
|
||
self._current_source_file = filepath
|
||
try:
|
||
with open(filepath, 'r', encoding='utf-8') as f:
|
||
self._current_source_lines = f.readlines()
|
||
except Exception: # 文件读取失败时回退为空行列表
|
||
self._current_source_lines = []
|
||
|
||
def _GetDebugInfo(self) -> Any:
|
||
"""返回本模块的 DebugInfoEmitter;未启用 --dwarf 或无源文件时返回 None。
|
||
|
||
未启用(dwarf_enabled=False)时不构造任何对象、不产生任何 IR 差异,
|
||
行为与改动前逐字节一致。源文件取 _current_source_file(由 GenerateCCode
|
||
在生成 IR 前设定,模块内恒定),每个模块一份 DICompileUnit + DIFile。
|
||
"""
|
||
if not self.dwarf_enabled:
|
||
return None
|
||
if not self._current_source_file:
|
||
return None
|
||
if self.DebugInfo is None:
|
||
from lib.core.LLVMCG.DebugInfo import DebugInfoEmitter
|
||
self.DebugInfo = DebugInfoEmitter(self.module, self._current_source_file)
|
||
return self.DebugInfo
|
||
|
||
def _skip_mangle(self, name: str) -> bool:
|
||
skip_prefixes: tuple[str, ...] = ('llvm.', 'str_const_', 'printf', 'memset_pattern')
|
||
if any(name.startswith(p) for p in skip_prefixes):
|
||
return True
|
||
if '.' in name:
|
||
parts: list[str] = name.split('.', 1)
|
||
if len(parts[0]) >= 8 and all(c in '0123456789abcdef' for c in parts[0]):
|
||
return True
|
||
return False
|
||
|
||
def _mangle_name(self, name: str) -> str:
|
||
if self._skip_mangle(name):
|
||
return name
|
||
if name in self._export_funcs:
|
||
return name
|
||
if self.module_sha1:
|
||
return f"{self.module_sha1}.{name}"
|
||
return name
|
||
|
||
def _find_function(self, name: str) -> ir.Function | None:
|
||
if name in self.functions:
|
||
return self.functions[name]
|
||
g: Any = self.module.globals.get(name)
|
||
if g and isinstance(g, ir.Function):
|
||
self.functions[name] = g
|
||
return g
|
||
mangled: str = self._mangle_func_name(name)
|
||
if mangled != name:
|
||
if mangled in self.functions:
|
||
self.functions[name] = self.functions[mangled]
|
||
return self.functions[mangled]
|
||
g = self.module.globals.get(mangled)
|
||
if g and isinstance(g, ir.Function):
|
||
self.functions[mangled] = g
|
||
self.functions[name] = g
|
||
return g
|
||
for sha1 in self.ModuleSha1Map.values():
|
||
prefixed: str = f"{sha1}.{name}"
|
||
if prefixed in self.functions:
|
||
self.functions[name] = self.functions[prefixed]
|
||
return self.functions[prefixed]
|
||
g = self.module.globals.get(prefixed)
|
||
if g and isinstance(g, ir.Function):
|
||
self.functions[prefixed] = g
|
||
self.functions[name] = g
|
||
return g
|
||
return None
|
||
|
||
def _has_function(self, name: str) -> bool:
|
||
return self._find_function(name) is not None
|
||
|
||
def _get_mro_chain(self, ClassName: str) -> list[str]:
|
||
"""MRO 线性化类名列表(深度优先 + 声明顺序 + 去重,含自身)。
|
||
|
||
顺序: 自身 → 父类1 及祖先 → 父类2 及祖先(先声明父类优先,菱形去重)。
|
||
与 TPV HandlesStruct.get_mro_chain 的语义一致。
|
||
|
||
支持带 sha1 前缀的类名(如 "c9b44d17cd621a75.CallGenLib",跨模块
|
||
structs 表键):查 class_parents 前剥离前缀,否则表键(裸类名)不匹配
|
||
→ MRO 链只剩自身 → 继承方法调用 fail-fast "Undefined method
|
||
'find_func_in_module' in class '...CallGenLib'"(CallGenLib 根因)。
|
||
"""
|
||
chain: list[str] = []
|
||
visited: set[str] = set()
|
||
stack: list[str] = [ClassName]
|
||
parents_map: dict[str, list[str]] = getattr(self, 'class_parents', {})
|
||
while stack:
|
||
cur: str = stack.pop()
|
||
if not cur or cur in visited:
|
||
continue
|
||
visited.add(cur)
|
||
chain.append(cur)
|
||
# 归一化:剥离 "16位hex." 前缀后用裸类名查父类表
|
||
lookup: str = cur
|
||
if len(cur) > 17 and cur[16] == '.':
|
||
lookup = cur[17:]
|
||
parents: list[str] = parents_map.get(lookup, parents_map.get(cur, []))
|
||
# 父类逆序压栈,保证声明顺序弹出(先访问父类1 的整条链)
|
||
for p in reversed(parents):
|
||
if p and p not in visited:
|
||
stack.append(p)
|
||
return chain
|
||
|
||
def _get_parent_classes(self, ClassName: str) -> list[str]:
|
||
"""获取类的所有父类名(声明顺序),兼容旧 class_parent 单链。"""
|
||
parents_map: dict[str, list[str]] = getattr(self, 'class_parents', {})
|
||
parents: list[str] = parents_map.get(ClassName)
|
||
if parents:
|
||
return parents
|
||
p: str | None = self.class_parent.get(ClassName)
|
||
if p:
|
||
return [p]
|
||
return []
|
||
|
||
def _get_or_declare_function(self, mangled_name: str, func_type: ir.FunctionType) -> ir.Function:
|
||
# DDEBUG: 检测 mangled_name 含双点(不应到达这里)
|
||
if '..' in mangled_name:
|
||
import sys
|
||
sys.stderr.write(f"[DDEBUG] _get_or_declare_function DOUBLE-DOT mangled_name='{mangled_name}'\n")
|
||
import traceback
|
||
traceback.print_stack(file=sys.stderr, limit=10)
|
||
sys.stderr.flush()
|
||
sys.exit(1)
|
||
g = self.module.globals.get(mangled_name)
|
||
if g and isinstance(g, ir.Function):
|
||
return g
|
||
return ir.Function(self.module, func_type, name=mangled_name)
|
||
|
||
def _get_function(self, name: str) -> ir.Function | None:
|
||
func: ir.Function | None = self._find_function(name)
|
||
return func
|
||
|
||
def _mangle_func_name(self, name: str, module_name: str | None = None) -> str:
|
||
# DDEBUG: 检测即将产生双点的情况,立即打印并终止
|
||
if name.startswith('.') or (module_name and module_name.endswith('.') and len(module_name) > 0):
|
||
import sys
|
||
sys.stderr.write(f"[DDEBUG] _mangle_func_name LEADING-DOT name='{name}' module_name='{module_name}'\n")
|
||
import traceback
|
||
traceback.print_stack(file=sys.stderr, limit=8)
|
||
sys.stderr.flush()
|
||
sys.exit(1)
|
||
if name in self._export_funcs and not module_name:
|
||
return name
|
||
if name in self._export_extern_funcs:
|
||
target_sha1: str = self._export_extern_funcs[name]
|
||
if module_name:
|
||
# 显式带 module_name 调用(如 stdlib.system):即使当前模块定义了同名函数,
|
||
# 也应按模块匹配 extern,返回裸名。snprintf 之所以工作而 system 不工作,
|
||
# 就是因为 os._win32 定义了自己的 system 包装,导致旧逻辑跳过了 extern 匹配。
|
||
if module_name == target_sha1:
|
||
return name
|
||
if module_name in self.ModuleSha1Map and self.ModuleSha1Map[module_name] == target_sha1:
|
||
return name
|
||
init_name: str = f"{module_name}.__init__"
|
||
if init_name in self.ModuleSha1Map and self.ModuleSha1Map[init_name] == target_sha1:
|
||
return name
|
||
# 兜底:module_name 不在 ModuleSha1Map 中或 sha1 不匹配,
|
||
# 但函数在 _export_extern_funcs 中(C extern 不应混淆),
|
||
# 仅当当前模块未定义同名函数时才返回裸名。
|
||
if name not in self._current_module_func_names:
|
||
return name
|
||
elif name not in self._current_module_func_names:
|
||
# 无 module_name 调用(如 from stdlib import system; system()):
|
||
# 仅当当前模块未定义同名函数时才返回 extern 裸名,
|
||
# 否则让本地定义走正常 mangle 路径(本地 def 遮蔽 import 语义)。
|
||
return name
|
||
# main 函数作为程序入口点,永远不混淆
|
||
if name == 'main':
|
||
return name
|
||
# Try class_sha1_map first for class methods (e.g., 'md5.__init__' -> class 'md5')
|
||
class_sha1_map: dict[str, str] = self.class_sha1_map
|
||
if class_sha1_map:
|
||
if '.' in name:
|
||
base_class: str = name.split('.')[0]
|
||
if base_class in class_sha1_map:
|
||
sha1: str = class_sha1_map[base_class]
|
||
return f"{sha1}.{name}"
|
||
elif name in class_sha1_map:
|
||
sha1: str = class_sha1_map[name]
|
||
return f"{sha1}.{name}"
|
||
# If module_name is already a SHA1 hash (16 hex chars), use it directly
|
||
if module_name and len(module_name) == 16 and all(c in '0123456789abcdef' for c in module_name):
|
||
return f"{module_name}.{name}"
|
||
if module_name and module_name in self.ModuleSha1Map:
|
||
sha1: str = self.ModuleSha1Map[module_name]
|
||
return f"{sha1}.{name}"
|
||
# Try parent package name (e.g., 'hashlib' -> 'hashlib.__init__')
|
||
if module_name:
|
||
init_name: str = f"{module_name}.__init__"
|
||
if init_name in self.ModuleSha1Map:
|
||
sha1: str = self.ModuleSha1Map[init_name]
|
||
return f"{sha1}.{name}"
|
||
if self._skip_mangle(name):
|
||
return name
|
||
if self.module_sha1:
|
||
result = f"{self.module_sha1}.{name}"
|
||
else:
|
||
result = name
|
||
# DDEBUG: 检测返回结果中是否含双点
|
||
if '..' in result:
|
||
import sys
|
||
sys.stderr.write(f"[DDEBUG] _mangle_func_name RESULT-DOUBLE-DOT name='{name}' module_name='{module_name}' result='{result}'\n")
|
||
import traceback
|
||
traceback.print_stack(file=sys.stderr, limit=10)
|
||
sys.stderr.flush()
|
||
sys.exit(1)
|
||
return result
|
||
|
||
def _get_source_line(self, lineno: int) -> str | None:
|
||
if 0 < lineno <= len(self._current_source_lines):
|
||
return self._current_source_lines[lineno - 1].rstrip('\r\n')
|
||
return None
|
||
|
||
def _get_node_info(self) -> str:
|
||
info: str = ""
|
||
if self._current_node_info:
|
||
info = f" [{self._current_node_info}]"
|
||
if self._current_source_file:
|
||
info += f" in {self._current_source_file}"
|
||
lineno: int = self._current_lineno
|
||
if lineno > 0:
|
||
info += f", line {lineno}:"
|
||
source_line: str | None = self._get_source_line(lineno)
|
||
if source_line:
|
||
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:
|
||
"""创建字符串全局变量并返回 i8* 指针。
|
||
|
||
统一字符串全局变量创建模式:编码为 utf-8 + '\\x00' 终止符,
|
||
创建 internal linkage 的 GlobalVariable,返回 i8* 指针常量。
|
||
"""
|
||
str_val: str = s + '\x00'
|
||
str_bytes: bytes = str_val.encode('utf-8')
|
||
arr_type: ir.ArrayType = ir.ArrayType(ir.IntType(8), len(str_bytes))
|
||
gv_name: str = name or f"str_const_{self.string_const_counter}"
|
||
self.string_const_counter += 1
|
||
gv: ir.GlobalVariable = ir.GlobalVariable(self.module, arr_type, name=gv_name)
|
||
gv.initializer = ir.Constant(arr_type, bytearray(str_bytes))
|
||
gv.linkage = 'internal'
|
||
return ir.Constant(ir.PointerType(ir.IntType(8)), gv.get_reference())
|
||
|
||
def _create_doc_global(self, symbol_name: str, docstring: str | None) -> None:
|
||
"""为符号创建 __doc__ 全局常量。
|
||
|
||
- docstring 为 None 或空字符串时,不创建常量(访问时内联空指针)
|
||
- slice_level != 0(优化开启)时,内容视为空
|
||
- 非空时创建 internal linkage、global_constant=True 的只读字符串常量
|
||
- 常量名格式:{module_sha1}.{symbol_name}.__doc__
|
||
"""
|
||
slice_level: int = getattr(self._Trans, 'SliceLevel', 3)
|
||
if slice_level != 0:
|
||
docstring = None
|
||
if docstring is None or docstring == '':
|
||
self._doc_strings[symbol_name] = None
|
||
return
|
||
self._doc_strings[symbol_name] = docstring
|
||
mangled: str = self._mangle_name(f"{symbol_name}.__doc__")
|
||
self._create_string_global(docstring, name=mangled)
|
||
gv: ir.GlobalVariable | None = self.module.globals.get(mangled)
|
||
if gv is not None:
|
||
gv.global_constant = True
|
||
|
||
def _get_doc_ptr(self, symbol_name: str) -> ir.Value | None:
|
||
"""获取符号 __doc__ 的 i8* 指针。
|
||
|
||
- 若符号未注册 __doc__,返回 None(调用方回退到其他解析)
|
||
- 若 __doc__ 为空(无 docstring 或优化剥离),返回 i8* null 内联空指针
|
||
- 若 __doc__ 非空,返回指向字符串全局常量的 i8* 指针
|
||
- 跨模块 docstring(在 _doc_strings 中有文本但 module.globals 无对应常量)首次访问时创建本地 internal linkage 只读副本
|
||
"""
|
||
if symbol_name not in self._doc_strings:
|
||
return None
|
||
docstring: str | None = self._doc_strings[symbol_name]
|
||
if docstring is None or docstring == '':
|
||
return ir.Constant(ir.PointerType(ir.IntType(8)), None)
|
||
mangled: str = self._mangle_name(f"{symbol_name}.__doc__")
|
||
gv: ir.GlobalVariable | None = self.module.globals.get(mangled)
|
||
if gv is None:
|
||
# 跨模块 docstring:创建本地 internal linkage 只读字符串常量副本
|
||
self._create_string_global(docstring, name=mangled)
|
||
gv = self.module.globals.get(mangled)
|
||
if gv is None:
|
||
return ir.Constant(ir.PointerType(ir.IntType(8)), None)
|
||
gv.global_constant = True
|
||
zero: ir.Constant = ir.Constant(ir.IntType(32), 0)
|
||
return self.builder.gep(gv, [zero, zero], name=f"{symbol_name}_doc_ptr")
|
||
|
||
def _apply_bswap_if_big(self, val: ir.Value, byte_order: str, name: str) -> ir.Value:
|
||
"""大端字节序时对整数应用 bswap,否则原值返回。
|
||
|
||
仅对 16/32/64 位整数生效,统一 bswap 逻辑。
|
||
"""
|
||
if byte_order == 'big' and isinstance(val.type, ir.IntType) and val.type.width in (16, 32, 64):
|
||
return self.builder.bswap(val, name=name)
|
||
return val
|
||
|
||
def _zero_value_for_type(self, llvm_type: ir.Type) -> ir.Constant | None:
|
||
"""为指定 LLVM 类型生成零值常量。
|
||
|
||
合并原 HandlesAssign._zero_value 与 StructGen._zero_value_for_type 的逻辑。
|
||
返回 ir.Constant 或 None(无法生成零值的类型,如 opaque struct)。
|
||
"""
|
||
if isinstance(llvm_type, ir.IntType):
|
||
return ir.Constant(llvm_type, 0)
|
||
elif isinstance(llvm_type, (ir.FloatType, ir.DoubleType)):
|
||
return ir.Constant(llvm_type, 0.0)
|
||
elif isinstance(llvm_type, ir.PointerType):
|
||
return ir.Constant(llvm_type, None)
|
||
elif isinstance(llvm_type, ir.ArrayType):
|
||
elem_zv: ir.Constant | None = self._zero_value_for_type(llvm_type.element)
|
||
if elem_zv is None:
|
||
return None
|
||
return ir.Constant(llvm_type, [elem_zv] * llvm_type.count)
|
||
elif isinstance(llvm_type, ir.BaseStructType):
|
||
if llvm_type.elements is not None and len(llvm_type.elements) > 0:
|
||
elem_zvs: list[ir.Constant | None] = [self._zero_value_for_type(m) for m in llvm_type.elements]
|
||
if any(zv is None for zv in elem_zvs):
|
||
return None
|
||
return ir.Constant(llvm_type, elem_zvs)
|
||
return None
|
||
return None
|
||
|
||
# ========== 调用点热点统计基础设施(--hotspot-sites) ==========
|
||
|
||
def _UniqueGlobalName(self, base: str) -> str:
|
||
"""返回本模块内未被占用的全局符号名(追加下划线直到不冲突)。
|
||
|
||
仅用于 --hotspot-sites 自动生成的内部符号,避免与用户全局/函数重名。
|
||
"""
|
||
name: str = base
|
||
while name in self.module.globals:
|
||
name += '_'
|
||
return name
|
||
|
||
def _DeclareExternalFunc(self, name: str, ftype: ir.FunctionType) -> ir.Function:
|
||
"""获取或声明外部 C 函数(用于 --hotspot-sites 所需的 Win32/CRT 接口)。
|
||
|
||
已存在同名函数时直接复用;若已存在符号类型不一致则显式报错(不静默降级)。
|
||
"""
|
||
existing: Any = self.module.globals.get(name)
|
||
if existing is not None:
|
||
if not isinstance(existing, ir.Function):
|
||
raise RuntimeError(
|
||
f'--hotspot-sites: 全局符号 {name} 已存在且不是函数,无法声明外部接口')
|
||
if existing.ftype != ftype:
|
||
raise RuntimeError(
|
||
f'--hotspot-sites: 外部函数 {name} 已存在但签名不一致'
|
||
f'(期望 {ftype},实际 {existing.ftype})')
|
||
return existing
|
||
return ir.Function(self.module, ftype, name=name)
|
||
|
||
def _InstallHotspotTruncate(
|
||
self,
|
||
defs: list[ir.Function],
|
||
path: str,
|
||
require_main: bool = False,
|
||
flag: str = '--hotspot-sites',
|
||
) -> ir.Function | None:
|
||
"""在含 main 的模块的 main 入口插入一次 "w" 打开→关闭,清空计数文件。
|
||
|
||
--hotspot-sites 的各处理器(调用点计数、每函数计时)以追加("a")方式写
|
||
<path>,若不清空会累积历史运行的数据。清空必须满足两点:
|
||
- 只在含 main 的模块执行一次(其它模块无 main,返回 None 且不截断);
|
||
- 必须在 main 入口而非构造器:@llvm.global_ctors 的跨模块执行顺序无保证,
|
||
放构造器里可能在其它模块已写入后清空 → 丢数据。
|
||
main 入口在程序启动、任何 Ctrl+C 处理器写入之前执行,故每次运行都以空文件
|
||
开始,配合追加写保证"一次运行一份结果"。
|
||
|
||
flag 仅用于报错信息(调用点计数与每函数计时各自调用一次,传入对应开关名)。
|
||
|
||
返回找到的 main 定义(供调用方继续注册处理器);未找到时返回 None。
|
||
require_main=True(种子 CLI 单模块)时未找到 main 直接报错(不静默降级)。
|
||
"""
|
||
main_func: ir.Function | None = None
|
||
for func in defs:
|
||
if func.name == 'main':
|
||
main_func = func
|
||
break
|
||
if main_func is None:
|
||
if require_main:
|
||
raise RuntimeError(
|
||
f'{flag}: 找不到 main 函数定义,无法清空计数文件')
|
||
return None
|
||
i32: ir.IntType = ir.IntType(32)
|
||
i8p: ir.PointerType = ir.PointerType(ir.IntType(8))
|
||
fopen: ir.Function = self._DeclareExternalFunc('fopen', ir.FunctionType(i8p, [i8p, i8p]))
|
||
fclose: ir.Function = self._DeclareExternalFunc('fclose', ir.FunctionType(i32, [i8p]))
|
||
path_ptr: ir.Constant = self._create_string_global(
|
||
path, name=self._UniqueGlobalName('__fhs_trunc_path'))
|
||
mode_ptr: ir.Constant = self._create_string_global(
|
||
'w', name=self._UniqueGlobalName('__fhs_trunc_mode'))
|
||
b: ir.IRBuilder = ir.IRBuilder(main_func.entry_basic_block)
|
||
b.position_at_start(main_func.entry_basic_block)
|
||
fp: ir.Value = b.call(fopen, [path_ptr, mode_ptr], name='fhs_trunc_fp')
|
||
b.call(fclose, [fp])
|
||
return main_func
|
||
|
||
# ========== 调用点热点统计(--hotspot-sites) ==========
|
||
|
||
def _NewIrBuilder(self, block: ir.Block) -> ir.IRBuilder:
|
||
"""创建 IR 构建器:仅在 --hotspot-sites 启用时挂钩源码位置。
|
||
|
||
未启用(hotspot_sites_file 为 None)时返回原生 ir.IRBuilder,行为与既有
|
||
完全一致(零影响,不挂任何属性);启用时返回 _HotspotSiteBuilder,仅在
|
||
每条新生成的 call 指令对象上追加 _src_file/_src_line 两个普通属性,供
|
||
调用点热点插桩读取源码位置。
|
||
"""
|
||
if self.hotspot_sites_file:
|
||
return _HotspotSiteBuilder(block, self)
|
||
return ir.IRBuilder(block)
|
||
|
||
@staticmethod
|
||
def _HotspotCalleeBase(name: str) -> str:
|
||
"""返回被调函数名的基名:去掉 16 位 sha1 模块前缀。
|
||
|
||
仅当形如 "<16位十六进制>.xxx" 时才剥离,避免误伤普通函数名;
|
||
例如 "23499a69276862b2.strcmp" -> "strcmp","strlen" -> "strlen"。
|
||
"""
|
||
if len(name) > 17 and name[16] == '.':
|
||
prefix: str = name[:16]
|
||
if all(ch in '0123456789abcdefABCDEF' for ch in prefix):
|
||
return name[17:]
|
||
return name
|
||
|
||
def _EmitHotspotSiteCounters(
|
||
self,
|
||
) -> tuple[list[ir.Function], list[tuple[ir.GlobalVariable, str, str, str, int]]]:
|
||
"""为匹配白名单的每个直接 call 调用点安装 internal i64 计数器并在 call 之前递增。
|
||
|
||
返回 (模块内函数定义列表, 记录列表);记录为
|
||
(计数器全局, 调用方函数名, 被调方基名, 源码文件名, 源码行号),
|
||
记录顺序即计数器编号 __fhs_site_<K> 的顺序。源码位置来自生成期挂在
|
||
call 指令对象上的 _src_file/_src_line(见 _HotspotSiteBuilder);生成期
|
||
无法关联到 AST 的合成调用点取不到位置,则文件名留空、行号记 0。
|
||
|
||
插桩规则:
|
||
- 白名单:hotspot_sites_all=True 时不过滤(全部直接调用点);否则仅对
|
||
"被调名去 16 位 sha1 前缀后的基名" 命中白名单的调用点插桩。
|
||
- 跳过间接调用(callee 不是具名 ir.Function):无法归属被调方,故不插桩。
|
||
- 跳过 __fhs_ 前缀的辅助函数自身,避免在插桩生成的代码里计数
|
||
(本功能稍后生成的处理器/构造器/计时辅助亦在此列)。
|
||
- 只在 call 之前插入 load/add/store,不改变返回值/参数/控制流。
|
||
- 计数器一律 internal,跨模块(独立翻译单元)链接不产生重复符号,初值 0。
|
||
"""
|
||
i64: ir.IntType = ir.IntType(64)
|
||
if self.hotspot_sites_all:
|
||
whitelist: set[str] | None = None
|
||
else:
|
||
names: list[str] = (
|
||
list(self.hotspot_callees) if self.hotspot_callees is not None
|
||
else list(DEFAULT_HOTSPOT_CALLEES)
|
||
)
|
||
whitelist = {self._HotspotCalleeBase(n) for n in names}
|
||
defs: list[ir.Function] = [f for f in self.module.functions if getattr(f, 'blocks', None)]
|
||
records: list[tuple[ir.GlobalVariable, str, str, str, int]] = []
|
||
for func in defs:
|
||
if func.name.startswith('__fhs_'):
|
||
continue
|
||
for block in list(func.blocks):
|
||
for instr in list(block.instructions):
|
||
if not isinstance(instr, ir.CallInstr):
|
||
continue
|
||
callee: Any = instr.callee
|
||
if not isinstance(callee, ir.Function) or not callee.name:
|
||
continue
|
||
base: str = self._HotspotCalleeBase(callee.name)
|
||
if whitelist is not None and base not in whitelist:
|
||
continue
|
||
idx: int = len(records)
|
||
counter: ir.GlobalVariable = ir.GlobalVariable(
|
||
self.module, i64, name=self._UniqueGlobalName(f'__fhs_site_{idx}'))
|
||
counter.linkage = 'internal'
|
||
counter.initializer = ir.Constant(i64, 0)
|
||
# 在 call 之前递增计数:load -> add 1 -> store(不改变 call 本身)
|
||
b: ir.IRBuilder = ir.IRBuilder(block)
|
||
b.position_before(instr)
|
||
cur: ir.Value = b.load(counter, name=f'fhs_site_load_{idx}')
|
||
inc: ir.Value = b.add(cur, ir.Constant(i64, 1), name=f'fhs_site_inc_{idx}')
|
||
b.store(inc, counter)
|
||
# 源码位置:生成期 _HotspotSiteBuilder 挂在 call 指令对象上;缺失则留空
|
||
src_file: str = getattr(instr, '_src_file', None) or ''
|
||
src_line: int = getattr(instr, '_src_line', 0) or 0
|
||
records.append((counter, func.name, base, src_file, src_line))
|
||
return defs, records
|
||
|
||
@staticmethod
|
||
def _HotspotTimePath(sites_path: str) -> str:
|
||
"""由调用点计数文件路径推导每函数计时输出文件路径。
|
||
|
||
取同目录下的固定名 hotspot_time.txt(不覆盖、不解析既有 hotspot_sites.txt
|
||
格式),便于下游分别解析两类结果。
|
||
"""
|
||
return os.path.join(os.path.dirname(sites_path), 'hotspot_time.txt')
|
||
|
||
# 深度栈容量:512。超出该深度的调用不记账(self/inc 均不计),但仍在
|
||
# __fhs_ftime_enter_<N> 里计入进入次数——溢出是"拿不到数值",明确跳过而非伪造。
|
||
_HotspotDepth: int = 512
|
||
|
||
def _EmitReadTimestamp(self, b: ir.IRBuilder, name: str = '') -> ir.Value:
|
||
"""发射一次**串行化**时间戳读取,返回 i64 tick(入口与每个 ret 前共用)。
|
||
|
||
串行化方案:在 x86_64 目标上先取一次内存屏障 @llvm.x86.sse2.lfence 再读
|
||
@llvm.readcyclecounter()(该 intrinsic 在 x86_64 后端降为 rdtsc)。理由:
|
||
- lfence 在 Intel/AMD 上对取指与访存有序列化效果,能阻止 rdtsc 与前后访存
|
||
乱序(裸 rdtsc 只保证自身 edx:eax 原子,不保证与其它指令的顺序),这是
|
||
"lfence; rdtsc" 这一业界通行的序列化读法;
|
||
- 用 LLVM intrinsic 组合而非 ir.InlineAsm:无内联汇编约束串的转义/渲染风险,
|
||
也不依赖 Clang 风格 asm 约束是否被 llvmlite 正确渲染,可移植性更好。
|
||
非 x86_64 目标(如 aarch64/arm/i386)降级为**裸** @llvm.readcyclecounter():
|
||
lfence 是 x86(SSE2) 专有 intrinsic,在非 x86 目标上不存在、编译期即不可用,
|
||
故此处按目标三元组在**编译期**决定是否发射 lfence(不在运行期静默退化)。
|
||
"""
|
||
i64: ir.IntType = ir.IntType(64)
|
||
if bool(self._platform_info.get('is_x86_64')):
|
||
lfence: ir.Function = self._DeclareExternalFunc(
|
||
'llvm.x86.sse2.lfence', ir.FunctionType(ir.VoidType(), []))
|
||
b.call(lfence, [])
|
||
rcc: ir.Function = self._DeclareExternalFunc(
|
||
'llvm.readcyclecounter', ir.FunctionType(i64, []))
|
||
return b.call(rcc, [], name=name)
|
||
|
||
def _EmitHotspotTimeCounters(
|
||
self,
|
||
) -> list[tuple[ir.GlobalVariable, ir.GlobalVariable, ir.GlobalVariable, str]]:
|
||
"""为模块内每个函数定义安装"自身耗时(self) + 包含式耗时(inc) + 进入次数"计时器。
|
||
|
||
计时原语(串行化读):见 _EmitReadTimestamp(x86_64 上 lfence; rdtsc,
|
||
其它目标降级为裸 rdtsc)。无 libc/Win32 依赖、无需额外链接;精度为 1 个
|
||
tick(x86_64 上即一个 CPU 周期,~0.3ns 量级),故耗时单位记为 tick。
|
||
|
||
exclusive(自身耗时)算法——深度栈:
|
||
- 全局 common i64 __fhs_tdepth:当前插桩深度,初值 0;common linkage 让每个
|
||
翻译单元各自发射同名 tentative 定义、由链接器合并为**全程序唯一一份**,
|
||
故跨模块调用也共用同一张深度栈(独立翻译单元链接不产生重复符号)。
|
||
- 全局 common [DEPTH x i64] __fhs_tstart / __fhs_tchild(零初始化,
|
||
DEPTH = _HotspotDepth = 512):按深度索引的"本帧起始 tick"与"本帧内被
|
||
插桩子调用 inclusive 之和";另有一个 internal i64 抖动槽 __fhs_tscratch,
|
||
用于把"不应记账"的写入导向一个永不读取的位置(等价于不写,但不分支)。
|
||
- 入口(每个被插桩函数 F,索引 N):
|
||
t0 = 串行化读时间戳()
|
||
d = load __fhs_tdepth
|
||
ok = d < DEPTH
|
||
__fhs_tstart[ok ? d : 0] = t0 (溢出时写抖动槽)
|
||
__fhs_tchild[ok ? d : 0] = 0 (溢出时写抖动槽)
|
||
__fhs_tdepth = ok ? d + 1 : d (溢出时不推进深度)
|
||
slot = ok ? d : -1 存进该次调用的 alloca(每调用一个独立栈槽)
|
||
__fhs_ftime_enter_N += 1 (溢出也计进入次数)
|
||
说明:slot 是"本帧独占的深度索引",递归/重入时各帧各占一层,天然正确;
|
||
因为本帧在其返回前独享 slot,故 ret 时用 __fhs_tstart[slot] 即本帧 t0
|
||
(无需再单独保存 t0 到 alloca,避免出现无人读取的死存储)。
|
||
- 每个 ret 之前(slot = load):
|
||
inok = slot >= 0;idx = inok ? slot : 0
|
||
t1 = 串行化读时间戳()
|
||
inc = t1 - __fhs_tstart[idx] (本次 inclusive)
|
||
self = inc - __fhs_tchild[idx] (减去本次内被插桩子调用的 inc 之和)
|
||
__fhs_fself_N += inok ? self : 0 (新增:自身耗时)
|
||
__fhs_finc_N += inok ? inc : 0 (保留:包含式耗时)
|
||
__fhs_tdepth = inok ? slot : load __fhs_tdepth (回到本帧深度)
|
||
if slot > 0(含用抖动槽等价实现): __fhs_tchild[slot-1] += inc
|
||
(把本次 inc 记到父帧的 child 累加器)
|
||
溢出帧(slot=-1)不记账、也不写入父帧 child;其墙钟时间仍落在父帧的
|
||
inc 里,因而会计入父帧的 **self**(见下方说明)。
|
||
|
||
返回记录列表 (自身耗时全局, 包含式耗时全局, 进入次数全局, 函数名),顺序即
|
||
N 的编号顺序。落盘由 _CreateHotspotTimeDump 完成(按 self 降序)。
|
||
|
||
固有性质(务必据此解读数值):**未插桩的被调方**(间接调用、无定义体的外部
|
||
函数如 strcmp/memcpy、名字以 __fhs_ 开头的辅助函数、深度溢出而跳过的调用)
|
||
其耗时不会被本方法从调用者 self 中扣除,一律计入**调用者自身 self**。故 self
|
||
是"该函数自身指令 + 未插桩子调用"的墙钟时间,只是比 inc 更贴近自身成本。
|
||
深度栈为 common(全程序一份)后,跨模块被调方只要被插桩即能正确扣除;修复前
|
||
各模块各持一份 internal 深度栈,跨模块子调用的 inc 记不进调用方 tchild,
|
||
表现为调用方 self ≈ inc 的假象(见 common linkage 说明)。
|
||
|
||
插桩规则与跳过条件:
|
||
- 只处理有基本块(即已定义)且名字不以 __fhs_ 开头的函数;每函数计数器一律
|
||
internal(同名索引跨模块并存,不可合并);深度栈用 common 由链接器合并为
|
||
全程序唯一一份,初值 0。
|
||
- 入口代码置于入口块最前(无终结指令,不改变 CFG);条件写入用 select 实现,
|
||
不新增基本块、不修改任何既有分支/终结指令。
|
||
- 只在该函数的 ret 终结指令之前插桩;以 unreachable 等其它终结指令结尾的
|
||
块(如 noreturn 调用后的代码)没有"返回点",明确跳过、不伪造数值(数量见
|
||
报告,如需可另行统计)。
|
||
- 累加只做 load/sub/add/store/select 与读取计时原语,不改变返回值/参数/控制流。
|
||
"""
|
||
depth: int = self._HotspotDepth
|
||
i32: ir.IntType = ir.IntType(32)
|
||
i64: ir.IntType = ir.IntType(64)
|
||
c0_32: ir.Constant = ir.Constant(i32, 0)
|
||
c0_64: ir.Constant = ir.Constant(i64, 0)
|
||
c1_64: ir.Constant = ir.Constant(i64, 1)
|
||
cneg1_64: ir.Constant = ir.Constant(i64, -1)
|
||
# 深度栈三件套用 common linkage:每模块各自发射一份同名 tentative 定义,
|
||
# 由链接器合并成"全程序唯一一份"(与 HandlesAssign 中多模块同名全局同法,
|
||
# 避免 ld.lld duplicate symbol)。跨模块调用链必须共用同一张深度栈,否则
|
||
# 被调方在自模块栈里 slot=0(或 slot 落在错误父帧)→ 其 inc 永远记不到
|
||
# 调用方 __fhs_tchild[slot-1] → 调用方 self ≈ inc(子时间一分不扣)。
|
||
# 抖动槽 __fhs_tscratch 只写不读,留 internal 即可(各模块独立无影响)。
|
||
depth_gv: ir.GlobalVariable = ir.GlobalVariable(
|
||
self.module, i64, name=self._UniqueGlobalName('__fhs_tdepth'))
|
||
depth_gv.linkage = 'common'
|
||
depth_gv.initializer = c0_64
|
||
arr_ty: ir.ArrayType = ir.ArrayType(i64, depth)
|
||
tstart_gv: ir.GlobalVariable = ir.GlobalVariable(
|
||
self.module, arr_ty, name=self._UniqueGlobalName('__fhs_tstart'))
|
||
tstart_gv.linkage = 'common'
|
||
tstart_gv.initializer = ir.Constant(arr_ty, None)
|
||
tchild_gv: ir.GlobalVariable = ir.GlobalVariable(
|
||
self.module, arr_ty, name=self._UniqueGlobalName('__fhs_tchild'))
|
||
tchild_gv.linkage = 'common'
|
||
tchild_gv.initializer = ir.Constant(arr_ty, None)
|
||
scratch_gv: ir.GlobalVariable = ir.GlobalVariable(
|
||
self.module, i64, name=self._UniqueGlobalName('__fhs_tscratch'))
|
||
scratch_gv.linkage = 'internal'
|
||
scratch_gv.initializer = c0_64
|
||
records: list[tuple[ir.GlobalVariable, ir.GlobalVariable, ir.GlobalVariable, str]] = []
|
||
for func in [f for f in self.module.functions if getattr(f, 'blocks', None)]:
|
||
if func.name.startswith('__fhs_'):
|
||
continue
|
||
idx: int = len(records)
|
||
self_gv: ir.GlobalVariable = ir.GlobalVariable(
|
||
self.module, i64, name=self._UniqueGlobalName(f'__fhs_fself_{idx}'))
|
||
self_gv.linkage = 'internal'
|
||
self_gv.initializer = c0_64
|
||
inc_gv: ir.GlobalVariable = ir.GlobalVariable(
|
||
self.module, i64, name=self._UniqueGlobalName(f'__fhs_finc_{idx}'))
|
||
inc_gv.linkage = 'internal'
|
||
inc_gv.initializer = c0_64
|
||
enter_gv: ir.GlobalVariable = ir.GlobalVariable(
|
||
self.module, i64, name=self._UniqueGlobalName(f'__fhs_ftime_enter_{idx}'))
|
||
enter_gv.linkage = 'internal'
|
||
enter_gv.initializer = c0_64
|
||
# 入口:取 t0 → 压深度栈 → 记录本帧 slot → 进入次数 +1(置于入口块最前)
|
||
entry: ir.Block = func.entry_basic_block
|
||
eb: ir.IRBuilder = ir.IRBuilder(entry)
|
||
eb.position_at_start(entry)
|
||
slot_ptr: ir.Value = eb.alloca(i64, name=f'fhs_t_slot_{idx}')
|
||
t0: ir.Value = self._EmitReadTimestamp(eb, name=f'fhs_t_t0_{idx}')
|
||
d: ir.Value = eb.load(depth_gv, name=f'fhs_t_depth_{idx}')
|
||
ok: ir.Value = eb.icmp_unsigned('<', d, ir.Constant(i64, depth), name=f'fhs_t_ok_{idx}')
|
||
safe_idx: ir.Value = eb.select(ok, d, c0_64, name=f'fhs_t_idx_{idx}')
|
||
eb.store(t0, eb.select(
|
||
ok, eb.gep(tstart_gv, [c0_32, safe_idx]), scratch_gv))
|
||
eb.store(c0_64, eb.select(
|
||
ok, eb.gep(tchild_gv, [c0_32, safe_idx]), scratch_gv))
|
||
eb.store(eb.select(ok, eb.add(d, c1_64), d), depth_gv)
|
||
eb.store(eb.select(ok, d, cneg1_64), slot_ptr)
|
||
eb.store(eb.add(eb.load(enter_gv), c1_64), enter_gv)
|
||
# 每个 ret 之前:算 self/inc,回退深度,累加父帧 child
|
||
for block in func.blocks:
|
||
term: Any = block.terminator
|
||
if not isinstance(term, ir.Ret):
|
||
continue
|
||
rb: ir.IRBuilder = ir.IRBuilder(block)
|
||
rb.position_before(term)
|
||
slot: ir.Value = rb.load(slot_ptr, name=f'fhs_t_slotv_{idx}')
|
||
inok: ir.Value = rb.icmp_signed('>=', slot, c0_64, name=f'fhs_t_inok_{idx}')
|
||
t1: ir.Value = self._EmitReadTimestamp(rb, name=f'fhs_t_t1_{idx}')
|
||
safe_slot: ir.Value = rb.select(inok, slot, c0_64, name=f'fhs_t_slotidx_{idx}')
|
||
inc_v: ir.Value = rb.sub(
|
||
t1, rb.load(rb.gep(tstart_gv, [c0_32, safe_slot])),
|
||
name=f'fhs_t_inc_{idx}')
|
||
child_v: ir.Value = rb.load(
|
||
rb.gep(tchild_gv, [c0_32, safe_slot]), name=f'fhs_t_child_{idx}')
|
||
self_v: ir.Value = rb.sub(inc_v, child_v, name=f'fhs_t_self_{idx}')
|
||
rb.store(rb.add(rb.load(self_gv), rb.select(inok, self_v, c0_64)), self_gv)
|
||
rb.store(rb.add(rb.load(inc_gv), rb.select(inok, inc_v, c0_64)), inc_gv)
|
||
rb.store(rb.select(inok, slot, rb.load(depth_gv)), depth_gv)
|
||
pok: ir.Value = rb.icmp_signed('>', slot, c0_64, name=f'fhs_t_pok_{idx}')
|
||
p_idx: ir.Value = rb.select(pok, rb.sub(slot, c1_64), c0_64)
|
||
p_ptr: ir.Value = rb.gep(tchild_gv, [c0_32, p_idx])
|
||
p_target: ir.Value = rb.select(pok, p_ptr, scratch_gv)
|
||
rb.store(rb.add(rb.load(p_target), inc_v), p_target)
|
||
records.append((self_gv, inc_gv, enter_gv, func.name))
|
||
return records
|
||
|
||
def _CreateHotspotSitesDump(
|
||
self,
|
||
path: str,
|
||
records: list[tuple[ir.GlobalVariable, str, str, str, int]],
|
||
module_id: str | None = None,
|
||
) -> ir.Function:
|
||
"""生成 void() 落盘函数:按调用次数降序把各调用点追加写入 <path>。
|
||
|
||
落盘格式(追加):
|
||
# sites <module_sha1>\\n
|
||
<module_sha1> <caller_func> -> <callee_base> <count> <file>:<line>\\n
|
||
module_id 为空时省略行首 sha1 与头部 sha1(留空,不伪造)。
|
||
|
||
- 排序:落盘前用选择排序(两层运行期循环,IR 规模 O(N)、运行期 O(N^2) 仅
|
||
在落盘时触发一次)按计数降序输出,只输出 count>0 的调用点;计数相同时
|
||
按计数器编号 K 升序(稳定、可复现)。
|
||
- 源码位置:作为可选尾字段,格式 "<file>:<line>"(含前置单个空格);该位置
|
||
来自生成期挂在 call 指令对象上的 _src_file/_src_line(见 _HotspotSiteBuilder)。
|
||
生成期无法关联到 AST 的合成调用点取不到位置,则此字段整体省略(不伪造、不填默认值)。
|
||
- 函数签名为 void(),由 __fhs_dump_all(见 _CreateHotspotDumpAll)统一带守卫
|
||
调用,本函数自身不检查守卫。用 CRT fopen("a")/fprintf/fclose 落盘(沿用既有做法)。
|
||
"""
|
||
n: int = len(records)
|
||
i8: ir.IntType = ir.IntType(8)
|
||
i32: ir.IntType = ir.IntType(32)
|
||
i64: ir.IntType = ir.IntType(64)
|
||
i8p: ir.PointerType = ir.PointerType(i8)
|
||
i64p: ir.PointerType = ir.PointerType(i64)
|
||
c0_8: ir.Constant = ir.Constant(i8, 0)
|
||
c1_8: ir.Constant = ir.Constant(i8, 1)
|
||
c0_32: ir.Constant = ir.Constant(i32, 0)
|
||
c0_64: ir.Constant = ir.Constant(i64, 0)
|
||
c1_64: ir.Constant = ir.Constant(i64, 1)
|
||
fopen: ir.Function = self._DeclareExternalFunc('fopen', ir.FunctionType(i8p, [i8p, i8p]))
|
||
fprintf: ir.Function = self._DeclareExternalFunc(
|
||
'fprintf', ir.FunctionType(i32, [i8p, i8p], var_arg=True))
|
||
fclose: ir.Function = self._DeclareExternalFunc('fclose', ir.FunctionType(i32, [i8p]))
|
||
dump_fn: ir.Function = ir.Function(
|
||
self.module, ir.FunctionType(ir.VoidType(), []),
|
||
name=self._UniqueGlobalName('__fhs_dump_sites'))
|
||
dump_fn.linkage = 'internal'
|
||
b: ir.IRBuilder = ir.IRBuilder(dump_fn.append_basic_block(name='entry'))
|
||
b.position_at_start(dump_fn.entry_basic_block)
|
||
# 无匹配调用点:生成空落盘函数(不落盘、不建数组),保持调用链一致
|
||
if n == 0:
|
||
b.ret_void()
|
||
return dump_fn
|
||
path_ptr: ir.Constant = self._create_string_global(
|
||
path, name=self._UniqueGlobalName('__fhs_sites_path'))
|
||
mode_ptr: ir.Constant = self._create_string_global(
|
||
'a', name=self._UniqueGlobalName('__fhs_sites_mode'))
|
||
mid_ptr: ir.Constant | None = None
|
||
hdr_fmt_ptr: ir.Constant | None = None
|
||
if module_id:
|
||
fmt_ptr: ir.Constant = self._create_string_global(
|
||
'%s %s -> %s %llu%s\n', name=self._UniqueGlobalName('__fhs_sites_fmt'))
|
||
hdr_fmt_ptr = self._create_string_global(
|
||
'# sites %s\n', name=self._UniqueGlobalName('__fhs_sites_hdr_fmt'))
|
||
mid_ptr = self._create_string_global(
|
||
module_id, name=self._UniqueGlobalName('__fhs_sites_module_id'))
|
||
else:
|
||
fmt_ptr = self._create_string_global(
|
||
'%s -> %s %llu%s\n', name=self._UniqueGlobalName('__fhs_sites_fmt'))
|
||
fp: ir.Value = b.call(fopen, [path_ptr, mode_ptr], name='fhs_sites_fp')
|
||
if module_id and hdr_fmt_ptr is not None:
|
||
b.call(fprintf, [fp, hdr_fmt_ptr, mid_ptr], name='fhs_sites_write_hdr')
|
||
# 归属元数据全局数组:运行期按索引取 "调用方名 / 被调方基名 / 计数器地址"
|
||
caller_ty: ir.ArrayType = ir.ArrayType(i8p, n)
|
||
callee_ty: ir.ArrayType = ir.ArrayType(i8p, n)
|
||
cntptr_ty: ir.ArrayType = ir.ArrayType(i64p, n)
|
||
caller_gv: ir.GlobalVariable = ir.GlobalVariable(
|
||
self.module, caller_ty, name=self._UniqueGlobalName('__fhs_sites_caller'))
|
||
caller_gv.linkage = 'internal'
|
||
caller_gv.global_constant = True
|
||
caller_gv.initializer = ir.Constant(caller_ty, [
|
||
self._create_string_global(rec[1], name=self._UniqueGlobalName(f'__fhs_sites_caller_{k}'))
|
||
for k, rec in enumerate(records)
|
||
])
|
||
callee_gv: ir.GlobalVariable = ir.GlobalVariable(
|
||
self.module, callee_ty, name=self._UniqueGlobalName('__fhs_sites_callee'))
|
||
callee_gv.linkage = 'internal'
|
||
callee_gv.global_constant = True
|
||
callee_gv.initializer = ir.Constant(callee_ty, [
|
||
self._create_string_global(rec[2], name=self._UniqueGlobalName(f'__fhs_sites_callee_{k}'))
|
||
for k, rec in enumerate(records)
|
||
])
|
||
# 源码位置全局数组:每项形如 " <file>:<line>"(含前置空格);取不到位置则为空串
|
||
loc_ty: ir.ArrayType = ir.ArrayType(i8p, n)
|
||
loc_gv: ir.GlobalVariable = ir.GlobalVariable(
|
||
self.module, loc_ty, name=self._UniqueGlobalName('__fhs_sites_loc'))
|
||
loc_gv.linkage = 'internal'
|
||
loc_gv.global_constant = True
|
||
loc_gv.initializer = ir.Constant(loc_ty, [
|
||
self._create_string_global(
|
||
f' {rec[3]}:{rec[4]}' if (rec[3] and rec[4] > 0) else '',
|
||
name=self._UniqueGlobalName(f'__fhs_sites_loc_{k}'))
|
||
for k, rec in enumerate(records)
|
||
])
|
||
cnt_gv: ir.GlobalVariable = ir.GlobalVariable(
|
||
self.module, cntptr_ty, name=self._UniqueGlobalName('__fhs_sites_cnt_ptrs'))
|
||
cnt_gv.linkage = 'internal'
|
||
cnt_gv.global_constant = True
|
||
cnt_gv.initializer = ir.Constant(cntptr_ty, [
|
||
ir.Constant(i64p, rec[0].get_reference()) for rec in records
|
||
])
|
||
# 全部 alloca 置于 entry 块:避免在循环体内 alloca 导致每次迭代爆栈
|
||
used: ir.Value = b.alloca(ir.ArrayType(i8, n), name='fhs_sites_used')
|
||
for i in range(n):
|
||
b.store(c0_8, b.gep(used, [c0_32, ir.Constant(i32, i)]))
|
||
k_ptr: ir.Value = b.alloca(i64, name='fhs_sites_k')
|
||
best_ptr: ir.Value = b.alloca(i64, name='fhs_sites_best')
|
||
bestv_ptr: ir.Value = b.alloca(i64, name='fhs_sites_bestv')
|
||
i_ptr: ir.Value = b.alloca(i64, name='fhs_sites_i')
|
||
b.store(c0_64, k_ptr)
|
||
# 选择排序:外层 k 走 0..n-1,每轮在未输出项里选计数最大者(计数相同取 K 小者)
|
||
loop_k: ir.Block = dump_fn.append_basic_block(name='fhs_sites_sort_k')
|
||
body_k: ir.Block = dump_fn.append_basic_block(name='fhs_sites_sort_k_body')
|
||
loop_i: ir.Block = dump_fn.append_basic_block(name='fhs_sites_sort_i')
|
||
body_i: ir.Block = dump_fn.append_basic_block(name='fhs_sites_sort_i_body')
|
||
scan_i: ir.Block = dump_fn.append_basic_block(name='fhs_sites_sort_i_scan')
|
||
upd_i: ir.Block = dump_fn.append_basic_block(name='fhs_sites_sort_i_upd')
|
||
next_i: ir.Block = dump_fn.append_basic_block(name='fhs_sites_sort_i_next')
|
||
end_i: ir.Block = dump_fn.append_basic_block(name='fhs_sites_sort_i_end')
|
||
print_k: ir.Block = dump_fn.append_basic_block(name='fhs_sites_print_k')
|
||
after_k: ir.Block = dump_fn.append_basic_block(name='fhs_sites_sort_end')
|
||
b.branch(loop_k)
|
||
# loop_k: if k < n -> body_k else after_k
|
||
b.position_at_end(loop_k)
|
||
k_val: ir.Value = b.load(k_ptr)
|
||
b.cbranch(b.icmp_signed('<', k_val, ir.Constant(i64, n)), body_k, after_k)
|
||
# body_k: best=-1, bestv=0, i=0; -> loop_i
|
||
b.position_at_end(body_k)
|
||
b.store(ir.Constant(i64, -1), best_ptr)
|
||
b.store(c0_64, bestv_ptr)
|
||
b.store(c0_64, i_ptr)
|
||
b.branch(loop_i)
|
||
# loop_i: if i < n -> body_i else end_i
|
||
b.position_at_end(loop_i)
|
||
i_val: ir.Value = b.load(i_ptr)
|
||
b.cbranch(b.icmp_signed('<', i_val, ir.Constant(i64, n)), body_i, end_i)
|
||
# body_i: if used[i]==0 -> scan_i else next_i
|
||
b.position_at_end(body_i)
|
||
used_i: ir.Value = b.load(b.gep(used, [c0_32, i_val]))
|
||
b.cbranch(b.icmp_signed('==', used_i, c0_8), scan_i, next_i)
|
||
# scan_i: v = *cnt_ptrs[i]; if v > bestv -> upd_i else next_i
|
||
b.position_at_end(scan_i)
|
||
cnt_p: ir.Value = b.load(b.gep(cnt_gv, [c0_32, i_val]))
|
||
v_val: ir.Value = b.load(cnt_p)
|
||
b.cbranch(b.icmp_signed('>', v_val, b.load(bestv_ptr)), upd_i, next_i)
|
||
# upd_i: bestv=v, best=i; -> next_i
|
||
b.position_at_end(upd_i)
|
||
b.store(v_val, bestv_ptr)
|
||
b.store(i_val, best_ptr)
|
||
b.branch(next_i)
|
||
# next_i: i++; -> loop_i
|
||
b.position_at_end(next_i)
|
||
b.store(b.add(b.load(i_ptr), c1_64), i_ptr)
|
||
b.branch(loop_i)
|
||
# end_i: best<0(剩余全为 0 计数)-> after_k;否则 print_k
|
||
b.position_at_end(end_i)
|
||
best_val: ir.Value = b.load(best_ptr)
|
||
b.cbranch(b.icmp_signed('<', best_val, c0_64), after_k, print_k)
|
||
# print_k: used[best]=1; fprintf(...); k++; -> loop_k
|
||
b.position_at_end(print_k)
|
||
idx: ir.Value = b.load(best_ptr)
|
||
b.store(c1_8, b.gep(used, [c0_32, idx]))
|
||
caller_ptr: ir.Value = b.load(b.gep(caller_gv, [c0_32, idx]))
|
||
callee_ptr: ir.Value = b.load(b.gep(callee_gv, [c0_32, idx]))
|
||
loc_ptr: ir.Value = b.load(b.gep(loc_gv, [c0_32, idx]))
|
||
if module_id and mid_ptr is not None:
|
||
b.call(fprintf, [fp, fmt_ptr, mid_ptr, caller_ptr, callee_ptr,
|
||
b.load(bestv_ptr), loc_ptr],
|
||
name='fhs_sites_write')
|
||
else:
|
||
b.call(fprintf, [fp, fmt_ptr, caller_ptr, callee_ptr,
|
||
b.load(bestv_ptr), loc_ptr],
|
||
name='fhs_sites_write')
|
||
b.store(b.add(b.load(k_ptr), c1_64), k_ptr)
|
||
b.branch(loop_k)
|
||
# after_k: fclose; ret_void
|
||
b.position_at_end(after_k)
|
||
b.call(fclose, [fp])
|
||
b.ret_void()
|
||
return dump_fn
|
||
|
||
def _CreateHotspotTimeDump(
|
||
self,
|
||
path: str,
|
||
records: list[tuple[ir.GlobalVariable, ir.GlobalVariable, ir.GlobalVariable, str]],
|
||
module_id: str | None = None,
|
||
) -> ir.Function:
|
||
"""生成 void() 落盘函数:按**自身耗时**降序写入每函数计时文件。
|
||
|
||
落盘格式(追加):
|
||
# time <module_sha1>\\n
|
||
<func_name> <self_tick> <inc_tick> <enter_count>\\n
|
||
module_id 为空时省略头部 sha1 行(留空,不伪造)。列含义(单位均为 tick):
|
||
- self_tick:自身耗时(exclusive,见 _EmitHotspotTimeCounters),已扣除本
|
||
函数内**被插桩**子调用的 inclusive 之和;但**未插桩**的被调方(间接调用、
|
||
无定义体的外部函数如 strcmp/memcpy、深度溢出跳过的调用)仍计入其中。
|
||
- inc_tick:包含式耗时(inclusive,含全部被调方)。
|
||
- enter_count:被进入次数(含递归/重入),用于交叉校验。
|
||
|
||
- 排序:与调用点计数同法,落盘前用选择排序按 self_tick 降序输出,只输出
|
||
进入次数 >0 的函数;self_tick 相同时按编号 N 升序(稳定、可复现)。
|
||
- 函数签名为 void(),由 __fhs_dump_all(见 _CreateHotspotDumpAll)统一带守卫
|
||
调用,本函数自身不检查守卫。用 CRT fopen("a")/fprintf/fclose 落盘(沿用既有做法)。
|
||
"""
|
||
n: int = len(records)
|
||
i8: ir.IntType = ir.IntType(8)
|
||
i32: ir.IntType = ir.IntType(32)
|
||
i64: ir.IntType = ir.IntType(64)
|
||
i8p: ir.PointerType = ir.PointerType(i8)
|
||
i64p: ir.PointerType = ir.PointerType(i64)
|
||
c0_8: ir.Constant = ir.Constant(i8, 0)
|
||
c1_8: ir.Constant = ir.Constant(i8, 1)
|
||
c0_32: ir.Constant = ir.Constant(i32, 0)
|
||
c0_64: ir.Constant = ir.Constant(i64, 0)
|
||
c1_64: ir.Constant = ir.Constant(i64, 1)
|
||
fopen: ir.Function = self._DeclareExternalFunc('fopen', ir.FunctionType(i8p, [i8p, i8p]))
|
||
fprintf: ir.Function = self._DeclareExternalFunc(
|
||
'fprintf', ir.FunctionType(i32, [i8p, i8p], var_arg=True))
|
||
fclose: ir.Function = self._DeclareExternalFunc('fclose', ir.FunctionType(i32, [i8p]))
|
||
dump_fn: ir.Function = ir.Function(
|
||
self.module, ir.FunctionType(ir.VoidType(), []),
|
||
name=self._UniqueGlobalName('__fhs_dump_time'))
|
||
dump_fn.linkage = 'internal'
|
||
b: ir.IRBuilder = ir.IRBuilder(dump_fn.append_basic_block(name='entry'))
|
||
b.position_at_start(dump_fn.entry_basic_block)
|
||
# 无计时对象(模块内无用户函数定义):生成空落盘函数占位,保持调用链一致
|
||
if n == 0:
|
||
b.ret_void()
|
||
return dump_fn
|
||
path_ptr: ir.Constant = self._create_string_global(
|
||
path, name=self._UniqueGlobalName('__fhs_ftime_path'))
|
||
mode_ptr: ir.Constant = self._create_string_global(
|
||
'a', name=self._UniqueGlobalName('__fhs_ftime_mode'))
|
||
fmt_ptr: ir.Constant = self._create_string_global(
|
||
'%s %llu %llu %llu\n', name=self._UniqueGlobalName('__fhs_ftime_fmt'))
|
||
fp: ir.Value = b.call(fopen, [path_ptr, mode_ptr], name='fhs_ftime_fp')
|
||
if module_id:
|
||
hdr_fmt_ptr: ir.Constant = self._create_string_global(
|
||
'# time %s name self inc enter (tick; self excludes instrumented callees)\n',
|
||
name=self._UniqueGlobalName('__fhs_ftime_hdr_fmt'))
|
||
mid_ptr: ir.Constant = self._create_string_global(
|
||
module_id, name=self._UniqueGlobalName('__fhs_ftime_module_id'))
|
||
b.call(fprintf, [fp, hdr_fmt_ptr, mid_ptr], name='fhs_ftime_write_hdr')
|
||
# 归属元数据全局数组:运行期按索引取 "函数名 / 自身耗时地址 / 包含式地址 / 进入次数地址"
|
||
name_ty: ir.ArrayType = ir.ArrayType(i8p, n)
|
||
selfptr_ty: ir.ArrayType = ir.ArrayType(i64p, n)
|
||
incptr_ty: ir.ArrayType = ir.ArrayType(i64p, n)
|
||
enterptr_ty: ir.ArrayType = ir.ArrayType(i64p, n)
|
||
name_gv: ir.GlobalVariable = ir.GlobalVariable(
|
||
self.module, name_ty, name=self._UniqueGlobalName('__fhs_ftime_name'))
|
||
name_gv.linkage = 'internal'
|
||
name_gv.global_constant = True
|
||
name_gv.initializer = ir.Constant(name_ty, [
|
||
self._create_string_global(rec[3], name=self._UniqueGlobalName(f'__fhs_ftime_name_{k}'))
|
||
for k, rec in enumerate(records)
|
||
])
|
||
self_gv: ir.GlobalVariable = ir.GlobalVariable(
|
||
self.module, selfptr_ty, name=self._UniqueGlobalName('__fhs_ftime_self_ptrs'))
|
||
self_gv.linkage = 'internal'
|
||
self_gv.global_constant = True
|
||
self_gv.initializer = ir.Constant(selfptr_ty, [
|
||
ir.Constant(i64p, rec[0].get_reference()) for rec in records
|
||
])
|
||
inc_gv: ir.GlobalVariable = ir.GlobalVariable(
|
||
self.module, incptr_ty, name=self._UniqueGlobalName('__fhs_ftime_inc_ptrs'))
|
||
inc_gv.linkage = 'internal'
|
||
inc_gv.global_constant = True
|
||
inc_gv.initializer = ir.Constant(incptr_ty, [
|
||
ir.Constant(i64p, rec[1].get_reference()) for rec in records
|
||
])
|
||
enter_gv: ir.GlobalVariable = ir.GlobalVariable(
|
||
self.module, enterptr_ty, name=self._UniqueGlobalName('__fhs_ftime_enter_ptrs'))
|
||
enter_gv.linkage = 'internal'
|
||
enter_gv.global_constant = True
|
||
enter_gv.initializer = ir.Constant(enterptr_ty, [
|
||
ir.Constant(i64p, rec[2].get_reference()) for rec in records
|
||
])
|
||
# 全部 alloca 置于 entry 块:避免在循环体内 alloca 导致每次迭代爆栈
|
||
used: ir.Value = b.alloca(ir.ArrayType(i8, n), name='fhs_ftime_used')
|
||
for i in range(n):
|
||
b.store(c0_8, b.gep(used, [c0_32, ir.Constant(i32, i)]))
|
||
k_ptr: ir.Value = b.alloca(i64, name='fhs_ftime_k')
|
||
best_ptr: ir.Value = b.alloca(i64, name='fhs_ftime_best')
|
||
bestv_ptr: ir.Value = b.alloca(i64, name='fhs_ftime_bestv')
|
||
i_ptr: ir.Value = b.alloca(i64, name='fhs_ftime_i')
|
||
b.store(c0_64, k_ptr)
|
||
# 选择排序:外层 k 走 0..n-1,每轮在未输出项里选自身耗时(self)最大者(相同取 N 小者)
|
||
loop_k: ir.Block = dump_fn.append_basic_block(name='fhs_ftime_sort_k')
|
||
body_k: ir.Block = dump_fn.append_basic_block(name='fhs_ftime_sort_k_body')
|
||
loop_i: ir.Block = dump_fn.append_basic_block(name='fhs_ftime_sort_i')
|
||
body_i: ir.Block = dump_fn.append_basic_block(name='fhs_ftime_sort_i_body')
|
||
scan_i: ir.Block = dump_fn.append_basic_block(name='fhs_ftime_sort_i_scan')
|
||
upd_i: ir.Block = dump_fn.append_basic_block(name='fhs_ftime_sort_i_upd')
|
||
next_i: ir.Block = dump_fn.append_basic_block(name='fhs_ftime_sort_i_next')
|
||
end_i: ir.Block = dump_fn.append_basic_block(name='fhs_ftime_sort_i_end')
|
||
print_k: ir.Block = dump_fn.append_basic_block(name='fhs_ftime_print_k')
|
||
after_k: ir.Block = dump_fn.append_basic_block(name='fhs_ftime_sort_end')
|
||
b.branch(loop_k)
|
||
b.position_at_end(loop_k)
|
||
k_val: ir.Value = b.load(k_ptr)
|
||
b.cbranch(b.icmp_signed('<', k_val, ir.Constant(i64, n)), body_k, after_k)
|
||
b.position_at_end(body_k)
|
||
b.store(ir.Constant(i64, -1), best_ptr)
|
||
b.store(c0_64, bestv_ptr)
|
||
b.store(c0_64, i_ptr)
|
||
b.branch(loop_i)
|
||
b.position_at_end(loop_i)
|
||
i_val: ir.Value = b.load(i_ptr)
|
||
b.cbranch(b.icmp_signed('<', i_val, ir.Constant(i64, n)), body_i, end_i)
|
||
b.position_at_end(body_i)
|
||
used_i: ir.Value = b.load(b.gep(used, [c0_32, i_val]))
|
||
b.cbranch(b.icmp_signed('==', used_i, c0_8), scan_i, next_i)
|
||
# scan_i: v = *self_ptrs[i](自身耗时);同时把进入次数非 0 作为"有样本"判据
|
||
b.position_at_end(scan_i)
|
||
self_p: ir.Value = b.load(b.gep(self_gv, [c0_32, i_val]))
|
||
v_val: ir.Value = b.load(self_p)
|
||
enter_p: ir.Value = b.load(b.gep(enter_gv, [c0_32, i_val]))
|
||
entered: ir.Value = b.load(enter_p)
|
||
has: ir.Value = b.icmp_signed('>', entered, c0_64)
|
||
better: ir.Value = b.icmp_signed('>', v_val, b.load(bestv_ptr))
|
||
b.cbranch(b.and_(has, better), upd_i, next_i)
|
||
b.position_at_end(upd_i)
|
||
b.store(v_val, bestv_ptr)
|
||
b.store(i_val, best_ptr)
|
||
b.branch(next_i)
|
||
b.position_at_end(next_i)
|
||
b.store(b.add(b.load(i_ptr), c1_64), i_ptr)
|
||
b.branch(loop_i)
|
||
# end_i: best<0(剩余均为 0 或未被进入)-> after_k;否则 print_k
|
||
b.position_at_end(end_i)
|
||
best_val: ir.Value = b.load(best_ptr)
|
||
b.cbranch(b.icmp_signed('<', best_val, c0_64), after_k, print_k)
|
||
b.position_at_end(print_k)
|
||
idx: ir.Value = b.load(best_ptr)
|
||
b.store(c1_8, b.gep(used, [c0_32, idx]))
|
||
name_ptr: ir.Value = b.load(b.gep(name_gv, [c0_32, idx]))
|
||
sp: ir.Value = b.load(b.gep(self_gv, [c0_32, idx]))
|
||
ip: ir.Value = b.load(b.gep(inc_gv, [c0_32, idx]))
|
||
ep: ir.Value = b.load(b.gep(enter_gv, [c0_32, idx]))
|
||
b.call(fprintf, [fp, fmt_ptr, name_ptr, b.load(sp), b.load(ip), b.load(ep)],
|
||
name='fhs_ftime_write')
|
||
b.store(b.add(b.load(k_ptr), c1_64), k_ptr)
|
||
b.branch(loop_k)
|
||
# after_k: fclose; ret_void
|
||
b.position_at_end(after_k)
|
||
b.call(fclose, [fp])
|
||
b.ret_void()
|
||
return dump_fn
|
||
|
||
def _CreateHotspotDumpAll(
|
||
self,
|
||
sites_dump: ir.Function,
|
||
time_dump: ir.Function,
|
||
) -> tuple[ir.Function, ir.Function]:
|
||
"""生成模块级"总落盘"链路:幂等守卫 + void() 总落盘函数 + Ctrl+C 处理器。
|
||
|
||
同一模块**只落盘一次**:Ctrl+C 与 atexit 可能先后都触发(例如处理器返回后
|
||
进程继续退出),故用每模块一份的 internal i32 @__fhs_dumped(初值 0)作幂等
|
||
守卫:
|
||
- void @__fhs_dump_all():先读守卫,非 0 直接返回;否则置 1 后依次调用
|
||
sites_dump(调用点计数)与 time_dump(每函数计时)——两个落盘函数本身
|
||
不检查守卫;
|
||
- i32 @__fhs_ctl_handler(i32):直接调用 __fhs_dump_all() 后返回 FALSE(0),
|
||
供 SetConsoleCtrlHandler 注册(保持"仍让进程终止"的既有行为)。
|
||
|
||
atexit 与 Ctrl+C 共用同一守卫,先到者落盘、后到者直接返回,避免重复段/行。
|
||
返回 (Ctrl+C 处理器, 总落盘函数)。
|
||
"""
|
||
i32: ir.IntType = ir.IntType(32)
|
||
guard: ir.GlobalVariable = ir.GlobalVariable(
|
||
self.module, i32, name=self._UniqueGlobalName('__fhs_dumped'))
|
||
guard.linkage = 'internal'
|
||
guard.initializer = ir.Constant(i32, 0)
|
||
dump_all: ir.Function = ir.Function(
|
||
self.module, ir.FunctionType(ir.VoidType(), []),
|
||
name=self._UniqueGlobalName('__fhs_dump_all'))
|
||
dump_all.linkage = 'internal'
|
||
b: ir.IRBuilder = ir.IRBuilder(dump_all.append_basic_block(name='entry'))
|
||
do_bb: ir.Block = dump_all.append_basic_block(name='fhs_dump_do')
|
||
done_bb: ir.Block = dump_all.append_basic_block(name='fhs_dump_done')
|
||
b.position_at_start(dump_all.entry_basic_block)
|
||
cur: ir.Value = b.load(guard, name='fhs_dumped_load')
|
||
b.cbranch(b.icmp_signed('!=', cur, ir.Constant(i32, 0)), done_bb, do_bb)
|
||
b.position_at_end(do_bb)
|
||
b.store(ir.Constant(i32, 1), guard)
|
||
b.call(sites_dump, [])
|
||
b.call(time_dump, [])
|
||
b.branch(done_bb)
|
||
b.position_at_end(done_bb)
|
||
b.ret_void()
|
||
ctl_handler: ir.Function = ir.Function(
|
||
self.module, ir.FunctionType(i32, [i32]),
|
||
name=self._UniqueGlobalName('__fhs_ctl_handler'))
|
||
ctl_handler.linkage = 'internal'
|
||
cb: ir.IRBuilder = ir.IRBuilder(ctl_handler.append_basic_block(name='entry'))
|
||
cb.call(dump_all, [])
|
||
cb.ret(ir.Constant(i32, 0))
|
||
return ctl_handler, dump_all
|
||
|
||
def _EmitHotspotDumpRegistration(
|
||
self,
|
||
builder: ir.IRBuilder,
|
||
ctl_handler: ir.Function,
|
||
dump_all: ir.Function,
|
||
) -> None:
|
||
"""向给定构建器发射两处落盘触发注册:
|
||
|
||
- SetConsoleCtrlHandler(bitcast @__fhs_ctl_handler, i8*), TRUE):Ctrl+C 路径;
|
||
- atexit(bitcast @__fhs_dump_all, i8*)):正常返回 / exit() 退出路径。
|
||
|
||
二者共用 __fhs_dumped 幂等守卫(见 _CreateHotspotDumpAll),任一先触发即落盘,
|
||
后触发者直接返回,不重复写段/行。atexit 声明沿用本文件既有外部函数写法(见
|
||
fopen/SetConsoleCtrlHandler:ir.FunctionType + ir.PointerType(ir.IntType(8)));
|
||
本仓库 IR 为 typed pointer(函数指针统一以 i8* 传递),故以 bitcast 取函数地址。
|
||
返回值为 int,此处有意忽略(拿不到 atexit 会在链接期报错,不静默降级)。
|
||
|
||
覆盖 libc exit()(含正常 return 与 sys.exit 的 fail-fast);不覆盖 _exit()/
|
||
abort()/TerminateProcess/崩溃信号等不经过 exit() 的终止路径。
|
||
"""
|
||
i32: ir.IntType = ir.IntType(32)
|
||
i8p: ir.PointerType = ir.PointerType(ir.IntType(8))
|
||
set_handler: ir.Function = self._DeclareExternalFunc(
|
||
'SetConsoleCtrlHandler', ir.FunctionType(i32, [i8p, i32]))
|
||
atexit_fn: ir.Function = self._DeclareExternalFunc(
|
||
'atexit', ir.FunctionType(i32, [i8p]))
|
||
builder.call(set_handler, [builder.bitcast(ctl_handler, i8p), ir.Constant(i32, 1)])
|
||
builder.call(atexit_fn, [builder.bitcast(dump_all, i8p)])
|
||
|
||
def _RegisterHotspotSitesViaCtor(self, ctl_handler: ir.Function, dump_all: ir.Function) -> None:
|
||
"""用 @llvm.global_ctors 在进程启动期为本模块注册热点落盘触发。
|
||
|
||
生成 internal void @__fhs_sites_ctor()(内部调用 SetConsoleCtrlHandler 注册
|
||
Ctrl+C 处理器 __fhs_ctl_handler,并调用 atexit 注册退出处理器 __fhs_dump_all),
|
||
并写入 appending linkage 的 @llvm.global_ctors —— 每个模块一份,链接器按
|
||
appending 语义把各模块的构造器拼接进 .init_array,进程启动时各执行一次。相比
|
||
"在 main 入口注册",本方案不依赖 main 所在模块,适配 Projectrans 的"每源文件
|
||
独立翻译单元"模型。
|
||
|
||
模块内若已存在 @llvm.global_ctors 视为冲突,显式报错(不静默降级)。
|
||
"""
|
||
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('__fhs_sites_ctor'))
|
||
ctor.linkage = 'internal'
|
||
cb: ir.IRBuilder = ir.IRBuilder(ctor.append_basic_block(name='entry'))
|
||
self._EmitHotspotDumpRegistration(cb, ctl_handler, dump_all)
|
||
cb.ret_void()
|
||
if 'llvm.global_ctors' in self.module.globals:
|
||
raise RuntimeError(
|
||
'--hotspot-sites: 模块内已存在 @llvm.global_ctors,无法注册热点处理器(不静默降级)')
|
||
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 对象):与 _create_string_global 同法,
|
||
# 否则 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 _RegisterTracebackInstallViaCtor(self) -> None:
|
||
"""用 @llvm.global_ctors 在进程启动期调用本模块的 Install()(--traceback)。
|
||
|
||
仅在"本翻译单元就是 includes/traceback.vp"时由 StructGen.finalize() 调用
|
||
(traceback_ctor 由 Phase2Translator._translate_file 按源文件名置位)。
|
||
生成 internal void @__traceback_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(
|
||
'--traceback: 在 traceback 模块内找不到 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('__traceback_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(
|
||
'--traceback: 模块内已存在 @llvm.global_ctors,'
|
||
'无法注入 traceback 启动期构造器(不静默降级)')
|
||
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 单模块路径。
|
||
|
||
顺序:先装调用点计数(其扫描只针对用户代码,不会把随后插入的计时原语
|
||
调用计入),再装每函数计时;随后在 main 入口用 "w" 各清空一次调用点计数
|
||
文件与计时文件,并注册落盘触发(Ctrl+C 处理器 SetConsoleCtrlHandler +
|
||
退出处理器 atexit,程序启动期各执行一次)。仅在 hotspot_sites_file 非 None
|
||
(用户传入 --hotspot-sites)时被调用;未启用时该方法不会执行,IR 输出与
|
||
既有行为一致(零影响)。
|
||
"""
|
||
path: str | None = self.hotspot_sites_file
|
||
if not path:
|
||
raise RuntimeError('--hotspot-sites: 需要提供调用点计数输出文件路径')
|
||
module_id: str | None = self.module_sha1 or None
|
||
defs, records = self._EmitHotspotSiteCounters()
|
||
time_records = self._EmitHotspotTimeCounters()
|
||
time_path: str = self._HotspotTimePath(path)
|
||
sites_dump: ir.Function = self._CreateHotspotSitesDump(
|
||
path, records, module_id=module_id)
|
||
time_dump: ir.Function = self._CreateHotspotTimeDump(
|
||
time_path, time_records, module_id=module_id)
|
||
ctl_handler, dump_all = self._CreateHotspotDumpAll(sites_dump, time_dump)
|
||
# main 入口各清空一次历史文件(require_main=True → 无 main 显式报错)
|
||
main_func: ir.Function | None = self._InstallHotspotTruncate(
|
||
defs, path, require_main=True, flag='--hotspot-sites')
|
||
if main_func is None:
|
||
raise RuntimeError(
|
||
'--hotspot-sites: 找不到 main 函数定义,无法注册热点落盘触发')
|
||
self._InstallHotspotTruncate(
|
||
defs, time_path, require_main=True, flag='--hotspot-sites')
|
||
# 在 main 入口注册落盘触发(清空/截断之后、程序 body 之前执行)
|
||
mb: ir.IRBuilder = ir.IRBuilder(main_func.entry_basic_block)
|
||
mb.position_at_start(main_func.entry_basic_block)
|
||
self._EmitHotspotDumpRegistration(mb, ctl_handler, dump_all)
|
||
|
||
def _InstallHotspotSitesModuleLocal(self) -> None:
|
||
"""安装调用点热点统计(--hotspot-sites):Projectrans 多模块路径。
|
||
|
||
每模块自带 internal 调用点计数器 + internal 每函数计时器 + internal 落盘函数
|
||
(调用点计数追加写最终 <path>,计时追加写同目录 hotspot_time.txt),并各自
|
||
通过 @llvm.global_ctors 在进程启动期注册落盘触发:Ctrl+C 处理器
|
||
(SetConsoleCtrlHandler) 与退出处理器 (atexit)(见 _RegisterHotspotSitesViaCtor)。
|
||
仅含 main 的模块额外在 main 入口以 "w" 各清空一次文件,保证"一次运行一份结果"。
|
||
未启用(hotspot_sites_file 为 None)时本方法不会被调用。
|
||
"""
|
||
path: str | None = self.hotspot_sites_file
|
||
if not path:
|
||
raise RuntimeError('--hotspot-sites: 需要提供调用点计数输出文件路径')
|
||
module_id: str = getattr(self, 'module_sha1', None) or ''
|
||
defs, records = self._EmitHotspotSiteCounters()
|
||
time_records = self._EmitHotspotTimeCounters()
|
||
time_path: str = self._HotspotTimePath(path)
|
||
sites_dump: ir.Function = self._CreateHotspotSitesDump(
|
||
path, records, module_id=module_id or None)
|
||
time_dump: ir.Function = self._CreateHotspotTimeDump(
|
||
time_path, time_records, module_id=module_id or None)
|
||
ctl_handler, dump_all = self._CreateHotspotDumpAll(sites_dump, time_dump)
|
||
self._RegisterHotspotSitesViaCtor(ctl_handler, dump_all)
|
||
# 非 main 模块无 main、不需要截断(返回 None 不报错):其它模块的写入
|
||
# 依赖含 main 模块在启动期清空,不会互相覆盖。
|
||
self._InstallHotspotTruncate(defs, path, flag='--hotspot-sites')
|
||
self._InstallHotspotTruncate(defs, time_path, flag='--hotspot-sites') |