diff --git a/App/lib/core/Gen/Call/CallGenBase.vp b/App/lib/core/Gen/Call/CallGenBase.vp index 07e040b..4d2b58e 100644 --- a/App/lib/core/Gen/Call/CallGenBase.vp +++ b/App/lib/core/Gen/Call/CallGenBase.vp @@ -7,11 +7,13 @@ from stdint import * import ast import llvmlite import memhub +import hashtable import string import stdio import stdlib import viperlib import w32.fileio as fileio +import w32.win32file as win32file import lib.core.VLogger as VLogger import lib.Stub.StubMerger as StubMerger import lib.core.CompilerFactory as CompilerFactory @@ -92,6 +94,14 @@ class CallGenBase: GlobalDefaults: GenTypes.FuncDefaultsEntry | t.CPtr GlobalDefaultsCount: int + # --- 依赖模块 text.ll「方法返回类型文本」缓存(_LookupMethodRetTyFromTextLL 性能优化) --- + # key = text.ll 里 define 的完整 mangled 函数名("{sha1}.{后缀}")→ 返回类型文本。 + # 表与键/值均 PermPool 分配(跨文件/Phase 存活);sha1 本身是内容哈希 ⇒ 天然 + # 内容寻址,无需 size/mtime 校验。值只存**文本**(绝不存 LLVMType*——其分配在 + # 每文件 reset_to 失效的 pool 中);命中后用调用方 pool 重建类型,与原路径等价。 + # 惰性建表;建表/分配失败即不缓存,回退原扫描(正确性保证,非兜底)。| 不用全局裸表。 + MethodRetTyMap: hashtable.HashTable | t.CPtr + def InitCallGen(self) -> int: """初始化调用 Mixin 字段(子类构造时内联调用,规避 NoVTable 继承符号不转发)""" if self is None: @@ -114,6 +124,7 @@ class CallGenBase: self.GlobalFuncNameIdxBuilt = -1 self.GlobalDefaults = None self.GlobalDefaultsCount = 0 + self.MethodRetTyMap = None return 0 # ============================================================ @@ -1062,6 +1073,127 @@ class CallGenBase: # 无法解析 return None + # ============================================================ + # _LookupCachedRetTyText - 查「依赖模块 text.ll 方法返回类型文本」持久缓存 + # + # 缓存键 = text.ll 中 define 的完整 mangled 函数名 + # ("{sha1}.{class}.{method}";bare 模式 "{sha1}.{method}")。sha1 本身是内容 + # 哈希 ⇒ 天然内容寻址,无需 size/mtime 校验。命中返回返回类型文本(PermPool + # 持久),未命中/表未建返回 None(调用方回退原扫描)。 + # ============================================================ + def _LookupCachedRetTyText(self, class_sha1: str, class_name: str, + method_name: str, use_bare: int) -> str: + """查返回类型文本缓存:命中返回文本,未命中返回 None""" + if self.Gen is None or class_sha1 is None or method_name is None: + return None + ht: hashtable.HashTable | t.CPtr = self.Gen.CallGenBase.MethodRetTyMap + if ht is None: + return None + sha1_len: sizet = string.strlen(class_sha1) + cls_len: sizet = 0 + if class_name is not None: + cls_len = string.strlen(class_name) + meth_len: sizet = string.strlen(method_name) + # 长度公式与 _LookupMethodRetTyFromTextLL 构造 search_buf 时完全一致 + key_len: sizet = sha1_len + meth_len + 2 + if use_bare == 0: + key_len = sha1_len + cls_len + meth_len + 4 + key_buf: bytes = stdlib.malloc(key_len) + if key_buf is None: + return None + if use_bare != 0: + viperlib.snprintf(key_buf, key_len, "%s.%s", class_sha1, method_name) + else: + viperlib.snprintf(key_buf, key_len, "%s.%s.%s", + class_sha1, class_name, method_name) + v: t.CPtr = ht.__getitem__(key_buf) + stdlib.free(key_buf) + if v is None: + return None + return (str | t.CPtr)(v) + + # ============================================================ + # _CacheModuleRetTyTexts - 把某模块 text.ll 全部 define 行的返回类型写入持久缓存 + # + # 仅在 _LookupMethodRetTyFromTextLL 成功读到 text.ll 后调用(未读到文件绝不写 + # 缓存:Phase A 按文件顺序翻译,依赖模块此遍可能尚未生成 text.ll,未命中必须 + # 回退原扫描、负结果不可缓存)。键=define 完整 mangled 函数名(深拷贝进 + # PermPool),值="define " 与 "@" 之间的返回类型文本(深拷贝进 PermPool)。 + # 同名只记首次出现(与逐行扫描「首个命中即返回」一致);只登记以 "{sha1}." + # 开头的本模块 mangled 名(调用方查询键恒为此前缀,外部/内部符号永不命中)。 + # ============================================================ + def _CacheModuleRetTyTexts(self, class_sha1: str, text_buf: bytes, + text_len: sizet) -> None: + """登记 text.ll 全部 define 行到持久缓存(建表/分配失败即不缓存,回退原扫描)""" + if self.Gen is None or class_sha1 is None or text_buf is None or text_len == 0: + return + perm: rowptr[memhub.MemBuddy] = CompilerFactory.factory.PermPool + if perm is None: + return + ht: hashtable.HashTable | t.CPtr = self.Gen.CallGenBase.MethodRetTyMap + if ht is None: + ht = hashtable.HashTable(perm) + if ht is None: + return + self.Gen.CallGenBase.MethodRetTyMap = ht + sha1_len: sizet = string.strlen(class_sha1) + pos: sizet = 0 + while pos < text_len: + ls: sizet = pos + le: sizet = pos + while le < text_len: + if text_buf[le] == '\n': + break + le += 1 + ll: sizet = le - ls + # define 行判定与 _LookupMethodRetTyFromTextLL 逐字一致 + if ll >= 7 and text_buf[ls] == 'd' and string.strncmp(text_buf + ls, "define ", 7) == 0: + at_pos: sizet = ls + 7 + while at_pos < ls + ll: + if text_buf[at_pos] == '@': + break + at_pos += 1 + if at_pos < ls + ll: + name_start: sizet = at_pos + 1 + quoted: int = 0 + if name_start < ls + ll and text_buf[name_start] == '"': + name_start += 1 + quoted = 1 + # 定界:带引号名到 '"',裸名到 '('(与查找侧边界判定等价) + name_end: sizet = name_start + if quoted != 0: + while name_end < ls + ll: + if text_buf[name_end] == '"': + break + name_end += 1 + else: + while name_end < ls + ll: + if text_buf[name_end] == '(': + break + name_end += 1 + name_len: sizet = name_end - name_start + # 只登记本模块 mangled 名 "{sha1}.{后缀}"(查询键恒为此前缀) + if name_len > sha1_len + 1: + if string.strncmp(text_buf + name_start, class_sha1, sha1_len) == 0 \ + and text_buf[name_start + sha1_len] == '.': + key_buf: bytes = stdlib.malloc(name_len + 1) + if key_buf is not None: + string.strncpy(key_buf, text_buf + name_start, name_len) + key_buf[name_len] = '\0' + # 同名首次为准(不被后续同名覆盖,与首个命中一致) + if ht.__contains__(key_buf) == 0: + rt_len: sizet = at_pos - (ls + 7) + kcopy: str = perm.alloc(name_len + 1) + tcopy: str = perm.alloc(rt_len + 1) + if kcopy is not None and tcopy is not None: + string.strncpy(kcopy, text_buf + name_start, name_len) + kcopy[name_len] = '\0' + string.strncpy(tcopy, text_buf + ls + 7, rt_len) + tcopy[rt_len] = '\0' + ht.set_str(kcopy, tcopy) + stdlib.free(key_buf) + pos = le + 1 + # ============================================================ # _LookupMethodRetTyFromTextLL - 从依赖模块的 text.ll 查找方法返回类型 # @@ -1103,6 +1235,15 @@ class CallGenBase: if sha1_len < 2: return None + # [按模块一次性缓存·命中] 该模块 text.ll 已被读过一次 ⇒ O(1) 取回函数返回 + # 类型文本,用**调用方 pool** 重建类型直接返回(与原路径语义等价,不再开 + # 文件/读盘/逐字节重扫)。未命中 → 走下方原实现;仅在成功读到 text.ll 后 + # 才回写缓存(依赖模块此遍可能尚未生成,负结果绝不缓存)。 + cached_text: str = self._LookupCachedRetTyText( + class_sha1, class_name, method_name, use_bare) + if cached_text is not None: + return self._ParseLLVMRetTyStr(pool, cached_text, 0, string.strlen(cached_text)) + # 构造 text.ll 路径 (level=1): {temp_dir}/{sha1[0:2]}/{sha1}.text.ll # 使用 subdir 缓冲区避免 %c 格式化问题(与 StubMerger._sliced_path 一致) path_len: sizet = self.Gen.CallGenBase.TempDirLen + sha1_len + 14 @@ -1170,6 +1311,12 @@ class CallGenBase: # 在 text.ll 中搜索 define 行包含 search_buf buf_len: sizet = string.strlen(read_buf) + + # [按模块一次性缓存·回写] 已成功读到本模块 text.ll:登记其全部 define 行 + # (同模块后续查询 O(1) 命中,不再重开/重读/逐字节重扫)。此处不改动下方 + # 原扫描逻辑(保留为未命中路径)。 + self._CacheModuleRetTyTexts(class_sha1, read_buf, buf_len) + pos: sizet = 0 result_ty: llvmlite.LLVMType | t.CPtr = None @@ -1250,11 +1397,19 @@ class CallGenBase: viperlib.snprintf(path_buf, path_len, "%s/%s/%s.text.ll", self.Gen.CallGenBase.TempDir, subdir_buf, sha1) stdlib.free(subdir_buf) - tf: fileio.File | t.CPtr = fileio.File(path_buf, fileio.MODE.R) - exists: int = 0 - if not tf.closed: - tf.close() - exists = 1 + # [廉价存在性判定] 用 GetFileAttributesA(无输出缓冲、不创建文件句柄)替代 + # 原「只读打开探测」:原实现只为看是否存在却真开句柄(走 create_file_utf8), + # 本函数 33,244 次调用占总 create_file_utf8 调用 46.6%。写法对齐同仓既有 + # IncludesScanner._PathExistsA(该处亦以属性查询替代开文件句柄)。 + attr: ULONG = win32file.GetFileAttributesA(path_buf) + exists: int = 1 + # INVALID_FILE_ATTRIBUTES = 不存在/不可访问 → 0。 + if attr == 0xFFFFFFFF: + exists = 0 + # 原实现以只读 CreateFileA 打开:目录会被拒绝(未带 FILE_FLAG_BACKUP_SEMANTICS) + # → 0,故此处显式排除目录以保持「是可读常规文件」的等价判定。 + if (attr & win32file.FILE_ATTRIBUTE_DIRECTORY) != 0: + exists = 0 stdlib.free(path_buf) return exists