From a9ebb207908a8693291e5b87c42ebeadc8bd999a Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Sat, 8 Aug 2026 02:24:12 +0800 Subject: [PATCH] =?UTF-8?q?"fix(compiler):=20vpi=20=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E6=B3=A8=E8=A7=A3=E7=BC=93=E5=AD=98=E6=8C=89=E9=9C=80=E5=8A=A0?= =?UTF-8?q?=E8=BD=BD=20+=20=E5=8A=A8=E6=80=81=E6=89=A9=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. _lookup_method_ret_ty_via_vpi 查缓存前按需读取 {temp_dir}/{sha1[0:2]}/{sha1}.vpi 填充缓存,覆盖模块自身翻译时 (同模块方法调用、定义在后)stub 尚未注入的场景 2. 缓存容量由固定 4096 改为 stdlib.realloc 翻倍动态扩容(上限 1M), 根治写满后条目静默丢弃导致 JsonValue._pool_alloc 等查不到" --- App/lib/StubGen/VpiReader.vp | 204 +++++----- App/lib/core/Handles/HandlesExprCall.vp | 490 ++++++++++++------------ 2 files changed, 352 insertions(+), 342 deletions(-) diff --git a/App/lib/StubGen/VpiReader.vp b/App/lib/StubGen/VpiReader.vp index a7fa503..c3be7b9 100644 --- a/App/lib/StubGen/VpiReader.vp +++ b/App/lib/StubGen/VpiReader.vp @@ -22,9 +22,12 @@ import lib.core.Handles.HandlesTranslator as HT # 背景:vpi tree 在"解析后立即"有效,但遍历处理到后续类的方法时, # 深层 AST 节点(如 parse_args 的 returns 注解)可能已被覆盖损坏 # (kind() 读取异常),直接 resolve 得到垃圾类型(如 i8*)。 -# 因此在 tree 解析后立即把每个方法的返回注解转成字符串缓存到 stdlib -# (持久,不受 pool 状态影响),_inject_method_declare 用 _parse_type_string -# 重新解析缓存字符串(新 AST 节点,立即使用不被覆盖)。 +# 因此采用文本提取方案:ParseVpiIntoModule 读 .vpi stub 时,从 +# vpi_buf 原始文本(在函数末尾才释放,始终有效)直接按行格式 +# 提取 "def m(...) -> Ret:" 的返回注解字符串缓存到 stdlib(持久, +# 不受 pool 状态影响)。HandlesExprCall._lookup_method_ret_ty_via_vpi +# 按 sha1.类名.方法名 查表并用 _resolve_vpi_ret_annot_str 纯字符串 +# 解析为 LLVMType,返回类型始终来自方法定义处,无白名单/无硬编码。 # ============================================================ VPI_RET_MAX: t.CDefine = 4096 VPI_KEY_LEN: t.CDefine = 224 @@ -33,92 +36,7 @@ VPI_RET_LEN: t.CDefine = 224 _g_vpi_ret_keys: bytes = None _g_vpi_ret_strs: bytes = None _g_vpi_ret_count: int = 0 - - -# ============================================================ -# _cache_vpi_return_annots - tree 解析后立即缓存方法的返回注解字符串 -# ============================================================ -def _cache_vpi_return_annots(pool: memhub.MemBuddy | t.CPtr, - tree: ast.AST | t.CPtr, - src_sha1: str) -> int: - """缓存 vpi tree 中所有 FunctionDef 的返回注解字符串(stdlib 持久)""" - global _g_vpi_ret_keys, _g_vpi_ret_strs, _g_vpi_ret_count - if tree is None or src_sha1 is None: - return 0 - if _g_vpi_ret_keys is None: - _g_vpi_ret_keys = stdlib.malloc(VPI_RET_MAX * VPI_KEY_LEN) - _g_vpi_ret_strs = stdlib.malloc(VPI_RET_MAX * VPI_RET_LEN) - if _g_vpi_ret_keys is None or _g_vpi_ret_strs is None: - return 0 - 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) - sha1_len: t.CSizeT = string.strlen(src_sha1) - ch: list[ast.AST | t.CPtr] | t.CPtr = tree.children - if ch is None: - return 0 - n: t.CSizeT = ch.__len__() - i: t.CSizeT - for i in range(n): - child: ast.AST | t.CPtr = ch.get(i) - if child is None: - continue - kd: int = child.kind() - if kd == ast.ASTKind.ClassDef: - cd: ast.ClassDef | t.CPtr = (ast.ClassDef | t.CPtr)(child) - if cd is None or cd.name is None: - continue - mch: list[ast.AST | t.CPtr] | t.CPtr = cd.children - if mch is None: - continue - mn: t.CSizeT = mch.__len__() - mi: t.CSizeT - for mi in range(mn): - m_child: ast.AST | t.CPtr = mch.get(mi) - if m_child is None or m_child.kind() != ast.ASTKind.FunctionDef: - continue - m_fd: ast.FunctionDef | t.CPtr = (ast.FunctionDef | t.CPtr)(m_child) - if m_fd is None or m_fd.name is None: - continue - _store_vpi_ret_entry(src_sha1, sha1_len, cd.name, m_fd.name, m_fd.returns) - elif kd == ast.ASTKind.FunctionDef: - fd: ast.FunctionDef | t.CPtr = (ast.FunctionDef | t.CPtr)(child) - if fd is None or fd.name is None: - continue - _store_vpi_ret_entry(src_sha1, sha1_len, None, fd.name, fd.returns) - return 1 - - -def _store_vpi_ret_entry(src_sha1: str, sha1_len: t.CSizeT, - cls_name: str, method_name: str, - returns: ast.AST | t.CPtr) -> None: - """存储单个方法的返回注解字符串到缓存""" - global _g_vpi_ret_count - if method_name is None: - return - cl_len: t.CSizeT = 0 - if cls_name is not None: - cl_len = string.strlen(cls_name) - me_len: t.CSizeT = string.strlen(method_name) - key_len: t.CSizeT = sha1_len + 1 + cl_len + 1 + me_len - if key_len >= VPI_KEY_LEN: - return - key_buf: bytes = stdlib.malloc(key_len + 1) - if key_buf is None: - return - if cls_name is not None: - viperlib.snprintf(key_buf, key_len + 1, "%s.%s.%s", src_sha1, cls_name, method_name) - else: - viperlib.snprintf(key_buf, key_len + 1, "%s.%s", src_sha1, method_name) - ret_str: str = None - if returns is not None: - ret_str = HandlesType.annotation_to_str(returns) - if ret_str is not None and string.strlen(ret_str) > 0: - if string.strlen(ret_str) < VPI_RET_LEN: - if _g_vpi_ret_count < VPI_RET_MAX: - string.strcpy(_g_vpi_ret_keys + _g_vpi_ret_count * VPI_KEY_LEN, key_buf) - string.strcpy(_g_vpi_ret_strs + _g_vpi_ret_count * VPI_RET_LEN, ret_str) - _g_vpi_ret_count += 1 - stdlib.free(key_buf) +_g_vpi_ret_cap: int = 0 # ============================================================ @@ -132,7 +50,7 @@ def _store_vpi_ret_entry(src_sha1: str, sha1_len: t.CSizeT, # ============================================================ 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 + global _g_vpi_ret_keys, _g_vpi_ret_strs, _g_vpi_ret_count, _g_vpi_ret_cap if vpi_buf is None or src_sha1 is None: return 0 if _g_vpi_ret_keys is None: @@ -142,6 +60,7 @@ def _cache_vpi_return_annots_text(vpi_buf: bytes, src_sha1: str) -> int: return 0 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 sha1_len: t.CSizeT = string.strlen(src_sha1) buf_len: t.CSizeT = string.strlen(vpi_buf) cur_cls: bytes = None @@ -221,12 +140,16 @@ def _cache_vpi_return_annots_text(vpi_buf: bytes, src_sha1: str) -> int: if string.strcmp(_g_vpi_ret_keys + dki * VPI_KEY_LEN, key_buf) == 0: dup_key = 1 break - if dup_key == 0 and _g_vpi_ret_count < VPI_RET_MAX: - string.strcpy(_g_vpi_ret_keys + _g_vpi_ret_count * VPI_KEY_LEN, key_buf) - string.strncpy(_g_vpi_ret_strs + _g_vpi_ret_count * VPI_RET_LEN, - vpi_buf + rstart, rtype_len) - (_g_vpi_ret_strs + _g_vpi_ret_count * VPI_RET_LEN)[rtype_len] = '\0' - _g_vpi_ret_count += 1 + if dup_key == 0: + grow_ok: int = 1 + if _g_vpi_ret_count >= _g_vpi_ret_cap: + grow_ok = _grow_vpi_ret_cache() + if grow_ok != 0: + string.strcpy(_g_vpi_ret_keys + _g_vpi_ret_count * VPI_KEY_LEN, key_buf) + string.strncpy(_g_vpi_ret_strs + _g_vpi_ret_count * VPI_RET_LEN, + vpi_buf + rstart, rtype_len) + (_g_vpi_ret_strs + _g_vpi_ret_count * VPI_RET_LEN)[rtype_len] = '\0' + _g_vpi_ret_count += 1 stdlib.free(key_buf) stdlib.free(mname_buf) pos = le + 1 @@ -236,6 +159,95 @@ def _cache_vpi_return_annots_text(vpi_buf: bytes, src_sha1: str) -> int: return 1 +# ============================================================ +# _grow_vpi_ret_cache - 扩容方法返回注解缓存(容量翻倍) +# +# 背景:VPI_RET_MAX 初始 4096,但 includes 模块量大(~90 个 stub、 +# 每个几十个方法),缓存写满后条目被静默丢弃,导致已缓存模块的 +# 方法(如 JsonValue._pool_alloc)查不到而 fail-fast 误报。 +# 用 stdlib.realloc 翻倍扩容(上限 1M 条目,防御异常增长)。 +# ============================================================ +def _grow_vpi_ret_cache() -> int: + """扩容返回注解缓存(容量翻倍),返回 1=成功 / 0=失败""" + global _g_vpi_ret_keys, _g_vpi_ret_strs, _g_vpi_ret_cap + if _g_vpi_ret_keys is None or _g_vpi_ret_cap <= 0: + return 0 + new_cap: int = _g_vpi_ret_cap * 2 + if new_cap > 1048576: + return 0 + new_keys: bytes = stdlib.realloc(_g_vpi_ret_keys, new_cap * VPI_KEY_LEN) + if new_keys is None: + return 0 + new_strs: bytes = stdlib.realloc(_g_vpi_ret_strs, new_cap * VPI_RET_LEN) + if new_strs is None: + return 0 + _g_vpi_ret_keys = new_keys + _g_vpi_ret_strs = new_strs + _g_vpi_ret_cap = new_cap + return 1 + + +# ============================================================ +# _ensure_vpi_ret_annot_loaded - 按需加载模块的 vpi stub 返回注解缓存 +# +# 背景:_cache_vpi_return_annots_text 仅在 stub 注入(LoadDepVpiIntoModule, +# 即"当前模块 import 了该模块")时填充缓存。但模块自身翻译时,自己的 +# stub 尚未被任何依赖方读取,且自己的 text.ll 要翻译完成才落盘,导致 +# "同模块方法调用(定义在后)"(如 json.__setitem__ 调用后定义的 +# _pool_alloc)在 found_func/text.ll/vpi 缓存三类来源上全部未命中。 +# 此函数在缓存未命中时直接读 {temp_dir}/{sha1[0:2]}/{sha1}.vpi +# (Converter 在 deps 阶段已生成全部 stub 文件),填充缓存后即可命中。 +# 幂等:模块已缓存过则跳过,不重复读文件。 +# ============================================================ +def _ensure_vpi_ret_annot_loaded(src_sha1: str, temp_dir: str) -> int: + """按需加载模块 vpi stub 的返回注解缓存,返回 1=已就绪 / 0=失败""" + if src_sha1 is None or temp_dir is None: + return 0 + sha1_len: t.CSizeT = string.strlen(src_sha1) + if sha1_len < 2: + return 0 + # 该模块已有缓存条目 → 跳过(幂等) + if _g_vpi_ret_keys is not None and _g_vpi_ret_count > 0: + pre_i: int + 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] == '.': + return 1 + # 构造路径 {temp_dir}/{sha1[0:2]}/{sha1}.vpi(逐字符拼接,避免 %c 格式化问题) + td_len: t.CSizeT = string.strlen(temp_dir) + path_len: t.CSizeT = td_len + sha1_len + 12 + vpi_path: bytes = stdlib.malloc(path_len) + if vpi_path is None: + return 0 + string.strcpy(vpi_path, temp_dir) + vpi_path[td_len] = '/' + vpi_path[td_len + 1] = src_sha1[0] + vpi_path[td_len + 2] = src_sha1[1] + vpi_path[td_len + 3] = '/' + string.strcpy(vpi_path + td_len + 4, src_sha1) + string.strcpy(vpi_path + td_len + 4 + sha1_len, ".vpi") + f: fileio.File | t.CPtr = fileio.File(vpi_path, fileio.MODE.R) + if f.closed: + stdlib.free(vpi_path) + return 0 + vpi_buf: bytes = stdlib.malloc(VPI_READ_BUF_SIZE) + if vpi_buf is None: + f.close() + stdlib.free(vpi_path) + return 0 + bytes_read: LONG = f.read_all(vpi_buf, VPI_READ_BUF_SIZE) + f.close() + if bytes_read > 0: + if bytes_read < VPI_READ_BUF_SIZE: + vpi_buf[bytes_read] = 0 + else: + vpi_buf[VPI_READ_BUF_SIZE - 1] = 0 + _cache_vpi_return_annots_text(vpi_buf, src_sha1) + stdlib.free(vpi_buf) + stdlib.free(vpi_path) + return 1 + + # ============================================================ # _lookup_vpi_ret_annot - 查找缓存的方法返回注解字符串 # ============================================================ diff --git a/App/lib/core/Handles/HandlesExprCall.vp b/App/lib/core/Handles/HandlesExprCall.vp index 264f6a0..8166d28 100644 --- a/App/lib/core/Handles/HandlesExprCall.vp +++ b/App/lib/core/Handles/HandlesExprCall.vp @@ -490,7 +490,6 @@ def _infer_external_func_ret_ty(pool: memhub.MemBuddy | t.CPtr, # 已知返回指针的函数在上方显式列出(返回 i8*),已知返回 i64 的函数也显式列出 # 避免使用 i64 导致 stub 声明(declare i64)与实际定义(define i32)类型不匹配 # x86-64 ABI 中 i32 返回值在 rax 低 32 位,与 define i32 完全一致 - # 与 _infer_method_ret_ty 的默认策略保持一致 # WARNING: 未登记的外部函数返回类型默认推断为 i32,若实际返回指针/i64 会被截断 VLogger.warning( f"外部函数返回类型未登记,默认推断为 i32(可能截断指针/i64): {func_name}", @@ -3071,84 +3070,57 @@ def _ensure_method_declare(pool: memhub.MemBuddy | t.CPtr, # ============================================================ -# _infer_method_ret_ty - 根据方法名推断返回类型 +# _lookup_method_ret_ty_via_vpi - 从 vpi stub 返回注解缓存精确获取方法返回类型 # -# stub 未注入时,find_func_in_module 返回 None, -# 需要根据方法名推断返回类型避免指针被截断为 i32。 +# 方法定义处(.vpi stub)的返回注解(如 json.JsonValue._pool_alloc → +# "t.CChar | t.CPtr")在 stub 注入时由 VpiReader 缓存(_cache_vpi_return_annots_text)。 +# 此函数按 模块SHA1 + 类名 + 方法名 查缓存并纯字符串解析为 LLVMType。 +# 无需硬编码白名单——返回类型始终来自方法定义处。 # ============================================================ -def _infer_method_ret_ty(pool: memhub.MemBuddy | t.CPtr, - method_name: str) -> llvmlite.LLVMType | t.CPtr: - """根据方法名推断返回类型(stub 未注入时的回退方案)""" - if pool is None or method_name is None: - return llvmlite.Int32(pool) - +def _lookup_method_ret_ty_via_vpi(pool: memhub.MemBuddy | t.CPtr, + cls_sha1: str, + cls_name: str, + method_name: str) -> llvmlite.LLVMType | t.CPtr: + """从 vpi stub 返回注解缓存获取方法返回类型,未命中返回 None""" + if pool is None or cls_sha1 is None or method_name is None: + return None + # 函数内懒 import:VpiReader imports HandlesExprCall,顶层 import 会成环 + import lib.StubGen.VpiReader as VpiReader + i32_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int32(pool) i8_ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, llvmlite.Int8(pool)) + # 按需加载:缓存未命中时直接读 {temp_dir}/{sha1[0:2]}/{sha1}.vpi + # 覆盖"模块自身翻译时 stub 尚未注入"(同模块方法调用,定义在后)的场景 + if _g_temp_dir is not None and _g_temp_dir_len > 0: + VpiReader._ensure_vpi_ret_annot_loaded(cls_sha1, _g_temp_dir) + annot: str = VpiReader._lookup_vpi_ret_annot(cls_sha1, cls_name, method_name) + if annot is None: + return None + return VpiReader._resolve_vpi_ret_annot_str(pool, annot, i32_ty, i8_ptr_ty) - # 返回 i8* 的方法(分配/返回指针) - if string.strcmp(method_name, "alloc") == 0: - return i8_ptr_ty - if string.strcmp(method_name, "calloc") == 0: - return i8_ptr_ty - if string.strcmp(method_name, "realloc") == 0: - return i8_ptr_ty - if string.strcmp(method_name, "alloc_buf") == 0: - return i8_ptr_ty - if string.strcmp(method_name, "_fl_pop") == 0: - return i8_ptr_ty - if string.strcmp(method_name, "_buddy_of") == 0: - return i8_ptr_ty - if string.strcmp(method_name, "_split_to_order") == 0: - return i8_ptr_ty - if string.strcmp(method_name, "__enter__") == 0: - return i8_ptr_ty - # 工厂方法:返回对象指针(截断会导致后续方法调用崩溃) - if string.strcmp(method_name, "parse_args") == 0: - return i8_ptr_ty - # 魔术方法:返回对象指针(如 JsonValue.__getitem__ 返回 JsonValue*) - if string.strcmp(method_name, "__getitem__") == 0: - return i8_ptr_ty - # 返回 i8* 的方法(返回字符串/类型名/描述) - if string.strcmp(method_name, "type_name") == 0: - return i8_ptr_ty - if string.strcmp(method_name, "get_name") == 0: - return i8_ptr_ty - if string.strcmp(method_name, "get_str") == 0: - return i8_ptr_ty - if string.strcmp(method_name, "get_path") == 0: - return i8_ptr_ty - if string.strcmp(method_name, "get_buf") == 0: - return i8_ptr_ty - if string.strcmp(method_name, "to_string") == 0: - return i8_ptr_ty - if string.strcmp(method_name, "as_string") == 0: - return i8_ptr_ty - if string.strcmp(method_name, "__repr__") == 0: - return i8_ptr_ty - if string.strcmp(method_name, "__str__") == 0: - return i8_ptr_ty - # 返回 i8* 的方法(返回对象指针,如 JsonValue.get_item → JsonValue*) - if string.strcmp(method_name, "get_item") == 0: - return i8_ptr_ty +# ============================================================ +# _fail_method_ret_ty - 方法返回类型无法精确获取时 fail-fast +# +# 返回类型来源(按序):当前模块函数 → text.ll define → vpi stub 返回注解缓存。 +# 全部未命中时立即 ERROR + 错误堆栈终止,绝不按名推断白名单/静默默认 i32。 +# +# node: 调用点 AST 节点(可选,用于 ERROR 堆栈定位) +# ============================================================ +def _fail_method_ret_ty(method_name: str, + node: ast.AST | t.CPtr = None) -> llvmlite.LLVMType | t.CPtr: + """方法返回类型无法精确获取时 fail-fast(ERROR + 错误堆栈) - # 返回 void 的方法 - if string.strcmp(method_name, "__before_init__") == 0: - return llvmlite.Void(pool) - if string.strcmp(method_name, "__init__") == 0: - return llvmlite.Void(pool) - if string.strcmp(method_name, "__exit__") == 0: - return llvmlite.Void(pool) - - # 默认 i32(整数):与本模块函数定义的默认返回类型一致(HandlesFunctions.py line 324) - # 已知返回指针的方法在上方显式列出(返回 i8*),已知返回 void 的方法也显式列出 - # 避免使用 i64 导致 stub 声明(declare i64)与实际定义(define i32)类型不匹配 - # x86-64 ABI 中 i32 返回值在 rax 低 32 位,与 define i32 完全一致 - # 与 _infer_external_func_ret_ty 的默认策略保持一致 - # WARNING: 未登记的方法返回类型默认推断为 i32,若实际返回指针/i64 会被截断 - VLogger.warning( - f"方法返回类型未登记,默认推断为 i32(可能截断指针/i64): {method_name}", + 返回类型来源(按序):当前模块函数 → text.ll define → vpi stub 返回注解缓存。 + 全部未命中说明方法定义缺失或 stub 损坏,立即终止暴露,绝不静默默认 i32。 + """ + VLogger.error( + f"方法返回类型无法精确获取(函数/text.ll/vpi stub 均未命中): {method_name}", "HandlesExprCall") - return llvmlite.Int32(pool) + HandlesType.fatal_error( + node, + f"方法返回类型无法精确获取,默认 i32 会截断指针/i64: {method_name}", + "TypeError") + return None # ============================================================ @@ -3156,9 +3128,9 @@ def _infer_method_ret_ty(pool: memhub.MemBuddy | t.CPtr, # # Phase A 翻译每个源文件后生成 text.ll,其中 define 行包含 # 方法的实际返回类型。当 Phase A 翻译后续文件调用跨模块方法时, -# _infer_method_ret_ty 默认返回 i32,导致指针返回值被截断。 +# 方法调用默认返回 i32,导致指针返回值被截断。 # _LookupMethodRetTyFromTextLL 读取依赖模块的 text.ll,从 define -# 行提取实际返回类型,避免指针截断。 +# 行提取实际返回类型,避免指针截断(未命中时回退 vpi stub 缓存)。 # ============================================================ _g_temp_dir: str = None @@ -3287,7 +3259,7 @@ def _ParseLLVMRetTyStr(pool: memhub.MemBuddy | t.CPtr, # define 行中包含 "{sha1}.{class_name}.{method_name}" 的函数, # 提取返回类型并转换为 llvmlite.LLVMType。 # -# 解决问题: _infer_method_ret_ty 默认返回 i32,导致指针返回值 +# 解决问题: 旧实现 _infer_method_ret_ty(已移除)默认返回 i32,导致指针返回值 # 被 inttoptr 截断为 32 位(coerce_to_type line 238-239)。 # 本函数从 text.ll 获取实际返回类型,避免截断。 # @@ -3475,7 +3447,13 @@ def _translate_property_call(pool: memhub.MemBuddy | t.CPtr, if looked_up_ty is not None: call_ret_ty = looked_up_ty else: - call_ret_ty = _infer_method_ret_ty(pool, method_name) + # 精确查找:vpi stub 返回注解缓存(方法定义处,不硬编码白名单) + vpi_ret_ty: llvmlite.LLVMType | t.CPtr = _lookup_method_ret_ty_via_vpi( + pool, cls_sha1_rt, class_name, method_name) + if vpi_ret_ty is not None: + call_ret_ty = vpi_ret_ty + else: + call_ret_ty = _fail_method_ret_ty(method_name) # 确保 declare 存在,然后调用 _ensure_method_declare(pool, mod, call_name, call_ret_ty, call_head, call_count) @@ -3735,6 +3713,16 @@ def _translate_method_call(pool: memhub.MemBuddy | t.CPtr, # 查找函数(支持 SHA1 后缀匹配) found_func: llvmlite.Function | t.CPtr = find_func_in_module(mod, lookup_name) + # 泛型模板类方法:特化函数名带类型参数({sha1}.list[int].get), + # "ClassName.method" 短名匹配不到。特化由触发方(通常即调用方)模块完成, + # 用对象实际特化名(mc_entry.Name)构造后缀匹配,获得精确返回类型。 + if found_func is None and mc_entry is not None and mc_entry.Name is not None: + if string.strcmp(mc_entry.Name, class_name) != 0: + spec_lookup: t.CChar | t.CPtr = pool.alloc(160) + if spec_lookup is not None: + viperlib.snprintf(spec_lookup, 160, "%s.%s", mc_entry.Name, method_name) + found_func = find_func_in_module(mod, spec_lookup) + # 沿继承链查找父类方法(子类未覆盖时回退到父类) if found_func is None: cur_parent: str = None @@ -3792,13 +3780,19 @@ def _translate_method_call(pool: memhub.MemBuddy | t.CPtr, call_ret_ty = frt else: # found_func 为 None(stub 未注入):尝试从 text.ll 查找实际返回类型 - # 避免指针返回值被 _infer_method_ret_ty 默认为 i32 导致 inttoptr 截断 + # 避免指针返回值被默认 i32 导致 inttoptr 截断 looked_up_ty: llvmlite.LLVMType | t.CPtr = _LookupMethodRetTyFromTextLL( pool, cls_sha1_mc, class_name, method_name) if looked_up_ty is not None: call_ret_ty = looked_up_ty else: - call_ret_ty = _infer_method_ret_ty(pool, method_name) + # 精确查找:vpi stub 返回注解缓存(方法定义处,不硬编码白名单) + vpi_ret_ty: llvmlite.LLVMType | t.CPtr = _lookup_method_ret_ty_via_vpi( + pool, cls_sha1_mc, class_name, method_name) + if vpi_ret_ty is not None: + call_ret_ty = vpi_ret_ty + else: + call_ret_ty = _fail_method_ret_ty(method_name, cl) # 翻译调用参数 cargs: list[ast.AST | t.CPtr] | t.CPtr = cl.args @@ -5002,7 +4996,11 @@ def translate_call(pool: memhub.MemBuddy | t.CPtr, ret_ty3: llvmlite.LLVMType | t.CPtr = _LookupMethodRetTyFromTextLL( pool, sha1_fc, cls_nm2, func_name) if ret_ty3 is None: - ret_ty3 = _infer_method_ret_ty(pool, func_name) + # 精确查找:vpi stub 返回注解缓存(方法定义处,不硬编码白名单) + ret_ty3 = _lookup_method_ret_ty_via_vpi( + pool, sha1_fc, cls_nm2, func_name) + if ret_ty3 is None: + ret_ty3 = _fail_method_ret_ty(func_name) _ensure_method_declare(pool, mod, mangled3, ret_ty3, head3, total3) return llvmlite.build_call(builder, mangled3, head3, total3, ret_ty3, 0) @@ -5975,122 +5973,122 @@ def translate_printf_call(pool: memhub.MemBuddy | t.CPtr, # is_variadic=1: 生成带显式函数类型签名的 call(如 call i32 (i8*, ...) @printf), # 让 llc 识别为 variadic 调用,生成正确的 Win64 ABI 代码(浮点参数同时传 XMM 和整数寄存器) - llvmlite.build_call(builder, "printf", fmt_ptr, can, i32_ty, 1) - - -# ============================================================ -# 翻译 print 调用 — 类型感知格式化 + 默认换行 -# -# 与 printf 不同,print 不需要用户提供格式串:每个参数按 LLVM -# 类型自动选择格式(%s/%d/%f/0x%llx),结尾追加 "\n"(对齐 Python -# print 默认 end="\n",以及 TPC HandlesExprBuiltin._HandlePrintLlvm)。 -# -# 类型分派规则: -# i8*(str/bytes) → %s -# 其他指针(struct*/int*) → 0x%llx(ptrtoint 到 i64) -# i1(bool) → %d(zext 到 i32) -# i8/i16/i32 → %d(sext 到 i32,C ABI 整数提升) -# i64 → %lld -# float/double → %f(float fpext 到 double,C ABI 提升) -# 无法识别的类型 → "(unknown)"(不传参) -# ============================================================ -def translate_print_call(pool: memhub.MemBuddy | t.CPtr, - builder: llvmlite.IRBuilder | t.CPtr, - mod: llvmlite.LLVMModule | t.CPtr, - cl: ast.Call | t.CPtr, - trans: HT.Translator | t.CPtr = None): - """翻译 print 调用(类型感知格式化 + 默认换行)""" - cargs: list[ast.AST | t.CPtr] | t.CPtr = cl.args - if cargs is None: - return - - FMT_BUF_SIZE: t.CSizeT = 256 - fmt_buf: bytes = stdlib.malloc(FMT_BUF_SIZE) - if fmt_buf is None: - return - fmt_buf[0] = '\0' - - i32_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int32(pool) - i64_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int64(pool) - dbl_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Double(pool) - - can: t.CSizeT = cargs.__len__() - arg_head: llvmlite.Value | t.CPtr = None - arg_tail: llvmlite.Value | t.CPtr = None - arg_count: int = 1 # 格式串本身是第 1 个 printf 参数 - - for ai in range(can): - arg: ast.AST | t.CPtr = cargs.get(ai) - if arg is None: - continue - arg_val: llvmlite.Value | t.CPtr = HandlesExpr.translate_value( - builder, pool, mod, arg, None, 0, trans) - if arg_val is None or arg_val.Ty is None: - # 参数翻译失败 → "(null)"(与 TPC 一致,不传参) - if string.strlen(fmt_buf) < FMT_BUF_SIZE - 16: - viperlib.snprintf(fmt_buf + string.strlen(fmt_buf), 16, "(null)") - continue - - token: t.CChar | t.CPtr = None - chain_arg: int = 1 # 1=该 token 消耗一个参数,0=纯文本 - arg_bits: int = HandlesExpr.get_llvm_type_bits(arg_val.Ty) - arg_fbits: int = HandlesExpr.get_llvm_float_bits(arg_val.Ty) - - if HandlesExpr.is_ptr_type(arg_val.Ty) != 0: - # i8*(str/bytes)→ %s;其他指针 → 0x%llx - pointee: llvmlite.LLVMType | t.CPtr = arg_val.Ty.Pointee - if pointee is not None and HandlesExpr.get_llvm_type_bits(pointee) == 8: - token = "%s" - else: - token = "0x%llx" - arg_val = llvmlite.build_ptrtoint(builder, arg_val, i64_ty) - elif arg_bits != 0: - if arg_bits == 1: - token = "%d" - arg_val = llvmlite.build_zext(builder, arg_val, i32_ty) - elif arg_bits == 8: - token = "%d" - arg_val = llvmlite.build_sext(builder, arg_val, i32_ty) - elif arg_bits == 16: - token = "%d" - arg_val = llvmlite.build_sext(builder, arg_val, i32_ty) - elif arg_bits == 32: - token = "%d" - else: - token = "%lld" - elif arg_fbits != 0: - token = "%f" - if arg_fbits < 64: - arg_val = llvmlite.build_fpext(builder, arg_val, dbl_ty) - else: - # 结构体值/数组/函数等 → "(unknown)",不传参 - token = "(unknown)" - chain_arg = 0 - - # 追加格式 token - if string.strlen(fmt_buf) < FMT_BUF_SIZE - 16: - viperlib.snprintf(fmt_buf + string.strlen(fmt_buf), 16, "%s", token) - # 链接参数 - if chain_arg != 0: - arg_val.Next = None - if arg_tail is None: - arg_head = arg_val - else: - arg_tail.Next = arg_val - arg_tail = arg_val - arg_count += 1 - - # 追加默认换行(Python print 默认 end="\n") - if string.strlen(fmt_buf) < FMT_BUF_SIZE - 2: - viperlib.snprintf(fmt_buf + string.strlen(fmt_buf), 2, "\n") - - fmt_ptr: llvmlite.Value | t.CPtr = HandlesExpr.create_global_string( - builder, pool, mod, fmt_buf, trans) - stdlib.free(fmt_buf) - if fmt_ptr is None: - return - fmt_ptr.Next = arg_head - # is_variadic=1: 带显式函数类型签名(C ABI 变参调用) + llvmlite.build_call(builder, "printf", fmt_ptr, can, i32_ty, 1) + + +# ============================================================ +# 翻译 print 调用 — 类型感知格式化 + 默认换行 +# +# 与 printf 不同,print 不需要用户提供格式串:每个参数按 LLVM +# 类型自动选择格式(%s/%d/%f/0x%llx),结尾追加 "\n"(对齐 Python +# print 默认 end="\n",以及 TPC HandlesExprBuiltin._HandlePrintLlvm)。 +# +# 类型分派规则: +# i8*(str/bytes) → %s +# 其他指针(struct*/int*) → 0x%llx(ptrtoint 到 i64) +# i1(bool) → %d(zext 到 i32) +# i8/i16/i32 → %d(sext 到 i32,C ABI 整数提升) +# i64 → %lld +# float/double → %f(float fpext 到 double,C ABI 提升) +# 无法识别的类型 → "(unknown)"(不传参) +# ============================================================ +def translate_print_call(pool: memhub.MemBuddy | t.CPtr, + builder: llvmlite.IRBuilder | t.CPtr, + mod: llvmlite.LLVMModule | t.CPtr, + cl: ast.Call | t.CPtr, + trans: HT.Translator | t.CPtr = None): + """翻译 print 调用(类型感知格式化 + 默认换行)""" + cargs: list[ast.AST | t.CPtr] | t.CPtr = cl.args + if cargs is None: + return + + FMT_BUF_SIZE: t.CSizeT = 256 + fmt_buf: bytes = stdlib.malloc(FMT_BUF_SIZE) + if fmt_buf is None: + return + fmt_buf[0] = '\0' + + i32_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int32(pool) + i64_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int64(pool) + dbl_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Double(pool) + + can: t.CSizeT = cargs.__len__() + arg_head: llvmlite.Value | t.CPtr = None + arg_tail: llvmlite.Value | t.CPtr = None + arg_count: int = 1 # 格式串本身是第 1 个 printf 参数 + + for ai in range(can): + arg: ast.AST | t.CPtr = cargs.get(ai) + if arg is None: + continue + arg_val: llvmlite.Value | t.CPtr = HandlesExpr.translate_value( + builder, pool, mod, arg, None, 0, trans) + if arg_val is None or arg_val.Ty is None: + # 参数翻译失败 → "(null)"(与 TPC 一致,不传参) + if string.strlen(fmt_buf) < FMT_BUF_SIZE - 16: + viperlib.snprintf(fmt_buf + string.strlen(fmt_buf), 16, "(null)") + continue + + token: t.CChar | t.CPtr = None + chain_arg: int = 1 # 1=该 token 消耗一个参数,0=纯文本 + arg_bits: int = HandlesExpr.get_llvm_type_bits(arg_val.Ty) + arg_fbits: int = HandlesExpr.get_llvm_float_bits(arg_val.Ty) + + if HandlesExpr.is_ptr_type(arg_val.Ty) != 0: + # i8*(str/bytes)→ %s;其他指针 → 0x%llx + pointee: llvmlite.LLVMType | t.CPtr = arg_val.Ty.Pointee + if pointee is not None and HandlesExpr.get_llvm_type_bits(pointee) == 8: + token = "%s" + else: + token = "0x%llx" + arg_val = llvmlite.build_ptrtoint(builder, arg_val, i64_ty) + elif arg_bits != 0: + if arg_bits == 1: + token = "%d" + arg_val = llvmlite.build_zext(builder, arg_val, i32_ty) + elif arg_bits == 8: + token = "%d" + arg_val = llvmlite.build_sext(builder, arg_val, i32_ty) + elif arg_bits == 16: + token = "%d" + arg_val = llvmlite.build_sext(builder, arg_val, i32_ty) + elif arg_bits == 32: + token = "%d" + else: + token = "%lld" + elif arg_fbits != 0: + token = "%f" + if arg_fbits < 64: + arg_val = llvmlite.build_fpext(builder, arg_val, dbl_ty) + else: + # 结构体值/数组/函数等 → "(unknown)",不传参 + token = "(unknown)" + chain_arg = 0 + + # 追加格式 token + if string.strlen(fmt_buf) < FMT_BUF_SIZE - 16: + viperlib.snprintf(fmt_buf + string.strlen(fmt_buf), 16, "%s", token) + # 链接参数 + if chain_arg != 0: + arg_val.Next = None + if arg_tail is None: + arg_head = arg_val + else: + arg_tail.Next = arg_val + arg_tail = arg_val + arg_count += 1 + + # 追加默认换行(Python print 默认 end="\n") + if string.strlen(fmt_buf) < FMT_BUF_SIZE - 2: + viperlib.snprintf(fmt_buf + string.strlen(fmt_buf), 2, "\n") + + fmt_ptr: llvmlite.Value | t.CPtr = HandlesExpr.create_global_string( + builder, pool, mod, fmt_buf, trans) + stdlib.free(fmt_buf) + if fmt_ptr is None: + return + fmt_ptr.Next = arg_head + # is_variadic=1: 带显式函数类型签名(C ABI 变参调用) llvmlite.build_call(builder, "printf", fmt_ptr, arg_count, i32_ty, 1) @@ -6704,57 +6702,57 @@ def _register_global_defaults(name: str, _global_defaults_count += 1 return 0 -def find_func_defaults(name: str) -> FuncDefaultsEntry | t.CPtr: - """按函数名查找默认参数信息,返回 FuncDefaultsEntry 或 None""" - if name is None or _global_defaults is None: - return None - entry_size: t.CSizeT = FuncDefaultsEntry.__sizeof__() - i: int = 0 - while i < _global_defaults_count: - entry_addr: t.CUInt64T = t.CUInt64T(_global_defaults) + i * entry_size - entry: FuncDefaultsEntry | t.CPtr = (FuncDefaultsEntry | t.CPtr)(t.CVoid(entry_addr, t.CPtr)) - if entry.Name is not None: - if string.strcmp(entry.Name, name) == 0: - return entry - i += 1 - return None - - -# _build_const_from_snapshot — 从常量快照重建常量 IR 值 -# -# 用于跨模块默认参数填充:gd_entry.Defaults(pool 内 AST)可能已被 reset 回收, -# 改用注册时提取的 stdlib 常量快照(ConstKinds/ConstInts/ConstStrs)重建常量 IR。 -# 返回 llvmlite.Value*,失败返回 None。 -def _build_const_from_snapshot(pool: memhub.MemBuddy | t.CPtr, - mod: llvmlite.LLVMModule | t.CPtr, - gd_entry: FuncDefaultsEntry | t.CPtr, - di: int, - trans: HT.Translator | t.CPtr, - builder: llvmlite.IRBuilder | t.CPtr) -> llvmlite.Value | t.CPtr: - """从快照重建第 di 个默认值常量 IR""" - if gd_entry is None or gd_entry.ConstKinds is None or gd_entry.ConstInts is None: - return None - if di < 0 or di >= gd_entry.DefaultCount: - return None - kind: t.CInt = gd_entry.ConstKinds[di] - ival: t.CInt64T = gd_entry.ConstInts[di] - if kind == ast.CONST_INT: - if ival > 2147483647 or ival < -2147483648: - return llvmlite.const_int64(pool, ival) - return llvmlite.const_int32(pool, ival) - elif kind == ast.CONST_BOOL: - if ival != 0: - return llvmlite.const_int32(pool, 1) - return llvmlite.const_int32(pool, 0) - elif kind == ast.CONST_NONE: - i8_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int8(pool) - i8_ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, i8_ty) - return llvmlite.ConstNull(pool, i8_ptr_ty, "null") - elif kind == ast.CONST_STR: - if gd_entry.ConstStrs is not None and gd_entry.ConstStrs[di] != 0: - sv: str = (str | t.CPtr)(t.CVoid(gd_entry.ConstStrs[di], t.CPtr)) - return HandlesExpr.create_global_string(builder, pool, mod, sv, trans) - # 无法重建的(如 CONST_FLOAT/复杂表达式)返回 None,调用方回退到 AST +def find_func_defaults(name: str) -> FuncDefaultsEntry | t.CPtr: + """按函数名查找默认参数信息,返回 FuncDefaultsEntry 或 None""" + if name is None or _global_defaults is None: + return None + entry_size: t.CSizeT = FuncDefaultsEntry.__sizeof__() + i: int = 0 + while i < _global_defaults_count: + entry_addr: t.CUInt64T = t.CUInt64T(_global_defaults) + i * entry_size + entry: FuncDefaultsEntry | t.CPtr = (FuncDefaultsEntry | t.CPtr)(t.CVoid(entry_addr, t.CPtr)) + if entry.Name is not None: + if string.strcmp(entry.Name, name) == 0: + return entry + i += 1 + return None + + +# _build_const_from_snapshot — 从常量快照重建常量 IR 值 +# +# 用于跨模块默认参数填充:gd_entry.Defaults(pool 内 AST)可能已被 reset 回收, +# 改用注册时提取的 stdlib 常量快照(ConstKinds/ConstInts/ConstStrs)重建常量 IR。 +# 返回 llvmlite.Value*,失败返回 None。 +def _build_const_from_snapshot(pool: memhub.MemBuddy | t.CPtr, + mod: llvmlite.LLVMModule | t.CPtr, + gd_entry: FuncDefaultsEntry | t.CPtr, + di: int, + trans: HT.Translator | t.CPtr, + builder: llvmlite.IRBuilder | t.CPtr) -> llvmlite.Value | t.CPtr: + """从快照重建第 di 个默认值常量 IR""" + if gd_entry is None or gd_entry.ConstKinds is None or gd_entry.ConstInts is None: + return None + if di < 0 or di >= gd_entry.DefaultCount: + return None + kind: t.CInt = gd_entry.ConstKinds[di] + ival: t.CInt64T = gd_entry.ConstInts[di] + if kind == ast.CONST_INT: + if ival > 2147483647 or ival < -2147483648: + return llvmlite.const_int64(pool, ival) + return llvmlite.const_int32(pool, ival) + elif kind == ast.CONST_BOOL: + if ival != 0: + return llvmlite.const_int32(pool, 1) + return llvmlite.const_int32(pool, 0) + elif kind == ast.CONST_NONE: + i8_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int8(pool) + i8_ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, i8_ty) + return llvmlite.ConstNull(pool, i8_ptr_ty, "null") + elif kind == ast.CONST_STR: + if gd_entry.ConstStrs is not None and gd_entry.ConstStrs[di] != 0: + sv: str = (str | t.CPtr)(t.CVoid(gd_entry.ConstStrs[di], t.CPtr)) + return HandlesExpr.create_global_string(builder, pool, mod, sv, trans) + # 无法重建的(如 CONST_FLOAT/复杂表达式)返回 None,调用方回退到 AST return None def find_func_entry_in_table(funcs: FuncEntry | t.CPtr,