fix(hotspot): 深度栈改用 common linkage,修复跨模块 self 记账漏扣

症状:--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 列不可信,需重新测量。
This commit is contained in:
TermiNexus committed 2026-09-14 14:35:17 +08:00
1 parent 136e9bc33e
commit bf4bab093a
1 file changed
+19 -7
+19 -7
View File
@@ -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'))