"fix(compiler): vpi 返回注解缓存按需加载 + 动态扩容

1. _lookup_method_ret_ty_via_vpi 查缓存前按需读取
   {temp_dir}/{sha1[0:2]}/{sha1}.vpi 填充缓存,覆盖模块自身翻译时
   (同模块方法调用、定义在后)stub 尚未注入的场景
2. 缓存容量由固定 4096 改为 stdlib.realloc 翻倍动态扩容(上限 1M),
   根治写满后条目静默丢弃导致 JsonValue._pool_alloc 等查不到"
This commit is contained in:
TermiNexus committed 2026-08-08 02:24:12 +08:00
1 parent 09833a80eb
commit a9ebb20790
2 files changed
+352 -342

No files matched your search

+108 -96
View File
@@ -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 - 查找缓存的方法返回注解字符串
# ============================================================
+244 -246
View File
@@ -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,