feat(hotspot): 热点日志在进程退出时也落盘(atexit + 幂等守卫)

此前 --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 逐字节不变;两个输出文件格式与排序不变。
This commit is contained in:
TermiNexus committed 2026-09-14 14:08:55 +08:00
1 parent 84d91d53e5
commit 136e9bc33e
1 file changed
+165 -93
+165 -93
View File
@@ -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 处理器:按调用次数降序把各调用点追加写入 <path>。
"""生成 void() 落盘函数:按调用次数降序把各调用点追加写入 <path>。
落盘格式(追加):
# sites <module_sha1>\\n
@@ -1246,14 +1246,13 @@ class BaseGenMixin:
module_id 为空时省略行首 sha1 与头部 sha1(留空,不伪造)。
- 排序:落盘前用选择排序(两层运行期循环,IR 规模 O(N)、运行期 O(N^2) 仅
在 Ctrl+C 触发一次)按计数降序输出,只输出 count>0 的调用点;计数相同时
在落盘时触发一次)按计数降序输出,只输出 count>0 的调用点;计数相同时
按计数器编号 K 升序(稳定、可复现)。
- 源码位置:作为可选尾字段,格式 "<file>:<line>"(含前置单个空格);该位置
来自生成期挂在 call 指令对象上的 _src_file/_src_line(见 _HotspotSiteBuilder)。
生成期无法关联到 AST 的合成调用点取不到位置,则此字段整体省略(不伪造、不填默认值)。
- 处理器签名为 Win32 PHANDLER_ROUTINE(i32 (i32) -> BOOL),用 CRT
fopen("a")/fprintf/fclose 落盘(沿用既有做法),返回 FALSE(0) 让默认
处理继续,使 Ctrl+C 仍能终止程序。
- 函数签名为 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 <module_sha1>\\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 落盘函数
(调用点计数追加写最终 <path>,计时追加写同目录 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')