From 136e9bc33ed51d8bce33ba773b1b64bd24388b00 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Mon, 14 Sep 2026 14:08:55 +0800 Subject: [PATCH] =?UTF-8?q?feat(hotspot):=20=E7=83=AD=E7=82=B9=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E5=9C=A8=E8=BF=9B=E7=A8=8B=E9=80=80=E5=87=BA=E6=97=B6?= =?UTF-8?q?=E4=B9=9F=E8=90=BD=E7=9B=98=EF=BC=88atexit=20+=20=E5=B9=82?= =?UTF-8?q?=E7=AD=89=E5=AE=88=E5=8D=AB=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 此前 --hotspot-sites 仅由 SetConsoleCtrlHandler 在 Ctrl+C 时落盘,导致正常退出与 fail-fast(VLogger.error 走 sys.exit(1),底层是 extern exit() 即 libc exit)都拿不到热点数据。 现把两个 handler 主体抽为 void() 落盘函数(__fhs_dump_sites/__fhs_dump_time),新增 __fhs_dump_all 与每模块 internal i32 __fhs_dumped 幂等守卫,并在 ctor/main 入口同时注册 SetConsoleCtrlHandler 与 atexit;Ctrl+C 处理器仍返回 FALSE(0) 保持原有终止行为。同模块只落盘一次,不产生重复段。 覆盖:正常 return、sys.exit(1) fail-fast、Ctrl+C。不覆盖(已在报告中列明):_exit、abort、崩溃信号、被强杀。不启用开关时 IR 逐字节不变;两个输出文件格式与排序不变。 --- lib/core/LLVMCG/BaseGen.py | 258 ++++++++++++++++++++++++------------- 1 file changed, 165 insertions(+), 93 deletions(-) diff --git a/lib/core/LLVMCG/BaseGen.py b/lib/core/LLVMCG/BaseGen.py index 0e94c02..c8009f6 100644 --- a/lib/core/LLVMCG/BaseGen.py +++ b/lib/core/LLVMCG/BaseGen.py @@ -1130,7 +1130,7 @@ class BaseGenMixin: inc 里,因而会计入父帧的 **self**(见下方说明)。 返回记录列表 (自身耗时全局, 包含式耗时全局, 进入次数全局, 函数名),顺序即 - N 的编号顺序。落盘由 _CreateHotspotTimeHandler 完成(按 self 降序)。 + N 的编号顺序。落盘由 _CreateHotspotTimeDump 完成(按 self 降序)。 固有性质(务必据此解读数值):**未插桩的被调方**(间接调用、无定义体的外部 函数如 strcmp/memcpy、名字以 __fhs_ 开头的辅助函数、深度溢出而跳过的调用) @@ -1232,13 +1232,13 @@ class BaseGenMixin: records.append((self_gv, inc_gv, enter_gv, func.name)) return records - def _CreateHotspotSitesHandler( + def _CreateHotspotSitesDump( self, path: str, records: list[tuple[ir.GlobalVariable, str, str, str, int]], module_id: str | None = None, ) -> ir.Function: - """生成 Win32 控制台 Ctrl+C 处理器:按调用次数降序把各调用点追加写入 。 + """生成 void() 落盘函数:按调用次数降序把各调用点追加写入 。 落盘格式(追加): # sites \\n @@ -1246,14 +1246,13 @@ class BaseGenMixin: module_id 为空时省略行首 sha1 与头部 sha1(留空,不伪造)。 - 排序:落盘前用选择排序(两层运行期循环,IR 规模 O(N)、运行期 O(N^2) 仅 - 在 Ctrl+C 触发一次)按计数降序输出,只输出 count>0 的调用点;计数相同时 + 在落盘时触发一次)按计数降序输出,只输出 count>0 的调用点;计数相同时 按计数器编号 K 升序(稳定、可复现)。 - 源码位置:作为可选尾字段,格式 ":"(含前置单个空格);该位置 来自生成期挂在 call 指令对象上的 _src_file/_src_line(见 _HotspotSiteBuilder)。 生成期无法关联到 AST 的合成调用点取不到位置,则此字段整体省略(不伪造、不填默认值)。 - - 处理器签名为 Win32 PHANDLER_ROUTINE(i32 (i32) -> BOOL),用 CRT - fopen("a")/fprintf/fclose 落盘(沿用既有做法),返回 FALSE(0) 让默认 - 处理继续,使 Ctrl+C 仍能终止程序。 + - 函数签名为 void(),由 __fhs_dump_all(见 _CreateHotspotDumpAll)统一带守卫 + 调用,本函数自身不检查守卫。用 CRT fopen("a")/fprintf/fclose 落盘(沿用既有做法)。 """ n: int = len(records) i8: ir.IntType = ir.IntType(8) @@ -1270,16 +1269,16 @@ class BaseGenMixin: fprintf: ir.Function = self._DeclareExternalFunc( 'fprintf', ir.FunctionType(i32, [i8p, i8p], var_arg=True)) fclose: ir.Function = self._DeclareExternalFunc('fclose', ir.FunctionType(i32, [i8p])) - handler: ir.Function = ir.Function( - self.module, ir.FunctionType(i32, [i32]), - name=self._UniqueGlobalName('__fhs_sites_handler')) - handler.linkage = 'internal' - b: ir.IRBuilder = ir.IRBuilder(handler.append_basic_block(name='entry')) - b.position_at_start(handler.entry_basic_block) - # 无匹配调用点:生成空处理器(不落盘、不建数组),仅占位以保持注册链路一致 + dump_fn: ir.Function = ir.Function( + self.module, ir.FunctionType(ir.VoidType(), []), + name=self._UniqueGlobalName('__fhs_dump_sites')) + dump_fn.linkage = 'internal' + b: ir.IRBuilder = ir.IRBuilder(dump_fn.append_basic_block(name='entry')) + b.position_at_start(dump_fn.entry_basic_block) + # 无匹配调用点:生成空落盘函数(不落盘、不建数组),保持调用链一致 if n == 0: - b.ret(ir.Constant(i32, 0)) - return handler + b.ret_void() + return dump_fn path_ptr: ir.Constant = self._create_string_global( path, name=self._UniqueGlobalName('__fhs_sites_path')) mode_ptr: ir.Constant = self._create_string_global( @@ -1348,16 +1347,16 @@ class BaseGenMixin: i_ptr: ir.Value = b.alloca(i64, name='fhs_sites_i') b.store(c0_64, k_ptr) # 选择排序:外层 k 走 0..n-1,每轮在未输出项里选计数最大者(计数相同取 K 小者) - loop_k: ir.Block = handler.append_basic_block(name='fhs_sites_sort_k') - body_k: ir.Block = handler.append_basic_block(name='fhs_sites_sort_k_body') - loop_i: ir.Block = handler.append_basic_block(name='fhs_sites_sort_i') - body_i: ir.Block = handler.append_basic_block(name='fhs_sites_sort_i_body') - scan_i: ir.Block = handler.append_basic_block(name='fhs_sites_sort_i_scan') - upd_i: ir.Block = handler.append_basic_block(name='fhs_sites_sort_i_upd') - next_i: ir.Block = handler.append_basic_block(name='fhs_sites_sort_i_next') - end_i: ir.Block = handler.append_basic_block(name='fhs_sites_sort_i_end') - print_k: ir.Block = handler.append_basic_block(name='fhs_sites_print_k') - after_k: ir.Block = handler.append_basic_block(name='fhs_sites_sort_end') + loop_k: ir.Block = dump_fn.append_basic_block(name='fhs_sites_sort_k') + body_k: ir.Block = dump_fn.append_basic_block(name='fhs_sites_sort_k_body') + loop_i: ir.Block = dump_fn.append_basic_block(name='fhs_sites_sort_i') + body_i: ir.Block = dump_fn.append_basic_block(name='fhs_sites_sort_i_body') + scan_i: ir.Block = dump_fn.append_basic_block(name='fhs_sites_sort_i_scan') + upd_i: ir.Block = dump_fn.append_basic_block(name='fhs_sites_sort_i_upd') + next_i: ir.Block = dump_fn.append_basic_block(name='fhs_sites_sort_i_next') + end_i: ir.Block = dump_fn.append_basic_block(name='fhs_sites_sort_i_end') + print_k: ir.Block = dump_fn.append_basic_block(name='fhs_sites_print_k') + after_k: ir.Block = dump_fn.append_basic_block(name='fhs_sites_sort_end') b.branch(loop_k) # loop_k: if k < n -> body_k else after_k b.position_at_end(loop_k) @@ -1412,19 +1411,19 @@ class BaseGenMixin: name='fhs_sites_write') b.store(b.add(b.load(k_ptr), c1_64), k_ptr) b.branch(loop_k) - # after_k: fclose; return 0 + # after_k: fclose; ret_void b.position_at_end(after_k) b.call(fclose, [fp]) - b.ret(ir.Constant(i32, 0)) - return handler + b.ret_void() + return dump_fn - def _CreateHotspotTimeHandler( + def _CreateHotspotTimeDump( self, path: str, records: list[tuple[ir.GlobalVariable, ir.GlobalVariable, ir.GlobalVariable, str]], module_id: str | None = None, ) -> ir.Function: - """生成 Win32 控制台 Ctrl+C 处理器:按**自身耗时**降序写入每函数计时文件。 + """生成 void() 落盘函数:按**自身耗时**降序写入每函数计时文件。 落盘格式(追加): # time \\n @@ -1438,9 +1437,8 @@ class BaseGenMixin: - 排序:与调用点计数同法,落盘前用选择排序按 self_tick 降序输出,只输出 进入次数 >0 的函数;self_tick 相同时按编号 N 升序(稳定、可复现)。 - - 处理器签名为 Win32 PHANDLER_ROUTINE(i32 (i32) -> BOOL),用 CRT - fopen("a")/fprintf/fclose 落盘(沿用既有做法),返回 FALSE(0) 让默认 - 处理继续,使 Ctrl+C 仍能终止程序。 + - 函数签名为 void(),由 __fhs_dump_all(见 _CreateHotspotDumpAll)统一带守卫 + 调用,本函数自身不检查守卫。用 CRT fopen("a")/fprintf/fclose 落盘(沿用既有做法)。 """ n: int = len(records) i8: ir.IntType = ir.IntType(8) @@ -1457,16 +1455,16 @@ class BaseGenMixin: 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) - # 无计时对象(模块内无用户函数定义):生成空处理器占位,保持注册链路一致 + dump_fn: ir.Function = ir.Function( + self.module, ir.FunctionType(ir.VoidType(), []), + name=self._UniqueGlobalName('__fhs_dump_time')) + dump_fn.linkage = 'internal' + b: ir.IRBuilder = ir.IRBuilder(dump_fn.append_basic_block(name='entry')) + b.position_at_start(dump_fn.entry_basic_block) + # 无计时对象(模块内无用户函数定义):生成空落盘函数占位,保持调用链一致 if n == 0: - b.ret(ir.Constant(i32, 0)) - return handler + b.ret_void() + return dump_fn path_ptr: ir.Constant = self._create_string_global( path, name=self._UniqueGlobalName('__fhs_ftime_path')) mode_ptr: ir.Constant = self._create_string_global( @@ -1525,16 +1523,16 @@ class BaseGenMixin: i_ptr: ir.Value = b.alloca(i64, name='fhs_ftime_i') b.store(c0_64, k_ptr) # 选择排序:外层 k 走 0..n-1,每轮在未输出项里选自身耗时(self)最大者(相同取 N 小者) - loop_k: ir.Block = 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') + loop_k: ir.Block = dump_fn.append_basic_block(name='fhs_ftime_sort_k') + body_k: ir.Block = dump_fn.append_basic_block(name='fhs_ftime_sort_k_body') + loop_i: ir.Block = dump_fn.append_basic_block(name='fhs_ftime_sort_i') + body_i: ir.Block = dump_fn.append_basic_block(name='fhs_ftime_sort_i_body') + scan_i: ir.Block = dump_fn.append_basic_block(name='fhs_ftime_sort_i_scan') + upd_i: ir.Block = dump_fn.append_basic_block(name='fhs_ftime_sort_i_upd') + next_i: ir.Block = dump_fn.append_basic_block(name='fhs_ftime_sort_i_next') + end_i: ir.Block = dump_fn.append_basic_block(name='fhs_ftime_sort_i_end') + print_k: ir.Block = dump_fn.append_basic_block(name='fhs_ftime_print_k') + after_k: ir.Block = dump_fn.append_basic_block(name='fhs_ftime_sort_end') b.branch(loop_k) b.position_at_end(loop_k) k_val: ir.Value = b.load(k_ptr) @@ -1581,36 +1579,112 @@ class BaseGenMixin: 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 + # after_k: fclose; ret_void b.position_at_end(after_k) b.call(fclose, [fp]) - b.ret(ir.Constant(i32, 0)) - return handler + b.ret_void() + return dump_fn - def _RegisterHotspotSitesViaCtor(self, handlers: list[ir.Function]) -> None: - """用 @llvm.global_ctors 在进程启动期为本模块注册全部热点处理器。 + def _CreateHotspotDumpAll( + self, + sites_dump: ir.Function, + time_dump: ir.Function, + ) -> tuple[ir.Function, ir.Function]: + """生成模块级"总落盘"链路:幂等守卫 + void() 总落盘函数 + Ctrl+C 处理器。 - 生成 internal void @__fhs_sites_ctor()(内部逐个调用 SetConsoleCtrlHandler - 注册 handlers:调用点计数处理器与每函数计时处理器),并写入 appending - linkage 的 @llvm.global_ctors —— 每个模块一份,链接器按 appending 语义把 - 各模块的构造器拼接进 .init_array,进程启动时各执行一次。相比"在 main 入口 - 注册",本方案不依赖 main 所在模块,适配 Projectrans 的"每源文件独立翻译 - 单元"模型。 + 同一模块**只落盘一次**:Ctrl+C 与 atexit 可能先后都触发(例如处理器返回后 + 进程继续退出),故用每模块一份的 internal i32 @__fhs_dumped(初值 0)作幂等 + 守卫: + - void @__fhs_dump_all():先读守卫,非 0 直接返回;否则置 1 后依次调用 + sites_dump(调用点计数)与 time_dump(每函数计时)——两个落盘函数本身 + 不检查守卫; + - i32 @__fhs_ctl_handler(i32):直接调用 __fhs_dump_all() 后返回 FALSE(0), + 供 SetConsoleCtrlHandler 注册(保持"仍让进程终止"的既有行为)。 + + atexit 与 Ctrl+C 共用同一守卫,先到者落盘、后到者直接返回,避免重复段/行。 + 返回 (Ctrl+C 处理器, 总落盘函数)。 + """ + i32: ir.IntType = ir.IntType(32) + guard: ir.GlobalVariable = ir.GlobalVariable( + self.module, i32, name=self._UniqueGlobalName('__fhs_dumped')) + guard.linkage = 'internal' + guard.initializer = ir.Constant(i32, 0) + dump_all: ir.Function = ir.Function( + self.module, ir.FunctionType(ir.VoidType(), []), + name=self._UniqueGlobalName('__fhs_dump_all')) + dump_all.linkage = 'internal' + b: ir.IRBuilder = ir.IRBuilder(dump_all.append_basic_block(name='entry')) + do_bb: ir.Block = dump_all.append_basic_block(name='fhs_dump_do') + done_bb: ir.Block = dump_all.append_basic_block(name='fhs_dump_done') + b.position_at_start(dump_all.entry_basic_block) + cur: ir.Value = b.load(guard, name='fhs_dumped_load') + b.cbranch(b.icmp_signed('!=', cur, ir.Constant(i32, 0)), done_bb, do_bb) + b.position_at_end(do_bb) + b.store(ir.Constant(i32, 1), guard) + b.call(sites_dump, []) + b.call(time_dump, []) + b.branch(done_bb) + b.position_at_end(done_bb) + b.ret_void() + ctl_handler: ir.Function = ir.Function( + self.module, ir.FunctionType(i32, [i32]), + name=self._UniqueGlobalName('__fhs_ctl_handler')) + ctl_handler.linkage = 'internal' + cb: ir.IRBuilder = ir.IRBuilder(ctl_handler.append_basic_block(name='entry')) + cb.call(dump_all, []) + cb.ret(ir.Constant(i32, 0)) + return ctl_handler, dump_all + + def _EmitHotspotDumpRegistration( + self, + builder: ir.IRBuilder, + ctl_handler: ir.Function, + dump_all: ir.Function, + ) -> None: + """向给定构建器发射两处落盘触发注册: + + - SetConsoleCtrlHandler(bitcast @__fhs_ctl_handler, i8*), TRUE):Ctrl+C 路径; + - atexit(bitcast @__fhs_dump_all, i8*)):正常返回 / exit() 退出路径。 + + 二者共用 __fhs_dumped 幂等守卫(见 _CreateHotspotDumpAll),任一先触发即落盘, + 后触发者直接返回,不重复写段/行。atexit 声明沿用本文件既有外部函数写法(见 + fopen/SetConsoleCtrlHandler:ir.FunctionType + ir.PointerType(ir.IntType(8))); + 本仓库 IR 为 typed pointer(函数指针统一以 i8* 传递),故以 bitcast 取函数地址。 + 返回值为 int,此处有意忽略(拿不到 atexit 会在链接期报错,不静默降级)。 + + 覆盖 libc exit()(含正常 return 与 sys.exit 的 fail-fast);不覆盖 _exit()/ + abort()/TerminateProcess/崩溃信号等不经过 exit() 的终止路径。 + """ + i32: ir.IntType = ir.IntType(32) + i8p: ir.PointerType = ir.PointerType(ir.IntType(8)) + set_handler: ir.Function = self._DeclareExternalFunc( + 'SetConsoleCtrlHandler', ir.FunctionType(i32, [i8p, i32])) + atexit_fn: ir.Function = self._DeclareExternalFunc( + 'atexit', ir.FunctionType(i32, [i8p])) + builder.call(set_handler, [builder.bitcast(ctl_handler, i8p), ir.Constant(i32, 1)]) + builder.call(atexit_fn, [builder.bitcast(dump_all, i8p)]) + + def _RegisterHotspotSitesViaCtor(self, ctl_handler: ir.Function, dump_all: ir.Function) -> None: + """用 @llvm.global_ctors 在进程启动期为本模块注册热点落盘触发。 + + 生成 internal void @__fhs_sites_ctor()(内部调用 SetConsoleCtrlHandler 注册 + Ctrl+C 处理器 __fhs_ctl_handler,并调用 atexit 注册退出处理器 __fhs_dump_all), + 并写入 appending linkage 的 @llvm.global_ctors —— 每个模块一份,链接器按 + appending 语义把各模块的构造器拼接进 .init_array,进程启动时各执行一次。相比 + "在 main 入口注册",本方案不依赖 main 所在模块,适配 Projectrans 的"每源文件 + 独立翻译单元"模型。 模块内若已存在 @llvm.global_ctors 视为冲突,显式报错(不静默降级)。 """ i32: ir.IntType = ir.IntType(32) i8p: ir.PointerType = ir.PointerType(ir.IntType(8)) void_fnptr: ir.PointerType = ir.PointerType(ir.FunctionType(ir.VoidType(), [])) - set_handler: ir.Function = self._DeclareExternalFunc( - 'SetConsoleCtrlHandler', ir.FunctionType(i32, [i8p, i32])) ctor: ir.Function = ir.Function( self.module, ir.FunctionType(ir.VoidType(), []), name=self._UniqueGlobalName('__fhs_sites_ctor')) ctor.linkage = 'internal' cb: ir.IRBuilder = ir.IRBuilder(ctor.append_basic_block(name='entry')) - for h in handlers: - cb.call(set_handler, [cb.bitcast(h, i8p), ir.Constant(i32, 1)]) + self._EmitHotspotDumpRegistration(cb, ctl_handler, dump_all) cb.ret_void() if 'llvm.global_ctors' in self.module.globals: raise RuntimeError( @@ -1633,48 +1707,45 @@ class BaseGenMixin: 顺序:先装调用点计数(其扫描只针对用户代码,不会把随后插入的计时原语 调用计入),再装每函数计时;随后在 main 入口用 "w" 各清空一次调用点计数 - 文件与计时文件,并注册全部 Ctrl+C 处理器(程序启动期各执行一次)。仅在 - hotspot_sites_file 非 None(用户传入 --hotspot-sites)时被调用;未启用时 - 该方法不会执行,IR 输出与既有行为一致(零影响)。 + 文件与计时文件,并注册落盘触发(Ctrl+C 处理器 SetConsoleCtrlHandler + + 退出处理器 atexit,程序启动期各执行一次)。仅在 hotspot_sites_file 非 None + (用户传入 --hotspot-sites)时被调用;未启用时该方法不会执行,IR 输出与 + 既有行为一致(零影响)。 """ path: str | None = self.hotspot_sites_file if not path: raise RuntimeError('--hotspot-sites: 需要提供调用点计数输出文件路径') - 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), - ] + sites_dump: ir.Function = self._CreateHotspotSitesDump( + path, records, module_id=module_id) + time_dump: ir.Function = self._CreateHotspotTimeDump( + time_path, time_records, module_id=module_id) + ctl_handler, dump_all = self._CreateHotspotDumpAll(sites_dump, time_dump) # main 入口各清空一次历史文件(require_main=True → 无 main 显式报错) main_func: ir.Function | None = self._InstallHotspotTruncate( defs, path, require_main=True, flag='--hotspot-sites') if main_func is None: raise RuntimeError( - '--hotspot-sites: 找不到 main 函数定义,无法注册 Ctrl+C 处理器') + '--hotspot-sites: 找不到 main 函数定义,无法注册热点落盘触发') self._InstallHotspotTruncate( defs, time_path, require_main=True, flag='--hotspot-sites') - # 在 main 入口注册全部处理器(清空/截断之后、程序 body 之前执行) + # 在 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)]) + self._EmitHotspotDumpRegistration(mb, ctl_handler, dump_all) def _InstallHotspotSitesModuleLocal(self) -> None: """安装调用点热点统计(--hotspot-sites):Projectrans 多模块路径。 - 每模块自带 internal 调用点计数器 + internal 每函数计时器 + internal 处理器 + 每模块自带 internal 调用点计数器 + internal 每函数计时器 + internal 落盘函数 (调用点计数追加写最终 ,计时追加写同目录 hotspot_time.txt),并各自 - 通过 @llvm.global_ctors 在进程启动期注册 SetConsoleCtrlHandler - (见 _RegisterHotspotSitesViaCtor)。仅含 main 的模块额外在 main 入口以 "w" - 各清空一次文件,保证"一次运行一份结果"。未启用(hotspot_sites_file 为 None) - 时本方法不会被调用。 + 通过 @llvm.global_ctors 在进程启动期注册落盘触发:Ctrl+C 处理器 + (SetConsoleCtrlHandler) 与退出处理器 (atexit)(见 _RegisterHotspotSitesViaCtor)。 + 仅含 main 的模块额外在 main 入口以 "w" 各清空一次文件,保证"一次运行一份结果"。 + 未启用(hotspot_sites_file 为 None)时本方法不会被调用。 """ path: str | None = self.hotspot_sites_file if not path: @@ -1683,11 +1754,12 @@ class BaseGenMixin: 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) + sites_dump: ir.Function = self._CreateHotspotSitesDump( + path, records, module_id=module_id or None) + time_dump: ir.Function = self._CreateHotspotTimeDump( + time_path, time_records, module_id=module_id or None) + ctl_handler, dump_all = self._CreateHotspotDumpAll(sites_dump, time_dump) + self._RegisterHotspotSitesViaCtor(ctl_handler, dump_all) # 非 main 模块无 main、不需要截断(返回 None 不报错):其它模块的写入 # 依赖含 main 模块在启动期清空,不会互相覆盖。 self._InstallHotspotTruncate(defs, path, flag='--hotspot-sites')