From fc5a6134caad08287e0121d3f1f5909ac8390e73 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Mon, 14 Sep 2026 13:10:06 +0800 Subject: [PATCH] =?UTF-8?q?feat(hotspot)!:=20=E4=B8=8B=E7=BA=BF=20--func-h?= =?UTF-8?q?otspot=20=E5=B9=B6=E4=B8=BA=20--hotspot-sites=20=E5=A2=9E?= =?UTF-8?q?=E5=8A=A0=E6=AF=8F=E5=87=BD=E6=95=B0=E8=AE=A1=E6=97=B6=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 移除 --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 分段 + 函数名/累计 tick/进入次数),不改变 hotspot_sites.txt 的既有格式与字段。不传 --hotspot-sites 时零影响。 --- lib/Projectrans/Config.py | 16 +- lib/Projectrans/Phase2Translator.py | 46 +- lib/Shell/TransPyC.py | 16 +- lib/constants/config.py | 8 +- lib/core/LLVMCG/BaseGen.py | 598 ++++++++++++++------------ lib/core/Translator/BaseTranslator.py | 8 +- lib/core/Translator/LlvmGenerator.py | 23 +- 7 files changed, 359 insertions(+), 356 deletions(-) diff --git a/lib/Projectrans/Config.py b/lib/Projectrans/Config.py index c9efbb7..f3bb9aa 100644 --- a/lib/Projectrans/Config.py +++ b/lib/Projectrans/Config.py @@ -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 各模块以追加方式把计数写入 (含 main 的模块在启动时清空一次,保证每次运行一份结果)(可用 python Projectrans.py --project ... --func-hotspot )') - parser.add_argument('--hotspot-sites', nargs='?', const='', default=None, help='调用点热点统计:为每个匹配白名单的直接 call 点插入计数器,运行时 Ctrl+C 各模块按真实次数降序写入 (默认为 hotspot_sites.txt;可独立于 --func-hotspot 使用)') + parser.add_argument('--hotspot-sites', nargs='?', const='', default=None, help='调用点热点统计:为每个匹配白名单的直接 call 点插入计数器,运行时 Ctrl+C 各模块按真实次数降序写入 (默认为 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 diff --git a/lib/Projectrans/Phase2Translator.py b/lib/Projectrans/Phase2Translator.py index cdb402d..44fa4b2 100644 --- a/lib/Projectrans/Phase2Translator.py +++ b/lib/Projectrans/Phase2Translator.py @@ -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 时各模块处理器统一追加写最终 - trans.func_hotspot_file = func_hotspot_file - # 调用点热点统计(--hotspot-sites):同样须显式透传(None=未启用,零影响) + # 调用点热点统计与每函数计时(--hotspot-sites):worker 子进程无父进程属性, + # 须显式透传(None=未启用,零影响);生成 IR 时各模块处理器统一追加写最终 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):批内每文件的处理器统一追加写最终 - 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 多模块): - # 各模块处理器统一以追加方式写最终 ;注册策略为本模块 - # @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 多模块): + # 各模块处理器统一以追加方式写最终 ,注册策略为本模块 + # @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 diff --git a/lib/Shell/TransPyC.py b/lib/Shell/TransPyC.py index 451c0df..074bd5c 100644 --- a/lib/Shell/TransPyC.py +++ b/lib/Shell/TransPyC.py @@ -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 == '': diff --git a/lib/constants/config.py b/lib/constants/config.py index 511bc32..579fd38 100644 --- a/lib/constants/config.py +++ b/lib/constants/config.py @@ -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 点插桩(忽略白名单,体积/开销大) ''' diff --git a/lib/core/LLVMCG/BaseGen.py b/lib/core/LLVMCG/BaseGen.py index 847ce76..b7b4b3a 100644 --- a/lib/core/LLVMCG/BaseGen.py +++ b/lib/core/LLVMCG/BaseGen.py @@ -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 落盘:多模块各自持有处理器,运行时各 - 自以追加方式写同一个 ,互不覆盖(见 _InstallFuncHotspotModuleLocal) - - module_id 非空时首行先写 "# module \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":多模块多处理器各自追加到同一个 ,互不覆盖 - 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")方式写 ,若不清空会 - 累积历史运行的数据。清空必须满足两点: + --hotspot-sites 的各处理器(调用点计数、每函数计时)以追加("a")方式写 + ,若不清空会累积历史运行的数据。清空必须满足两点: - 只在含 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" 打开一次 清空历史计数,再注册 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 处理器(统一追加写最终 ), - 并各自通过 @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_;同时用 internal i64 __fhs_ftime_enter_ + 统计该函数被进入次数,便于交叉校验。 + + 返回记录列表 (累计耗时全局, 进入次数全局, 函数名),顺序即 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_ + 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 \\n + \\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" 打开一次 清空历史计数,再注册 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 处理器(统一追加写最终 ), - 并各自通过 @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') \ No newline at end of file + 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 处理器 + (调用点计数追加写最终 ,计时追加写同目录 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') \ No newline at end of file diff --git a/lib/core/Translator/BaseTranslator.py b/lib/core/Translator/BaseTranslator.py index 59dd21c..6d16f5d 100644 --- a/lib/core/Translator/BaseTranslator.py +++ b/lib/core/Translator/BaseTranslator.py @@ -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 # 导出表 diff --git a/lib/core/Translator/LlvmGenerator.py b/lib/core/Translator/LlvmGenerator.py index e5a6c3c..bf82ffd 100644 --- a/lib/core/Translator/LlvmGenerator.py +++ b/lib/core/Translator/LlvmGenerator.py @@ -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)