From bf4bab093a330a08e675c76179e42377637df608 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Mon, 14 Sep 2026 14:35:17 +0800 Subject: [PATCH] =?UTF-8?q?fix(hotspot):=20=E6=B7=B1=E5=BA=A6=E6=A0=88?= =?UTF-8?q?=E6=94=B9=E7=94=A8=20common=20linkage=EF=BC=8C=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E8=B7=A8=E6=A8=A1=E5=9D=97=20self=20=E8=AE=B0?= =?UTF-8?q?=E8=B4=A6=E6=BC=8F=E6=89=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 症状:--hotspot-sites 下跨模块调用链的调用方 self 约等于 inc(子调用 inc 未被扣除),而同模块链正常,导致所有基于 self 的结论带偏差(实测 _translate_module_level 421.9e9/422.0e9、translate_children 164.5e9/164.9e9 等)。 根因:__fhs_tdepth/__fhs_tstart/__fhs_tchild 原为 internal linkage,Projectrans 多模块模式下每个 .vp 翻译单元各持一份独立深度栈;跨模块调用时被调方读到的深度为 0,slot=0,ret 时 slot>0 判定为假,其 inc 永远写不到调用方的 tchild[slot-1],于是调用方 self = inc - 0 = inc。 修复:三件套改 common linkage,由链接器合并为全程序唯一一份(与本仓库多模块同名全局的既有做法一致);__fhs_tscratch 与每函数计数器仍保持 internal。已用逐指令 Python 模拟验证:同模块各形态均正确、跨模块独立栈必然漏扣、共享栈后全部正确。 仅 --hotspot-sites 启用时生效,未启用路径逐字节不变。旧的 hotspot_time.txt 在跨模块链上的 self 列不可信,需重新测量。 --- lib/core/LLVMCG/BaseGen.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/lib/core/LLVMCG/BaseGen.py b/lib/core/LLVMCG/BaseGen.py index c8009f6..8cc6cbf 100644 --- a/lib/core/LLVMCG/BaseGen.py +++ b/lib/core/LLVMCG/BaseGen.py @@ -1099,8 +1099,10 @@ class BaseGenMixin: tick(x86_64 上即一个 CPU 周期,~0.3ns 量级),故耗时单位记为 tick。 exclusive(自身耗时)算法——深度栈: - - 全局 internal i64 __fhs_tdepth:当前插桩深度,初值 0; - - 全局 internal [DEPTH x i64] __fhs_tstart / __fhs_tchild(零初始化, + - 全局 common i64 __fhs_tdepth:当前插桩深度,初值 0;common linkage 让每个 + 翻译单元各自发射同名 tentative 定义、由链接器合并为**全程序唯一一份**, + 故跨模块调用也共用同一张深度栈(独立翻译单元链接不产生重复符号)。 + - 全局 common [DEPTH x i64] __fhs_tstart / __fhs_tchild(零初始化, DEPTH = _HotspotDepth = 512):按深度索引的"本帧起始 tick"与"本帧内被 插桩子调用 inclusive 之和";另有一个 internal i64 抖动槽 __fhs_tscratch, 用于把"不应记账"的写入导向一个永不读取的位置(等价于不写,但不分支)。 @@ -1136,10 +1138,14 @@ class BaseGenMixin: 函数如 strcmp/memcpy、名字以 __fhs_ 开头的辅助函数、深度溢出而跳过的调用) 其耗时不会被本方法从调用者 self 中扣除,一律计入**调用者自身 self**。故 self 是"该函数自身指令 + 未插桩子调用"的墙钟时间,只是比 inc 更贴近自身成本。 + 深度栈为 common(全程序一份)后,跨模块被调方只要被插桩即能正确扣除;修复前 + 各模块各持一份 internal 深度栈,跨模块子调用的 inc 记不进调用方 tchild, + 表现为调用方 self ≈ inc 的假象(见 common linkage 说明)。 插桩规则与跳过条件: - - 只处理有基本块(即已定义)且名字不以 __fhs_ 开头的函数;计数器/深度栈 - 一律 internal,跨模块(独立翻译单元)链接不产生重复符号,初值 0。 + - 只处理有基本块(即已定义)且名字不以 __fhs_ 开头的函数;每函数计数器一律 + internal(同名索引跨模块并存,不可合并);深度栈用 common 由链接器合并为 + 全程序唯一一份,初值 0。 - 入口代码置于入口块最前(无终结指令,不改变 CFG);条件写入用 select 实现, 不新增基本块、不修改任何既有分支/终结指令。 - 只在该函数的 ret 终结指令之前插桩;以 unreachable 等其它终结指令结尾的 @@ -1154,18 +1160,24 @@ class BaseGenMixin: c0_64: ir.Constant = ir.Constant(i64, 0) c1_64: ir.Constant = ir.Constant(i64, 1) cneg1_64: ir.Constant = ir.Constant(i64, -1) + # 深度栈三件套用 common linkage:每模块各自发射一份同名 tentative 定义, + # 由链接器合并成"全程序唯一一份"(与 HandlesAssign 中多模块同名全局同法, + # 避免 ld.lld duplicate symbol)。跨模块调用链必须共用同一张深度栈,否则 + # 被调方在自模块栈里 slot=0(或 slot 落在错误父帧)→ 其 inc 永远记不到 + # 调用方 __fhs_tchild[slot-1] → 调用方 self ≈ inc(子时间一分不扣)。 + # 抖动槽 __fhs_tscratch 只写不读,留 internal 即可(各模块独立无影响)。 depth_gv: ir.GlobalVariable = ir.GlobalVariable( self.module, i64, name=self._UniqueGlobalName('__fhs_tdepth')) - depth_gv.linkage = 'internal' + depth_gv.linkage = 'common' 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.linkage = 'common' 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.linkage = 'common' tchild_gv.initializer = ir.Constant(arr_ty, None) scratch_gv: ir.GlobalVariable = ir.GlobalVariable( self.module, i64, name=self._UniqueGlobalName('__fhs_tscratch'))