Files
TransPyC/lib/core/LLVMCG/BaseGen.py
T
TermiNexus fc5a6134ca feat(hotspot)!: 下线 --func-hotspot 并为 --hotspot-sites 增加每函数计时器
移除 --func-hotspot 全链路(CLI 解析与绑定、Config argparse 与注入、Translator 属性、LlvmGenerator 收口与透传、BaseGen 专属实现、Phase2Translator 透传、帮助文本);全仓已无 FuncHotspot/func-hotspot 残留引用。

通用截断逻辑保留并改名为 _InstallHotspotTruncate,供 --hotspot-sites 使用(分别清空 hotspot_sites.txt 与 hotspot_time.txt);ctor 注册改为直接新建并接收处理器列表(sites 与 time 两个处理器在同一 ctor 内注册)。

--hotspot-sites 现附带每函数计时:入口读一次 @llvm.readcyclecounter() 存起始 tick,在函数所有 ret 之前再读一次并累加差值到 internal i64 __fhs_ftime_N,同时 __fhs_ftime_enter_N 记录进入次数;单位为 tick(x86_64 即 CPU 周期),语义为 inclusive(包含内层调用)。

计时结果按耗时降序写入同目录 hotspot_time.txt(# time <module_sha1> 分段 + 函数名/累计 tick/进入次数),不改变 hotspot_sites.txt 的既有格式与字段。不传 --hotspot-sites 时零影响。
2026-09-14 13:10:06 +08:00

1575 lines
86 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.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] = {}
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()
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
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 _get_or_create_struct(self, name: str, source_sha1: str | None = None, packed: bool = False) -> ir.IdentifiedStructType:
clean_name: str = name.replace(' *', '').replace('*', '').replace(' ', '_').replace('const', 'const_').replace('volatile', 'volatile_')
# 如果名称包含模块前缀(如 "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]
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}"
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:
return ir.IntType(32)
# 治本修复:当 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 ir.IntType(32)
if Entry.BaseType is _t.CEnum:
return ir.IntType(32)
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)
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})"
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 _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
# ========== 统一工具方法 ==========
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')
def _EmitHotspotTimeCounters(
self,
) -> list[tuple[ir.GlobalVariable, ir.GlobalVariable, str]]:
"""为模块内每个函数定义安装"每函数累计耗时"计时器(--hotspot-sites 附带)。
计时方式(inclusive / 包含式,含被调方耗时):在函数入口用一次
@llvm.readcyclecounter() 取起始 tick 存入**本调用帧**的 alloca;在该函数
每个 ret 终结指令之前再取一次 tick,把"本次调用耗时"累加进该函数专属的
internal i64 __fhs_ftime_<N>;同时用 internal i64 __fhs_ftime_enter_<N>
统计该函数被进入次数,便于交叉校验。
返回记录列表 (累计耗时全局, 进入次数全局, 函数名),顺序即 N 的编号顺序。
计时原语:@llvm.readcyclecounter()(LLVM 内建 intrinsic,x86 上降为 rdtsc、
arm64 上降为 cntvct_el0),无 libc/Win32 依赖、无需额外链接;精度为 1 个
tick(x86_64 上即一个 CPU 周期,~0.3ns 量级),故耗时单位记为 tick。
插桩规则与跳过条件:
- 只处理有基本块(即已定义)且名字不以 __fhs_ 开头的函数;计数器一律
internal,跨模块(独立翻译单元)链接不产生重复符号,初值 0。
- 入口 alloca 置于入口块最前:每次调用都会执行,故递归/重入时各调用帧
各自持有起始 tick,差值互不串扰;本属性为"包含式"耗时(递归/嵌套会
把内层耗时重复计入外层,需结合进入次数解读)。
- 只在该函数的 ret 终结指令之前插桩;以 unreachable 等其它终结指令结尾的
块(如 noreturn 调用后的代码)没有"返回点",明确跳过、不伪造数值。
- 累加只做 load/sub/add/store 与读取计时原语,不改变返回值/参数/控制流。
"""
i64: ir.IntType = ir.IntType(64)
rcc: ir.Function = self._DeclareExternalFunc(
'llvm.readcyclecounter', ir.FunctionType(i64, []))
records: list[tuple[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)
sum_gv: ir.GlobalVariable = ir.GlobalVariable(
self.module, i64, name=self._UniqueGlobalName(f'__fhs_ftime_{idx}'))
sum_gv.linkage = 'internal'
sum_gv.initializer = ir.Constant(i64, 0)
enter_gv: ir.GlobalVariable = ir.GlobalVariable(
self.module, i64, name=self._UniqueGlobalName(f'__fhs_ftime_enter_{idx}'))
enter_gv.linkage = 'internal'
enter_gv.initializer = ir.Constant(i64, 0)
# 入口:alloca 起始 tick → 取 tick 存入 → 进入次数 +1(置于入口块最前)
entry: ir.Block = func.entry_basic_block
eb: ir.IRBuilder = ir.IRBuilder(entry)
eb.position_at_start(entry)
start_ptr: ir.Value = eb.alloca(i64, name=f'fhs_ftime_start_{idx}')
eb.store(eb.call(rcc, [], name=f'fhs_ftime_begin_{idx}'), start_ptr)
eb.store(eb.add(eb.load(enter_gv), ir.Constant(i64, 1)), enter_gv)
# 每个 ret 之前:tick - 起始 = 本次调用耗时,累加进 __fhs_ftime_<N>
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)
delta: ir.Value = rb.sub(
rb.call(rcc, [], name=f'fhs_ftime_end_{idx}'),
rb.load(start_ptr), name=f'fhs_ftime_delta_{idx}')
rb.store(rb.add(rb.load(sum_gv), delta), sum_gv)
records.append((sum_gv, enter_gv, func.name))
return records
def _CreateHotspotSitesHandler(
self,
path: str,
records: list[tuple[ir.GlobalVariable, str, str, str, int]],
module_id: str | None = None,
) -> ir.Function:
"""生成 Win32 控制台 Ctrl+C 处理器:按调用次数降序把各调用点追加写入 <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) 仅
在 Ctrl+C 触发一次)按计数降序输出,只输出 count>0 的调用点;计数相同时
按计数器编号 K 升序(稳定、可复现)。
- 源码位置:作为可选尾字段,格式 "<file>:<line>"(含前置单个空格);该位置
来自生成期挂在 call 指令对象上的 _src_file/_src_line(见 _HotspotSiteBuilder)。
生成期无法关联到 AST 的合成调用点取不到位置,则此字段整体省略(不伪造、不填默认值)。
- 处理器签名为 Win32 PHANDLER_ROUTINE(i32 (i32) -> BOOL),用 CRT
fopen("a")/fprintf/fclose 落盘(沿用既有做法),返回 FALSE(0) 让默认
处理继续,使 Ctrl+C 仍能终止程序。
"""
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]))
handler: ir.Function = ir.Function(
self.module, ir.FunctionType(i32, [i32]),
name=self._UniqueGlobalName('__fhs_sites_handler'))
handler.linkage = 'internal'
b: ir.IRBuilder = ir.IRBuilder(handler.append_basic_block(name='entry'))
b.position_at_start(handler.entry_basic_block)
# 无匹配调用点:生成空处理器(不落盘、不建数组),仅占位以保持注册链路一致
if n == 0:
b.ret(ir.Constant(i32, 0))
return handler
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 = handler.append_basic_block(name='fhs_sites_sort_k')
body_k: ir.Block = handler.append_basic_block(name='fhs_sites_sort_k_body')
loop_i: ir.Block = handler.append_basic_block(name='fhs_sites_sort_i')
body_i: ir.Block = handler.append_basic_block(name='fhs_sites_sort_i_body')
scan_i: ir.Block = handler.append_basic_block(name='fhs_sites_sort_i_scan')
upd_i: ir.Block = handler.append_basic_block(name='fhs_sites_sort_i_upd')
next_i: ir.Block = handler.append_basic_block(name='fhs_sites_sort_i_next')
end_i: ir.Block = handler.append_basic_block(name='fhs_sites_sort_i_end')
print_k: ir.Block = handler.append_basic_block(name='fhs_sites_print_k')
after_k: ir.Block = handler.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; return 0
b.position_at_end(after_k)
b.call(fclose, [fp])
b.ret(ir.Constant(i32, 0))
return handler
def _CreateHotspotTimeHandler(
self,
path: str,
records: list[tuple[ir.GlobalVariable, ir.GlobalVariable, str]],
module_id: str | None = None,
) -> ir.Function:
"""生成 Win32 控制台 Ctrl+C 处理器:按累计耗时降序写入每函数计时文件。
落盘格式(追加):
# time <module_sha1>\\n
<func_name> <acc_ticks> <enter_count>\\n
module_id 为空时省略头部 sha1 行(留空,不伪造)。
- 排序:与调用点计数同法,落盘前用选择排序按累计 tick 降序输出,只输出
进入次数 >0 的函数;tick 相同时按编号 N 升序(稳定、可复现)。
- acc_ticks 单位为 @llvm.readcyclecounter() 的 tick(x86_64 上即 CPU 周期);
enter_count 为函数被进入次数(含递归/重入),用于交叉校验。
- 处理器签名为 Win32 PHANDLER_ROUTINE(i32 (i32) -> BOOL),用 CRT
fopen("a")/fprintf/fclose 落盘(沿用既有做法),返回 FALSE(0) 让默认
处理继续,使 Ctrl+C 仍能终止程序。
"""
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]))
handler: ir.Function = ir.Function(
self.module, ir.FunctionType(i32, [i32]),
name=self._UniqueGlobalName('__fhs_ftime_handler'))
handler.linkage = 'internal'
b: ir.IRBuilder = ir.IRBuilder(handler.append_basic_block(name='entry'))
b.position_at_start(handler.entry_basic_block)
# 无计时对象(模块内无用户函数定义):生成空处理器占位,保持注册链路一致
if n == 0:
b.ret(ir.Constant(i32, 0))
return handler
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\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\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)
sumptr_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[2], name=self._UniqueGlobalName(f'__fhs_ftime_name_{k}'))
for k, rec in enumerate(records)
])
sum_gv: ir.GlobalVariable = ir.GlobalVariable(
self.module, sumptr_ty, name=self._UniqueGlobalName('__fhs_ftime_sum_ptrs'))
sum_gv.linkage = 'internal'
sum_gv.global_constant = True
sum_gv.initializer = ir.Constant(sumptr_ty, [
ir.Constant(i64p, rec[0].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[1].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,每轮在未输出项里选累计耗时最大者(相同取 N 小者)
loop_k: ir.Block = handler.append_basic_block(name='fhs_ftime_sort_k')
body_k: ir.Block = handler.append_basic_block(name='fhs_ftime_sort_k_body')
loop_i: ir.Block = handler.append_basic_block(name='fhs_ftime_sort_i')
body_i: ir.Block = handler.append_basic_block(name='fhs_ftime_sort_i_body')
scan_i: ir.Block = handler.append_basic_block(name='fhs_ftime_sort_i_scan')
upd_i: ir.Block = handler.append_basic_block(name='fhs_ftime_sort_i_upd')
next_i: ir.Block = handler.append_basic_block(name='fhs_ftime_sort_i_next')
end_i: ir.Block = handler.append_basic_block(name='fhs_ftime_sort_i_end')
print_k: ir.Block = handler.append_basic_block(name='fhs_ftime_print_k')
after_k: ir.Block = handler.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 = *sum_ptrs[i];同时把进入次数非 0 作为"有样本"判据
b.position_at_end(scan_i)
sum_p: ir.Value = b.load(b.gep(sum_gv, [c0_32, i_val]))
v_val: ir.Value = b.load(sum_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(sum_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(ep)],
name='fhs_ftime_write')
b.store(b.add(b.load(k_ptr), c1_64), k_ptr)
b.branch(loop_k)
# after_k: fclose; return 0
b.position_at_end(after_k)
b.call(fclose, [fp])
b.ret(ir.Constant(i32, 0))
return handler
def _RegisterHotspotSitesViaCtor(self, handlers: list[ir.Function]) -> None:
"""用 @llvm.global_ctors 在进程启动期为本模块注册全部热点处理器。
生成 internal void @__fhs_sites_ctor()(内部逐个调用 SetConsoleCtrlHandler
注册 handlers:调用点计数处理器与每函数计时处理器),并写入 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(), []))
set_handler: ir.Function = self._DeclareExternalFunc(
'SetConsoleCtrlHandler', ir.FunctionType(i32, [i8p, i32]))
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'))
for h in handlers:
cb.call(set_handler, [cb.bitcast(h, i8p), ir.Constant(i32, 1)])
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 _InstallHotspotSites(self) -> None:
"""安装调用点热点统计(--hotspot-sites):种子 CLI 单模块路径。
顺序:先装调用点计数(其扫描只针对用户代码,不会把随后插入的计时原语
调用计入),再装每函数计时;随后在 main 入口用 "w" 各清空一次调用点计数
文件与计时文件,并注册全部 Ctrl+C 处理器(程序启动期各执行一次)。仅在
hotspot_sites_file 非 None(用户传入 --hotspot-sites)时被调用;未启用时
该方法不会执行,IR 输出与既有行为一致(零影响)。
"""
path: str | None = self.hotspot_sites_file
if not path:
raise RuntimeError('--hotspot-sites: 需要提供调用点计数输出文件路径')
i32: ir.IntType = ir.IntType(32)
i8p: ir.PointerType = ir.PointerType(ir.IntType(8))
module_id: str | None = self.module_sha1 or None
defs, records = self._EmitHotspotSiteCounters()
time_records = self._EmitHotspotTimeCounters()
time_path: str = self._HotspotTimePath(path)
handlers: list[ir.Function] = [
self._CreateHotspotSitesHandler(path, records, module_id=module_id),
self._CreateHotspotTimeHandler(time_path, time_records, module_id=module_id),
]
# 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 函数定义,无法注册 Ctrl+C 处理器')
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)
set_handler: ir.Function = self._DeclareExternalFunc(
'SetConsoleCtrlHandler', ir.FunctionType(i32, [i8p, i32]))
for h in handlers:
mb.call(set_handler, [mb.bitcast(h, i8p), ir.Constant(i32, 1)])
def _InstallHotspotSitesModuleLocal(self) -> None:
"""安装调用点热点统计(--hotspot-sites):Projectrans 多模块路径。
每模块自带 internal 调用点计数器 + internal 每函数计时器 + internal 处理器
(调用点计数追加写最终 <path>,计时追加写同目录 hotspot_time.txt),并各自
通过 @llvm.global_ctors 在进程启动期注册 SetConsoleCtrlHandler
(见 _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)
handlers: list[ir.Function] = [
self._CreateHotspotSitesHandler(path, records, module_id=module_id or None),
self._CreateHotspotTimeHandler(time_path, time_records, module_id=module_id or None),
]
self._RegisterHotspotSitesViaCtor(handlers)
# 非 main 模块无 main、不需要截断(返回 None 不报错):其它模块的写入
# 依赖含 main 模块在启动期清空,不会互相覆盖。
self._InstallHotspotTruncate(defs, path, flag='--hotspot-sites')
self._InstallHotspotTruncate(defs, time_path, flag='--hotspot-sites')