perf(gen): 结构体/类型查找去线性化并守卫全量遍历残量

StructGenMixin: 加「名 | Ty 指针值 -> 首注册下标」索引,find_struct/find_struct_by_module/find_struct_by_type 走 O(1) 快速路径;每次注册占槽保序,中和/改名后键不等自然跳过,未命中或索引不可信回退原扫描。并补 _struct_name_hash/_struct_ty_hash(32 位 FNV,常量均小于 2^63 以避开大十进制字面量解析风险)。

TypeSpec.InternSpec: 加「文本键 -> 驻留下标」开放寻址索引,去重由 O(n) 降为期望 O(1);命中返回原同一实例,注册顺序/容量 fail-fast 语义不变。

register_struct/lookup_field/get_class_name_by_type 的按 Ty 指针首匹配改走 find_struct_by_type;CallGenLeafKit/CallGenCall 的按序号全量遍历补守卫注释(避免后续误改为按名查找)。
This commit is contained in:
TermiNexus committed 2026-09-13 18:56:16 +08:00
1 parent 0dd7511a9b
commit 76df3f0225
4 files changed
+387 -47

No files matched your search

+4
View File
@@ -846,6 +846,10 @@ class CallGenCall(CallGenBase.CallGenBase):
if sib_recv_local != 0:
sc_i: int = 0
sc_n: int = self.Gen.StructGen.get_struct_count()
# 【守卫】这是按序号(0..StructCount-1)的全量遍历,非按名查找,
# 不走 StructNameIdx 索引:兄弟 Mixin 候选是"名字不等 class_name
# 且本模块存在同名方法"的任意条目,需逐条枚举探测,不可改为按名
# find_struct(会退化为单条精确查找、漏掉全部兄弟候选)。
while sc_i < sc_n and found_func is None and mixin_hit == 0:
cand_entry: GenTypes.StructEntry | t.CPtr = self.Gen.StructGen._get_struct_entry(sc_i)
if cand_entry is not None and cand_entry.Name is not None:
+8 -10
View File
@@ -393,6 +393,9 @@ class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
names[outcount] = sizet(cur)
outcount += 1
# 子类入队:遍历全部已注册 struct,ParentNames 含 cur 者
# 【守卫】这是按序号(0..StructCount-1)的全量遍历,非按名查找,
# 不走 StructNameIdx 索引:需枚举每个条目的 ParentNames | 按序号
# 取条目,语义等价于"扫描全表收集子类",不可改为 find_struct。
scnt: int = self.Gen.StructGen.get_struct_count()
for i in range(scnt):
e2: GenTypes.StructEntry | t.CPtr = self.Gen.StructGen._get_struct_entry(i)
@@ -584,23 +587,18 @@ class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin):
def _ensure_closure_names(self) -> int:
"""确保闭包名表已分配,0 失败"""
if self.ClosureNames is not None:
return 1
if self.ClosureNames is not None: return 1
self.ClosureNames = CompilerFactory.factory.PermPool.alloc(64 * MAX_CLOSURE_NAMES)
if self.ClosureNames is None:
return 0
if self.ClosureNames is None: return 0
string.memset(self.ClosureNames, 0, 64 * MAX_CLOSURE_NAMES)
return 1
def register_closure_name(self, name: str) -> int:
"""注册嵌套函数(闭包变量)名,0 成功"""
if name is None:
return 1
if self._ensure_closure_names() == 0:
return 1
if self.ClosureCount >= MAX_CLOSURE_NAMES:
return 1
if name is None: return 1
if self._ensure_closure_names() == 0: return 1
if self.ClosureCount >= MAX_CLOSURE_NAMES: return 1
idx: sizet = sizet(self.ClosureCount) * 64
string.strncpy(self.ClosureNames + idx, name, 63)
self.ClosureCount += 1
+264 -26
View File
@@ -33,6 +33,8 @@ import lib.core.Gen.GenApi as GenApi
# 常量(与 HandlesStruct 保持一致)
# ============================================================
STRUCT_MAX: t.CDefine = 512
# 名 | 类型 下标索引槽数(2 的幂且 > 2*STRUCT_MAX ⇒ 负载 < 0.5,探测必然终止)
STRUCT_IDX_CAP: t.CDefine = 1024
# 字段槽位上限制(与 HandlesStruct 保持一致;Generator 43 Mixin 展平 ~120 字段,
# 2026-08-16 追加78:64 截断导致第 9+ 父类字段(StmtGenMixin.DiagStmtCount 等)
# 丢失 → __init__ 写 self.DiagStmtCount "属性写入失败")
@@ -43,6 +45,43 @@ FIELD_NAME_MAX: t.CDefine = 64
PARENT_MAX: t.CDefine = 64
MRO_MAX: t.CDefine = 64
# 索引哈希常量(32 位 FNV-1a,常量均 < 2^63 以避开大十进制字面量解析风险)
FNV32_OFFSET: t.CDefine = 2166136261
FNV32_PRIME: t.CDefine = 16777619
FNV32_MASK: t.CDefine = 4294967295
# ============================================================
# 索引哈希函数(名 | Ty 指针值 → 64 位哈希)
# 供 StructTable 的「名 | Ty 指针值 → 首注册下标」索引探测使用
# (见 _struct_name_idx_insert / _struct_name_idx_find 等)。
# ============================================================
def _struct_name_hash(name: str) -> uint64:
"""名字 → 哈希(32 位 FNV-1a,逐字节到 NUL 结束;空名返回偏移基值)"""
h: uint64 = uint64(FNV32_OFFSET)
if name is None:
return h
i: int = 0
while name[i] != '\0':
h = (h ^ uint64(int(name[i]))) * uint64(FNV32_PRIME)
h = h & uint64(FNV32_MASK)
i += 1
return h
def _struct_ty_hash(tyval: uint64) -> uint64:
"""Ty 指针值 → 哈希(对其 8 字节做 32 位 FNV-1a)"""
h: uint64 = uint64(FNV32_OFFSET)
v: uint64 = tyval
j: int = 0
while j < 8:
h = (h ^ (v & uint64(255))) * uint64(FNV32_PRIME)
h = h & uint64(FNV32_MASK)
v = v >> 8
j += 1
return h
# ============================================================
# StructGenMixin - 结构体注册表/字段查找/可见性
@@ -63,6 +102,12 @@ class StructGenMixin:
VisibleStructs: hashtable.HashTable | t.CPtr
StrictVisibility: int
# --- 名 | 类型 下标索引(查找快速路径;索引不可信时回退原线性扫描) ---
StructNameIdx: sizet | t.CPtr # 名 → 首注册下标(槽 u64:0=空槽,否则 index+1)
StructTypeIdx: sizet | t.CPtr # Ty 指针值 → 首注册下标(同布局)
StructIdxCap: int # 索引槽数(2 的幂,> 2*STRUCT_MAX)
StructIdxBuilt: int # 已同步建索引条目数(-1=不可信;= StructCount 才可信)
def InitStructGen(self) -> int:
"""初始化结构体 Mixin 字段(子类构造时内联调用,规避 NoVTable 继承符号不转发)"""
if self is None:
@@ -72,6 +117,10 @@ class StructGenMixin:
self.StdlibAlloc = 0
self.VisibleStructs = None
self.StrictVisibility = 0
self.StructNameIdx = None
self.StructTypeIdx = None
self.StructIdxCap = 0
self.StructIdxBuilt = 0
return 0
# ============================================================
@@ -145,6 +194,24 @@ class StructGenMixin:
return 0
string.memset(self.StructTable, 0, entry_size * STRUCT_MAX)
self.StructCount = 0
# 建 名 | 类型 索引(与表同池分配:表存活则索引存活,生命周期一致)。
# 索引是可选加速层:分配失败则保持 None 且 StructIdxBuilt=-1,查找自动
# 回退原线性扫描(语义完全一致),故不因此使初始化失败。
idx_bytes: sizet = sizet(STRUCT_IDX_CAP) * 8
name_idx: sizet | t.CPtr = table_pool.alloc(idx_bytes)
type_idx: sizet | t.CPtr = table_pool.alloc(idx_bytes)
if name_idx is None or type_idx is None:
self.StructNameIdx = None
self.StructTypeIdx = None
self.StructIdxCap = 0
self.StructIdxBuilt = -1
else:
string.memset(name_idx, 0, idx_bytes)
string.memset(type_idx, 0, idx_bytes)
self.StructNameIdx = name_idx
self.StructTypeIdx = type_idx
self.StructIdxCap = STRUCT_IDX_CAP
self.StructIdxBuilt = 0
return 1
def _get_struct_entry(self, i: int) -> GenTypes.StructEntry | t.CPtr:
@@ -155,6 +222,154 @@ class StructGenMixin:
addr: uint64 = uint64(self.StructTable) + i * entry_size
return (GenTypes.StructEntry | t.CPtr)(t.CVoid(addr, t.CPtr))
# ============================================================
# 名 | 类型 索引(开放寻址 | 线性探测;槽存 index+1,0=空槽)
#
# 目的:把 find_struct/find_struct_by_module(名)与 find_struct_by_type
# (类型指针)从 O(n) 全表扫描降为期望 O(1)。索引在注册处同步维护;
# 冲突逐键比较(名用 strcmp,类型用 Ty 指针值),不能只比哈希。
# 索引"可信"条件 = StructIdxBuilt == StructCount(每次注册后同步);
# 不可信/不可用时查找一律回退原线性扫描,保证语义逐字节一致。
# 条目被中和(Name[0]=0 / Ty=None,见 ClassGenDef/ClassGenEnum)后索引槽成为
# 失效键:查找探测时键不等 → 继续探测/判定未命中,不会返回失效条目。
# ============================================================
def _struct_entry_name(self, i: int) -> str:
"""取第 i 个条目的 Name(供索引探测比较),越界/空返回 None"""
e: GenTypes.StructEntry | t.CPtr = self._get_struct_entry(i)
if e is None:
return None
return e.Name
def _struct_entry_tyval(self, i: int) -> uint64:
"""取第 i 个条目的 Ty 指针值(Ty=None 返回 0)"""
e: GenTypes.StructEntry | t.CPtr = self._get_struct_entry(i)
if e is None or e.Ty is None:
return 0
return uint64(e.Ty)
def _struct_name_idx_insert(self, ei: int, name: str) -> int:
"""把 (name → ei) 写入名索引;返回 0=成功 / 1=失败
每次注册都占用一个槽(不按键去重):同键的多个条目按注册顺序沿探测链
递增排列,查找取链上首个"当前键相等"者即 = 原扫描的首个命中。若按键去重,
则最早的重复项被中和(Name[0]=0)后,被丢弃的后续重复项将无法找回。
"""
base: sizet | t.CPtr = self.StructNameIdx
if base is None or self.StructIdxCap <= 0:
return 1
h: uint64 = _struct_name_hash(name)
mask: sizet = sizet(self.StructIdxCap - 1)
idx: sizet = sizet(h) & mask
i: int = 0
while i < self.StructIdxCap:
slot: sizet | t.CPtr = (sizet | t.CPtr)(
t.CVoid(uint64(base) + uint64(idx * 8), t.CPtr))
if slot[0] == 0:
slot[0] = sizet(ei + 1)
return 0
idx = (idx + 1) & mask
i += 1
return 1
def _struct_type_idx_insert(self, ei: int, tyval: uint64) -> int:
"""把 (Ty 指针值 → ei) 写入类型索引;0=成功 / 1=失败
与名索引同策略:每次注册都占槽(不去重),保证探测链上同键条目按注册
顺序排列,查找首个"当前 Ty 相等"者 = 原扫描首个命中。
"""
base: sizet | t.CPtr = self.StructTypeIdx
if base is None or self.StructIdxCap <= 0 or tyval == 0:
return 1
h: uint64 = _struct_ty_hash(tyval)
mask: sizet = sizet(self.StructIdxCap - 1)
idx: sizet = sizet(h) & mask
i: int = 0
while i < self.StructIdxCap:
slot: sizet | t.CPtr = (sizet | t.CPtr)(
t.CVoid(uint64(base) + uint64(idx * 8), t.CPtr))
if slot[0] == 0:
slot[0] = sizet(ei + 1)
return 0
idx = (idx + 1) & mask
i += 1
return 1
def _struct_idx_add(self, ei: int, entry: GenTypes.StructEntry | t.CPtr) -> None:
"""新增条目后同步两张索引;任一写入失败 → 标记索引不可信(查找回退扫描)"""
if self.StructNameIdx is None or self.StructTypeIdx is None or self.StructIdxCap <= 0:
self.StructIdxBuilt = -1
return
failed: int = 0
if entry is not None and entry.Name is not None:
failed = failed + self._struct_name_idx_insert(ei, entry.Name)
if entry is not None and entry.Ty is not None:
failed = failed + self._struct_type_idx_insert(ei, uint64(entry.Ty))
if failed != 0:
self.StructIdxBuilt = -1
else:
# ei = StructCount-1,故同步后已建索引条目数 = StructCount
self.StructIdxBuilt = self.StructCount
def _struct_name_idx_find(self, name: str) -> int:
"""名索引查首注册下标:>=0 命中(= 原扫描首个同名条目下标);否则 -1
-1 表示"未命中或索引不可用",调用方须回退原线性扫描(保证正确性)。
探测链上同键条目按注册顺序排列,故首个"当前键相等"者即原扫描首个命中。
"""
base: sizet | t.CPtr = self.StructNameIdx
if base is None or self.StructIdxCap <= 0 or self.StructIdxBuilt != self.StructCount:
return -1
# 空名("")特例:被中和的条目 Name 恰为 ""(Name[0]=0),原扫描会命中首个
# 空名条目;索引槽按"注册时的原名"哈希放置,无法保证给出首个空名条目 →
# 空名一律回退原扫描,保证逐字节一致。
if name is None or name[0] == '\0':
return -1
h: uint64 = _struct_name_hash(name)
mask: sizet = sizet(self.StructIdxCap - 1)
idx: sizet = sizet(h) & mask
i: int = 0
while i < self.StructIdxCap:
slot: sizet | t.CPtr = (sizet | t.CPtr)(
t.CVoid(uint64(base) + uint64(idx * 8), t.CPtr))
sv: sizet = slot[0]
if sv == 0:
return -1
se: int = int(sv) - 1
ek: str = self._struct_entry_name(se)
if ek is not None and string.strcmp(ek, name) == 0:
return se
idx = (idx + 1) & mask
i += 1
return -1
def _struct_type_idx_find(self, tyval: uint64) -> int:
"""类型索引查首注册下标:>=0 命中;-1 未命中/不可信(须回退扫描)
类型索引不保证覆盖全部 Ty 值(如 ClassGenDef 深拷贝后 existing.Ty 被
改写为新指针,未入索引)⇒ 未命中不能断言"确无",一律回退线性扫描。
"""
base: sizet | t.CPtr = self.StructTypeIdx
if base is None or self.StructIdxCap <= 0 or tyval == 0:
return -1
if self.StructIdxBuilt != self.StructCount:
return -1
h: uint64 = _struct_ty_hash(tyval)
mask: sizet = sizet(self.StructIdxCap - 1)
idx: sizet = sizet(h) & mask
i: int = 0
while i < self.StructIdxCap:
slot: sizet | t.CPtr = (sizet | t.CPtr)(
t.CVoid(uint64(base) + uint64(idx * 8), t.CPtr))
sv: sizet = slot[0]
if sv == 0:
return -1
se: int = int(sv) - 1
if self._struct_entry_tyval(se) == tyval:
return se
idx = (idx + 1) & mask
i += 1
return -1
def _get_field_entry(self, struct_entry: GenTypes.StructEntry | t.CPtr, i: int) -> GenTypes.FieldEntry | t.CPtr:
"""获取结构体中第 i 个字段条目"""
if struct_entry is None or i < 0 or i >= FIELD_MAX:
@@ -178,12 +393,13 @@ class StructGenMixin:
# 用类型指针去重(同一类型不重复注册,支持跨模块同名类)
# 用 uint64 指针值比较(is 比较会触发类型安全检查解引用悬空 Ty)
st_dedup: uint64 = uint64(struct_ty)
for i in range(self.StructCount):
existing: GenTypes.StructEntry | t.CPtr = self._get_struct_entry(i)
if existing is not None and existing.Ty is not None:
if uint64(existing.Ty) == st_dedup:
return existing
# 类型索引快速路径:find_struct_by_type 的 Ty 指针身份语义 = 原去重扫描
# "首个 Ty 指针相等条目"(首注册优先),且其索引未命中 | 不可信时会回退
# 原线性扫描(正确性保证,非兜底)⇒ 与原扫描结果逐字节一致。
# 注册途中索引恒与 StructCount 同步(每次注册后 _struct_idx_add 同步)。
existing: GenTypes.StructEntry | t.CPtr = self.find_struct_by_type(struct_ty)
if existing is not None:
return existing
if self.StructCount >= STRUCT_MAX:
VLogger.error(f"table full, cannot register {name}", "STRUCT")
@@ -234,6 +450,8 @@ class StructGenMixin:
entry.ModuleSha1 = sha1_buf
self.StructCount += 1
# 同步名 | 类型索引(新条目下标 = StructCount-1)
self._struct_idx_add(self.StructCount - 1, entry)
return entry
def register_struct_alias(self, pool: rowptr[memhub.MemBuddy],
@@ -302,6 +520,8 @@ class StructGenMixin:
entry.MethodsEmitted = 0
entry.SpecTriggerSha1 = None
self.StructCount += 1
# 同步名 | 类型索引(别名条目与源条目同名 | 同 Ty,索引保留首注册下标)
self._struct_idx_add(self.StructCount - 1, entry)
return entry
def set_struct_sha1(self, pool: rowptr[memhub.MemBuddy],
@@ -565,6 +785,12 @@ class StructGenMixin:
if self.StructCount < 0 or self.StructCount > 200000:
VLogger.error(f"StructCount 异常: {self.StructCount} (find_struct)", "STRUCT")
stdlib.exit(1)
# 名索引快速路径:命中即首注册下标(语义 = 原精确匹配扫描首个命中)。
# 未命中/索引不可信 → 回退下方原精确匹配扫描(保证正确性);
# 精确匹配确无时继续走模块限定名/裸名回退逻辑(不提前 return None)。
fi: int = self._struct_name_idx_find(name)
if fi >= 0:
return self._get_struct_entry(fi)
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:
@@ -657,7 +883,14 @@ class StructGenMixin:
VLogger.error(f"StructCount 异常: {self.StructCount} (find_struct_by_module)", "STRUCT")
stdlib.exit(1)
if sha1 is not None:
for i in range(self.StructCount):
# 名索引快速路径:同名条目都位于 >= 首个同名下标处,故可从该下标起扫描
# name+sha1(等价原全表扫描"首个 name 且 sha1 命中")。
# 未命中/索引不可信 ⇒ start=0,走原全表扫描(保证正确性)。
start: int = 0
fri: int = self._struct_name_idx_find(name)
if fri >= 0:
start = fri
for i in range(start, 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:
@@ -723,6 +956,12 @@ 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)
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:
@@ -774,19 +1013,18 @@ class StructGenMixin:
# ============================================================
# 第一遍: is 身份比较(用 uint64 指针值比较,避免 is 编译路径解引用悬空 Ty)
st_lk: uint64 = uint64(struct_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:
if uint64(entry.Ty) == st_lk:
# is 匹配,查找字段
for fi in range(entry.FieldCount):
fe: GenTypes.FieldEntry | t.CPtr = self._get_field_entry(entry, fi)
if fe is not None and fe.Name is not None:
if string.strcmp(fe.Name, field_name) == 0:
return fe
# is 匹配但字段未找到(类型唯一确定,无需继续)
return None
# 类型索引快速路径:find_struct_by_type 的语义 = 原扫描"首个 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):
fe: GenTypes.FieldEntry | t.CPtr = self._get_field_entry(lk_entry, fi)
if fe is not None and fe.Name is not None:
if string.strcmp(fe.Name, field_name) == 0:
return fe
return None
# 第二遍: 名称回退(is 未匹配到)
ty_name: str = self._extract_struct_name(struct_ty)
@@ -1262,12 +1500,12 @@ class StructGenMixin:
"""按类型指针查找类名,返回类名字符串或 None"""
if struct_ty is None or self.StructTable is None:
return None
st_cn: uint64 = uint64(struct_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:
if uint64(entry.Ty) == st_cn:
return entry.Name
# 类型索引快速路径:find_struct_by_type 的 Ty 指针身份语义 = 原扫描
# "首个 Ty 指针相等条目"(首注册优先),返回其 Name;索引未命中 |
# 不可信时其内部回退原线性扫描(正确性保证,非兜底)⇒ 逐字节一致。
fast_cn: GenTypes.StructEntry | t.CPtr = self.find_struct_by_type(struct_ty)
if fast_cn is not None:
return fast_cn.Name
# 回退: 类型指针比较失败时,遍历所有注册的结构体按名称匹配
# 提取 struct_ty 的 Name 字段(可能是 "SHA1.ClassName" 格式)
ty_name: str = self._extract_struct_name(struct_ty)
+111 -11
View File
@@ -281,6 +281,27 @@ class TypeSpecArena:
Specs: sizet | t.CPtr
Count: int
Cap: int
# 规范文本键 → 驻留下标 的开放寻址哈希索引(槽为 u64:0=空槽,否则 index+1)。
# 可选加速层:为 None 时 InternSpec 回退原线性扫描(语义完全一致)。
Index: sizet | t.CPtr
# 索引槽数(2 的幂,> 2*Cap;仅 Index 非 None 时有效)
IndexCap: int
# 驻留哈希索引 FNV-1a 64 位常量(与 hashtable.vp 同款)
_SPEC_FNV_OFFSET: t.CUInt64T = 0xcbf29ce484222325
_SPEC_FNV_PRIME: t.CUInt64T = 0x100000001b3
def _spec_key_hash(key: str) -> uint64:
"""规范文本键的 FNV-1a 64 位哈希(同一键必得同一值;仅用于槽定位)"""
h: t.CUInt64T = _SPEC_FNV_OFFSET
k: str = key
while k[0] != 0:
h = h ^ t.CUInt64T(k[0])
h = h * _SPEC_FNV_PRIME
k += 1
return uint64(h)
# ============================================================
@@ -294,6 +315,56 @@ def _spec_key_at(keys: bytes, index: int) -> str:
return (str | t.CPtr)(t.CVoid(addr, t.CPtr))
# ============================================================
# _spec_index_find - 在驻留哈希索引中查找键,返回条目下标(未命中/不可用返回 -1)
# 槽存 index+1(0=空槽);线性探测,逐槽比较键文本——必须按键内容区分,
# 不能只比哈希(否则冲突键会误命中并返回错误实例)。
# ============================================================
def _spec_index_find(arena: TypeSpecArena | t.CPtr, key: str, h: uint64) -> int:
"""查驻留哈希索引,命中返回条目下标;未命中返回 -1"""
if arena.Index is None or arena.IndexCap <= 0:
return -1
mask: sizet = sizet(arena.IndexCap - 1)
idx: sizet = sizet(h) & mask
i: int = 0
while i < arena.IndexCap:
slot: sizet | t.CPtr = (sizet | t.CPtr)(
t.CVoid(uint64(arena.Index) + uint64(idx * 8), t.CPtr))
sv: sizet = slot[0]
if sv == 0:
return -1
ei: int = int(sv) - 1
ek: str = _spec_key_at(arena.Keys, ei)
if ek is not None and string.strcmp(ek, key) == 0:
return ei
idx = (idx + 1) & mask
i += 1
return -1
# ============================================================
# _spec_index_insert - 把条目 ei 写入驻留哈希索引
# 返回 0=成功 / 1=索引不可用或已满。仅在追加新条目后调用
# (条目只增不删,故无需墓碑;槽数 > 2*Cap ⇒ 必然存在空槽,探测终止)。
# ============================================================
def _spec_index_insert(arena: TypeSpecArena | t.CPtr, key: str, h: uint64, ei: int) -> int:
"""把下标 ei 写入驻留哈希索引,返回 0=成功 / 1=不可用"""
if arena.Index is None or arena.IndexCap <= 0:
return 1
mask: sizet = sizet(arena.IndexCap - 1)
idx: sizet = sizet(h) & mask
i: int = 0
while i < arena.IndexCap:
slot: sizet | t.CPtr = (sizet | t.CPtr)(
t.CVoid(uint64(arena.Index) + uint64(idx * 8), t.CPtr))
if slot[0] == 0:
slot[0] = sizet(ei + 1)
return 0
idx = (idx + 1) & mask
i += 1
return 1
# ============================================================
# SpecArenaInit - 初始化驻留表(幂等;cap<=0 时用 SPEC_INTERN_MAX)
# 返回 0=成功 / 1=失败(分配失败 fail-fast 由调用方处理,此处返回错误码)
@@ -320,6 +391,20 @@ def SpecArenaInit(arena: TypeSpecArena | t.CPtr, cap: int) -> int:
arena.Specs = specs
arena.Count = 0
arena.Cap = use_cap
# 建驻留哈希索引(槽数取 2 的幂且 > 2*Cap ⇒ 负载 < 0.5)。索引是可选加速层:
# 分配失败则保持 None,InternSpec 自动回退线性扫描(语义完全一致),
# 故不因索引失败而使整个初始化失败(不新增失败面)。
icap: int = 1
while icap < use_cap * 2:
icap = icap * 2
idx_buf: sizet | t.CPtr = pp.alloc(icap * 8)
if idx_buf is not None:
string.memset(idx_buf, 0, icap * 8)
arena.Index = idx_buf
arena.IndexCap = icap
else:
arena.Index = None
arena.IndexCap = 0
return 0
@@ -831,17 +916,29 @@ def InternSpec(arena: TypeSpecArena | t.CPtr,
# 关键在于**绝不能把截断键写入表**:截断键会让后续 strcmp 撞上别的类型
# 并返回**错误实例**(2026-09-11 实测的格式化闸门误报根因)。
return sp
# 线性查找已有键(规模与类型种数同阶,量级不大;命中即复用)
i: int = 0
while i < arena.Count:
exist_key: str = _spec_key_at(arena.Keys, i)
if exist_key is not None and string.strcmp(exist_key, key_buf) == 0:
slot_e: sizet | t.CPtr = (sizet | t.CPtr)(
t.CVoid(uint64(arena.Specs) + uint64(i * 8), t.CPtr))
if slot_e is None:
return None
return (TypeSpec | t.CPtr)(t.CVoid(uint64(slot_e[0]), t.CPtr))
i += 1
# 哈希索引快速路径:期望 O(1)。索引在下方追加处同步维护(条目只增不删)⇒
# 索引"完整":未命中即可直接追加,无需回退线性扫描。仅当索引不可用(分配失败/
# 旧表未建)时回退原线性扫描 —— 这是保证正确性的回退,非静默兜底。
key_hash: uint64 = _spec_key_hash(key_buf)
found: int = _spec_index_find(arena, key_buf, key_hash)
if found >= 0:
slot_e: sizet | t.CPtr = (sizet | t.CPtr)(
t.CVoid(uint64(arena.Specs) + uint64(found * 8), t.CPtr))
if slot_e is None:
return None
return (TypeSpec | t.CPtr)(t.CVoid(uint64(slot_e[0]), t.CPtr))
if arena.Index is None:
# 索引不可用:回退原线性查找(规模与类型种数同阶)
i: int = 0
while i < arena.Count:
exist_key: str = _spec_key_at(arena.Keys, i)
if exist_key is not None and string.strcmp(exist_key, key_buf) == 0:
slot_e2: sizet | t.CPtr = (sizet | t.CPtr)(
t.CVoid(uint64(arena.Specs) + uint64(i * 8), t.CPtr))
if slot_e2 is None:
return None
return (TypeSpec | t.CPtr)(t.CVoid(uint64(slot_e2[0]), t.CPtr))
i += 1
# 未命中:追加。**表满 = fail-fast**(Debugger/_Type.md D14 风险 2):
# 不能返回 None —— 调用链会把 None 一路传到 entry.Spec,条目**静默失去类型信息**
# (如 _entry_spec_unsigned 恒返回 0 ⇒ zext/sext 方向取错),属静默数据损坏,
@@ -863,6 +960,9 @@ def InternSpec(arena: TypeSpecArena | t.CPtr,
if slot_n is None:
return None
slot_n[0] = sizet(sp)
# 同步写入哈希索引(下标 = 追加前的 Count)。插入失败仅意味着后续查找对该键
# 回退线性扫描,不影响正确性。
_spec_index_insert(arena, key_buf, key_hash, arena.Count)
arena.Count += 1
return sp