diff --git a/App/lib/Stub/StubGen/VpiReader.vp b/App/lib/Stub/StubGen/VpiReader.vp index 8fc1908..54b47ce 100644 --- a/App/lib/Stub/StubGen/VpiReader.vp +++ b/App/lib/Stub/StubGen/VpiReader.vp @@ -58,11 +58,176 @@ VPI_PARAM_MAX: t.CDefine = 4096 VPI_PARAM_KEY_LEN: t.CDefine = 224 VPI_PARAM_LEN: t.CDefine = 1024 +# ============================================================ +# 键表开放寻址索引(名 → 槽下标) +# +# 两张键表(返回注解 / 参数注解)都是定长槽数组(每槽 VPI_*_KEY_LEN 字节), +# 插入只追加、从不重排 ⇒ 索引可只存"槽下标"(比存指针更省,且 realloc 移动 +# 键表基址后槽下标仍有效)。索引槽数恒为键表容量的 2 倍(2 的幂,负载 < 0.5, +# 线性探测必然终止)。每次注册占一槽(不按键去重):同键条目沿探测链按注册 +# 顺序递增排列,查找取链上首个"当前键相等"者 = 原线性扫描首个命中(首注册 +# 优先)。可信判据 = Built == Count;不可信/未命中一律回退原线性扫描。 +# ============================================================ +VPI_RET_IDX_CAP: t.CDefine = 8192 +VPI_PARAM_IDX_CAP: t.CDefine = 8192 +# 索引哈希常量(32 位 FNV-1a,与 StructGenMixin 范式一致) +VPI_IDX_FNV_OFFSET: t.CDefine = 2166136261 +VPI_IDX_FNV_PRIME: t.CDefine = 16777619 +VPI_IDX_FNV_MASK: t.CDefine = 4294967295 + _g_vpi_param_keys: bytes = None _g_vpi_param_strs: bytes = None _g_vpi_param_count: int = 0 _g_vpi_param_cap: int = 0 +# 返回注解键表索引(与键表同 malloc 生命周期;Built == Count 才可信) +_g_vpi_ret_idx: bytes = None +_g_vpi_ret_idx_cap: int = 0 +_g_vpi_ret_idx_built: int = 0 +# 参数注解键表索引(同上) +_g_vpi_param_idx: bytes = None +_g_vpi_param_idx_cap: int = 0 +_g_vpi_param_idx_built: int = 0 + + +# ============================================================ +# 索引探测/插入/重建(模块级函数,供两张键表共用) +# ============================================================ +def _vpi_idx_hash(key: bytes) -> uint64: + """键串 → 64 位哈希(32 位 FNV-1a,逐字节到 NUL 结束;空键返回偏移基值)""" + h: uint64 = uint64(VPI_IDX_FNV_OFFSET) + if key is None: + return h + i: sizet = 0 + while key[i] != '\0': + h = (h ^ uint64(int(key[i]))) * uint64(VPI_IDX_FNV_PRIME) + h = h & uint64(VPI_IDX_FNV_MASK) + i += 1 + return h + + +def _vpi_idx_slot(idx_base: bytes, si: sizet) -> sizet | t.CPtr: + """取索引第 si 个槽(8 字节,存 slot_index+1;0=空槽)""" + return (sizet | t.CPtr)(t.CVoid(uint64(idx_base) + uint64(si) * 8, t.CPtr)) + + +def _vpi_idx_find(keys: bytes, key_stride: int, idx_base: bytes, + cap: int, key: bytes) -> int: + """在索引中探测 key,返回命中槽下标(>=0);未命中/索引不可用返回 -1 + + 命中判据是"当前键相等"(strcmp),不能只比哈希。 + """ + if keys is None or idx_base is None or cap <= 0 or key is None: + return -1 + h: uint64 = _vpi_idx_hash(key) + mask: sizet = sizet(cap - 1) + si: sizet = sizet(h) & mask + i: int = 0 + while i < cap: + slot: sizet | t.CPtr = _vpi_idx_slot(idx_base, si) + sv: sizet = slot[0] + if sv == 0: + return -1 + se: int = int(sv) - 1 + ek: bytes = keys + se * key_stride + if string.strcmp(ek, key) == 0: + return se + si = (si + 1) & mask + i += 1 + return -1 + + +def _vpi_idx_insert(idx_base: bytes, cap: int, slot_index: int, key: bytes) -> int: + """把 (key → slot_index) 记入索引(每次注册占一槽,不去重);0=成功 / 1=失败""" + if idx_base is None or cap <= 0 or key is None: + return 1 + h: uint64 = _vpi_idx_hash(key) + mask: sizet = sizet(cap - 1) + si: sizet = sizet(h) & mask + i: int = 0 + while i < cap: + slot: sizet | t.CPtr = _vpi_idx_slot(idx_base, si) + if slot[0] == 0: + slot[0] = sizet(slot_index + 1) + return 0 + si = (si + 1) & mask + i += 1 + return 1 + + +def _vpi_rebuild_ret_idx(new_cap: int) -> int: + """随返回注解键表扩容重建索引(槽数=new_cap,2 的幂);0=可信 / 1=不可信 + + 槽存下标非指针:键表 realloc 后仅基址变,槽下标与键内容均未变,故重建 + 只需从新基址逐条重放现有 count 个键即可(顺序不变 ⇒ 首注册优先不变)。 + """ + global _g_vpi_ret_idx, _g_vpi_ret_idx_cap, _g_vpi_ret_idx_built + if new_cap <= 0: + _g_vpi_ret_idx = None + _g_vpi_ret_idx_cap = 0 + _g_vpi_ret_idx_built = -1 + return 1 + new_idx: bytes = None + if _g_vpi_ret_idx is None: + new_idx = stdlib.malloc(new_cap * 8) + else: + new_idx = stdlib.realloc(_g_vpi_ret_idx, new_cap * 8) + if new_idx is None: + _g_vpi_ret_idx = None + _g_vpi_ret_idx_cap = 0 + _g_vpi_ret_idx_built = -1 + return 1 + string.memset(new_idx, 0, sizet(new_cap) * 8) + _g_vpi_ret_idx = new_idx + _g_vpi_ret_idx_cap = new_cap + fail: int = 0 + ri: int + for ri in range(_g_vpi_ret_count): + if _vpi_idx_insert(new_idx, new_cap, ri, + _g_vpi_ret_keys + ri * VPI_RET_KEY_LEN) != 0: + fail = 1 + break + if fail != 0: + _g_vpi_ret_idx_built = -1 + return 1 + _g_vpi_ret_idx_built = _g_vpi_ret_count + return 0 + + +def _vpi_rebuild_param_idx(new_cap: int) -> int: + """随参数注解键表扩容重建索引(同 _vpi_rebuild_ret_idx);0=可信 / 1=不可信""" + global _g_vpi_param_idx, _g_vpi_param_idx_cap, _g_vpi_param_idx_built + if new_cap <= 0: + _g_vpi_param_idx = None + _g_vpi_param_idx_cap = 0 + _g_vpi_param_idx_built = -1 + return 1 + new_idx: bytes = None + if _g_vpi_param_idx is None: + new_idx = stdlib.malloc(new_cap * 8) + else: + new_idx = stdlib.realloc(_g_vpi_param_idx, new_cap * 8) + if new_idx is None: + _g_vpi_param_idx = None + _g_vpi_param_idx_cap = 0 + _g_vpi_param_idx_built = -1 + return 1 + string.memset(new_idx, 0, sizet(new_cap) * 8) + _g_vpi_param_idx = new_idx + _g_vpi_param_idx_cap = new_cap + fail: int = 0 + ri: int + for ri in range(_g_vpi_param_count): + if _vpi_idx_insert(new_idx, new_cap, ri, + _g_vpi_param_keys + ri * VPI_PARAM_KEY_LEN) != 0: + fail = 1 + break + if fail != 0: + _g_vpi_param_idx_built = -1 + return 1 + _g_vpi_param_idx_built = _g_vpi_param_count + return 0 + # ============================================================ # _cache_vpi_return_annots_text - 从 vpi_buf 文本直接提取方法返回注解 @@ -76,6 +241,7 @@ _g_vpi_param_cap: int = 0 def _cache_vpi_return_annots_text(vpi_buf: bytes, src_sha1: str) -> int: """从 vpi_buf 文本提取方法返回注解字符串(stdlib 持久)""" global _g_vpi_ret_keys, _g_vpi_ret_strs, _g_vpi_ret_count, _g_vpi_ret_cap + global _g_vpi_ret_idx, _g_vpi_ret_idx_cap, _g_vpi_ret_idx_built if vpi_buf is None or src_sha1 is None: return 0 if _g_vpi_ret_keys is None: @@ -86,6 +252,16 @@ def _cache_vpi_return_annots_text(vpi_buf: bytes, src_sha1: str) -> int: string.memset(_g_vpi_ret_keys, 0, VPI_RET_MAX * VPI_KEY_LEN) string.memset(_g_vpi_ret_strs, 0, VPI_RET_MAX * VPI_RET_LEN) _g_vpi_ret_cap = VPI_RET_MAX + # 名索引:槽数 = 键表容量 * 2。分配失败则保持不可信(Built=-1), + # 查找/写去重一律回退原线性扫描(语义完全一致,不因此使缓存失败)。 + _g_vpi_ret_idx = stdlib.malloc(VPI_RET_IDX_CAP * 8) + if _g_vpi_ret_idx is not None: + string.memset(_g_vpi_ret_idx, 0, VPI_RET_IDX_CAP * 8) + _g_vpi_ret_idx_cap = VPI_RET_IDX_CAP + _g_vpi_ret_idx_built = _g_vpi_ret_count + else: + _g_vpi_ret_idx_cap = 0 + _g_vpi_ret_idx_built = -1 sha1_len: sizet = string.strlen(src_sha1) buf_len: sizet = string.strlen(vpi_buf) cur_cls: bytes = None @@ -189,10 +365,23 @@ def _cache_vpi_return_annots_text(vpi_buf: bytes, src_sha1: str) -> int: # 去重:同一 key 已存在则跳过(同文件可能被多次加载) dup_key: int = 0 dki: int - for dki in range(_g_vpi_ret_count): - if string.strcmp(_g_vpi_ret_keys + dki * VPI_KEY_LEN, key_buf) == 0: + # 索引快速路径:索引可信(Built==Count)时一次探测判定 + # 重复(命中即"首个注册优先"= 原线性扫描首个同名); + # 未命中 | 索引不可信 → 回退原线性扫描(正确性保证)。 + 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: + if _vpi_idx_find(_g_vpi_ret_keys, VPI_KEY_LEN, + _g_vpi_ret_idx, _g_vpi_ret_idx_cap, key_buf) >= 0: dup_key = 1 - break + else: + # 快速路径:重复键必首字节相同(strcmp 判等蕴含首字节 + # 相等),键首字节 = src_sha1[0],首字节不同者跳过 strcmp。 + dup_first: int = key_buf[0] + for dki in range(_g_vpi_ret_count): + ent_d: bytes = _g_vpi_ret_keys + dki * VPI_KEY_LEN + if ent_d[0] == dup_first and string.strcmp(ent_d, key_buf) == 0: + dup_key = 1 + break grow_ok: int = 1 if dup_key == 0 and _g_vpi_ret_count >= _g_vpi_ret_cap: grow_ok = _grow_vpi_ret_cache() @@ -202,6 +391,14 @@ def _cache_vpi_return_annots_text(vpi_buf: bytes, src_sha1: str) -> int: ret_src, rtype_len) (_g_vpi_ret_strs + _g_vpi_ret_count * VPI_RET_LEN)[rtype_len] = '\0' _g_vpi_ret_count += 1 + # 同步索引:仅当插入前可信(Built==Count-1,含扩容 + # 重建后)且插入成功;否则标记不可信(查找回退扫描)。 + if _g_vpi_ret_idx_built == _g_vpi_ret_count - 1 \ + and _vpi_idx_insert(_g_vpi_ret_idx, _g_vpi_ret_idx_cap, + _g_vpi_ret_count - 1, key_buf) == 0: + _g_vpi_ret_idx_built = _g_vpi_ret_count + else: + _g_vpi_ret_idx_built = -1 stdlib.free(key_buf) stdlib.free(mname_buf) pos = le + 1 @@ -225,6 +422,7 @@ def _cache_vpi_return_annots_text(vpi_buf: bytes, src_sha1: str) -> int: def _cache_vpi_param_annots_text(vpi_buf: bytes, src_sha1: str) -> int: """从 vpi_buf 文本提取函数/方法参数注解字符串(stdlib 持久)""" global _g_vpi_param_keys, _g_vpi_param_strs, _g_vpi_param_count, _g_vpi_param_cap + global _g_vpi_param_idx, _g_vpi_param_idx_cap, _g_vpi_param_idx_built if vpi_buf is None or src_sha1 is None: return 0 if _g_vpi_param_keys is None: @@ -235,6 +433,16 @@ def _cache_vpi_param_annots_text(vpi_buf: bytes, src_sha1: str) -> int: string.memset(_g_vpi_param_keys, 0, VPI_PARAM_MAX * VPI_PARAM_KEY_LEN) string.memset(_g_vpi_param_strs, 0, VPI_PARAM_MAX * VPI_PARAM_LEN) _g_vpi_param_cap = VPI_PARAM_MAX + # 名索引:槽数 = 键表容量 * 2。分配失败则保持不可信(Built=-1), + # 查找/写去重一律回退原线性扫描(语义完全一致,不因此使缓存失败)。 + _g_vpi_param_idx = stdlib.malloc(VPI_PARAM_IDX_CAP * 8) + if _g_vpi_param_idx is not None: + string.memset(_g_vpi_param_idx, 0, VPI_PARAM_IDX_CAP * 8) + _g_vpi_param_idx_cap = VPI_PARAM_IDX_CAP + _g_vpi_param_idx_built = _g_vpi_param_count + else: + _g_vpi_param_idx_cap = 0 + _g_vpi_param_idx_built = -1 sha1_len: sizet = string.strlen(src_sha1) buf_len: sizet = string.strlen(vpi_buf) cur_cls: bytes = None @@ -386,10 +594,23 @@ def _cache_vpi_param_annots_text(vpi_buf: bytes, src_sha1: str) -> int: viperlib.snprintf(key_buf, key_len + 1, "%s.%s", src_sha1, mname_buf) dup_key: int = 0 dki: int - for dki in range(_g_vpi_param_count): - if string.strcmp(_g_vpi_param_keys + dki * VPI_PARAM_KEY_LEN, key_buf) == 0: + # 索引快速路径:索引可信(Built==Count)时一次探测判定 + # 重复(命中即"首个注册优先"= 原线性扫描首个同名); + # 未命中 | 索引不可信 → 回退原线性扫描(正确性保证)。 + 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: + if _vpi_idx_find(_g_vpi_param_keys, VPI_PARAM_KEY_LEN, + _g_vpi_param_idx, _g_vpi_param_idx_cap, key_buf) >= 0: dup_key = 1 - break + else: + # 快速路径:重复键必首字节相同(strcmp 判等蕴含首字节 + # 相等),键首字节 = src_sha1[0],首字节不同者跳过 strcmp。 + dup_first: int = key_buf[0] + for dki in range(_g_vpi_param_count): + ent_d: bytes = _g_vpi_param_keys + dki * VPI_PARAM_KEY_LEN + if ent_d[0] == dup_first and string.strcmp(ent_d, key_buf) == 0: + dup_key = 1 + break grow_ok: int = 1 if dup_key == 0 and _g_vpi_param_count >= _g_vpi_param_cap: grow_ok = _grow_vpi_param_cache() @@ -399,6 +620,14 @@ def _cache_vpi_param_annots_text(vpi_buf: bytes, src_sha1: str) -> int: plist, plist_pos) (_g_vpi_param_strs + _g_vpi_param_count * VPI_PARAM_LEN)[plist_pos] = '\0' _g_vpi_param_count += 1 + # 同步索引:仅当插入前可信(Built==Count-1,含扩容 + # 重建后)且插入成功;否则标记不可信(查找回退扫描)。 + if _g_vpi_param_idx_built == _g_vpi_param_count - 1 \ + and _vpi_idx_insert(_g_vpi_param_idx, _g_vpi_param_idx_cap, + _g_vpi_param_count - 1, key_buf) == 0: + _g_vpi_param_idx_built = _g_vpi_param_count + else: + _g_vpi_param_idx_built = -1 stdlib.free(key_buf) stdlib.free(mname_buf) stdlib.free(plist) @@ -428,6 +657,8 @@ def _grow_vpi_param_cache() -> int: _g_vpi_param_keys = new_keys _g_vpi_param_strs = new_strs _g_vpi_param_cap = new_cap + # 索引随表同步扩容并整体重建(槽数 = 表容量 * 2,恒为 2 的幂) + _vpi_rebuild_param_idx(new_cap * 2) return 1 @@ -455,10 +686,24 @@ def _lookup_vpi_param_annots(src_sha1: str, cls_name: str, method_name: str) -> viperlib.snprintf(key_buf, key_len + 1, "%s.%s", src_sha1, method_name) r: str = None ki: int - for ki in range(_g_vpi_param_count): - if string.strcmp(_g_vpi_param_keys + ki * VPI_PARAM_KEY_LEN, key_buf) == 0: - r = _g_vpi_param_strs + ki * VPI_PARAM_LEN - break + # 索引快速路径:索引可信(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(仅做否定判定,不改变命中)。 + 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 + if ent_p[0] == key_first and string.strcmp(ent_p, key_buf) == 0: + hit_p = ki + break + if hit_p >= 0: + r = _g_vpi_param_strs + hit_p * VPI_PARAM_LEN stdlib.free(key_buf) return r @@ -597,6 +842,8 @@ def _grow_vpi_ret_cache() -> int: _g_vpi_ret_keys = new_keys _g_vpi_ret_strs = new_strs _g_vpi_ret_cap = new_cap + # 索引随表同步扩容并整体重建(槽数 = 表容量 * 2,恒为 2 的幂) + _vpi_rebuild_ret_idx(new_cap * 2) return 1 @@ -622,9 +869,14 @@ def _ensure_vpi_ret_annot_loaded(src_sha1: str, temp_dir: str) -> int: # 该模块已有缓存条目 → 跳过(幂等) if _g_vpi_ret_keys is not None and _g_vpi_ret_count > 0: pre_i: int + # 快速路径:前缀匹配必首两字节相等(strncmp 判等蕴含前缀逐字节相等), + # src_sha1 前两字节即模块子目录名;先比二者可跳过绝大多数条目的 strncmp。 + s0: int = src_sha1[0] + s1: int = src_sha1[1] for pre_i in range(_g_vpi_ret_count): pre_key: bytes = _g_vpi_ret_keys + pre_i * VPI_KEY_LEN - if string.strncmp(pre_key, src_sha1, sha1_len) == 0 and pre_key[sha1_len] == '.': + if pre_key[0] == s0 and pre_key[1] == s1 and \ + string.strncmp(pre_key, src_sha1, sha1_len) == 0 and pre_key[sha1_len] == '.': return 1 # 构造路径 {temp_dir}/{sha1[0:2]}/{sha1}.vpi(逐字符拼接,避免 %c 格式化问题) td_len: sizet = string.strlen(temp_dir) @@ -680,9 +932,14 @@ def _ensure_vpi_param_annot_loaded(src_sha1: str, temp_dir: str) -> int: # 该模块已有参数缓存条目 → 跳过(幂等) if _g_vpi_param_keys is not None and _g_vpi_param_count > 0: pre_i: int + # 快速路径:前缀匹配必首两字节相等(strncmp 判等蕴含前缀逐字节相等), + # src_sha1 前两字节即模块子目录名;先比二者可跳过绝大多数条目的 strncmp。 + s0: int = src_sha1[0] + s1: int = src_sha1[1] for pre_i in range(_g_vpi_param_count): pre_key: bytes = _g_vpi_param_keys + pre_i * VPI_PARAM_KEY_LEN - if string.strncmp(pre_key, src_sha1, sha1_len) == 0 and pre_key[sha1_len] == '.': + if pre_key[0] == s0 and pre_key[1] == s1 and \ + string.strncmp(pre_key, src_sha1, sha1_len) == 0 and pre_key[sha1_len] == '.': return 1 # 构造路径 {temp_dir}/{sha1[0:2]}/{sha1}.vpi(逐字符拼接,避免 %c 格式化问题) td_len: sizet = string.strlen(temp_dir) @@ -805,10 +1062,24 @@ def _lookup_vpi_ret_annot(src_sha1: str, cls_name: str, method_name: str) -> str else: viperlib.snprintf(key_buf, key_len + 1, "%s.%s", src_sha1, method_name) i: int - for i in range(_g_vpi_ret_count): - if string.strcmp(_g_vpi_ret_keys + i * VPI_KEY_LEN, key_buf) == 0: - stdlib.free(key_buf) - return _g_vpi_ret_strs + i * VPI_RET_LEN + # 索引快速路径:索引可信(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]),首字节不同者跳过。 + 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 + if ent_r[0] == key_first and string.strcmp(ent_r, key_buf) == 0: + hit_r = i + break + if hit_r >= 0: + stdlib.free(key_buf) + return _g_vpi_ret_strs + hit_r * VPI_RET_LEN # 泛型特化归一化匹配:list[AST|t.CPtr].get → list[T].get 模板缓存。 # 命中后把模板注解中的类型参数名(如 T)替换为实参(AST|t.CPtr)再返回。 # 不依赖模板类已注册/特化已发生(base.vp 可能先于 list 模块翻译)。 diff --git a/App/lib/core/Gen/Call/CallGenBase.vp b/App/lib/core/Gen/Call/CallGenBase.vp index 138c3a4..4df3eba 100644 --- a/App/lib/core/Gen/Call/CallGenBase.vp +++ b/App/lib/core/Gen/Call/CallGenBase.vp @@ -80,6 +80,14 @@ class CallGenBase: GlobalFuncs: GenTypes.FuncEntry | t.CPtr GlobalFuncCount: int + # --- 全局函数表"名 → 槽下标"索引(开放寻址 | 线性探测;槽存 index+1,0=空槽) --- + # GlobalFuncs 在 PermPool 固定容量分配、只追加不重排、从不 reset ⇒ 存槽下标 + # 稳定(远优于存会被回收的节点指针);GlobalFuncCount 单调递增, + # Built == GlobalFuncCount 才可信,不可信/未命中一律回退线性扫描。 + GlobalFuncNameIdx: sizet | t.CPtr + GlobalFuncNameIdxCap: int + GlobalFuncNameIdxBuilt: int + # --- 全局默认参数表(原 _global_defaults/_global_defaults_count) --- GlobalDefaults: GenTypes.FuncDefaultsEntry | t.CPtr GlobalDefaultsCount: int @@ -101,6 +109,9 @@ class CallGenBase: self.FuncptrCount = 0 self.GlobalFuncs = None self.GlobalFuncCount = 0 + self.GlobalFuncNameIdx = None + self.GlobalFuncNameIdxCap = 0 + self.GlobalFuncNameIdxBuilt = -1 self.GlobalDefaults = None self.GlobalDefaultsCount = 0 return 0 @@ -536,7 +547,10 @@ class CallGenBase: while cur is not None: cur_name: str = llvmlite.function_get_name(cur) if cur_name is not None: - if string.strcmp(cur_name, name) == 0: + # 快速路径:精确匹配要求首字符相等(strcmp 判等蕴含首字符相等), + # 首字符不同者直接跳过 strcmp;mangled 名以 sha1 十六进制字符打头, + # 裸名多为字母/下划线,绝大多数节点可被此条件短路。 + if cur_name[0] == name[0] and string.strcmp(cur_name, name) == 0: return cur # 本模块同名候选:仅接受恰为 "{owner_sha1}.{name}" 的形态 # (mangled 模块级函数名含且只含一个 '.'),不匹配 @@ -1162,7 +1176,10 @@ class CallGenBase: ll: sizet = le - ls # 检查是否是 define 行 - if ll >= 7 and string.strncmp(read_buf + ls, "define ", 7) == 0: + # 快速路径:行首字节不是 'd' 时不可能等于 "define "(strncmp 判等 + # 蕴含首个字节相等),直接短路省去绝大多数非 define 行的 strncmp 调用; + # 仅做否定判定,不改变任何原本会命中的行。 + if ll >= 7 and read_buf[ls] == 'd' and string.strncmp(read_buf + ls, "define ", 7) == 0: # 找 @ 符号 at_pos: sizet = ls + 7 while at_pos < ls + ll: diff --git a/App/lib/core/Gen/Call/CallGenLeafKit.vp b/App/lib/core/Gen/Call/CallGenLeafKit.vp index 09189da..cf549d0 100644 --- a/App/lib/core/Gen/Call/CallGenLeafKit.vp +++ b/App/lib/core/Gen/Call/CallGenLeafKit.vp @@ -47,6 +47,81 @@ MAX_FUNC_PTR_SIGS: t.CDefine = 64 # isinstance 子类收集上限(防数组越界;t.CDefine 注解由 TPC 解析为 i32) ISINSTANCE_CLASS_MAX: t.CDefine = 64 +# ============================================================ +# 全局函数表名索引(名 → 槽下标,开放寻址 | 线性探测) +# +# 槽数 = 2 * GLOBAL_FUNC_MAX(2 的幂,负载 < 0.5,线性探测必然终止)。 +# 槽存槽下标 + 1(0=空槽)。GlobalFuncs 在 PermPool 定长分配、只追加不重排、 +# 从不 reset ⇒ 槽下标稳定;Built == GlobalFuncCount 才可信。 +# ============================================================ +GLOBAL_FUNC_IDX_CAP: t.CDefine = 16384 +# 索引哈希常量(32 位 FNV-1a,与 StructGenMixin 范式一致) +GFN_FNV_OFFSET: t.CDefine = 2166136261 +GFN_FNV_PRIME: t.CDefine = 16777619 +GFN_FNV_MASK: t.CDefine = 4294967295 + + +def _gfn_hash(name: str) -> uint64: + """函数名 → 64 位哈希(32 位 FNV-1a,逐字节到 NUL 结束)""" + h: uint64 = uint64(GFN_FNV_OFFSET) + if name is None: + return h + i: sizet = 0 + while name[i] != '\0': + h = (h ^ uint64(int(name[i]))) * uint64(GFN_FNV_PRIME) + h = h & uint64(GFN_FNV_MASK) + i += 1 + return h + + +def _gfn_idx_slot(idx_base: sizet | t.CPtr, si: sizet) -> sizet | t.CPtr: + """取索引第 si 个槽(8 字节,存槽下标+1;0=空槽)""" + return (sizet | t.CPtr)(t.CVoid(uint64(idx_base) + uint64(si) * 8, t.CPtr)) + + +def _gfn_idx_find(funcs: GenTypes.FuncEntry | t.CPtr, + idx_base: sizet | t.CPtr, cap: int, name: str) -> int: + """在全局函数表名索引中探测 name,返回命中槽下标(>=0);未命中/不可用返回 -1""" + if funcs is None or idx_base is None or cap <= 0 or name is None: + return -1 + h: uint64 = _gfn_hash(name) + mask: sizet = sizet(cap - 1) + si: sizet = sizet(h) & mask + entry_size: sizet = GenTypes.FuncEntry.__sizeof__() + i: int = 0 + while i < cap: + slot: sizet | t.CPtr = _gfn_idx_slot(idx_base, si) + sv: sizet = slot[0] + if sv == 0: + return -1 + se: int = int(sv) - 1 + entry_addr: uint64 = uint64(funcs) + se * entry_size + entry: GenTypes.FuncEntry | t.CPtr = (GenTypes.FuncEntry | t.CPtr)(t.CVoid(entry_addr, t.CPtr)) + if entry is not None and entry.Name is not None: + if string.strcmp(entry.Name, name) == 0: + return se + si = (si + 1) & mask + i += 1 + return -1 + + +def _gfn_idx_insert(idx_base: sizet | t.CPtr, cap: int, slot_index: int, name: str) -> int: + """把 (name → slot_index) 记入索引;0=成功 / 1=失败""" + if idx_base is None or cap <= 0 or name is None: + return 1 + h: uint64 = _gfn_hash(name) + mask: sizet = sizet(cap - 1) + si: sizet = sizet(h) & mask + i: int = 0 + while i < cap: + slot: sizet | t.CPtr = _gfn_idx_slot(idx_base, si) + if slot[0] == 0: + slot[0] = sizet(slot_index + 1) + return 0 + si = (si + 1) & mask + i += 1 + return 1 + # ============================================================ # CallGenLeafKit - EH/isinstance/闭包/print/函数表 @@ -959,6 +1034,24 @@ class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): if name is None or funcs is None: return None entry_size: sizet = GenTypes.FuncEntry.__sizeof__() + # 全局函数表 + 无 owner(find_func_global / _register_global_func 去重路径): + # owner 分支不激活 ⇒ 等价于"首个同名条目的 Func"(含 Func 为 None → None), + # 走名索引快速路径。funcs 非全局表(逐文件 _funcs)或带 owner 时保持下方 + # 线性扫描(owner 优先需按同组顺序遍历,索引不适用)。 + gfuncs_match: int = 0 + if owner_sha1 is None and self.GlobalFuncs is not None: + if uint64(funcs) == uint64(self.GlobalFuncs) and func_count == self.GlobalFuncCount: + gfuncs_match = 1 + if gfuncs_match != 0 and self.GlobalFuncNameIdxBuilt == self.GlobalFuncCount \ + and self.GlobalFuncNameIdx is not None and self.GlobalFuncNameIdxCap > 0: + fi_t: int = _gfn_idx_find(self.GlobalFuncs, self.GlobalFuncNameIdx, + self.GlobalFuncNameIdxCap, name) + if fi_t >= 0: + fe_t: GenTypes.FuncEntry | t.CPtr = (GenTypes.FuncEntry | t.CPtr)( + t.CVoid(uint64(self.GlobalFuncs) + fi_t * entry_size, t.CPtr)) + if fe_t is not None: + return fe_t.Func + return None # 单遍扫描(合并原 owner 优先遍 + 通用遍,语义完全一致但省一半遍历): # 扫描中同时记录"首个同裸名条目"作通用兜底;一旦发现本模块条目 # (mangled 名以 "{owner_sha1}." 开头,16 位前缀 + '.')立即返回。 @@ -972,7 +1065,9 @@ class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): entry_addr: uint64 = uint64(funcs) + i * entry_size entry: GenTypes.FuncEntry | t.CPtr = (GenTypes.FuncEntry | t.CPtr)(t.CVoid(entry_addr, t.CPtr)) if entry is not None and entry.Name is not None: - if string.strcmp(entry.Name, name) == 0: + # 快速路径:同名要求首字符相等(strcmp 判等蕴含首字符相等), + # 首字符不同者直接跳过 strcmp,避免全表每槽一次函数调用。 + if entry.Name[0] == name[0] and string.strcmp(entry.Name, name) == 0: if fallback_entry is None: fallback_entry = entry if entry.Func is not None and owner_sha1 is not None: @@ -1035,6 +1130,19 @@ class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): return 0 string.memset(self.GlobalFuncs, 0, size) self.GlobalFuncCount = 0 + # 名索引(PermPool,与表同生命周期)。分配失败 → 保持不可信(Built=-1), + # 查找/去重一律回退线性扫描(语义完全一致,不因此使初始化失败)。 + idx_bytes: sizet = sizet(GLOBAL_FUNC_IDX_CAP) * 8 + idx_buf: sizet | t.CPtr = CompilerFactory.factory.PermPool.alloc(idx_bytes) + if idx_buf is None: + self.GlobalFuncNameIdx = None + self.GlobalFuncNameIdxCap = 0 + self.GlobalFuncNameIdxBuilt = -1 + else: + string.memset(idx_buf, 0, idx_bytes) + self.GlobalFuncNameIdx = idx_buf + self.GlobalFuncNameIdxCap = GLOBAL_FUNC_IDX_CAP + self.GlobalFuncNameIdxBuilt = 0 return 1 @@ -1071,6 +1179,14 @@ class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): # Spec 不在此生成:函数参数在 add_func_to_table 之后才添加, # 需由调用方在参数添加完成后调用 update_global_func_spec 补全。 self.GlobalFuncCount += 1 + # 同步名索引(槽下标 = 新条目下标):仅当插入前可信(Built==Count-1) + # 且插入成功;否则标记不可信(查找回退线性扫描)。 + if self.GlobalFuncNameIdxBuilt == self.GlobalFuncCount - 1 \ + and _gfn_idx_insert(self.GlobalFuncNameIdx, self.GlobalFuncNameIdxCap, + self.GlobalFuncCount - 1, name_copy) == 0: + self.GlobalFuncNameIdxBuilt = self.GlobalFuncCount + else: + self.GlobalFuncNameIdxBuilt = -1 return 0 @@ -1155,11 +1271,24 @@ class CallGenLeafKit(CallGenCBuiltin.CallGenCBuiltin): if name is None or self.GlobalFuncs is None: return None entry_size: sizet = GenTypes.FuncEntry.__sizeof__() + # 名索引快速路径:可信(Built==Count)时一次探测返回首个同名条目 + # (= 原线性扫描首个命中);未命中/索引不可信 → 回退原线性扫描(正确性保证)。 + if self.GlobalFuncNameIdxBuilt == self.GlobalFuncCount \ + and self.GlobalFuncNameIdx is not None and self.GlobalFuncNameIdxCap > 0: + gi: int = _gfn_idx_find(self.GlobalFuncs, self.GlobalFuncNameIdx, + self.GlobalFuncNameIdxCap, name) + if gi >= 0: + 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)) if entry.Name is not None: - if string.strcmp(entry.Name, name) == 0: + # 快速路径:同名要求首字符相等(strcmp 判等蕴含首字符相等), + # 首字符不同者直接跳过 strcmp。 + if entry.Name[0] == name[0] and string.strcmp(entry.Name, name) == 0: return entry return None diff --git a/App/lib/core/Gen/Type/TypeGenBase.vp b/App/lib/core/Gen/Type/TypeGenBase.vp index 307c759..7a924b5 100644 --- a/App/lib/core/Gen/Type/TypeGenBase.vp +++ b/App/lib/core/Gen/Type/TypeGenBase.vp @@ -55,6 +55,13 @@ class TypeGenBase: CDefineIsDouble: int CDefineIsStr: int CDefineFound: int + # --- CDefine 名 → 槽下标 索引(开放寻址 | 线性探测;槽存 index+1,0=空槽) --- + # 与 CDefineNames 同池分配、同 clear_cdefine_constants 复位;CDefineNames 只 + # 追加不重排 ⇒ 存槽下标稳定;Built == len(CDefineNames) 才可信,不可信/未命中 + # 一律回退线性扫描(正确性保证)。 + CDefineNameIdx: sizet | t.CPtr + CDefineNameIdxCap: int + CDefineNameIdxBuilt: int # --- 跨模块 CDefine 查找缓存(perm_pool 永久分配,跨文件复用) --- # 2026-08-14:跨模块 CDefine 查找(_lookup_cross_module_cdefine)结果缓存到 @@ -133,6 +140,9 @@ class TypeGenBase: self.CDefineIsDouble = 0 self.CDefineIsStr = 0 self.CDefineFound = 0 + self.CDefineNameIdx = None + self.CDefineNameIdxCap = 0 + self.CDefineNameIdxBuilt = -1 self.XmodCdefNames = None self.XmodCdefValues = None self.XmodCdefDoubleFlags = None diff --git a/App/lib/core/Gen/Type/TypeGenCDef.vp b/App/lib/core/Gen/Type/TypeGenCDef.vp index e9f543d..861100b 100644 --- a/App/lib/core/Gen/Type/TypeGenCDef.vp +++ b/App/lib/core/Gen/Type/TypeGenCDef.vp @@ -15,6 +15,20 @@ import lib.core.CompilerFactory as CompilerFactory import lib.core.Gen.Type.TypeGenBase as TypeGenBase +# ============================================================ +# CDefine 名索引(名 → 槽下标,开放寻址 | 线性探测) +# +# 槽数 = 4096(2 的幂;负载 < 0.5 上限 2048 条,超限标记不可信回退线性扫描)。 +# 槽存槽下标 + 1(0=空槽)。CDefineNames 只追加不重排 ⇒ 槽下标稳定; +# Built == len(CDefineNames) 才可信。 +# ============================================================ +CDEFINE_IDX_CAP: t.CDefine = 4096 +# 索引哈希常量(32 位 FNV-1a,与 StructGenMixin 范式一致) +CDEF_IDX_FNV_OFFSET: t.CDefine = 2166136261 +CDEF_IDX_FNV_PRIME: t.CDefine = 16777619 +CDEF_IDX_FNV_MASK: t.CDefine = 4294967295 + + # ============================================================ # TypeGenCDef - TypeGenMixin 拆分(阶段 D):CDefine 编译期常量表(register/lookup/is/set/clear) # @@ -26,6 +40,86 @@ import lib.core.Gen.Type.TypeGenBase as TypeGenBase class TypeGenCDef(TypeGenBase.TypeGenBase): """CDefine 编译期常量表(register/lookup/is/set/clear)""" + # ============================================================ + # CDefine 名索引(开放寻址;槽存 index+1,0=空槽) + # ============================================================ + def _cdef_idx_hash(self, name: str) -> uint64: + """名 → 64 位哈希(32 位 FNV-1a,逐字节到 NUL 结束)""" + h: uint64 = uint64(CDEF_IDX_FNV_OFFSET) + if name is None: + return h + i: sizet = 0 + while name[i] != '\0': + h = (h ^ uint64(int(name[i]))) * uint64(CDEF_IDX_FNV_PRIME) + h = h & uint64(CDEF_IDX_FNV_MASK) + i += 1 + return h + + def _cdef_idx_slot(self, si: sizet) -> sizet | t.CPtr: + """取索引第 si 个槽(8 字节,存槽下标+1;0=空槽)""" + return (sizet | t.CPtr)(t.CVoid(uint64(self.CDefineNameIdx) + uint64(si) * 8, t.CPtr)) + + def _cdef_idx_init(self, pool: rowptr[memhub.MemBuddy]) -> None: + """创建/清零名索引(与 CDefineNames 同池);失败则标记不可信""" + idx: sizet | t.CPtr = pool.alloc(sizet(CDEFINE_IDX_CAP) * 8) + if idx is None: + self.CDefineNameIdx = None + self.CDefineNameIdxCap = 0 + self.CDefineNameIdxBuilt = -1 + return + string.memset(idx, 0, sizet(CDEFINE_IDX_CAP) * 8) + self.CDefineNameIdx = idx + self.CDefineNameIdxCap = CDEFINE_IDX_CAP + self.CDefineNameIdxBuilt = 0 + + def _cdef_idx_find(self, name: str) -> int: + """在名索引中探测 name,返回命中槽下标(>=0);未命中/不可用返回 -1""" + if self.CDefineNameIdx is None or self.CDefineNameIdxCap <= 0 or name is None: + return -1 + if self.CDefineNameIdxBuilt != int(len(self.CDefineNames)): + return -1 + h: uint64 = self._cdef_idx_hash(name) + mask: sizet = sizet(self.CDefineNameIdxCap - 1) + si: sizet = sizet(h) & mask + i: int = 0 + while i < self.CDefineNameIdxCap: + slot: sizet | t.CPtr = self._cdef_idx_slot(si) + sv: sizet = slot[0] + if sv == 0: + return -1 + se: int = int(sv) - 1 + nm: str = self.CDefineNames.get(sizet(se)) + if nm is not None and string.strcmp(nm, name) == 0: + return se + si = (si + 1) & mask + i += 1 + return -1 + + def _cdef_idx_insert(self, slot_index: int, name: str) -> int: + """把 (name → slot_index) 记入索引;0=成功 / 1=失败""" + if self.CDefineNameIdx is None or self.CDefineNameIdxCap <= 0 or name is None: + return 1 + h: uint64 = self._cdef_idx_hash(name) + mask: sizet = sizet(self.CDefineNameIdxCap - 1) + si: sizet = sizet(h) & mask + i: int = 0 + while i < self.CDefineNameIdxCap: + slot: sizet | t.CPtr = self._cdef_idx_slot(si) + if slot[0] == 0: + slot[0] = sizet(slot_index + 1) + return 0 + si = (si + 1) & mask + i += 1 + return 1 + + def _cdef_idx_sync_append(self, name: str) -> None: + """CDefineNames.append 后同步索引(槽下标 = len-1);不可信则保持不可信""" + n: int = int(len(self.CDefineNames)) + if self.CDefineNameIdxBuilt == n - 1 and self._cdef_idx_insert(n - 1, name) == 0: + self.CDefineNameIdxBuilt = n + else: + self.CDefineNameIdxBuilt = -1 + def register_cdefine_constant(self, pool: rowptr[memhub.MemBuddy], name: str, value: int64) -> None: """注册 CDefine 编译期常量到本地表 @@ -49,12 +143,14 @@ class TypeGenCDef(TypeGenBase.TypeGenBase): self.CDefineValues = list[str](pool, 64) self.CDefineDoubleFlags = list[int](pool, 64) self.CDefineStrFlags = list[int](pool, 64) + self._cdef_idx_init(pool) name_copy: str = pool.alloc(string.strlen(name) + 1) if name_copy is None: self.Gen.TypeGenErr.fatal_error(None, f"CDefine '{name}' 名称缓冲区分配失败", "MemoryError") return string.strcpy(name_copy, name) self.CDefineNames.append(name_copy) + self._cdef_idx_sync_append(name_copy) val_buf: str = pool.alloc(32) if val_buf is not None: viperlib.snprintf(val_buf, 32, "%lld", value) @@ -85,12 +181,14 @@ class TypeGenCDef(TypeGenBase.TypeGenBase): self.CDefineValues = list[str](pool, 64) self.CDefineDoubleFlags = list[int](pool, 64) self.CDefineStrFlags = list[int](pool, 64) + self._cdef_idx_init(pool) name_copy_d: str = pool.alloc(string.strlen(name) + 1) if name_copy_d is None: self.Gen.TypeGenErr.fatal_error(None, f"CDefine 浮点 '{name}' 名称缓冲区分配失败", "MemoryError") return string.strcpy(name_copy_d, name) self.CDefineNames.append(name_copy_d) + self._cdef_idx_sync_append(name_copy_d) val_buf: str = pool.alloc(32) if val_buf is not None: # 用 CRT stdio.snprintf 而非 viperlib.snprintf:viperlib 自实现格式化器的 @@ -126,12 +224,14 @@ class TypeGenCDef(TypeGenBase.TypeGenBase): self.CDefineValues = list[str](pool, 64) self.CDefineDoubleFlags = list[int](pool, 64) self.CDefineStrFlags = list[int](pool, 64) + self._cdef_idx_init(pool) ncopy_s: str = pool.alloc(string.strlen(name) + 1) if ncopy_s is None: self.Gen.TypeGenErr.fatal_error(None, f"CDefine 字符串 '{name}' 名称缓冲区分配失败", "MemoryError") return string.strcpy(ncopy_s, name) self.CDefineNames.append(ncopy_s) + self._cdef_idx_sync_append(ncopy_s) vlen: sizet = string.strlen(value) vcopy: str = pool.alloc(vlen + 1) if vcopy is None: @@ -239,7 +339,18 @@ class TypeGenCDef(TypeGenBase.TypeGenBase): return 0 n: sizet = len(self.CDefineNames) i: sizet - for i in range(n): + # 名索引快速路径:可信(Built==n)时直接定位唯一候选下标(= 原扫描首个 + # 命中);未命中 → 确实不存在,直接返回;不可用 → 回退原全表线性扫描。 + 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) + 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: val_str: str = self.CDefineValues.get(i) @@ -268,9 +379,21 @@ class TypeGenCDef(TypeGenBase.TypeGenBase): return 0.0 n: sizet = len(self.CDefineNames) i: sizet - for i in range(n): + # 名索引快速路径:可信(Built==n)时直接定位唯一候选下标(= 原扫描首个 + # 命中);未命中 → 确实不存在,直接返回;不可用 → 回退原全表线性扫描。 + 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) + 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: + # 快速路径:strcmp 判等蕴含首字符相等,首字符不同者跳过 strcmp。 + if nm is not None and nm[0] == name[0] and string.strcmp(nm, name) == 0: val_str: str = self.CDefineValues.get(i) if val_str is not None: self.CDefineFound = 1 @@ -297,7 +420,18 @@ class TypeGenCDef(TypeGenBase.TypeGenBase): return None n: sizet = len(self.CDefineNames) i: sizet - for i in range(n): + # 名索引快速路径:可信(Built==n)时直接定位唯一候选下标(= 原扫描首个 + # 命中);未命中 → 确实不存在,直接返回;不可用 → 回退原全表线性扫描。 + 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) + 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: val_str: str = self.CDefineValues.get(i) @@ -326,4 +460,8 @@ class TypeGenCDef(TypeGenBase.TypeGenBase): self.CDefineValues = None self.CDefineDoubleFlags = None self.CDefineStrFlags = None + # 名索引随表复位(下次 register 重建);不同步会使 Built 与空表错配 + self.CDefineNameIdx = None + self.CDefineNameIdxCap = 0 + self.CDefineNameIdxBuilt = -1 self.CDefineIsStr = 0 \ No newline at end of file