fix(lookup): 索引未命中一律回退原线性扫描,停用不可信的 Ty 索引判定

降低「索引可信且未命中即判定确无」语义带来的风险面(该语义是本轮构建 fail-fast 的最高嫌疑):索引只用于加速命中,未命中一律回退原线性扫描,使各查找函数在所有输入上的返回值与优化前逐字节一致。

StructGenMixin.find_struct:去掉「索引可信且查询名非空则跳过 O(n) 扫描」分支,未命中恢复原精确匹配全表扫描。

StructGenMixin.find_struct_by_type:整体停用 Ty 索引判定 —— 条目 Ty 会被深拷贝改写,槽按注册时 Ty 落位,改写后与 live 键脱钩,命中不保证是原线性首命中(不等价),故恢复原线性精确扫描;get_class_name_by_type(REnum.kind 识别路径)、register_struct 去重、lookup_field 首遍随之回到优化前语义。

CallGenLeafKit.find_func_in_table/find_func_entry_global、TypeGenCType.lookup_ctypedef、TypeGenCDef.lookup_cdefine_{constant,double,string}、VpiReader._lookup_vpi_param_annots/_lookup_vpi_ret_annot:可信未命中不再直接返回,一律落入原线性扫描。

未触碰:Function.NameLen/FUNC_SIZE、各处首字符预筛、includes/string.vp。
This commit is contained in:
TermiNexus committed 2026-09-14 12:41:27 +08:00
1 parent e7f47735ed
commit 4ed7f3909a
5 files changed
+57 -68

No files matched your search

