From 84d91d53e5e9e2ef2e3c048cb2526b2218e256f1 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Mon, 14 Sep 2026 13:42:14 +0800 Subject: [PATCH] =?UTF-8?q?perf(hotspot):=20=E8=AE=A1=E6=97=B6=E4=B8=B2?= =?UTF-8?q?=E8=A1=8C=E5=8C=96=E5=B9=B6=E6=94=B9=E4=B8=BA=E8=87=AA=E8=BA=AB?= =?UTF-8?q?=E8=80=97=E6=97=B6(exclusive)=E7=BB=9F=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --hotspot-sites 的每函数计时原为裸 rdtsc 的 inclusive 耗时,导致 _parse_* 一条调用链各项数值几乎相同、无法定位自身成本,且短函数测量不可信。 新增 _EmitReadTimestamp:x86_64 用 lfence 加 readcyclecounter 串行化读(业界通行的 lfence;rdtsc),非 x86_64 编译期降级为裸 readcyclecounter;入口与每个 ret 前共用。选 intrinsic 组合而非内联汇编,避免约束串转义与渲染风险。 用深度栈实现自身耗时:全局 __fhs_tdepth 与 [512 x i64] __fhs_tstart/__fhs_tchild,self = inc 减去本帧内被插桩子调用 inc 之和;递归靠每调用独立 slot 与深度索引天然正确;深度溢出或终结指令非 ret 的块明确跳过不记账(仅计进入次数),不伪造数值。入口段用 select 而非条件分支,避免改动 CFG。 hotspot_time.txt 行为 函数名 self inc 进入次数(仍按 self 降序),段头注明单位 tick 与 self 口径;hotspot_sites.txt 格式保持不变;帮助文本同步更新。未插桩的被调方(含 libc)耗时计入调用者 self,属该方法固有性质并已注明。 --- lib/Projectrans/Config.py | 2 +- lib/constants/config.py | 7 +- lib/core/LLVMCG/BaseGen.py | 249 +++++++++++++++++++++++++++---------- 3 files changed, 190 insertions(+), 68 deletions(-) diff --git a/lib/Projectrans/Config.py b/lib/Projectrans/Config.py index f3bb9aa..6288df2 100644 --- a/lib/Projectrans/Config.py +++ b/lib/Projectrans/Config.py @@ -149,7 +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('--hotspot-sites', nargs='?', const='', default=None, help='调用点热点统计:为每个匹配白名单的直接 call 点插入计数器,运行时 Ctrl+C 各模块按真实次数降序写入 (默认为 hotspot_sites.txt);同时为每个函数定义加计时器,按累计耗时降序写入同目录 hotspot_time.txt') + parser.add_argument('--hotspot-sites', nargs='?', const='', default=None, help='调用点热点统计:为每个匹配白名单的直接 call 点插入计数器,运行时 Ctrl+C 各模块按真实次数降序写入 (默认为 hotspot_sites.txt);同时为每个函数定义加计时器,按自身耗时(self)降序写入同目录 hotspot_time.txt,行为 `<函数名> <进入次数>`(单位 tick,x86_64 上为序列化 lfence; rdtsc);self 已扣除被插桩子调用的 inc,但不含未插桩被调方(间接调用、strcmp/memcpy 等无定义外部函数),它们计入调用者的 self') 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 点插桩(忽略白名单;体积/开销大,用于深度排查)') diff --git a/lib/constants/config.py b/lib/constants/config.py index 579fd38..df2c459 100644 --- a/lib/constants/config.py +++ b/lib/constants/config.py @@ -47,8 +47,11 @@ Usage: python TransPyC.py -f InputFile -o OutputFile [-wh header_files] [-debug [--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 --hotspot-sites: 统计各调用点真实运行次数(Ctrl+C 退出时写入 SitesFile,默认 hotspot_sites.txt, - 按计数降序),并为每个函数定义加计时器(累计耗时与进入次数按耗时降序写入同目录 - hotspot_time.txt;耗时单位为 tick = @llvm.readcyclecounter() 计数) + 按计数降序),并为每个函数定义加计时器:自身耗时 self、包含式耗时 inc 与进入次数, + 按 self 降序写入同目录 hotspot_time.txt(行为 `<函数名> <进入次数>`); + 单位 tick(x86_64 上为序列化的 lfence; rdtsc 计数)。self 已扣除本函数内被插桩 + 子调用的 inc,但**不含未插桩的被调方**(间接调用、strcmp/memcpy 等无定义的外部 + 函数、深度溢出跳过的调用),这些仍计入调用者的 self --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 b7b4b3a..0e94c02 100644 --- a/lib/core/LLVMCG/BaseGen.py +++ b/lib/core/LLVMCG/BaseGen.py @@ -1062,68 +1062,174 @@ class BaseGenMixin: """ return os.path.join(os.path.dirname(sites_path), 'hotspot_time.txt') - def _EmitHotspotTimeCounters( - self, - ) -> list[tuple[ir.GlobalVariable, ir.GlobalVariable, str]]: - """为模块内每个函数定义安装"每函数累计耗时"计时器(--hotspot-sites 附带)。 + # 深度栈容量:512。超出该深度的调用不记账(self/inc 均不计),但仍在 + # __fhs_ftime_enter_ 里计入进入次数——溢出是"拿不到数值",明确跳过而非伪造。 + _HotspotDepth: int = 512 - 计时方式(inclusive / 包含式,含被调方耗时):在函数入口用一次 - @llvm.readcyclecounter() 取起始 tick 存入**本调用帧**的 alloca;在该函数 - 每个 ret 终结指令之前再取一次 tick,把"本次调用耗时"累加进该函数专属的 - internal i64 __fhs_ftime_;同时用 internal i64 __fhs_ftime_enter_ - 统计该函数被进入次数,便于交叉校验。 + def _EmitReadTimestamp(self, b: ir.IRBuilder, name: str = '') -> ir.Value: + """发射一次**串行化**时间戳读取,返回 i64 tick(入口与每个 ret 前共用)。 - 返回记录列表 (累计耗时全局, 进入次数全局, 函数名),顺序即 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 与读取计时原语,不改变返回值/参数/控制流。 + 串行化方案:在 x86_64 目标上先取一次内存屏障 @llvm.x86.sse2.lfence 再读 + @llvm.readcyclecounter()(该 intrinsic 在 x86_64 后端降为 rdtsc)。理由: + - lfence 在 Intel/AMD 上对取指与访存有序列化效果,能阻止 rdtsc 与前后访存 + 乱序(裸 rdtsc 只保证自身 edx:eax 原子,不保证与其它指令的顺序),这是 + "lfence; rdtsc" 这一业界通行的序列化读法; + - 用 LLVM intrinsic 组合而非 ir.InlineAsm:无内联汇编约束串的转义/渲染风险, + 也不依赖 Clang 风格 asm 约束是否被 llvmlite 正确渲染,可移植性更好。 + 非 x86_64 目标(如 aarch64/arm/i386)降级为**裸** @llvm.readcyclecounter(): + lfence 是 x86(SSE2) 专有 intrinsic,在非 x86 目标上不存在、编译期即不可用, + 故此处按目标三元组在**编译期**决定是否发射 lfence(不在运行期静默退化)。 """ i64: ir.IntType = ir.IntType(64) + if bool(self._platform_info.get('is_x86_64')): + lfence: ir.Function = self._DeclareExternalFunc( + 'llvm.x86.sse2.lfence', ir.FunctionType(ir.VoidType(), [])) + b.call(lfence, []) rcc: ir.Function = self._DeclareExternalFunc( 'llvm.readcyclecounter', ir.FunctionType(i64, [])) - records: list[tuple[ir.GlobalVariable, ir.GlobalVariable, str]] = [] + return b.call(rcc, [], name=name) + + def _EmitHotspotTimeCounters( + self, + ) -> list[tuple[ir.GlobalVariable, ir.GlobalVariable, ir.GlobalVariable, str]]: + """为模块内每个函数定义安装"自身耗时(self) + 包含式耗时(inc) + 进入次数"计时器。 + + 计时原语(串行化读):见 _EmitReadTimestamp(x86_64 上 lfence; rdtsc, + 其它目标降级为裸 rdtsc)。无 libc/Win32 依赖、无需额外链接;精度为 1 个 + tick(x86_64 上即一个 CPU 周期,~0.3ns 量级),故耗时单位记为 tick。 + + exclusive(自身耗时)算法——深度栈: + - 全局 internal i64 __fhs_tdepth:当前插桩深度,初值 0; + - 全局 internal [DEPTH x i64] __fhs_tstart / __fhs_tchild(零初始化, + DEPTH = _HotspotDepth = 512):按深度索引的"本帧起始 tick"与"本帧内被 + 插桩子调用 inclusive 之和";另有一个 internal i64 抖动槽 __fhs_tscratch, + 用于把"不应记账"的写入导向一个永不读取的位置(等价于不写,但不分支)。 + - 入口(每个被插桩函数 F,索引 N): + t0 = 串行化读时间戳() + d = load __fhs_tdepth + ok = d < DEPTH + __fhs_tstart[ok ? d : 0] = t0 (溢出时写抖动槽) + __fhs_tchild[ok ? d : 0] = 0 (溢出时写抖动槽) + __fhs_tdepth = ok ? d + 1 : d (溢出时不推进深度) + slot = ok ? d : -1 存进该次调用的 alloca(每调用一个独立栈槽) + __fhs_ftime_enter_N += 1 (溢出也计进入次数) + 说明:slot 是"本帧独占的深度索引",递归/重入时各帧各占一层,天然正确; + 因为本帧在其返回前独享 slot,故 ret 时用 __fhs_tstart[slot] 即本帧 t0 + (无需再单独保存 t0 到 alloca,避免出现无人读取的死存储)。 + - 每个 ret 之前(slot = load): + inok = slot >= 0;idx = inok ? slot : 0 + t1 = 串行化读时间戳() + inc = t1 - __fhs_tstart[idx] (本次 inclusive) + self = inc - __fhs_tchild[idx] (减去本次内被插桩子调用的 inc 之和) + __fhs_fself_N += inok ? self : 0 (新增:自身耗时) + __fhs_finc_N += inok ? inc : 0 (保留:包含式耗时) + __fhs_tdepth = inok ? slot : load __fhs_tdepth (回到本帧深度) + if slot > 0(含用抖动槽等价实现): __fhs_tchild[slot-1] += inc + (把本次 inc 记到父帧的 child 累加器) + 溢出帧(slot=-1)不记账、也不写入父帧 child;其墙钟时间仍落在父帧的 + inc 里,因而会计入父帧的 **self**(见下方说明)。 + + 返回记录列表 (自身耗时全局, 包含式耗时全局, 进入次数全局, 函数名),顺序即 + N 的编号顺序。落盘由 _CreateHotspotTimeHandler 完成(按 self 降序)。 + + 固有性质(务必据此解读数值):**未插桩的被调方**(间接调用、无定义体的外部 + 函数如 strcmp/memcpy、名字以 __fhs_ 开头的辅助函数、深度溢出而跳过的调用) + 其耗时不会被本方法从调用者 self 中扣除,一律计入**调用者自身 self**。故 self + 是"该函数自身指令 + 未插桩子调用"的墙钟时间,只是比 inc 更贴近自身成本。 + + 插桩规则与跳过条件: + - 只处理有基本块(即已定义)且名字不以 __fhs_ 开头的函数;计数器/深度栈 + 一律 internal,跨模块(独立翻译单元)链接不产生重复符号,初值 0。 + - 入口代码置于入口块最前(无终结指令,不改变 CFG);条件写入用 select 实现, + 不新增基本块、不修改任何既有分支/终结指令。 + - 只在该函数的 ret 终结指令之前插桩;以 unreachable 等其它终结指令结尾的 + 块(如 noreturn 调用后的代码)没有"返回点",明确跳过、不伪造数值(数量见 + 报告,如需可另行统计)。 + - 累加只做 load/sub/add/store/select 与读取计时原语,不改变返回值/参数/控制流。 + """ + depth: int = self._HotspotDepth + i32: ir.IntType = ir.IntType(32) + i64: ir.IntType = ir.IntType(64) + c0_32: ir.Constant = ir.Constant(i32, 0) + c0_64: ir.Constant = ir.Constant(i64, 0) + c1_64: ir.Constant = ir.Constant(i64, 1) + cneg1_64: ir.Constant = ir.Constant(i64, -1) + depth_gv: ir.GlobalVariable = ir.GlobalVariable( + self.module, i64, name=self._UniqueGlobalName('__fhs_tdepth')) + depth_gv.linkage = 'internal' + depth_gv.initializer = c0_64 + arr_ty: ir.ArrayType = ir.ArrayType(i64, depth) + tstart_gv: ir.GlobalVariable = ir.GlobalVariable( + self.module, arr_ty, name=self._UniqueGlobalName('__fhs_tstart')) + tstart_gv.linkage = 'internal' + tstart_gv.initializer = ir.Constant(arr_ty, None) + tchild_gv: ir.GlobalVariable = ir.GlobalVariable( + self.module, arr_ty, name=self._UniqueGlobalName('__fhs_tchild')) + tchild_gv.linkage = 'internal' + tchild_gv.initializer = ir.Constant(arr_ty, None) + scratch_gv: ir.GlobalVariable = ir.GlobalVariable( + self.module, i64, name=self._UniqueGlobalName('__fhs_tscratch')) + scratch_gv.linkage = 'internal' + scratch_gv.initializer = c0_64 + records: list[tuple[ir.GlobalVariable, 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) + self_gv: ir.GlobalVariable = ir.GlobalVariable( + self.module, i64, name=self._UniqueGlobalName(f'__fhs_fself_{idx}')) + self_gv.linkage = 'internal' + self_gv.initializer = c0_64 + inc_gv: ir.GlobalVariable = ir.GlobalVariable( + self.module, i64, name=self._UniqueGlobalName(f'__fhs_finc_{idx}')) + inc_gv.linkage = 'internal' + inc_gv.initializer = c0_64 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(置于入口块最前) + enter_gv.initializer = c0_64 + # 入口:取 t0 → 压深度栈 → 记录本帧 slot → 进入次数 +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_ + slot_ptr: ir.Value = eb.alloca(i64, name=f'fhs_t_slot_{idx}') + t0: ir.Value = self._EmitReadTimestamp(eb, name=f'fhs_t_t0_{idx}') + d: ir.Value = eb.load(depth_gv, name=f'fhs_t_depth_{idx}') + ok: ir.Value = eb.icmp_unsigned('<', d, ir.Constant(i64, depth), name=f'fhs_t_ok_{idx}') + safe_idx: ir.Value = eb.select(ok, d, c0_64, name=f'fhs_t_idx_{idx}') + eb.store(t0, eb.select( + ok, eb.gep(tstart_gv, [c0_32, safe_idx]), scratch_gv)) + eb.store(c0_64, eb.select( + ok, eb.gep(tchild_gv, [c0_32, safe_idx]), scratch_gv)) + eb.store(eb.select(ok, eb.add(d, c1_64), d), depth_gv) + eb.store(eb.select(ok, d, cneg1_64), slot_ptr) + eb.store(eb.add(eb.load(enter_gv), c1_64), enter_gv) + # 每个 ret 之前:算 self/inc,回退深度,累加父帧 child 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)) + slot: ir.Value = rb.load(slot_ptr, name=f'fhs_t_slotv_{idx}') + inok: ir.Value = rb.icmp_signed('>=', slot, c0_64, name=f'fhs_t_inok_{idx}') + t1: ir.Value = self._EmitReadTimestamp(rb, name=f'fhs_t_t1_{idx}') + safe_slot: ir.Value = rb.select(inok, slot, c0_64, name=f'fhs_t_slotidx_{idx}') + inc_v: ir.Value = rb.sub( + t1, rb.load(rb.gep(tstart_gv, [c0_32, safe_slot])), + name=f'fhs_t_inc_{idx}') + child_v: ir.Value = rb.load( + rb.gep(tchild_gv, [c0_32, safe_slot]), name=f'fhs_t_child_{idx}') + self_v: ir.Value = rb.sub(inc_v, child_v, name=f'fhs_t_self_{idx}') + rb.store(rb.add(rb.load(self_gv), rb.select(inok, self_v, c0_64)), self_gv) + rb.store(rb.add(rb.load(inc_gv), rb.select(inok, inc_v, c0_64)), inc_gv) + rb.store(rb.select(inok, slot, rb.load(depth_gv)), depth_gv) + pok: ir.Value = rb.icmp_signed('>', slot, c0_64, name=f'fhs_t_pok_{idx}') + p_idx: ir.Value = rb.select(pok, rb.sub(slot, c1_64), c0_64) + p_ptr: ir.Value = rb.gep(tchild_gv, [c0_32, p_idx]) + p_target: ir.Value = rb.select(pok, p_ptr, scratch_gv) + rb.store(rb.add(rb.load(p_target), inc_v), p_target) + records.append((self_gv, inc_gv, enter_gv, func.name)) return records def _CreateHotspotSitesHandler( @@ -1315,20 +1421,23 @@ class BaseGenMixin: def _CreateHotspotTimeHandler( self, path: str, - records: list[tuple[ir.GlobalVariable, ir.GlobalVariable, str]], + records: list[tuple[ir.GlobalVariable, ir.GlobalVariable, ir.GlobalVariable, str]], module_id: str | None = None, ) -> ir.Function: - """生成 Win32 控制台 Ctrl+C 处理器:按累计耗时降序写入每函数计时文件。 + """生成 Win32 控制台 Ctrl+C 处理器:按**自身耗时**降序写入每函数计时文件。 落盘格式(追加): # time \\n - \\n - module_id 为空时省略头部 sha1 行(留空,不伪造)。 + \\n + module_id 为空时省略头部 sha1 行(留空,不伪造)。列含义(单位均为 tick): + - self_tick:自身耗时(exclusive,见 _EmitHotspotTimeCounters),已扣除本 + 函数内**被插桩**子调用的 inclusive 之和;但**未插桩**的被调方(间接调用、 + 无定义体的外部函数如 strcmp/memcpy、深度溢出跳过的调用)仍计入其中。 + - inc_tick:包含式耗时(inclusive,含全部被调方)。 + - enter_count:被进入次数(含递归/重入),用于交叉校验。 - - 排序:与调用点计数同法,落盘前用选择排序按累计 tick 降序输出,只输出 - 进入次数 >0 的函数;tick 相同时按编号 N 升序(稳定、可复现)。 - - acc_ticks 单位为 @llvm.readcyclecounter() 的 tick(x86_64 上即 CPU 周期); - enter_count 为函数被进入次数(含递归/重入),用于交叉校验。 + - 排序:与调用点计数同法,落盘前用选择排序按 self_tick 降序输出,只输出 + 进入次数 >0 的函数;self_tick 相同时按编号 N 升序(稳定、可复现)。 - 处理器签名为 Win32 PHANDLER_ROUTINE(i32 (i32) -> BOOL),用 CRT fopen("a")/fprintf/fclose 落盘(沿用既有做法),返回 FALSE(0) 让默认 处理继续,使 Ctrl+C 仍能终止程序。 @@ -1363,39 +1472,48 @@ class BaseGenMixin: 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')) + '%s %llu %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')) + '# time %s name self inc enter (tick; self excludes instrumented callees)\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) + selfptr_ty: ir.ArrayType = ir.ArrayType(i64p, n) + incptr_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}')) + self._create_string_global(rec[3], 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, [ + self_gv: ir.GlobalVariable = ir.GlobalVariable( + self.module, selfptr_ty, name=self._UniqueGlobalName('__fhs_ftime_self_ptrs')) + self_gv.linkage = 'internal' + self_gv.global_constant = True + self_gv.initializer = ir.Constant(selfptr_ty, [ ir.Constant(i64p, rec[0].get_reference()) for rec in records ]) + inc_gv: ir.GlobalVariable = ir.GlobalVariable( + self.module, incptr_ty, name=self._UniqueGlobalName('__fhs_ftime_inc_ptrs')) + inc_gv.linkage = 'internal' + inc_gv.global_constant = True + inc_gv.initializer = ir.Constant(incptr_ty, [ + ir.Constant(i64p, rec[1].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 + ir.Constant(i64p, rec[2].get_reference()) for rec in records ]) # 全部 alloca 置于 entry 块:避免在循环体内 alloca 导致每次迭代爆栈 used: ir.Value = b.alloca(ir.ArrayType(i8, n), name='fhs_ftime_used') @@ -1406,7 +1524,7 @@ class BaseGenMixin: 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 小者) + # 选择排序:外层 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') @@ -1432,10 +1550,10 @@ class BaseGenMixin: 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 作为"有样本"判据 + # scan_i: v = *self_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) + self_p: ir.Value = b.load(b.gep(self_gv, [c0_32, i_val])) + v_val: ir.Value = b.load(self_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) @@ -1456,9 +1574,10 @@ class BaseGenMixin: 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])) + sp: ir.Value = b.load(b.gep(self_gv, [c0_32, idx])) + ip: ir.Value = b.load(b.gep(inc_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)], + b.call(fprintf, [fp, fmt_ptr, name_ptr, b.load(sp), b.load(ip), b.load(ep)], name='fhs_ftime_write') b.store(b.add(b.load(k_ptr), c1_64), k_ptr) b.branch(loop_k)