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 时零影响。
This commit is contained in:
1 parent
2021c0edb7
commit
fc5a6134ca
7 files changed
+359
-356
No files matched your search
@@ -149,8 +149,7 @@ def main() -> None:
|
||||
parser.add_argument('--clear-cache', action='store_true', help='清除 .transpyc_cache 全局缓存(.vpi/.stub.ll/.doc.json)')
|
||||
parser.add_argument('--asan', action='store_true', help='ASan 模式:对全部 .ll 做 opt -passes=asan 插桩,并显式链接 clang64 的 ASan 运行库(mingw64 的 clang 没有 x86_64-w64-windows-gnu 目标的 ASan runtime,不能用 -fsanitize=address)')
|
||||
parser.add_argument('--nowarn-register', action='store_true', help='抑制"外部函数返回类型未登记"类 WARN 输出(透传给自举编译的 TransPyV.exe)')
|
||||
parser.add_argument('--func-hotspot', help='函数热点统计:为每个模块的函数定义插入计数器,运行时 Ctrl+C 各模块以追加方式把计数写入 <path>(含 main 的模块在启动时清空一次,保证每次运行一份结果)(可用 python Projectrans.py --project ... --func-hotspot <path>)')
|
||||
parser.add_argument('--hotspot-sites', nargs='?', const='', default=None, help='调用点热点统计:为每个匹配白名单的直接 call 点插入计数器,运行时 Ctrl+C 各模块按真实次数降序写入 <path>(默认为 hotspot_sites.txt;可独立于 --func-hotspot 使用)')
|
||||
parser.add_argument('--hotspot-sites', nargs='?', const='', default=None, help='调用点热点统计:为每个匹配白名单的直接 call 点插入计数器,运行时 Ctrl+C 各模块按真实次数降序写入 <path>(默认为 hotspot_sites.txt);同时为每个函数定义加计时器,按累计耗时降序写入同目录 hotspot_time.txt')
|
||||
parser.add_argument('--hotspot-callees', default=None, help='--hotspot-sites 的白名单被调函数基名(逗号分隔),默认内存/字符串族 strcmp,strncmp,strchr,strlen,strstr,memcpy,memset,memmove,strcpy,strcat')
|
||||
parser.add_argument('--hotspot-sites-all', action='store_true', help='--hotspot-sites 对全部直接 call 点插桩(忽略白名单;体积/开销大,用于深度排查)')
|
||||
|
||||
@@ -197,11 +196,6 @@ def main() -> None:
|
||||
pass
|
||||
target_triple: str | None = resolved.get('target', {}).get('triple')
|
||||
target_datalayout: str | None = resolved.get('target', {}).get('datalayout')
|
||||
# 函数热点统计(--func-hotspot):解析为绝对路径,保证运行时(exe 工作目录为
|
||||
# output_dir)各模块处理器追加写入的路径与预期完全一致。
|
||||
func_hotspot_file: str | None = args.func_hotspot
|
||||
if func_hotspot_file:
|
||||
func_hotspot_file = os.path.abspath(func_hotspot_file)
|
||||
# 调用点热点统计(--hotspot-sites):None=未启用;''=启用但未给路径(默认
|
||||
# hotspot_sites.txt);其余为显式路径。一律绝对化,保证运行时(exe 工作目录为
|
||||
# output_dir)各模块处理器追加写入的路径与预期一致。
|
||||
@@ -230,10 +224,8 @@ def main() -> None:
|
||||
_vlog().info(f"目标三元组: {target_triple}")
|
||||
if target_datalayout:
|
||||
_vlog().info(f"数据布局: {target_datalayout}")
|
||||
if func_hotspot_file:
|
||||
_vlog().info(f"函数热点统计: {func_hotspot_file}(各模块运行时以追加方式写入,含 main 的模块启动时清空一次)")
|
||||
if hotspot_sites_file:
|
||||
_vlog().info(f"调用点热点统计: {hotspot_sites_file}(按真实调用次数降序落盘,含 main 的模块启动时清空一次)")
|
||||
_vlog().info(f"调用点热点统计: {hotspot_sites_file}(按真实调用次数降序落盘,含 main 的模块启动时清空一次);每函数计时: {os.path.join(os.path.dirname(hotspot_sites_file), 'hotspot_time.txt')}")
|
||||
|
||||
# 每次编译自动清理子进程性能剖析输出(PROF 目录 *.prof,硬编码路径)
|
||||
from lib.Projectrans.Phase2Translator import _CleanProfFiles, PROF_DIR
|
||||
@@ -339,9 +331,7 @@ def main() -> None:
|
||||
trans: Phase2Translator = Phase2Translator(src, temp_dir, output_dir, compile_cmd=cc_cmd, compile_flags=cc_flags, linker_cmd=linker_cmd, linker_flags=linker_flags, linker_output=linker_output, static_libs=static_libs, dynamic_libs=dynamic_libs, include_dirs=project_include_dirs, target_triple=target_triple, target_datalayout=target_datalayout, force_recompile_includes=force_recompile_includes, asan_mode=args.asan)
|
||||
trans.sha1_map = Load_sha1_map(temp_dir)
|
||||
trans.slice_level = slice_level
|
||||
# 函数热点统计(--func-hotspot):None=未启用(零影响)
|
||||
trans.func_hotspot_file = func_hotspot_file
|
||||
# 调用点热点统计(--hotspot-sites):None=未启用(零影响)
|
||||
# 调用点热点统计与每函数计时(--hotspot-sites):None=未启用(零影响)
|
||||
trans.hotspot_sites_file = hotspot_sites_file
|
||||
trans.hotspot_sites_all = bool(args.hotspot_sites_all)
|
||||
trans.hotspot_callees = hotspot_callees
|
||||
|
||||
@@ -162,7 +162,6 @@ def _parallel_translate_worker(src_path: str, out_path: str, src_root: str, temp
|
||||
stub_files: dict[str, str], include_py_map: dict[str, str], slice_level: int,
|
||||
shared_sym_pickle_path: str, struct_sha1_map: dict[str, str],
|
||||
prebuilt_ModuleSha1Map: dict[str, str] | None = None,
|
||||
func_hotspot_file: str | None = None,
|
||||
hotspot_sites_file: str | None = None,
|
||||
hotspot_sites_all: bool = False,
|
||||
hotspot_callees: list[str] | None = None) -> tuple[str, str, str]:
|
||||
@@ -183,10 +182,8 @@ def _parallel_translate_worker(src_path: str, out_path: str, src_root: str, temp
|
||||
trans.include_py_map = include_py_map
|
||||
trans.slice_level = slice_level
|
||||
trans.struct_sha1_map = struct_sha1_map
|
||||
# 函数热点统计(--func-hotspot):worker 子进程无父进程属性,须显式透传
|
||||
# (None=未启用,零影响);生成 IR 时各模块处理器统一追加写最终 <path>
|
||||
trans.func_hotspot_file = func_hotspot_file
|
||||
# 调用点热点统计(--hotspot-sites):同样须显式透传(None=未启用,零影响)
|
||||
# 调用点热点统计与每函数计时(--hotspot-sites):worker 子进程无父进程属性,
|
||||
# 须显式透传(None=未启用,零影响);生成 IR 时各模块处理器统一追加写最终 <path>
|
||||
trans.hotspot_sites_file = hotspot_sites_file
|
||||
trans.hotspot_sites_all = hotspot_sites_all
|
||||
trans.hotspot_callees = hotspot_callees
|
||||
@@ -234,7 +231,6 @@ def _parallel_translate_batch(batch: list, src_root: str, temp_dir: str, output_
|
||||
sha1_map: dict[str, str], sig_files: dict[str, str],
|
||||
stub_files: dict[str, str], include_py_map: dict[str, str], slice_level: int,
|
||||
shared_sym_pickle_path: str, struct_sha1_map: dict[str, str],
|
||||
func_hotspot_file: str | None = None,
|
||||
hotspot_sites_file: str | None = None,
|
||||
hotspot_sites_all: bool = False,
|
||||
hotspot_callees: list[str] | None = None) -> tuple[list, list]:
|
||||
@@ -258,9 +254,7 @@ def _parallel_translate_batch(batch: list, src_root: str, temp_dir: str, output_
|
||||
trans.include_py_map = include_py_map
|
||||
trans.slice_level = slice_level
|
||||
trans.struct_sha1_map = struct_sha1_map
|
||||
# 函数热点统计(--func-hotspot):批内每文件的处理器统一追加写最终 <path>
|
||||
trans.func_hotspot_file = func_hotspot_file
|
||||
# 调用点热点统计(--hotspot-sites):批内每文件同样透传
|
||||
# 调用点热点统计与每函数计时(--hotspot-sites):批内每文件同样透传
|
||||
trans.hotspot_sites_file = hotspot_sites_file
|
||||
trans.hotspot_sites_all = hotspot_sites_all
|
||||
trans.hotspot_callees = hotspot_callees
|
||||
@@ -662,13 +656,10 @@ class Phase2Translator:
|
||||
os.environ['PATH'] = _clang64_bin + os.pathsep + os.environ.get('PATH', '')
|
||||
self.asan_rt_dir = _FindAsanRuntimeDir()
|
||||
self.slice_level = 3
|
||||
# 函数热点统计(--func-hotspot):None=未启用(零影响);str=计数器输出
|
||||
# 文件路径。各模块运行时自带 Ctrl+C 处理器、统一以追加方式写该路径;
|
||||
# 含 main 的模块在 main 入口清空一次 → 每次运行一份结果(无构建后合并步骤)。
|
||||
self.func_hotspot_file: str | None = None
|
||||
# 调用点热点统计(--hotspot-sites):None=未启用(零影响);str=调用点计数
|
||||
# 落盘文件路径。各模块运行时自带 Ctrl+C 处理器、统一以追加方式写该路径;
|
||||
# 含 main 的模块在 main 入口清空一次 → 每次运行一份结果。
|
||||
# 落盘文件路径(每函数计时落同目录 hotspot_time.txt)。各模块运行时自带
|
||||
# Ctrl+C 处理器、统一以追加方式写该路径;含 main 的模块在 main 入口清空一次
|
||||
# → 每次运行一份结果。
|
||||
self.hotspot_sites_file: str | None = None
|
||||
# 是否对全部直接 call 点插桩(忽略白名单);白名单被调函数基名列表(None=默认)
|
||||
self.hotspot_sites_all: bool = False
|
||||
@@ -876,11 +867,11 @@ class Phase2Translator:
|
||||
self.struct_sha1_map = self._build_struct_sha1_map()
|
||||
|
||||
need_translate = []
|
||||
# --func-hotspot / --hotspot-sites:缓存 .ll / .stub.ll+.text.ll 可能不含
|
||||
# 热点计数 IR,启用任一功能时一律重译,保证每个模块都嵌入 internal 计数器
|
||||
# 与 @llvm.global_ctors 自注册。未启用时 hotspot_on=False,缓存与重组分支
|
||||
# --hotspot-sites:缓存 .ll / .stub.ll+.text.ll 可能不含热点计数/计时 IR,
|
||||
# 启用时一律重译,保证每个模块都嵌入 internal 计数器、计时器与
|
||||
# @llvm.global_ctors 自注册。未启用时 hotspot_on=False,缓存与重组分支
|
||||
# 行为与改动前完全一致(零影响)。
|
||||
hotspot_on: bool = bool(self.func_hotspot_file or self.hotspot_sites_file)
|
||||
hotspot_on: bool = bool(self.hotspot_sites_file)
|
||||
for i, src_path in enumerate(py_files, 1):
|
||||
rel = os.path.relpath(src_path, self.src_root)
|
||||
# 复用 parse_python_file 的 AST 缓存(get_file_dependencies 已 parse 过)
|
||||
@@ -1039,7 +1030,6 @@ class Phase2Translator:
|
||||
self.sha1_map, self.sig_files, self.stub_files,
|
||||
self.include_py_map, self.slice_level,
|
||||
shared_pickle_path, self.struct_sha1_map,
|
||||
func_hotspot_file=self.func_hotspot_file,
|
||||
hotspot_sites_file=self.hotspot_sites_file,
|
||||
hotspot_sites_all=self.hotspot_sites_all,
|
||||
hotspot_callees=self.hotspot_callees,
|
||||
@@ -1317,7 +1307,7 @@ class Phase2Translator:
|
||||
|
||||
if stripped.startswith('@') and ' global ' in stripped and 'external global' not in stripped:
|
||||
text_lines.append(line)
|
||||
# LLVM 保留全局(如 --func-hotspot 的 @llvm.global_ctors;名字含点故
|
||||
# LLVM 保留全局(如 --hotspot-sites 的 @llvm.global_ctors;名字含点故
|
||||
# 最终 IR 保留引号)是 appending 定义,禁止改写为 "external global"
|
||||
# 存根(否则 .ll 重组后 llc 报错)
|
||||
if re.match(r'@("?)llvm\.', stripped):
|
||||
@@ -1583,15 +1573,9 @@ class Phase2Translator:
|
||||
trans.translator._shared_class_methods = self._shared_class_methods
|
||||
trans.translator._shared_cross_module_novtable = self._shared_cross_module_novtable
|
||||
|
||||
# 函数热点统计(--func-hotspot,Projectrans 多模块):
|
||||
# 各模块处理器统一以追加方式写最终 <path>;注册策略为本模块
|
||||
# @llvm.global_ctors 自注册(见 BaseGen._InstallFuncHotspotModuleLocal)。
|
||||
# self.func_hotspot_file 为 None 时不设置任何属性 → 与未启用时逐字节一致(零影响)。
|
||||
if self.func_hotspot_file:
|
||||
trans.translator.FuncHotspotFile = self.func_hotspot_file
|
||||
trans.translator.FuncHotspotModuleLocal = True
|
||||
# 调用点热点统计(--hotspot-sites,Projectrans 多模块):同上,注册策略为
|
||||
# 本模块 ctor 自注册(见 BaseGen._InstallHotspotSitesModuleLocal);
|
||||
# 调用点热点统计与每函数计时(--hotspot-sites,Projectrans 多模块):
|
||||
# 各模块处理器统一以追加方式写最终 <path>,注册策略为本模块
|
||||
# @llvm.global_ctors 自注册(见 BaseGen._InstallHotspotSitesModuleLocal);
|
||||
# self.hotspot_sites_file 为 None 时不设置任何属性 → 与未启用时逐字节一致。
|
||||
if self.hotspot_sites_file:
|
||||
trans.translator.HotspotSitesFile = self.hotspot_sites_file
|
||||
@@ -2878,7 +2862,6 @@ class Phase2Translator:
|
||||
self.include_py_map, self.slice_level,
|
||||
shared_pickle_path, self.struct_sha1_map,
|
||||
include_ModuleSha1Map,
|
||||
func_hotspot_file=self.func_hotspot_file,
|
||||
hotspot_sites_file=self.hotspot_sites_file,
|
||||
hotspot_sites_all=self.hotspot_sites_all,
|
||||
hotspot_callees=self.hotspot_callees
|
||||
@@ -2898,7 +2881,6 @@ class Phase2Translator:
|
||||
self.include_py_map, self.slice_level,
|
||||
shared_pickle_path, self.struct_sha1_map,
|
||||
include_ModuleSha1Map,
|
||||
func_hotspot_file=self.func_hotspot_file,
|
||||
hotspot_sites_file=self.hotspot_sites_file,
|
||||
hotspot_sites_all=self.hotspot_sites_all,
|
||||
hotspot_callees=self.hotspot_callees
|
||||
|
||||
+3
-13
@@ -332,14 +332,6 @@ class TransPyC:
|
||||
else:
|
||||
error('--datalayout requires an argument')
|
||||
sys.exit(1)
|
||||
elif sys.argv[I] == '--func-hotspot':
|
||||
# 函数热点统计:为每个函数定义配 u64 计数器,Ctrl+C 时写入指定文件
|
||||
if I + 1 < len(sys.argv):
|
||||
self.Args['FuncHotspot'] = sys.argv[I + 1]
|
||||
I += 2
|
||||
else:
|
||||
error('--func-hotspot requires an argument')
|
||||
sys.exit(1)
|
||||
elif sys.argv[I] == '--hotspot-sites':
|
||||
# 调用点热点统计:可选路径(省略时默认 hotspot_sites.txt,运行期由
|
||||
# 处理器按计数降序写入)。空串表示"已启用但未给路径"。
|
||||
@@ -628,11 +620,9 @@ class TransPyC:
|
||||
# 有命令行参数,使用 ParseArgs 方法解析
|
||||
self.ParseArgs()
|
||||
|
||||
# 函数热点统计(--func-hotspot):绑定到翻译器;未传时为 None(零影响)
|
||||
self.translator.FuncHotspotFile = self.Args.get('FuncHotspot')
|
||||
|
||||
# 调用点热点统计(--hotspot-sites):None=未启用(零影响);''=启用但未给
|
||||
# 路径(默认 hotspot_sites.txt)。注册走种子单模块路径(main 入口注册)。
|
||||
# 调用点热点统计与每函数计时(--hotspot-sites):None=未启用(零影响);
|
||||
# ''=启用但未给路径(默认 hotspot_sites.txt)。注册走种子单模块路径
|
||||
# (main 入口注册);计时结果写到同目录 hotspot_time.txt。
|
||||
HotspotSites: str | None = self.Args.get('HotspotSites')
|
||||
if HotspotSites is not None:
|
||||
if HotspotSites == '':
|
||||
|
||||
@@ -44,11 +44,11 @@ ERROR_MESSAGES: dict[str, str] = {
|
||||
HELP_MESSAGE: str = '''
|
||||
Usage: python TransPyC.py -f InputFile -o OutputFile [-wh header_files] [-debug DebugFile]
|
||||
[-cc compile_command] [-cflags CompileFlags] [-run] [-args RunArgs] [-h HelperFiles]
|
||||
[-func-hotspot HotFile] [--hotspot-sites [SitesFile]] [--hotspot-callees a,b,c] [--hotspot-sites-all]
|
||||
[--hotspot-sites [SitesFile]] [--hotspot-callees a,b,c] [--hotspot-sites-all]
|
||||
-h: Specify helper files (C or Python) to help identify structs, functions, variables, and ptrs
|
||||
-func-hotspot: 统计各函数调用次数,Ctrl+C 退出时写入 HotFile(每行 "函数名 计数")
|
||||
--hotspot-sites: 统计各调用点真实运行次数,Ctrl+C 退出时写入 SitesFile(默认 hotspot_sites.txt,
|
||||
按计数降序;可独立于 -func-hotspot 使用)
|
||||
--hotspot-sites: 统计各调用点真实运行次数(Ctrl+C 退出时写入 SitesFile,默认 hotspot_sites.txt,
|
||||
按计数降序),并为每个函数定义加计时器(累计耗时与进入次数按耗时降序写入同目录
|
||||
hotspot_time.txt;耗时单位为 tick = @llvm.readcyclecounter() 计数)
|
||||
--hotspot-callees: --hotspot-sites 的白名单被调函数基名(逗号分隔,默认内存/字符串族)
|
||||
--hotspot-sites-all: --hotspot-sites 对全部直接 call 点插桩(忽略白名单,体积/开销大)
|
||||
'''
|
||||
|
||||
+327
-271
@@ -203,18 +203,14 @@ class BaseGenMixin:
|
||||
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]]] = {}
|
||||
# 函数热点统计(--func-hotspot):None=未启用(零影响);str=计数器落盘文件路径
|
||||
self.func_hotspot_file: str | None = None
|
||||
# 注册策略:False=种子 CLI 单模块(在 main 入口注册);True=Projectrans
|
||||
# 多模块(每模块用 @llvm.global_ctors 自注册,不依赖 main 所在模块)
|
||||
self.func_hotspot_module_local: bool = False
|
||||
# 调用点热点统计(--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
|
||||
# 注册策略:与 func_hotspot_module_local 同理
|
||||
# 注册策略:False=种子 CLI 单模块(在 main 入口注册);True=Projectrans
|
||||
# 多模块(每模块用 @llvm.global_ctors 自注册,不依赖 main 所在模块)
|
||||
self.hotspot_sites_module_local: bool = False
|
||||
pi: dict[str, bool | int] = self._platform_info
|
||||
# 根据目标平台设置宏(声明式,从三元组推导)
|
||||
@@ -894,12 +890,12 @@ class BaseGenMixin:
|
||||
return None
|
||||
return None
|
||||
|
||||
# ========== 函数热点统计(--func-hotspot) ==========
|
||||
# ========== 调用点热点统计基础设施(--hotspot-sites) ==========
|
||||
|
||||
def _UniqueGlobalName(self, base: str) -> str:
|
||||
"""返回本模块内未被占用的全局符号名(追加下划线直到不冲突)。
|
||||
|
||||
仅用于 --func-hotspot 自动生成的内部符号,避免与用户全局/函数重名。
|
||||
仅用于 --hotspot-sites 自动生成的内部符号,避免与用户全局/函数重名。
|
||||
"""
|
||||
name: str = base
|
||||
while name in self.module.globals:
|
||||
@@ -907,7 +903,7 @@ class BaseGenMixin:
|
||||
return name
|
||||
|
||||
def _DeclareExternalFunc(self, name: str, ftype: ir.FunctionType) -> ir.Function:
|
||||
"""获取或声明外部 C 函数(用于 --func-hotspot 所需的 Win32/CRT 接口)。
|
||||
"""获取或声明外部 C 函数(用于 --hotspot-sites 所需的 Win32/CRT 接口)。
|
||||
|
||||
已存在同名函数时直接复用;若已存在符号类型不一致则显式报错(不静默降级)。
|
||||
"""
|
||||
@@ -915,156 +911,32 @@ class BaseGenMixin:
|
||||
if existing is not None:
|
||||
if not isinstance(existing, ir.Function):
|
||||
raise RuntimeError(
|
||||
f'--func-hotspot: 全局符号 {name} 已存在且不是函数,无法声明外部接口')
|
||||
f'--hotspot-sites: 全局符号 {name} 已存在且不是函数,无法声明外部接口')
|
||||
if existing.ftype != ftype:
|
||||
raise RuntimeError(
|
||||
f'--func-hotspot: 外部函数 {name} 已存在但签名不一致'
|
||||
f'--hotspot-sites: 外部函数 {name} 已存在但签名不一致'
|
||||
f'(期望 {ftype},实际 {existing.ftype})')
|
||||
return existing
|
||||
return ir.Function(self.module, ftype, name=name)
|
||||
|
||||
def _EmitFuncHotspotCounters(self) -> tuple[list[ir.Function], list[ir.GlobalVariable], list[ir.Constant]]:
|
||||
"""为模块内每个函数定义安装 internal i64 计数器并在入口递增。
|
||||
|
||||
返回 (被计数的函数定义列表, 计数器列表, 函数名常量指针列表)。
|
||||
|
||||
抽出自原 _InstallFuncHotspot,供两种注册策略复用:
|
||||
- 种子 CLI 单模块:在 main 入口注册处理器(原行为);
|
||||
- Projectrans 多模块:每模块用 @llvm.global_ctors 自注册。
|
||||
计数器一律 internal,跨模块(独立翻译单元)链接不产生重复符号。
|
||||
"""
|
||||
i64: ir.IntType = ir.IntType(64)
|
||||
# 收集模块内所有函数定义(只有声明没有基本块的属于 declare,跳过)
|
||||
defs: list[ir.Function] = [f for f in self.module.functions if getattr(f, 'blocks', None)]
|
||||
if not defs:
|
||||
raise RuntimeError('--func-hotspot: 模块内没有任何函数定义,无法安装计数')
|
||||
counters: list[ir.GlobalVariable] = []
|
||||
name_ptrs: list[ir.Constant] = []
|
||||
for idx, func in enumerate(defs):
|
||||
# 计数器全局:唯一命名的 internal i64,初值 0
|
||||
counter: ir.GlobalVariable = ir.GlobalVariable(
|
||||
self.module, i64, name=self._UniqueGlobalName(f'__fhs_cnt_{idx}'))
|
||||
counter.linkage = 'internal'
|
||||
counter.initializer = ir.Constant(i64, 0)
|
||||
counters.append(counter)
|
||||
# 可读函数名(IR 中真实符号名)写入只读字符串常量,供处理器打印
|
||||
name_ptrs.append(self._create_string_global(
|
||||
func.name, name=self._UniqueGlobalName(f'__fhs_name_{idx}')))
|
||||
# 入口块最前递增:load -> add 1 -> store(置于最前,不破坏原有跳转)
|
||||
entry: ir.Block = func.entry_basic_block
|
||||
b: ir.IRBuilder = ir.IRBuilder(entry)
|
||||
b.position_at_start(entry)
|
||||
cur: ir.Value = b.load(counter, name=f'fhs_load_{idx}')
|
||||
inc: ir.Value = b.add(cur, ir.Constant(i64, 1), name=f'fhs_inc_{idx}')
|
||||
b.store(inc, counter)
|
||||
return defs, counters, name_ptrs
|
||||
|
||||
def _CreateFuncHotspotHandler(
|
||||
self,
|
||||
path: str,
|
||||
counters: list[ir.GlobalVariable],
|
||||
name_ptrs: list[ir.Constant],
|
||||
module_id: str | None = None,
|
||||
) -> ir.Function:
|
||||
"""生成 Win32 控制台 Ctrl+C 处理器:把所有计数器追加写入文件。
|
||||
|
||||
- 处理器签名为 Win32 PHANDLER_ROUTINE(i32 (i32) -> BOOL)
|
||||
- 用 CRT fopen("a")/fprintf/fclose 落盘:多模块各自持有处理器,运行时各
|
||||
自以追加方式写同一个 <path>,互不覆盖(见 _InstallFuncHotspotModuleLocal)
|
||||
- module_id 非空时首行先写 "# module <module_id>\n",用于区分不同模块的计数段
|
||||
- 每行格式为 "函数名 计数\n"
|
||||
- 返回 FALSE(0),让默认处理继续,使 Ctrl+C 仍能终止程序
|
||||
"""
|
||||
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]))
|
||||
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_handler'))
|
||||
handler.linkage = 'internal'
|
||||
b: ir.IRBuilder = ir.IRBuilder(handler.append_basic_block(name='entry'))
|
||||
b.position_at_start(handler.entry_basic_block)
|
||||
path_ptr: ir.Constant = self._create_string_global(
|
||||
path, name=self._UniqueGlobalName('__fhs_path'))
|
||||
# 追加模式 "a":多模块多处理器各自追加到同一个 <path>,互不覆盖
|
||||
mode_ptr: ir.Constant = self._create_string_global(
|
||||
'a', name=self._UniqueGlobalName('__fhs_mode'))
|
||||
fmt_ptr: ir.Constant = self._create_string_global(
|
||||
'%s %llu\n', name=self._UniqueGlobalName('__fhs_fmt'))
|
||||
fp: ir.Value = b.call(fopen, [path_ptr, mode_ptr], name='fhs_fp')
|
||||
if module_id:
|
||||
hdr_fmt_ptr: ir.Constant = self._create_string_global(
|
||||
'# module %s\n', name=self._UniqueGlobalName('__fhs_hdr_fmt'))
|
||||
mid_ptr: ir.Constant = self._create_string_global(
|
||||
module_id, name=self._UniqueGlobalName('__fhs_module_id'))
|
||||
b.call(fprintf, [fp, hdr_fmt_ptr, mid_ptr], name='fhs_write_module')
|
||||
for gv, name_ptr in zip(counters, name_ptrs):
|
||||
b.call(fprintf, [fp, fmt_ptr, name_ptr, b.load(gv)], name='fhs_write')
|
||||
b.call(fclose, [fp])
|
||||
b.ret(ir.Constant(i32, 0))
|
||||
return handler
|
||||
|
||||
def _RegisterFuncHotspotViaCtor(self, handler: ir.Function) -> None:
|
||||
"""用 @llvm.global_ctors 在进程启动期为本模块注册 Ctrl+C 处理器。
|
||||
|
||||
生成 internal void @__fhs_ctor()(内部调用 SetConsoleCtrlHandler),
|
||||
并把它写入 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))
|
||||
# 构造器元素类型 { i32, void ()*, i8* }:优先级、函数指针、关联数据(NULL)
|
||||
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_ctor'))
|
||||
ctor.linkage = 'internal'
|
||||
b: ir.IRBuilder = ir.IRBuilder(ctor.append_basic_block(name='entry'))
|
||||
b.call(set_handler, [b.bitcast(handler, i8p), ir.Constant(i32, 1)])
|
||||
b.ret_void()
|
||||
if 'llvm.global_ctors' in self.module.globals:
|
||||
raise RuntimeError(
|
||||
'--func-hotspot: 模块内已存在 @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 _InstallFuncHotspotTruncate(
|
||||
def _InstallHotspotTruncate(
|
||||
self,
|
||||
defs: list[ir.Function],
|
||||
path: str,
|
||||
require_main: bool = False,
|
||||
flag: str = '--func-hotspot',
|
||||
flag: str = '--hotspot-sites',
|
||||
) -> ir.Function | None:
|
||||
"""在含 main 的模块的 main 入口插入一次 "w" 打开→关闭,清空计数文件。
|
||||
|
||||
--func-hotspot/--hotspot-sites 的处理器以追加("a")方式写 <path>,若不清空会
|
||||
累积历史运行的数据。清空必须满足两点:
|
||||
--hotspot-sites 的各处理器(调用点计数、每函数计时)以追加("a")方式写
|
||||
<path>,若不清空会累积历史运行的数据。清空必须满足两点:
|
||||
- 只在含 main 的模块执行一次(其它模块无 main,返回 None 且不截断);
|
||||
- 必须在 main 入口而非构造器:@llvm.global_ctors 的跨模块执行顺序无保证,
|
||||
放构造器里可能在其它模块已写入后清空 → 丢数据。
|
||||
main 入口在程序启动、任何 Ctrl+C 处理器写入之前执行,故每次运行都以空文件
|
||||
开始,配合追加写保证"一次运行一份结果"。
|
||||
|
||||
flag 仅用于报错信息,调用方可传 '--hotspot-sites'。
|
||||
flag 仅用于报错信息(调用点计数与每函数计时各自调用一次,传入对应开关名)。
|
||||
|
||||
返回找到的 main 定义(供调用方继续注册处理器);未找到时返回 None。
|
||||
require_main=True(种子 CLI 单模块)时未找到 main 直接报错(不静默降级)。
|
||||
@@ -1093,59 +965,6 @@ class BaseGenMixin:
|
||||
b.call(fclose, [fp])
|
||||
return main_func
|
||||
|
||||
def _InstallFuncHotspot(self) -> None:
|
||||
"""安装函数热点统计(--func-hotspot):种子 CLI 单模块路径。
|
||||
|
||||
在 main 入口最前:先用 "w" 打开一次 <path> 清空历史计数,再注册 Ctrl+C
|
||||
处理器(程序启动期各执行一次)。仅在 func_hotspot_file 非 None(用户传入
|
||||
--func-hotspot)时被调用;未启用时该方法不会执行,IR 输出与既有行为一致。
|
||||
"""
|
||||
path: str | None = self.func_hotspot_file
|
||||
if not path:
|
||||
raise RuntimeError('--func-hotspot: 需要提供计数器输出文件路径')
|
||||
i32: ir.IntType = ir.IntType(32)
|
||||
i8p: ir.PointerType = ir.PointerType(ir.IntType(8))
|
||||
defs, counters, name_ptrs = self._EmitFuncHotspotCounters()
|
||||
handler: ir.Function = self._CreateFuncHotspotHandler(
|
||||
path, counters, name_ptrs, module_id=self.module_sha1 or None)
|
||||
# main 入口清空历史计数文件(require_main=True → 无 main 显式报错)
|
||||
main_func: ir.Function | None = self._InstallFuncHotspotTruncate(
|
||||
defs, path, require_main=True)
|
||||
if main_func is None:
|
||||
raise RuntimeError(
|
||||
'--func-hotspot: 找不到 main 函数定义,无法注册 Ctrl+C 处理器')
|
||||
# 在 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]))
|
||||
handler_ptr: ir.Value = mb.bitcast(handler, i8p)
|
||||
mb.call(set_handler, [handler_ptr, ir.Constant(i32, 1)])
|
||||
|
||||
def _InstallFuncHotspotModuleLocal(self) -> None:
|
||||
"""安装函数热点统计(--func-hotspot):Projectrans 多模块路径。
|
||||
|
||||
每个模块是独立翻译单元、各自 llc 后链接,无法跨模块共享同一处理器,
|
||||
故每模块自带 internal 计数器 + internal 处理器(统一追加写最终 <path>),
|
||||
并各自通过 @llvm.global_ctors 在进程启动期注册 SetConsoleCtrlHandler。
|
||||
所有新增符号均 internal,跨模块不产生重复符号;多个处理器在 Ctrl+C 时
|
||||
由系统 LIFO 依次调用、各自以追加("a")方式写同一文件,互不覆盖。
|
||||
仅含 main 的模块额外在 main 入口以 "w" 清空一次文件(见
|
||||
_InstallFuncHotspotTruncate),保证"一次运行一份结果"。未启用
|
||||
(func_hotspot_file 为 None)时本方法不会被调用。
|
||||
"""
|
||||
path: str | None = self.func_hotspot_file
|
||||
if not path:
|
||||
raise RuntimeError('--func-hotspot: 需要提供计数器输出文件路径')
|
||||
module_id: str = getattr(self, 'module_sha1', None) or ''
|
||||
defs, counters, name_ptrs = self._EmitFuncHotspotCounters()
|
||||
handler: ir.Function = self._CreateFuncHotspotHandler(
|
||||
path, counters, name_ptrs, module_id=module_id or None)
|
||||
self._RegisterFuncHotspotViaCtor(handler)
|
||||
# 非 main 模块无 main、不需要截断(返回 None 不报错):其它模块的写入
|
||||
# 依赖含 main 模块在启动期清空,不会互相覆盖。
|
||||
self._InstallFuncHotspotTruncate(defs, path)
|
||||
|
||||
# ========== 调用点热点统计(--hotspot-sites) ==========
|
||||
|
||||
def _NewIrBuilder(self, block: ir.Block) -> ir.IRBuilder:
|
||||
@@ -1189,7 +1008,7 @@ class BaseGenMixin:
|
||||
"被调名去 16 位 sha1 前缀后的基名" 命中白名单的调用点插桩。
|
||||
- 跳过间接调用(callee 不是具名 ir.Function):无法归属被调方,故不插桩。
|
||||
- 跳过 __fhs_ 前缀的辅助函数自身,避免在插桩生成的代码里计数
|
||||
(--func-hotspot 的处理器/构造器也在此列)。
|
||||
(本功能稍后生成的处理器/构造器/计时辅助亦在此列)。
|
||||
- 只在 call 之前插入 load/add/store,不改变返回值/参数/控制流。
|
||||
- 计数器一律 internal,跨模块(独立翻译单元)链接不产生重复符号,初值 0。
|
||||
"""
|
||||
@@ -1234,6 +1053,79 @@ class BaseGenMixin:
|
||||
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,
|
||||
@@ -1420,15 +1312,173 @@ class BaseGenMixin:
|
||||
b.ret(ir.Constant(i32, 0))
|
||||
return handler
|
||||
|
||||
def _RegisterHotspotSitesViaCtor(self, handler: ir.Function) -> None:
|
||||
"""为本模块的调用点热点处理器注册 Ctrl+C(复用/追加 @llvm.global_ctors)。
|
||||
def _CreateHotspotTimeHandler(
|
||||
self,
|
||||
path: str,
|
||||
records: list[tuple[ir.GlobalVariable, ir.GlobalVariable, str]],
|
||||
module_id: str | None = None,
|
||||
) -> ir.Function:
|
||||
"""生成 Win32 控制台 Ctrl+C 处理器:按累计耗时降序写入每函数计时文件。
|
||||
|
||||
生成 internal void @__fhs_sites_ctor()(内部调用 SetConsoleCtrlHandler),
|
||||
并写入 appending linkage 的 @llvm.global_ctors:
|
||||
- 模块内尚无该全局时新建(单模块/纯 --hotspot-sites 场景);
|
||||
- 已存在且为本功能兼容的 appending 结构数组时追加一条(常见于同时启用
|
||||
--func-hotspot:其构造器已建好 @llvm.global_ctors);
|
||||
- 已存在但不兼容时显式报错(不静默降级)。
|
||||
落盘格式(追加):
|
||||
# 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))
|
||||
@@ -1440,80 +1490,86 @@ class BaseGenMixin:
|
||||
name=self._UniqueGlobalName('__fhs_sites_ctor'))
|
||||
ctor.linkage = 'internal'
|
||||
cb: ir.IRBuilder = ir.IRBuilder(ctor.append_basic_block(name='entry'))
|
||||
cb.call(set_handler, [cb.bitcast(handler, i8p), ir.Constant(i32, 1)])
|
||||
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),
|
||||
])
|
||||
existing: Any = self.module.globals.get('llvm.global_ctors')
|
||||
if existing is None:
|
||||
arr_ty: ir.ArrayType = ir.ArrayType(elem_ty, 1)
|
||||
gv: ir.GlobalVariable = ir.GlobalVariable(self.module, arr_ty, name='llvm.global_ctors')
|
||||
gv.linkage = 'appending'
|
||||
gv.initializer = ir.Constant(arr_ty, [entry_val])
|
||||
return
|
||||
value_ty: Any = getattr(existing, 'value_type', None)
|
||||
if (not isinstance(existing, ir.GlobalVariable)
|
||||
or existing.linkage != 'appending'
|
||||
or not isinstance(value_ty, ir.ArrayType)
|
||||
or value_ty.element != elem_ty
|
||||
or existing.initializer is None):
|
||||
raise RuntimeError(
|
||||
'--hotspot-sites: 模块内已存在不兼容的 @llvm.global_ctors,'
|
||||
'无法注册调用点热点处理器(不静默降级)')
|
||||
old_entries: list = list(existing.initializer.constant)
|
||||
new_ty: ir.ArrayType = ir.ArrayType(elem_ty, len(old_entries) + 1)
|
||||
existing.type = ir.PointerType(new_ty)
|
||||
existing.value_type = new_ty
|
||||
existing.initializer = ir.Constant(new_ty, old_entries + [entry_val])
|
||||
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" 打开一次 <path> 清空历史计数,再注册 Ctrl+C
|
||||
处理器(程序启动期各执行一次),与 _InstallFuncHotspot 同法。仅在
|
||||
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))
|
||||
defs, records = self._EmitHotspotSiteCounters()
|
||||
handler: ir.Function = self._CreateHotspotSitesHandler(
|
||||
path, records, module_id=self.module_sha1 or None)
|
||||
main_func: ir.Function | None = self._InstallFuncHotspotTruncate(
|
||||
defs, path, require_main=True, flag='--hotspot-sites')
|
||||
if main_func is None:
|
||||
raise RuntimeError(
|
||||
'--hotspot-sites: 找不到 main 函数定义,无法注册 Ctrl+C 处理器')
|
||||
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]))
|
||||
mb.call(set_handler, [mb.bitcast(handler, i8p), ir.Constant(i32, 1)])
|
||||
|
||||
def _InstallHotspotSitesModuleLocal(self) -> None:
|
||||
"""安装调用点热点统计(--hotspot-sites):Projectrans 多模块路径。
|
||||
|
||||
每模块自带 internal 调用点计数器 + internal 处理器(统一追加写最终 <path>),
|
||||
并各自通过 @llvm.global_ctors 在进程启动期注册 SetConsoleCtrlHandler
|
||||
(见 _RegisterHotspotSitesViaCtor;若 --func-hotspot 已建该全局则追加复用)。
|
||||
仅含 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()
|
||||
handler: ir.Function = self._CreateHotspotSitesHandler(
|
||||
path, records, module_id=module_id or None)
|
||||
self._RegisterHotspotSitesViaCtor(handler)
|
||||
# 非 main 模块无 main、不需要截断(返回 None 不报错):其它模块的写入
|
||||
# 依赖含 main 模块在启动期清空,不会互相覆盖。
|
||||
self._InstallFuncHotspotTruncate(defs, path, flag='--hotspot-sites')
|
||||
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')
|
||||
@@ -76,18 +76,14 @@ class BaseTranslatorMixin:
|
||||
self.config: Any = None
|
||||
self._ImportedModules: set[str] | None = None
|
||||
self._t_c_imported_names: dict = {}
|
||||
# 函数热点统计(--func-hotspot):None=未启用(零影响),否则为计数器落盘文件路径
|
||||
self.FuncHotspotFile: str | None = None
|
||||
# 注册策略:False=种子 CLI 单模块(在 main 入口注册);True=Projectrans 多模块
|
||||
# (每模块 @llvm.global_ctors 自注册,见 BaseGen._InstallFuncHotspotModuleLocal)
|
||||
self.FuncHotspotModuleLocal: bool = False
|
||||
# 调用点热点统计(--hotspot-sites):None=未启用(零影响),否则为调用点计数落盘文件路径
|
||||
self.HotspotSitesFile: str | None = None
|
||||
# 是否对全部直接 call 点插桩(忽略白名单;体积/开销大)
|
||||
self.HotspotSitesAll: bool = False
|
||||
# 白名单被调函数基名列表;None=使用默认内存/字符串族白名单
|
||||
self.HotspotCallees: list[str] | None = None
|
||||
# 注册策略:与 FuncHotspotModuleLocal 同理(True=Projectrans 多模块 ctor 自注册)
|
||||
# 注册策略:False=种子 CLI 单模块(在 main 入口注册);True=Projectrans
|
||||
# 多模块(每模块 @llvm.global_ctors 自注册,见 BaseGen._InstallHotspotSitesModuleLocal)
|
||||
self.HotspotSitesModuleLocal: bool = False
|
||||
|
||||
# 导出表
|
||||
|
||||
@@ -228,19 +228,10 @@ class LlvmGeneratorMixin:
|
||||
Gen._set_Vtable_initializers()
|
||||
# 执行 DecoratorPass:为带自定义装饰器的函数生成 wrapper
|
||||
_run_decorator_pass(Gen)
|
||||
# 函数热点统计(--func-hotspot):在装饰器 wrapper 生成之后统一收口,
|
||||
# 为模块内所有函数定义安装计数器与 Ctrl+C 处理器;未启用时不执行(零影响)。
|
||||
# func_hotspot_module_local=True 走 Projectrans 多模块路径(每模块
|
||||
# @llvm.global_ctors 自注册);否则走种子 CLI 单模块路径(main 入口注册)。
|
||||
if Gen.func_hotspot_file:
|
||||
if Gen.func_hotspot_module_local:
|
||||
Gen._InstallFuncHotspotModuleLocal()
|
||||
else:
|
||||
Gen._InstallFuncHotspot()
|
||||
# 调用点热点统计(--hotspot-sites):在函数热点之后装,保证本功能不会把
|
||||
# --func-hotspot 生成的 fopen/fprintf/fclose/SetConsoleCtrlHandler 调用点
|
||||
# 计入(再加上本功能自身的处理器/构造器晚于扫描生成,天然不自计数);
|
||||
# 多模块路径下 --func-hotspot 已建的 @llvm.global_ctors 由本功能追加复用。
|
||||
# 调用点热点统计与每函数计时(--hotspot-sites):在装饰器 wrapper 生成之后
|
||||
# 统一收口;未启用时不执行(零影响)。hotspot_sites_module_local=True 走
|
||||
# Projectrans 多模块路径(每模块 @llvm.global_ctors 自注册),否则走种子
|
||||
# CLI 单模块路径(在 main 入口注册)。
|
||||
if Gen.hotspot_sites_file:
|
||||
if Gen.hotspot_sites_module_local:
|
||||
Gen._InstallHotspotSitesModuleLocal()
|
||||
@@ -665,10 +656,8 @@ class LlvmGeneratorMixin:
|
||||
self.LlvmGen._temp_dir = self._temp_dir
|
||||
if hasattr(self, '_export_extern_funcs'):
|
||||
self.LlvmGen._export_extern_funcs = self._export_extern_funcs
|
||||
# 函数热点统计(--func-hotspot):透传落盘路径与注册策略;未启用时不产生任何 IR 改动
|
||||
self.LlvmGen.func_hotspot_file = getattr(self, 'FuncHotspotFile', None)
|
||||
self.LlvmGen.func_hotspot_module_local = getattr(self, 'FuncHotspotModuleLocal', False)
|
||||
# 调用点热点统计(--hotspot-sites):同上;未启用时不产生任何 IR 改动
|
||||
# 调用点热点统计与每函数计时(--hotspot-sites):透传落盘路径与注册策略;
|
||||
# 未启用时不产生任何 IR 改动
|
||||
self.LlvmGen.hotspot_sites_file = getattr(self, 'HotspotSitesFile', None)
|
||||
self.LlvmGen.hotspot_sites_all = getattr(self, 'HotspotSitesAll', False)
|
||||
self.LlvmGen.hotspot_callees = getattr(self, 'HotspotCallees', None)
|
||||
|
||||
Reference in new issue
Block a user