From 76df3f0225aa64f9569eaca696380aef31d2a0a3 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Sun, 13 Sep 2026 18:56:16 +0800 Subject: [PATCH] =?UTF-8?q?perf(gen):=20=E7=BB=93=E6=9E=84=E4=BD=93/?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E6=9F=A5=E6=89=BE=E5=8E=BB=E7=BA=BF=E6=80=A7?= =?UTF-8?q?=E5=8C=96=E5=B9=B6=E5=AE=88=E5=8D=AB=E5=85=A8=E9=87=8F=E9=81=8D?= =?UTF-8?q?=E5=8E=86=E6=AE=8B=E9=87=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 的按序号全量遍历补守卫注释(避免后续误改为按名查找)。 --- App/lib/core/Gen/Call/CallGenCall.vp | 4 + App/lib/core/Gen/Call/CallGenLeafKit.vp | 18 +- App/lib/core/Gen/StructGenMixin.vp | 290 +++++++++++++++++++++--- App/lib/core/Gen/Type/TypeSpec.vp | 122 +++++++++- 4 files changed, 387 insertions(+), 47 deletions(-) diff --git a/App/lib/core/Gen/Call/CallGenCall.vp b/App/lib/core/Gen/Call/CallGenCall.vp index 02f48a0..a641a6e 100644 --- a/App/lib/core/Gen/Call/CallGenCall.vp +++ b/App/lib/core/Gen/Call/CallGenCall.vp @@ -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: diff --git a/App/lib/core/Gen/Call/CallGenLeafKit.vp b/App/lib/core/Gen/Call/CallGenLeafKit.vp index ea46bc6..09189da 100644 --- a/App/lib/core/Gen/Call/CallGenLeafKit.vp +++ b/App/lib/core/Gen/Call/CallGenLeafKit.vp @@ -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 diff --git a/App/lib/core/Gen/StructGenMixin.vp b/App/lib/core/Gen/StructGenMixin.vp index b34214d..349d457 100644 --- a/App/lib/core/Gen/StructGenMixin.vp +++ b/App/lib/core/Gen/StructGenMixin.vp @@ -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) diff --git a/App/lib/core/Gen/Type/TypeSpec.vp b/App/lib/core/Gen/Type/TypeSpec.vp index fca7f19..a8d9f5d 100644 --- a/App/lib/core/Gen/Type/TypeSpec.vp +++ b/App/lib/core/Gen/Type/TypeSpec.vp @@ -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