Files
ViperC/App/lib/Stub/StubGen/VpiReader.vp
T
TermiNexus 15b416b527 fix(type): 统一注入期类归属与导入上下文记账,修占位表爆与属性标记误解析
- StubMergerStore 新增共享导入上下文记账(AppendImportedModule / AppendFromImport /
  ImportCtx / AllocImportCtx / NotePlainImport),ImportsGenMixin 与 VpiReader 共用同一份
  口径;顺带移除 add_from_import 的 256 缓冲静默截断,VpiReader 删除两份私有副本

- 注入窗口按「名字归属」判据解析(is_module_class + CurDefModuleSha1):
  _forward_declare_ptr_from_node 的 Name 分支、resolve_annotation_type 的 Name/Attribute
  分支均补齐,使同一 stub 内先声明/先引用的类产出**具名占位**而不是 Ptr(i8),并新增
  _is_tattr_marker 让 t.attr.* 属性标记不参与类型联合竞争

- VpiReader._inject_class_struct_declare:命中前向声明占位时不再早退,改为「占位 Ty
  原地写入」(对齐 ClassGenDef.fill_fwd),修 StructTable 爆表(table full, cannot
  register Translator)

- 清除全部一次性探针:[DBG-GEN](VpiReader / StructGenMixin / ClassGenDef)、
  [DBG-ATTR](ExprGenAttr)、[MODSHA1](CallGenMixin)

根因记录:vpi 注入期漏记 `import X as Y -> from_imports "Y:X"`,使 GenTypes.VarEntry.Spec
(TSpec.TypeSpec | t.CPtr)取不到模块 SHA1、退化为 i8* 并被永久冻结
(CallGenCtor.vp:897 .PtrLevel 属性访问失败)。
2026-10-02 17:29:38 +08:00