+9 -7
View File
@@ -687,15 +687,16 @@ def _lookup_vpi_param_annots(src_sha1: str, cls_name: str, method_name: str) ->
r: str = None
ki: int
# 索引快速路径:索引可信(Built==Count)时一次探测返回命中槽下标(= 原线性
# 扫描首个同名,首注册优先);未命中 | 索引不可信 → 回退原线性扫描(正确性保证)。
# 扫描首个同名,首注册优先);未命中一律回退下方原线性扫描(索引只加速命中,
# 未命中行为与未加索引前逐字节一致);此前"可信未命中即确无"的语义已回退。
hit_p: int = -1
if _g_vpi_param_idx_built == _g_vpi_param_count \
and _g_vpi_param_idx is not None and _g_vpi_param_idx_cap > 0:
hit_p = _vpi_idx_find(_g_vpi_param_keys, VPI_PARAM_KEY_LEN,
_g_vpi_param_idx, _g_vpi_param_idx_cap, key_buf)
else:
# 快速路径:strcmp 判等蕴含首字节相等;键首字节 = src_sha1[0],缓存含多模块
# 条目,首字节不同者必不相等,直接跳过其 strcmp(仅做否定判定,不改变命中)。
if hit_p < 0:
# 回退原线性扫描:strcmp 判等蕴含首字节相等;键首字节 = src_sha1[0],缓存含
# 多模块条目,首字节不同者必不相等,直接跳过其 strcmp(仅做否定判定,不改命中)。
key_first: int = key_buf[0]
for ki in range(_g_vpi_param_count):
ent_p: bytes = _g_vpi_param_keys + ki * VPI_PARAM_KEY_LEN
@@ -1063,14 +1064,15 @@ def _lookup_vpi_ret_annot(src_sha1: str, cls_name: str, method_name: str) -> str
viperlib.snprintf(key_buf, key_len + 1, "%s.%s", src_sha1, method_name)
i: int
# 索引快速路径:索引可信(Built==Count)时一次探测返回命中槽下标(= 原线性
# 扫描首个同名,首注册优先);未命中 | 索引不可信 → 回退原线性扫描(正确性保证)。
# 扫描首个同名,首注册优先);未命中一律回退下方原线性扫描(索引只加速命中,
# 未命中行为与未加索引前逐字节一致);此前"可信未命中即确无"的语义已回退。
hit_r: int = -1
if _g_vpi_ret_idx_built == _g_vpi_ret_count \
and _g_vpi_ret_idx is not None and _g_vpi_ret_idx_cap > 0:
hit_r = _vpi_idx_find(_g_vpi_ret_keys, VPI_KEY_LEN,
_g_vpi_ret_idx, _g_vpi_ret_idx_cap, key_buf)
else:
# 快速路径:strcmp 判等蕴含首字节相等(键首字节 = src_sha1[0]),首字节不同者跳过。
if hit_r < 0:
# 回退原线性扫描:strcmp 判等蕴含首字节相等(键首字节 = src_sha1[0]),跳过首字节不同者。
key_first: int = key_buf[0]
for i in range(_g_vpi_ret_count):
ent_r: bytes = _g_vpi_ret_keys + i * VPI_KEY_LEN
+4 -3
View File
@@ -1051,7 +1051,8 @@ class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
t.CVoid(uint64(self.GlobalFuncs) + fi_t * entry_size, t.CPtr))
if fe_t is not None:
return fe_t.Func
return None
# 索引未命中一律回退下方原线性扫描(索引只加速命中,未命中行为与未加
# 索引前逐字节一致);此前"可信未命中即 return None"的语义已回退。
# 单遍扫描(合并原 owner 优先遍 + 通用遍,语义完全一致但省一半遍历):
# 扫描中同时记录"首个同裸名条目"作通用兜底;一旦发现本模块条目
# (mangled 名以 "{owner_sha1}." 开头,16 位前缀 + '.')立即返回。
@@ -1272,7 +1273,8 @@ class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
return None
entry_size: sizet = GenTypes.FuncEntry.__sizeof__()
# 名索引快速路径:可信(Built==Count)时一次探测返回首个同名条目
# (= 原线性扫描首个命中);未命中/索引不可信 → 回退原线性扫描(正确性保证)。
# (= 原线性扫描首个命中);未命中一律回退下方原线性扫描(索引只加速命中,
# 未命中行为与未加索引前逐字节一致);此前"可信未命中即 return None"已回退。
if self.GlobalFuncNameIdxBuilt == self.GlobalFuncCount \
and self.GlobalFuncNameIdx is not None and self.GlobalFuncNameIdxCap > 0:
gi: int = _gfn_idx_find(self.GlobalFuncs, self.GlobalFuncNameIdx,
@@ -1281,7 +1283,6 @@ class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
entry_g: GenTypes.FuncEntry | t.CPtr = (GenTypes.FuncEntry | t.CPtr)(
t.CVoid(uint64(self.GlobalFuncs) + gi * entry_size, t.CPtr))
return entry_g
return None
for i in range(self.GlobalFuncCount):
entry_addr: uint64 = uint64(self.GlobalFuncs) + i * entry_size
entry: GenTypes.FuncEntry | t.CPtr = (GenTypes.FuncEntry | t.CPtr)(t.CVoid(entry_addr, t.CPtr))
+21 -35
View File
@@ -393,10 +393,8 @@ class StructGenMixin:
# 用类型指针去重(同一类型不重复注册,支持跨模块同名类)
# 用 uint64 指针值比较(is 比较会触发类型安全检查解引用悬空 Ty)
# 类型索引快速路径:find_struct_by_type 的 Ty 指针身份语义 = 原去重扫描
# "首个 Ty 指针相等条目"(首注册优先),且其索引未命中 | 不可信时会回退
# 原线性扫描(正确性保证,非兜底)⇒ 与原扫描结果逐字节一致。
# 注册途中索引恒与 StructCount 同步(每次注册后 _struct_idx_add 同步)。
# 走 find_struct_by_type(其 Ty 索引判定已整体停用、恢复原线性扫描),
# 去重语义 = 原去重扫描"首个 Ty 指针相等条目"(首注册优先),逐字节一致。
existing: GenTypes.StructEntry | t.CPtr = self.find_struct_by_type(struct_ty)
if existing is not None:
return existing
@@ -786,27 +784,18 @@ class StructGenMixin:
VLogger.error(f"StructCount 异常: {self.StructCount} (find_struct)", "STRUCT")
stdlib.exit(1)
# 名索引快速路径:命中即首注册下标(语义 = 原精确匹配扫描首个命中)。
# 未命中/索引不可信 → 回退下方原精确匹配扫描(保证正确性);
# 精确匹配确无时继续走模块限定名/裸名回退逻辑(不提前 return None)。
# 未命中一律回退下方原精确匹配扫描(索引只加速命中,未命中行为与未加索引
# 前逐字节一致);精确匹配确无时继续走模块限定名/裸名回退逻辑(不提前
# return None)。此前"索引可信且查询名非空 ⇒ 未命中即确无、跳过 O(n) 扫描"
# 的语义已回退(一旦判据有未覆盖情形就会把"找得到"变成"找不到")。
fi: int = self._struct_name_idx_find(name)
if fi >= 0:
return self._get_struct_entry(fi)
# 名索引可信(Built==Count | 索引存在 | 查询名非空)时,索引未命中即
# "确无同名条目":条目在注册时即命名并同步入索引,之后名至多被中和为 ""
# (ClassGenDef/ClassGenEnum 置 Name[0]=0),故任何非空查询名的命中项必在
# 索引探测链上 ⇒ 可跳过下方 O(n) 精确扫描(热点:本扫描占 find_struct 的
# 全部 strcmp)。索引不可信 / 无索引 / 空名查询时仍回退原线性扫描
# (正确性保证,非兜底)。
name_idx_trusted: int = 0
if self.StructNameIdx is not None and self.StructIdxCap > 0 \
and self.StructIdxBuilt == self.StructCount:
name_idx_trusted = 1
if name_idx_trusted == 0 or name[0] == '\0':
for i in range(self.StructCount):
entry: GenTypes.StructEntry | t.CPtr = self._get_struct_entry(i)
if entry is not None and entry.Name is not None:
if string.strcmp(entry.Name, name) == 0:
return entry
for i in range(self.StructCount):
entry: GenTypes.StructEntry | t.CPtr = self._get_struct_entry(i)
if entry is not None and entry.Name is not None:
if string.strcmp(entry.Name, name) == 0:
return entry
# 模块限定名(module.Class):模块可解析时按模块归属精确匹配,
# 防止裸名回退抓跨模块同名类。模块未命中时若裸名类唯一(无冲突)
# 仍回退,避免误杀未登记模块的既有解析。
@@ -969,12 +958,11 @@ class StructGenMixin:
VLogger.error(f"StructCount 异常: {self.StructCount} (find_struct_by_type)", "STRUCT")
stdlib.exit(1)
st_val: uint64 = uint64(struct_ty)
# 类型索引快速路径:命中即首注册下标(语义 = 原扫描首个 Ty 指针相等条目)。
# 未命中/索引不可信 → 回退原线性扫描(Ty 可能被深拷贝改写,索引未覆盖全部
# Ty 值,故此处未命中不能断言"确无",必须回退)。
ti: int = self._struct_type_idx_find(st_val)
if ti >= 0:
return self._get_struct_entry(ti)
# Ty 索引整体停用判定(索引结构仍由 _struct_idx_add 维护,但不用于此处):
# 条目 Ty 可能被深拷贝改写为新指针(ClassGenDef),索引槽按"注册时的 Ty 值"
# 落位,改写后该条目的 live 键与槽位脱钩 ⇒ 索引命中返回的未必是"原线性扫描
# 首个 Ty 相等条目",命中路径与原语义不等价、且无法低成本保证等价。故此处
# 恢复原线性精确扫描(未命中/命中行为均与未加索引前逐字节一致)。
for i in range(self.StructCount):
entry: GenTypes.StructEntry | t.CPtr = self._get_struct_entry(i)
if entry is not None and entry.Ty is not None:
@@ -1026,10 +1014,9 @@ class StructGenMixin:
# ============================================================
# 第一遍: is 身份比较(用 uint64 指针值比较,避免 is 编译路径解引用悬空 Ty)
# 类型索引快速路径:find_struct_by_type 的语义 = 原扫描"首个 Ty 指针相等
# 条目"(首注册优先);索引未命中 | 不可信时其内部回退原线性扫描
# (正确性保证,非兜底)⇒ 选中的 entry 与原 is 身份扫描完全一致。
# 类型唯一确定 entry:命中后查字段,字段未找到直接返回 None(不继续遍历)。
# find_struct_by_type 的语义 = 原扫描"首个 Ty 指针相等条目"(首注册优先);
# 其 Ty 索引判定已整体停用、恢复原线性扫描 ⇒ 选中的 entry 与原 is 身份扫描
# 完全一致。类型唯一确定 entry:命中后查字段,字段未找到直接返回 None。
lk_entry: GenTypes.StructEntry | t.CPtr = self.find_struct_by_type(struct_ty)
if lk_entry is not None:
for fi in range(lk_entry.FieldCount):
@@ -1513,9 +1500,8 @@ class StructGenMixin:
"""按类型指针查找类名,返回类名字符串或 None"""
if struct_ty is None or self.StructTable is None:
return None
# 类型索引快速路径:find_struct_by_type 的 Ty 指针身份语义 = 原扫描
# "首个 Ty 指针相等条目"(首注册优先),返回其 Name;索引未命中 |
# 不可信时其内部回退原线性扫描(正确性保证,非兜底)⇒ 逐字节一致。
# find_struct_by_type 的语义 = 原扫描"首个 Ty 指针相等条目"(首注册优先),
# 返回其 Name;其 Ty 索引判定已整体停用、恢复原线性扫描 ⇒ 逐字节一致。
fast_cn: GenTypes.StructEntry | t.CPtr = self.find_struct_by_type(struct_ty)
if fast_cn is not None:
return fast_cn.Name
+15 -15
View File
@@ -340,16 +340,16 @@ class TypeGenCDef(TypeGenBase.TypeGenBase):
n: sizet = len(self.CDefineNames)
i: sizet
# 名索引快速路径:可信(Built==n)时直接定位唯一候选下标(= 原扫描首个
# 命中);未命中 → 确实不存在,直接返回;不可用 → 回退原全表线性扫描。
# 命中);未命中一律保持全表范围、回退下方原全表线性扫描(索引只加速命中,
# 未命中行为与未加索引前逐字节一致);此前"未命中即确无、直接 return"已回退。
cd_start: sizet = 0
cd_end: sizet = n
if name is not None and self.CDefineNameIdxBuilt == int(n) \
and self.CDefineNameIdx is not None and self.CDefineNameIdxCap > 0:
cd_hit: int = self._cdef_idx_find(name)
if cd_hit < 0:
return 0
cd_start = sizet(cd_hit)
cd_end = sizet(cd_hit + 1)
if cd_hit >= 0:
cd_start = sizet(cd_hit)
cd_end = sizet(cd_hit + 1)
for i in range(cd_start, cd_end):
nm: str = self.CDefineNames.get(i)
if nm is not None and string.strcmp(nm, name) == 0:
@@ -380,16 +380,16 @@ class TypeGenCDef(TypeGenBase.TypeGenBase):
n: sizet = len(self.CDefineNames)
i: sizet
# 名索引快速路径:可信(Built==n)时直接定位唯一候选下标(= 原扫描首个
# 命中);未命中 → 确实不存在,直接返回;不可用 → 回退原全表线性扫描。
# 命中);未命中一律保持全表范围、回退下方原全表线性扫描(索引只加速命中,
# 未命中行为与未加索引前逐字节一致);此前"未命中即确无、直接 return"已回退。
cd_start: sizet = 0
cd_end: sizet = n
if name is not None and self.CDefineNameIdxBuilt == int(n) \
and self.CDefineNameIdx is not None and self.CDefineNameIdxCap > 0:
cd_hit: int = self._cdef_idx_find(name)
if cd_hit < 0:
return 0.0
cd_start = sizet(cd_hit)
cd_end = sizet(cd_hit + 1)
if cd_hit >= 0:
cd_start = sizet(cd_hit)
cd_end = sizet(cd_hit + 1)
for i in range(cd_start, cd_end):
nm: str = self.CDefineNames.get(i)
# 快速路径:strcmp 判等蕴含首字符相等,首字符不同者跳过 strcmp。
@@ -421,16 +421,16 @@ class TypeGenCDef(TypeGenBase.TypeGenBase):
n: sizet = len(self.CDefineNames)
i: sizet
# 名索引快速路径:可信(Built==n)时直接定位唯一候选下标(= 原扫描首个
# 命中);未命中 → 确实不存在,直接返回;不可用 → 回退原全表线性扫描。
# 命中);未命中一律保持全表范围、回退下方原全表线性扫描(索引只加速命中,
# 未命中行为与未加索引前逐字节一致);此前"未命中即确无、直接 return"已回退。
cd_start: sizet = 0
cd_end: sizet = n
if name is not None and self.CDefineNameIdxBuilt == int(n) \
and self.CDefineNameIdx is not None and self.CDefineNameIdxCap > 0:
cd_hit: int = self._cdef_idx_find(name)
if cd_hit < 0:
return None
cd_start = sizet(cd_hit)
cd_end = sizet(cd_hit + 1)
if cd_hit >= 0:
cd_start = sizet(cd_hit)
cd_end = sizet(cd_hit + 1)
for i in range(cd_start, cd_end):
nm: str = self.CDefineNames.get(i)
if nm is not None and string.strcmp(nm, name) == 0:
+8 -8
View File
@@ -219,17 +219,17 @@ class TypeGenCType(TypeGenBase.TypeGenBase):
return None
n: sizet = len(self.CTypedefNames)
i: sizet
# 名索引快速路径:可信(Built==n)时直接定位唯一候选槽下标。CTypedef 表
# 注册时按名去重(register_ctypedef 已存在则原地更新类型串)⇒ 名唯一,
# 索引未命中 ⇒ 确实不存在,直接返回;索引不可用/不可信 → 回退原全表扫描。
# 名索引快速路径:可信(Built==n)时直接定位唯一候选槽下标(CTypedef 表
# 注册时按名去重 ⇒ 名唯一,命中即原全表扫描首个命中)。未命中一律回退下方
# 原全表线性扫描(索引只加速命中,未命中行为与未加索引前逐字节一致);
# 此前"可信未命中即确无、直接 return None"的语义已回退。
if self.CTypedefNameIdxBuilt == int(n) \
and self.CTypedefNameIdx is not None and self.CTypedefNameIdxCap > 0:
ti: int = self._ctypedef_idx_find(name)
if ti < 0:
return None
ts_hit: str = self.CTypedefTypeStrs.get(sizet(ti))
self.CTypedefFound = 1
return ts_hit
if ti >= 0:
ts_hit: str = self.CTypedefTypeStrs.get(sizet(ti))
self.CTypedefFound = 1
return ts_hit
for i in range(n):
nm: str = self.CTypedefNames.get(i)
if nm is not None and string.strcmp(nm, name) == 0: