Files
ViperC/App/lib/StubGen/VpiReader.vp
T
TermiNexus 987088fdff 'fix(core): support keyword args in method calls
_translate_method_call ignored cl.keywords, so keyword-style calls
like add_argument("--count", short="-c", arg_type=INT) lost all
keyword values (short_name/arg_type stayed default), breaking
Test 4. Match keywords against function params by name, fill
missing params with zero values to keep call arity consistent.'
2026-08-07 17:30:41 +08:00

886 lines
41 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import t, c
from stdint import *
import ast
import string
import stdlib
import stdio
import sys
import memhub
import llvmlite
import viperlib
import w32.fileio as fileio
import lib.core.VLogger as VLogger
import lib.core.Handles.HandlesType as HandlesType
import lib.core.Handles.HandlesStruct as HandlesStruct
import lib.core.Handles.HandlesExprCall as HandlesExprCall
import lib.core.Handles.HandlesFunctions as HandlesFunctions
import lib.core.Handles.HandlesTranslator as HT
# ============================================================
# 方法返回注解缓存(stdlib 持久)
#
# 背景:vpi tree 在"解析后立即"有效,但遍历处理到后续类的方法时,
# 深层 AST 节点(如 parse_args 的 returns 注解)可能已被覆盖损坏
# (kind() 读取异常),直接 resolve 得到垃圾类型(如 i8*)。
# 因此在 tree 解析后立即把每个方法的返回注解转成字符串缓存到 stdlib
# (持久,不受 pool 状态影响),_inject_method_declare 用 _parse_type_string
# 重新解析缓存字符串(新 AST 节点,立即使用不被覆盖)。
# ============================================================
VPI_RET_MAX: t.CDefine = 4096
VPI_KEY_LEN: t.CDefine = 224
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)
# ============================================================
# _cache_vpi_return_annots_text - 从 vpi_buf 文本直接提取方法返回注解
#
# 完全不依赖 AST tree(tree 深层节点在后续处理中可能损坏),
# 直接按 vpi 生成文件的行格式提取:
# " def method(...) -> Ret: pass"(类内方法,缩进)
# "def method(...) -> Ret: pass"(模块级函数)
# "class X:"(跟踪当前类名)
# ============================================================
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
if vpi_buf 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)
buf_len: t.CSizeT = string.strlen(vpi_buf)
cur_cls: bytes = None
pos: t.CSizeT = 0
line_no: int = 0
while pos < buf_len:
ls: t.CSizeT = pos
le: t.CSizeT = pos
while le < buf_len and vpi_buf[le] != '\n':
le += 1
line_len: t.CSizeT = le - ls
# 跳过前导空格,检查 class/def
i: t.CSizeT = ls
while i < le and vpi_buf[i] == ' ':
i += 1
if i < le:
# class X:
if line_len >= 6 and string.strncmp(vpi_buf + i, "class ", 6) == 0:
cstart: t.CSizeT = i + 6
cend: t.CSizeT = cstart
while cend < le and vpi_buf[cend] != ':' and vpi_buf[cend] != ' ':
cend += 1
if cend > cstart:
if cur_cls is not None:
stdlib.free(cur_cls)
cur_cls = stdlib.malloc(cend - cstart + 1)
if cur_cls is not None:
string.strncpy(cur_cls, vpi_buf + cstart, cend - cstart)
cur_cls[cend - cstart] = '\0'
# def name(...) -> Ret:
elif line_len >= 4 and string.strncmp(vpi_buf + i, "def ", 4) == 0:
dstart: t.CSizeT = i + 4
dend: t.CSizeT = dstart
while dend < le and vpi_buf[dend] != '(':
dend += 1
# 方法名 = [dstart, dend)
# 找 "->"
rpos: t.CSizeT = dstart
arrow_pos: t.CSizeT = le
while rpos < le - 1:
if vpi_buf[rpos] == '-' and vpi_buf[rpos + 1] == '>':
arrow_pos = rpos
break
rpos += 1
if arrow_pos < le and dend > dstart:
# 返回类型 = arrow_pos+2 跳过空格 到 ':' 前
rstart: t.CSizeT = arrow_pos + 2
while rstart < le and vpi_buf[rstart] == ' ':
rstart += 1
rend: t.CSizeT = rstart
while rend < le and vpi_buf[rend] != ':':
rend += 1
if rend > rstart:
# 构造 key 和 ret_str,存入缓存
mname_len: t.CSizeT = dend - dstart
rtype_len: t.CSizeT = rend - rstart
cls_len2: t.CSizeT = 0
if cur_cls is not None:
cls_len2 = string.strlen(cur_cls)
key_len: t.CSizeT = sha1_len + 1 + cls_len2 + 1 + mname_len
if key_len < VPI_KEY_LEN and rtype_len < VPI_RET_LEN:
# 方法名复制到临时缓冲(vpi_buf+dstart 非 null 结尾,不能直接 snprintf)
mname_buf: bytes = stdlib.malloc(mname_len + 1)
if mname_buf is not None:
string.strncpy(mname_buf, vpi_buf + dstart, mname_len)
mname_buf[mname_len] = '\0'
key_buf: bytes = stdlib.malloc(key_len + 1)
if key_buf is not None:
if cur_cls is not None:
viperlib.snprintf(key_buf, key_len + 1, "%s.%s.%s", src_sha1, cur_cls, mname_buf)
else:
viperlib.snprintf(key_buf, key_len + 1, "%s.%s", src_sha1, mname_buf)
# 去重:同一 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:
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
stdlib.free(key_buf)
stdlib.free(mname_buf)
pos = le + 1
line_no += 1
if cur_cls is not None:
stdlib.free(cur_cls)
return 1
# ============================================================
# _lookup_vpi_ret_annot - 查找缓存的方法返回注解字符串
# ============================================================
def _lookup_vpi_ret_annot(src_sha1: str, cls_name: str, method_name: str) -> str:
"""查找缓存的方法返回注解字符串,未找到返回 None"""
if _g_vpi_ret_keys is None or _g_vpi_ret_count == 0:
return None
if src_sha1 is None or method_name is None:
return None
sha1_len: t.CSizeT = string.strlen(src_sha1)
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
key_buf: bytes = stdlib.malloc(key_len + 1)
if key_buf is None:
return None
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)
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
stdlib.free(key_buf)
return None
# ============================================================
# _resolve_single_vpi_type_str - 解析单个类型段(无 CPtr/CVoid)为 LLVMType
#
# 纯字符串解析,不构造 AST 节点。
# ============================================================
def _resolve_single_vpi_type_str(pool: memhub.MemBuddy | t.CPtr,
seg: str) -> llvmlite.LLVMType | t.CPtr:
"""解析单个类型段为 LLVMType,无法识别返回 None"""
if seg is None or pool is None:
return None
i8_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int8(pool)
i8_ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, i8_ty)
# 内置别名
if string.strcmp(seg, "str") == 0 or string.strcmp(seg, "bytes") == 0:
return i8_ptr_ty
if string.strcmp(seg, "int") == 0:
return llvmlite.Int32(pool)
if string.strcmp(seg, "bool") == 0:
return llvmlite.Int1(pool)
# t.XXX / c.XXX 内置类型映射
if string.strncmp(seg, "t.", 2) == 0 or string.strncmp(seg, "c.", 2) == 0:
attr_nm: str = seg + 2
mapped_ty: llvmlite.LLVMType | t.CPtr = HandlesType.map_t_type(pool, attr_nm)
if mapped_ty is not None:
return mapped_ty
return None
# 模块限定类名 "memhub.MemBuddy" → 取最后一段查 struct
last_dot: str = string.strrchr(seg, '.')
if last_dot is not None:
cls_nm2: str = last_dot + 1
mod_struct_ty: llvmlite.LLVMType | t.CPtr = HandlesStruct.get_struct_type(cls_nm2)
if mod_struct_ty is not None:
return mod_struct_ty
return None
# 裸类名 → 全局 struct 注册表
bare_struct_ty: llvmlite.LLVMType | t.CPtr = HandlesStruct.get_struct_type(seg)
if bare_struct_ty is not None:
return bare_struct_ty
# 裸 typedef 名(如 VOIDPTR)
mapped_bare: llvmlite.LLVMType | t.CPtr = HandlesType.map_t_type(pool, seg)
if mapped_bare is not None:
return mapped_bare
return None
# ============================================================
# _resolve_vpi_ret_annot_str - 直接解析 vpi 返回注解字符串为 LLVMType
#
# 背景:_parse_type_string 跨模块构造 ast.Name/ast.BinOp 在 TPV 中可能
# 退化为裸函数调用(__init__/vtable 未执行),节点 kind() 异常导致
# resolve 得到垃圾类型(如 Ptr(i8))。此处纯字符串解析避免该问题。
#
# 支持: "str"/"bytes" → i8*; "int" → i32; "bool" → i1;
# "t.CChar" 等 → map_t_type 映射;
# "X | t.CPtr" → Ptr(Struct X); "t.CVoid | t.CPtr" → i8*;
# "memhub.MemBuddy | t.CPtr" → Ptr(Struct MemBuddy)。
# ============================================================
def _resolve_vpi_ret_annot_str(pool: memhub.MemBuddy | t.CPtr,
ret_annot: str,
i32_ty: llvmlite.LLVMType | t.CPtr,
i8_ptr_ty: llvmlite.LLVMType | t.CPtr) -> llvmlite.LLVMType | t.CPtr:
"""直接解析 vpi 返回注解字符串为 LLVMType(不构造 AST 节点)"""
if ret_annot is None or pool is None:
return None
has_cptr: int = 0
has_cvoid: int = 0
first_ty: llvmlite.LLVMType | t.CPtr = None
buf_len: t.CSizeT = string.strlen(ret_annot)
pos: t.CSizeT = 0
while pos <= buf_len:
# 跳过前导空格
while pos < buf_len and ret_annot[pos] == ' ':
pos += 1
seg_start: t.CSizeT = pos
while pos < buf_len and ret_annot[pos] != '|':
pos += 1
seg_end: t.CSizeT = pos
# trim 尾随空格
while seg_end > seg_start and ret_annot[seg_end - 1] == ' ':
seg_end -= 1
if seg_end > seg_start:
seg_len2: t.CSizeT = seg_end - seg_start
seg_buf: bytes = stdlib.malloc(seg_len2 + 1)
if seg_buf is None:
break
string.strncpy(seg_buf, ret_annot + seg_start, seg_len2)
seg_buf[seg_len2] = '\0'
if string.strcmp(seg_buf, "t.CPtr") == 0 or string.strcmp(seg_buf, "CPtr") == 0:
has_cptr = 1
elif string.strcmp(seg_buf, "t.CVoid") == 0 or string.strcmp(seg_buf, "CVoid") == 0:
has_cvoid = 1
else:
seg_ty: llvmlite.LLVMType | t.CPtr = _resolve_single_vpi_type_str(pool, seg_buf)
if seg_ty is not None and first_ty is None:
first_ty = seg_ty
stdlib.free(seg_buf)
pos += 1 # 跳过 '|'
if has_cptr != 0:
if has_cvoid != 0:
return i8_ptr_ty
if first_ty is not None:
return llvmlite.Ptr(pool, first_ty)
return i8_ptr_ty
if first_ty is not None:
return first_ty
return None
# ============================================================
# _ddebug_exit - DDEBUG 拦截退出辅助函数
#
# 刷新 stderr 缓冲区并通过 VLogger 输出可见消息后退出。
# 解决 sys.exit(1) 不刷新缓冲区导致 DDEBUG 消息丢失的问题。
# ============================================================
def _ddebug_exit() -> None:
stdio.fflush(stdio.stderr)
VLogger.error("DDEBUG intercept triggered - check stderr for details")
# ============================================================
# VpiReader - .vpi AST 解析器
#
# 从 .vpi 存根文件读取函数签名和 CTypedef 定义,注入 declare
# 到当前模块。用于跨模块函数调用时补充缺失的函数声明。
#
# .vpi 文件由 StubGen.Converter._GeneratePyiFromAst 生成,
# 包含函数签名(无函数体)、类定义、变量声明、import 语句。
# VpiReader 解析 .vpi 为 AST,提取 FunctionDef 和 CTypedef
# AnnAssign,在当前模块创建对应的 declare 声明。
# ============================================================
# .vpi 读取缓冲区大小(256KB,与 PYI_BUF_SIZE 一致)
VPI_READ_BUF_SIZE: t.CSizeT = 262144
# SHA1 字符串长度(16 hex 字符)
SHA1_LEN: t.CSizeT = 16
# ============================================================
# _extract_sha1_from_path - 从 .vpi 文件路径提取 SHA1
#
# .vpi 文件名格式为 {sha1}.vpi,sha1 是 16 位十六进制字符。
# 从路径中找到最后一个路径分隔符(/ 或 \),取后续文件名的前
# 16 个字符作为 SHA1。分配新缓冲区存储 SHA1(调用者负责 free)。
#
# Args:
# vpi_path: .vpi 文件路径
#
# Returns:
# SHA1 字符串(stdlib.malloc 分配,需 stdlib.free),None 失败
# ============================================================
def _extract_sha1_from_path(vpi_path: str) -> str:
"""从 .vpi 文件路径提取 SHA1(文件名前16字符,malloc 分配)"""
if vpi_path is None:
return None
# 查找最后一个路径分隔符 / 或 \
total_len: t.CSizeT = string.strlen(vpi_path)
if total_len < SHA1_LEN + 4: # 至少需要 "xxxx.vpi" 的长度
return None
# 从后向前查找最后一个 / 或 \
sep_pos: t.CSizeT = total_len
while sep_pos > 0:
sep_pos -= 1
ch: t.CChar = vpi_path[sep_pos]
if ch == '/' or ch == '\\':
sep_pos += 1
break
if sep_pos + SHA1_LEN > total_len:
return None
# 分配缓冲区并复制 SHA1(16 字符 + null 终止符)
sha1_buf: str = stdlib.malloc(SHA1_LEN + 1)
if sha1_buf is None:
return None
string.strncpy(sha1_buf, vpi_path + sep_pos, SHA1_LEN)
sha1_buf[SHA1_LEN] = '\0'
# DDEBUG: 检测从路径提取的 SHA1 是否带尾随点(>=17 字符),发现问题立即终止
_ext_sl: t.CSizeT = string.strlen(sha1_buf)
if _ext_sl >= 17:
stdio.printf("[DDEBUG] _extract_sha1_from_path too long: len=%d sha1='%s' path='%s'\n", _ext_sl, sha1_buf, vpi_path)
_ddebug_exit()
return sha1_buf
# ============================================================
# ParseVpiIntoModule - 解析 .vpi 文件,将函数声明注入到当前模块
#
# 读取 .vpi 文件 → AST 解析 → 遍历顶层节点 → 创建 declare 声明。
# 对每个 FunctionDef:
# 1. 提取函数名、参数、返回类型
# 2. 检测 CExport/CExtern/State 标记(决定是否用裸名)
# 3. 用 find_func_in_module 去重(已存在则跳过)
# 4. 用 create_declare + add_param 创建 declare 声明
# 5. 注册到函数表(用裸名,非 mangled_name)
# 对 AnnAssign with t.CTypedef:调用 register_ctypedef 注册
# ClassDef/Import/ImportFrom:跳过
#
# Args:
# pool: 内存池
# mod: 当前 LLVM 模块
# vpi_path: .vpi 文件路径
# trans: 翻译器对象
#
# Returns:
# 1=成功,0=失败
# ============================================================
def ParseVpiIntoModule(pool: memhub.MemBuddy | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
vpi_path: str,
trans: HT.Translator | t.CPtr) -> int:
"""解析 .vpi 文件,将函数声明注入到当前模块"""
if pool is None or mod is None or vpi_path is None or trans is None:
return 0
# 获取当前翻译器的导入信息(供 resolve_annotation_type 使用)
imported_modules: str = trans._imported_modules
from_imports: str = trans._from_imports
# 1. 读取 .vpi 文件
f: fileio.File | t.CPtr = fileio.File(vpi_path, fileio.MODE.R)
if f.closed:
return 0
vpi_buf: bytes = stdlib.malloc(VPI_READ_BUF_SIZE)
if vpi_buf is None:
f.close()
return 0
bytes_read: LONG = f.read_all(vpi_buf, VPI_READ_BUF_SIZE)
f.close()
if bytes_read <= 0:
stdlib.free(vpi_buf)
return 0
if bytes_read < VPI_READ_BUF_SIZE:
vpi_buf[bytes_read] = 0
else:
vpi_buf[VPI_READ_BUF_SIZE - 1] = 0
# 2. 解析为 AST
lx: ast.Lexer | t.CPtr = ast.new_lexer(pool)
if lx is None:
stdlib.free(vpi_buf)
return 0
ast._lexer_init(lx, vpi_buf, pool)
tokens: ast.Token | t.CPtr = ast.tokenize(lx)
tree: ast.AST | t.CPtr = ast.parse_tokens(pool, tokens)
# vpi_buf 延迟释放:AST 字符串字段可能引用 vpi_buf 内部(token 零拷贝),
# 提前 free 会导致 Name.id/Attribute.attr 等悬空 → 后续解析返回错误类型。
# 在 ParseVpiIntoModule 末尾统一释放。
if tree is None:
stdlib.free(vpi_buf)
return 0
# 提取源模块 SHA1(用于函数名混淆,在 AST 解析成功后提取避免提前返回时泄漏)
src_sha1: str = _extract_sha1_from_path(vpi_path)
# 从 vpi_buf 文本直接提取方法返回注解字符串缓存到 stdlib
# (不依赖 AST tree:tree 深层节点在后续遍历处理时可能被覆盖损坏,
# 但 vpi_buf 原始文本在 ParseVpiIntoModule 末尾才释放,始终有效)
_cache_vpi_return_annots_text(vpi_buf, src_sha1)
# 3. 遍历 AST 顶层节点
ch: list[ast.AST | t.CPtr] | t.CPtr = tree.children
if ch is None:
if src_sha1 is not None:
stdlib.free(src_sha1)
return 1
n: t.CSizeT = ch.__len__()
i32_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int32(pool)
i8_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Int8(pool)
i8_ptr_ty: llvmlite.LLVMType | t.CPtr = llvmlite.Ptr(pool, i8_ty)
# 函数表信息
funcs_ptr: HandlesExprCall.FuncEntry | t.CPtr = trans._funcs
max_funcs: int = 256
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()
# FunctionDef → 提取函数名、参数、返回类型 → 注入 declare
if kd == ast.ASTKind.FunctionDef:
fd: ast.FunctionDef | t.CPtr = (ast.FunctionDef | t.CPtr)(child)
if fd is None or fd.name is None:
continue
_inject_function_declare(pool, mod, fd, trans, src_sha1,
imported_modules, from_imports,
i32_ty, i8_ptr_ty,
funcs_ptr, max_funcs)
continue
# AnnAssign with t.CTypedef → 注册 CTypedef
if kd == ast.ASTKind.AnnAssign:
aa: ast.AnnAssign | t.CPtr = (ast.AnnAssign | t.CPtr)(child)
if aa is None:
continue
if HandlesType._is_ctypedef_annotation(aa.annotation) != 0:
_register_ctypedef_from_annassign(pool, aa)
continue
# ClassDef → 递归注入类内方法(如 MemBuddy.__init__/alloc)
# 方法 declare 使用 {sha1}.{ClassName}.{method} 混淆名,
# 参数类型从 vpi 注解解析(避免调用时 _ensure_method_declare 用实参类型
# 创建错误 declare,如 size: t.CSizeT 被写成 i32 而非 i64)
if kd == ast.ASTKind.ClassDef:
class_cd: ast.ClassDef | t.CPtr = (ast.ClassDef | t.CPtr)(child)
if class_cd is None or class_cd.name is None:
continue
cls_name_v: str = class_cd.name
cls_children: list[ast.AST | t.CPtr] | t.CPtr = class_cd.children
if cls_children is not None:
cn2: t.CSizeT = cls_children.__len__()
for ci2 in range(cn2):
m_child: ast.AST | t.CPtr = cls_children.get(ci2)
if m_child is None:
continue
if 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
_inject_method_declare(pool, mod, m_fd, cls_name_v, trans,
src_sha1, imported_modules, from_imports,
i32_ty, i8_ptr_ty, funcs_ptr, max_funcs)
continue
# Import/ImportFrom → 跳过
# (import 不需要处理)
# 释放 SHA1 缓冲区(_extract_sha1_from_path 用 stdlib.malloc 分配)
if src_sha1 is not None:
stdlib.free(src_sha1)
# vpi_buf 延迟到全部 tree 处理完成后释放(见解析处注释)
stdlib.free(vpi_buf)
return 1
# ============================================================
# _inject_function_declare - 为单个 FunctionDef 创建 declare 声明
#
# 检测 CExport/CExtern/State 标记,计算 mangled name,
# 用 find_func_in_module 去重,创建 declare 并添加参数,
# 注册到函数表。
# ============================================================
def _inject_function_declare(pool: memhub.MemBuddy | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
fd: ast.FunctionDef | t.CPtr,
trans: HT.Translator | t.CPtr,
src_sha1: str,
imported_modules: str,
from_imports: str,
i32_ty: llvmlite.LLVMType | t.CPtr,
i8_ptr_ty: llvmlite.LLVMType | t.CPtr,
funcs_ptr: HandlesExprCall.FuncEntry | t.CPtr,
max_funcs: int) -> None:
"""为 FunctionDef 创建 declare 声明并注入到模块"""
if fd.name is None:
return
# 检测 CExtern/State/CExport 标记(从返回类型注解)
has_extern: int = 0
has_state: int = 0
has_export: int = 0
if fd.returns is not None:
has_extern = HandlesType.has_decorator_marker(fd.returns, "CExtern")
has_state = HandlesType.has_decorator_marker(fd.returns, "State")
has_export = HandlesType.has_decorator_marker(fd.returns, "CExport")
# 计算 mangled name
# CExport/CExtern/State/main 函数用裸名,其他函数用源模块 SHA1 前缀
use_bare_name: int = 0
if has_extern != 0 or has_state != 0 or has_export != 0 or string.strcmp(fd.name, "main") == 0:
mangled_name: str = fd.name
use_bare_name = 1
else:
mangled_name = HandlesFunctions._mangle_name_with_sha1(src_sha1, fd.name)
# DDEBUG: VpiReader 是注入模块级函数 declare 的路径(无拦截,最可疑双点源头)
# 检测 src_sha1 带尾随点 / fd.name 带前导点 / mangled_name 含双点,发现立即终止
if mangled_name is not None and src_sha1 is not None and fd.name is not None:
_vpi_sl: t.CSizeT = string.strlen(src_sha1)
if _vpi_sl >= 17:
stdio.printf("[DDEBUG] VpiReader src_sha1 too long: len=%d sha1='%s' func='%s'\n", _vpi_sl, src_sha1, fd.name)
_ddebug_exit()
_vpi_fn_len: t.CSizeT = string.strlen(fd.name)
if _vpi_fn_len > 0 and fd.name[0] == '.':
stdio.printf("[DDEBUG] VpiReader fd.name starts with dot: name='%s' sha1='%s'\n", fd.name, src_sha1)
_ddebug_exit()
_vpi_mi: int = 0
_vpi_ml: t.CSizeT = string.strlen(mangled_name)
while _vpi_mi < _vpi_ml - 1:
if mangled_name[_vpi_mi] == 46 and mangled_name[_vpi_mi + 1] == 46:
stdio.printf("[DDEBUG] VpiReader mangled double-dot: sha1='%s' func='%s' mangled='%s'\n", src_sha1, fd.name, mangled_name)
_ddebug_exit()
_vpi_mi += 1
# 去重:如果函数已存在则跳过
existing: llvmlite.Function | t.CPtr = HandlesExprCall.find_func_in_module(mod, mangled_name)
if existing is not None:
return
# 也检查裸名(非裸名模式时,CExport 函数可能已通过其他途径注册)
if use_bare_name == 0:
existing_bare: llvmlite.Function | t.CPtr = HandlesExprCall.find_func_in_module(mod, fd.name)
if existing_bare is not None:
return
# 推断返回类型
# 优先用缓存的返回注解字符串(tree 解析后立即提取,节点未损坏)
# 直接字符串解析(绕开 _parse_type_string 跨模块 AST 构造在 TPV 中的
# __init__/vtable 未执行问题,节点 kind() 异常导致 resolve 得垃圾类型)
ret_ty: llvmlite.LLVMType | t.CPtr = None
ret_annot_fd: str = _lookup_vpi_ret_annot(src_sha1, None, fd.name)
if ret_annot_fd is not None:
ret_ty = _resolve_vpi_ret_annot_str(pool, ret_annot_fd, i32_ty, i8_ptr_ty)
if ret_ty is None and fd.returns is not None:
ret_ty = HandlesType.resolve_annotation_type(
pool, fd.returns, imported_modules, from_imports, trans)
# 如果返回类型是纯 t.State(无实际类型),用 Void
if ret_ty is None and fd.returns is not None:
if has_state != 0:
ret_ty = llvmlite.Void(pool)
# 默认返回 i32
if ret_ty is None:
ret_ty = i32_ty
# 创建 declare
func: llvmlite.Function | t.CPtr = llvmlite.create_declare(
pool, mod, mangled_name, ret_ty)
if func is None:
return
# 提取参数并添加到 declare
param_count: int = 0
if fd.args is not None:
args_node: ast.Arguments | t.CPtr = (ast.Arguments | t.CPtr)(fd.args)
if args_node.args is not None:
args_list: list[ast.AST | t.CPtr] | t.CPtr = args_node.args
param_count = args_list.__len__()
pi: t.CSizeT
for pi in range(param_count):
ag: ast.Arg | t.CPtr = (ast.Arg | t.CPtr)(args_list.get(pi))
if ag is None:
continue
pname: str = ag.arg
if pname is None:
pname = "arg"
# 解析参数类型
param_ty: llvmlite.LLVMType | t.CPtr = i8_ptr_ty
if ag.annotation is not None:
resolved_ty: llvmlite.LLVMType | t.CPtr = HandlesType.resolve_annotation_type(
pool, ag.annotation, imported_modules, from_imports, trans)
if resolved_ty is not None:
# 结构体按值参数 → i64:跨模块结构体在本模块是 opaque
# (未定义字段),LLVM 不允许 opaque struct 作为函数参数
# (llc: invalid type for function argument)。Windows ABI
# 小结构体(≤8 字节,如 COORD)按整数寄存器传,映射 i64 兼容。
match resolved_ty:
case llvmlite.LLVMType.Struct(_, _, _):
param_ty = llvmlite.Int64(pool)
case _:
param_ty = resolved_ty
llvmlite.add_param(pool, func, param_ty, pname)
# *args 变长参数:标记 IsVarArg=1,使调用侧走 C ABI 参数提升
# (i8/i16 → i32,float → double),避免 %d/%x 等格式因参数按 i64
# 传递而读错(第二个 %x 读到第一个 i64 槽的高 4 字节)。
if args_node.vararg is not None:
func.IsVarArg = 1
# 注册到函数表(用裸名 fd.name,不是 mangled_name)
if funcs_ptr is not None:
cur_count: int = trans._func_count
if HandlesExprCall.add_func_to_table(funcs_ptr, cur_count, fd.name, func, max_funcs,
None, 0, param_count) == 0:
trans._func_count = cur_count + 1
# ============================================================
# _inject_method_declare - 为 ClassDef 内的方法创建 declare
#
# 方法混淆名: {src_sha1}.{ClassName}.{method}
# 参数类型从 vpi 注解解析(self 无注解时默认为 i8*)。
# 注入正确签名避免调用时 _ensure_method_declare 用实参类型
# 创建错误 declare(如 size: t.CSizeT 被写成 i32 而非 i64)。
# ============================================================
def _inject_method_declare(pool: memhub.MemBuddy | t.CPtr,
mod: llvmlite.LLVMModule | t.CPtr,
m_fd: ast.FunctionDef | t.CPtr,
cls_name: str,
trans: HT.Translator | t.CPtr,
src_sha1: str,
imported_modules: str,
from_imports: str,
i32_ty: llvmlite.LLVMType | t.CPtr,
i8_ptr_ty: llvmlite.LLVMType | t.CPtr,
funcs_ptr: HandlesExprCall.FuncEntry | t.CPtr,
max_funcs: int) -> None:
"""为 ClassDef 内方法创建 declare 声明并注入到模块"""
if m_fd is None or m_fd.name is None or cls_name is None:
return
if src_sha1 is None:
return
# 构造方法查找名: ClassName.method
lookup_name_len: t.CSizeT = string.strlen(cls_name) + 1 + string.strlen(m_fd.name) + 1
lookup_name: str = pool.alloc(lookup_name_len)
if lookup_name is None:
return
viperlib.snprintf(lookup_name, lookup_name_len, "%s.%s", cls_name, m_fd.name)
# mangled name: {src_sha1}.{ClassName}.{method}
mangled_len: t.CSizeT = string.strlen(src_sha1) + 1 + lookup_name_len
mangled_name: str = pool.alloc(mangled_len)
if mangled_name is None:
return
viperlib.snprintf(mangled_name, mangled_len, "%s.%s", src_sha1, lookup_name)
# 去重:如果方法已存在则跳过
existing: llvmlite.Function | t.CPtr = HandlesExprCall.find_func_in_module(mod, mangled_name)
if existing is not None:
return
# 推断返回类型
# 优先用缓存的返回注解字符串(tree 解析后立即提取,节点未损坏),
# 用 _parse_type_string 重新解析成新 AST(立即使用不被覆盖)。
# 直接解析 m_fd.returns 可能因 vpi tree 深层节点被覆盖而得到垃圾类型。
ret_ty: llvmlite.LLVMType | t.CPtr = None
ret_annot: str = _lookup_vpi_ret_annot(src_sha1, cls_name, m_fd.name)
if ret_annot is not None:
ret_ty = _resolve_vpi_ret_annot_str(pool, ret_annot, i32_ty, i8_ptr_ty)
if ret_ty is None and m_fd.returns is not None:
ret_ty = HandlesType.resolve_annotation_type(
pool, m_fd.returns, imported_modules, from_imports, trans)
# __new__ 无返回注解时推断为类指针(__new__ 返回 self 指针,默认 i32 会截断指针)
if ret_ty is None and string.strcmp(m_fd.name, "__new__") == 0:
cls_ty: llvmlite.LLVMType | t.CPtr = HandlesStruct.get_struct_type(cls_name)
if cls_ty is not None:
ret_ty = llvmlite.Ptr(pool, cls_ty)
if ret_ty is None:
ret_ty = i32_ty
# 创建 declare
func: llvmlite.Function | t.CPtr = llvmlite.create_declare(
pool, mod, mangled_name, ret_ty)
if func is None:
return
# 提取参数并添加到 declare
param_count: int = 0
if m_fd.args is not None:
args_node: ast.Arguments | t.CPtr = (ast.Arguments | t.CPtr)(m_fd.args)
if args_node.args is not None:
args_list: list[ast.AST | t.CPtr] | t.CPtr = args_node.args
param_count = args_list.__len__()
pi: t.CSizeT
for pi in range(param_count):
ag: ast.Arg | t.CPtr = (ast.Arg | t.CPtr)(args_list.get(pi))
if ag is None:
continue
pname: str = ag.arg
if pname is None:
pname = "arg"
# 解析参数类型
param_ty: llvmlite.LLVMType | t.CPtr = i8_ptr_ty
if ag.annotation is not None:
resolved_ty: llvmlite.LLVMType | t.CPtr = HandlesType.resolve_annotation_type(
pool, ag.annotation, imported_modules, from_imports, trans)
if resolved_ty is not None:
# 结构体按值参数 → i64:跨模块结构体在本模块是 opaque
# (未定义字段),LLVM 不允许 opaque struct 作为函数参数
# (llc: invalid type for function argument)。Windows ABI
# 小结构体(≤8 字节,如 COORD)按整数寄存器传,映射 i64 兼容。
match resolved_ty:
case llvmlite.LLVMType.Struct(_, _, _):
param_ty = llvmlite.Int64(pool)
case _:
param_ty = resolved_ty
llvmlite.add_param(pool, func, param_ty, pname)
# 注册到函数表(用裸名 ClassName.method,与调用方 lookup_name 一致)
if funcs_ptr is not None:
cur_count: int = trans._func_count
if HandlesExprCall.add_func_to_table(funcs_ptr, cur_count, lookup_name, func, max_funcs,
None, 0, param_count) == 0:
trans._func_count = cur_count + 1
# ============================================================
# _register_ctypedef_from_annassign - 从 AnnAssign 节点注册 CTypedef
#
# 提取 NAME: t.CTypedef = Type 中的名称和类型表达式字符串,
# 调用 register_ctypedef 注册到动态 CTypedef 表。
# ============================================================
def _register_ctypedef_from_annassign(pool: memhub.MemBuddy | t.CPtr,
aa: ast.AnnAssign | t.CPtr) -> None:
"""从 AnnAssign 节点提取并注册 CTypedef"""
if aa is None or pool is None:
return
# 提取目标名称(必须是 Name 节点)
if aa.target is None or aa.target.kind() != ast.ASTKind.Name:
return
tgt: ast.Name | t.CPtr = (ast.Name | t.CPtr)(aa.target)
if tgt.id is None:
return
# 提取类型表达式字符串(从 value AST 节点)
if aa.value is None:
return
type_str: str = HandlesType._get_type_name_from_annotation(pool, aa.value)
if type_str is None:
return
# 注册 CTypedef
HandlesType.register_ctypedef(pool, tgt.id, type_str)