4681 lines
247 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 lib.core.Gen.GenApi as GenApi
import lib.core.Gen.GenTypes as GenTypes
import lib.core.CompilerFactory as CompilerFactory
import t, c
from stdint import *
import ast
import string
import stdio
import stdlib
import sys
import memhub
import llvmlite
import viperlib
import w32.fileio as fileio
import lib.core.VLogger as VLogger
import lib.core.Gen.Translator as HT
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
import lib.Projectrans.Config as Config
# ============================================================
# REPL 存根布局恢复:常量与类表条目(实现见文件末同名段)
#
# 定义前置(先于 `_inject_class_struct_declare` 的 REPL 钩子使用):类型注解必须
# 在首次使用之前可见,故常量与条目类放在文件头部,函数体统一放在文件末。
# 该机制仅在 ReplStubLayoutMode != 0(REPL 会话初始化)时生效,批处理恒 0。
# ============================================================
_REPL_STUB_CLS_MAX: t.CDefine = 1024
_REPL_STUB_FIELD_MAX: int = 128
_REPL_STUB_VT_MAX: int = 128
_REPL_STUB_NAME_CAP: sizet = 256
_REPL_STUB_TXT_CAP: sizet = 4194304
_REPL_STUB_WARN_MAX: int = 10
_REPL_STUB_FINISH_PASSES: int = 8
# ReplStubClass.LayoutCount 的两个哨兵值(**不是字段数**)
_REPL_STUB_NOCOUNT_NOCLASS: int = -1 # 存根文本里没有该 key 的 `= type` 行(或整份存根没读到)
_REPL_STUB_NOCOUNT_OPAQUE: int = -2 # 存根里该 key 是 opaque(无 body)
# 布局"不可用 ⇒ 明确拒绝"的原因码(_repl_stub_warn_nodata):把"数据缺失"
# (上层数据没到位,补存根/重跑即可消除)与"判据拒绝"(数据在但不可信,绝不放行)
# 在文案与汇总计数上分开 —— 两类都会走到"不登记 StructEntry",但成因完全不同。
_REPL_SU_NOSTUB: t.CDefine = 1 # 数据缺失:整个模块没有 .stub.ll 文件
_REPL_SU_NOCLASS: t.CDefine = 2 # 判据拒绝:存根存在但无该类的 `= type` 行
_REPL_SU_OPAQUE: t.CDefine = 3 # 判据拒绝:存根里该类为 opaque(无 body)
_REPL_SU_PARENT: t.CDefine = 4 # 上游无数据:父类布局不可用/未在会话注册(走 _repl_stub_warn_parent)
# ReplStubClass.Flags / MethodFlags 位
_REPL_SF_CVTABLE: t.CDefine = 1
_REPL_SF_NOVTABLE: t.CDefine = 2
_REPL_SF_ANYFUNC: t.CDefine = 4
_REPL_SF_PACKED: t.CDefine = 8
# ReplStubClass.State
_REPL_SS_REFUSED: t.CDefine = 0 # 布局不可信:永不登记(字段访问明确失败)
_REPL_SS_OK: t.CDefine = 1 # 完整布局已登记
_REPL_SS_PENDING: t.CDefine = 2 # 父类尚未就绪:留待收尾遍
_REPL_SS_EMPTY: t.CDefine = 3 # 无父类/无虚表/字段类型不可解析:交既有 vpi 注入口径
class ReplStubClass:
Key: str # "<sha1>.<类名>"(= 定义侧命名结构体名)
ClassName: str
Sha1: str
Flags: int
OwnNames: sizet* # 元素 = str 指针(本类字段名,AnnAssign 顺序)
OwnTypes: sizet* # 元素 = LLVMType 指针
OwnCount: int
MethodNames: sizet*
MethodFlags: sizet*
MethodCount: int
Bases: sizet*
BaseCount: int
LayoutCount: int # 存根布局字段数(>=0 = 命中该类的 `= type {…}`;负数 = **哨兵,不是字段数**,
# 见 _REPL_STUB_NOCOUNT_NOCLASS/-OPAQUE:存根里没这个类 / 没读到存根 / opaque)
VtArity: int # 存根 .vtable 全局槽数(0 = 存根无该全局)
Names: sizet*
Types: sizet*
Count: int
HasVt: int
IsNoVTable: int
IsOOP: int
HasInit: int
HasNew: int
VtNames: sizet*
VtCount: int
VtTrust: int # 1 = 虚表槽数已与存根一致(可登记 VTableMethods)
OwnFail: int # 1 = 本类有字段注解解析失败 ⇒ OwnCount 只是"已解析字段数"(不完整)
State: int
# ============================================================
# 方法返回注解缓存(stdlib 持久)
#
# vpi tree 仅在解析后立即有效:处理到后续类的方法时深层 AST 节点(如 returns 注解)可能已被覆盖损坏(kind() 异常)→ 直接 resolve 得到垃圾类型。
# 故改文本提取: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
VPI_RET_LEN: t.CDefine = 224
_g_vpi_ret_keys: bytes = None
_g_vpi_ret_strs: bytes = None
_g_vpi_ret_count: int = 0
_g_vpi_ret_cap: int = 0
# ============================================================
# 方法参数注解缓存(stdlib 持久,仿返回注解缓存)
#
# vpi tree 深层 AST 节点(含函数参数列表)可能被覆盖损坏 → args 被截断(带默认值的尾部参数丢失,同一 vpi 不同模块注入时忽对忽错)→ declare 参数数不足 →
# 调用点缺参(fill 无法超 declare 补)→ 被调函数按 x64 ABI 读残留寄存器/栈槽垃圾。故与返回注解同源:从 vpi_buf 原始文本按行提取参数注解串缓存,
# 注入 declare 时若 tree 参数数不足则用缓存补全,保证 declare 与 define 参数数一致。
# ============================================================
VPI_PARAM_MAX: t.CDefine = 4096
VPI_PARAM_KEY_LEN: t.CDefine = 224
VPI_PARAM_LEN: t.CDefine = 1024
# ============================================================
# 键表开放寻址索引(名 → 槽下标)
#
# 两张键表(返回注解 / 参数注解)都是定长槽数组(每槽 VPI_*_KEY_LEN 字节),
# 插入只追加、从不重排 ⇒ 索引可只存"槽下标"(比存指针更省,且 realloc 移动
# 键表基址后槽下标仍有效)。索引槽数恒为键表容量的 2 倍(2 的幂,负载 < 0.5,
# 线性探测必然终止)。每次注册占一槽(不按键去重):同键条目沿探测链按注册
# 顺序递增排列,查找取链上首个"当前键相等"者 = 原线性扫描首个命中(首注册
# 优先)。可信判据 = Built == Count;不可信/未命中一律回退原线性扫描。
# ============================================================
VPI_RET_IDX_CAP: t.CDefine = 8192
VPI_PARAM_IDX_CAP: t.CDefine = 8192
# 索引哈希常量(32 位 FNV-1a,与 StructGenMixin 范式一致)
VPI_IDX_FNV_OFFSET: t.CDefine = 2166136261
VPI_IDX_FNV_PRIME: t.CDefine = 16777619
VPI_IDX_FNV_MASK: t.CDefine = 4294967295
_g_vpi_param_keys: bytes = None
_g_vpi_param_strs: bytes = None
_g_vpi_param_count: int = 0
_g_vpi_param_cap: int = 0
# 返回注解键表索引(与键表同 malloc 生命周期;Built == Count 才可信)
_g_vpi_ret_idx: bytes = None
_g_vpi_ret_idx_cap: int = 0
_g_vpi_ret_idx_built: int = 0
# 参数注解键表索引(同上)
_g_vpi_param_idx: bytes = None
_g_vpi_param_idx_cap: int = 0
_g_vpi_param_idx_built: int = 0
# ============================================================
# 索引探测/插入/重建(模块级函数,供两张键表共用)
# ============================================================
def _vpi_idx_hash(key: bytes) -> uint64:
"""键串 → 64 位哈希(32 位 FNV-1a,逐字节到 NUL 结束;空键返回偏移基值)"""
h: uint64 = uint64(VPI_IDX_FNV_OFFSET)
if key is None:
return h
i: sizet = 0
while key[i] != '\0':
h = (h ^ uint64(int(key[i]))) * uint64(VPI_IDX_FNV_PRIME)
h = h & uint64(VPI_IDX_FNV_MASK)
i += 1
return h
def _vpi_idx_slot(idx_base: bytes, si: sizet) -> sizet*:
"""取索引第 si 个槽(8 字节,存 slot_index+1;0=空槽)"""
return (sizet*)(uint64(idx_base) + uint64(si) * 8)
def _vpi_idx_find(keys: bytes, key_stride: int, idx_base: bytes,
cap: int, key: bytes) -> int:
"""在索引中探测 key,返回命中槽下标(>=0);未命中/索引不可用返回 -1
命中判据是"当前键相等"(strcmp),不能只比哈希。
"""
if keys is None or idx_base is None or cap <= 0 or key is None:
return -1
h: uint64 = _vpi_idx_hash(key)
mask: sizet = sizet(cap - 1)
si: sizet = sizet(h) & mask
i: int = 0
while i < cap:
slot: sizet* = _vpi_idx_slot(idx_base, si)
sv: sizet = slot[0]
if sv == 0:
return -1
se: int = int(sv) - 1
ek: bytes = keys + se * key_stride
if string.strcmp(ek, key) == 0:
return se
si = (si + 1) & mask
i += 1
return -1
def _vpi_idx_insert(idx_base: bytes, cap: int, slot_index: int, key: bytes) -> int:
"""把 (key → slot_index) 记入索引(每次注册占一槽,不去重);0=成功 / 1=失败"""
if idx_base is None or cap <= 0 or key is None:
return 1
h: uint64 = _vpi_idx_hash(key)
mask: sizet = sizet(cap - 1)
si: sizet = sizet(h) & mask
i: int = 0
while i < cap:
slot: sizet* = _vpi_idx_slot(idx_base, si)
if slot[0] == 0:
slot[0] = sizet(slot_index + 1)
return 0
si = (si + 1) & mask
i += 1
return 1
def _vpi_rebuild_ret_idx(new_cap: int) -> int:
"""随返回注解键表扩容重建索引(槽数=new_cap,2 的幂);0=可信 / 1=不可信
槽存下标非指针:键表 realloc 后仅基址变,槽下标与键内容均未变,故重建
只需从新基址逐条重放现有 count 个键即可(顺序不变 ⇒ 首注册优先不变)。
"""
global _g_vpi_ret_idx, _g_vpi_ret_idx_cap, _g_vpi_ret_idx_built
if new_cap <= 0:
_g_vpi_ret_idx = None
_g_vpi_ret_idx_cap = 0
_g_vpi_ret_idx_built = -1
return 1
new_idx: bytes = None
if _g_vpi_ret_idx is None:
new_idx = stdlib.malloc(new_cap * 8)
else:
new_idx = stdlib.realloc(_g_vpi_ret_idx, new_cap * 8)
if new_idx is None:
_g_vpi_ret_idx = None
_g_vpi_ret_idx_cap = 0
_g_vpi_ret_idx_built = -1
return 1
string.memset(new_idx, 0, sizet(new_cap) * 8)
_g_vpi_ret_idx = new_idx
_g_vpi_ret_idx_cap = new_cap
fail: int = 0
ri: int
for ri in range(_g_vpi_ret_count):
if _vpi_idx_insert(new_idx, new_cap, ri,
_g_vpi_ret_keys + ri * VPI_KEY_LEN) != 0:
fail = 1
break
if fail != 0:
_g_vpi_ret_idx_built = -1
return 1
_g_vpi_ret_idx_built = _g_vpi_ret_count
return 0
def _vpi_rebuild_param_idx(new_cap: int) -> int:
"""随参数注解键表扩容重建索引(同 _vpi_rebuild_ret_idx);0=可信 / 1=不可信"""
global _g_vpi_param_idx, _g_vpi_param_idx_cap, _g_vpi_param_idx_built
if new_cap <= 0:
_g_vpi_param_idx = None
_g_vpi_param_idx_cap = 0
_g_vpi_param_idx_built = -1
return 1
new_idx: bytes = None
if _g_vpi_param_idx is None:
new_idx = stdlib.malloc(new_cap * 8)
else:
new_idx = stdlib.realloc(_g_vpi_param_idx, new_cap * 8)
if new_idx is None:
_g_vpi_param_idx = None
_g_vpi_param_idx_cap = 0
_g_vpi_param_idx_built = -1
return 1
string.memset(new_idx, 0, sizet(new_cap) * 8)
_g_vpi_param_idx = new_idx
_g_vpi_param_idx_cap = new_cap
fail: int = 0
ri: int
for ri in range(_g_vpi_param_count):
if _vpi_idx_insert(new_idx, new_cap, ri,
_g_vpi_param_keys + ri * VPI_PARAM_KEY_LEN) != 0:
fail = 1
break
if fail != 0:
_g_vpi_param_idx_built = -1
return 1
_g_vpi_param_idx_built = _g_vpi_param_count
return 0
# ============================================================
# _cache_vpi_return_annots_text - 从 vpi_buf 文本直接提取方法返回注解
#
# 完全不依赖 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, _g_vpi_ret_cap
global _g_vpi_ret_idx, _g_vpi_ret_idx_cap, _g_vpi_ret_idx_built
if vpi_buf is None or src_sha1 is None:
return 0
if _g_vpi_ret_keys is None:
_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)
_g_vpi_ret_cap = VPI_RET_MAX
# 名索引:槽数 = 键表容量 * 2。分配失败则保持不可信(Built=-1),
# 查找/写去重一律回退原线性扫描(语义完全一致,不因此使缓存失败)。
_g_vpi_ret_idx = stdlib.malloc(VPI_RET_IDX_CAP * 8)
if _g_vpi_ret_idx is not None:
string.memset(_g_vpi_ret_idx, 0, VPI_RET_IDX_CAP * 8)
_g_vpi_ret_idx_cap = VPI_RET_IDX_CAP
_g_vpi_ret_idx_built = _g_vpi_ret_count
else:
_g_vpi_ret_idx_cap = 0
_g_vpi_ret_idx_built = -1
sha1_len: sizet = string.strlen(src_sha1)
buf_len: sizet = string.strlen(vpi_buf)
cur_cls: bytes = None
pos: sizet = 0
line_no: int = 0
while pos < buf_len:
ls: sizet = pos
le: sizet = pos
while le < buf_len and vpi_buf[le] != '\n':
le += 1
line_len: sizet = le - ls
# 跳过前导空格,检查 class/def
i: sizet = 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: sizet = i + 6
cend: sizet = cstart
# 类名提取:到 ':' / ' ' / '(' 前停止(继承基类括号不入类名,
# 如 "class MemBuddy(MemManager):" → "MemBuddy",与调用方短名一致)
while cend < le and vpi_buf[cend] != ':' 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:
# 模块级函数(行首无缩进):清空类名上下文,避免类结束后残留
# (如 json 的 null/int_val 等模块级函数被误存为 JsonValue.null)
if i == ls and cur_cls is not None:
stdlib.free(cur_cls)
cur_cls = None
dstart: sizet = i + 4
dend: sizet = dstart
while dend < le and vpi_buf[dend] != '(':
dend += 1
# 方法名 = [dstart, dend)
# 找 "->"
rpos: sizet = dstart
arrow_pos: sizet = le
while rpos < le - 1:
if vpi_buf[rpos] == '-' and vpi_buf[rpos + 1] == '>':
arrow_pos = rpos
break
rpos += 1
if dend > dstart:
# 返回类型:有 "-> Ret" 提取注解;无注解的非特殊方法默认 "i32"
# (与 HandlesType.infer_return_type 无 return 语句时的默认行为一致,
# 如 json._append_child / __setitem__ 无注解实际编译为 i32)。
ret_src: str = None
rtype_len: sizet = 0
if arrow_pos < le:
# 返回类型 = arrow_pos+2 跳过空格 到 ':' 前
rstart2: sizet = arrow_pos + 2
while rstart2 < le and vpi_buf[rstart2] == ' ':
rstart2 += 1
rend2: sizet = rstart2
while rend2 < le and vpi_buf[rend2] != ':':
rend2 += 1
if rend2 > rstart2:
rtype_len = rend2 - rstart2
ret_src = vpi_buf + rstart2
else:
# 无返回注解:默认 i32(特殊方法除外,见下方过滤)
rtype_len = 3
ret_src = "i32"
if ret_src is not None:
# 构造 key 和 ret_str,存入缓存
mname_len: sizet = dend - dstart
cls_len2: sizet = 0
if cur_cls is not None:
cls_len2 = string.strlen(cur_cls)
key_len: sizet = 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'
# 无注解的特殊方法(构造/析构)跳过:它们有专门调用路径
skip_def: int = 0
if arrow_pos >= le and (
string.strcmp(mname_buf, "__init__") == 0 or \
string.strcmp(mname_buf, "__new__") == 0 or \
string.strcmp(mname_buf, "__before_init__") == 0 or \
string.strcmp(mname_buf, "__after_init__") == 0):
skip_def = 1
if skip_def == 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
# 索引快速路径:索引可信(Built==Count)时一次探测判定
# 重复(命中即"首个注册优先"= 原线性扫描首个同名);
# 未命中 | 索引不可信 → 回退原线性扫描(正确性保证)。
if _g_vpi_ret_idx_built == _g_vpi_ret_count \
and _g_vpi_ret_idx is not None and _g_vpi_ret_idx_cap > 0:
if _vpi_idx_find(_g_vpi_ret_keys, VPI_KEY_LEN,
_g_vpi_ret_idx, _g_vpi_ret_idx_cap, key_buf) >= 0:
dup_key = 1
else:
# 快速路径:重复键必首字节相同(strcmp 判等蕴含首字节
# 相等),键首字节 = src_sha1[0],首字节不同者跳过 strcmp。
dup_first: int = key_buf[0]
for dki in range(_g_vpi_ret_count):
ent_d: bytes = _g_vpi_ret_keys + dki * VPI_KEY_LEN
if ent_d[0] == dup_first and string.strcmp(ent_d, key_buf) == 0:
dup_key = 1
break
grow_ok: int = 1
if dup_key == 0 and _g_vpi_ret_count >= _g_vpi_ret_cap:
grow_ok = _grow_vpi_ret_cache()
if dup_key == 0 and 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,
ret_src, rtype_len)
(_g_vpi_ret_strs + _g_vpi_ret_count * VPI_RET_LEN)[rtype_len] = '\0'
_g_vpi_ret_count += 1
# 同步索引:仅当插入前可信(Built==Count-1,含扩容
# 重建后)且插入成功;否则标记不可信(查找回退扫描)。
if _g_vpi_ret_idx_built == _g_vpi_ret_count - 1 \
and _vpi_idx_insert(_g_vpi_ret_idx, _g_vpi_ret_idx_cap,
_g_vpi_ret_count - 1, key_buf) == 0:
_g_vpi_ret_idx_built = _g_vpi_ret_count
else:
_g_vpi_ret_idx_built = -1
stdlib.free(key_buf)
stdlib.free(mname_buf)
pos = le + 1
line_no += 1
if cur_cls is not None:
stdlib.free(cur_cls)
return 1
# ============================================================
# _cache_vpi_param_annots_text - 从 vpi_buf 文本提取函数/方法参数注解串
#
# vpi tree 深层节点在遍历中可能被覆盖损坏(参数列表截断),故从原始文本按行提取参数注解,以 '@' 分隔存储(含首参 self → 空串占位,保证「参数位置 == 段索引」;注解文法不含 '@')。
# 存储格式:key="sha1.ClassName.method"(类内)/"sha1.method"(模块级),val=各参数注解串 '@' 分隔(无注解的参数为空串)。
# 调用方:ParseVpiIntoModule(tree 解析后立即调用,文本始终有效)。
# ============================================================
def _cache_vpi_param_annots_text(vpi_buf: bytes, src_sha1: str) -> int:
"""从 vpi_buf 文本提取函数/方法参数注解字符串(stdlib 持久)"""
global _g_vpi_param_keys, _g_vpi_param_strs, _g_vpi_param_count, _g_vpi_param_cap
global _g_vpi_param_idx, _g_vpi_param_idx_cap, _g_vpi_param_idx_built
if vpi_buf is None or src_sha1 is None:
return 0
if _g_vpi_param_keys is None:
_g_vpi_param_keys = stdlib.malloc(VPI_PARAM_MAX * VPI_PARAM_KEY_LEN)
_g_vpi_param_strs = stdlib.malloc(VPI_PARAM_MAX * VPI_PARAM_LEN)
if _g_vpi_param_keys is None or _g_vpi_param_strs is None:
return 0
string.memset(_g_vpi_param_keys, 0, VPI_PARAM_MAX * VPI_PARAM_KEY_LEN)
string.memset(_g_vpi_param_strs, 0, VPI_PARAM_MAX * VPI_PARAM_LEN)
_g_vpi_param_cap = VPI_PARAM_MAX
# 名索引:槽数 = 键表容量 * 2。分配失败则保持不可信(Built=-1),
# 查找/写去重一律回退原线性扫描(语义完全一致,不因此使缓存失败)。
_g_vpi_param_idx = stdlib.malloc(VPI_PARAM_IDX_CAP * 8)
if _g_vpi_param_idx is not None:
string.memset(_g_vpi_param_idx, 0, VPI_PARAM_IDX_CAP * 8)
_g_vpi_param_idx_cap = VPI_PARAM_IDX_CAP
_g_vpi_param_idx_built = _g_vpi_param_count
else:
_g_vpi_param_idx_cap = 0
_g_vpi_param_idx_built = -1
sha1_len: sizet = string.strlen(src_sha1)
buf_len: sizet = string.strlen(vpi_buf)
cur_cls: bytes = None
pos: sizet = 0
while pos < buf_len:
ls: sizet = pos
le: sizet = pos
while le < buf_len and vpi_buf[le] != '\n':
le += 1
line_len: sizet = le - ls
i: sizet = ls
while i < le and vpi_buf[i] == ' ':
i += 1
if i < le:
if line_len >= 6 and string.strncmp(vpi_buf + i, "class ", 6) == 0:
cstart: sizet = i + 6
cend: sizet = cstart
while cend < le and vpi_buf[cend] != ':' 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'
elif line_len >= 4 and string.strncmp(vpi_buf + i, "def ", 4) == 0:
# 模块级函数(行首无缩进):清空类名上下文
if i == ls and cur_cls is not None:
stdlib.free(cur_cls)
cur_cls = None
dstart: sizet = i + 4
dend: sizet = dstart
while dend < le and vpi_buf[dend] != '(':
dend += 1
if dend >= le:
pos = le + 1
continue
# 找匹配的 ')'(深度感知:注解可含 t.CArray[...] / tuple[...] 括号)
p: sizet = dend + 1
pdepth: int = 0
pend: sizet = dend
while p < le:
ch_p: char = vpi_buf[p]
if ch_p == '(' or ch_p == '[' or ch_p == '{':
pdepth += 1
elif ch_p == ')' or ch_p == ']' or ch_p == '}':
pdepth -= 1
if pdepth < 0:
pend = p
break
p += 1
# 逐段解析参数(顶层逗号分隔;跳过 *args/**kwargs)
plist: bytes = stdlib.malloc(VPI_PARAM_LEN)
if plist is None:
pos = le + 1
continue
plist[0] = '\0'
plist_pos: sizet = 0
seg_written: int = 0
seg_cur: sizet = dend + 1
while seg_cur < pend:
while seg_cur < pend and (vpi_buf[seg_cur] == ' ' or vpi_buf[seg_cur] == ','):
seg_cur += 1
if seg_cur >= pend:
break
s_start: sizet = seg_cur
sd: int = 0
while seg_cur < pend:
ch_s: char = vpi_buf[seg_cur]
if ch_s == '(' or ch_s == '[' or ch_s == '{':
sd += 1
elif ch_s == ')' or ch_s == ']' or ch_s == '}':
sd -= 1
elif ch_s == ',' and sd == 0:
break
seg_cur += 1
s_end: sizet = seg_cur
# 跳过 * / **(vararg/kwarg 不入 args 列表)
st: sizet = s_start
while st < s_end and vpi_buf[st] == ' ':
st += 1
if st < s_end and vpi_buf[st] == '*':
if seg_cur < pend and vpi_buf[seg_cur] == ',':
seg_cur += 1
continue
# 截断默认值(顶层 '=',如 ": str = None")
e_pos: sizet = st
ed: int = 0
while e_pos < s_end:
ch_e: char = vpi_buf[e_pos]
if ch_e == '(' or ch_e == '[' or ch_e == '{':
ed += 1
elif ch_e == ')' or ch_e == ']' or ch_e == '}':
ed -= 1
elif ch_e == '=' and ed == 0:
break
e_pos += 1
s_end = e_pos
# 提取注解:顶层 ':' 之后(无 ':' → 空串,如 self)
c_pos: sizet = st
cd2: int = 0
annot_start: sizet = s_end
while c_pos < s_end:
ch_c: char = vpi_buf[c_pos]
if ch_c == '(' or ch_c == '[' or ch_c == '{':
cd2 += 1
elif ch_c == ')' or ch_c == ']' or ch_c == '}':
cd2 -= 1
elif ch_c == ':' and cd2 == 0:
annot_start = c_pos + 1
break
c_pos += 1
a_start: sizet = annot_start
while a_start < s_end and vpi_buf[a_start] == ' ':
a_start += 1
a_end: sizet = s_end
while a_end > a_start and vpi_buf[a_end - 1] == ' ':
a_end -= 1
if plist_pos + (a_end - a_start) + 1 < VPI_PARAM_LEN:
if seg_written > 0:
plist[plist_pos] = '@'
plist_pos += 1
ai2: sizet
for ai2 in range(a_end - a_start):
plist[plist_pos] = vpi_buf[a_start + ai2]
plist_pos += 1
plist[plist_pos] = '\0'
seg_written += 1
if seg_cur < pend and vpi_buf[seg_cur] == ',':
seg_cur += 1
# 存入缓存(key = sha1.class.method / sha1.method,去重)
if plist_pos > 0 and plist[0] != '\0':
mname_len: sizet = dend - dstart
cls_len2: sizet = 0
if cur_cls is not None:
cls_len2 = string.strlen(cur_cls)
key_len: sizet = sha1_len + 1 + cls_len2 + 1 + mname_len
if key_len < VPI_PARAM_KEY_LEN:
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)
dup_key: int = 0
dki: int
# 索引快速路径:索引可信(Built==Count)时一次探测判定
# 重复(命中即"首个注册优先"= 原线性扫描首个同名);
# 未命中 | 索引不可信 → 回退原线性扫描(正确性保证)。
if _g_vpi_param_idx_built == _g_vpi_param_count \
and _g_vpi_param_idx is not None and _g_vpi_param_idx_cap > 0:
if _vpi_idx_find(_g_vpi_param_keys, VPI_PARAM_KEY_LEN,
_g_vpi_param_idx, _g_vpi_param_idx_cap, key_buf) >= 0:
dup_key = 1
else:
# 快速路径:重复键必首字节相同(strcmp 判等蕴含首字节
# 相等),键首字节 = src_sha1[0],首字节不同者跳过 strcmp。
dup_first: int = key_buf[0]
for dki in range(_g_vpi_param_count):
ent_d: bytes = _g_vpi_param_keys + dki * VPI_PARAM_KEY_LEN
if ent_d[0] == dup_first and string.strcmp(ent_d, key_buf) == 0:
dup_key = 1
break
grow_ok: int = 1
if dup_key == 0 and _g_vpi_param_count >= _g_vpi_param_cap:
grow_ok = _grow_vpi_param_cache()
if dup_key == 0 and grow_ok != 0:
string.strcpy(_g_vpi_param_keys + _g_vpi_param_count * VPI_PARAM_KEY_LEN, key_buf)
string.strncpy(_g_vpi_param_strs + _g_vpi_param_count * VPI_PARAM_LEN,
plist, plist_pos)
(_g_vpi_param_strs + _g_vpi_param_count * VPI_PARAM_LEN)[plist_pos] = '\0'
_g_vpi_param_count += 1
# 同步索引:仅当插入前可信(Built==Count-1,含扩容
# 重建后)且插入成功;否则标记不可信(查找回退扫描)。
if _g_vpi_param_idx_built == _g_vpi_param_count - 1 \
and _vpi_idx_insert(_g_vpi_param_idx, _g_vpi_param_idx_cap,
_g_vpi_param_count - 1, key_buf) == 0:
_g_vpi_param_idx_built = _g_vpi_param_count
else:
_g_vpi_param_idx_built = -1
stdlib.free(key_buf)
stdlib.free(mname_buf)
stdlib.free(plist)
pos = le + 1
if cur_cls is not None:
stdlib.free(cur_cls)
return 1
# ============================================================
# _grow_vpi_param_cache - 扩容参数注解缓存(容量翻倍)
# ============================================================
def _grow_vpi_param_cache() -> int:
"""扩容参数注解缓存(容量翻倍),返回 1=成功 / 0=失败"""
global _g_vpi_param_keys, _g_vpi_param_strs, _g_vpi_param_cap
if _g_vpi_param_keys is None or _g_vpi_param_cap <= 0:
return 0
new_cap: int = _g_vpi_param_cap * 2
if new_cap > 1048576:
return 0
new_keys: bytes = stdlib.realloc(_g_vpi_param_keys, new_cap * VPI_PARAM_KEY_LEN)
if new_keys is None:
return 0
new_strs: bytes = stdlib.realloc(_g_vpi_param_strs, new_cap * VPI_PARAM_LEN)
if new_strs is None:
return 0
_g_vpi_param_keys = new_keys
_g_vpi_param_strs = new_strs
_g_vpi_param_cap = new_cap
# 索引随表同步扩容并整体重建(槽数 = 表容量 * 2,恒为 2 的幂)
_vpi_rebuild_param_idx(new_cap * 2)
return 1
# ============================================================
# _lookup_vpi_param_annots - 查参数注解缓存
# ============================================================
def _lookup_vpi_param_annots(src_sha1: str, cls_name: str, method_name: str) -> str:
"""查参数注解缓存;返回注解串('@' 分隔,含首参空串占位)或 None"""
if src_sha1 is None or method_name is None or _g_vpi_param_keys is None:
return None
sha1_len: sizet = string.strlen(src_sha1)
mlen: sizet = string.strlen(method_name)
clen: sizet = 0
if cls_name is not None:
clen = string.strlen(cls_name)
key_len: sizet = sha1_len + clen + mlen + 2
if key_len >= VPI_PARAM_KEY_LEN:
return None
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)
r: str = None
ki: int
# 索引快速路径:索引可信(Built==Count)时一次探测返回命中槽下标(= 原线性
# 扫描首个同名,首注册优先);未命中即"确无"(键表只追加、键文本注册后不改写,
# 索引随扩容整体重建且覆盖全表)⇒ 无需再线性扫描;索引不可信 → 回退线性扫描。
hit_p: int = -1
if _g_vpi_param_idx_built == _g_vpi_param_count \
and _g_vpi_param_idx is not None and _g_vpi_param_idx_cap > 0:
hit_p = _vpi_idx_find(_g_vpi_param_keys, VPI_PARAM_KEY_LEN,
_g_vpi_param_idx, _g_vpi_param_idx_cap, key_buf)
else:
# 回退原线性扫描:strcmp 判等蕴含首字节相等;键首字节 = src_sha1[0],缓存含
# 多模块条目,首字节不同者必不相等,直接跳过其 strcmp(仅做否定判定,不改命中)。
key_first: int = key_buf[0]
for ki in range(_g_vpi_param_count):
ent_p: bytes = _g_vpi_param_keys + ki * VPI_PARAM_KEY_LEN
if ent_p[0] == key_first and string.strcmp(ent_p, key_buf) == 0:
hit_p = ki
break
if hit_p >= 0:
r = _g_vpi_param_strs + hit_p * VPI_PARAM_LEN
stdlib.free(key_buf)
return r
# ============================================================
# _resolve_vpi_param_annot_seg_template - 泛型特化类的参数注解段解析
#
# 缓存键段取自 vpi 的 class 行文本(`class list[T]:` ⇒ 键段 "list[T]"),故特化类名(list[str]/list[int])的精确键必然遗漏,定义侧形参注解取不到 →
# 调用点 declare 停在调用点实参宽度(如 elem_size 字面量 i32 把定义侧 sizet(i64) 固化窄)。本函数按 {sha1}.{base}[T].{method} 形态定位模板条目,
# 把段内独立的类型形参 token 用特化实参(cls_name 的 [...] 内容)替换后再解析;未命中/多参模板(键段含空格被截断)返回 None,不做兜底推断。
# ============================================================
def _resolve_vpi_param_annot_seg_template(pool: memhub.MemBuddy*,
src_sha1: str,
cls_name: str,
method_name: str,
seg_idx: int,
i32_ty: llvmlite.LLVMType*,
i8_ptr_ty: llvmlite.LLVMType*) -> llvmlite.LLVMType*:
"""特化类 X[arg] 的参数注解段解析(模板 X[T] 注解 + T→arg 替换);未命中 None"""
if pool is None or src_sha1 is None or cls_name is None or method_name is None \
or _g_vpi_param_keys is None:
return None
lbkt_t: str = string.strchr(cls_name, 91) # '[' = 91
if lbkt_t is None:
return None
rbkt_t: str = string.strrchr(cls_name, 93) # ']' = 93
if rbkt_t is None or rbkt_t <= lbkt_t:
return None
base_len_t: sizet = uint64(lbkt_t) - uint64(cls_name)
arg_len_t: sizet = uint64(rbkt_t) - uint64(lbkt_t) - 1
if base_len_t <= 0 or arg_len_t <= 0:
return None
base_t: bytes = stdlib.malloc(base_len_t + 1)
if base_t is None:
return None
arg_t: bytes = stdlib.malloc(arg_len_t + 1)
if arg_t is None:
stdlib.free(base_t)
return None
string.strncpy(base_t, cls_name, base_len_t)
base_t[base_len_t] = '\0'
string.strncpy(arg_t, lbkt_t + 1, arg_len_t)
arg_t[arg_len_t] = '\0'
sha1_len_t: sizet = string.strlen(src_sha1)
ty_t: llvmlite.LLVMType* = None
ki_t: int
for ki_t in range(_g_vpi_param_count):
gk_t: bytes = _g_vpi_param_keys + ki_t * VPI_PARAM_KEY_LEN
if string.strncmp(gk_t, src_sha1, sha1_len_t) != 0 or gk_t[sha1_len_t] != '.':
continue
if string.strncmp(gk_t + sha1_len_t + 1, base_t, base_len_t) != 0:
continue
if gk_t[sha1_len_t + 1 + base_len_t] != 91:
continue
gklb_t: str = string.strchr(gk_t + sha1_len_t + 1 + base_len_t, 93)
if gklb_t is None:
continue
tplen_t: sizet = uint64(gklb_t) - uint64(gk_t + sha1_len_t + 1 + base_len_t) - 1
# 方法名须整体匹配(gklb_t 后即 ']' 与 '.';mangled 形态保证无同名歧义)
if tplen_t <= 0 or string.strcmp(gklb_t + 2, method_name) != 0:
continue
annots_t: str = _g_vpi_param_strs + ki_t * VPI_PARAM_LEN
if annots_t is None or annots_t[0] == '\0':
break
# 段定位(与 _resolve_vpi_param_annot_seg 同口径:'@' 分隔,段 0 = self 空段)
cur_t: sizet = 0
cur_seg_t: int = 0
while cur_seg_t < seg_idx and annots_t[cur_t] != '\0':
while annots_t[cur_t] != '\0' and annots_t[cur_t] != '@':
cur_t += 1
if annots_t[cur_t] == '@':
cur_t += 1
cur_seg_t += 1
seg_start_t: sizet = cur_t
seg_len_t: sizet = 0
while annots_t[seg_start_t + seg_len_t] != '\0' \
and annots_t[seg_start_t + seg_len_t] != '@':
seg_len_t += 1
if seg_len_t == 0:
break
seg_buf_t: bytes = stdlib.malloc(seg_len_t + 1)
if seg_buf_t is None:
break
string.strncpy(seg_buf_t, annots_t + seg_start_t, seg_len_t)
seg_buf_t[seg_len_t] = '\0'
# 类型形参 token(如 T)→ 特化实参(如 str / rawptr[AST] / AST|t.CPtr):
# 模板注解按源码原样缓存,不替换则 `item: T` 解析不出类型(None 跳过)
tparam_t: bytes = stdlib.malloc(tplen_t + 1)
if tparam_t is not None:
string.strncpy(tparam_t, gk_t + sha1_len_t + 1 + base_len_t + 1, tplen_t)
tparam_t[tplen_t] = '\0'
subst_t: str = _subst_template_arg(seg_buf_t, tparam_t, arg_t)
if subst_t is not None:
# _subst_template_arg 早退分支(tplen==0 或 tplen>alen)返回入参
# 本身 ⇒ subst_t 与 seg_buf_t 同指针时并非新分配,此处不得 free
# (否则与末尾 free(seg_buf_t) 重复释放)
if uint64(subst_t) != uint64(seg_buf_t):
stdlib.free(seg_buf_t)
seg_buf_t = subst_t
stdlib.free(tparam_t)
ty_t = _resolve_vpi_ret_annot_str(pool, seg_buf_t, i32_ty, i8_ptr_ty)
stdlib.free(seg_buf_t)
break
stdlib.free(base_t)
stdlib.free(arg_t)
return ty_t
# ============================================================
# _repair_declare_params_from_text - 用文本缓存补全 declare 参数
#
# vpi tree 参数列表被覆盖损坏时(param_count 偏小,尾部默认参数丢失),用 _cache_vpi_param_annots_text 的文本缓存补全:参数位置 p 对应缓存段 p(含首参空串占位)。
# 补全后 declare 参数数与 define 一致,调用点默认参数填充才能生效,避免被调函数读栈垃圾。
# ============================================================
def _repair_declare_params_from_text(pool: memhub.MemBuddy*,
func: llvmlite.Function*,
src_sha1: str,
cls_name: str,
method_name: str,
param_count: int,
i32_ty: llvmlite.LLVMType*,
i8_ptr_ty: llvmlite.LLVMType*) -> int:
"""用 vpi 文本缓存的参数注解补全 declare 参数;返回补全后参数数"""
if func is None or src_sha1 is None or method_name is None:
return param_count
prm_annots: str = _lookup_vpi_param_annots(src_sha1, cls_name, method_name)
if prm_annots is None or prm_annots[0] == '\0':
return param_count
# 统计文本参数数('@' 分隔;段数 = 分隔符数 + 1,含首参)
pa_count: int = 1
pi2: sizet = 0
while prm_annots[pi2] != '\0':
if prm_annots[pi2] == '@':
pa_count += 1
pi2 += 1
if pa_count <= param_count:
return param_count
# 补全缺失参数:段索引 >= param_count 的段即缺失参数位置
seg_idx: int = 0
seg_start2: sizet = 0
cur2: sizet = 0
while prm_annots[cur2] != '\0':
while prm_annots[cur2] != '\0' and prm_annots[cur2] != '@':
cur2 += 1
seg_len2: sizet = cur2 - seg_start2
if prm_annots[cur2] == '@':
cur2 += 1
if seg_idx >= param_count:
param_ty_r: llvmlite.LLVMType* = i8_ptr_ty
if seg_len2 > 0:
seg_buf_r: bytes = stdlib.malloc(seg_len2 + 1)
if seg_buf_r is not None:
string.strncpy(seg_buf_r, prm_annots + seg_start2, seg_len2)
seg_buf_r[seg_len2] = '\0'
rt: llvmlite.LLVMType* = _resolve_vpi_ret_annot_str(
pool, seg_buf_r, i32_ty, i8_ptr_ty)
if rt is not None:
param_ty_r = rt
stdlib.free(seg_buf_r)
llvmlite.add_param(pool, func, param_ty_r, "arg")
seg_start2 = cur2
seg_idx += 1
return pa_count
# ============================================================
# _resolve_vpi_param_annot_seg - 按段索引解析 vpi 参数注解为 LLVMType
#
# 与返回类型同源地用注解串解析(绕开 vpi tree 深层节点 kind() 损坏与 AST resolve_annotation_type 得到的垃圾类型,如 sizet → i32):
# 参数类型错会导致调用侧 trunc 传参、callee 按 i64 读栈残留高位 → can=巨值读未写槽位崩溃(SlotsBugTest 根因)。
# 从 @-joined 注解串取第 seg_idx 段(0=self 空段),用 _resolve_vpi_ret_annot_str 解析;空段/未命中返回 None(走 AST 兜底)。
# ============================================================
def _resolve_vpi_param_annot_seg(pool: memhub.MemBuddy*,
src_sha1: str,
cls_name: str,
method_name: str,
seg_idx: int,
i32_ty: llvmlite.LLVMType*,
i8_ptr_ty: llvmlite.LLVMType*) -> llvmlite.LLVMType*:
"""按段索引解析 vpi 参数注解段为 LLVMType,空段/失败返回 None"""
if pool is None or src_sha1 is None or method_name is None:
return None
prm_annots_p: str = _lookup_vpi_param_annots(src_sha1, cls_name, method_name)
if prm_annots_p is None or prm_annots_p[0] == '\0':
return None
cur_p: sizet = 0
cur_seg_p: int = 0
while cur_seg_p < seg_idx and prm_annots_p[cur_p] != '\0':
while prm_annots_p[cur_p] != '\0' and prm_annots_p[cur_p] != '@':
cur_p += 1
if prm_annots_p[cur_p] == '@':
cur_p += 1
cur_seg_p += 1
seg_start_p: sizet = cur_p
seg_len_p: sizet = 0
while prm_annots_p[seg_start_p + seg_len_p] != '\0' \
and prm_annots_p[seg_start_p + seg_len_p] != '@':
seg_len_p += 1
if seg_len_p == 0:
return None
seg_buf_p: bytes = stdlib.malloc(seg_len_p + 1)
if seg_buf_p is None:
return None
string.strncpy(seg_buf_p, prm_annots_p + seg_start_p, seg_len_p)
seg_buf_p[seg_len_p] = '\0'
seg_ty_p: llvmlite.LLVMType* = _resolve_vpi_ret_annot_str(
pool, seg_buf_p, i32_ty, i8_ptr_ty)
stdlib.free(seg_buf_p)
return seg_ty_p
# ============================================================
# _grow_vpi_ret_cache - 扩容方法返回注解缓存(容量翻倍)
#
# VPI_RET_MAX 初始 4096,但 includes 模块量大(~90 个 stub、每模块几十个方法),缓存写满后条目被静默丢弃会让已缓存模块的方法查不到而 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
# 索引随表同步扩容并整体重建(槽数 = 表容量 * 2,恒为 2 的幂)
_vpi_rebuild_ret_idx(new_cap * 2)
return 1
# ============================================================
# _ensure_vpi_ret_annot_loaded - 按需加载模块的 vpi stub 返回注解缓存
#
# _cache_vpi_return_annots_text 仅在 stub 注入(当前模块 import 了该模块)时填充;模块自身翻译时自己的 stub 尚未被依赖方读取、text.ll 也未落盘,
# 导致「同模块方法调用(定义在后)」在 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: sizet = 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
# 快速路径:前缀匹配必首两字节相等(strncmp 判等蕴含前缀逐字节相等),
# src_sha1 前两字节即模块子目录名;先比二者可跳过绝大多数条目的 strncmp。
s0: int = src_sha1[0]
s1: int = src_sha1[1]
for pre_i in range(_g_vpi_ret_count):
pre_key: bytes = _g_vpi_ret_keys + pre_i * VPI_KEY_LEN
if pre_key[0] == s0 and pre_key[1] == s1 and \
string.strncmp(pre_key, src_sha1, sha1_len) == 0 and pre_key[sha1_len] == '.':
return 1
# 构造路径 {temp_dir}/{sha1[0:2]}/{sha1}.vpi(逐字符拼接,避免 %c 格式化问题)
td_len: sizet = string.strlen(temp_dir)
path_len: sizet = 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: rawptr[fileio.File] = 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
_cache_vpi_return_annots_text(vpi_buf, src_sha1)
stdlib.free(vpi_buf)
stdlib.free(vpi_path)
return 1
# ============================================================
# _ensure_vpi_param_annot_loaded - 按需加载模块的 vpi stub 参数注解缓存
#
# 与 _ensure_vpi_ret_annot_loaded 同构(缓存仅在 stub 注入时填充);跨模块方法调用可能绕过 stub 注入——如经 Generator 属性(self.Gen.ExprGenCdef.method)
# 调用依赖模块方法时依赖收集不加载其 vpi → 函数表无 ParamCount/默认参数 → 调用点缺参 → 被调函数读栈残留(IterTest 假递归根因)。
# 故参数缓存未命中时直接读 vpi 文件填充,幂等跳过。
# ============================================================
def _ensure_vpi_param_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: sizet = string.strlen(src_sha1)
if sha1_len < 2:
return 0
# 该模块已有参数缓存条目 → 跳过(幂等)
if _g_vpi_param_keys is not None and _g_vpi_param_count > 0:
pre_i: int
# 快速路径:前缀匹配必首两字节相等(strncmp 判等蕴含前缀逐字节相等),
# src_sha1 前两字节即模块子目录名;先比二者可跳过绝大多数条目的 strncmp。
s0: int = src_sha1[0]
s1: int = src_sha1[1]
for pre_i in range(_g_vpi_param_count):
pre_key: bytes = _g_vpi_param_keys + pre_i * VPI_PARAM_KEY_LEN
if pre_key[0] == s0 and pre_key[1] == s1 and \
string.strncmp(pre_key, src_sha1, sha1_len) == 0 and pre_key[sha1_len] == '.':
return 1
# 构造路径 {temp_dir}/{sha1[0:2]}/{sha1}.vpi(逐字符拼接,避免 %c 格式化问题)
td_len: sizet = string.strlen(temp_dir)
path_len: sizet = 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: rawptr[fileio.File] = 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
_cache_vpi_param_annots_text(vpi_buf, src_sha1)
stdlib.free(vpi_buf)
stdlib.free(vpi_path)
return 1
# ============================================================
# _subst_template_arg - 替换注解字符串中独立的模板参数 token
#
# 泛型模板返回注解(如 list[T].get -> T)中的类型参数名需替换为实际特化实参后才能解析;仅替换前后为非字母数字下划线边界的独立 token(避免误替换 sizet 等)。
# 返回 stdlib 新分配字符串(调用者负责 free);失败返回 None。
# ============================================================
def _subst_template_arg(annot: str, tparam: str, targ: str) -> str:
"""替换 annot 中独立的 tparam token 为 targ"""
if annot is None or tparam is None or targ is None:
return None
alen: sizet = string.strlen(annot)
tplen: sizet = string.strlen(tparam)
tglen: sizet = string.strlen(targ)
if tplen == 0 or tplen > alen:
return annot
# 容量 = alen + tglen*8 + 16 只覆盖 ≤8 次替换;替换次数无上限时
# opos 会越界写穿 stdlib 堆(哨兵盲区 → CRT 堆损坏后任意 malloc/free
# 静默段错误、崩溃点漂移)。按最坏上界分配:每替换消耗 tplen 写 tglen,
# 最坏全部被替换 → alen + (alen/tplen)*tglen + 1。
max_sub: sizet = alen / tplen
if max_sub == 0:
max_sub = 1
out_size: sizet = alen + max_sub * tglen + 1
out: bytes = stdlib.malloc(out_size)
if out is None:
return None
opos: sizet = 0
p: sizet = 0
while p < alen:
# 防御:opos 超分配即 fail-fast 返回 None(绝不越界写)
if opos >= out_size:
stdlib.free(out)
return None
matched: int = 0
if annot[p] == tparam[0] and p + tplen <= alen:
if string.strncmp(annot + p, tparam, tplen) == 0:
prev_ch: int = 0
next_ch: int = 0
if p > 0:
prev_ch = annot[p - 1]
if p + tplen < alen:
next_ch = annot[p + tplen]
prev_ok: int = 1
if (prev_ch >= 'a' and prev_ch <= 'z') or (prev_ch >= 'A' and prev_ch <= 'Z') or \
(prev_ch >= '0' and prev_ch <= '9') or prev_ch == '_':
prev_ok = 0
next_ok: int = 1
if (next_ch >= 'a' and next_ch <= 'z') or (next_ch >= 'A' and next_ch <= 'Z') or \
(next_ch >= '0' and next_ch <= '9') or next_ch == '_':
next_ok = 0
if prev_ok != 0 and next_ok != 0:
string.strncpy(out + opos, targ, tglen)
opos += tglen
p += tplen
matched = 1
if matched == 0:
out[opos] = annot[p]
opos += 1
p += 1
out[opos] = '\0'
return out
# ============================================================
# _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: sizet = string.strlen(src_sha1)
cl_len: sizet = 0
if cls_name is not None:
cl_len = string.strlen(cls_name)
me_len: sizet = string.strlen(method_name)
key_len: sizet = 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
# 索引快速路径:索引可信(Built==Count)时一次探测返回命中槽下标(= 原线性
# 扫描首个同名,首注册优先);未命中即"确无"精确键(键表只追加、键文本注册后
# 不改写,索引随扩容整体重建且覆盖全表)⇒ 跳过精确线性扫描(下方泛型特化归一化
# 匹配兜底仍保留,其键形态不同、不依赖精确键命中);索引不可信 → 回退线性扫描。
hit_r: int = -1
if _g_vpi_ret_idx_built == _g_vpi_ret_count \
and _g_vpi_ret_idx is not None and _g_vpi_ret_idx_cap > 0:
hit_r = _vpi_idx_find(_g_vpi_ret_keys, VPI_KEY_LEN,
_g_vpi_ret_idx, _g_vpi_ret_idx_cap, key_buf)
else:
# 回退原线性扫描:strcmp 判等蕴含首字节相等(键首字节 = src_sha1[0]),跳过首字节不同者。
key_first: int = key_buf[0]
for i in range(_g_vpi_ret_count):
ent_r: bytes = _g_vpi_ret_keys + i * VPI_KEY_LEN
if ent_r[0] == key_first and string.strcmp(ent_r, key_buf) == 0:
hit_r = i
break
if hit_r >= 0:
stdlib.free(key_buf)
return _g_vpi_ret_strs + hit_r * VPI_RET_LEN
# 泛型特化归一化匹配:list[AST|t.CPtr].get → list[T].get 模板缓存。
# 命中后把模板注解中的类型参数名(如 T)替换为实参(AST|t.CPtr)再返回。
# 不依赖模板类已注册/特化已发生(base.vp 可能先于 list 模块翻译)。
if cls_name is not None:
lbkt_v: str = string.strchr(cls_name, '[')
if lbkt_v is not None:
rbkt_v: str = string.strrchr(cls_name, ']')
if rbkt_v is not None and rbkt_v > lbkt_v:
base_len_v: sizet = sizet(uint64(lbkt_v) - uint64(cls_name))
param_len_v: sizet = sizet(uint64(rbkt_v) - uint64(lbkt_v) - 1)
if base_len_v > 0 and param_len_v > 0:
base_v: str = stdlib.malloc(base_len_v + 1)
param_v: str = stdlib.malloc(param_len_v + 1)
if base_v is not None and param_v is not None:
string.strncpy(base_v, cls_name, base_len_v)
base_v[base_len_v] = '\0'
string.strncpy(param_v, lbkt_v + 1, param_len_v)
param_v[param_len_v] = '\0'
# 遍历缓存找 {sha1}.{base}[xxx].{method}
gi: int
for gi in range(_g_vpi_ret_count):
gk: bytes = _g_vpi_ret_keys + gi * VPI_KEY_LEN
if string.strncmp(gk, src_sha1, sha1_len) == 0 and \
gk[sha1_len] == '.' and \
string.strncmp(gk + sha1_len + 1, base_v, base_len_v) == 0 and \
gk[sha1_len + 1 + base_len_v] == '[':
gklb: str = string.strchr(gk + sha1_len + 1 + base_len_v, ']')
if gklb is not None:
tparam_len_v: sizet = sizet(
uint64(gklb) - uint64(gk + sha1_len + 1 + base_len_v) - 1)
meth_start_v: str = gklb + 2 # 跳过 ']' 和 '.'
if tparam_len_v > 0 and string.strcmp(meth_start_v, method_name) == 0:
tparam_v: str = stdlib.malloc(tparam_len_v + 1)
if tparam_v is not None:
string.strncpy(tparam_v, gk + sha1_len + 1 + base_len_v + 1, tparam_len_v)
tparam_v[tparam_len_v] = '\0'
new_annot_v: str = _subst_template_arg(
_g_vpi_ret_strs + gi * VPI_RET_LEN, tparam_v, param_v)
stdlib.free(tparam_v)
stdlib.free(base_v)
stdlib.free(param_v)
stdlib.free(key_buf)
return new_annot_v
stdlib.free(base_v)
stdlib.free(param_v)
stdlib.free(key_buf)
return None
# ============================================================
# _resolve_single_vpi_type_str - 解析单个类型段(无 CPtr/CVoid)为 LLVMType
#
# 纯字符串解析,不构造 AST 节点。
# ============================================================
def _resolve_single_vpi_type_str(pool: memhub.MemBuddy*,
seg: str) -> llvmlite.LLVMType*:
"""解析单个类型段为 LLVMType,无法识别返回 None"""
if seg is None or pool is None:
return None
# 【指针实参唯一规范形 `T*`(用户定口径,2026-09-30)】`rawptr[T]` ≡
# `t.CPtr[T]` ≡ `T | t.CPtr` ⇒ 一律折叠为 `T*`(见 TypeGenResolve
# ._norm_type_arg_name 规则 1.0/1.5)。为什么必须在此补:_subst_template_arg
# 会用**特化类名 `[...]` 内的已归一实参文本**(如 "{sha}.AST*")替换模板注解
# 里的 T,替换结果再送到本函数解析;本函数此前只认 `rawptr[` / `t.CPtr[` /
# 裸 `t.CPtr`,遇到 `{sha}.AST*` 会落到下方 strrchr('.') 取末段得 "AST*" →
# 查 struct miss → **静默**返回 None(跨模块参数/返回宽度被悄悄收窄)。
# 现场:Test/AstTest/App/main.vp:21 的 `list[ast.AST | t.CPtr] | t.CPtr` 特化链。
# 最小改法:把尾随 `*`(先跳尾随空格)按层数重写回**已存在**的 `rawptr[...]`
# 文本形再走原分支(与 `rawptr[` 分支逐字等价,不新写 Ptr 包装逻辑):
# "{sha}.AST*" → "rawptr[{sha}.AST]";"i8**" → "rawptr[rawptr[i8]]"。
seg_len_ps: sizet = string.strlen(seg)
star_end_ps: sizet = seg_len_ps
while star_end_ps > 0 and seg[star_end_ps - 1] == ' ':
star_end_ps -= 1
star_cnt_ps: sizet = 0
star_base_end_ps: sizet = star_end_ps
while star_base_end_ps > 0 and seg[star_base_end_ps - 1] == '*':
star_cnt_ps += 1
star_base_end_ps -= 1
if star_cnt_ps > 0 and star_base_end_ps > 0:
# 基数段长度 = star_base_end_ps;每层 "rawptr[" + "]" 共 8 字符
star_buf_len_ps: sizet = star_base_end_ps + 8 * star_cnt_ps + 1
star_buf_ps: bytes = stdlib.malloc(star_buf_len_ps)
if star_buf_ps is None:
return None
star_buf_ps[0] = '\0'
star_k_ps: sizet = 0
while star_k_ps < star_cnt_ps:
string.strcat(star_buf_ps, "rawptr[")
star_k_ps += 1
star_off_ps: sizet = string.strlen(star_buf_ps)
string.strncpy(star_buf_ps + star_off_ps, seg, star_base_end_ps)
star_buf_ps[star_off_ps + star_base_end_ps] = '\0'
star_k_ps = 0
while star_k_ps < star_cnt_ps:
string.strcat(star_buf_ps, "]")
star_k_ps += 1
star_ty_ps: llvmlite.LLVMType* = _resolve_single_vpi_type_str(pool, star_buf_ps)
stdlib.free(star_buf_ps)
return star_ty_ps
i8_ty: llvmlite.LLVMType* = llvmlite.Int8(pool)
i8_ptr_ty: llvmlite.LLVMType* = 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)
# LLVM 类型串:vpi 缓存对无返回注解方法默认存 "i32"
# (见 _cache_vpi_return_annots_text;push 等无注解方法返回 i32)。
# 之前缺分支导致解析返回 None → 泛型模板方法(Vector[int].push)调用
# 走 fail-fast"方法返回类型无法精确获取",实际是约定不一致。
if string.strcmp(seg, "i32") == 0: return llvmlite.Int32(pool)
if string.strcmp(seg, "i64") == 0: return llvmlite.Int64(pool)
if string.strcmp(seg, "i16") == 0: return llvmlite.Int16(pool)
if string.strcmp(seg, "i8") == 0: return i8_ty
if string.strcmp(seg, "i1") == 0: return llvmlite.Int1(pool)
if string.strcmp(seg, "f32") == 0: return llvmlite.Float(pool)
if string.strcmp(seg, "f64") == 0: return llvmlite.Double(pool)
if string.strcmp(seg, "void") == 0: return llvmlite.Void(pool)
# vpi 源文本中 `-> None` 被原样缓存为 "None"(_cache_vpi_return_annots_text
# 提取 arrow_pos 后到 ':' 前的内容)。此前无 "None" 分支 → 解析返回 None →
# `-> None` 的跨模块方法(如 AssignGenMixin._register_cdefine_from_value)vpi
# 缓存命中也解析不出 void → 方法返回类型 fail-fast 误报(追加80e 断点 B)。
# None 与 void 同义,归一到 Void。
if string.strcmp(seg, "None") == 0:
return llvmlite.Void(pool)
# rawptr[T] 内建裸指针(等价 t.CPtr[T] → Ptr(T)):vpi 返回/参数注解按源码
# 原样保留 Subscript 形态(如 tokens.vp `new_token -> rawptr[Token]`)。
# 此前无此分支 → 整个注解解析返回 None → text.ll 尚未生成(合法时序:依赖
# 模块尚未翻译,占位 text.ll 为空)时跨模块调用 fail-fast 误报
# (includes/ast/lexer.vp new_token 根因)。与定义侧 TypeGenResolve
# resolve_annotation_type 的 rawptr 分支同口径:Ptr(resolve(T))。
if string.strncmp(seg, "rawptr[", 7) == 0:
seg_len_rp: sizet = string.strlen(seg)
if seg_len_rp > 8 and seg[seg_len_rp - 1] == ']':
inner_len_rp: sizet = seg_len_rp - 8
inner_buf_rp: bytes = stdlib.malloc(inner_len_rp + 1)
if inner_buf_rp is not None:
string.strncpy(inner_buf_rp, seg + 7, inner_len_rp)
inner_buf_rp[inner_len_rp] = '\0'
inner_ty_rp: llvmlite.LLVMType* = _resolve_single_vpi_type_str(pool, inner_buf_rp)
stdlib.free(inner_buf_rp)
if inner_ty_rp is not None:
return llvmlite.Ptr(pool, inner_ty_rp)
return None
# t.CPtr[T] / c.CPtr[T] 下标指针(含嵌套,如 t.CPtr[t.CPtr] → Ptr(Ptr(i8))):与定义侧 TypeGenResolve 的 Subscript(CPtr) 分支同口径 Ptr(resolve(T))。
# vpi 注解文本按源码原样缓存;本函数若对该形态返回 None 会让该段被丢弃 → 联合坍缩到首段(i8),与定义侧指针层级不一致(跨模块 declare/define ABI 错位)。
cptr_sub_pfx: sizet = 0
if string.strncmp(seg, "t.CPtr[", 7) == 0 or string.strncmp(seg, "c.CPtr[", 7) == 0:
cptr_sub_pfx = 7
if cptr_sub_pfx > 0:
seg_len_cp: sizet = string.strlen(seg)
if seg_len_cp > cptr_sub_pfx + 1 and seg[seg_len_cp - 1] == ']':
inner_len_cp: sizet = seg_len_cp - cptr_sub_pfx - 1
inner_buf_cp: bytes = stdlib.malloc(inner_len_cp + 1)
if inner_buf_cp is not None:
string.strncpy(inner_buf_cp, seg + cptr_sub_pfx, inner_len_cp)
inner_buf_cp[inner_len_cp] = '\0'
inner_ty_cp: llvmlite.LLVMType* = _resolve_single_vpi_type_str(
pool, inner_buf_cp)
stdlib.free(inner_buf_cp)
if inner_ty_cp is not None:
return llvmlite.Ptr(pool, inner_ty_cp)
return None
# t.XXX / c.XXX 内置类型映射
if string.strncmp(seg, "t.", 2) == 0 or string.strncmp(seg, "c.", 2) == 0:
attr_nm: str = seg + 2
# t.CExport:导出标记,非真实类型。定义侧 FuncGenDef 已特判
# CExport → Void(返回空),vpi 返回注解缓存原样缓存 "t.CExport",
# 调用方解析必须同样归一 Void,否则 vpi 缓存 miss → 跨模块函数
# 返回类型无法精确获取 fail-fast 误报(VKernel mm.free -> t.CExport
# "1fdad1c76fb116bc.free 返回类型无法精确获取" 根因)。
if string.strcmp(attr_nm, "CExport") == 0:
return llvmlite.Void(pool)
# [阶段2] 统一口径:CTypedef 动态表优先(库 typedef 走正常库分析),
# miss 回退 map_t_type(t/c 库内部特判)。
mapped_ty: llvmlite.LLVMType* = GenApi.resolve_typedef_or_map(pool, attr_nm, None, None)
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* = GenApi.get_struct_type(cls_nm2)
if mod_struct_ty is not None:
return mod_struct_ty
# 枚举兜底:t.CEnum 注册在独立 EnumTable(EnumEntry),不进 StructTable,
# get_struct_type 查不到。如 fat32.close -> types.FRESULT(FRESULT 是枚举),
# struct 落空必须回退枚举表取 BaseTy(通常 i32)。否则 vpi 返回注解解析
# 失败 → 跨模块函数返回类型无法精确获取 → text.ll 缺失(依赖环)时
# 永久 pending(VKernel fat32→sched→process→fat32 Phase A 死锁根因)。
mod_enum_ty: llvmlite.LLVMType* = GenApi.get_enum_base_type(cls_nm2)
if mod_enum_ty is not None:
return mod_enum_ty
return None
# 裸类名 → 全局 struct 注册表
bare_struct_ty: llvmlite.LLVMType* = GenApi.get_struct_type(seg)
if bare_struct_ty is not None:
return bare_struct_ty
# 裸枚举名 → EnumTable 兜底(同上:枚举不在 StructTable)
bare_enum_ty: llvmlite.LLVMType* = GenApi.get_enum_base_type(seg)
if bare_enum_ty is not None:
return bare_enum_ty
# 裸 typedef 名(如 VOIDPTR):CTypedef 动态表优先,miss 回退 map_t_type
mapped_bare: llvmlite.LLVMType* = GenApi.resolve_typedef_or_map(pool, seg, None, None)
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 时节点 kind() 异常(可能退化为裸函数调用)导致 resolve 得到垃圾类型(如 Ptr(i8))。
#
# 支持: "str"/"bytes" → i8*; "int" → i32; "bool" → i1; "char" 等 → map_t_type 映射; "X | t.CPtr" → Ptr(Struct X); "t.CPtr" → i8*; "memhub.MemBuddy*" → Ptr(Struct MemBuddy)。
# ============================================================
def _resolve_vpi_ret_annot_str(pool: memhub.MemBuddy*,
ret_annot: str,
i32_ty: llvmlite.LLVMType*,
i8_ptr_ty: llvmlite.LLVMType*) -> llvmlite.LLVMType*:
"""直接解析 vpi 返回注解字符串为 LLVMType(不构造 AST 节点)"""
if ret_annot is None or pool is None:
return None
# tuple[T1, T2, ...] → 匿名结构体 {T1, T2, ...}(多返回值打包,
# 与 TypeGenMixin._resolve_generic_subscript_ty 的 tuple 分支一致)。
# b2a_hqx -> tuple[str, t.CUnsignedShort] 之前按普通段解析失败 → 调用侧
# 返回类型退化 i8* → tuple 解包失效(hqx 段崩溃根因)。
if string.strncmp(ret_annot, "tuple[", 6) == 0:
inner_len_t: sizet = 0
while ret_annot[6 + inner_len_t] != ']' and ret_annot[6 + inner_len_t] != '\0':
inner_len_t += 1
if ret_annot[6 + inner_len_t] != ']' or inner_len_t == 0:
return None
# tuple 返回类型的字段链表是 llvmlite.ParamNode(new_param_node / paramnode_set_next /
# Struct 形参均为 rawptr[ParamNode])——原先误注解为 Function*。
tup_head_t: llvmlite.ParamNode* = None
tup_tail_t: llvmlite.ParamNode* = None
tup_count_t: int = 0
p_t: sizet = 0
while p_t < inner_len_t:
# 跳过前导空格/逗号
while p_t < inner_len_t and (ret_annot[6 + p_t] == ' ' or ret_annot[6 + p_t] == ','):
p_t += 1
seg_s_t: sizet = p_t
while p_t < inner_len_t and ret_annot[6 + p_t] != ',':
p_t += 1
seg_e_t: sizet = p_t
while seg_e_t > seg_s_t and ret_annot[6 + seg_e_t - 1] == ' ':
seg_e_t -= 1
if seg_e_t > seg_s_t:
seg_len_t: sizet = seg_e_t - seg_s_t
seg_buf_t: bytes = stdlib.malloc(seg_len_t + 1)
if seg_buf_t is None:
return None
string.strncpy(seg_buf_t, ret_annot + 6 + seg_s_t, seg_len_t)
seg_buf_t[seg_len_t] = '\0'
seg_ty_t: llvmlite.LLVMType* = _resolve_single_vpi_type_str(pool, seg_buf_t)
stdlib.free(seg_buf_t)
if seg_ty_t is None:
return None
tup_node_t: llvmlite.ParamNode* = llvmlite.new_param_node(pool, seg_ty_t)
if tup_node_t is None:
return None
if tup_head_t is None:
tup_head_t = tup_node_t
else:
llvmlite.paramnode_set_next(tup_tail_t, tup_node_t)
tup_tail_t = tup_node_t
tup_count_t += 1
if tup_head_t is None:
return None
return llvmlite.Struct(pool, tup_head_t, tup_count_t, None)
has_cptr: int = 0
has_cvoid: int = 0
first_ty: llvmlite.LLVMType* = None
# 产生 first_ty 那一段文本的属性(供末尾 has_cptr 分支复刻定义侧规则,N12):
# first_qual=1 段是限定名(含 '.',如 mod.Class / int64)
# first_wl =1 段是白名单裸标量名(int/char/str/bytes/bool/float/double)
# first_td =1 段是裸 typedef 别名且已成功解析出**非指针**类型(N13,
# 如 sizet/uint64 ⇒ i64):判据=“解析是否成功”,与定义侧
# TypeGenResolve.resolve_annotation_type 的 BinOp(A | t.CPtr)
# 分支同源(同一 resolve_typedef_or_map 口径)。
first_qual: int = 0
first_wl: int = 0
first_td: int = 0
# CPtr 下标段(t.CPtr[T]/c.CPtr[T],含嵌套)的联合合并参数:cptr_sub_lvl = 该段解析后的外层 Ptr 层数 N(如 t.CPtr[t.CPtr] ⇒ 2);cptr_sub_ty = 该段解析出的类型本身。
# 与定义侧 `X | t.CPtr[T]` 分支同口径:CPtr 侧贡献 N 层指针、另一侧提供基类型 ⇒ Ptr^N(基类型)。
# 与 has_cptr(裸 t.CPtr 段)分开记录:裸标记恒「追加 1 层」且有 struct 吸收特例,下标路径无吸收,两者在定义侧分属互斥分支。
cptr_sub_lvl: int = 0
cptr_sub_ty: llvmlite.LLVMType* = None
buf_len: sizet = string.strlen(ret_annot)
pos: sizet = 0
while pos <= buf_len:
# 跳过前导空格
while pos < buf_len and ret_annot[pos] == ' ':
pos += 1
seg_start: sizet = pos
# 只在**顶层**切 `|`:`[...]` 内的 `|` 属于泛型实参内部的联合注解,裸切会把首段
# 截成 `list[ast.AST`(来自 `list[ast.AST | t.CPtr] | t.CPtr`),该段再经
# strrchr('.') 落到 `AST` ⇒ 定义侧解析成 `AST*`(元素的指针),而正确答案是
# list 专化结构的指针(至少也应落 i8* 通配)。深度计数按 `[`/`]`,与
# _cache_vpi_param_annots_text 的顶层逗号切分同口径。
depth_ra: int = 0
while pos < buf_len:
ch_ra: char = ret_annot[pos]
if ch_ra == '[':
depth_ra += 1
elif ch_ra == ']':
if depth_ra > 0:
depth_ra -= 1
elif ch_ra == '|':
if depth_ra == 0:
break
pos += 1
seg_end: sizet = pos
# trim 尾随空格
while seg_end > seg_start and ret_annot[seg_end - 1] == ' ':
seg_end -= 1
if seg_end > seg_start:
seg_len2: sizet = 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'
# CPtr 下标段(t.CPtr[T]/c.CPtr[T],含嵌套如 t.CPtr[t.CPtr]):
# 必须先行识别,不能落普通段分支取 first_ty——定义侧
# TypeGenResolve.vp L2596-2639 对 `X | t.CPtr[T]` 是"CPtr 侧贡献
# N 层指针、另一侧给基类型 ⇒ Ptr^N(X)",与"把该段本身当联合取值"
# 语义不同(如 X=struct S、t.CPtr[t.CPtr]:定义侧 Ptr(Ptr(S)),
# 取该段本身则得 i8**)。此处只记录 N 与段类型,末尾统一合并。
if string.strncmp(seg_buf, "t.CPtr[", 7) == 0 or string.strncmp(seg_buf, "c.CPtr[", 7) == 0:
seg_ty_cs: llvmlite.LLVMType* = _resolve_single_vpi_type_str(pool, seg_buf)
if seg_ty_cs is not None:
# 计算外层 Ptr 嵌套数(定义侧 L2616-2632 同口径)。注意不能在
# case body 内更新 while 条件变量(自举编译的 match 会写到 subject
# 快照槽 → 条件读旧槽死循环,见 TypeGenResolve.vp L2620-2624):
# case 绑定 pointee 到局部变量,match 外更新循环变量。
lvl_cs: int = 0
cur_cs: llvmlite.LLVMType* = seg_ty_cs
while cur_cs is not None:
nxt_cs: llvmlite.LLVMType* = None
match cur_cs:
case llvmlite.LLVMType.Ptr(pointee_cs):
lvl_cs += 1
nxt_cs = pointee_cs
case _:
break
cur_cs = nxt_cs
# 多下标段取最大层数(定义侧按最外层联合右值合并,等价于层数累加)
if lvl_cs > cptr_sub_lvl:
cptr_sub_lvl = lvl_cs
cptr_sub_ty = seg_ty_cs
elif 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 \
or string.strcmp(seg_buf, "void") == 0:
# 裸名 void 是 t.CVoid 的别名(用户 2026-10-01):必须与 CVoid 同处理,
# 否则会走 else 分支取到 first_td=1 → Ptr(Void) 这种非法类型
# (void* 应为 i8*,由下方 has_cptr 分支统一给出)。
has_cvoid = 1
else:
seg_ty: llvmlite.LLVMType* = _resolve_single_vpi_type_str(pool, seg_buf)
if seg_ty is not None and first_ty is None:
first_ty = seg_ty
# 记录该段属性(N12):定义侧 TypeGenResolve.resolve_annotation_type
# 对**裸名** pointee 仅在它解析为已注册结构体、泛型参数名或白名单
# 裸标量时才保留 pointee,否则联合坍缩为 i8*;stub 侧此前无条件
# Ptr(first_ty) → `uint64 | t.CPtr` 两侧不一致(定义 i8* vs declare i64*)。
if string.strchr(seg_buf, 46) is not None:
first_qual = 1
if string.strcmp(seg_buf, "int") == 0 or \
string.strcmp(seg_buf, "char") == 0 or \
string.strcmp(seg_buf, "str") == 0 or \
string.strcmp(seg_buf, "bytes") == 0 or \
string.strcmp(seg_buf, "bool") == 0 or \
string.strcmp(seg_buf, "float") == 0 or \
string.strcmp(seg_buf, "double") == 0:
first_wl = 1
# 裸名 typedef 别名保真(N13,与定义侧同源):sizet/uint64/INT/u32 等别名若被判「非 struct 且不在白名单」会被坍缩成 i8*,
# 而定义侧已改为「解析成功即保留」⇒ 跨模块同型收敛被破坏。判据直接用 GenApi.resolve_typedef_or_map(与定义侧同一 API),命中即置位保留;
# 仅对**非指针**解析结果置位(指针别名 BYTEPTR/HANDLE/VOIDPTR 不置位 → 仍坍缩 i8*,与定义侧守卫对称)。
if first_qual == 0 and first_wl == 0:
if first_ty.kind() != llvmlite.LLVMType.Ptr:
if GenApi.resolve_typedef_or_map(pool, seg_buf, None, None) is not None:
first_td = 1
stdlib.free(seg_buf)
pos += 1 # 跳过 '|'
if has_cptr != 0:
if has_cvoid != 0:
return i8_ptr_ty
if first_ty is not None:
# 解析结果本身即 void(如 `typedef 不透明结构体 = void` 的名字经 typedef 兜底
# 解析出 void)⇒ 与 t.CPtr 合并即 void* = i8*,与 has_cvoid 分支同结论
# (否则会拼出非法的 Ptr(void))。
match first_ty:
case llvmlite.LLVMType.Void():
return i8_ptr_ty
case _:
pass
# 复刻定义侧规则(N12/N13):限定名 / 白名单裸标量 / 已注册结构体 / 可解析的非指针裸 typedef 别名 → 保留 pointee;
# 其余(解析失败、裸枚举名、指针别名)→ 联合坍缩为 i8*,与定义侧 resolve_annotation_type 对称(两侧同型即收敛)。
# 指针别名(first_td 不置位,如 VOIDPTR = CVoid|CPtr → i8*):`| t.CPtr`
# **恒追加恰好 1 层** → i8**(不吸收);指针型 T 同理(见下)。
if first_qual != 0 or first_wl != 0 or first_td != 0:
return llvmlite.Ptr(pool, first_ty)
if first_ty.kind() == llvmlite.LLVMType.Struct:
return llvmlite.Ptr(pool, first_ty)
if first_ty.kind() == llvmlite.LLVMType.Ptr:
# `| t.CPtr` **恒追加恰好 1 层**(与定义侧 TypeGenResolve 的 BinOp 分支同口径):
# 这里原先还做"pointee 命中 find_struct_by_type(类引用语义)就吸收 1 层",
# 那是"裸类名自动升指针"时代的补偿,自裸类名恒为**原型 struct** 后已无对象;
# 而泛型形参实例化为指针类型时(T 的实参文本 `{sha}.AST*`)first_ty 正是
# Ptr(已注册 struct) ⇒ 误吸收把 `T*` 降成 AST*(应为 AST**),
# 现场 includes/_list.vp:54 `elem_ptr[0] = item`。
return llvmlite.Ptr(pool, first_ty)
return i8_ptr_ty
return i8_ptr_ty
# CPtr 下标段联合合并(与定义侧同口径):有另一侧基类型 first_ty → 结果 = Ptr^N(first_ty)(N=cptr_sub_lvl);仅 CPtr 下标段自身 → 该段解析类型本身。
# 下标段无 struct 吸收,不与上方 has_cptr 分支的吸收判据混用;has_cptr 在上一分支已全部返回,二者不叠加。
if cptr_sub_lvl > 0:
if first_ty is None:
return cptr_sub_ty
res_ty_cs: llvmlite.LLVMType* = first_ty
wrap_i_cs: int = 0
while wrap_i_cs < cptr_sub_lvl:
res_ty_cs = llvmlite.Ptr(pool, res_ty_cs)
wrap_i_cs += 1
return res_ty_cs
if has_cvoid != 0:
# t\.CVoid | t.State:State 是装饰器标记(编译侧忽略),t\.CVoid → void。
# 此前缺此分支 → first_ty=None → 返回 None → 跨模块函数返回类型
# fail-fast(CPythonTest Py_Initialize() -> t\.CVoid | t.State 根因)。
return llvmlite.Void(pool)
if first_ty is not None:
return first_ty
return None
# ============================================================
# VpiReader - .vpi AST 解析器
#
# 从 .vpi 存根文件读取函数签名与 CTypedef 定义,注入 declare 到当前模块,用于跨模块函数调用时补充缺失的函数声明。
# .vpi 由 StubGen.Converter._GeneratePyiFromAst 生成(含函数签名、类定义、变量声明、import 语句);
# 本模块将其解析为 AST,提取 FunctionDef 与 CTypedef AnnAssign 并创建对应的 declare 声明。
# ============================================================
# .vpi 读取缓冲区大小(256KB,与 PYI_BUF_SIZE 一致)
VPI_READ_BUF_SIZE: sizet = 262144
# SHA1 hex 字符数(16,仅文件名中的十六进制位,不含结尾 NUL)
#
# 注意:本常量与 lib.Phase.Phase1 的 Phase1.SHA1_LEN(17 = 16 字符 + NUL 的存储步长)同名但语义不同;
# 同名跨模块 CDefine 会造成解析被遮蔽/串值(mb.alloc(0) 分配失败),故改为唯一名 SHA1_HEX_LEN。
SHA1_HEX_LEN: sizet = 16
# ============================================================
# _extract_sha1_from_path - 从 .vpi 文件路径提取 SHA1
#
# .vpi 文件名为 {sha1}.vpi(sha1 为 16 位十六进制字符):取最后一个路径分隔符(/ 或 \)之后文件名的前 16 个字符,分配新缓冲存储(调用者负责 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: sizet = string.strlen(vpi_path)
if total_len < SHA1_HEX_LEN + 4: # 至少需要 "xxxx.vpi" 的长度
return None
# 从后向前查找最后一个 / 或 \
sep_pos: sizet = total_len
while sep_pos > 0:
sep_pos -= 1
ch: char = vpi_path[sep_pos]
if ch == '/' or ch == '\\':
sep_pos += 1
break
if sep_pos + SHA1_HEX_LEN > total_len:
return None
# 分配缓冲区并复制 SHA1(16 字符 + null 终止符)
sha1_buf: str = stdlib.malloc(SHA1_HEX_LEN + 1)
if sha1_buf is None:
return None
string.strncpy(sha1_buf, vpi_path + sep_pos, SHA1_HEX_LEN)
sha1_buf[SHA1_HEX_LEN] = '\0'
return sha1_buf
# ============================================================
# stub 导入上下文记账:一律走 StubMergerStore(单一真源)
#
# 原 _stub_add_imported / _stub_add_from 是本模块内的两份私有副本,与翻译期
# ImportsGenMixin.add_imported_module / add_from_import 重复;其中 Import 分支
# 只抄了 imported_modules 的追加,漏掉 from_imports 的别名映射,导致 vpi 注解
# 前向声明解析不到模块 SHA1(GenTypes.VarEntry.Spec.PtrLevel 失败根因)。
# 记账已统一到 StubMergerStore.AppendImportedModule / AppendFromImport /
# NotePlainImport(纯记账、无索引副作用,正合 stub 上下文所需)。
# ============================================================
# ============================================================
# 递归 typedef 预注册(import 闭包)
#
# CTypedef 表是每文件隔离的:当前模块若未直接 import typedef 定义所在模块(如 UINT 在 stdint.vp),其 stub 未注入 → 表里没有该 typedef
# → 参数类型解析失败退化为 i8_ptr_ty → 调用侧整数实参触发「整数隐式转指针已禁止」CastError 误报。
# 本函数在被读 stub 的 import 闭包上递归注册 CTypedef/CDefine(防环、限深),使注解解析上下文完整,参数/返回类型不再静默退化。
# ============================================================
_TYPEDEF_VISIT_MAX: t.CDefine = 1024
_g_vpi_typedef_visited: bytes = None
_g_vpi_typedef_visited_count: int = 0
# 访问表当前容量(元素数)。**表满必须扩容,不允许静默丢标记**:
# 旧实现 `count >= _TYPEDEF_VISIT_MAX 就 return`(丢标记)⇒ 防环失效 ⇒ 递归闭包
# 反复重解析同一批 .vpi(限深 8 也挡不住宽度爆炸),每个 vpi 的 Lexer/Token/AST 都
# 从池里分配且本窗口不回收 ⇒ 单次 translate 吃掉数百 MB ⇒ 1 GiB 池 OOM
# (OOM 报告特征:ctx=translate/<json 模块>、chk_depth 数万、free_bytes=0、
# 「镜像存活=0 块」且记录项块头读作 0x0)。
_g_vpi_typedef_visited_cap: int = 0
def ResetVpiTypedefVisited() -> None:
"""清空递归 typedef 注册的防环访问标记(每个模块的 stub 注入前调用)"""
global _g_vpi_typedef_visited, _g_vpi_typedef_visited_count, _g_vpi_typedef_visited_cap
if _g_vpi_typedef_visited is not None:
stdlib.free(_g_vpi_typedef_visited)
_g_vpi_typedef_visited = None
_g_vpi_typedef_visited_count = 0
_g_vpi_typedef_visited_cap = 0
def _vpi_typedef_visited_check(sha1: str) -> int:
"""检查 sha1 是否已递归注册过(1=已处理,0=未处理;纯查询不标记)"""
global _g_vpi_typedef_visited, _g_vpi_typedef_visited_count
if sha1 is None:
return 1
if _g_vpi_typedef_visited is None:
return 0
vi: int
for vi in range(_g_vpi_typedef_visited_count):
if string.strcmp(_g_vpi_typedef_visited + vi * 17, sha1) == 0:
return 1
return 0
def _vpi_typedef_visited_mark(sha1: str) -> None:
"""标记 sha1 为已递归注册(成功后调用,防环)"""
global _g_vpi_typedef_visited, _g_vpi_typedef_visited_count, _g_vpi_typedef_visited_cap
if sha1 is None:
return
if _g_vpi_typedef_visited is None:
_g_vpi_typedef_visited = stdlib.malloc(_TYPEDEF_VISIT_MAX * 17)
if _g_vpi_typedef_visited is None:
# fail-fast:没有防环表 ⇒ 递归闭包重解析爆炸(池 OOM),不能静默继续
stdio.printf("[VPI] 防环访问表分配失败(无表则闭包重解析爆炸,终止)\n")
stdio.fflush(None)
stdlib.exit(1)
string.memset(_g_vpi_typedef_visited, 0, _TYPEDEF_VISIT_MAX * 17)
_g_vpi_typedef_visited_count = 0
_g_vpi_typedef_visited_cap = _TYPEDEF_VISIT_MAX
if _g_vpi_typedef_visited_count >= _g_vpi_typedef_visited_cap:
# 表满:扩容(×2),**绝不能静默丢标记**——丢标记 = 防环失效 = 同一批 .vpi 被
# 反复重解析(每个 vpi 的 Lexer/Token/AST 都从池分配且本窗口不回收)⇒ 池 OOM。
old_cap: int = _g_vpi_typedef_visited_cap
new_cap: int = old_cap * 2
nb: bytes = stdlib.realloc(_g_vpi_typedef_visited, new_cap * 17)
if nb is None:
stdio.printf("[VPI] 防环访问表扩容失败 cap=%d(丢标记引发重解析爆炸,终止)\n",
old_cap)
stdio.fflush(None)
stdlib.exit(1)
_g_vpi_typedef_visited = nb
string.memset(nb + old_cap * 17, 0, old_cap * 17)
_g_vpi_typedef_visited_cap = new_cap
string.strcpy(_g_vpi_typedef_visited + _g_vpi_typedef_visited_count * 17, sha1)
_g_vpi_typedef_visited_count += 1
def _vpi_register_typedef_closure(pool: memhub.MemBuddy*,
import_mods: str,
temp_dir: str,
depth: int) -> int:
"""递归注册 import 闭包模块的 CTypedef/CDefine,返回 0 恒(副作用注册)
仅注册 AnnAssign 的 t.CTypedef/t.CDefine,不注入函数 declare(避免
传递依赖的函数表膨胀)。vpi 文件缺失(Phase A 早期 stub 未生成)时
静默跳过,Phase 2 stub 就绪后自然命中。
"""
if pool is None or import_mods is None or temp_dir is None:
return 0
if depth > 8:
return 0
td_len: sizet = string.strlen(temp_dir)
total_len: sizet = string.strlen(import_mods)
pos: sizet = 0
while pos < total_len:
while pos < total_len and import_mods[pos] == ' ':
pos += 1
if pos >= total_len:
break
m_start: sizet = pos
while pos < total_len and import_mods[pos] != ' ':
pos += 1
m_len: sizet = pos - m_start
if m_len == 0:
continue
# t/c 内建库不编译,无 vpi stub
if m_len == 1 and (import_mods[m_start] == 't' or import_mods[m_start] == 'c'):
continue
mod_buf: bytes = stdlib.malloc(m_len + 1)
if mod_buf is None:
continue
string.strncpy(mod_buf, import_mods + m_start, m_len)
mod_buf[m_len] = '\0'
sha1_r: str = StubMergerStore.LookupSha1ByModName(mod_buf)
stdlib.free(mod_buf)
if sha1_r is None:
continue
if _vpi_typedef_visited_check(sha1_r) != 0:
continue
# 构造 {temp_dir}/{sha1[0:2]}/{sha1}.vpi(手写拼接,与
# _ensure_vpi_ret_annot_loaded 一致,避免引入 StubMergerCollect 环依赖)
path_len_r: sizet = td_len + 16 + 12
vpi_path_r: bytes = stdlib.malloc(path_len_r)
if vpi_path_r is None:
continue
string.strcpy(vpi_path_r, temp_dir)
vpi_path_r[td_len] = '/'
vpi_path_r[td_len + 1] = sha1_r[0]
vpi_path_r[td_len + 2] = sha1_r[1]
vpi_path_r[td_len + 3] = '/'
string.strcpy(vpi_path_r + td_len + 4, sha1_r)
string.strcpy(vpi_path_r + td_len + 4 + 16, ".vpi")
f_r: rawptr[fileio.File] = fileio.File(vpi_path_r, fileio.MODE.R)
if f_r.closed:
stdlib.free(vpi_path_r)
continue
vpi_buf_r: bytes = stdlib.malloc(VPI_READ_BUF_SIZE)
if vpi_buf_r is None:
f_r.close()
stdlib.free(vpi_path_r)
continue
bytes_read_r: LONG = f_r.read_all(vpi_buf_r, VPI_READ_BUF_SIZE)
f_r.close()
if bytes_read_r > 0:
# 成功读到 vpi 才标记已处理(文件缺失/读取失败不标记,
# 后续 pass 的 stub 注入可重试)
_vpi_typedef_visited_mark(sha1_r)
if bytes_read_r < VPI_READ_BUF_SIZE:
vpi_buf_r[bytes_read_r] = 0
lx_r: ast.Lexer* = ast.new_lexer(pool)
if lx_r is not None:
ast._lexer_init(lx_r, vpi_buf_r, pool)
tokens_r: ast.Token* = ast.tokenize(lx_r)
tree_r: ast.AST* = ast.parse_tokens(pool, tokens_r)
if tree_r is not None and tree_r.children is not None:
ch_r: list[ast.AST*]* = tree_r.children
cn_r: sizet = len(ch_r)
# 收集子模块 imports(递归闭包):两遍处理——先累计所需长度,再按精确长度分配后填充,不存在长度上限,故无需丢弃。
# (定长缓冲 + "装不下就静默丢弃" 会让闭包缺模块 ⇒ 该模块的 CTypedef/CDefine 不可见 ⇒ 注解解析退化。)
# 计数阶段每项多算 1 字节分隔符(首项不用),偏保守,无害。
sub_need: sizet = 1 # 末尾 NUL
ci_c: sizet
for ci_c in range(cn_r):
child_c: ast.AST* = ch_r.get(ci_c)
if child_c is None:
continue
kk_c: int = child_c.kind()
if kk_c == ast.ASTKind.Import:
imp_c: ast.Import* = ast.Import*(child_c)
if imp_c.names is not None:
in_nc: sizet = len(imp_c.names)
for ii_c in range(in_nc):
al_c: ast.Alias* = ast.Alias*(imp_c.names.get(ii_c))
if al_c is not None and al_c.name is not None:
sub_need += string.strlen(al_c.name) + 1
elif kk_c == ast.ASTKind.ImportFrom:
ifr_c: ast.ImportFrom* = ast.ImportFrom*(child_c)
if ifr_c.module is not None:
sub_need += string.strlen(ifr_c.module) + 1
sub_imports: bytes = stdlib.malloc(sub_need)
if sub_imports is not None:
sub_imports[0] = '\0'
sub_len: sizet = 0
ci_r: sizet
for ci_r in range(cn_r):
child_r: ast.AST* = ch_r.get(ci_r)
if child_r is None:
continue
kk_r: int = child_r.kind()
if kk_r == ast.ASTKind.Import:
imp_r: ast.Import* = ast.Import*(child_r)
if imp_r.names is not None:
in_n: sizet = len(imp_r.names)
ii_r: sizet
for ii_r in range(in_n):
al_r: ast.Alias* = ast.Alias*(imp_r.names.get(ii_r))
if al_r is not None and al_r.name is not None:
il_r: sizet = string.strlen(al_r.name)
if sub_len > 0:
sub_imports[sub_len] = ' '
sub_len += 1
string.strcpy(sub_imports + sub_len, al_r.name)
sub_len += il_r
sub_imports[sub_len] = '\0'
elif kk_r == ast.ASTKind.ImportFrom:
ifr_r: ast.ImportFrom* = ast.ImportFrom*(child_r)
if ifr_r.module is not None:
ml_r: sizet = string.strlen(ifr_r.module)
if sub_len > 0:
sub_imports[sub_len] = ' '
sub_len += 1
string.strcpy(sub_imports + sub_len, ifr_r.module)
sub_len += ml_r
sub_imports[sub_len] = '\0'
# 第二遍:注册本模块 AnnAssign 的 CTypedef/CDefine
ci2_r: sizet
for ci2_r in range(cn_r):
child2_r: ast.AST* = ch_r.get(ci2_r)
if child2_r is None:
continue
if child2_r.kind() != ast.ASTKind.AnnAssign:
continue
aa_r: ast.AnnAssign* = ast.AnnAssign*(child2_r)
if aa_r.annotation is None or aa_r.target is None or aa_r.value is None:
continue
if GenApi._is_ctypedef_annotation(aa_r.annotation) != 0:
_register_ctypedef_from_annassign(pool, aa_r)
elif GenApi.is_cdefine_annotation(aa_r.annotation) != 0:
if aa_r.target.kind() != ast.ASTKind.Name:
continue
cdef_t: ast.Name* = ast.Name*(aa_r.target)
if cdef_t.id is None:
continue
if aa_r.value.kind() == ast.ASTKind.Constant:
cn_v: ast.Constant* = ast.Constant*(aa_r.value)
if cn_v.const_kind == ast.CONST_FLOAT:
GenApi.register_cdefine_double(pool, cdef_t.id, cn_v.float_val)
elif cn_v.const_kind == ast.CONST_INT:
cdef_val: int64 = GenApi.extract_cdefine_int_value(aa_r.value)
GenApi.register_cdefine_constant(pool, cdef_t.id, cdef_val)
# 递归处理子模块 imports(防环由 visited 标记保证)
if sub_imports[0] != '\0':
_vpi_register_typedef_closure(pool, sub_imports, temp_dir, depth + 1)
stdlib.free(sub_imports)
stdlib.free(vpi_buf_r)
stdlib.free(vpi_path_r)
return 0
# ============================================================
# ParseVpiIntoModule - 解析 .vpi 文件,将函数声明注入到当前模块
#
# 读取 .vpi → AST 解析 → 遍历顶层节点创建 declare:对每个 FunctionDef 提取函数名/参数/返回类型,检测 CExport/CExtern/State 标记(决定是否用裸名),
# 用 find_func_in_module 去重,再用 create_declare + add_param 创建声明并以裸名(非 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*,
mod: llvmlite.LLVMModule*,
vpi_path: str,
trans: HT.Translator*) -> int:
"""解析 .vpi 文件,将函数声明注入到当前模块
解析 stub 期间临时将错误归因切换到被读 stub 文件(.vpi):
注解解析失败时 fatal_error 应指向 stub 及其行号(真实出错位置),
而非当前编译模块(错误归因修复)。
"""
if pool is None or mod is None or vpi_path is None or trans is None:
return 0
saved_cur: str = GenApi.get_current_file()
GenApi.set_current_file_type(vpi_path)
ret: int = _ParseVpiIntoModuleImpl(pool, mod, vpi_path, trans)
GenApi.set_current_file_type(saved_cur)
return ret
def _ParseVpiIntoModuleImpl(pool: memhub.MemBuddy*,
mod: llvmlite.LLVMModule*,
vpi_path: str,
trans: HT.Translator*) -> int:
"""解析 .vpi 文件,将函数声明注入到当前模块(ParseVpiIntoModule 实际实现)"""
if pool is None or mod is None or vpi_path is None or trans is None:
return 0
# 1. 读取 .vpi 文件
f: rawptr[fileio.File] = 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
# 2. 解析为 AST
lx: ast.Lexer* = ast.new_lexer(pool)
if lx is None:
stdlib.free(vpi_buf)
return 0
ast._lexer_init(lx, vpi_buf, pool)
tokens: ast.Token* = ast.tokenize(lx)
tree: ast.AST* = 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)
# 同源:提取函数/方法参数注解字符串缓存(tree 参数列表同样可能被
# 覆盖截断,_inject_*_declare 用此缓存补全 declare 参数数)
_cache_vpi_param_annots_text(vpi_buf, src_sha1)
# 3. 遍历 AST 顶层节点
ch: list[ast.AST*]* = tree.children
if ch is None:
if src_sha1 is not None:
stdlib.free(src_sha1)
return 1
n: sizet = len(ch)
# 收集 stub 内被读模块自己的 import 语句,构建独立的注解解析上下文。
# .vpi stub 顶部含被读模块的 import(Converter 已写入,如 string.vp 的
# "import t, c"),用它替代当前模块的 imported_modules/from_imports 去
# 解析注解——根治跨上下文误报(如 t.CArray[str] 需要 't' 已导入)。
stub_imported_modules: str = None
stub_from_imports: str = None
# 记账统一走 StubMergerStore(单一真源,与翻译期 ImportsGenMixin.translate_import
# 同口径):含 `import X as Y → from_imports "Y:X"`(别名反查模块真名)与
# `import a.b.c → imported_modules "a"`(顶层包名)。
stub_ctx: StubMergerStore.ImportCtx* = StubMergerStore.AllocImportCtx(pool, None, None)
for si in range(n):
schild: ast.AST* = ch.get(si)
if schild is None:
continue
sk: int = schild.kind()
if sk == ast.ASTKind.Import:
simp: ast.Import* = ast.Import*(schild)
if simp.names is not None:
sn_n: sizet = len(simp.names)
for sni in range(sn_n):
sal: ast.Alias* = ast.Alias*(simp.names.get(sni))
if sal is not None and sal.name is not None:
StubMergerStore.NotePlainImport(pool, stub_ctx, sal.name, sal.asname)
elif sk == ast.ASTKind.ImportFrom:
sifr: ast.ImportFrom* = ast.ImportFrom*(schild)
sifr_mod: str = sifr.module
# 相对导入补包前缀 + 包 __init__ 判定(两者**都不再依赖 rel_path 表**)。
#
# vpi 里 `from .base import X` 只存 module="base"(前导点记在 level),按字面
# "base" 精确查找查不到 "ast.base" ⇒ 裸类名(AST 等)解析失败。包前缀的
# 权威来源是**模块名表**:兄弟模块 S 的全名 = "<包前缀>.S"(如 "ast.stmts")
# ⇒ 包前缀 = S 的模块名去掉最后一段;而 Utils.PathToModuleName 把
# `pkg/__init__.vp` 记成 "pkg"(**不带** .__init__,见其 :150 文档)
# ⇒ "本 stub 的模块名 == 该包前缀"即证明**本 stub 就是该包的 __init__**,
# 这正是重导出别名 entry 的登记条件。
# 为什么要脱离 rel_path:rel_path 反查在部分运行形态下取不到条目 ⇒
# 原 `strstr(rel_path,"__init__.vp")` 判定恒 false ⇒ 别名静默不登记 ⇒
# `ast.ClassDef` 这类**限定名在泛型实参里保留包前缀**,与构造函数侧(真
# 定义模块前缀)分裂成两个类型(现场 ClassGenGeneric.vp:92 的 CastError;
# 幽灵名 `list[1abcd5277fa442de.ClassDef*]` 在全部产物里恒为 opaque)。
# 取不到模块名证据时回退旧的 rel_path 路线,行为不变。
pkg_prefix_rt: str = None
if sifr.level > 0 and sifr_mod is not None and sifr_mod[0] != '\0':
_sib_sha_rt: str = StubMergerStore.LookupSha1ByModNameSuffix(sifr_mod)
if _sib_sha_rt is not None:
_sub_mod_rt: str = StubMergerStore.LookupModNameBySha1(_sib_sha_rt)
if _sub_mod_rt is not None:
_dot_rt: str = string.strrchr(_sub_mod_rt, '.')
if _dot_rt is not None:
_pfx_len_rt: sizet = sizet(uint64(_dot_rt) - uint64(_sub_mod_rt))
if _pfx_len_rt > 0:
_my_mod_rt: str = StubMergerStore.LookupModNameBySha1(src_sha1)
# 确认:本 stub 的模块名必须**恰好等于**该前缀(= 本 stub
# 就是包 __init__)。后缀命中可能落到同后缀的别的模块,
# 故必须用本 stub 自己的模块名确认,不确认就让位给 rel_path。
_conf_rt: int = 0
if _my_mod_rt is not None and string.strlen(_my_mod_rt) == _pfx_len_rt:
if string.strncmp(_my_mod_rt, _sub_mod_rt, _pfx_len_rt) == 0:
_conf_rt = 1
if _conf_rt != 0:
pkg_prefix_rt = pool.alloc(_pfx_len_rt + 1)
if pkg_prefix_rt is not None:
string.strncpy(pkg_prefix_rt, _sub_mod_rt, _pfx_len_rt)
pkg_prefix_rt[_pfx_len_rt] = '\0'
sifr_mod_q: str = sifr_mod
if sifr_mod is not None and sifr_mod[0] != '\0' and sifr.level == 1:
if pkg_prefix_rt is not None:
qtotal_p: sizet = string.strlen(pkg_prefix_rt) + 1 + string.strlen(sifr_mod) + 1
qbuf_p: str = pool.alloc(qtotal_p)
if qbuf_p is not None:
qbuf_p[0] = '\0'
string.strcat(qbuf_p, pkg_prefix_rt)
string.strcat(qbuf_p, ".")
string.strcat(qbuf_p, sifr_mod)
sifr_mod_q = qbuf_p
else:
rel_p_r: str = StubMergerStore.LookupSha1RelPath(src_sha1)
if rel_p_r is not None:
slash_r: str = string.strrchr(rel_p_r, '/')
if slash_r is not None:
pkg_len_r: sizet = string.strlen(rel_p_r) - string.strlen(slash_r)
if pkg_len_r > 0:
qtotal_r: sizet = pkg_len_r + 1 + string.strlen(sifr_mod) + 1
qbuf_r: str = pool.alloc(qtotal_r)
if qbuf_r is not None:
string.strncpy(qbuf_r, rel_p_r, pkg_len_r)
qbuf_r[pkg_len_r] = '.'
string.strcpy(qbuf_r + pkg_len_r + 1, sifr_mod)
sifr_mod_q = qbuf_r
if sifr_mod is not None:
if stub_ctx is not None:
stub_ctx.ImportedModules = StubMergerStore.AppendImportedModule(
pool, stub_ctx.ImportedModules, sifr_mod_q)
# 方案A:包 __init__.vpi 的重导出类以包 sha1 注册别名 entry
# (ast.Attribute 定义在 ast.exprs 但归属 ast 包;查找端按包
# sha1 匹配,注册端按定义文件 sha1 → name+sha1 错位 fail-fast。
# 仅 __init__ 触发,普通模块 vpi 的跨模块 from-import 不注册,
# 避免把类错误归属到当前模块 sha1)
# 包 __init__ 判定(**首选不依赖 rel_path 表**):pkg_prefix_rt 非空
# 即上面的模块名证据成立——"本 stub 的模块名 == 兄弟模块名去掉末段"
# ⇒ 本 stub 就是该包的 __init__(同一事实,不重复计算)。
# rel_path 反查取不到条目时原判定恒 false ⇒ 重导出别名静默不登记 ⇒
# `ast.ClassDef` 这类限定名在泛型实参里保留**包前缀**,与定义模块
# 前缀分裂(现场 ClassGenGeneric.vp:92 的 CastError)。
# 仅当模块名证据不足时才回退旧 rel_path 判定(行为不变)。
is_pkg_init_rt: int = 0
if pkg_prefix_rt is not None:
is_pkg_init_rt = 1
if is_pkg_init_rt == 0:
rel_path_rt: str = StubMergerStore.LookupSha1RelPath(src_sha1)
if rel_path_rt is not None:
# [FIX] 原判定 strcmp(rel_path + strlen(rel_path) - 9, "__init__.vp")
# 假设 "__init__.vp" 从 len-9 开始;实际包路径 "ast/__init__.vp"
# 长 15,该后缀从索引 4 起(11 字符),len-9=6 指向 "init__.vp"
# → 判定恒 false → 包重导出永不触发(ast.Attribute 未注册根因之一)。
# 改用 strstr 判定 rel_path 包含 "__init__.vp"(仅包 __init__ 文件命中)。
if string.strstr(rel_path_rt, "__init__.vp") is not None:
is_pkg_init_rt = 1
# 解析被 from-import 的模块 sha1:相对导入按 parser 表示判定——
# 前导点存于 level(from .exprs import → level=1, module="exprs",
# 见 includes/ast/stmts.vp ImportFrom.level / parser.vp _parse_from_import),
# module 字段不含前导点。原判定 sifr_mod[0]=='.' 永远不成立 → 走绝对
# 分支 LookupSha1ByModName("exprs") 查不到 "ast.exprs" → sub_sha1=None
# → 重导出被跳过(ast.Attribute 未注册根因之二)。
sub_sha1_rt: str = None
if sifr.level > 0:
if sifr_mod is not None and sifr_mod[0] != '\0':
# 优先用补前缀后的**精确**模块名(sifr_mod_q,如 ast.base)——
# 后缀匹配只作旧行为兜底:后缀命中可能落到别的同后缀模块,
# 而下面的按需注入会按该 sha1 拉 vpi,注入错模块代价大。
sub_sha1_rt = StubMergerStore.LookupSha1ByModName(sifr_mod_q)
if sub_sha1_rt is None:
sub_sha1_rt = StubMergerStore.LookupSha1ByModNameSuffix(sifr_mod)
else:
sub_sha1_rt = StubMergerStore.LookupSha1ByModName(sifr_mod)
# [D26 fail-fast] 包 __init__ 的 from-import 解析不出子模块 sha1 时
# **不再静默跳过**:跳过会让该子模块的类一个都注册不上 → 包 stub 里
# 引用这些类的注解在后续解析时落空 → 报错点远离真因(实测:报错指向
# ast/__init__.vpi 的 `parse_expression -> rawptr[Expression]`,而
# Expression 定义在 ast.stmts,真因是子模块 sha1 查找/注入失败)。
# 此处直接给出包名与子模块名(含补前缀后的精确名)便于定位。
if is_pkg_init_rt != 0 and sub_sha1_rt is None \
and sifr_mod is not None and sifr_mod[0] != '\0':
GenApi.fatal_error(
sifr,
f"包 stub 的 from-import 子模块 sha1 查找失败:"
f"包={rel_path_rt if rel_path_rt is not None else '?'},"
f"子模块='{sifr_mod}'(补前缀后='{sifr_mod_q}');"
f"该子模块的类无法注册,引用它们的注解会在后续解析时落空",
"NameError")
if sifr.names is not None:
sifr_nn: sizet = len(sifr.names)
sifri: sizet
for sifri in range(sifr_nn):
sial: ast.Alias* = ast.Alias*(sifr.names.get(sifri))
if sial is not None and sial.name is not None:
slocal: str = sial.name
sorig: str = None
if sial.asname is not None:
slocal = sial.asname
sorig = sial.name
# `from X import *` 亦写 "*:mod"(与翻译期
# ImportsGenMixin.translate_from_import_names 同口径):
# lookup_from_import 的 star 回退正是靠这条 entry,旧实现
# 在此跳过 ⇒ stub 上下文里 star 回退恒失效(同一份约定
# 的两处 loop 又一处不一致)。
if stub_ctx is not None:
stub_ctx.FromImports = StubMergerStore.AppendFromImport(
pool, stub_ctx.FromImports, slocal, sifr_mod_q, sorig)
# 重导出类以包 sha1 注册(asname 时按别名注册);star 无
# 具体类名,不参与重导出(仅记账,见上)。
if string.strcmp(sial.name, "*") != 0:
if is_pkg_init_rt != 0 and sub_sha1_rt is not None:
_inject_reexport_class_declare(
pool, mod, sial.name, slocal,
sub_sha1_rt, src_sha1, trans)
# 用 stub 自己的 imports 替代当前模块的(stub 注解解析上下文正确性)
if stub_ctx is not None:
stub_imported_modules = stub_ctx.ImportedModules
stub_from_imports = stub_ctx.FromImports
imported_modules: str = trans._imported_modules
from_imports: str = trans._from_imports
if stub_imported_modules is not None:
imported_modules = stub_imported_modules
if stub_from_imports is not None:
from_imports = stub_from_imports
# 把源模块自己加入 stub 注解上下文:stub 注解(如 __sha512 的 transform
# 参数 t.CArray[uint8, SHA512_BLOCK_LEN])引用源模块自身的 CDefine,
# 但 stub 的 import 只含被 import 的模块(t/c/string),不含自己。
# 不加则 _eval_const_int_expr 跨模块 CDefine 查找遍历不到源模块
# (imported_modules 无 __sha512)→ t.CArray count 报"必须是整数常量"。
# 示例:RelPath "hashlib/__sha512.vp" → 模块名 "hashlib.__sha512"。
if src_sha1 is not None:
stub_rel_src: str = StubMergerStore.LookupSha1RelPath(src_sha1)
if stub_rel_src is not None:
sr_len_src: sizet = string.strlen(stub_rel_src)
if sr_len_src > 3 and string.strncmp(stub_rel_src + sr_len_src - 3, ".vp", 3) == 0:
sr_len_src -= 3
if sr_len_src > 0:
src_mod_buf: str = stdlib.malloc(sr_len_src + 1)
if src_mod_buf is not None:
sri_src: sizet
for sri_src in range(sr_len_src):
ch_src: char = stub_rel_src[sri_src]
if ch_src == '/' or ch_src == '\\':
src_mod_buf[sri_src] = '.'
else:
src_mod_buf[sri_src] = ch_src
src_mod_buf[sr_len_src] = '\0'
imported_modules = StubMergerStore.AppendImportedModule(
pool, imported_modules, src_mod_buf)
stdlib.free(src_mod_buf)
# 递归预注册 stub import 闭包的 CTypedef/CDefine:注解(如 codepage:
# UINT)的 typedef 定义在 stdint.vp,但 CTypedef 表每文件隔离——当前
# 模块未直接 import stdint 时表里没有 UINT → 参数类型退化为 i8_ptr_ty
# → 调用侧整数实参误报 CastError(testcheck.SetConsoleOutputCP 根因)。
if imported_modules is not None:
_vpi_register_typedef_closure(pool, imported_modules, GenApi.get_temp_dir(), 0)
i32_ty: llvmlite.LLVMType* = llvmlite.Int32(pool)
i8_ty: llvmlite.LLVMType* = llvmlite.Int8(pool)
i8_ptr_ty: llvmlite.LLVMType* = llvmlite.Ptr(pool, i8_ty)
# 函数表信息
funcs_ptr: GenTypes.FuncEntry* = trans._funcs
max_funcs: int = 1024
# ============================================================
# REPL(ReplStubLayoutMode):先从 .stub.ll 恢复各类的**完整布局**
# (__vtable__ + 父类展平字段 + 本类字段)与虚表方法索引,再走下方既有
# vpi 注入(已登记者只标可见)。详见文件末"REPL:includes 类完整布局与
# 虚表恢复"段;批处理该标志恒 0,本块不执行。
# ============================================================
if ReplStubLayoutMode != 0:
_repl_stub_collect_module(pool, mod, ch, n, trans, src_sha1,
stub_imported_modules, stub_from_imports)
# ============================================================
# 第一遍 A:先注册**所有** ClassDef(字段 + 结构体),不处理方法。
# 否则类定义位于全局变量之后时(如 devfs.vp 的 `directories: t.CArray[devfs_directory, MAX]` 在 `class devfs_directory` 之前),
# AnnAssign 解析裸类名时类未注册 → CArray 元素解析失败 fatal。
# ============================================================
i0: sizet
for i0 in range(n):
child0: ast.AST* = ch.get(i0)
if child0 is None:
continue
if child0.kind() != ast.ASTKind.ClassDef:
continue
class_cd0: ast.ClassDef* = ast.ClassDef*(child0)
if class_cd0 is None or class_cd0.name is None:
continue
cls_name_v0: str = class_cd0.name
# 泛型类(class X[T])跳过:stub 模板签名含类级类型参数 T,注入时
# T 无法确定;特化方法 define 已在 Phase1 生成,无需 stub 模板 declare。
tp_stub0: list[str]* = class_cd0.type_params
if tp_stub0 is not None and len(tp_stub0) > 0:
continue
_inject_class_struct_declare(pool, mod, class_cd0, cls_name_v0, trans,
src_sha1, imported_modules, from_imports)
# App 预注册(Phase A-pre,_pre_allow_dep_inject)**跳过方法/函数 declare 注入**:
# 预注册的唯一目的是注册 struct/enum/union(第一遍 A)与包重导出别名(上方
# ImportFrom 段),方法/函数 declare 写在其瞬时模块里、随即随 mb.reset_to 丢弃,
# 不产生任何跨遍作用。而依赖 vpi 的形参/返回注解可能引用**本遍尚未注册**的类
# (跨文件注册顺序:被依赖模块的类可能排在更后,需靠下一遍收敛);此时
# resolve 落空会在 declare 注入里 fail-fast,直接打断收敛。
# 现场:App 预注册 AssignGenMixin.vp 注入 Translator.vpi 时,其
# InjectTracebackInstallCtor 形参 rawptr[Translator] 解析失败(Translator
# 结构体因字段引用尚未注册的 GenTypes 类而本遍未登记)。
# 全量翻译(_declare_only==0,_pre_allow_dep_inject==0)不跳过,仍 fail-fast。
pre_skip_fn_declares: int = 0
if trans is not None and trans._pre_allow_dep_inject != 0:
pre_skip_fn_declares = 1
# ============================================================
# 第一遍 B:**全部类注册完成之后**再处理方法 declare——方法形参/返回值可能引用本文件更靠后定义的类(类间前向引用),
# 若边注册边处理方法,前向引用的类此刻尚未注册 → 形参注解裸名解析落空 → fail-fast。
# ============================================================
for i1 in range(n):
child1: ast.AST* = ch.get(i1)
if child1 is None:
continue
if child1.kind() != ast.ASTKind.ClassDef:
continue
class_cd1: ast.ClassDef* = ast.ClassDef*(child1)
if class_cd1 is None or class_cd1.name is None:
continue
cls_name_v1: str = class_cd1.name
# 泛型类跳过(与第一遍 A 同判据)
tp_stub1: list[str]* = class_cd1.type_params
if tp_stub1 is not None and len(tp_stub1) > 0:
continue
cls_children1: list[ast.AST*]* = class_cd1.children
if cls_children1 is None:
continue
cn21: sizet = len(cls_children1)
for ci21 in range(cn21):
m_child1: ast.AST* = cls_children1.get(ci21)
if m_child1 is None:
continue
if m_child1.kind() != ast.ASTKind.FunctionDef:
continue
m_fd1: ast.FunctionDef* = ast.FunctionDef*(m_child1)
if m_fd1 is None or m_fd1.name is None:
continue
# App 预注册跳过方法 declare(原因见上方 pre_skip_fn_declares 注释)。
if pre_skip_fn_declares == 0:
_inject_method_declare(pool, mod, m_fd1, cls_name_v1, trans,
src_sha1, imported_modules, from_imports,
i32_ty, i8_ptr_ty, funcs_ptr, max_funcs)
i: sizet
for i in range(n):
child: ast.AST* = ch.get(i)
if child is None:
continue
kd: int = child.kind()
# FunctionDef → 提取函数名、参数、返回类型 → 注入 declare
if kd == ast.ASTKind.FunctionDef:
fd: ast.FunctionDef* = ast.FunctionDef*(child)
if fd is None or fd.name is None:
continue
# App 预注册跳过函数 declare(原因见上方 pre_skip_fn_declares 注释):
# 其形参/返回注解可能引用本遍尚未注册的类,resolve 落空会 fail-fast
# 打断多遍收敛(现场 Translator.vpi 的 InjectTracebackInstallCtor
# 形参 rawptr[Translator])。
if pre_skip_fn_declares != 0:
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;
# 普通模块级全局变量(如 tokens._op_head)→ external global declare
if kd == ast.ASTKind.AnnAssign:
aa: ast.AnnAssign* = ast.AnnAssign*(child)
if aa is None:
continue
if aa.target is not None and aa.target.kind() == ast.ASTKind.Name:
dbg_tn: ast.Name* = ast.Name*(aa.target)
if dbg_tn.id is not None and dbg_tn.id[0] != '\0': # 存疑
if string.strcmp(dbg_tn.id, "irq_handler_t") == 0 \
or string.strcmp(dbg_tn.id, "gdt") == 0 \
or string.strcmp(dbg_tn.id, "KBD_BUF_SIZE") == 0:
dbg_ct: int = GenApi._is_ctypedef_annotation(aa.annotation)
dbg_cd: int = GenApi.is_cdefine_annotation(aa.annotation)
if GenApi._is_ctypedef_annotation(aa.annotation) != 0:
_register_ctypedef_from_annassign(pool, aa)
elif GenApi.is_cdefine_annotation(aa.annotation) != 0:
# vpi 里 CDefine 定义(如 keyboard.vp 的 KBD_BUF_SIZE: t.CDefine = 1024)
# 注册到当前文件 CDefine 表,供同 vpi 后续 t.CArray count 引用解析
# (否则跨模块 count 查找 [CDEF-MISS] fail-fast,VKernel PhaseA 根因)
if aa.target is not None and aa.target.kind() == ast.ASTKind.Name \
and aa.value is not None:
cdef_tgt: ast.Name* = ast.Name*(aa.target)
if cdef_tgt.id is not None:
# 字符串 CDefine(版本号等)跳过:extract_cdefine_int_value
# 对字符串常量 fail-fast,且 count 引用只关心整数 CDefine
cdef_is_str: int = 0
cdef_is_float: int = 0
cdef_float_val: double = 0.0
if aa.value.kind() == ast.ASTKind.Constant:
cn_v: ast.Constant* = ast.Constant*(aa.value)
if cn_v.const_kind == ast.CONST_STR:
cdef_is_str = 1
elif cn_v.const_kind == ast.CONST_FLOAT:
cdef_is_float = 1
cdef_float_val = cn_v.float_val
if cdef_is_str == 0 and cdef_is_float != 0:
# 浮点 CDefine(如 FRAGMENTATION_THRESHOLD: t.CDefine = 0.7)
# 用 register_cdefine_double 注册(走整数提取会 fail-fast)
GenApi.register_cdefine_double(pool, cdef_tgt.id, cdef_float_val)
elif cdef_is_str == 0:
cdef_val: int64 = GenApi.extract_cdefine_int_value(aa.value)
GenApi.register_cdefine_constant(pool, cdef_tgt.id, cdef_val)
else:
_inject_global_variable_declare(pool, mod, aa, trans,
stub_imported_modules, stub_from_imports,
i32_ty, i8_ptr_ty, src_sha1)
continue
# ClassDef → 已在第一遍统一注册(含字段 + 方法 declare),此处跳过
# (原单遍顺序处理在类定义位于全局变量之后时,AnnAssign 全局变量
# 解析裸类名找不到类 → CArray 元素解析失败 fatal,见上方第一遍注释)
# 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
# ============================================================
# 依赖 vpi 按需注入(包 __init__ 重导出与子模块 stub 互相等待问题)
#
# LoadDepVpiIntoModule 只按 imported_modules 注入**一层**依赖 vpi、不递归;包 __init__.vpi 自身不含 class 定义(类全部来自 `from .sub import X` 重导出),
# 而重导出注册要求源模块 stub 已注入并以**定义文件 sha1** 注册过 entry ⇒ 两者互相等待 ⇒ 包内类都注册不上 ⇒ 包 stub 中引用这些类的注解裸名解析落空 → fail-fast。
#
# 故在重导出注册发现「源 entry 为空」时,按需把 sub_sha1 的 vpi 注入当前模块再重试注册(注入过程递归触发同一逻辑,依赖闭包自动补齐,与加载顺序无关)。
# 防重入表按 sha1 去重(环直接短路);注入失败不改语义,照原逻辑 return 0,不把可恢复问题升级为 fatal。
# ============================================================
VPI_INJ_MAX: t.CDefine = 1024
_vpi_inj_sha1s: bytes = None
_vpi_inj_count: int = 0
_vpi_inj_depth: int = 0
def ResetVpiInjectVisited() -> None:
"""清空按需注入的防重入表(每个模块的 stub 注入会话开始前调用)
表必须按"模块注入会话"重置:注入目标 mod 每个模块都不同,跨模块沿用
会让第二个模块命中缓存而拿不到本模块需要的 declare。
"""
global _vpi_inj_sha1s, _vpi_inj_count, _vpi_inj_depth
if _vpi_inj_sha1s is not None:
stdlib.free(_vpi_inj_sha1s)
_vpi_inj_sha1s = None
_vpi_inj_count = 0
_vpi_inj_depth = 0
def _ensure_dep_vpi_injected(pool: memhub.MemBuddy*,
mod: llvmlite.LLVMModule*,
dep_sha1: str,
trans: HT.Translator*) -> int:
"""按需注入 dep_sha1 对应的 .vpi 到当前模块(防重入),1=就绪 / 0=失败"""
global _vpi_inj_sha1s, _vpi_inj_count, _vpi_inj_depth
if pool is None or mod is None or dep_sha1 is None or trans is None:
return 0
if dep_sha1[0] == '\0':
return 0
if _vpi_inj_sha1s is None:
_vpi_inj_sha1s = stdlib.malloc(VPI_INJ_MAX * 17)
if _vpi_inj_sha1s is None:
return 0
string.memset(_vpi_inj_sha1s, 0, VPI_INJ_MAX * 17)
_vpi_inj_count = 0
# 已注入过 / 正在注入(环)→ 视为就绪,直接短路
ii: int
for ii in range(_vpi_inj_count):
if string.strcmp(_vpi_inj_sha1s + sizet(ii) * 17, dep_sha1) == 0:
return 1
if _vpi_inj_count >= VPI_INJ_MAX or _vpi_inj_depth >= 32:
return 0
temp_dir: str = GenApi.get_temp_dir()
if temp_dir is None:
return 0
td_len_i: sizet = string.strlen(temp_dir)
sh_len_i: sizet = string.strlen(dep_sha1)
sub_i: str = Config.slice_subdir(dep_sha1, Config.Sha1SliceLevel)
sub_len_i: sizet = 0
if sub_i is not None:
sub_len_i = string.strlen(sub_i)
path_len_i: sizet = td_len_i + sub_len_i + sh_len_i + 8
path_i: bytes = stdlib.malloc(path_len_i)
if path_i is None:
return 0
if sub_len_i > 0:
viperlib.snprintf(path_i, path_len_i, "%s/%s/%s.vpi", temp_dir, sub_i, dep_sha1)
else:
viperlib.snprintf(path_i, path_len_i, "%s/%s.vpi", temp_dir, dep_sha1)
vf_i: rawptr[fileio.File] = fileio.File(path_i, fileio.MODE.R)
if vf_i.closed:
stdlib.free(path_i)
return 0
vf_i.close()
# 先登记再注入:注入过程中的递归遇到同 sha1 直接短路(防环)
my_idx_i: int = _vpi_inj_count
string.strcpy(_vpi_inj_sha1s + sizet(my_idx_i) * 17, dep_sha1)
_vpi_inj_count += 1
_vpi_inj_depth += 1
ret_i: int = ParseVpiIntoModule(pool, mod, path_i, trans)
_vpi_inj_depth -= 1
stdlib.free(path_i)
if ret_i != 1:
# 注入失败:**撤销登记**(把表尾项搬到本槽,保持 LIFO 语义),
# 否则后续对同一 sha1 的请求会被永久短路 → 该类永远注册不上,
# 错误却表现为远处注解解析失败。
_vpi_inj_count -= 1
if my_idx_i != _vpi_inj_count:
string.strcpy(_vpi_inj_sha1s + sizet(my_idx_i) * 17,
_vpi_inj_sha1s + sizet(_vpi_inj_count) * 17)
return 0
return 1
# ============================================================
# _inject_reexport_class_declare - 包重导出类以包 sha1 注册别名 entry
#
# 场景:源码 `import ast` 后写 `ast.Attribute`(模块限定名),查找端按「__init__ 包回退」解析为包 sha1,但 Attribute 定义在 ast/exprs.vp 并以定义文件 sha1 注册
# → find_struct_by_module 的 name+sha1 双精确匹配错位 fail-fast。本函数在处理包 __init__.vpi 的 `from .exprs import Attribute` 重导出行时调用:
# 复用子模块已注册的 entry(Ty/Fields/父类/虚表)以包 sha1 追加别名注册,使 ast.Attribute 按包归属可解析;非类名 find_struct_by_module 查不到自然跳过。
# 仅包 __init__ 触发,避免把普通模块的跨模块 from-import 错误归属到当前模块 sha1。
#
# Args: src_name 子模块中的原类名;local_name 包命名空间中的名字(或 as 别名);sub_sha1 子模块(定义文件)sha1;pkg_sha1 当前包 __init__ 的 sha1;trans 翻译器对象(源 entry 缺失时按需注入子模块 vpi 需要)
# ============================================================
def _inject_reexport_class_declare(pool: memhub.MemBuddy*,
mod: llvmlite.LLVMModule*,
src_name: str,
local_name: str,
sub_sha1: str,
pkg_sha1: str,
trans: HT.Translator*) -> int:
"""把包子模块重导出的类以包 sha1 注册别名 entry(方案A:ast.Attribute 归属 ast 包)"""
if pool is None or mod is None or src_name is None or local_name is None:
return 0
if sub_sha1 is None or sub_sha1[0] == '\0' or pkg_sha1 is None or pkg_sha1[0] == '\0':
return 0
if src_name[0] == '\0' or local_name[0] == '\0':
return 0
# 幂等:包 sha1 下 local_name 已注册则跳过(重复 import 不重复注册)
if GenApi.find_struct_by_module(local_name, pkg_sha1) is not None:
return 1
# 找子模块源 entry(仅类有 entry;函数/常量查不到自然跳过)
src_entry: GenTypes.StructEntry* = GenApi.find_struct_by_module(src_name, sub_sha1)
if src_entry is None or src_entry.Ty is None:
# 源模块 stub 尚未注入(包 __init__ 与子模块互相等待,见
# _ensure_dep_vpi_injected 顶部说明):按需注入 sub_sha1 的 vpi 后
# 重试一次。失败照原逻辑跳过(不把可恢复问题升级为 fatal)。
if _ensure_dep_vpi_injected(pool, mod, sub_sha1, trans) != 0:
src_entry = GenApi.find_struct_by_module(src_name, sub_sha1)
if src_entry is None or src_entry.Ty is None:
return 0
# 注册别名 entry(绕过 register_struct 的 Ty 去重)
if GenApi.register_struct_alias(pool, local_name, pkg_sha1, src_entry) is None:
return 0
GenApi.add_visible_struct(pool, local_name)
return 1
# ============================================================
# _vpi_tattr_name / _vpi_class_has_packed - vpi 类装饰器 packed 判定
#
# vpi 存根保留 @c.Attribute(t.attr.packed) 装饰器(Converter 原样输出)。
# 跨模块 stub 注入 struct 时必须同步 packed 标记(<{...}>),否则引用
# 模块按非 packed 布局 GEP,与定义侧 packed 布局错位(B5)。
# ============================================================
def _vpi_tattr_name(node: ast.AST*, attr_name: str) -> int:
"""从 t.attr.<name> / t.attr.<name>(args) 提取最外层属性名并比较"""
if node is None:
return 0
if node.kind() == ast.ASTKind.Call:
cl2: ast.Call* = ast.Call*(node)
if cl2.func is None:
return 0
return _vpi_tattr_name(cl2.func, attr_name)
if node.kind() == ast.ASTKind.Attribute:
at2: ast.Attribute* = ast.Attribute*(node)
if at2.attr is None:
return 0
return 1 if string.strcmp(at2.attr, attr_name) == 0 else 0
return 0
def _vpi_class_has_packed(decorator_list: list[ast.AST*]*) -> int:
"""检查 vpi ClassDef 装饰器列表是否含 t.attr.packed"""
if decorator_list is None:
return 0
dn: sizet = len(decorator_list)
if dn == 0:
return 0
for di in range(dn):
deco: ast.AST* = decorator_list.get(di)
if deco is None:
continue
if deco.kind() == ast.ASTKind.Call:
cl: ast.Call* = ast.Call*(deco)
if cl.func is None or cl.func.kind() != ast.ASTKind.Attribute:
continue
at0: ast.Attribute* = ast.Attribute*(cl.func)
if at0.attr is None or string.strcmp(at0.attr, "Attribute") != 0:
continue
if cl.args is None:
continue
an: sizet = len(cl.args)
for ai in range(an):
ag: ast.AST* = cl.args.get(ai)
if ag is not None and _vpi_tattr_name(ag, "packed") != 0:
return 1
else:
if _vpi_tattr_name(deco, "packed") != 0:
return 1
return 0
def _vpi_class_is_renum(class_cd: ast.ClassDef*) -> int:
"""检查 vpi ClassDef 是否为 REnum(基类含 t.REnum),返回 1=是 / 0=否
vpi 文本保留原始基类(`class LLVMType(t.REnum):`),故可据此识别。
REnum 布局 {i32 __tag, <max_variant_payload>} 由 ClassGenEnum.
_translate_renum_def 在正式翻译时按变体计算;vpi 侧按普通 struct 注册
会得到错误布局(变体字段都在嵌套类里,直接 AnnAssign 扫描得到 0 字段)。
"""
if class_cd is None or class_cd.bases is None:
return 0
bases: list[ast.AST*]* = class_cd.bases
bn: sizet = len(bases)
for bi in range(bn):
base_node: ast.AST* = bases.get(bi)
if base_node is None:
continue
bk: int = base_node.kind()
# Attribute(t.REnum) 形式
if bk == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(base_node)
if at.attr is not None and string.strcmp(at.attr, "REnum") == 0:
return 1
# Name(REnum) 形式(from t import REnum)
elif bk == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(base_node)
if nm.id is not None and string.strcmp(nm.id, "REnum") == 0:
return 1
return 0
# ============================================================
# _inject_class_struct_declare - 从 vpi ClassDef 注册类为 struct 类型
#
# vpi 注入若只注入类内方法 declare 而不注册类本身,则严格模式下同一 vpi 内后续 AnnAssign 的裸类名注解(如 CArray 的元素 gdt_entry)is_struct_visible=0
# → resolve_annotation_type 返回 None → CArray 元素解析失败 fatal。故此处补齐源模块编译时 ClassGenDef.translate_class_def 的注册动作:
# 1) 已注册(find_struct_by_module 按 name+sha1 命中)→ 仅 add_visible_struct 标记当前文件可见;
# 2) 未注册 → 解析字段类型 → 创建命名 StructType("sha1.ClassName") → register_struct + add_visible_struct。
# 字段解析失败不 fatal(不注册该类,留给源模块编译注册,后续裸类名引用由调用侧 fail-fast 暴露)。
# ============================================================
def _inject_class_struct_declare(pool: memhub.MemBuddy*,
mod: llvmlite.LLVMModule*,
class_cd: ast.ClassDef*,
cls_name: str,
trans: HT.Translator*,
src_sha1: str,
imported_modules: str,
from_imports: str,
name_prefix: str = None) -> int:
"""从 vpi ClassDef 注册类为 struct 类型(含字段),使同 vpi 内裸类名注解可解析
name_prefix:非 None 表示"本类是 prefix 类的嵌套类",注册名取扁平名
`{prefix}_{cls_name}`(多层嵌套自然递归为 A_B_C)——与定义端
ClassGenDef._translate_nested_classdefs 的命名口径逐字一致。
"""
if pool is None or mod is None or class_cd is None or cls_name is None:
return 0
# 扁平名(嵌套类):`{外类}_{内类}`。父类未注册时也照常登记(本函数自带
# "字段解析失败即不注册"的既有口径,交给多遍收敛)。
real_name: str = cls_name
if name_prefix is not None and name_prefix[0] != '\0':
p_len: sizet = string.strlen(name_prefix)
c_len: sizet = string.strlen(cls_name)
flat_buf_c: str = pool.alloc(p_len + 1 + c_len + 1)
if flat_buf_c is not None:
string.strcpy(flat_buf_c, name_prefix)
flat_buf_c[p_len] = '_'
string.strcpy(flat_buf_c + p_len + 1, cls_name)
real_name = flat_buf_c
# REPL(ReplStubLayoutMode,见文件末"REPL:includes 类完整布局与虚表恢复"):
# 本类若已由 .stub.ll 驱动的路径接管 ⇒ 这里整类让位——已登记(或本遍就地
# 登记成功)只标可见;父类未就绪 ⇒ 本遍不登记(收尾遍补齐);布局不可信 ⇒
# 不登记(片段侧字段访问明确失败,绝不按错偏移静默访问)。
# State=EMPTY 的类(无父类/无虚表/字段类型不可解析)不在此列,仍走下方既有口径。
if ReplStubLayoutMode != 0:
se: rawptr[ReplStubClass] = _repl_stub_find(
_repl_stub_key(pool, src_sha1, real_name))
if se is not None and se.State != _REPL_SS_EMPTY:
if se.State == _REPL_SS_PENDING:
_repl_stub_try(se, mod)
if se.State == _REPL_SS_OK:
GenApi.add_visible_struct(pool, real_name)
return 1
# 1. 已注册检查(含源模块编译时注册的 entry):仅标记可见,避免重复注册。
# 例外:**前向声明占位**(IsForwardDecl != 0)不早退——必须走下方注册路径,
# 并用「占位 Ty 原地写入」把字段体填进**同一个 Ty 对象**(见下方 fill_fwd_c 段)。
# existing_c/fill_fwd_c 声明在 if 外:下方填充段要跨块读取。
existing_c: GenTypes.StructEntry* = None
fill_fwd_c: int = 0
if src_sha1 is not None and src_sha1[0] != '\0':
existing_c = GenApi.find_struct_by_module(real_name, src_sha1)
if existing_c is not None and existing_c.Ty is not None:
if existing_c.IsForwardDecl != 0:
fill_fwd_c = 1
else:
GenApi.add_visible_struct(pool, real_name)
return 1
# REnum 类(class X(t.REnum))跳过:布局 {i32 __tag, <payload>} 由
# ClassGenEnum._translate_renum_def 按变体计算;vpi 侧按普通 struct 注册
# 会得到错误布局(变体字段都在嵌套类里,直接 AnnAssign 扫描得 0 字段)。
# 留给源模块正式翻译注册。LLVMType 本就因 field_count==0 早退,此守卫把
# 该行为显式化,避免将来 REnum 带直接字段时被误注册为普通 struct。
if _vpi_class_is_renum(class_cd) != 0:
return 0
# 规则 5.5 的证据前置(**必须早于**下方字段注解解析):
# 本函数在**注入期**解析字段注解(GenApi.resolve_annotation_type →
# _norm_type_arg_name),此刻被注入的类尚未 register_struct,且注入期**没有
# "类定义窗口"**(CurDefModuleSha1=None)⇒ 规则 5.5 的入口闸门
# (`_def_sha1 is None and _def_mod is not None`)整条关闭、四条证据一条都查不到
# ⇒ 注解里的裸类名实参取不到定义模块 SHA1(如 `children: list[rawptr[AST]]`
# 里的 AST)⇒ 特化名被冻结成**裸名版本**,与该类在自身翻译期(类已注册)解析出的
# 带前缀形式分裂成两个特化名,最终以"非基本类型指针不可隐式转换"报出。现场:
# includes/ast/base.vp:359 `_append_child(self.children, node, self.pool)`
# 源 %"…list[rawptr[AST]]"* vs 目标 %"…list[rawptr<定义模块SHA1>.AST]"*。
# mark_module_class 一次调用同时完成两件事:
# ① 登记"{src_sha1}.{real_name}"(名字归属,与 struct 注册状态无关);
# ② 把 CurDefModuleSha1/CurDefClassName 指向本次注入的模块与类(规则 5.5 的闸门)。
GenApi.mark_module_class(pool, src_sha1, real_name)
# 1.5 嵌套类 / 嵌套 struct:vpi 里的嵌套类是**缩进 ClassDef**(Converter 原样写出,
# 见 Converter._PyiClass 的嵌套类分支),定义端以扁平名 `{外类}_{内类}` 发射
# (ClassGenDef._translate_nested_classdefs)⇒ 此处按同一命名**先**递归登记。
# 必须早于下方字段注解解析:外层字段可能写成 `外类.内类`,只有扁平名条目已在
# 结构体表里,TypeGenResolve._resolve_nested_class_type 才解析得出类型。
_inject_nested_class_declares(pool, mod, class_cd, real_name, trans,
src_sha1, imported_modules, from_imports)
# 还原"正在定义类"窗口:嵌套递归内的 mark_module_class 会把 CurDefClassName 改成
# 嵌套类名(A_B)。不还原会让外层字段注解的规则 5.5 自引用证据判错(裸类名票名冻结
# 成裸名版本、与定义侧带前缀形式分裂),也会让 `外类.内类` 的守卫①(CurDefClassName
# == 外类名)失效。
GenApi.mark_module_class(pool, src_sha1, real_name)
# 2. 收集字段类型(AnnAssign 子节点,仿 ClassGenDef.translate_class_def)
field_names_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX)
field_types_buf: sizet* = pool.alloc(8 * GenTypes.FIELD_MAX)
if field_names_buf is None or field_types_buf is None:
return 0
string.memset(field_names_buf, 0, 8 * GenTypes.FIELD_MAX)
string.memset(field_types_buf, 0, 8 * GenTypes.FIELD_MAX)
field_count: int = 0
cls_children_c: list[ast.AST*]* = class_cd.children
if cls_children_c is not None:
cn_c: sizet = len(cls_children_c)
for ci_c in range(cn_c):
m_child_c: ast.AST* = cls_children_c.get(ci_c)
if m_child_c is None:
continue
if m_child_c.kind() != ast.ASTKind.AnnAssign:
continue
m_aa_c: ast.AnnAssign* = ast.AnnAssign*(m_child_c)
if m_aa_c is None or m_aa_c.target is None:
continue
if m_aa_c.target.kind() != ast.ASTKind.Name:
continue
m_tgt_c: ast.Name* = ast.Name*(m_aa_c.target)
if m_tgt_c.id is None:
continue
field_ty_c: llvmlite.LLVMType* = GenApi.resolve_annotation_type(
pool, m_aa_c.annotation, imported_modules, from_imports, trans, 0)
if field_ty_c is None:
# 字段类型解析失败:不注册该类(留给源模块编译注册),不 fatal
return 0
if field_count < GenTypes.FIELD_MAX:
field_names_buf[field_count] = sizet(m_tgt_c.id)
field_types_buf[field_count] = sizet(field_ty_c)
field_count += 1
# 3. vpi 存根不含类字段(只序列化 class 名 + def 签名)→ field_count 恒为 0;注册空类会让源模块编译时 find_struct_by_module 命中空类
# → "结构体已注册但字段数为0" InternalCompilerError。故改为不注册:依赖模块已编译时命中真实类(上方已处理),未编译时由 _deps_pending 多遍收敛。
if field_count == 0:
return 0
# 4. 构建 ParamNode 链表
first_node_c: rawptr[llvmlite.ParamNode] = None
prev_node_c: rawptr[llvmlite.ParamNode] = None
for fi_c in range(field_count):
fty_c: llvmlite.LLVMType* = (llvmlite.LLVMType*)(field_types_buf[fi_c])
pnode_c: rawptr[llvmlite.ParamNode] = pool.alloc(llvmlite.ParamNode.__sizeof__())
if pnode_c is None:
continue
string.memset(pnode_c, 0, llvmlite.ParamNode.__sizeof__())
pnode_c.Ty = fty_c
if first_node_c is None:
first_node_c = pnode_c
if prev_node_c is not None:
prev_node_c.Next = pnode_c
prev_node_c = pnode_c
# 4. 命名结构体类型名 "sha1.ClassName"(与定义侧 ClassGenDef 一致)。
# 嵌套类用扁平名 real_name(`外类_内类`),与定义端
# ClassGenDef._translate_nested_classdefs 发射的 "sha1.外类_内类" 逐字一致。
type_name_c: str = real_name
if src_sha1 is not None and src_sha1[0] != '\0':
# [D7·截断纪律] 旧实现固定 64 缓冲 + snprintf("%s.%s"):sha1(16)+'.' 占 17
# 字节,类名超过 46 字符即被**静默截断** ⇒ 命名结构体名与定义侧
# ClassGenDef 的 "sha1.ClassName" 不一致 → find_struct_by_module /
# lookup_field_by_class 查不到(误判"未注册")。改为精确长度分配。
sh_len_c: sizet = string.strlen(src_sha1)
cn_len_c: sizet = string.strlen(real_name)
name_buf_c: str = pool.alloc(sh_len_c + 1 + cn_len_c + 1)
if name_buf_c is not None:
string.strcpy(name_buf_c, src_sha1)
name_buf_c[sh_len_c] = '.'
string.strcpy(name_buf_c + sh_len_c + 1, real_name)
type_name_c = name_buf_c
struct_ty_c: llvmlite.LLVMType* = llvmlite.Struct(
pool, first_node_c, field_count, type_name_c)
if struct_ty_c is None:
return 0
# 5. StdlibAlloc(Phase B)时深拷贝到 perm_pool,跨文件 reset 存活
# (对齐 ClassGenDef.translate_class_def L489-494)
if GenApi.get_stdlib_alloc() != 0:
st_copy_c: llvmlite.LLVMType* = llvmlite.DeepCopyType(
CompilerFactory.factory.PermPool, struct_ty_c)
if st_copy_c is not None:
struct_ty_c = st_copy_c
# 5.5 前向声明占位命中 ⇒ **占位 Ty 原地写入**(与 ClassGenDef.translate_class_def
# 的 fill_fwd 段逐句同款):给已存在的类型对象 `*existing.Ty` 赋入刚构建的
# 字段体,并让 struct_ty_c 指向**同一个 Ty**。
# 这是"不新建条目"的唯一正确做法:若此处让 struct_ty_c 保持新对象,下方
# register_struct 按 **Ty 指针**去重就会 miss ⇒ 每一遍注入都追加一个新条目
# ⇒ StructTable 爆表(现场:table full, cannot register Translator)。
# 必须早于 module_add_named_type:避免同一名字先以占位名注册、又以新对象重复注册。
if fill_fwd_c != 0 and existing_c is not None and existing_c.Ty is not None:
fwd_name_c: str = llvmlite.get_struct_name(existing_c.Ty)
fwd_fields_c: rawptr[llvmlite.ParamNode] = llvmlite.get_struct_fields(struct_ty_c)
fwd_fc_c: int = llvmlite.get_struct_fcount(struct_ty_c)
*existing_c.Ty = llvmlite.LLVMType.Struct(fwd_fields_c, fwd_fc_c, fwd_name_c)
struct_ty_c = existing_c.Ty
type_name_c = fwd_name_c
existing_c.IsForwardDecl = 0
existing_c.FieldCount = 0
# 6. 注册到当前模块(输出类型定义行)+ 全局 StructTable + 可见性
llvmlite.module_add_named_type(mod, pool, struct_ty_c)
# B5:vpi 保留 @c.Attribute(t.attr.packed) 装饰器,stub 注入的跨模块
# struct 必须同样 packed(<{...}>),否则引用模块 GEP 按非 packed 布局
# 算偏移,与定义侧 packed 布局错位(gdt/elf/fat32 二进制格式字段错读)。
if _vpi_class_has_packed(class_cd.decorator_list) != 0:
llvmlite.module_set_named_type_packed(mod, pool, struct_ty_c, 1)
# 注册名用扁平名 real_name:跨模块引用 `外类.内类` 时 TypeGenResolve 查的是
# 扁平名条目(与定义端 StructGen 的登记名同源)。
entry_c: GenTypes.StructEntry* = GenApi.register_struct(
pool, real_name, struct_ty_c, src_sha1)
if entry_c is None:
return 0
# B5:vpi 注入注册的 entry 同步 IsPacked——后续跨模块 stub 注入
# (ensure_struct_def_in_module)按 entry.IsPacked 判定 packed,否则读 0
# → 注入的跨模块 struct 非 packed(GEP 布局错位)。
if _vpi_class_has_packed(class_cd.decorator_list) != 0:
entry_c.IsPacked = 1
# vpi 注入类时同步注册字段到 StructEntry 字段表(名字+类型):仅设 Ty 而字段表空(FieldCount=0)会让 lookup_field/lookup_field_by_class 按名查字段失败
# → 属性访问走 _deps_pending → includes 无重译 → 失败版本落盘恒 0(如 File.size 的 li.QuadPart 恒 0 → read_all=0)。
# add_field 内部按 StdlibAlloc 复制字段名到 perm_pool,跨 reset 存活。
afi_c: int = 0
for afi_c in range(field_count):
af_name_c: str = str(field_names_buf[afi_c])
af_ty_c: llvmlite.LLVMType* = (llvmlite.LLVMType*)(field_types_buf[afi_c])
GenApi.add_field(pool, entry_c, af_name_c, af_ty_c)
GenApi.add_visible_struct(pool, real_name)
return 1
# ============================================================
# _inject_nested_class_declares - 递归登记 vpi 类里的嵌套类
#
# vpi 里的嵌套类是缩进的 ClassDef(Converter._PyiClass 原样写出),定义端以
# 扁平名 `{外类}_{内类}` 发射(ClassGenDef._translate_nested_classdefs)⇒ 注入侧
# 必须按同一命名登记,跨模块引用 `外类.内类` 才解析得出类型(否则与 TPC 侧
# stub.ll 的递归声明不对齐,引用模块拿到 None)。
#
# 多层嵌套靠递归自然展开(A.B.C → prefix=A_B、cls=C ⇒ A_B_C)。这里**不**过滤
# REnum / 空字段:把判定统一交给 _inject_class_struct_declare 既有的守卫
# (REnum 跳过、field_count==0 不注册、已注册只标可见)。
# 每个内层类登记后,_inject_class_struct_declare 会把自己的
# mark_module_class 窗口留在嵌套类上;故外层调用点在其后**再调一次**
# mark_module_class 还原(见 _inject_class_struct_declare 内注释)。
# ============================================================
def _inject_nested_class_declares(pool: memhub.MemBuddy*,
mod: llvmlite.LLVMModule*,
class_cd: ast.ClassDef*,
prefix_name: str,
trans: HT.Translator*,
src_sha1: str,
imported_modules: str,
from_imports: str) -> None:
"""递归登记 class_cd 的所有嵌套 ClassDef 为扁平名类型(前缀 = prefix_name)"""
if pool is None or mod is None or class_cd is None or prefix_name is None:
return
children: list[ast.AST*]* = class_cd.children
if children is None:
return
cn: sizet = len(children)
for ci in range(cn):
child_ast: ast.AST* = children.get(ci)
if child_ast is None:
continue
if child_ast.kind() != ast.ASTKind.ClassDef:
continue
child_cd: ast.ClassDef* = ast.ClassDef*(child_ast)
if child_cd is None or child_cd.name is None or child_cd.name[0] == '\0':
continue
_inject_class_struct_declare(pool, mod, child_cd, child_cd.name, trans,
src_sha1, imported_modules, from_imports,
prefix_name)
# ============================================================
# _ensure_full_value_types - 跨模块「值类型」引用注册完整定义
#
# opaque 不足以支撑 llc:值 struct global(@_per_cpu = global %per_cpu_data)、[N x opaque] 数组(@gdt = global [7 x %gdt_entry])、值 struct 返回类型
# 均报 "use of undefined type"(值类型大小需已知)。vpi 注入类已 add_field 含字段(完整 Ty),在注册表可查时注册完整定义;
# 指针引用(Ptr(Struct))无需——_scan_type_for_cross_module_refs 的 Ptr 分支添加 opaque 即可。
# ============================================================
def _ensure_full_value_types(mod: llvmlite.LLVMModule*,
pool: memhub.MemBuddy*,
ty: llvmlite.LLVMType*) -> None:
"""递归为值 struct / 数组元素 struct 注册完整定义(递归深度守卫)"""
if mod is None or pool is None or ty is None: return
match ty:
case llvmlite.LLVMType.Struct(_, _, _):
full_se_h: GenTypes.StructEntry* = GenApi.find_struct_by_type(ty)
if full_se_h is not None and full_se_h.Ty is not None:
llvmlite.module_add_named_type(mod, pool, full_se_h.Ty)
# B5:值类型注入同步 packed 标记(对齐 ensure_struct_def_in_module)——
# vpi 全局变量(@_per_cpu = global %per_cpu_data)/值返回类型注入完整
# 定义时若只 add 不 set_packed → 引用模块节点 IsPacked=0 → 输出 {…}
# 非 packed(GEP 与定义侧 packed 布局错位,stub packed 失效根因)。
if full_se_h.IsPacked != 0:
llvmlite.module_set_named_type_packed(mod, pool, full_se_h.Ty, 1)
case llvmlite.LLVMType.Array(aelem_h, _):
if aelem_h is not None:
_ensure_full_value_types(mod, pool, aelem_h)
case _:
pass
# ============================================================
# _AggregateParamTy - 裸结构体(聚合)形参类型统一判据
#
# 「裸结构体形参」(注解是结构体/类名、无 `| t.CPtr`)的 LLVM 形参类型判据,三处必须同步(同一文字、同一结果):
# 1) 定义侧 FuncGenDef.forward_declare_functions / translate_function_def 的 extern 声明分支;
# 2) declare 注入侧 _inject_function_declare / _inject_method_declare(本函数);
# 3) 调用侧:per-函数 found_func 走 ExprGenBase.coerce_to_type(指针 → 非指针值 build_load 解引用);跨模块名走 CallGenLib._coerce_args_to_def_params(按本判据得出的形参类型 coerce)。
# 规则(对齐 TPC「仅 IsStruct 吸收 1 层 CPtr」+「CStruct → 具名结构体值」):
# * C ABI 外部声明(t.CExtern/t.State,本模块无 define 侧)→ 按值 %Struct:真实被调方在 DLL/CRT,形参按 C ABI 传值;
# 值参数要求具名类型有**完整定义**(opaque/未定义具名类型作值参数 ⇒ llc "invalid type for function argument")。判据 = find_struct_by_type 命中且非 opaque:
# 命中 → 先注册完整定义(module_add_named_type)再按值;真 opaque/未注册 → 降级 i64(不引用任何具名类型,恒合法;
# Windows x64 ABI 小结构体(≤8 字节)按整数寄存器传、位兼容,且与定义侧同样降级 ⇒ 同符号签名仍逐字一致)。
# * TPV 内部函数/方法(存在 define 侧)→ Ptr(struct):与定义侧逐字一致,指针目标只需 opaque 声明(module_ensure_opaque_for_type)。
# ============================================================
def _AggregateParamTy(mod: llvmlite.LLVMModule*,
pool: memhub.MemBuddy*,
ty: llvmlite.LLVMType*,
is_c_abi: int) -> llvmlite.LLVMType*:
"""裸结构体形参类型(C ABI 按值 / 内部 Ptr(struct),见上方注释)"""
if mod is None or pool is None or ty is None:
return ty
if is_c_abi != 0:
# 真 opaque 哨兵:fcount < 0(includes/llvmlite/__module.vp L491)
is_opaque_ap: int = 0
match ty:
case llvmlite.LLVMType.Struct(_, fcount_ap, _):
if fcount_ap < 0:
is_opaque_ap = 1
case _:
pass
if is_opaque_ap == 0 and GenApi.find_struct_by_type(ty) is not None:
_ensure_full_value_types(mod, pool, ty)
return ty
return llvmlite.Int64(pool)
ptr_ty_ap: llvmlite.LLVMType* = llvmlite.Ptr(pool, ty)
llvmlite.module_ensure_opaque_for_type(mod, pool, ptr_ty_ap)
return ptr_ty_ap
# ============================================================
# _inject_global_variable_declare - 从 vpi AnnAssign 创建模块级全局变量 declare
#
# vpi 里模块级全局变量(如 tokens.vp 的 _op_head: OpEntry | t.CPtr = None)需在当前模块创建 external global 声明(短名 @name,COFF 按名链接),
# 否则跨模块 from-import 读取全局变量时 find_global_in_module 找不到 → 误杀「跨模块全局变量类型未知(stub 未注入)」。
# ============================================================
def _inject_global_variable_declare(pool: memhub.MemBuddy*,
mod: llvmlite.LLVMModule*,
aa: ast.AnnAssign*,
trans: HT.Translator*,
imported_modules: str,
from_imports: str,
i32_ty: llvmlite.LLVMType*,
i8_ptr_ty: llvmlite.LLVMType*,
src_sha1: str) -> None:
"""从 vpi AnnAssign 创建模块级全局变量 external declare(mangled 名 @<src_sha1>.<name>)"""
if pool is None or mod is None or aa is None or trans is None:
return
tgt_v: ast.AST* = aa.target
if tgt_v is None or tgt_v.kind() != ast.ASTKind.Name:
return
nm_v: ast.Name* = ast.Name*(tgt_v)
if nm_v is None or nm_v.id is None:
return
gname_v: str = nm_v.id
if gname_v[0] == '\0':
return
# mangled 名:带源模块 SHA1 前缀(对齐定义端 5f7a873 的 @<sha1>.<name>
# 与查找端 from-import 精确查找三方一致;裸短名会退化为未定义)。
# 注:a42923c 曾把此处写成未定义变量 gv_name_m(笔误,mangle 未实现),
# 导致注入空名 @ = external global → llc "expected top-level entity"。
gv_name_m: str = gname_v
# CExport 全局:定义端用裸名(@gp),stub 声明必须同步裸名,否则跨模块
# 引用生成 @sha1.gp → 与定义端 @gp 不匹配 → ld.lld undefined symbol
# (gdt.vp gdt/gp、idt.vp idt、sched.vp _sched_ptr/_switch_*_rsp、
# coroutine.vp _coro_*、process.vp _saved_cr3 等 CExport 全局根因)。
is_gv_cexport: int = 0
if aa.annotation is not None:
is_gv_cexport = GenApi.has_decorator_marker(aa.annotation, "CExport")
if src_sha1 is not None and src_sha1[0] != '\0' and is_gv_cexport == 0:
ml_v: sizet = string.strlen(src_sha1) + 1 + string.strlen(gname_v) + 1
gv_name_m = pool.alloc(ml_v)
if gv_name_m is not None:
viperlib.snprintf(gv_name_m, ml_v, "%s.%s", src_sha1, gname_v)
# 去重:遍历模块全局变量链表(mangled 名匹配)
cur_v: llvmlite.GlobalVariable* = mod.GlobalHead
while cur_v is not None:
if cur_v.Name is not None and string.strcmp(cur_v.Name, gv_name_m) == 0:
return
cur_v = cur_v.Next
# 解析注解类型(裸类 → Ptr(struct),CArray → 元素指针)
resolved_v: llvmlite.LLVMType* = None
if aa.annotation is not None:
resolved_v = GenApi.resolve_annotation_type(
pool, aa.annotation, imported_modules, from_imports, trans)
if resolved_v is None:
return # 类型解析失败跳过:调用侧有 fail-fast 定位
gv_ty_v: llvmlite.LLVMType* = resolved_v
match resolved_v:
case llvmlite.LLVMType.Struct(_, _, _):
# 对象类(@t.Object/@t.CVTable:对象即指针,HasNew/HasVTable/IsOOP)→ Ptr(struct),与定义端 handle_module_level_var 一致;
# 纯 t.CStruct 值类(如 pci_device)保持值实例——一律转 Ptr 会把实例前 8 字节当指针解引用(#GP),一律保持值则 @t.Object 类模块级变量按指针访问读错 vtable(#PF)。
gv_entry_v: GenTypes.StructEntry* = GenApi.find_struct_by_type(resolved_v)
if gv_entry_v is not None \
and (gv_entry_v.HasNew != 0 or gv_entry_v.HasVTable != 0 or gv_entry_v.IsOOP != 0):
gv_ty_v = llvmlite.Ptr(pool, resolved_v)
else:
gv_ty_v = resolved_v
case llvmlite.LLVMType.Array(aelem_v, acount_v):
# 定长数组(CArray[T, N]):保留完整数组类型 [N x T],与定义端
# handle_module_level_var 生成的 global [N x T] 一致——此前一律
# 衰减为元素指针 i32*,链接后引用端 load 把数组前 8 字节当指针
# 读 → 垃圾地址 → zdeflate_len_to_symbol 读 NULL/垃圾崩溃
# (zdeflate_len_base 跨模块数组根因)。
if acount_v > 0:
gv_ty_v = resolved_v
else:
# 不定长数组(CArray[T, None]):元素指针(C 数组衰减)
gv_ty_v = llvmlite.Ptr(pool, aelem_v)
case _:
gv_ty_v = resolved_v
# 确保跨模块结构体类型在模块中有 opaque 声明:external global 类型
# (如 [7 x %gdt_entry])内嵌 struct 无任何定义时,llc 报 "use of
# undefined type named 'sha1.gdt_entry'"(fpu/fat32 合并 IR 根因)。
# 对齐 TranslatorGenMixin 创建全局前的 module_ensure_opaque_for_type
# (_scan_type_for_cross_module_refs 递归 Array/Struct/Ptr)。
# 值类型(struct 值 / 数组元素 struct)注册完整定义(见 _ensure_full_value_types)
_ensure_full_value_types(mod, pool, gv_ty_v)
llvmlite.module_ensure_opaque_for_type(mod, pool, gv_ty_v)
gv_v: llvmlite.GlobalVariable* = llvmlite.new_global_variable(pool, gv_name_m, gv_ty_v)
if gv_v is None:
return
llvmlite.module_add_global(mod, gv_v)
gv_v.Linkage = "external"
# ============================================================
# _inject_function_declare - 为单个 FunctionDef 创建 declare 声明
#
# 检测 CExport/CExtern/State 标记,计算 mangled name,
# 用 find_func_in_module 去重,创建 declare 并添加参数,
# 注册到函数表。
# ============================================================
def _inject_function_declare(pool: memhub.MemBuddy*,
mod: llvmlite.LLVMModule*,
fd: ast.FunctionDef*,
trans: HT.Translator*,
src_sha1: str,
imported_modules: str,
from_imports: str,
i32_ty: llvmlite.LLVMType*,
i8_ptr_ty: llvmlite.LLVMType*,
funcs_ptr: GenTypes.FuncEntry*,
max_funcs: int) -> None:
"""为 FunctionDef 创建 declare 声明并注入到模块"""
if fd.name is None:
return
# 自注入守卫(仅全量翻译阶段 _declare_only == 0):本模块自己定义的顶层函数,
# 其 define 由 FuncGenDef.forward_declare_functions + translate_function_def
# 负责发射;经依赖链把**本模块自己的** vpi 反向注入(循环 import)会在同一模块
# 里制造同名 declare,且注入侧类型推断与定义端可能不一致。
# 现场:json/__parser.vp ↔ json/__init__.vp 循环 import,__parser 编译时注入的
# ParseState.__init__ 取 i32(vpi 无返回注解 → i32 默认),而定义端是 void
# → llc: value doesn't match function result type 'i32'。
if trans is not None and trans._declare_only == 0 and trans.ModuleSha1 is not None:
if string.strcmp(src_sha1, trans.ModuleSha1) == 0:
return
# 检测 CExtern/State/CExport 标记(从返回类型注解)
has_extern: int = 0
has_state: int = 0
has_export: int = 0
if fd.returns is not None:
has_extern = GenApi.has_decorator_marker(fd.returns, "CExtern")
has_state = GenApi.has_decorator_marker(fd.returns, "State")
has_export = GenApi.has_decorator_marker(fd.returns, "CExport")
# 裸结构体形参判据(见 _AggregateParamTy):C ABI 外部声明(t.CExtern/
# t.State,本模块无 define 侧)按值;其余(含 t.CExport,其 define 侧在
# 本树内)保持 Ptr(struct)。与定义侧 FuncGenDef is_extern_decl 同源,
# 保证注入侧 declare 与定义侧 define 同口径(三处必须同步)。
agg_is_c_abi: int = 0
if has_extern != 0 or has_state != 0:
agg_is_c_abi = 1
# 计算 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 = GenApi._mangle_name_with_sha1(src_sha1, fd.name)
# 本文件自己的裸名声明优先:extern/state/export 声明的符号**不带 SHA1 前缀**
# (与 FuncGenDef.vp:172-173 同口径),若本文件自己也声明了同名符号,先注入的
# 依赖版本会占用该名字 → forward_declare_functions 见名已存在即 continue
# (FuncGenDef.vp:178-182)→ 本文件自己的签名被静默丢弃,调用点改按依赖签名
# coerce。现场:includes/socket.vp 的
# `extern def connect(fd: u64, addr: SocketAddr | t.CPtr, addrlen: INT)`
# 被 w32.winsock2 的 `connect(..., WSASocketAddr | t.CPtr, ...)` 顶掉 →
# socket.vp:236 `connect(self.fd, c.Addr(addr), 16)` 报
# 「指针隐式转指针已禁止:SocketAddr* → WSASocketAddr*」。
# 判据只用**裸名**:带 SHA1 前缀的依赖函数名与本文件同名声明不会相互顶替。
if use_bare_name != 0 and GenApi.is_file_func(fd.name) != 0:
return
# 去重:如果函数已存在则跳过
existing: llvmlite.Function* = GenApi.find_func_in_module(mod, mangled_name)
if existing is not None:
return
# 也检查裸名(非裸名模式时,CExport 函数可能已通过其他途径注册)
if use_bare_name == 0:
existing_bare: llvmlite.Function* = GenApi.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* = 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 = GenApi.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:
# t.State/t.CExtern 是存储类装饰器标记,非真实返回类型。
# 注解 "HANDLE | t.State"(FindFirstFileA)含真实类型 → 不能归 Void
# (此前无条件 has_state→Void,extern declare 返回 void,运行期
# FindFirstFileA 句柄丢失 → 文件存在性判断失效根因)。
# 仅纯标记注解(t.State/t.CExtern/t.CExport 单独出现)才返回 Void。
is_pure_marker_rt: int = 0
rk2_rt: int = fd.returns.kind()
if rk2_rt == ast.ASTKind.Name or rk2_rt == ast.ASTKind.Constant:
is_pure_marker_rt = 1
elif rk2_rt == ast.ASTKind.Attribute:
pm_at: ast.Attribute* = ast.Attribute*(fd.returns)
if pm_at.attr is not None and (string.strcmp(pm_at.attr, "State") == 0
or string.strcmp(pm_at.attr, "CExtern") == 0
or string.strcmp(pm_at.attr, "CExport") == 0):
is_pure_marker_rt = 1
if is_pure_marker_rt != 0:
ret_ty = llvmlite.Void(pool)
elif has_state != 0 or has_extern != 0 or has_export != 0:
# 含真实类型但解析失败:回退外部函数返回类型白名单
# (FindFirstFileA/GetStdHandle → i8* HANDLE 等),避免默认 i32
# 截断指针(vpi 缓存/AST 解析 miss 时的稳健兜底)。
ret_ty = GenApi.infer_external_func_ret_ty(pool, fd.name)
# [S4 fail-fast] 有返回注解却解析不出类型 → 不再静默退 i32;
# 无注解(fd.returns is None)是合法默认,保持下方 i32。
if ret_ty is None and fd.returns is not None:
GenApi.fatal_error(
fd.returns, f"extern 声明 '{fd.name}' 返回类型注解解析失败",
"NameError")
# 默认返回 i32
if ret_ty is None:
ret_ty = i32_ty
# 值 struct 返回类型(如 gfx.vp Mat4_Identity -> Mat4)需要完整定义:
# declare %Mat4 opaque → llc "use of undefined type"。注册完整定义。
_ensure_full_value_types(mod, pool, ret_ty)
llvmlite.module_ensure_opaque_for_type(mod, pool, ret_ty)
# 创建 declare
func: llvmlite.Function* = llvmlite.create_declare(
pool, mod, mangled_name, ret_ty)
if func is None:
return
# 提取参数并添加到 declare
param_count: int = 0
fd_defaults: t.CPtr = None
fd_default_count: int = 0
if fd.args is not None:
args_node: ast.Arguments* = ast.Arguments*(fd.args)
if args_node.args is not None:
args_list: list[ast.AST*]* = args_node.args
param_count = len(args_list)
# 参数数上限哨兵(fail-fast):vpi tree 深层节点在遍历处理过程中
# 可能被覆盖损坏(见本文件顶部注释),fd.args 悬空时 __len__()
# 返回垃圾大数 → for 循环 add_param 海量次 → Params 链超长,
# dump 时步数哨兵误报"链表成环"。超限立即报错定位。
if param_count < 0 or param_count > 128:
stdio.printf(
"[VPIS] 参数数异常: 函数名=%s param_count=%d(fd.args=%llu,tree 可能被覆盖,终止)\n",
fd.name, param_count, uint64(fd.args))
# 终止点收敛:REPL 会话内 longjmp 回 REPL 循环(诊断已打印)
VLogger.repl_abort()
pi: sizet
for pi in range(param_count):
ag: ast.Arg* = ast.Arg*(args_list.get(pi))
if ag is None:
continue
pname: str = ag.arg
if pname is None:
pname = "arg"
# 解析参数类型
param_ty: llvmlite.LLVMType* = i8_ptr_ty
# 优先用 vpi 文本注解缓存解析参数类型(与返回类型同源——vpi
# tree 深层节点 kind() 可能损坏,AST resolve 得垃圾类型如
# sizet→i32,跨模块 declare 参数宽错 → 调用侧 trunc 传参、
# callee 按宽类型读栈残留垃圾高位 → 参数巨值崩溃(SlotsBugTest
# 根因)。段索引 = pi(模块级函数无 self,0=首参)。
pseg_ty: llvmlite.LLVMType* = _resolve_vpi_param_annot_seg(
pool, src_sha1, None, fd.name, pi, i32_ty, i8_ptr_ty)
if pseg_ty is not None:
match pseg_ty:
case llvmlite.LLVMType.Struct(_, _, _):
# 裸结构体形参:C ABI 按值 / 内部 Ptr(struct)
# (判据与降级见 _AggregateParamTy,三处必须同步)
param_ty = _AggregateParamTy(mod, pool, pseg_ty, agg_is_c_abi)
case llvmlite.LLVMType.Array(aelem_ty, _):
param_ty = llvmlite.Ptr(pool, aelem_ty)
case _:
param_ty = pseg_ty
elif ag.annotation is not None:
resolved_ty: llvmlite.LLVMType* = GenApi.resolve_annotation_type(
pool, ag.annotation, imported_modules, from_imports, trans)
if resolved_ty is not None:
# CArray 参数 → 元素指针 ty*(C 数组衰减,与编译侧一致)。
# 裸结构体参数 → _AggregateParamTy(同一判据)。
match resolved_ty:
case llvmlite.LLVMType.Struct(_, _, _):
param_ty = _AggregateParamTy(mod, pool, resolved_ty, agg_is_c_abi)
case llvmlite.LLVMType.Array(aelem_ty, _):
param_ty = llvmlite.Ptr(pool, aelem_ty)
case _:
param_ty = resolved_ty
else:
# [S4 fail-fast] 有注解却解析不出类型 → 不再静默退 i8*
# (无注解 ag.annotation is None 是合法默认,保持 i8*)
GenApi.fatal_error(
ag.annotation,
f"extern 声明 '{fd.name}' 形参 '{pname}' 类型注解解析失败",
"NameError")
llvmlite.add_param(pool, func, param_ty, pname)
# 文本缓存校验/修复:vpi tree args 可能被覆盖损坏(尾部默认参数丢失)
# → declare 参数数不足 → 调用点缺参 → 被调函数读栈垃圾。用 vpi 文本
# 缓存的参数注解补全 declare 参数(与 define 参数数一致)。
param_count = _repair_declare_params_from_text(
pool, func, src_sha1, None, fd.name, param_count, i32_ty, i8_ptr_ty)
# *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
# 默认参数:vpi 签名含 '= <default>'(Converter 已输出),提取 defaults
# 列表注册默认参数表——跨模块缺参调用(如 resolve_annotation_type 的
# depth=0)才能按表填充,否则被调函数读到栈垃圾(假报递归过深/崩溃)。
if args_node.defaults is not None:
fd_dlist: list[ast.AST*]* = args_node.defaults
fd_default_count = len(fd_dlist)
if fd_default_count < 0 or fd_default_count > 128:
# vpi tree 深层节点(args.defaults)可能被覆盖损坏,len 返回
# 垃圾大数 → _register_global_defaults 按垃圾大数 alloc + 数组
# 越界写 perm_pool(覆盖当前模块 AST/表,SlotsBugTest 崩溃根因
# 候选)。与 param_count 哨兵(L1493-1497)对齐:垃圾值放弃
# 注册默认值(跨模块缺参调用走零值兜底),阻断越界写。
stdio.printf(
"[VPIS] 默认参数数异常: 函数名=%s default_count=%d(tree 可能被覆盖,跳过默认值注册)\n",
fd.name, fd_default_count)
fd_defaults = None
fd_default_count = 0
else:
fd_defaults = fd_dlist
# 注册到函数表(用裸名 fd.name,不是 mangled_name)
if funcs_ptr is not None:
cur_count: int = trans._func_count
if GenApi.add_func_to_table(funcs_ptr, cur_count, fd.name, func, max_funcs,
fd_defaults, fd_default_count, param_count, None, src_sha1) == 0:
trans._func_count = cur_count + 1
# 补全全局函数表 Spec(参数已全部添加,对齐 FuncGenDef 行为):
# vpi 注入路径若不补全,跨模块 vtable 生成 find_func_entry_global()
# 时 Spec 为空 → "vtable 方法找不到函数类型"(依赖模块晚于本模块编译时必现)
GenApi.update_global_func_spec(fd.name, func)
# ============================================================
# _inject_method_declare - 为 ClassDef 内的方法创建 declare
#
# 方法混淆名 {src_sha1}.{ClassName}.{method};参数类型从 vpi 注解解析(self 无注解时默认为 i8*)。
# 注入正确签名可避免调用时 _ensure_method_declare 用实参类型创建错误 declare(如 size: sizet 被写成 i32 而非 i64)。
# ============================================================
def _inject_method_declare(pool: memhub.MemBuddy*,
mod: llvmlite.LLVMModule*,
m_fd: ast.FunctionDef*,
cls_name: str,
trans: HT.Translator*,
src_sha1: str,
imported_modules: str,
from_imports: str,
i32_ty: llvmlite.LLVMType*,
i8_ptr_ty: llvmlite.LLVMType*,
funcs_ptr: GenTypes.FuncEntry*,
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
# 自注入守卫(仅全量翻译阶段 _declare_only == 0):本模块自己定义的类,其方法
# define 由 ClassGenVTable._translate_oop_methods 发射;把本模块自己的 vpi 经
# 依赖链反向注入(循环 import)会在同一模块制造同名 declare,且注入侧类型与
# 定义端可能不一致 —— 现场 json/__parser.vp 编译时其自身 vpi 被注入,
# ParseState.__init__ 注入成 i32(vpi 无返回注解 → i32 默认)而定义端是 void
# → llc: value doesn't match function result type 'i32'。
if trans is not None and trans._declare_only == 0 and trans.ModuleSha1 is not None:
if string.strcmp(src_sha1, trans.ModuleSha1) == 0:
return
# 构造方法查找名: ClassName.method
lookup_name_len: sizet = 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: sizet = 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* = GenApi.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* = 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 = GenApi.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* = GenApi.get_struct_type(cls_name)
if cls_ty is not None:
ret_ty = llvmlite.Ptr(pool, cls_ty)
# [S4 fail-fast] 有返回注解却解析不出类型 → 不再静默退 i32;
# __new__ 无注解、普通方法无注解均走下方 i32 合法默认。
if ret_ty is None and m_fd.returns is not None:
GenApi.fatal_error(
m_fd.returns,
f"extern 方法 '{cls_name}.{m_fd.name}' 返回类型注解解析失败",
"NameError")
# __init__ 无返回注解 → 与定义端一致取 Void(定义端
# ClassGenVTable._translate_method 对 __init__ 恒 Void)。若按"普通方法无注解"
# 退成下方 i32 默认,就会出现 declare i32 / define void 的返回类型分裂:
# 同模块内 llc 报 value doesn't match function result type,跨模块是静默 ABI 不一致。
if ret_ty is None and string.strcmp(m_fd.name, "__init__") == 0:
ret_ty = llvmlite.Void(pool)
if ret_ty is None:
ret_ty = i32_ty
# 创建 declare
func: llvmlite.Function* = llvmlite.create_declare(
pool, mod, mangled_name, ret_ty)
if func is None:
return
# 提取参数并添加到 declare
param_count: int = 0
md_defaults: t.CPtr = None
md_default_count: int = 0
if m_fd.args is not None:
args_node: ast.Arguments* = ast.Arguments*(m_fd.args)
if args_node.args is not None:
args_list: list[ast.AST*]* = args_node.args
param_count = len(args_list)
# 参数数上限哨兵(fail-fast):与 _inject_function_declare 同因
# (vpi tree 深层节点被覆盖损坏时 __len__() 返回垃圾大数)。
if param_count < 0 or param_count > 128:
stdio.printf(
"[VPIS] 参数数异常: 方法名=%s.%s param_count=%d(m_fd.args=%llu,tree 可能被覆盖,终止)\n",
cls_name, m_fd.name, param_count, uint64(m_fd.args))
# 终止点收敛:REPL 会话内 longjmp 回 REPL 循环(诊断已打印)
VLogger.repl_abort()
pi: sizet
for pi in range(param_count):
ag: ast.Arg* = ast.Arg*(args_list.get(pi))
if ag is None:
continue
pname: str = ag.arg
if pname is None:
pname = "arg"
# self/cls 参数(首参):跨模块声明用类指针(%"sha1.Class"*)而非
# 默认 i8* —— 与定义侧 _translate_method 的 self 参数 Ptr(StructTy)
# 签名一致,消除跨模块声明/定义歧义。调用侧按形参 coerce 自动
# bitcast(见 _call_method_on_ptr/_translate_method_call 的 coerce
# 块)。类类型未注册时兜底走下方注解解析/i8*(保持原行为)。
if pi == 0 and (string.strcmp(pname, "self") == 0
or string.strcmp(pname, "cls") == 0):
cls_ty_sd: llvmlite.LLVMType* = GenApi.get_struct_type(cls_name)
if cls_ty_sd is not None:
llvmlite.add_param(pool, func, llvmlite.Ptr(pool, cls_ty_sd), pname)
continue
param_ty: llvmlite.LLVMType* = i8_ptr_ty
# 优先用 vpi 文本注解缓存解析参数类型(与返回类型同源——vpi
# tree 深层节点 kind() 可能损坏,AST resolve 得垃圾类型如
# sizet→i32,declare 参数宽错 → 调用侧 trunc i64→i32 传参、
# callee 按 i64 读栈残留垃圾高位 → can=巨值读未写槽位崩溃,
# SlotsBugTest 根因)。段索引 = pi(0=self 空段)。
pseg_ty: llvmlite.LLVMType* = _resolve_vpi_param_annot_seg(
pool, src_sha1, cls_name, m_fd.name, pi, i32_ty, i8_ptr_ty)
if pseg_ty is not None:
match pseg_ty:
case llvmlite.LLVMType.Struct(_, _, _):
# 裸结构体形参:方法恒为 TPV 内部符号(mangled =
# {sha1}.{Class}.{method},无 C ABI 外部 define),
# 故 is_c_abi=0 → Ptr(struct),与定义侧
# ClassGenVTable L744-750 逐字一致(三处必须同步)。
param_ty = _AggregateParamTy(mod, pool, pseg_ty, 0)
case llvmlite.LLVMType.Array(aelem_ty, _):
param_ty = llvmlite.Ptr(pool, aelem_ty)
case _:
param_ty = pseg_ty
elif ag.annotation is not None:
resolved_ty: llvmlite.LLVMType* = GenApi.resolve_annotation_type(
pool, ag.annotation, imported_modules, from_imports, trans)
if resolved_ty is not None:
# CArray 参数 → 元素指针 ty*(C 数组衰减,与编译侧一致)。
# 裸结构体参数 → _AggregateParamTy(同一判据,is_c_abi=0)。
match resolved_ty:
case llvmlite.LLVMType.Struct(_, _, _):
param_ty = _AggregateParamTy(mod, pool, resolved_ty, 0)
case llvmlite.LLVMType.Array(aelem_ty, _):
param_ty = llvmlite.Ptr(pool, aelem_ty)
case _:
param_ty = resolved_ty
else:
# [S4 fail-fast] 有注解却解析不出类型 → 不再静默退 i8*
# (无注解 ag.annotation is None 是合法默认,保持 i8*)
GenApi.fatal_error(
ag.annotation,
f"extern 方法 '{cls_name}.{m_fd.name}' 形参 '{pname}' 类型注解解析失败",
"NameError")
llvmlite.add_param(pool, func, param_ty, pname)
# 文本缓存校验/修复:vpi tree args 可能被覆盖损坏(尾部默认参数丢失)
# → declare 参数数不足 → 调用点缺参 → 被调函数读栈垃圾(跨模块 CDefine
# 查找 depth=160 假递归根因)。用 vpi 文本缓存的参数注解补全 declare
# 参数(与 define 参数数一致,调用点默认参数填充才能生效)。
param_count = _repair_declare_params_from_text(
pool, func, src_sha1, cls_name, m_fd.name, param_count, i32_ty, i8_ptr_ty)
# OOP iter 协议:__next__ 方法 stub 声明带隐藏 __stop_iter_flag__(i1*),
# 与 _translate_method 的 define 签名一致(对齐 TPC HandlesFunctions.py
# L703-705)。for x in obj 循环会以 2 个实参调用 __next__(self, flag)。
if m_fd.name is not None and string.strcmp(m_fd.name, "__next__") == 0:
i1_ty_si: llvmlite.LLVMType* = llvmlite.Int1(pool)
if i1_ty_si is not None:
i1_ptr_ty_si: llvmlite.LLVMType* = llvmlite.Ptr(pool, i1_ty_si)
if i1_ptr_ty_si is not None:
llvmlite.add_param(pool, func, i1_ptr_ty_si, "__stop_iter_flag__")
# 默认参数:vpi 签名含 '= <default>'(Converter 已输出),提取 defaults
# 列表注册默认参数表——跨模块缺参调用(如 resolve_annotation_type 的
# depth=0)才能按表填充,否则被调函数读到栈垃圾(假报递归过深/崩溃)。
if m_fd.args is not None:
md_args_node: ast.Arguments* = ast.Arguments*(m_fd.args)
if md_args_node.defaults is not None:
md_dlist: list[ast.AST*]* = md_args_node.defaults
md_default_count = len(md_dlist)
if md_default_count < 0 or md_default_count > 128:
# 同 _inject_function_declare:vpi tree 深层节点被覆盖时
# defaults len 可能为垃圾大数 → 越界写 perm_pool。放弃注册。
stdio.printf(
"[VPIS] 默认参数数异常: 方法名=%s.%s default_count=%d(tree 可能被覆盖,跳过默认值注册)\n",
cls_name, m_fd.name, md_default_count)
md_defaults = None
md_default_count = 0
else:
md_defaults = md_dlist
# 注册到函数表(用裸名 ClassName.method,与调用方 lookup_name 一致)
# ParamCount 不含 self(方法首参 self/cls 不计入,与 _translate_method 的
# md_param_count = len(args) - 1 保持一致)——默认参数填充按非 self
# 参数计数,若含 self 会导致 mdf_param_count 虚高、填充逻辑错位/越界。
if funcs_ptr is not None:
cur_count: int = trans._func_count
param_count_no_self: int = param_count
if param_count_no_self > 0:
param_count_no_self -= 1
if GenApi.add_func_to_table(funcs_ptr, cur_count, lookup_name, func, max_funcs,
md_defaults, md_default_count, param_count_no_self, None, src_sha1) == 0:
trans._func_count = cur_count + 1
# 补全全局函数表 Spec(参数已全部添加,对齐 ClassGenVTable 行为):
# vpi 注入的方法声明若不补全 Spec,跨模块继承 vtable 生成时
# find_func_entry_global().Spec 为空 → "vtable 方法找不到函数类型"
# (astaux.vp 先于 ast.base.vp 编译时必现:AST.accept 仅有 vpi 注入条目)
GenApi.update_global_func_spec(lookup_name, func)
# ============================================================
# _register_ctypedef_from_annassign - 从 AnnAssign 节点注册 CTypedef
#
# 提取 NAME: t.CTypedef = Type 中的名称和类型表达式字符串,
# 调用 register_ctypedef 注册到动态 CTypedef 表。
# ============================================================
def _register_ctypedef_from_annassign(pool: memhub.MemBuddy*,
aa: ast.AnnAssign*) -> 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* = ast.Name*(aa.target)
if tgt.id is None:
return
# 提取类型表达式字符串(从 value AST 节点)
if aa.value is None:
return
type_str: str = GenApi._get_type_name_from_annotation(pool, aa.value)
if type_str is None:
return
# 注册 CTypedef
GenApi.register_ctypedef(pool, tgt.id, type_str)
# ============================================================
# REPL:includes 类**完整布局**与虚表恢复(.stub.ll 驱动)
#
# 为什么需要(M4 遗留缺口):会话初始化用 .vpi 注入注册 includes 类,而 .vpi 只含
# "类名 + 本类 AnnAssign 字段 + 方法签名" ⇒ **父类字段**与 **__vtable__ 占位
# 字段**全部缺失 ⇒ 带父类/虚表的类在片段侧的字段索引与宿主真实布局错位
# (静默读错字段)。
#
# 布局真源 = .stub.ll(批处理落盘点:Phase1Main.vp:1078 / Phase2/__init__.vp:1050
# 用 tr.dump_ir(..., llvmlite.OUTPUT_STUB)):
# - `%"<sha1>.<Class>" = type { ... }` 是**展平后**的完整布局(ClassGenDef 的
# 字段展平:__vtable__ 首字段 + 父类字段 + 本类字段);
# - `@...<sha1>.<Class>.vtable` 全局的类型槽数 = 该类的虚方法数
# (ClassGenVTable._generate_vtable 按 _collect_vtable_methods 的顺序发射)。
#
# 本段仅在 ReplStubLayoutMode != 0(REPL 会话初始化)时生效,批处理永不置位:
# ① 在 _inject_class_struct_declare **之前**登记完整布局:字段名/类型与
# ClassGenDef 同源(父类字段来自已在会话登记的父条目=字段展平;本类字段
# 来自本 vpi 的 AnnAssign,走同一条 resolve_annotation_type);
# ② 布局由 .stub.ll 的**字段数**校验:Count(名单) 必须等于存根的字段数
# (有虚表时 +1 = __vtable__);不符即判"布局不可信";
# ③ 虚表:HasVTable 与虚方法**短名顺序**按 ClassGenDef._detect_vtable_status /
# ClassGenBase._should_method_be_virtual 的规则转写(父类虚方法在前、本类
# 合格方法按类体顺序追加、按短名去重),并用存根 .vtable 槽数校验;
# 槽数不符 ⇒ 不登记 VTableMethods(退化为直接调用 {sha1}.{Class}.{m})。
#
# 拒绝策略(能报错就别静默):布局不可信 ⇒ **不登记**该类的 StructEntry ⇒ 片段侧
# 字段访问报"类未注册/属性访问失败",绝不按错偏移静默访问;方法调用不受影响
# (self 只是指针,方法体在宿主里按真实布局编译,符号经宿主 import lib 解析)。
#
# 拒绝的两类成因必须分开(见 _REPL_SU_* 与 _repl_stub_warn_nodata):
# - 『数据缺失』= LayoutCount 只是哨兵(本模块没有 .stub.ll 文件 / 存根里查不到该
# 类),任一侧无数据 ⇒ **不比较**、也就不存在"字段数不符"这回事;文案与汇总计数
# 都写明成因,避免"-1 vs 推断 -1"这种两侧都无数据却像在比较的误导输出;
# - 『判据拒绝』= 数据在但不可信(字段数不符 / opaque / 父类被拒),一律不放行。
# 两者都**不登记**(M4.2 原则不变),差别只在"能不能靠补数据消除"。
#
# 逐模块隔离:全部工作发生在一次 .vpi 注入内(调用方已有 per-module _setjmp 兜底),
# 单模块失败只告警 + 该类不可用,不终止会话;配合 ReplInitIncludesClasses 的
# 多遍收敛,跨模块父类在后续遍就绪后即可登记(存根元数据已缓存在类表,无需重解析)。
# ============================================================
# 会话级状态(PermPool;会话初始化期间有效)
ReplStubLayoutMode: int = 0
ReplStubPool: memhub.MemBuddy* = None
ReplStubTempDir: str = None
ReplStubTable: sizet* = None
ReplStubCount: int = 0
ReplStubOk: int = 0
ReplStubVtOk: int = 0
ReplStubRefused: int = 0
ReplStubRefusedNoStub: int = 0 # 拒绝原因分布(_REPL_SU_*):数据缺失(模块无 .stub.ll)
ReplStubRefusedNoClass: int = 0 # 判据拒绝:存根有该模块但无此类
ReplStubRefusedOpaque: int = 0 # 判据拒绝:存根里该类 opaque
ReplStubRefusedParent: int = 0 # 上游无数据:父类不可用/未注册
ReplStubNoStub: int = 0
ReplStubWarnLeft: int = 0
ReplStubTmpCount: int = -1
ReplStubTmpPacked: int = 0
ReplStubTmpArity: int = 0
ReplStubNameBuf: str = None
def _repl_stub_dupstr(pool: memhub.MemBuddy*, src: str) -> str:
"""把字符串深拷贝到指定池"""
if src is None or pool is None:
return None
ln: sizet = string.strlen(src)
dst: str = pool.alloc(ln + 1)
if dst is None:
return None
string.strcpy(dst, src)
return dst
def _repl_stub_key(pool: memhub.MemBuddy*, sha1: str, cls: str) -> str:
"""拼接 <sha1>.<类名>:与定义侧 ClassGenDef 的命名结构体名逐字一致"""
if pool is None or sha1 is None or cls is None:
return None
if sha1[0] == '\0' or cls[0] == '\0':
return None
ln: sizet = string.strlen(sha1) + 1 + string.strlen(cls) + 1
buf: str = pool.alloc(ln)
if buf is None:
return None
viperlib.snprintf(buf, ln, "%s.%s", sha1, cls)
return buf
def _repl_stub_path(sha1: str, ext: str) -> str:
"""REPL 临时目录下的切片路径 <temp_dir>/<sub>/<sha1>.<ext>(stdlib 分配)"""
if ReplStubTempDir is None or sha1 is None:
return None
td_len: sizet = string.strlen(ReplStubTempDir)
sh_len: sizet = string.strlen(sha1)
sub: str = Config.slice_subdir(sha1, Config.Sha1SliceLevel)
sub_len: sizet = 0
if sub is not None:
sub_len = string.strlen(sub)
cap: sizet = td_len + sub_len + sh_len + 8
buf: str = stdlib.malloc(cap)
if buf is None:
return None
if sub_len > 0:
viperlib.snprintf(buf, cap, "%s/%s/%s.%s", ReplStubTempDir, sub, sha1, ext)
else:
viperlib.snprintf(buf, cap, "%s/%s.%s", ReplStubTempDir, sha1, ext)
return buf
def _repl_stub_readtext(path: str) -> str:
"""读入存根文本(stdlib.malloc,调用方 free);不存在/超限返回 None"""
if path is None:
return None
f: rawptr[fileio.File] = fileio.File(path, fileio.MODE.R)
if f is None or f.closed:
return None
sz: sizet = f.size()
if sz <= 0 or sz >= _REPL_STUB_TXT_CAP:
f.close()
return None
buf: str = stdlib.malloc(sz + 1)
if buf is None:
f.close()
return None
rd: LONG = f.read_all(buf, sz + 1)
f.close()
if rd <= 0:
stdlib.free(buf)
return None
if rd < LONG(sz + 1):
buf[rd] = 0
return buf
# 从 txt[pos] 起取一个标识符(跳过 '%' / '@' 前缀与可选引号)写进 ReplStubNameBuf;
# 返回名字之后的位置(0 = 失败/超长;合法调用至少前进 2 字符,故 0 可作失败哨兵)
def _repl_stub_take_name(txt: str, pos: sizet) -> sizet:
global ReplStubNameBuf
if txt is None or ReplStubNameBuf is None:
return 0
n: sizet = string.strlen(txt)
i: sizet = pos
if i < n and (txt[i] == '%' or txt[i] == '@'):
i += 1
if i >= n:
return 0
if txt[i] == '\"':
i += 1
k: sizet = 0
while i < n and txt[i] != '\"':
if k + 1 >= _REPL_STUB_NAME_CAP:
return 0
ReplStubNameBuf[k] = txt[i]
k += 1
i += 1
if i >= n:
return 0
ReplStubNameBuf[k] = 0
return i + 1
k2: sizet = 0
while i < n:
ch: char = txt[i]
if ch == ' ' or ch == '\t' or ch == '=' or ch == ',' or ch == '{' or ch == '}':
break
if ch == '[' or ch == ']' or ch == '(' or ch == ')' or ch == '*' or ch == '<' or ch == '>':
break
if k2 + 1 >= _REPL_STUB_NAME_CAP:
return 0
ReplStubNameBuf[k2] = ch
k2 += 1
i += 1
if k2 == 0:
return 0
ReplStubNameBuf[k2] = 0
return i
# 从 txt[pos](应指向 '{')起数顶层字段数:{} / [] / () / <> / "..." 内的逗号不计;
# 返回字段数(-1 = 未闭合/格式异常)
def _repl_stub_body_count(txt: str, pos: sizet) -> int:
if txt is None:
return -1
n: sizet = string.strlen(txt)
i: sizet = pos
if i >= n or txt[i] != '{':
return -1
i += 1
depth: int = 0
in_str: int = 0
cnt: int = 0
has: int = 0
while i < n:
ch: char = txt[i]
if in_str != 0:
if ch == '\"':
in_str = 0
i += 1
continue
if ch == '\"':
in_str = 1
has = 1
i += 1
continue
if ch == 0:
return -1
if ch == ',':
if depth == 0:
cnt += 1
i += 1
continue
if ch == '{' or ch == '[' or ch == '(' or ch == '<':
depth += 1
has = 1
i += 1
continue
if ch == '}' or ch == ']' or ch == ')' or ch == '>':
if ch == '}' and depth == 0:
if has == 0:
return 0
return cnt + 1
depth -= 1
i += 1
continue
if ch != ' ' and ch != '\t' and ch != '\n' and ch != '\r':
has = 1
i += 1
return -1
# 扫描存根文本,取 `%"<key>" = type ...` 与 `@...<key>.vtable = ...` 两条元数据,
# 结果写全局 scratch:ReplStubTmpCount(字段数 / -1 无此类 / -2 opaque)、
# ReplStubTmpPacked、ReplStubTmpArity(.vtable 槽数,0 = 无该全局)
def _repl_stub_scan_meta(txt: str, key: str) -> None:
global ReplStubTmpCount, ReplStubTmpPacked, ReplStubTmpArity
ReplStubTmpCount = -1
ReplStubTmpPacked = 0
ReplStubTmpArity = 0
if txt is None or key is None:
return
n: sizet = string.strlen(txt)
key_len: sizet = string.strlen(key)
i: sizet = 0
while i < n:
ls: sizet = i
while i < n and txt[i] != '\n':
i += 1
le: sizet = i
if i < n:
i += 1
p: sizet = ls
while p < le and (txt[p] == ' ' or txt[p] == '\t' or txt[p] == '\r'):
p += 1
if p >= le:
continue
c0: char = txt[p]
if c0 == '%':
# 类型定义行
q: sizet = _repl_stub_take_name(txt, p)
if q == 0 or q > le:
continue
if string.strcmp(ReplStubNameBuf, key) != 0:
continue
# 找 "= type"
j: sizet = q
while j + 6 < le and string.strncmp(txt + j, "= type", 6) != 0:
j += 1
if j + 6 >= le:
continue
j += 6
while j < le and (txt[j] == ' ' or txt[j] == '\t'):
j += 1
if j + 6 <= le and string.strncmp(txt + j, "opaque", 6) == 0:
ReplStubTmpCount = -2
continue
if j < le and txt[j] == '<':
ReplStubTmpPacked = 1
j += 1
if j < le and txt[j] == '{':
ReplStubTmpCount = _repl_stub_body_count(txt, j)
continue
if c0 == '@':
# 全局变量行:只关心 <key>.vtable
q2: sizet = _repl_stub_take_name(txt, p)
if q2 == 0 or q2 > le:
continue
nm_len: sizet = string.strlen(ReplStubNameBuf)
if nm_len != key_len + 7:
continue
if string.strncmp(ReplStubNameBuf, key, key_len) != 0:
continue
if string.strncmp(ReplStubNameBuf + key_len, ".vtable", 7) != 0:
continue
j2: sizet = q2
while j2 < le and txt[j2] != '=':
j2 += 1
if j2 >= le:
continue
j2 += 1
while j2 < le and txt[j2] != '{':
j2 += 1
if j2 < le and txt[j2] == '{':
ar: int = _repl_stub_body_count(txt, j2)
if ar > 0:
ReplStubTmpArity = ar
continue
return
def ReplStubLayoutBegin(temp_dir: str) -> int:
"""开启会话级"存根布局恢复"(Repl.vp 在 includes 类注册前调用一次);0=成功"""
global ReplStubLayoutMode, ReplStubPool, ReplStubTempDir, ReplStubTable
global ReplStubCount, ReplStubOk, ReplStubVtOk, ReplStubRefused, ReplStubNoStub
global ReplStubWarnLeft, ReplStubNameBuf
global ReplStubRefusedNoStub, ReplStubRefusedNoClass, ReplStubRefusedOpaque, ReplStubRefusedParent
pool: memhub.MemBuddy* = CompilerFactory.factory.PermPool
if pool is None or temp_dir is None:
return 1
ReplStubPool = pool
ReplStubTempDir = _repl_stub_dupstr(pool, temp_dir)
if ReplStubTempDir is None:
return 1
ReplStubTable = pool.alloc(8 * _REPL_STUB_CLS_MAX)
if ReplStubTable is None:
return 1
string.memset(ReplStubTable, 0, 8 * _REPL_STUB_CLS_MAX)
ReplStubNameBuf = pool.alloc(_REPL_STUB_NAME_CAP)
if ReplStubNameBuf is None:
return 1
ReplStubNameBuf[0] = 0
ReplStubCount = 0
ReplStubOk = 0
ReplStubVtOk = 0
ReplStubRefused = 0
ReplStubRefusedNoStub = 0
ReplStubRefusedNoClass = 0
ReplStubRefusedOpaque = 0
ReplStubRefusedParent = 0
ReplStubNoStub = 0
ReplStubWarnLeft = _REPL_STUB_WARN_MAX
ReplStubLayoutMode = 1
return 0
def _repl_stub_at(i: int) -> rawptr[ReplStubClass]:
"""类表第 i 项(无则 None)"""
if ReplStubTable is None or i < 0 or i >= ReplStubCount:
return None
addr: sizet = ReplStubTable[i]
if addr == 0:
return None
return rawptr[ReplStubClass](uint64(addr))
def _repl_stub_find(key: str) -> rawptr[ReplStubClass]:
"""按 "<sha1>.<类名>" 查类表条目(无则 None)"""
if key is None:
return None
i: int
for i in range(ReplStubCount):
e: rawptr[ReplStubClass] = _repl_stub_at(i)
if e is None or e.Key is None:
continue
if string.strcmp(e.Key, key) == 0:
return e
return None
def _repl_stub_find_base(e: rawptr[ReplStubClass], bi: int) -> rawptr[ReplStubClass]:
"""父类表条目:先同模块 SHA1 精确匹配,再按裸类名首个匹配(镜像
StructGen.find_struct_by_module → find_struct 的父类回退口径)"""
if e is None or e.Bases is None or bi < 0 or bi >= e.BaseCount:
return None
pname: str = rawptr(e.Bases[bi])
if pname is None:
return None
i: int
for i in range(ReplStubCount):
pe: rawptr[ReplStubClass] = _repl_stub_at(i)
if pe is None or pe.ClassName is None:
continue
if string.strcmp(pe.ClassName, pname) != 0:
continue
if e.Sha1 is not None and pe.Sha1 is not None:
if string.strcmp(pe.Sha1, e.Sha1) == 0:
return pe
for i in range(ReplStubCount):
pe2: rawptr[ReplStubClass] = _repl_stub_at(i)
if pe2 is None or pe2.ClassName is None:
continue
if string.strcmp(pe2.ClassName, pname) == 0:
return pe2
return None
def _repl_stub_has_name(arr: sizet*, cnt: int, name: str) -> int:
"""名单里是否已有该名(先声明优先去重)"""
if arr is None or name is None:
return 0
i: int
for i in range(cnt):
addr: sizet = arr[i]
if addr == 0:
continue
ex: str = rawptr(addr)
if ex is not None and string.strcmp(ex, name) == 0:
return 1
return 0
def _repl_stub_deco_flags(deco_list: list[ast.AST*]*) -> int:
"""类/方法装饰器标志(位0=@t.NoVTable,位1=@t.CVTable)
与 ClassGenBase._has_decorator 同判据(Attribute(t.NoVTable) 与 Name(NoVTable) 两种写法)。
"""
if deco_list is None:
return 0
flags: int = 0
dn: sizet = len(deco_list)
di: sizet
for di in range(dn):
deco: ast.AST* = deco_list.get(di)
if deco is None:
continue
nm: str = None
k: int = deco.kind()
if k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(deco)
if at.attr is not None and at.value is not None \
and at.value.kind() == ast.ASTKind.Name:
vn: ast.Name* = ast.Name*(at.value)
if vn.id is not None and string.strcmp(vn.id, "t") == 0:
nm = at.attr
elif k == ast.ASTKind.Name:
nn: ast.Name* = ast.Name*(deco)
nm = nn.id
if nm is None:
continue
if string.strcmp(nm, "NoVTable") == 0:
flags = flags | 1
elif string.strcmp(nm, "CVTable") == 0:
flags = flags | 2
return flags
def _repl_stub_is_virtual(e: rawptr[ReplStubClass], mi: int,
first: rawptr[ReplStubClass]) -> int:
"""本类第 mi 个方法是否进虚表(转写 ClassGenBase._should_method_be_virtual)"""
if e is None or e.MethodFlags is None or mi < 0 or mi >= e.MethodCount:
return 0
mf: int = int(e.MethodFlags[mi])
if (mf & 1) != 0:
return 0
if (mf & 2) != 0:
return 1
if (e.Flags & _REPL_SF_CVTABLE) != 0:
return 1
if first is not None:
if first.IsNoVTable != 0:
return 0
return first.HasVt
return 0
def _repl_stub_warn_refuse(e: rawptr[ReplStubClass], why: str, a: int, b: int) -> None:
"""**判据拒绝**告警(两侧都是真数字时才用它):存根字段数 a 与展平+虚表推断 b 不符"""
global ReplStubWarnLeft, ReplStubRefused
ReplStubRefused += 1
if ReplStubWarnLeft <= 0:
return
ReplStubWarnLeft -= 1
stdio.printf("includes 类布局: %s.%s 布局不可信 ⇒ 明确拒绝(原因=判据拒绝:%s;"
"存根字段数=%d ≠ 展平字段数+虚表位=%d)。该类字段访问被明确拒绝"
"(方法调用不受影响)\n",
e.Sha1, e.ClassName, why, a, b)
def _repl_stub_warn_nodata(e: rawptr[ReplStubClass], cause: int) -> None:
"""『无数据 ⇒ 明确拒绝』告警(与 _repl_stub_warn_refuse 分开)
与"字段数不符"不同,这条路径上根本没做比较(任一侧无数据即不比较,也不假装成
"不匹配"),所以文案打印两侧数据的来源而不是数字,并把『数据缺失』
(补存根/重跑即可消除)与『判据拒绝』(数据在但不可信,绝不放行)分开。
"""
global ReplStubWarnLeft, ReplStubRefused
global ReplStubRefusedNoStub, ReplStubRefusedNoClass
global ReplStubRefusedOpaque, ReplStubRefusedParent
ReplStubRefused += 1
if cause == _REPL_SU_NOSTUB:
ReplStubRefusedNoStub += 1
elif cause == _REPL_SU_NOCLASS:
ReplStubRefusedNoClass += 1
elif cause == _REPL_SU_OPAQUE:
ReplStubRefusedOpaque += 1
else:
ReplStubRefusedParent += 1
if ReplStubWarnLeft <= 0:
return
ReplStubWarnLeft -= 1
if cause == _REPL_SU_NOSTUB:
# 数据缺失:连模块存根文件都没有(LayoutCount = -1 只是"没读到存根"的哨兵)
stdio.printf("includes 类布局: %s.%s 布局不可用 ⇒ 明确拒绝(原因=『数据缺失』:"
"模块 %s 没有 .stub.ll 文件;存根字段数=无 / 推断字段数=未计算 ⇒ "
"两侧都没数据,『未做比较』,不是字段数不符)。该类字段访问被"
"拒绝(方法调用不受影响);补上该模块存根后用同一 --project 重跑 "
"REPL 即恢复\n", e.Sha1, e.ClassName, e.Sha1)
elif cause == _REPL_SU_OPAQUE:
# 判据拒绝:存根里该 key 是 opaque(无 body ⇒ 无字段数可比)
stdio.printf("includes 类布局: %s.%s 布局不可用 ⇒ 明确拒绝(原因=『判据拒绝』:"
"存根 %s.stub.ll 里键 %s 为 opaque 声明(无 body);存根字段数=不可得 / "
"推断字段数=未计算 ⇒ 『未做比较』)。该类字段访问被拒绝"
"(方法调用不受影响)\n", e.Sha1, e.ClassName, e.Sha1, e.Key)
else:
# 判据拒绝:存根文件在,但没有该类的 `= type` 行(LayoutCount = -1 只是"未找到"的哨兵)
stdio.printf("includes 类布局: %s.%s 布局不可用 ⇒ 明确拒绝(原因=『判据拒绝』:"
"存根 %s.stub.ll 存在但其中没有键 %s 的 `= type {…}` 定义行;"
"存根字段数=未找到 / 推断字段数=未计算 ⇒ 『未做比较』)。该类字段访问"
"被拒绝(方法调用不受影响);若该类确实定义于该模块,说明存根陈旧/"
"不完整,请重建\n", e.Sha1, e.ClassName, e.Sha1, e.Key)
def _repl_stub_warn_parent(e: rawptr[ReplStubClass], detail: str, pname: str) -> None:
"""**上游无数据 ⇒ 明确拒绝**:父类布局不可用/未注册(本类无法展平 ⇒ 不比较)"""
global ReplStubWarnLeft, ReplStubRefused, ReplStubRefusedParent
ReplStubRefused += 1
ReplStubRefusedParent += 1
if ReplStubWarnLeft <= 0:
return
ReplStubWarnLeft -= 1
stdio.printf("includes 类布局: %s.%s 布局不可用 ⇒ 明确拒绝(原因=『上游无数据』:%s,"
"父类=%s;父类字段与虚表位无法展平 ⇒ 本类未做字段数比较)。该类字段访问"
"被拒绝(方法调用不受影响)\n", e.Sha1, e.ClassName, detail, pname)
def _repl_stub_try(e: rawptr[ReplStubClass], mod: llvmlite.LLVMModule*) -> int:
"""按存根校验并登记完整布局:1=已登记 / 0=父类未就绪(推迟)/ -1=拒绝"""
global ReplStubOk, ReplStubVtOk, ReplStubWarnLeft
if e is None:
return -1
if e.State == _REPL_SS_OK:
return 1
if e.State == _REPL_SS_REFUSED:
return -1
if e.State != _REPL_SS_PENDING:
return -1
pool: memhub.MemBuddy* = ReplStubPool
if pool is None:
e.State = _REPL_SS_REFUSED
return -1
# ① 父类必须已就绪(未就绪 ⇒ 推迟;被拒 ⇒ 本类布局不可信)
bi: int
for bi in range(e.BaseCount):
pe: rawptr[ReplStubClass] = _repl_stub_find_base(e, bi)
if pe is None:
return 0
if pe.State == _REPL_SS_PENDING:
return 0
if pe.State == _REPL_SS_REFUSED:
e.State = _REPL_SS_REFUSED
_repl_stub_warn_parent(e, "父类布局不可信", pe.ClassName)
return -1
if pe.State == _REPL_SS_EMPTY and pe.OwnFail != 0:
# 父类自身有字段注解解析失败 ⇒ 其 Count 只是"已解析字段数"(不是完整布局)
# ⇒ 展平出来的本类字段数不可用:既不比较(避免误报"字段数不符")、也不放行
# (M4.2:布局不可信 ⇒ 明确拒绝)。
e.State = _REPL_SS_REFUSED
_repl_stub_warn_parent(e, "父类的字段类型未全部解析(其字段数不完整)",
pe.ClassName)
return -1
first: rawptr[ReplStubClass] = None
if e.BaseCount > 0:
first = _repl_stub_find_base(e, 0)
# ② HasVTable(转写 ClassGenBase._detect_vtable_status)
hv: int = 0
if (e.Flags & _REPL_SF_CVTABLE) != 0:
hv = 1
elif (e.Flags & _REPL_SF_NOVTABLE) != 0:
hv = 1 if (e.Flags & _REPL_SF_ANYFUNC) != 0 else 0
elif first is not None:
if first.IsNoVTable != 0:
hv = 1 if (e.Flags & _REPL_SF_ANYFUNC) != 0 else 0
else:
hv = first.HasVt
else:
hv = 1 if (e.Flags & _REPL_SF_ANYFUNC) != 0 else 0
# ③ 字段展平(父类字段在前 + 本类字段,短名去重先声明优先)
cn: int = 0
for bi in range(e.BaseCount):
pe: rawptr[ReplStubClass] = _repl_stub_find_base(e, bi)
fi: int
for fi in range(pe.Count):
if cn >= _REPL_STUB_FIELD_MAX:
break
nm: str = rawptr(pe.Names[fi])
if nm is None:
continue
if _repl_stub_has_name(e.Names, cn, nm) != 0:
continue
e.Names[cn] = pe.Names[fi]
e.Types[cn] = pe.Types[fi]
cn += 1
oi: int
for oi in range(e.OwnCount):
if cn >= _REPL_STUB_FIELD_MAX:
break
nm2: str = rawptr(e.OwnNames[oi])
if nm2 is None:
continue
if _repl_stub_has_name(e.Names, cn, nm2) != 0:
continue
e.Names[cn] = e.OwnNames[oi]
e.Types[cn] = e.OwnTypes[oi]
cn += 1
e.Count = cn
e.HasVt = hv
# ④ 布局校验(**仅当两侧都可用时才走到这里**:LayoutCount >= 0 已由 PENDING 前提
# 保证,展平侧由 ① 保证父类字段数完整):存根字段数 == __vtable__(有虚表时) + 展平字段数
if e.LayoutCount != cn + hv:
e.State = _REPL_SS_REFUSED
_repl_stub_warn_refuse(e, "存根字段数与字段名单不符", e.LayoutCount, cn + hv)
return -1
# ⑤ 虚方法短名顺序(父类虚方法在前 + 本类合格方法按类体顺序)
vn: int = 0
if hv == 1:
for bi in range(e.BaseCount):
pe: rawptr[ReplStubClass] = _repl_stub_find_base(e, bi)
vi: int
for vi in range(pe.VtCount):
if vn >= _REPL_STUB_VT_MAX:
break
mn: str = rawptr(pe.VtNames[vi])
if mn is None:
continue
if _repl_stub_has_name(e.VtNames, vn, mn) != 0:
continue
e.VtNames[vn] = pe.VtNames[vi]
vn += 1
mi: int
for mi in range(e.MethodCount):
if vn >= _REPL_STUB_VT_MAX:
break
mn2: str = rawptr(e.MethodNames[mi])
if mn2 is None:
continue
if string.strcmp(mn2, "__init__") == 0:
continue
if string.strcmp(mn2, "__before_init__") == 0:
continue
if string.strcmp(mn2, "__new__") == 0:
continue
if _repl_stub_has_name(e.VtNames, vn, mn2) != 0:
continue
if _repl_stub_is_virtual(e, mi, first) == 0:
continue
e.VtNames[vn] = e.MethodNames[mi]
vn += 1
e.VtCount = vn
e.VtTrust = 0
if hv == 1 and vn > 0:
if vn == e.VtArity:
e.VtTrust = 1
elif ReplStubWarnLeft > 0:
ReplStubWarnLeft -= 1
stdio.printf("includes 类布局: %s.%s 的虚表槽数与存根不符(%d vs %d)"
"——该类不做虚分派(退化为直接调用,会话继续)\n",
e.Sha1, e.ClassName, vn, e.VtArity)
# ⑥ 建 LLVM 结构体(命名 "%<sha1>.<类名>",与定义侧同口径)
total: int = cn + hv
i8_ptr: llvmlite.LLVMType* = llvmlite.Ptr(pool, llvmlite.Int8(pool))
if i8_ptr is None:
return 0
first_node: rawptr[llvmlite.ParamNode] = None
prev_node: rawptr[llvmlite.ParamNode] = None
if hv == 1:
nd0: rawptr[llvmlite.ParamNode] = pool.alloc(llvmlite.ParamNode.__sizeof__())
if nd0 is None:
return 0
string.memset(nd0, 0, llvmlite.ParamNode.__sizeof__())
nd0.Ty = i8_ptr
first_node = nd0
prev_node = nd0
fi2: int
for fi2 in range(cn):
fty: llvmlite.LLVMType* = (llvmlite.LLVMType*)(e.Types[fi2])
if fty is None:
continue
nd: rawptr[llvmlite.ParamNode] = pool.alloc(llvmlite.ParamNode.__sizeof__())
if nd is None:
continue
string.memset(nd, 0, llvmlite.ParamNode.__sizeof__())
nd.Ty = fty
if first_node is None:
first_node = nd
if prev_node is not None:
prev_node.Next = nd
prev_node = nd
struct_ty: llvmlite.LLVMType* = llvmlite.Struct(pool, first_node, total, e.Key)
if struct_ty is None:
return 0
# ⑦ 登记(StdlibAlloc=1 期间的注册走 PermPool,跨片段存活)
entry: GenTypes.StructEntry* = GenApi.register_struct(
pool, e.ClassName, struct_ty, e.Sha1)
if entry is None:
e.State = _REPL_SS_REFUSED
return -1
if mod is not None:
llvmlite.module_add_named_type(mod, pool, struct_ty)
if (e.Flags & _REPL_SF_PACKED) != 0:
entry.IsPacked = 1
if mod is not None:
llvmlite.module_set_named_type_packed(mod, pool, struct_ty, 1)
if hv == 1:
entry.HasVTable = 1
GenApi.add_field(pool, entry, "__vtable__", i8_ptr)
fi3: int
for fi3 in range(cn):
fname: str = rawptr(e.Names[fi3])
fty3: llvmlite.LLVMType* = (llvmlite.LLVMType*)(e.Types[fi3])
if fname is None or fty3 is None:
continue
GenApi.add_field(pool, entry, fname, fty3)
if e.IsNoVTable != 0:
entry.IsNoVTable = 1
entry.IsOOP = e.IsOOP
entry.HasInit = e.HasInit
entry.HasNew = e.HasNew
# 父类名(多继承数组;顺序同声明序,供 vtable/MRO/字段继承查询)
if e.BaseCount > 0:
arr: sizet* = pool.alloc(8 * e.BaseCount)
if arr is not None:
string.memset(arr, 0, 8 * e.BaseCount)
bi3: int
for bi3 in range(e.BaseCount):
bnm: str = rawptr(e.Bases[bi3])
bdup: str = _repl_stub_dupstr(pool, bnm)
if bdup is not None:
arr[bi3] = sizet(bdup)
entry.ParentNames = arr
entry.ParentCount = e.BaseCount
pn: str = rawptr(e.Bases[0])
entry.ParentName = _repl_stub_dupstr(pool, pn)
if hv == 1 and e.VtTrust == 1 and e.VtCount > 0:
entry.VTableMethods = e.VtNames
entry.VTableMethodCount = e.VtCount
ReplStubVtOk += 1
e.State = _REPL_SS_OK
ReplStubOk += 1
return 1
def _repl_stub_add_class(pool: memhub.MemBuddy*,
mod: llvmlite.LLVMModule*,
cd: ast.ClassDef*,
sha1: str,
txt: str,
have_stub: int,
trans: HT.Translator*,
imported_modules: str,
from_imports: str) -> int:
"""收集单个 vpi ClassDef 的布局要素并尝试登记(1=已登记/推迟处理,0=跳过)
have_stub:本模块是否读到了 .stub.ll 文本(0 时本模块带父类/虚表的类按
『数据缺失』拒绝,与"存根在但无此类"的『判据拒绝』在文案/计数上分开)。
"""
global ReplStubCount
if pool is None or cd is None or cd.name is None or ReplStubTable is None:
return 0
if cd.name[0] == '\0' or ReplStubCount >= _REPL_STUB_CLS_MAX:
return 0
key: str = _repl_stub_key(pool, sha1, cd.name)
if key is None:
return 0
if _repl_stub_find(key) is not None:
return 0
e: rawptr[ReplStubClass] = pool.alloc(ReplStubClass.__sizeof__())
if e is None:
return 0
string.memset(e, 0, ReplStubClass.__sizeof__())
e.Key = key
e.ClassName = _repl_stub_dupstr(pool, cd.name)
e.Sha1 = _repl_stub_dupstr(pool, sha1)
e.OwnNames = pool.alloc(8 * _REPL_STUB_FIELD_MAX)
e.OwnTypes = pool.alloc(8 * _REPL_STUB_FIELD_MAX)
e.MethodNames = pool.alloc(8 * _REPL_STUB_FIELD_MAX)
e.MethodFlags = pool.alloc(8 * _REPL_STUB_FIELD_MAX)
e.Bases = pool.alloc(8 * _REPL_STUB_FIELD_MAX)
e.Names = pool.alloc(8 * _REPL_STUB_FIELD_MAX)
e.Types = pool.alloc(8 * _REPL_STUB_FIELD_MAX)
e.VtNames = pool.alloc(8 * _REPL_STUB_VT_MAX)
if e.OwnNames is None or e.OwnTypes is None or e.MethodNames is None \
or e.MethodFlags is None or e.Bases is None or e.Names is None \
or e.Types is None or e.VtNames is None:
return 0
string.memset(e.OwnNames, 0, 8 * _REPL_STUB_FIELD_MAX)
string.memset(e.OwnTypes, 0, 8 * _REPL_STUB_FIELD_MAX)
string.memset(e.MethodNames, 0, 8 * _REPL_STUB_FIELD_MAX)
string.memset(e.MethodFlags, 0, 8 * _REPL_STUB_FIELD_MAX)
string.memset(e.Bases, 0, 8 * _REPL_STUB_FIELD_MAX)
string.memset(e.Names, 0, 8 * _REPL_STUB_FIELD_MAX)
string.memset(e.Types, 0, 8 * _REPL_STUB_FIELD_MAX)
string.memset(e.VtNames, 0, 8 * _REPL_STUB_VT_MAX)
# 类装饰器
cf: int = _repl_stub_deco_flags(cd.decorator_list)
if (cf & 2) != 0:
e.Flags = e.Flags | _REPL_SF_CVTABLE
if (cf & 1) != 0:
e.Flags = e.Flags | _REPL_SF_NOVTABLE
e.IsNoVTable = 1
if _vpi_class_has_packed(cd.decorator_list) != 0:
e.Flags = e.Flags | _REPL_SF_PACKED
# 真实父类(复用 ClassGenBase._collect_parents:别名解析 + marker 剔除)
pb: sizet* = pool.alloc(8 * GenTypes.PARENT_MAX)
bcnt: int = 0
if pb is not None and GenApi._g_gen is not None:
string.memset(pb, 0, 8 * GenTypes.PARENT_MAX)
bcnt = GenApi._g_gen.ClassGenBase._collect_parents(
pool, cd, pb, GenTypes.PARENT_MAX, trans)
if bcnt > 0:
if bcnt > _REPL_STUB_FIELD_MAX:
bcnt = _REPL_STUB_FIELD_MAX
bi: int
for bi in range(bcnt):
bnm: str = rawptr(pb[bi])
bdup: str = _repl_stub_dupstr(pool, bnm)
if bdup is not None:
e.Bases[bi] = sizet(bdup)
e.BaseCount = bcnt
# 类体扫描:字段(AnnAssign)+ 方法(FunctionDef)
children: list[ast.AST*]* = cd.children
own_fail: int = 0
if children is not None:
cn: sizet = len(children)
ci: sizet
for ci in range(cn):
ch2: ast.AST* = children.get(ci)
if ch2 is None:
continue
k2: int = ch2.kind()
if k2 == ast.ASTKind.AnnAssign:
if e.OwnCount >= _REPL_STUB_FIELD_MAX:
continue
aa2: ast.AnnAssign* = ast.AnnAssign*(ch2)
if aa2 is None or aa2.target is None or aa2.annotation is None:
continue
if aa2.target.kind() != ast.ASTKind.Name:
continue
tg: ast.Name* = ast.Name*(aa2.target)
if tg.id is None or tg.id[0] == '\0':
continue
fty2: llvmlite.LLVMType* = GenApi.resolve_annotation_type(
pool, aa2.annotation, imported_modules, from_imports, trans, 0)
if fty2 is None:
own_fail = 1
continue
fdup: str = _repl_stub_dupstr(pool, tg.id)
if fdup is None:
own_fail = 1
continue
e.OwnNames[e.OwnCount] = sizet(fdup)
e.OwnTypes[e.OwnCount] = sizet(fty2)
e.OwnCount += 1
continue
if k2 == ast.ASTKind.FunctionDef:
fd2: ast.FunctionDef* = ast.FunctionDef*(ch2)
if fd2 is None or fd2.name is None or fd2.name[0] == '\0':
continue
mf2: int = _repl_stub_deco_flags(fd2.decorator_list)
if (mf2 & 2) != 0:
e.Flags = e.Flags | _REPL_SF_ANYFUNC
if string.strcmp(fd2.name, "__init__") == 0:
e.HasInit = 1
elif string.strcmp(fd2.name, "__new__") == 0:
e.HasNew = 1
e.IsOOP = 1
if e.MethodCount < _REPL_STUB_FIELD_MAX:
e.MethodNames[e.MethodCount] = sizet(_repl_stub_dupstr(pool, fd2.name))
e.MethodFlags[e.MethodCount] = sizet(mf2)
e.MethodCount += 1
continue
# 存根元数据(缺存根时 LayoutCount 保持"未找到"哨兵;两种成因由 have_stub 区分)
e.LayoutCount = _REPL_STUB_NOCOUNT_NOCLASS
if txt is not None:
_repl_stub_scan_meta(txt, key)
e.LayoutCount = ReplStubTmpCount
e.VtArity = ReplStubTmpArity
if ReplStubTmpPacked != 0:
e.Flags = e.Flags | _REPL_SF_PACKED
# 状态判定
if own_fail != 0:
# 字段类型解析失败:交既有 vpi 注入口径(其整类跳过),本表只做展平来源
# (OwnFail 记下来:本类 OwnCount 只是"已解析字段数",不可当作完整布局供子类展平)
e.OwnFail = 1
e.State = _REPL_SS_EMPTY
e.Count = e.OwnCount
e.Names = e.OwnNames
e.Types = e.OwnTypes
elif e.LayoutCount >= 0:
e.State = _REPL_SS_PENDING
elif e.BaseCount == 0 and (e.Flags & (_REPL_SF_CVTABLE | _REPL_SF_ANYFUNC)) == 0:
# 无父类、无虚表 ⇒ 本类自有字段即完整布局(与既有口径等价)
e.State = _REPL_SS_EMPTY
e.Count = e.OwnCount
e.Names = e.OwnNames
e.Types = e.OwnTypes
else:
# 布局无数据 ⇒ 明确拒绝(不登记 StructEntry)。两类成因必须分开报:
# ① have_stub == 0:连模块 .stub.ll 都没有(**数据缺失**,补存根即可恢复)
# ② LayoutCount == -2:存根里该类是 opaque(判据拒绝)
# ③ 其余:存根在但没有该类的 `= type` 行(判据拒绝;若该类确实定义于此模块
# 则说明存根陈旧/不完整)
e.State = _REPL_SS_REFUSED
if have_stub == 0:
_repl_stub_warn_nodata(e, _REPL_SU_NOSTUB)
elif e.LayoutCount == _REPL_STUB_NOCOUNT_OPAQUE:
_repl_stub_warn_nodata(e, _REPL_SU_OPAQUE)
else:
_repl_stub_warn_nodata(e, _REPL_SU_NOCLASS)
ReplStubTable[ReplStubCount] = sizet(e)
ReplStubCount += 1
if e.State == _REPL_SS_PENDING:
_repl_stub_try(e, mod)
return 1
def _repl_stub_collect_module(pool: memhub.MemBuddy*,
mod: llvmlite.LLVMModule*,
ch: list[ast.AST*]*,
n: sizet,
trans: HT.Translator*,
src_sha1: str,
imported_modules: str,
from_imports: str) -> int:
"""(REPL)为当前模块的类收集布局要素 + 尝试登记;返回处理的类数"""
global ReplStubNoStub, ReplStubWarnLeft
if ReplStubLayoutMode == 0 or pool is None or ch is None:
return 0
if src_sha1 is None or src_sha1[0] == '\0' or ReplStubTable is None:
return 0
# 存根文本(缺失:带父类/虚表的类将因"数据缺失"被明确拒绝,逐个可见)
txt: str = None
sp: str = _repl_stub_path(src_sha1, "stub.ll")
if sp is not None:
txt = _repl_stub_readtext(sp)
stdlib.free(sp)
have_stub: int = 0
if txt is not None:
have_stub = 1
else:
ReplStubNoStub += 1
if ReplStubWarnLeft > 0:
ReplStubWarnLeft -= 1
stdio.printf("includes 类布局: 模块 %s 无 .stub.ll 存根(数据缺失)—— 该模块"
"带父类/虚表的类将逐个按『数据缺失』明确拒绝(读不到存根 ⇒ 无"
"字段数可比,不是字段数不符)。用同一工程先跑一次批处理生成存根,"
"再 `--project .\\project.vpj --repl`(TransPyV 目录)即可复用\n",
src_sha1)
cnt: int = 0
i: sizet
for i in range(n):
child: ast.AST* = ch.get(i)
if child is None:
continue
if child.kind() != ast.ASTKind.ClassDef:
continue
cd: ast.ClassDef* = ast.ClassDef*(child)
if cd is None or cd.name is None:
continue
# 泛型模板类:与 _inject_class_struct_declare / 第一遍 A 同口径跳过(模板签名
# 含类级类型参数 T,注入时 T 无法确定)。但**归属模块必须登记**:REPL 会话不
# 注册 includes 泛型模板,故注解里的泛型下标(list[rawptr[AST]] 等)要靠
# "模板模块 sha1" 拼定义侧特化名造类型占位(TypeGenResolve 的 ReplCtx 分支)。
tp: list[str]* = cd.type_params
if tp is not None and len(tp) > 0:
StubMergerStore.RegisterGenericOwner(cd.name, src_sha1)
continue
# REnum 类:布局 {i32 tag, payload} 由 ClassGenEnum 计算,既有路径整体跳过
if _vpi_class_is_renum(cd) != 0:
continue
if _repl_stub_add_class(pool, mod, cd, src_sha1, txt, have_stub, trans,
imported_modules, from_imports) != 0:
cnt += 1
if txt is not None:
stdlib.free(txt)
return cnt
def ReplStubLayoutFinish() -> int:
"""收尾:收敛登记剩余类 + 打印汇总;返回已登记完整布局的类数"""
global ReplStubRefused, ReplStubWarnLeft
if ReplStubLayoutMode == 0:
return 0
rounds: int
for rounds in range(_REPL_STUB_FINISH_PASSES):
prog: int = 0
i: int
for i in range(ReplStubCount):
e: rawptr[ReplStubClass] = _repl_stub_at(i)
if e is None or e.State != _REPL_SS_PENDING:
continue
if _repl_stub_try(e, None) != 0:
prog += 1
if prog == 0:
break
# 仍未就绪 ⇒ 父类始终未注册(缺存根/被拒):明确拒绝(本类无法展平 ⇒ 不比较)
i2: int
for i2 in range(ReplStubCount):
e2: rawptr[ReplStubClass] = _repl_stub_at(i2)
if e2 is None or e2.State != _REPL_SS_PENDING:
continue
e2.State = _REPL_SS_REFUSED
# 找出仍未就绪的父类名(自证"上游是谁")
pbad: str = "(未定位)"
bi2: int
for bi2 in range(e2.BaseCount):
peb: rawptr[ReplStubClass] = _repl_stub_find_base(e2, bi2)
if peb is not None and peb.State == _REPL_SS_OK:
continue
if peb is not None and peb.ClassName is not None:
pbad = peb.ClassName
break
_repl_stub_warn_parent(e2, "父类未在会话注册或其布局被拒", pbad)
stdio.printf("includes 类布局: 完整布局 %d 个(其中虚表索引可用 %d 个)/ "
"被拒 %d 个 / 缺存根模块 %d 个\n",
ReplStubOk, ReplStubVtOk, ReplStubRefused, ReplStubNoStub)
stdio.printf(" 拒绝原因分布(自证数据来源):数据缺失-模块无 .stub.ll %d 个类 / "
"判据拒绝-存根无此类 %d 个 / 判据拒绝-存根为 opaque %d 个 / "
"上游无数据-父类不可用 %d 个\n",
ReplStubRefusedNoStub, ReplStubRefusedNoClass,
ReplStubRefusedOpaque, ReplStubRefusedParent)
if ReplStubRefused > 0 or ReplStubNoStub > 0:
stdio.printf(" 说明:被拒类的字段访问会报『类未注册/属性访问失败』"
"(不按错偏移静默访问);方法调用不受影响。\n")
if ReplStubRefusedNoStub > 0:
stdio.printf(" 其中『数据缺失』的 %d 个类只是没读到模块存根(本会话临时目录 %s "
"缺这些 .stub.ll):批处理才会落盘 .stub.ll(REPL 分支在 Phase1 之前"
"返回,故 --repl 自身不生成存根)。修法:用同一个工程跑一次批处理"
"(TransPyV 目录:.\\tpv_build\\output\\TransPyV.exe --project "
".\\project.vpj;includes 有改动时加 --rebuild-includes),随后 "
"--project .\\project.vpj --repl 复用其临时目录即可\n", ReplStubRefusedNoStub,
ReplStubTempDir)
if ReplStubRefusedNoClass + ReplStubRefusedOpaque > 0:
stdio.printf(" 其中『判据拒绝』的 %d 个类:数据在但不可信(存根里没有该类的字段数、"
"或其声明为 opaque),按 M4.2 一律不放行——请确认该类是否本应按结构体"
"登记:应按结构体登记而存根里查不到 ⇒ 存根陈旧/不完整(重建 includes "
"存根)\n", ReplStubRefusedNoClass + ReplStubRefusedOpaque)
return ReplStubOk
def ReplStubLayoutEnd() -> None:
"""关闭存根布局恢复(批处理路径此后完全不受影响)"""
global ReplStubLayoutMode
ReplStubLayoutMode = 0