Files
ViperC/App/lib/core/Gen/StructGenMixin.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

2308 lines
126 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import t, c
from stdint import *
import memhub
import string
import stdlib
import llvmlite
import stdio
import ast
import hashtable
import viperlib
import lib.core.CompilerFactory as CompilerFactory
import lib.core.VLogger as VLogger
import lib.Stub.StubMerger.StubMergerStore as StubMergerStore
import lib.core.Gen.GenTypes as GenTypes
import lib.core.Gen.Generator as Generator
import lib.core.Gen.Type.TypeSpec as TSpec
import lib.core.Gen.GenApi as GenApi
# ============================================================
# StructGenMixin - 结构体注册/字段查找 Mixin
#
# 职责:struct 表注册与查找(名/类型索引)、字段查找、跨模块归属匹配。
# @t.NoVTable:静态绑定(方法沿 MRO 直接调用 + this 调整,无 vtable 开销)。
# ============================================================
# ============================================================
# 常量(与 HandlesStruct 保持一致)
# ============================================================
STRUCT_MAX: t.CDefine = 512
# 名 | 类型 下标索引槽数(2 的幂且 > 2*STRUCT_MAX ⇒ 负载 < 0.5,探测必然终止)
STRUCT_IDX_CAP: t.CDefine = 1024
# 字段槽位上限制(与 HandlesStruct 保持一致):Generator 43 Mixin 展平后约 120 字段,
# 上限取 64 会导致第 9+ 父类字段丢失 → __init__ 写属性失败。
FIELD_MAX: t.CDefine = 256
FIELD_NAME_MAX: t.CDefine = 64
# 多继承(MRO)支持:单类最大父类数 / MRO 链最大长度(Generator 43 父类;
# PARENT_MAX=16 时第 17+ 父类字段丢失 → __init__ 写 self.DiagStmtCount 失败)
PARENT_MAX: t.CDefine = 64
MRO_MAX: t.CDefine = 64
# 索引哈希常量(32 位 FNV-1a,常量均 < 2^63 以避开大十进制字面量解析风险)
FNV32_OFFSET: t.CDefine = 2166136261
FNV32_PRIME: t.CDefine = 16777619
FNV32_MASK: t.CDefine = 4294967295
# ============================================================
# 索引哈希函数(名 | Ty 指针值 → 64 位哈希)
# 供 StructTable 的「名 | Ty 指针值 → 首注册下标」索引探测使用
# (见 _struct_name_idx_insert / _struct_name_idx_find 等)。
# ============================================================
def _struct_name_hash(name: str) -> uint64:
"""名字 → 哈希(32 位 FNV-1a,逐字节到 NUL 结束;空名返回偏移基值)"""
h: uint64 = uint64(FNV32_OFFSET)
if name is None:
return h
i: int = 0
while name[i] != '\0':
h = (h ^ uint64(int(name[i]))) * uint64(FNV32_PRIME)
h = h & uint64(FNV32_MASK)
i += 1
return h
def _struct_ty_hash(tyval: uint64) -> uint64:
"""Ty 指针值 → 哈希(对其 8 字节做 32 位 FNV-1a)"""
h: uint64 = uint64(FNV32_OFFSET)
v: uint64 = tyval
j: int = 0
while j < 8:
h = (h ^ (v & uint64(255))) * uint64(FNV32_PRIME)
h = h & uint64(FNV32_MASK)
v = v >> 8
j += 1
return h
# ============================================================
# StructGenMixin - 结构体注册表/字段查找/可见性
# ============================================================
novirtual class StructGenMixin:
"""结构体注册与查找 Mixin(状态从 HandlesStruct 全局变量迁入)"""
# --- 全局注册表(原 _struct_table/_struct_count) ---
Gen: Generator.Generator*
StructTable: GenTypes.StructEntry* # STRUCT_MAX 槽位数组
StructCount: int
# --- OOM 回收分配模式(原 _g_stdlib_alloc) ---
StdlibAlloc: int
# --- 跨模块命名空间隔离(原 _visible_structs/_strict_visibility) ---
VisibleStructs: hashtable.HashTable*
StrictVisibility: int
# --- 各模块"声明的类名"集合(键 "{模块SHA1}.{类名}")---
# 与 StructTable 的注册状态**无关**:类定义入口处即登记(ClassGenDef:43 之后),
# 因此"类正在定义中、struct 还没有条目"时也能答出该裸类名的归属模块。
# 用途:泛型类型实参命名规范化(TypeGenResolve._norm_type_arg_name 规则 5.5)
# 判定裸类名属于哪个模块 —— 自引用字段(如 includes/ast/base.vp
# `AST.children: list[rawptr[AST]]`)解析时本类尚未 register_struct,只按
# 注册表查会把实参留在裸名,与同一个类在构造调用/别的模块处的带 SHA1 形式
# 分裂成两个特化名(现场:base.vp:357 CastError,源/目标只差实参类名前缀)。
ModuleClasses: hashtable.HashTable*
# --- **正在定义类**所属模块的 SHA1(mark_module_class 同步记录)---
# 为什么不复用 TypeGenBase.CurrentModuleSha1:A-pre/StubMerger 等路径只设了
# Translator.ModuleSha1、没设 CurrentModuleSha1(或留着上一文件的旧值),
# 而类定义入口拿到的 trans.ModuleSha1 在任何路径上都是权威的 ⇒ 以它为"当前
# 定义模块"的唯一真源。
CurDefModuleSha1: str
# --- **正在定义类**的类名(mark_module_class 同步记录;表无关的自引用判据)---
# 为什么需要:自引用字段(如 includes/ast/base.vp `AST.children: list[rawptr[AST]]`)
# 解析时本类尚未 register_struct ⇒ 注册表查不到;而 ModuleClasses 表可能因
# "登记时表未就绪/键随 pool 回收"而查不到 ⇒ 实参留在裸名,与同一类在形参注解/
# 构造调用处的带 SHA1 形式分裂成两个特化名(现场:base.vp:359 `_append_child(
# self.children, ...)` 源 %"list[rawptr[AST]]"* vs 目标 %"list[rawptr<sha1>.AST]"*,
# 被"非基本类型指针不可隐式转换"拦下)。类名直接来自类定义入口,不经任何表 ⇒
# 该窗口内**必然**可判(与 CurDefModuleSha1 同源、同生命周期)。
CurDefClassName: str
# --- 本文件顶层 ClassDef 的类名表(每文件重建;register_file_classes 登记)---
# 为什么需要(**第二个"裸类名"窗口**):Translator.translate 的 prologue 先翻译
# 模块级变量与前向声明(Translator.vp:592-601),**早于** translate_children(:602)
# —— 此时"正在定义类"窗口尚未建立(CurDefModuleSha1=None),规则 5.5 的证据
# ①②③④ 全落空 ⇒ 形参/全局注解里的裸类名(如 includes/ast/base.vp:148
# `_append_child(..., lst: list[rawptr[AST]])`)以**裸名**形式被首次注册并被后续
# 复用(首写者胜),与该类自身翻译期解析出的带 SHA1 形式分裂
# (现场:base.vp:359 报"非基本类型指针不可隐式转换")。
# 本表与"窗口"无关:判据是**正向事实**"该名字是本文件声明的类"(文件级预扫描
# 在本文件任何注解解析之前登记),配合 FileModuleSha1 即得归属,不猜。
FileClassNames: hashtable.HashTable*
# 本文件(正在翻译的文件)的模块 SHA1:由 Translator 文件入口随预扫描传入。
# 仅与 FileClassNames 命中**配合**使用 ⇒ 即便窗口里留着"上一文件的旧值"也不会
# 误归属(旧 sha1 与"本文件声明了该类"两个正向事实不会同时成立)。
FileModuleSha1: str
# 本文件顶层 FunctionDef 的函数名表:用途见 register_file_funcs ——
# 依赖 stub 注入的**裸名**声明(extern/state/export,不带 SHA1 前缀)若与
# 本文件自己的声明同名,会先占用该名字,使本文件声明在
# forward_declare_functions 里被静默丢弃(调用点改按依赖签名 coerce)。
# 注入侧(VpiReader._inject_function_declare)据此跳过这些名字。
FileFuncNames: hashtable.HashTable*
# --- 名 | 类型 下标索引(查找快速路径;索引不可信时回退原线性扫描) ---
StructNameIdx: sizet* # 名 → 首注册下标(槽 u64:0=空槽,否则 index+1)
StructTypeIdx: sizet* # Ty 指针值 → 首注册下标(同布局)
StructIdxCap: int # 索引槽数(2 的幂,> 2*STRUCT_MAX)
StructIdxBuilt: int # 已同步建索引条目数(-1=不可信;= StructCount 才可信)
def InitStructGen(self) -> int:
"""初始化结构体 Mixin 字段(子类构造时内联调用,规避 NoVTable 继承符号不转发)"""
if self is None: return 1
self.StructTable = None
self.StructCount = 0
self.StdlibAlloc = 0
self.VisibleStructs = None
self.StrictVisibility = 0
self.ModuleClasses = None
self.CurDefModuleSha1 = None
self.CurDefClassName = None
self.FileClassNames = None
self.FileModuleSha1 = None
self.FileFuncNames = None
self.StructNameIdx = None
self.StructTypeIdx = None
self.StructIdxCap = 0
self.StructIdxBuilt = 0
return 0
# ============================================================
# P2-2 迁移方法(函数体来自 HandlesStruct.vp,全局变量改 self.字段,
# 同模块函数调用改 self.方法;签名与 HandlesStruct 原函数一致)
# ============================================================
def set_stdlib_alloc(self, flag: int) -> None:
"""设置 OOM 回收分配模式:1=1b 阶段(新条目用 stdlib),0=注册阶段(用 pool)"""
self.StdlibAlloc = flag
def get_stdlib_alloc(self) -> int:
"""读取 OOM 回收分配模式(供其他 Mixin 经 HandlesStruct 委托访问,
避免跨 Mixin 直接读 self.StdlibAlloc——Mixin 方法体按自身块内索引编译)"""
return self.StdlibAlloc
def reset_visible_structs(self, pool: memhub.MemBuddy*, strict: int) -> None:
"""重置可见性状态(每个文件翻译前调用)
pool: 内存分配器(严格模式下创建 HashTable)
strict: 1=严格模式(用户文件),0=宽松模式(includes 文件)
`ModuleClasses` 也在此**每文件重建**:它的键是 pool 字符串,而 pool 在
文件/遍切换时会被 reset ⇒ 若表跨文件长期存活,`_find_slot` 会 strcmp 到
已回收的键(现场:ASan access-violation,rcx=0x1,栈
mark_module_class → HashTable.set_int → _resize → _find_slot → strcmp)。
与 `VisibleStructs` 同口径(同样每文件新建、键同池),故无悬空。
`CurDefModuleSha1` 指向 `trans.ModuleSha1`(同一 pool 的串),同样必须
每文件清空,否则下一次 strcat/strcmp 读的是已回收内存。
"""
self.StrictVisibility = strict
self.CurDefModuleSha1 = None
self.CurDefClassName = None
# 文件级类名表同口径每文件重建(键与表同池 ⇒ 无悬空;由 Translator 文件入口的
# 预扫描重新登记,见 register_file_classes)。FileModuleSha1 指向 trans.ModuleSha1
# (同池串)⇒ 必须一并清空,避免下一文件读到已回收内存。
self.FileClassNames = None
self.FileModuleSha1 = None
self.FileFuncNames = None
self.ModuleClasses = hashtable.HashTable(pool)
if strict != 0:
# 严格模式:创建新的 HashTable 记录可见结构体
self.VisibleStructs = hashtable.HashTable(pool)
else:
# 宽松模式:全部可见,不需要 HashTable
self.VisibleStructs = None
def add_visible_struct(self, pool: memhub.MemBuddy*, name: str) -> None:
"""添加可见结构体名(类定义时和 from-import 时调用)"""
if name is None: return
# 宽松模式:全部可见,无需记录
if self.StrictVisibility == 0: return
if self.VisibleStructs is None: return
# 已存在则跳过(HashTable.__setitem__ 会覆盖,此处提前检查避免重复分配)
if self.VisibleStructs.__contains__(name) != 0: return
self.VisibleStructs.set_int(name, 1)
def is_struct_visible(self, name: str) -> int:
"""检查结构体是否可见,返回 1=可见 / 0=不可见"""
if self.StrictVisibility == 0: return 1
if name is None or self.VisibleStructs is None: return 0
return self.VisibleStructs.__contains__(name)
def _module_class_key(self, pool: memhub.MemBuddy*, sha1: str, name: str) -> str:
"""拼 "{sha1}.{类名}" 查询/登记键(分配在给定 pool,调用方无需释放)。"""
if pool is None or sha1 is None or name is None:
return None
k_len: sizet = string.strlen(sha1) + 1 + string.strlen(name) + 1
k_buf: str = pool.alloc(k_len)
if k_buf is None:
return None
k_buf[0] = '\0'
string.strcat(k_buf, sha1)
string.strcat(k_buf, ".")
string.strcat(k_buf, name)
return k_buf
def mark_module_class(self, pool: memhub.MemBuddy*, sha1: str, name: str) -> None:
"""登记"{sha1}.{类名}":**该模块声明了这个类**(类定义入口调用一次)。
键里带 SHA1 ⇒ 天然按"声明模块"区分。表由 `reset_visible_structs` **每文件
重建**(与键同池 ⇒ 无悬空),因此这里的登记只在本文件本遍内有效 ——
恰好覆盖使用者(`_norm_type_arg_name` 规则 5.5)的窗口:判"类定义中"的
自引用字段时,登记与查询在同一文件同一遍内。
`CurDefModuleSha1` 同步记录("正在定义类所属模块"的权威来源,任何翻译
路径上都是 trans.ModuleSha1)。
"""
if sha1 is None or name is None:
return
self.CurDefModuleSha1 = sha1
# 类名同步记录(表无关的自引用判据;池/表缺失也必须记 —— 它只是当前
# 类定义入口手里的那个串,不经任何表查询,见 _norm_type_arg_name 规则 5.5)。
self.CurDefClassName = name
if pool is None or self.ModuleClasses is None:
# 未按文件初始化(该路径未调用 reset_visible_structs):不登记。
# 不在此懒建表 —— 表必须与键同池、且随文件重置,否则会 strcmp 到
# 已回收的键(ASan access-violation 现场)。
return
k_buf: str = self._module_class_key(pool, sha1, name)
if k_buf is None:
return
if self.ModuleClasses.__contains__(k_buf) != 0:
return
self.ModuleClasses.set_int(k_buf, 1)
def is_module_class(self, pool: memhub.MemBuddy*, sha1: str, name: str) -> int:
"""裸名 name 是否由模块 sha1 声明(1=是 / 0=否;与 struct 注册状态无关)。"""
if pool is None or sha1 is None or name is None:
return 0
if self.ModuleClasses is None:
return 0
k_buf: str = self._module_class_key(pool, sha1, name)
if k_buf is None:
return 0
return self.ModuleClasses.__contains__(k_buf)
def register_file_classes(self, pool: memhub.MemBuddy*, sha1: str, tree: ast.AST*) -> None:
"""文件级预登记:把**本文件顶层 ClassDef 的类名**与**本文件模块 SHA1** 记下来。
用途(`_norm_type_arg_name` 规则 5.5 的第五类证据):Translator.translate 的
prologue 会先翻译**模块级变量**与**前向声明**(Translator.vp:592-601),此后才
translate_children(:602) 逐个翻译类 —— 前者解析注解时"正在定义类"窗口尚未建立
(CurDefModuleSha1==None),规则 5.5 的四条证据全落空 ⇒ 注解里的裸类名
(如 includes/ast/base.vp:148 `_append_child(..., lst: list[rawptr[AST]])` 的
形参、或模块级全局的注解)以**裸名**形式被首次注册、并被后续复用(首写者胜),
最终与该类自身翻译期解析出的带 SHA1 形式分裂成两个特化名,报"被指类型是非基本
类型……不可隐式转换"(现场:base.vp:359)。
判据是**正向事实**(该名字确由本文件声明),且与"窗口"无关 ⇒ 不依赖类定义顺序,
不需要在窗口里"猜"归属。表与键同池、每文件重建(见 reset_visible_structs)。
"""
if pool is None or sha1 is None or tree is None:
return
self.FileModuleSha1 = sha1
ch: list[ast.AST*]* = tree.children
if ch is None:
return
if self.FileClassNames is None:
self.FileClassNames = hashtable.HashTable(pool)
if self.FileClassNames is None:
return
cn: sizet = len(ch)
ci: sizet
for ci in range(cn):
child: ast.AST* = ch.get(ci)
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
if self.FileClassNames.__contains__(cd.name) != 0:
continue
# 键必须与表同池(AST 的 name 串零拷贝指向 src_buf,跨阶段可能失效):
# 复制到 pool 后再登记,避免表 resize/查询时 strcmp 到悬空内存。
nm_len: sizet = string.strlen(cd.name)
nm_buf: str = pool.alloc(nm_len + 1)
if nm_buf is None:
continue
string.strcpy(nm_buf, cd.name)
self.FileClassNames.set_int(nm_buf, 1)
def is_file_class(self, pool: memhub.MemBuddy*, name: str) -> int:
"""裸名 name 是否为本文件顶层声明的类(1=是 / 0=否;与窗口、与注册状态无关)。"""
if name is None or self.FileClassNames is None:
return 0
return self.FileClassNames.__contains__(name)
def register_file_funcs(self, pool: memhub.MemBuddy*, tree: ast.AST*) -> None:
"""文件级预登记:把**本文件顶层 FunctionDef 的函数名**记下来。
用途(依赖 stub 注入的裸名去重):编译本文件时,prologue 会**先**把依赖模块的
.vpi 注入进来(StubMergerBuild.LoadDepVpiIntoModule);而 extern/state/export
声明的符号**不带 SHA1 前缀**(FuncGenDef.vp:172-173 `mangled_name = fd.name`)
⇒ 依赖的裸名声明先占用该名字;随后 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"*」。
注入侧据本表跳过"本文件已声明的裸名"(VpiReader._inject_function_declare),
使本文件自己的声明优先。表与键同池、每文件重建(与 FileClassNames 同规矩)。
"""
if pool is None or tree is None:
return
ch: list[ast.AST*]* = tree.children
if ch is None:
return
if self.FileFuncNames is None:
self.FileFuncNames = hashtable.HashTable(pool)
if self.FileFuncNames is None:
return
cn: sizet = len(ch)
ci: sizet
for ci in range(cn):
child: ast.AST* = ch.get(ci)
if child is None:
continue
if child.kind() != ast.ASTKind.FunctionDef:
continue
fd: ast.FunctionDef* = ast.FunctionDef*(child)
if fd is None or fd.name is None:
continue
if self.FileFuncNames.__contains__(fd.name) != 0:
continue
# 键必须与表同池(AST 的 name 串零拷贝指向 src_buf,跨阶段可能失效):
# 复制到 pool 后再登记,避免表 resize/查询时 strcmp 到悬空内存。
nm_len: sizet = string.strlen(fd.name)
nm_buf: str = pool.alloc(nm_len + 1)
if nm_buf is None:
continue
string.strcpy(nm_buf, fd.name)
self.FileFuncNames.set_int(nm_buf, 1)
def is_file_func(self, name: str) -> int:
"""裸名 name 是否为本文件顶层声明的函数(1=是 / 0=否)"""
if name is None or self.FileFuncNames is None:
return 0
return self.FileFuncNames.__contains__(name)
def get_file_module_sha1(self) -> str:
"""本文件(正在翻译的文件)的模块 SHA1(register_file_classes 传入;未登记返回 None)。"""
return self.FileModuleSha1
def init_struct_table(self, pool: memhub.MemBuddy*) -> int:
"""初始化结构体注册表,返回 1 成功
StdlibAlloc=1 时表体分配在 perm_pool:1a 每文件 reset 后表本体
不被回收(否则首次 register_struct 若发生在文件级 mark 之后,
reset 会把 StructTable 连根回收,全局 find_struct 悬空)。
"""
if self.StructTable is not None: return 1
entry_size: sizet = GenTypes.StructEntry.__sizeof__()
table_pool: memhub.MemBuddy* = pool
if self.StdlibAlloc != 0:
table_pool = CompilerFactory.factory.PermPool
if table_pool is None: table_pool = pool
self.StructTable = table_pool.alloc(entry_size * STRUCT_MAX)
if self.StructTable is None: return 0
string.memset(self.StructTable, 0, entry_size * STRUCT_MAX)
self.StructCount = 0
# 建 名 | 类型 索引(与表同池分配:表存活则索引存活,生命周期一致)。
# 索引是可选加速层:分配失败则保持 None 且 StructIdxBuilt=-1,查找自动
# 回退原线性扫描(语义完全一致),故不因此使初始化失败。
idx_bytes: sizet = sizet(STRUCT_IDX_CAP) * 8
name_idx: sizet* = table_pool.alloc(idx_bytes)
type_idx: sizet* = table_pool.alloc(idx_bytes)
if name_idx is None or type_idx is None:
self.StructNameIdx = None
self.StructTypeIdx = None
self.StructIdxCap = 0
self.StructIdxBuilt = -1
else:
string.memset(name_idx, 0, idx_bytes)
string.memset(type_idx, 0, idx_bytes)
self.StructNameIdx = name_idx
self.StructTypeIdx = type_idx
self.StructIdxCap = STRUCT_IDX_CAP
self.StructIdxBuilt = 0
return 1
def _get_struct_entry(self, i: int) -> GenTypes.StructEntry*:
"""获取第 i 个结构体条目"""
if self.StructTable is None or i < 0 or i >= STRUCT_MAX:
return None
entry_size: sizet = GenTypes.StructEntry.__sizeof__()
addr: uint64 = uint64(self.StructTable) + i * entry_size
return GenTypes.StructEntry*(addr)
# 名 | 类型 索引(开放寻址 | 线性探测;槽存 index+1,0=空槽)
# 目的:把 find_struct/find_struct_by_module(名)与 find_struct_by_type(类型指针)
# 从 O(n) 全表扫描降为期望 O(1)。索引在注册处同步维护,冲突逐键比较(名用
# strcmp,类型用 Ty 指针值),不能只比哈希。
# 可信条件 = StructIdxBuilt == StructCount;不可信/不可用时回退线性扫描。
# 条目被中和(Name[0]=0 / Ty=None)后索引槽成失效键,探测时键不等即判定未命中。
def _struct_entry_name(self, i: int) -> str:
"""取第 i 个条目的 Name(供索引探测比较),越界/空返回 None"""
e: GenTypes.StructEntry* = self._get_struct_entry(i)
if e is None:
return None
return e.Name
def _struct_entry_tyval(self, i: int) -> uint64:
"""取第 i 个条目的 Ty 指针值(Ty=None 返回 0)"""
e: GenTypes.StructEntry* = self._get_struct_entry(i)
if e is None or e.Ty is None:
return 0
return uint64(e.Ty)
def _struct_name_idx_insert(self, ei: int, name: str) -> int:
"""把 (name → ei) 写入名索引;返回 0=成功 / 1=失败
每次注册都占用一个槽(不按键去重):同键的多个条目按注册顺序沿探测链
递增排列,查找取链上首个"当前键相等"者即 = 原扫描的首个命中。若按键去重,
则最早的重复项被中和(Name[0]=0)后,被丢弃的后续重复项将无法找回。
"""
base: sizet* = self.StructNameIdx
if base is None or self.StructIdxCap <= 0:
return 1
h: uint64 = _struct_name_hash(name)
mask: sizet = sizet(self.StructIdxCap - 1)
idx: sizet = sizet(h) & mask
i: int = 0
while i < self.StructIdxCap:
slot: sizet* = (sizet*)(uint64(base) + uint64(idx * 8))
if slot[0] == 0:
slot[0] = sizet(ei + 1)
return 0
idx = (idx + 1) & mask
i += 1
return 1
def _struct_type_idx_insert(self, ei: int, tyval: uint64) -> int:
"""把 (Ty 指针值 → ei) 写入类型索引;0=成功 / 1=失败
与名索引同策略:每次注册都占槽(不去重),保证探测链上同键条目按注册
顺序排列,查找首个"当前 Ty 相等"者 = 原扫描首个命中。
"""
base: sizet* = self.StructTypeIdx
if base is None or self.StructIdxCap <= 0 or tyval == 0:
return 1
h: uint64 = _struct_ty_hash(tyval)
mask: sizet = sizet(self.StructIdxCap - 1)
idx: sizet = sizet(h) & mask
i: int = 0
while i < self.StructIdxCap:
slot: sizet* = (sizet*)(uint64(base) + uint64(idx * 8))
if slot[0] == 0:
slot[0] = sizet(ei + 1)
return 0
idx = (idx + 1) & mask
i += 1
return 1
def _struct_idx_add(self, ei: int, entry: GenTypes.StructEntry*) -> None:
"""新增条目后同步两张索引;任一写入失败 → 标记索引不可信(查找回退扫描)"""
if self.StructNameIdx is None or self.StructTypeIdx is None or self.StructIdxCap <= 0:
self.StructIdxBuilt = -1
return
failed: int = 0
if entry is not None and entry.Name is not None:
failed = failed + self._struct_name_idx_insert(ei, entry.Name)
if entry is not None and entry.Ty is not None:
failed = failed + self._struct_type_idx_insert(ei, uint64(entry.Ty))
if failed != 0:
self.StructIdxBuilt = -1
else:
# ei = StructCount-1,故同步后已建索引条目数 = StructCount
self.StructIdxBuilt = self.StructCount
def _struct_name_idx_find(self, name: str) -> int:
"""名索引查首注册下标:>=0 命中(= 原扫描首个同名条目下标);否则 -1
-1 表示"未命中或索引不可用",调用方须回退原线性扫描(保证正确性)。
探测链上同键条目按注册顺序排列,故首个"当前键相等"者即原扫描首个命中。
"""
base: sizet* = self.StructNameIdx
if base is None or self.StructIdxCap <= 0 or self.StructIdxBuilt != self.StructCount:
return -1
# 空名("")特例:被中和的条目 Name 恰为 ""(Name[0]=0),原扫描会命中首个
# 空名条目;索引槽按"注册时的原名"哈希放置,无法保证给出首个空名条目 →
# 空名一律回退原扫描,保证逐字节一致。
if name is None or name[0] == '\0':
return -1
h: uint64 = _struct_name_hash(name)
mask: sizet = sizet(self.StructIdxCap - 1)
idx: sizet = sizet(h) & mask
for i in range(self.StructIdxCap):
slot: sizet* = (sizet*)(uint64(base) + uint64(idx * 8))
sv: sizet = slot[0]
if sv == 0: return -1
se: int = int(sv) - 1
ek: str = self._struct_entry_name(se)
if ek is not None and string.strcmp(ek, name) == 0: return se
idx = (idx + 1) & mask
return -1
def _struct_type_idx_find(self, tyval: uint64) -> int:
"""类型索引查首注册下标:>=0 命中;-1 未命中/不可信(须回退扫描)
类型索引不保证覆盖全部 Ty 值(如 ClassGenDef 深拷贝后 existing.Ty 被
改写为新指针,未入索引)⇒ 未命中不能断言"确无",一律回退线性扫描。
"""
base: sizet* = self.StructTypeIdx
if base is None or self.StructIdxCap <= 0 or tyval == 0: return -1
if self.StructIdxBuilt != self.StructCount: return -1
h: uint64 = _struct_ty_hash(tyval)
mask: sizet = sizet(self.StructIdxCap - 1)
idx: sizet = sizet(h) & mask
for i in range(self.StructIdxCap):
slot: sizet* = (sizet*)(uint64(base) + uint64(idx * 8))
sv: sizet = slot[0]
if sv == 0:
return -1
se: int = int(sv) - 1
if self._struct_entry_tyval(se) == tyval: return se
idx = (idx + 1) & mask
return -1
def _get_field_entry(self, struct_entry: GenTypes.StructEntry*, i: int) -> GenTypes.FieldEntry*:
"""获取结构体中第 i 个字段条目"""
if struct_entry is None or i < 0 or i >= FIELD_MAX: return None
field_size: sizet = GenTypes.FieldEntry.__sizeof__()
addr: uint64 = uint64(struct_entry.Fields) + i * field_size
return GenTypes.FieldEntry*(addr)
def register_struct(self, pool: memhub.MemBuddy*,
name: str,
struct_ty: llvmlite.LLVMType*,
sha1: str = None) -> GenTypes.StructEntry*:
"""注册结构体类型,返回 StructEntry 指针
用类型指针去重(is 比较),不用类名去重。
这样跨模块同名类可以共存,各自有独立的字段定义。
sha1 参数在注册时直接设置 ModuleSha1(避免 set_struct_sha1 找错 entry)。
"""
if self.init_struct_table(pool) == 0: return None
# 用类型指针去重(同一类型不重复注册,支持跨模块同名类)
# 用 uint64 指针值比较(is 比较会触发类型安全检查解引用悬空 Ty)
# 走 find_struct_by_type(其 Ty 索引判定已整体停用、恢复原线性扫描),
# 去重语义 = 原去重扫描"首个 Ty 指针相等条目"(首注册优先),逐字节一致。
existing: GenTypes.StructEntry* = self.find_struct_by_type(struct_ty)
if existing is not None:
return existing
if self.StructCount >= STRUCT_MAX:
VLogger.error(f"table full, cannot register {name}", "STRUCT")
return None
entry: GenTypes.StructEntry* = self._get_struct_entry(self.StructCount)
if entry is None: return None
# 分配字段数组(1b 阶段用永久池,避免 reset 回收后 Fields 悬空)
field_size: sizet = GenTypes.FieldEntry.__sizeof__()
if self.StdlibAlloc != 0:
perm_sg: memhub.MemBuddy* = CompilerFactory.factory.PermPool
if perm_sg is None: perm_sg = pool
entry.Fields = perm_sg.alloc(field_size * FIELD_MAX)
else:
entry.Fields = pool.alloc(field_size * FIELD_MAX)
if entry.Fields is None: return None
string.memset(entry.Fields, 0, field_size * FIELD_MAX)
# 复制类名(1b/Phase2(StdlibAlloc=1)用永久池跨 reset 存活;1a 用主 pool,
# mark1 前不回收同样存活)。不复制则 find_struct_by_module 的 strcmp 读悬空 Name。
name_pool: memhub.MemBuddy* = pool
if self.StdlibAlloc != 0:
name_pool = CompilerFactory.factory.PermPool
if name_pool is None: name_pool = pool
name_len: sizet = string.strlen(name)
name_buf: str = name_pool.alloc(name_len + 1)
if name_buf is not None:
string.strcpy(name_buf, name)
entry.Name = name_buf
entry.Ty = struct_ty
entry.FieldCount = 0
# 注册时直接设置 SHA1(避免 set_struct_sha1 跨模块同名时找错 entry)
# SHA1 同样按 StdlibAlloc 选分配源(跨 1a/1b 存活,防 strcmp 悬空)
if sha1 is not None:
sha1_len: sizet = string.strlen(sha1)
sha1_buf: str = name_pool.alloc(sha1_len + 1)
if sha1_buf is not None:
string.strcpy(sha1_buf, sha1)
entry.ModuleSha1 = sha1_buf
self.StructCount += 1
# 同步名 | 类型索引(新条目下标 = StructCount-1)
self._struct_idx_add(self.StructCount - 1, entry)
return entry
# ============================================================
# ensure_forward_class - 按"类名 + 定义模块 SHA1"前向声明一个类
#
# 语义对齐 TPC(未注册类 → 具名空 CStruct 占位 + 后续 set_body 填充):
# 注解里出现"指向尚未注册的类的指针"时,返回 Ptr(本占位.Ty) 而不是匿名的
# Ptr(i8)——后者会丢类身份、冻结成 i8* 且无法修复(Gen↔Generator 循环依赖
# 使"按顺序先注册被指类"不可能)。定义侧命中该占位条目时**原位填充**同一
# Ty 对象(保持 Ty 指针不变),所有已发出的 Ptr 引用继续有效。
#
# 幂等:已存在 (class_name, sha1) 条目则直接返回,不重复登记。
# 返回占位/既有条目;参数非法或表满时返回 None。
# ============================================================
def ensure_forward_class(self, pool: memhub.MemBuddy*, sha1: str, class_name: str) -> GenTypes.StructEntry*:
if pool is None or sha1 is None or class_name is None:
return None
if sha1[0] == '\0' or class_name[0] == '\0':
return None
existing: GenTypes.StructEntry* = self.find_struct_by_module(class_name, sha1)
if existing is not None:
return existing
if self.init_struct_table(pool) == 0:
return None
if self.StructCount >= STRUCT_MAX:
VLogger.error(f"table full, cannot forward-declare {class_name}", "STRUCT")
return None
# 具名不透明结构体 "%{sha}.{Class}"(fields=None, fcount=0):名称与定义侧
# ClassGenDef 的 "sha1.ClassName" 逐字一致,故定义侧能按名定位并原位填充。
# 名称与 Ty 必须按 StdlibAlloc 选**持久池**(与 register_struct 同口径):
# 占位条目不随本文件 reset 回收,Ty/Name 若在瞬时 pool 会悬空。
fwd_pool: memhub.MemBuddy* = pool
if self.StdlibAlloc != 0:
fwd_pool = CompilerFactory.factory.PermPool
if fwd_pool is None:
fwd_pool = pool
sh_len: sizet = string.strlen(sha1)
cn_len: sizet = string.strlen(class_name)
name_buf: str = fwd_pool.alloc(sh_len + 1 + cn_len + 1)
if name_buf is None:
return None
string.strcpy(name_buf, sha1)
name_buf[sh_len] = '.'
string.strcpy(name_buf + sh_len + 1, class_name)
fwd_ty: llvmlite.LLVMType* = llvmlite.Struct(fwd_pool, None, 0, name_buf)
if fwd_ty is None:
return None
entry: GenTypes.StructEntry* = self.register_struct(pool, class_name, fwd_ty, sha1)
if entry is None:
return None
entry.IsForwardDecl = 1
self.add_visible_struct(pool, class_name)
return entry
def register_struct_alias(self, pool: memhub.MemBuddy*,
name: str,
sha1: str,
src_entry: GenTypes.StructEntry*) -> GenTypes.StructEntry*:
"""为已注册类追加包重导出别名 entry(方案A:ast.Attribute 归属 ast 包)
子模块类从包 __init__ 重导出时,查找端 _resolve_module_sha1 按
"__init__ 包回退"解析为包 sha1,但类以定义文件 sha1 注册 → name+sha1
双精确匹配错位 fail-fast(lookup_field_by_class 'ast.Attribute' 未找到
根因)。本函数绕过 register_struct 的 Ty 去重,追加同名 entry:
ModuleSha1=包 sha1,Ty/Fields/父类/虚表标志复用源 entry(同一类型,
仅归属模块不同,不影响 Ty 去重与 find_struct_by_type 指针匹配)。
幂等:目标 (name, sha1) 已注册则返回现有 entry。返回 None 失败。
"""
if name is None or sha1 is None or src_entry is None: return None
if self.init_struct_table(pool) == 0: return None
# 幂等:目标 (name, sha1) 已注册则跳过(find_struct_by_module 不回退裸名)
if sha1[0] != '\0':
existing: GenTypes.StructEntry* = self.find_struct_by_module(name, sha1)
if existing is not None: return existing
if self.StructCount >= STRUCT_MAX: return None
entry: GenTypes.StructEntry* = self._get_struct_entry(self.StructCount)
if entry is None: return None
# 复制类名与 SHA1(别名注册同样按 StdlibAlloc 选分配源,跨 1a/1b reset 存活)
alias_pool: memhub.MemBuddy* = pool
if self.StdlibAlloc != 0:
alias_pool = CompilerFactory.factory.PermPool
if alias_pool is None: alias_pool = pool
name_len: sizet = string.strlen(name)
name_buf: str = alias_pool.alloc(name_len + 1)
if name_buf is None: return None
string.strcpy(name_buf, name)
entry.Name = name_buf
sha1_len: sizet = string.strlen(sha1)
sha1_buf: str = alias_pool.alloc(sha1_len + 1)
if sha1_buf is None: return None
string.strcpy(sha1_buf, sha1)
entry.ModuleSha1 = sha1_buf
# 复用源 entry 的类型、字段与类属性(同一类,仅归属模块不同)
entry.Ty = src_entry.Ty
entry.FieldCount = src_entry.FieldCount
entry.Fields = src_entry.Fields
entry.IsUnion = src_entry.IsUnion
entry.IsPacked = src_entry.IsPacked
entry.IsOOP = src_entry.IsOOP
entry.HasInit = src_entry.HasInit
entry.HasNew = src_entry.HasNew
entry.HasVTable = src_entry.HasVTable
entry.IsNoVTable = src_entry.IsNoVTable
entry.ParentName = src_entry.ParentName
entry.ParentNames = src_entry.ParentNames
entry.ParentCount = src_entry.ParentCount
entry.VTableMethodCount = src_entry.VTableMethodCount
entry.VTableMethods = src_entry.VTableMethods
entry.MethodsEmitted = 0
entry.SpecTriggerSha1 = None
self.StructCount += 1
# 同步名 | 类型索引(别名条目与源条目同名 | 同 Ty,索引保留首注册下标)
self._struct_idx_add(self.StructCount - 1, entry)
return entry
def set_struct_sha1(self, pool: memhub.MemBuddy*, class_name: str, sha1: str) -> int:
"""设置结构体所属模块的 SHA1,返回 1=成功 / 0=失败"""
if class_name is None or sha1 is None:
return 0
entry: GenTypes.StructEntry* = self.find_struct(class_name)
if entry is None:
return 0
# 复制 SHA1 字符串(StdlibAlloc=1 用永久池,跨 1a/1b 存活防 strcmp 悬空)
sha1_len: sizet = string.strlen(sha1)
sha1_pool: memhub.MemBuddy* = pool
if self.StdlibAlloc != 0:
sha1_pool = CompilerFactory.factory.PermPool
if sha1_pool is None:
sha1_pool = pool
sha1_buf: str = sha1_pool.alloc(sha1_len + 1)
if sha1_buf is None: return 0
string.strcpy(sha1_buf, sha1)
entry.ModuleSha1 = sha1_buf
return 1
def get_struct_sha1(self, class_name: str) -> str:
"""获取结构体所属模块的 SHA1,返回 None=无 SHA1"""
if class_name is None or self.StructTable is None:
return None
entry: GenTypes.StructEntry* = self.find_struct(class_name)
if entry is None:
return None
return entry.ModuleSha1
def add_field(self, pool: memhub.MemBuddy*,
struct_entry: GenTypes.StructEntry*,
field_name: str,
field_ty: llvmlite.LLVMType*,
default_val: ast.AST* = None,
annot_class_name: str = None) -> int:
"""向结构体添加字段,返回字段索引(-1 失败)
default_val: 字段默认值 AST 节点(可选,None=无默认值)
annot_class_name: 原始类型注解的类名(可选,用于联合类型字段的方法调用解析)
"""
if struct_entry is None or field_name is None or field_ty is None: return -1
if struct_entry.FieldCount >= FIELD_MAX:
VLogger.error(f"field table full for {struct_entry.Name}", "STRUCT")
return -1
idx: int = struct_entry.FieldCount
fe: GenTypes.FieldEntry* = self._get_field_entry(struct_entry, idx)
if fe is None: return -1
# 复制字段名(StdlibAlloc=1 用永久池跨 reset 存活);否则 lookup_field 的
# strcmp(fe.Name, ...) 读悬空越界。
fname_pool: memhub.MemBuddy* = pool
if self.StdlibAlloc != 0:
fname_pool = CompilerFactory.factory.PermPool
if fname_pool is None: fname_pool = pool
name_len: sizet = string.strlen(field_name)
# 带调用点 tag:写入者若为该 32B 附近的字段名缓冲,报文直接指名本处。
name_buf: str = fname_pool.alloc_tagged(name_len + 1, memhub.DETSITE_STRUCTGENMIXIN_570)
if name_buf is not None:
string.strcpy(name_buf, field_name)
fe.Name = name_buf
fe.Index = idx
# 1b 阶段(StdlibAlloc=1)字段类型必须深拷贝到 stdlib:字段级 Ty 若仍存 pool
# 指针,本文件 reset_to(mark1) 回收后跨文件读取(字段展平、多继承 this 调整、
# _apply_struct_defaults 的 build_gep_struct 用 fe.Ty 作 GEP 结果类型)会解引用
# 悬空内存。
# 注:注解类名已并入 Spec.SourceName,Spec 一律 PermPool 分配 ⇒ 天然跨 reset 存活。
if self.StdlibAlloc != 0:
dp_pool_sg: memhub.MemBuddy* = CompilerFactory.factory.PermPool
if dp_pool_sg is None: dp_pool_sg = pool
ty_copy: llvmlite.LLVMType* = llvmlite.DeepCopyType(dp_pool_sg, field_ty)
if ty_copy is not None: fe.Ty = ty_copy
else: fe.Ty = field_ty
# DefaultVal:跨文件结构体构造(_translate_struct_ctor→_apply_struct_defaults)
# 在调用方文件翻译时读取字段默认值,原 pool AST 已 reset 回收 → 常量深拷贝
# 到 perm_pool(1a 每文件 reset 后 DefaultVal 同样必须跨文件存活)
fe.DefaultVal = self._deep_copy_default_ast(dp_pool_sg, default_val)
else:
fe.Ty = field_ty
fe.DefaultVal = default_val
# 实例一律 PermPool 分配(驻留或独立副本)⇒ 天然跨 1b reset 存活。
arena_f: TSpec.TypeSpecArena* = self.Gen.TypeGenBase.ensure_spec_arena()
if arena_f is not None:
fe.Spec = TSpec.SpecFromLLVMType(arena_f, field_ty)
# [S4 注解侧填 Spec] 补字段 Spec 的符号性与注解来源名(annot_class_name
# 携带真实无符号性与注解书写形态名;LLVM 反推恒 TS_INT 且无名)。
# 类型信息缺失(Spec 为 None)时建 TS_UNKNOWN 承载注解名(不表态具体类型)。
if annot_class_name is not None:
if fe.Spec is None:
fe.Spec = TSpec.NewTypeSpec(arena_f, TSpec.TS_UNKNOWN, 0, TSpec.TS_SIGN_UNKNOWN)
if fe.Spec is not None:
sign_f: int = self.Gen.TypeGenCType.spec_sign_of_annot(annot_class_name)
if sign_f != TSpec.TS_SIGN_UNKNOWN:
fe.Spec = TSpec.SpecWithSign(arena_f, fe.Spec, sign_f)
fe.Spec = TSpec.SpecWithSource(fe.Spec, annot_class_name)
# const 标记显式置 0(不依赖条目内存的零初值约定):本字段由 ClassGenDef
# 在确认为 const 对象时才置 1,显式清零杜绝"未初始化即被读成 1"的误报。
fe.IsConst = 0
struct_entry.FieldCount = idx + 1
return idx
def _deep_copy_default_ast(self, pool: memhub.MemBuddy*, default_val: ast.AST*) -> ast.AST*:
"""深拷贝字段默认值 AST 到永久池(仅常量节点),跨 1b reset 存活。
字段默认值(fe.DefaultVal)除定义文件内 _generate_before_init 使用外,
跨文件结构体构造(_translate_struct_ctor → _apply_struct_defaults)会在
调用方文件翻译时读取——原 pool AST 已 reset 回收必须深拷贝。
支持 Constant(标量)与 List(列表字面量,如 t.CArray[str] = [...])。
其他非常量默认值(Name/BinOp 等引用表达式)无法安全深拷贝:返回 None
(调用方 fe.DefaultVal 保持 None,仅定义文件内 _generate_before_init
仍持有原始 pool 指针,不受影响)。
"""
if default_val is None: return None
if default_val.kind() == ast.ASTKind.List: return self._deep_copy_list_ast(pool, default_val)
if default_val.kind() != ast.ASTKind.Constant: return None
src_c: ast.Constant* = ast.Constant*(default_val)
dst_c: ast.Constant* = ast.Constant*(pool.alloc_tagged(ast.Constant.__sizeof__(), memhub.DETSITE_STRUCTGENMIXIN_641))
if dst_c is None:
return None
string.memset(dst_c, 0, ast.Constant.__sizeof__())
# 拷贝 vtable 指针(AST 是 @t.CVTable,kind() 虚调用依赖首字段 vptr)
# 【必须与 _deep_copy_list_ast 同款:c.Deref 读首字 + c.DerefAs 写首字】
# 旧写法(本行 + 下一行)在 TPC 下**静默失效**,是 vptr=0 AST 节点的产生源:
# ① `vt_ptr: rawptr = rawptr(*(uint64*)(default_val))` 被折成裸 0
# ——IR 实证 `inttoptr i32 0 to i8*`(**没有 load vptr 的指令**);
# ② `*dst_c = vt_ptr`(目标 LHS 是结构体类型的解引用)**根本不发射任何
# 指令**——全模块无任何指令带 `!dbg !425`。
# ⇒ dst_c 的前 8 字节保持 memset 的 0(vptr = 0),随后对它的
# `node.kind()` 虚调用生成 `load i8*, i8** [0 + 0]` ⇒ ASan READ at 0x0。
# 逐字 IR 证据:`TransPyV\output\b5883dc9c114a604.ll:10382-10384`
# (!424 = 本函数 :587 行;!426 = :590 行;两者之间无任何指令)。
# 对照可用写法(List 分支,同文件 :10611-10615):`bitcast AST* → i64*` +
# `load i64` + `inttoptr i64 → i8*` + `bitcast List* → i8**` + `store i8*`。
vt_ptr_c: t.CPtr = t.CPtr(c.Deref((uint64*)(default_val)))
c.DerefAs(dst_c, vt_ptr_c)
# 拷贝位置字段(报错定位用)
dst_c.lineno = src_c.lineno
dst_c.col_offset = src_c.col_offset
dst_c.end_lineno = src_c.end_lineno
dst_c.end_col_offset = src_c.end_col_offset
# 拷贝常量值(int/float/str/bool 由 const_kind 区分)
dst_c.const_kind = src_c.const_kind
dst_c.int_val = src_c.int_val
dst_c.float_val = src_c.float_val
if src_c.str_val is not None:
s_len: sizet = string.strlen(src_c.str_val)
s_copy: str = pool.alloc_tagged(s_len + 1, memhub.DETSITE_STRUCTGENMIXIN_659)
if s_copy is not None:
string.strcpy(s_copy, src_c.str_val)
dst_c.str_val = s_copy
return (ast.AST*)(dst_c)
def _deep_copy_list_ast(self, pool: memhub.MemBuddy*, default_val: ast.AST*) -> ast.AST*:
"""深拷贝 List 节点(列表字面量默认值)到永久池,跨 1b reset 存活。
elts 容器手动在永久池构造(list 的 __pool__ 置 None:深拷贝的 elts
只被只读访问 get/__len__/迭代,不 append,不会触发 pool 扩容)。
元素递归调用 _deep_copy_default_ast(支持 Constant 与嵌套 List)。
"""
src_l: ast.List* = ast.List*(default_val)
dst_l: ast.List* = ast.List*(pool.alloc(ast.List.__sizeof__()))
if dst_l is None: return None
string.memset(dst_l, 0, ast.List.__sizeof__())
# 拷贝 vtable 指针(AST 是 @t.CVTable,kind() 虚调用依赖首字段 vptr)
vt_l: t.CPtr = t.CPtr(c.Deref((uint64*)(default_val)))
c.DerefAs(dst_l, vt_l)
# 拷贝位置字段与 ctx
dst_l.lineno = src_l.lineno
dst_l.col_offset = src_l.col_offset
dst_l.end_lineno = src_l.end_lineno
dst_l.end_col_offset = src_l.end_col_offset
dst_l.ctx = src_l.ctx
# 深拷贝 elts:stdlib 手动构造 list[AST | t.CPtr] 容器
src_elts_l: list[ast.AST*]* = src_l.elts
if src_elts_l is not None:
cnt_l: sizet = len(src_elts_l)
lst_copy_l: list[ast.AST*]* = (list[ast.AST*] | t.CPtr)(pool.alloc_tagged(list[ast.AST*].__sizeof__(), memhub.DETSITE_STRUCTGENMIXIN_693))
if lst_copy_l is not None:
string.memset(lst_copy_l, 0, list[ast.AST*].__sizeof__())
# 计数**绝不先按源计数写死**:必须在填充循环里按"实际写入槽数"落定
# (下方 written_l)。否则源元素为空指针 / 不可深拷贝而被静默跳过时,
# 会留下 __count__ > 实际填充数 的零槽 ⇒ 后续 _init_global_array_from_list
# 的 elts.get(i) 读到裸 0 ⇒ 空 AST 元素崩溃(ASan access-violation 0x0)。
# 本处只先定容量。
lst_copy_l.__capacity__ = cnt_l
# 元素尺寸的唯一真相源:申请(cnt_l * elem_sz_l)、清零(同式)与
# 写入(槽基址按 elem_sz_l 步进)必须同源;写成两处字面量 8 会让
# "申请 n*8 却按别的宽度写"重新变得可能。
elem_sz_l: sizet = 8
lst_copy_l.__elem_size__ = elem_sz_l
lst_copy_l.__pool__ = None
data_l: t.CPtr = pool.alloc_tagged(cnt_l * elem_sz_l,
memhub.DETSITE_STRUCTGENMIXIN_698)
if data_l is not None:
string.memset(data_l, 0, cnt_l * elem_sz_l)
lst_copy_l.__data__ = data_l
written_l: sizet = 0
li_l: sizet
for li_l in range(cnt_l):
elem_src_l: ast.AST* = src_elts_l.get(li_l)
# 显式判空(本仓硬约定:指针判空一律 `is None`)。上一轮曾写作
# 整型字面量比较 `elem_src_l == 0` 并附注"与 `is None` 同义":
# 该注的 IR 事实对(`x == 0` 同样编译为 `icmp eq <ptr>, null`,
# 逐字证据见 AssignGenMixin.vp:1126-1145 记录的反例现场),
# 但结论表述误导——`== 0` 并非无效判据,只是不该作为本仓判空写法。
# 源列表自身存在空元素,说明产生源已损坏(计数 > 实际填充)
# ——响亮失败,绝不静默补 0。
if elem_src_l is None:
self.Gen.TypeGenErr.fatal_error(default_val,
f"字段默认值深拷贝:列表字面量第 {li_l} 个元素为空 AST 元素(指针为 0)。"
f"上层某处产生了空 AST 元素(元素计数与实际填充不一致),"
f"拒绝静默补 0 生成残缺的默认值;已终止编译。",
"ValueError")
return None
elem_dst_l: ast.AST* = self._deep_copy_default_ast(pool, elem_src_l)
if elem_dst_l is None:
self.Gen.TypeGenErr.fatal_error(elem_src_l,
f"字段默认值深拷贝:列表字面量第 {li_l} 个元素(kind={elem_src_l.kind()})"
f"既非 Constant 也非 List,无法深拷贝到永久池。若在此静默跳过该槽,"
f"会形成【计数 > 实际填充数】的空槽(后续 elts.get 读到裸 0 崩溃);"
f"若静默截短计数则会改变数组初始化长度。两者都是静默降级,故响亮失败。",
"TypeError")
return None
# 槽元素类型必须是**指针**:`ast.AST*` 的 `[0]` 是
# 对象 `ast.AST`(48 字节)⇒ `slot[0] = <指针>` 被静默按
# "结构体赋值"编译成 48 字节 load + 48 字节 store,而本
# 缓冲只申请 cnt_l*elem_sz_l ⇒ 每槽越界 40 字节,写穿本块
# canary 与右邻空闲块首字(三代 [FREEFRAME] 现场根因)。
# 按本仓既有合并规则(hashtable.vp:82-85):`X | t.CPtr`
# = Ptr(resolve(X)) ⇒ 取下标得到**指针元素** ⇒ 8 字节
# 指针 store,与申请宽度同源。
slot_l: ast.AST** = (ast.AST**)(uint64(data_l) + li_l * elem_sz_l)
slot_l[0] = elem_dst_l
written_l += 1
# 计数 == 实际填充数(成功时 == cnt_l;任何不一致都已在上面终止)。
lst_copy_l.__count__ = written_l
dst_l.elts = lst_copy_l
return (ast.AST*)(dst_l)
def get_struct_count(self) -> int:
"""返回当前已注册的结构体数量"""
return self.StructCount
def _resolve_module_sha1(self, mod_name: str,
from_imports: str = None) -> str:
"""模块名 → 模块 SHA1(精确 → import 别名还原后再查 → 后缀兜底)
模块名可能是:模块真名(fat32_types)、包后缀(sched.sched)、
import 别名(import X as Y 的 Y)。逐级尝试:
1. LookupSha1ByModName:精确 + "__init__" 包回退
2. LookupModNameByAlias:from_imports "别名:模块真名" 还原后
精确/后缀重试——必须优先于裸名后缀匹配:store 可能同时存在
"vpsdk.process" 与 "VKernel.Kernel.sched.process",裸名
"process" 后缀匹配会命中先扫描的内核模块(别名 process 被
劫持 → 跨模块调用解析到未编译的内核 → Phase A _deps_pending
永不收敛,Scene3D root cause)
3. LookupSha1ByModNameSuffix:后缀匹配(store 键是全路径模块名,
如 "Kernel.drivers.fs.fat32.fat32_types"),非别名裸名兜底
"""
if mod_name is None: return None
s_rm: str = StubMergerStore.LookupSha1ByModName(mod_name)
if s_rm is None and from_imports is not None:
real_rm: str = StubMergerStore.LookupModNameByAlias(from_imports, mod_name)
if real_rm is not None:
s_rm = StubMergerStore.LookupSha1ByModName(real_rm)
if s_rm is None:
s_rm = StubMergerStore.LookupSha1ByModNameSuffix(real_rm)
if s_rm is None:
s_rm = StubMergerStore.LookupSha1ByModNameSuffix(mod_name)
return s_rm
def find_struct_entry_with_ty(self, name: str, alt_name: str) -> GenTypes.StructEntry*:
"""按名字(含备选名)查**带有效 Ty** 的结构体条目;找不到返回 None。
为什么需要它:`find_struct` 的语义是"首注册命中"。D30 的 REnum 重注册把旧
entry **就地中和**(`Name[0]='\\0'` / `Ty=None` / `FieldCount=0`)后再追加新
entry;若名索引/线性扫描先命中那条已中和的旧条目,调用方拿到的 `Ty` 是 None ——
依赖 `Ty` 的代码(如 match 的 REnum 变体字段绑定)会**静默跳过**,症状表现为
case 绑定名未定义(NameError,真因被藏到下游)。本函数跳过 `Ty` 无效的同名条目,
返回首个 `Ty` 有效的同名(或备选名)条目;都没有则返回 None,由调用方决定报错。
name / alt_name:主名与备选名(如模块限定名与裸类名),任一命中即可。
"""
if self.StructTable is None:
return None
for i in range(self.StructCount):
entry: GenTypes.StructEntry* = self._get_struct_entry(i)
if entry is None or entry.Name is None or entry.Ty is None:
continue
if entry.Name[0] == '\0':
continue
if string.strcmp(entry.Name, name) == 0:
return entry
if alt_name is not None and string.strcmp(entry.Name, alt_name) == 0:
return entry
return None
def find_struct(self, name: str) -> GenTypes.StructEntry*:
"""按类名查找结构体,返回 StructEntry 或 None
支持模块限定名(如 "HT.Translator"):
第一遍精确匹配,第二遍按类名(最后一个 '.' 之后的部分)回退。
模块可解析的限定名(module.Class)按模块归属精确匹配,不做裸名
跨库回退(防 sched.Thread 被 vthreading.Thread 顶掉的同类问题);
仅模块名无法解析时保留裸名回退(兼容旧行为)。
"""
if name is None or self.StructTable is None:
return None
# 第一遍:精确匹配
# StructCount 合理性守卫(内存损坏导致巨大值 → 索引循环假挂起)
if self.StructCount < 0 or self.StructCount > 200000:
VLogger.error(f"StructCount 异常: {self.StructCount} (find_struct)", "STRUCT")
stdlib.exit(1)
# 名索引快速路径:命中即首注册下标(语义 = 原精确匹配扫描首个命中)。
fi: int = self._struct_name_idx_find(name)
if fi >= 0: return self._get_struct_entry(fi)
# 名索引可信(Built==StructCount | 索引存在 | 查询名非空)时,索引未命中即
# "确无同名条目":条目在注册时即命名并同步入索引,之后名至多被中和为 ""
# (ClassGenDef/ClassGenEnum 置 Name[0]=0),故任何非空查询名的命中项必在
# 索引探测链上 ⇒ 可跳过下方 O(n) 精确扫描(热点:本扫描占 find_struct 的
# 全部 strcmp)。索引不可信 / 无索引 / 空名查询时仍回退原线性扫描
# (正确性保证,非兜底);精确匹配确无时继续走模块限定名/裸名回退逻辑。
name_idx_trusted: int = 0
if self.StructNameIdx is not None and self.StructIdxCap > 0 and self.StructIdxBuilt == self.StructCount:
name_idx_trusted = 1
if name_idx_trusted == 0 or name[0] == '\0':
for i in range(self.StructCount):
entry: GenTypes.StructEntry* = self._get_struct_entry(i)
if entry is not None and entry.Name is not None:
if string.strcmp(entry.Name, name) == 0:
return entry
# 模块限定名(module.Class):模块可解析时按模块归属精确匹配,
# 防止裸名回退抓跨模块同名类。模块未命中时若裸名类唯一(无冲突)
# 仍回退,避免误杀未登记模块的既有解析。
# 取点跳过泛型下标(numpy.ndarray[t.CDouble] → numpy + ndarray[t.CDouble]),
# 否则 strrchr 落到下标内部截错模块名(同 lookup_field_by_class 根因)。
dot_pos_q: str = self._last_dot_outside_brackets(name)
if dot_pos_q is not None and dot_pos_q[1] != '\0':
bare_f: str = dot_pos_q + 1
mod_len_f: sizet = string.strlen(name) - string.strlen(bare_f) - 1
if mod_len_f > 0:
mod_buf_f: str = stdlib.malloc(mod_len_f + 1)
if mod_buf_f is not None:
string.strncpy(mod_buf_f, name, mod_len_f)
mod_buf_f[mod_len_f] = '\0'
if string.strcmp(mod_buf_f, "t") != 0 and string.strcmp(mod_buf_f, "c") != 0:
# 模块段是 16 位 hex = SHA1 字面量(如 "{9aab}.Scene3D",
# TypeGenResolve L1397-1402 的 sha1 前缀形态):跳过 store
# 模块名查询(store 键是模块名不是 sha1),直接按 name+sha1
# 精确匹配。否则 _resolve_module_sha1("9aab") 返回 None →
# 落到第二遍裸名回退 → 跨模块同名类(VKernel gfx 与 App gfx
# 都定义 Scene3D)返回先注册的错误 entry(Scene3D 归属错乱
# → ret_sha1=0ae → Phase A _deps_pending 死锁根因)。
# [C10 收敛] 模块段恰为 16 位小写 hex = SHA1 字面量:
# 统一走 GenApi.is_sha1_hex16(该判定的唯一真源)。
mod_hex_ok: int = GenApi.is_sha1_hex16(mod_buf_f)
if mod_hex_ok != 0:
me_h: GenTypes.StructEntry* = \
self.find_struct_by_module(bare_f, mod_buf_f)
stdlib.free(mod_buf_f)
return me_h
mod_sha1_f: str = self._resolve_module_sha1(mod_buf_f)
if mod_sha1_f is not None:
me_f: GenTypes.StructEntry* = \
self.find_struct_by_module(bare_f, mod_sha1_f)
if me_f is not None:
stdlib.free(mod_buf_f)
return me_f
# 模块类未注册:统计裸名同名类数量,唯一时回退(无冲突)
same_cnt_f: int = 0
same_entry_f: GenTypes.StructEntry* = None
for i2 in range(self.StructCount):
e2: GenTypes.StructEntry* = self._get_struct_entry(i2)
if e2 is not None and e2.Name is not None:
if string.strcmp(e2.Name, bare_f) == 0:
same_cnt_f += 1
same_entry_f = e2
stdlib.free(mod_buf_f)
if same_cnt_f == 1:
return same_entry_f
return None
stdlib.free(mod_buf_f)
# 第二遍:类名回退(处理模块限定名 "HT.Translator" → "Translator",
# 仅模块名无法解析时走到这里;取点同样跳过泛型下标)
dot_pos: str = self._last_dot_outside_brackets(name)
if dot_pos is not None:
class_name: str = dot_pos + 1
if class_name is not None and class_name[0] != '\0':
# 同名唯一性检查(对齐上方限定名路径):跨模块同名类(如 VKernel gfx
# 与 App gfx 都定义 Scene3D)存在时不得返回先注册的错误 entry——否则
# 模块段无法解析的限定名被劫持到错误模块 → 返回类型解析查错 text.ll。
same_cnt_b: int = 0
same_entry_b: GenTypes.StructEntry* = None
for i in range(self.StructCount):
entry: GenTypes.StructEntry* = self._get_struct_entry(i)
if entry is not None and entry.Name is not None:
if string.strcmp(entry.Name, class_name) == 0:
same_cnt_b += 1
same_entry_b = entry
if same_cnt_b == 1:
return same_entry_b
return None
def find_struct_by_module(self, name: str, sha1: str) -> GenTypes.StructEntry*:
"""按类名 + SHA1 查找结构体,返回 StructEntry 或 None
SHA1 非 None 时:精确匹配 name+sha1,未找到返回 None(不回退,避免跨模块同名找错)
SHA1 为 None 时:回退到 find_struct(name) 按类名查找第一个
"""
if name is None or self.StructTable is None:
return None
# StructCount 合理性守卫(内存损坏导致巨大值 → 索引循环假挂起)
if self.StructCount < 0 or self.StructCount > 200000:
VLogger.error(f"StructCount 异常: {self.StructCount} (find_struct_by_module)", "STRUCT")
stdlib.exit(1)
if sha1 is not None:
# 名索引快速路径:同名条目都位于 >= 首个同名下标处,故可从该下标起扫描
# name+sha1(等价原全表扫描"首个 name 且 sha1 命中")。
# 未命中/索引不可信 ⇒ start=0,走原全表扫描(保证正确性)。
start: int = 0
fri: int = self._struct_name_idx_find(name)
if fri >= 0:
start = fri
for i in range(start, self.StructCount):
entry: GenTypes.StructEntry* = self._get_struct_entry(i)
if entry is not None and entry.Name is not None:
# 快速路径:strcmp 判等蕴含首字符相等,首字符不同者跳过 strcmp。
if entry.Name[0] == name[0] and string.strcmp(entry.Name, name) == 0:
if entry.ModuleSha1 is not None:
# 同上:sha1 判等蕴含首字符相等,先比首字符再 strcmp。
if entry.ModuleSha1[0] == sha1[0] and string.strcmp(entry.ModuleSha1, sha1) == 0:
return entry
# SHA1 查找失败:不回退到 find_struct(会返回跨模块同名错误 entry)
# 返回 None 让调用方创建新类型,避免 Phase B 用错误类型短路
return None
# 无 SHA1:按类名查找第一个
return self.find_struct(name)
def reset_module_methods_emitted(self, sha1: str) -> int:
"""回滚指定模块注册类的 MethodsEmitted 标志(Phase A 重试前调用)
根因(追加87):Phase A 第 1 遍翻译类方法体时 _translate_oop_methods
先置 MethodsEmitted=1(防重入)再发射方法;方法体内跨模块调用因依赖
text.ll 未生成触发 trans._deps_pending → 整文件不写盘下遍重译。但
MethodsEmitted 存于 perm_pool 分配的 StructEntry,跨 mb.reset_to(mark2)
存活 → 第 2 遍 existing 分支被 _translate_oop_methods 的
MethodsEmitted==1 防重复检查拦截 → 类方法(含 __before_init__)永不
发射 → 链接 undefined symbol(模块级函数不受此标志保护所以正常,
AssignGenMixin text.ll 空 / HandlesTranslator 有模块函数缺类方法)。
按本文件 ModuleSha1 精确回滚,不影响其他已写盘文件的 MethodsEmitted。
"""
if sha1 is None or self.StructTable is None:
return 0
if self.StructCount < 0 or self.StructCount > 200000:
VLogger.error(f"StructCount 异常: {self.StructCount} (reset_module_methods_emitted)", "STRUCT")
return 0
reset_count: int = 0
for i in range(self.StructCount):
entry: GenTypes.StructEntry* = self._get_struct_entry(i)
if entry is not None:
# 普通类按 ModuleSha1 匹配
if entry.ModuleSha1 is not None:
if string.strcmp(entry.ModuleSha1, sha1) == 0:
entry.MethodsEmitted = 0
reset_count += 1
continue
# 泛型特化类按触发文件 sha1 匹配:特化类 ModuleSha1 是模板 sha1(方法名
# 带模板前缀),无法匹配当前作废文件。不重置则特化方法体残留
# MethodsEmitted=1,下遍不补译 → text.ll 只有 call 无 define。
if entry.SpecTriggerSha1 is not None:
if string.strcmp(entry.SpecTriggerSha1, sha1) == 0:
entry.MethodsEmitted = 0
reset_count += 1
return reset_count
def find_struct_by_type(self, struct_ty: llvmlite.LLVMType*) -> GenTypes.StructEntry*:
"""按类型指针查找结构体(指针值比较),返回 StructEntry 或 None
注意: 不能用 `entry.Ty is struct_ty` 的 is 比较——TPV 编译 is 时会触发
translate_compare 的类型安全检查(is_ptr_type/TypePrint 递归打印),
对悬空 entry.Ty(1b 泛型特化类注册后被 reset 回收的 pool 指针)解引用崩溃。
改用 uint64 纯指针值比较,不解引用任何内容。
"""
if struct_ty is None or self.StructTable is None:
return None
# StructCount 合理性守卫(内存损坏导致巨大值 → 索引循环假挂起)
if self.StructCount < 0 or self.StructCount > 200000:
VLogger.error(f"StructCount 异常: {self.StructCount} (find_struct_by_type)", "STRUCT")
stdlib.exit(1)
st_val: uint64 = uint64(struct_ty)
# Ty 索引整体停用判定(索引结构仍由 _struct_idx_add 维护,但不用于此处):
# 条目 Ty 可能被深拷贝改写为新指针(ClassGenDef),索引槽按"注册时的 Ty 值"
# 落位,改写后该条目的 live 键与槽位脱钩 ⇒ 索引命中返回的未必是"原线性扫描
# 首个 Ty 相等条目",命中路径与原语义不等价、且无法低成本保证等价。故此处
# 恢复原线性精确扫描(未命中/命中行为均与未加索引前逐字节一致)。
for i in range(self.StructCount):
entry: GenTypes.StructEntry* = self._get_struct_entry(i)
if entry is not None and entry.Ty is not None:
if uint64(entry.Ty) == st_val:
return entry
return None
def struct_type_is_packed(self, struct_ty: llvmlite.LLVMType*) -> int:
"""判断命名结构体是否 packed(@c.Attribute(t.attr.packed)),返回 1/0
先按类型指针身份匹配(find_struct_by_type,跨模块 deep copy 后仍
指向同一 entry.Ty)、miss 时按结构体名回退(等价类型实例),最终读
StructEntry.IsPacked(B5 在定义侧/stub 注入侧写入)。供 __sizeof__
尺寸与字段偏移计算:packed 结构体无字段填充,大小=字段尺寸之和。
"""
if struct_ty is None:
return 0
sname: str = llvmlite.get_struct_name(struct_ty)
if sname is None:
return 0
entry: GenTypes.StructEntry* = self.find_struct_by_type(struct_ty)
if entry is None:
entry = self.find_struct(sname)
if entry is None:
return 0
return entry.IsPacked
# ============================================================
# verify_field_slot_consistency - 字段表 ↔ 结构体槽位一致性护栏(fail-fast)
#
# 背景(真实崩溃,2026-09-27):模块 8a96a2885bae09ca(ExprGenOps)把
# `ast.Name.id` 编译成 GEP 下标 9(该槽是 ctx,i32),而**同一次构建**里
# 320b55907b889d59(AssignGenMixin)用下标 8(i8*)——两者共用同一 %struct
# 布局(stub.ll:560 = {i8*, AST*, MemManager*, i32×4, list*, i8*(id), i32(ctx)})。
# 后果:`_apply_aug_op` → `_node_type_unsigned` 读 `nm.id` 得到 ctx
# (ASTCtx.Store = 1),`inttoptr i32 1 → i8*` 后进 strlen ⇒
# ASan access-violation at 0x1(IR 实证:8a96a2885bae09ca.text.ll:1510-1517)。
#
# `FieldEntry.Index` 全仓唯一写者是 StructGenMixin.add_field(值 = 当时 FieldCount),
# 而结构体 type 的槽位由**同一字段列表**另一路生成;两者一旦错位,按 Index
# 算出的 GEP 就会读到相邻字段(指针被当整数读,或反之)。此处宁可立即报错并
# 指名 类/字段表条数/槽位数/条目与槽位类型,也不放行按错误偏移算出的 GEP。
#
# 只判"明确不同"的类别(指针性 / 整数位宽 / 浮点位宽);结构体/数组等结构类型
# 不做值比较(避免 DeepCopyType 副本、递归定义的假阳性)。
# 空字段表(FieldCount=0,vpi 注入的"仅签名类")不判:既有语义是"按名查不到
# 返回 None",由调用方处理。
# ============================================================
def verify_field_slot_consistency(self, struct_ty: llvmlite.LLVMType*,
entry: GenTypes.StructEntry*) -> int:
"""字段表条数须等于结构体槽位数;每条目 Ty 须与其 Index 槽位同类"""
if struct_ty is None or entry is None:
return 0
if entry.FieldCount <= 0:
return 0
# 联合体豁免:union 的字段表有 N 条(每个成员一条),但 LLVM 布局只有 **1 个
# 槽位**(成员全部偏移 0、按最大成员定宽),二者本就不相等;字段访问走的是
# bitcast 到字段类型后的 GEP(见 ExprGenAttr 的 union 分支),不依赖槽位下标,
# 故本护栏不适用(误报实证:EdgeCaseTest 的 `class Data(t.CUnion)`,2 成员 → 1 槽)。
if entry.IsUnion != 0 or self.is_union_by_type(struct_ty) != 0:
return 0
fcount: int = llvmlite.get_struct_fcount(struct_ty)
if fcount <= 0:
return 0
if entry.FieldCount != fcount:
self.Gen.TypeGenErr.fatal_error(None,
f"结构体 '{entry.Name}' 的字段表条数({entry.FieldCount})与 LLVM 槽位数({fcount})不一致:"
f"字段条目与结构体布局由两条路径产生,错位会让字段访问按错误下标算 GEP"
f"(实证:ast.Name.id 落到 ctx 槽 ⇒ 指针被读成整数 1 ⇒ strlen(0x1) 崩溃)。",
"TypeError")
return 0
node: rawptr[llvmlite.ParamNode] = llvmlite.get_struct_fields(struct_ty)
i: int = 0
while i < entry.FieldCount and node is not None:
fe: GenTypes.FieldEntry* = self._get_field_entry(entry, i)
slot_ty: llvmlite.LLVMType* = llvmlite.paramnode_get_ty(node)
if fe is not None and fe.Name is not None and fe.Ty is not None and slot_ty is not None:
ptr_f: int = self.Gen.ExprGenBase.is_ptr_type(fe.Ty)
ptr_s: int = self.Gen.ExprGenBase.is_ptr_type(slot_ty)
bits_f: int = self.Gen.ExprGenBase.get_llvm_type_bits(fe.Ty)
bits_s: int = self.Gen.ExprGenBase.get_llvm_type_bits(slot_ty)
fbits_f: int = self.Gen.ExprGenBase.get_llvm_float_bits(fe.Ty)
fbits_s: int = self.Gen.ExprGenBase.get_llvm_float_bits(slot_ty)
if ptr_f != ptr_s or bits_f != bits_s or fbits_f != fbits_s:
self.Gen.TypeGenErr.fatal_error(None,
f"结构体 '{entry.Name}' 的字段 '{fe.Name}'(条目下标 {i})与其槽位类型不同类:"
f"条目 指针性={ptr_f}/整数位宽={bits_f}/浮点位宽={fbits_f},"
f"槽位 指针性={ptr_s}/整数位宽={bits_s}/浮点位宽={fbits_s}。"
f"字段条目与结构体布局错位(实证:id 落到 ctx 槽 ⇒ 指针被读成整数 1 ⇒ strlen(0x1) 崩溃)。",
"TypeError")
return 0
node = llvmlite.paramnode_get_next(node)
i += 1
return 0
def lookup_field(self, struct_ty: llvmlite.LLVMType*,
field_name: str) -> GenTypes.FieldEntry*:
"""按结构体类型和字段名查找字段信息
两遍查找策略(跨模块同名类冲突的根本修复):
第一遍: 类型指针 is 身份比较(最高优先级,类型唯一确定 entry)
第二遍: 名称回退(is 未匹配时,按类名/strstr 匹配,字段未找到时 continue)
"""
if struct_ty is None or field_name is None or self.StructTable is None:
return None
# ============================================================
# 两遍查找策略(跨模块同名类冲突的根本修复):
#
# 第一遍: 用类型指针 is 身份比较(最高优先级)
# - is 匹配唯一确定 entry,字段未找到直接返回 None
# - 规避联合类型 == BUG,且不受遍历顺序影响
#
# 第二遍: 名称回退(is 未匹配到任何 entry 时才执行)
# - 用于未注册类型或 is 不可靠的场景
# - 名称匹配但字段未找到时 continue(继续检查其他同名 entry)
# ============================================================
# 第一遍: is 身份比较(用 uint64 指针值比较,避免 is 编译路径解引用悬空 Ty)
# find_struct_by_type 的语义 = 原扫描"首个 Ty 指针相等条目"(首注册优先);
# 其 Ty 索引判定已整体停用、恢复原线性扫描 ⇒ 选中的 entry 与原 is 身份扫描
# 完全一致。类型唯一确定 entry:命中后查字段,字段未找到直接返回 None。
lk_entry: GenTypes.StructEntry* = self.find_struct_by_type(struct_ty)
if lk_entry is not None:
# 字段表与结构体槽位的一致性护栏(fail-fast):错位即拒绝,见
# verify_field_slot_consistency 头注释(真实崩溃:ast.Name.id → 槽 9)。
self.verify_field_slot_consistency(struct_ty, lk_entry)
for fi in range(lk_entry.FieldCount):
fe: GenTypes.FieldEntry* = self._get_field_entry(lk_entry, fi)
if fe is not None and fe.Name is not None:
if string.strcmp(fe.Name, field_name) == 0:
return fe
return None
# 第二遍: 名称回退(is 未匹配到)
ty_name: str = self._extract_struct_name(struct_ty)
if ty_name is not None:
# 加强 SHA1 命名空间隔离:类型名形如 "3d4afd9cdffeba6d.Thread",提取 16 位
# hex 前缀与 entry.ModuleSha1 前 16 字符精确比较,禁止 strstr 裸名跨模块抓
# 同名类(sched.Thread 被 includes/vthreading.vp 同名 Thread 顶掉 → 字段错位
# → _thread_entry call 0)。strncmp 不分配临时缓冲。
# 前缀判定走唯一真源 GenApi.sha1_prefix_len。
ty_has_sha1_lf: int = 0
if GenApi.sha1_prefix_len(ty_name) != 0:
ty_has_sha1_lf = 1
for i in range(self.StructCount):
entry: GenTypes.StructEntry* = self._get_struct_entry(i)
if entry is not None and entry.Name is not None:
# 类型名带 SHA1 前缀:必须与 entry.ModuleSha1 前 16 字符
# 精确一致,否则跳过(跨模块同名类命名空间隔离)
if ty_has_sha1_lf != 0:
if entry.ModuleSha1 is None \
or string.strlen(entry.ModuleSha1) < 16 \
or string.strncmp(entry.ModuleSha1, ty_name, 16) != 0:
continue
matched: int = 0
if string.strcmp(entry.Name, ty_name) == 0:
matched = 1
elif string.strstr(ty_name, entry.Name) is not None:
matched = 1
if matched != 0:
# 查找字段
for fi in range(entry.FieldCount):
fe: GenTypes.FieldEntry* = self._get_field_entry(entry, fi)
if fe is not None and fe.Name is not None:
if string.strcmp(fe.Name, field_name) == 0:
return fe
# 名称匹配但字段未找到,继续检查其他同名 entry(跨模块同名)
continue
# 所有条目都未匹配
return None
def _last_dot_outside_brackets(self, s: str) -> str:
"""返回字符串中最后一个位于 [...] 括号外的 '.' 指针,无则 None
泛型特化类名含下标(如 'numpy.ndarray[t.CDouble]'、
'list[f467978339422f32.AST|t.CPtr]'),下标内部也含 '.'(t.CDouble
的模块限定 / sha1.AST)。strrchr 直接找最后一个 '.' 会落到下标内部,
把模块名截错('numpy.ndarray[t' → sha1 映射表无法解析,NumpyTest
lookup_field_by_class FAIL 根因)。从末尾扫描并跟踪括号深度,
只认深度为 0 的 '.'。
"""
if s is None:
return None
n: sizet = string.strlen(s)
depth: int = 0
i: sizet = n
while i > 0:
i -= 1
ch: char = s[i]
if ch == ']': depth += 1
elif ch == '[' and depth > 0: depth -= 1
elif ch == '.' and depth == 0: return s + i
return None
def lookup_field_by_class(self, class_name: str,
field_name: str,
sha1: str = None,
node: ast.AST* = None,
from_imports: str = None) -> GenTypes.FieldEntry*:
"""按类名和字段名查找字段信息
优先用 SHA1 匹配(跨模块同名类区分),无 SHA1 时按类名查找第一个。
node: 出错位置 AST 节点(可选,供失败报错输出 Python 风格 traceback 帧;
传 None 时用 trace 栈兜底定位)
from_imports: from-import 映射("local:module" 格式),供模块限定名
的 import 别名(import X as Y → Y.Class)解析为模块 sha1
"""
if class_name is None or field_name is None: return None
# 优先用 SHA1 匹配(跨模块同名类区分)
# 支持模块限定名:class_name 可能是 "HT.Translator",entry.Name 是 "Translator"
# 先精确匹配,失败后用 strrchr 提取类名部分回退匹配
if sha1 is not None:
sha1_matched: int = 0
# 计算类名回退部分("HT.Translator" → "Translator";含下标的
# "numpy.ndarray[t.CDouble]" → "ndarray[t.CDouble]")
bare_name: str = class_name
dot_pos_lfbc: str = self._last_dot_outside_brackets(class_name)
if dot_pos_lfbc is not None:
bare_name = dot_pos_lfbc + 1
if bare_name is None or bare_name[0] == '\0':
bare_name = class_name
for i in range(self.StructCount):
entry: GenTypes.StructEntry* = self._get_struct_entry(i)
if entry is not None and entry.Name is not None:
# 精确匹配或类名回退匹配
name_match: int = 0
if string.strcmp(entry.Name, class_name) == 0:
name_match = 1
elif bare_name is not class_name and string.strcmp(entry.Name, bare_name) == 0:
name_match = 1
if name_match != 0:
if entry.ModuleSha1 is not None \
and string.strcmp(entry.ModuleSha1, sha1) == 0:
sha1_matched = 1
# SHA1 + 类名匹配,查找字段
for fi in range(entry.FieldCount):
fe: GenTypes.FieldEntry* = self._get_field_entry(entry, fi)
if fe is not None and fe.Name is not None \
and string.strcmp(fe.Name, field_name) == 0:
return fe
return None
# 模块限定名(module.Class,module 非 t/c 内置):按模块归属解析,禁止裸名回退
# 抓跨模块同名类(根因:VKernel sched.Thread 被 includes/vthreading.vp 同名
# Thread 顶掉 → 'tid' 字段缺失)。模块名经 StubMergerStore sha1 映射表解析为
# 模块 sha1,再精确匹配 name+sha1(vpi stub 注入 / 定义侧注册的裸名 entry)。
# 注意:泛型特化名含下标,取点必须走 _last_dot_outside_brackets,否则模块名截错。
dot_pos_q: str = self._last_dot_outside_brackets(class_name)
if dot_pos_q is not None and dot_pos_q[1] != '\0':
bare_q: str = dot_pos_q + 1
mod_len_q: sizet = string.strlen(class_name) - string.strlen(bare_q) - 1
if mod_len_q > 0:
mod_buf_q: str = stdlib.malloc(mod_len_q + 1)
if mod_buf_q is not None:
string.strncpy(mod_buf_q, class_name, mod_len_q)
mod_buf_q[mod_len_q] = '\0'
if string.strcmp(mod_buf_q, "t") != 0 and string.strcmp(mod_buf_q, "c") != 0:
# 模块段是 16 位 hex = SHA1 字面量("{16hex}.Class" 类键形态,
# 泛型类型实参 _norm_type_arg_name 规范化产物,如
# "29fdb5e25dc949f4.Param"——GSList[Param] 特化方法体内
# `cur: T | t.CPtr = self.Head` 经 resolve_generic_type_name
# 替换 T 为实参串后 AnnotClassName 即该形态):模块段即模块
# SHA1,类已注册就证明该模块已编译,直接按 name+sha1 精确匹配,
# 不走 _resolve_module_sha1(store 键是模块名不是 sha1,查
# "29fdb5e25dc949f4" 恒 None → L956 fail-fast 误杀。本应找得到)。
# 对齐 find_struct L571-592 同款 hex 处理与 TypeGenResolve
# _is_sha1_prefixed 语义(含 sha1 前缀名的 Name/Attribute 注解
# 解析均跳过 import 检查)。泛型下标内点(GSList[sha.Param])
# 已被 _last_dot_outside_brackets 跳过,此处仅顶层 a.b 形态。
# [C10 收敛] 模块段恰为 16 位小写 hex = SHA1 字面量:
# 统一走 GenApi.is_sha1_hex16(该判定的唯一真源)。
mod_hex_ok_q: int = GenApi.is_sha1_hex16(mod_buf_q)
mod_sha1_q: str = None
if mod_hex_ok_q != 0:
# SHA1 前缀自身即"已登记"证据(与既有解析同语义):
# mod_sha1_q 别名 mod_buf_q(下方各返回路径统一 free 一次)
mod_sha1_q = mod_buf_q
else:
mod_sha1_q = self._resolve_module_sha1(mod_buf_q, from_imports)
if mod_sha1_q is not None:
mod_entry_q: GenTypes.StructEntry* = \
self.find_struct_by_module(bare_q, mod_sha1_q)
if mod_entry_q is not None:
# 依赖未就绪容忍(对齐裸名路径):FieldCount==0 的空类
# (注册不完整/源模块未编译)→ 返回 None 让上层标记
# _deps_pending 下遍重译,不在此 fatal。
if mod_entry_q.FieldCount == 0:
stdlib.free(mod_buf_q)
return None
for fi_q in range(mod_entry_q.FieldCount):
fe_q: GenTypes.FieldEntry* = \
self._get_field_entry(mod_entry_q, fi_q)
if fe_q is not None and fe_q.Name is not None:
if string.strcmp(fe_q.Name, field_name) == 0:
stdlib.free(mod_buf_q)
return fe_q
# REnum 变体字段别名回退(与裸名路径一致):模块限定访问
# (llvmlite.LLVMType.Pointee)类命中后字段未找到时,直接查
# 变体别名(REnum 字段是 _p1/_p2 槽位)。此前该回退只在裸名
# 路径,模块限定路径此处 fatal 先触发 → 永远走不到。
rn_cls_q: str = mod_entry_q.Name
if rn_cls_q is None:
rn_cls_q = bare_q
rn_is_renum_q: int = \
self.Gen.ClassGenEnum._is_renum_class_name(bare_q)
if rn_is_renum_q == 0 and rn_cls_q is not None:
rn_is_renum_q = \
self.Gen.ClassGenEnum._is_renum_class_name(rn_cls_q)
rn_alias_q: int = -1
if rn_is_renum_q != 0:
rn_alias_q = self.Gen.ClassGenEnum._lookup_renum_field_alias(rn_cls_q, field_name)
if rn_alias_q < 1 and bare_q is not rn_cls_q:
rn_alias_q = self.Gen.ClassGenEnum._lookup_renum_field_alias(bare_q, field_name)
if rn_alias_q >= 1:
rn_fe_q: GenTypes.FieldEntry* = \
self._get_field_entry(mod_entry_q, rn_alias_q)
if rn_fe_q is not None and rn_fe_q.Ty is not None:
stdlib.free(mod_buf_q)
return rn_fe_q
# 类命中但字段未找到:可读化报错(traceback 格式)
self.Gen.TypeGenErr.fatal_error(node, f"lookup_field_by_class: 类 '{class_name}' 已按模块归属解析(ModuleSha1={mod_sha1_q}, FieldCount={mod_entry_q.FieldCount})但字段 '{field_name}' 未找到", "AttributeError")
stdlib.free(mod_buf_q)
return None
# 模块可解析但类未注册:fail-fast(暴露 vpi 注入/注册缺陷,
# 不再裸名跨库抓其他模块同名类)
self.Gen.TypeGenErr.fatal_error(node, f"lookup_field_by_class: 类 '{class_name}' 未找到(模块 '{mod_buf_q}' 可解析 sha1={mod_sha1_q},但无同名注册类;请检查 vpi stub 注入或编译顺序)", "AttributeError")
stdlib.free(mod_buf_q)
return None
# 模块名未登记:fail-fast(不再裸名跨库回退)
self.Gen.TypeGenErr.fatal_error(node, f"lookup_field_by_class: 类 '{class_name}' 的模块 '{mod_buf_q}' 无法解析(sha1 映射表未登记,请检查 import 与编译顺序)", "AttributeError")
stdlib.free(mod_buf_q)
return None
stdlib.free(mod_buf_q)
# 回退: 无 SHA1 或 SHA1 匹配失败,按类名查找第一个
entry = self.find_struct(class_name)
if entry is None:
# 诊断:目标类未注册(仿 Python traceback 格式,立即终止编译)
self.Gen.TypeGenErr.fatal_error(node, f"lookup_field_by_class: 类 '{class_name}' 未注册 (struct_count={self.StructCount})", "AttributeError")
return None
# 依赖未就绪容忍:调用方期望特定模块(sha1 非 None)但裸名回退命中的是
# FieldCount==0 的 stub 空类(vpi 注入不含字段 / 定义侧模块未编译注册)→
# 返回 None 让上层标记 _deps_pending 下遍重译(源模块编译后真实定义覆盖
# stub)。SECURITY_ATTRIBUTES FieldCount=0 根因(subprocess.vp sa.nLength)。
if entry.FieldCount == 0 and sha1 is not None:
return None
# 先查找字段(诊断/error 之前返回,否则 error 立即退出导致查找永远失败)
for fi in range(entry.FieldCount):
fe: GenTypes.FieldEntry* = self._get_field_entry(entry, fi)
if fe is not None and fe.Name is not None:
if string.strcmp(fe.Name, field_name) == 0:
return fe
# REnum 变体字段别名回退:REnum 类(如 LLVMType)的变体内部字段(Ptr.Pointee
# 等)不是普通字段,而是 _p1/_p2 槽位别名。若留给调用方回退,本函数字段未找到
# 时的 fatal 会先 sys.exit(1),调用方回退永远执行不到,故此处直接查变体别名。
# 注意:_lookup_renum_field_alias 用 class_name 指针值比较,必须传 entry.Name
# (register_struct 复制 + _update_renum_alias_class_ptr 统一后的池指针)。
rn_cls_l: str = entry.Name
if rn_cls_l is None:
rn_cls_l = class_name
rn_is_renum_l: int = self.Gen.ClassGenEnum._is_renum_class_name(class_name)
if rn_is_renum_l == 0 and rn_cls_l is not None:
# 类名可能是模块限定名("mod.LLVMType"),REnum 表存裸名,双保险
rn_is_renum_l = self.Gen.ClassGenEnum._is_renum_class_name(rn_cls_l)
rn_alias_l: int = -1
if rn_is_renum_l != 0:
rn_alias_l = self.Gen.ClassGenEnum._lookup_renum_field_alias(rn_cls_l, field_name)
if rn_alias_l < 1 and class_name is not rn_cls_l:
# 双保险:别名表可能仍持有旧 AST 指针(_update_renum_alias_class_ptr
# 未执行/多模块重复注册场景),用调用方 class_name 再试一次
rn_alias_l = self.Gen.ClassGenEnum._lookup_renum_field_alias(class_name, field_name)
if rn_alias_l >= 1:
rn_fe_l: GenTypes.FieldEntry* = \
self.Gen.StructGen._get_field_entry(entry, rn_alias_l)
if rn_fe_l is not None and rn_fe_l.Ty is not None:
return rn_fe_l
# 诊断:找到结构体但字段确实未找到
# 拼接注册字段列表(一行逗号分隔,供报错一眼定位)
fb_flds_lst: str = stdlib.malloc(1024)
if fb_flds_lst is not None:
fb_flds_lst[0] = '\0'
for fi in range(entry.FieldCount):
fe: GenTypes.FieldEntry* = self._get_field_entry(entry, fi)
if fe is not None and fe.Name is not None:
if string.strlen(fb_flds_lst) > 0 and string.strlen(fb_flds_lst) + 3 < 1024:
string.strcat(fb_flds_lst, ", ")
if string.strlen(fb_flds_lst) + string.strlen(fe.Name) < 1024:
string.strcat(fb_flds_lst, fe.Name)
err: str
if rn_is_renum_l != 0:
# 报错友好化:REnum 类直接说明是"变体字段别名查找失败",并附
# entry.Name 与别名槽位结果,一眼可辨"别名未注册"(槽位=-1)
# 还是"class_name 指针未统一"(已注册但传错指针查不到)
err = f"lookup_field_by_class: 类 '{class_name}' 是 REnum 类(FieldCount={entry.FieldCount}),变体字段 '{field_name}' 别名查找失败(entry.Name='{rn_cls_l}',别名槽位={rn_alias_l},请检查该变体字段是否已注册别名,或 class_name 指针是否统一)"
else:
# 可读化:附 entry.Name/ModuleSha1(跨模块同名定位)+ 注册字段
# 列表 + 期望 sha1(调用方)→ 一眼分辨"跨模块同名类误匹配"
# vs "vpi stub 注入字段不全" vs "类定义与访问处不一致"
sha1_txt: str = "?"
if entry.ModuleSha1 is not None:
sha1_txt = entry.ModuleSha1
want_sha1_txt: str = "?"
if sha1 is not None:
want_sha1_txt = sha1
fld_lst_txt: str = "(无)"
if fb_flds_lst is not None and string.strlen(fb_flds_lst) > 0:
fld_lst_txt = fb_flds_lst
err = f"lookup_field_by_class: 类 '{class_name}' 已注册 (FieldCount={entry.FieldCount}, "\
f"entry.Name='{entry.Name}', ModuleSha1={sha1_txt}, 调用方期望 sha1={want_sha1_txt}) "\
f"但字段 '{field_name}' 未找到;注册字段: [{fld_lst_txt}]。可能原因: 跨模块同名类"\
f"被误匹配 / vpi stub 注入字段不全 / 类定义与访问处不一致"
# 仿 Python traceback 格式输出(Traceback 帧 + 文件:行号 + 错误类型),
# 立即终止编译(与 VLogger.error 同语义,格式对齐 fatal_error)
self.Gen.TypeGenErr.fatal_error(node, err, "AttributeError")
if fb_flds_lst is not None:
stdlib.free(fb_flds_lst)
# 诊断:找到结构体但字段未找到
return None
def get_struct_type(self, class_name: str) -> llvmlite.LLVMType*:
"""按类名获取结构体的 LLVM 类型"""
entry: GenTypes.StructEntry* = self.find_struct(class_name)
if entry is not None:
return entry.Ty
return None
def get_field_by_index(self, class_name: str,
idx: int) -> GenTypes.FieldEntry*:
"""按类名和字段索引获取字段信息"""
if class_name is None:
return None
entry: GenTypes.StructEntry* = self.find_struct(class_name)
if entry is None:
return None
if idx < 0 or idx >= entry.FieldCount:
return None
return self._get_field_entry(entry, idx)
def struct_has_defaults(self, class_name: str) -> int:
"""检查结构体是否有任何带默认值的字段,返回 1=有 / 0=无"""
if class_name is None:
return 0
entry: GenTypes.StructEntry* = self.find_struct(class_name)
if entry is None:
return 0
for fi in range(entry.FieldCount):
fe: GenTypes.FieldEntry* = self._get_field_entry(entry, fi)
if fe is not None and fe.DefaultVal is not None:
return 1
return 0
def get_struct_type_from_value(self, val: llvmlite.Value*) -> llvmlite.LLVMType*:
"""从 Value 的类型推断结构体类型
递归解 Ptr:支持 X|t.CPtr 联合简化产生的 Ptr(Ptr(Struct)) 对象指针
(如运算符重载中间结果 a*2.0 的 Vec3**),否则 try_operator_overload
重载识别失败回退原生运算,对指针生成非法 sub 指令(llc: invalid
operand type for instruction)。
"""
if val is None or val.Ty is None:
return None
ty: llvmlite.LLVMType* = val.Ty
while ty is not None:
# 同类编译 bug(见 TypeGenResolve L1463 注释):case body 直接
# 更新 while 条件变量(ty = pointee)时自举编译出的 match 把赋值
# 写到 subject 快照槽,while 条件读旧槽 → 死循环。改法:case
# 绑定到局部变量,match 外更新循环变量。
nxt_ty: llvmlite.LLVMType* = None
match ty:
case llvmlite.LLVMType.Ptr(pointee):
nxt_ty = pointee
case llvmlite.LLVMType.Struct(fields, fcount, name):
return ty
case _:
return None
ty = nxt_ty
return None
def mark_as_union(self, name: str) -> int:
"""标记已注册的结构体为联合体,返回 1=成功 / 0=失败"""
if name is None:
return 0
entry: GenTypes.StructEntry* = self.find_struct(name)
if entry is None:
return 0
entry.IsUnion = 1
return 1
def is_union_by_name(self, name: str) -> int:
"""按类名检查是否为联合体,返回 1=是 / 0=否"""
if name is None:
return 0
entry: GenTypes.StructEntry* = self.find_struct(name)
if entry is None:
return 0
return entry.IsUnion
def is_union_by_type(self, struct_ty: llvmlite.LLVMType*) -> int:
"""按类型指针检查是否为联合体,返回 1=是 / 0=否
先尝试类型指针 == 比较,失败时回退到名称匹配
"""
if struct_ty is None or self.StructTable is None:
return 0
ty_name: str = self._extract_struct_name(struct_ty)
st_iu: uint64 = uint64(struct_ty)
for i in range(self.StructCount):
entry: GenTypes.StructEntry* = self._get_struct_entry(i)
if entry is not None and entry.Ty is not None:
matched: int = 0
if uint64(entry.Ty) == st_iu:
matched = 1
elif ty_name is not None and entry.Name is not None:
if string.strcmp(entry.Name, ty_name) == 0:
matched = 1
elif string.strstr(ty_name, entry.Name) is not None:
matched = 1
if matched != 0:
return entry.IsUnion
return 0
def mark_as_oop(self, name: str) -> int:
"""标记已注册的结构体为 OOP,返回 1=成功 / 0=失败"""
if name is None:
return 0
entry: GenTypes.StructEntry* = self.find_struct(name)
if entry is None:
return 0
entry.IsOOP = 1
return 1
def is_oop_by_name(self, name: str) -> int:
"""按类名检查是否为 OOP 结构体,返回 1=是 / 0=否"""
if name is None:
return 0
entry: GenTypes.StructEntry* = self.find_struct(name)
if entry is None:
return 0
return entry.IsOOP
def mark_has_init(self, name: str) -> int:
"""标记结构体拥有 __init__ 方法,返回 1=成功 / 0=失败"""
if name is None:
return 0
entry: GenTypes.StructEntry* = self.find_struct(name)
if entry is None:
return 0
entry.HasInit = 1
return 1
def has_init_by_name(self, name: str) -> int:
"""按类名检查是否有 __init__ 方法,返回 1=有 / 0=无"""
if name is None:
return 0
entry: GenTypes.StructEntry* = self.find_struct(name)
if entry is None:
return 0
return entry.HasInit
def mark_has_new(self, name: str) -> int:
"""标记结构体拥有 __new__ 方法,返回 1=成功 / 0=失败"""
if name is None:
return 0
entry: GenTypes.StructEntry* = self.find_struct(name)
if entry is None:
return 0
entry.HasNew = 1
return 1
def has_new_by_name(self, name: str) -> int:
"""按类名检查是否有 __new__ 方法,返回 1=有 / 0=无"""
if name is None:
return 0
entry: GenTypes.StructEntry* = self.find_struct(name)
if entry is None:
return 0
return entry.HasNew
def get_class_name_by_type(self, pool: memhub.MemBuddy*,
struct_ty: llvmlite.LLVMType*) -> str:
"""按类型指针查找类名,返回类名字符串或 None"""
if struct_ty is None or self.StructTable is None:
return None
# find_struct_by_type 的语义 = 原扫描"首个 Ty 指针相等条目"(首注册优先),
# 返回其 Name;其 Ty 索引判定已整体停用、恢复原线性扫描 ⇒ 逐字节一致。
fast_cn: GenTypes.StructEntry* = self.find_struct_by_type(struct_ty)
if fast_cn is not None:
return fast_cn.Name
# 回退: 类型指针比较失败时,遍历所有注册的结构体按名称匹配
# 提取 struct_ty 的 Name 字段(可能是 "SHA1.ClassName" 格式)
ty_name: str = self._extract_struct_name(struct_ty)
if ty_name is not None:
for j in range(self.StructCount):
entry2: GenTypes.StructEntry* = self._get_struct_entry(j)
if entry2 is not None and entry2.Name is not None:
# 构造 "SHA1.ClassName" 格式进行比较
entry2_sha1: str = entry2.ModuleSha1
if entry2_sha1 is not None:
full_nm: str = pool.alloc(128)
if full_nm is not None:
viperlib.snprintf(full_nm, 128, "%s.%s", entry2_sha1, entry2.Name)
if string.strcmp(full_nm, ty_name) == 0:
return entry2.Name
# 也直接比较类名(struct_ty 的 Name 可能无 SHA1 前缀)
if string.strcmp(entry2.Name, ty_name) == 0:
return entry2.Name
# 最后回退: 后缀匹配("SHA1.ClassName" 尾部)——比 strstr 子串
# 匹配安全:特化名 "SHA1.GSList[Param]" 会被基础名 "GSList" 错误
# 命中(子串),导致链式方法调用 class_name 解析错(如
# func.Params.append → GSList.append 查找失败 → 兄弟 Mixin 兜底
# 误匹配 LinkedNode.append,this 前移 sizeof(LinkedNode) 写穿
# 容器前 48 字节,LLvmLiteTest Test 3 崩溃根因)。
if entry2.Name is not None and string.strlen(entry2.Name) > 0:
_tlen_cn: sizet = string.strlen(ty_name)
_nlen_cn: sizet = string.strlen(entry2.Name)
if _tlen_cn > _nlen_cn and ty_name[_tlen_cn - _nlen_cn - 1] == '.':
_tail_ok_cn: int = 1
_ti_cn: sizet
for _ti_cn in range(_nlen_cn):
if ty_name[_tlen_cn - _nlen_cn + _ti_cn] != entry2.Name[_ti_cn]:
_tail_ok_cn = 0
break
if _tail_ok_cn == 1:
return entry2.Name
return None
def _extract_struct_name(self, ty: llvmlite.LLVMType*) -> str:
"""从 LLVMType 提取 Struct 变体的 Name 字段,返回 None 非 Struct"""
if ty is None:
return None
match ty:
case llvmlite.LLVMType.Struct(fields, fcount, name):
return name
case _:
return None
def mark_has_vtable(self, name: str) -> int:
"""标记结构体拥有虚表,返回 1=成功 / 0=失败"""
if name is None:
return 0
entry: GenTypes.StructEntry* = self.find_struct(name)
if entry is None:
return 0
entry.HasVTable = 1
return 1
def has_vtable_by_name(self, name: str) -> int:
"""按类名检查是否有虚表,返回 1=有 / 0=无"""
if name is None:
return 0
entry: GenTypes.StructEntry* = self.find_struct(name)
if entry is None:
return 0
return entry.HasVTable
def mark_novtable(self, name: str) -> int:
"""标记结构体为 NoVTable,返回 1=成功 / 0=失败"""
if name is None:
return 0
entry: GenTypes.StructEntry* = self.find_struct(name)
if entry is None:
return 0
entry.IsNoVTable = 1
return 1
def is_novtable_by_name(self, name: str) -> int:
"""按类名检查是否为 NoVTable,返回 1=是 / 0=否"""
if name is None:
return 0
entry: GenTypes.StructEntry* = self.find_struct(name)
if entry is None:
return 0
return entry.IsNoVTable
def set_parent_name(self, name: str, parent_name: str) -> int:
"""设置结构体的父类名,返回 1=成功 / 0=失败"""
if name is None:
return 0
entry: GenTypes.StructEntry* = self.find_struct(name)
if entry is None:
return 0
entry.ParentName = parent_name
return 1
def set_parent_names(self, pool: memhub.MemBuddy*,
name: str, names_ptr: sizet*,
count: int) -> int:
"""设置结构体的父类名数组(永久池深拷贝,跨文件永久存活)"""
if name is None:
return 0
entry: GenTypes.StructEntry* = self.find_struct(name)
if entry is None:
return 0
if names_ptr is None or count <= 0:
entry.ParentNames = None
entry.ParentCount = 0
return 1
if count > PARENT_MAX:
count = PARENT_MAX
pn_pool: memhub.MemBuddy* = pool
if self.StdlibAlloc != 0:
pn_pool = CompilerFactory.factory.PermPool
if pn_pool is None:
pn_pool = pool
arr: sizet* = pn_pool.alloc(8 * count)
if arr is None:
return 0
for i in range(count):
addr: sizet = names_ptr[i]
arr[i] = 0
if addr != 0:
src: str = rawptr(addr)
if src is not None:
slen: sizet = string.strlen(src)
dup: str = pn_pool.alloc(slen + 1)
if dup is not None:
string.strcpy(dup, src)
arr[i] = sizet(dup)
entry.ParentNames = arr
entry.ParentCount = count
return 1
def get_parent_count(self, name: str) -> int:
"""获取父类数量(0=无父类,1=单继承,>1=多继承)"""
if name is None:
return 0
entry: GenTypes.StructEntry* = self.find_struct(name)
if entry is None:
return 0
return entry.ParentCount
def get_parent_name_at(self, name: str, idx: int) -> str:
"""获取第 idx 个父类名,越界返回 None"""
if name is None or idx < 0:
return None
entry: GenTypes.StructEntry* = self.find_struct(name)
if entry is None or entry.ParentNames is None:
return None
if idx >= entry.ParentCount:
return None
parr: sizet* = (sizet*)(entry.ParentNames)
addr: sizet = parr[idx]
if addr == 0:
return None
return rawptr(addr)
def get_mro_chain(self, pool: memhub.MemBuddy*,
name: str) -> sizet*:
"""MRO 线性化类名数组(深度优先 + 声明顺序 + 去重,含自身)"""
if pool is None or name is None:
return None
arr: sizet* = pool.alloc(8 * (MRO_MAX + 1))
if arr is None:
return None
stack: sizet* = pool.alloc(8 * MRO_MAX)
if stack is None:
return None
visited: sizet* = pool.alloc(8 * MRO_MAX)
if visited is None:
return None
stack_count: int = 0
out_count: int = 0
visit_count: int = 0
# 初始压入自身
stack[0] = sizet(name)
stack_count = 1
while stack_count > 0:
stack_count -= 1
cur_addr: sizet = stack[stack_count]
cur: str = rawptr(cur_addr)
if cur is None:
continue
# 已访问检查(线性搜索)
dup: int = 0
for vi in range(visit_count):
va: sizet = visited[vi]
if va != 0:
vn: str = rawptr(va)
if vn is not None and string.strcmp(vn, cur) == 0:
dup = 1
break
if dup != 0:
continue
# 加入结果与已访问集合
if out_count < MRO_MAX:
arr[out_count] = sizet(cur)
out_count += 1
if visit_count < MRO_MAX:
visited[visit_count] = sizet(cur)
visit_count += 1
# 父类逆序压栈(正向 range 计算逆序索引,保证声明顺序弹出)
entry: GenTypes.StructEntry* = self.find_struct(cur)
if entry is not None and entry.ParentCount > 0 and entry.ParentNames is not None:
pc: int = entry.ParentCount
parr: sizet* = (sizet*)(entry.ParentNames)
for pi2 in range(pc):
pi: int = pc - 1 - pi2
if stack_count < MRO_MAX:
stack[stack_count] = parr[pi]
stack_count += 1
# 末尾 0 结束
arr[out_count] = 0
return arr
def get_parent_name(self, name: str) -> str:
"""获取结构体的父类名,返回 None=无父类"""
if name is None:
return None
entry: GenTypes.StructEntry* = self.find_struct(name)
if entry is None:
return None
return entry.ParentName
def set_vtable_methods(self, name: str, methods: str, count: int) -> int:
"""设置虚表方法名数组,返回 1=成功 / 0=失败"""
if name is None:
return 0
entry: GenTypes.StructEntry* = self.find_struct(name)
if entry is None:
return 0
entry.VTableMethods = methods
entry.VTableMethodCount = count
return 1
def get_vtable_method_index(self, name: str, method_name: str) -> int:
"""获取方法在虚表中的索引,返回 -1=未找到"""
if name is None or method_name is None:
return -1
entry: GenTypes.StructEntry* = self.find_struct(name)
if entry is None or entry.VTableMethods is None:
return -1
# VTableMethods 字段声明为 char*,但实际存储的是 sizet 数组
# 必须转换为 sizet* 才能正确读取 8 字节指针值
methods_arr: sizet* = (sizet*)(entry.VTableMethods)
for i in range(entry.VTableMethodCount):
mname_addr: sizet = methods_arr[i]
if mname_addr == 0:
continue
mname: str = rawptr(mname_addr)
if mname is not None and string.strcmp(mname, method_name) == 0:
return i
return -1
def get_vtable_method_count(self, name: str) -> int:
"""获取虚表方法数量"""
if name is None:
return 0
entry: GenTypes.StructEntry* = self.find_struct(name)
if entry is None:
return 0
return entry.VTableMethodCount
def get_vtable_method_name(self, name: str, idx: int) -> str:
"""获取虚表中第 idx 个方法名,返回 None=越界"""
if name is None or idx < 0:
return None
entry: GenTypes.StructEntry* = self.find_struct(name)
if entry is None or entry.VTableMethods is None:
return None
if idx >= entry.VTableMethodCount:
return None
methods_arr: sizet* = (sizet*)(entry.VTableMethods)
mname_addr: sizet = methods_arr[idx]
if mname_addr == 0:
return None
return rawptr(mname_addr)
def get_field_count(self, name: str) -> int:
"""获取结构体字段数量,返回 -1=未找到"""
if name is None:
return -1
entry: GenTypes.StructEntry* = self.find_struct(name)
if entry is None:
return -1
return entry.FieldCount
def ensure_struct_def_in_module(self, pool: memhub.MemBuddy*,
mod: llvmlite.LLVMModule*,
ty: llvmlite.LLVMType*):
"""确保跨模块结构体的完整定义在当前模块中可用(供 alloca 使用)
处理两种情况:
1. Struct 值类型:需要完整定义(供 alloca 分配空间)
2. Ptr(Struct) 指针类型:需要 pointee 至少有 opaque 声明
"""
if ty is None or mod is None or pool is None:
return
# 指针类型:确保 pointee 至少有 opaque 声明
match ty:
case llvmlite.LLVMType.Ptr(pointee):
llvmlite.module_ensure_opaque_for_type(mod, pool, ty)
# 如果 pointee 是 Struct,递归复制完整定义
# (GEP 访问字段需要 sized 类型,仅 opaque 会导致 llc 报错)
if pointee is not None:
self.ensure_struct_def_in_module(pool, mod, pointee)
return
case _:
pass
# 获取结构体名称(非 Struct 类型返回 None)
sname: str = llvmlite.get_struct_name(ty)
if sname is None:
return
# 检查是否为跨模块引用(名称包含 '.')
if string.strchr(sname, 46) is None:
return # 非跨模块引用,无需处理
# 提取类名(第一个 '.' 之后的部分)
# 必须用 strchr 而非 strrchr:泛型特化名如 "list[ast.AST|t.CPtr]"
# 内部含 '.'(ast.AST),strrchr 会错误地把类名解析为 "AST|t.CPtr]"
dot_pos: str = string.strchr(sname, '.')
if dot_pos is None:
return
class_name: str = dot_pos + 1
if class_name is None or class_name[0] == '\0':
return
# 提取 SHA1(sname 到 dot_pos 之间,用 strlen 避免指针减法)
total_len: sizet = string.strlen(sname)
class_name_len: sizet = string.strlen(class_name)
sha1_len: sizet = total_len - class_name_len - 1
sha1_buf: str = pool.alloc_tagged(32,
memhub.DETSITE_STRUCTGENMIXIN_1804)
if sha1_buf is None:
return
# 32 字节缓冲:strncpy 不截断,前缀超过 31 字符会越界写 + 手动 NUL 再越界
sha1_cp: sizet = sha1_len
if sha1_cp > 31:
sha1_cp = 31
string.strncpy(sha1_buf, sname, sha1_cp)
sha1_buf[sha1_cp] = '\0'
# 按类名 + SHA1 查找结构体定义
entry: GenTypes.StructEntry* = self.find_struct_by_module(class_name, sha1_buf)
if entry is None:
# 回退到按类名查找(可能找到不同模块的同名类,但总比没有好)
entry = self.find_struct(class_name)
if entry is None:
return
if entry.Ty is None:
return
# 将完整定义添加到当前模块(会替换已存在的 opaque 声明)
llvmlite.module_add_named_type(mod, pool, entry.Ty)
# B5:跨模块注入的 packed 类必须同步 packed 标记(<{...}>),否则引用
# 模块按非 packed 布局 GEP,与定义侧 packed 布局错位(gdt/elf/fat32
# 二进制格式字段错读;合并 IR 中 idt_entry/gdt_entry 退化 {…} 根因)。
if entry.IsPacked != 0:
llvmlite.module_set_named_type_packed(mod, pool, entry.Ty, 1)
# 递归扫描字段类型,确保值类型子结构体(如 WIN32_FIND_DATAA 中的 FILETIME)
# 也有完整定义。指针类型字段只需 opaque 声明(已由 module_ensure_opaque_for_type 处理)。
self._ensure_field_types_in_module(pool, mod, entry.Ty)
def _ensure_field_types_in_module(self, pool: memhub.MemBuddy*,
mod: llvmlite.LLVMModule*,
ty: llvmlite.LLVMType*):
"""递归扫描类型树,确保值类型子结构体有完整定义"""
if ty is None or mod is None or pool is None:
return
# 嵌套 match 遍历类型树
match ty:
case llvmlite.LLVMType.Struct(fields, fcount, sname):
# 遍历字段链表
if fields is not None:
fld: rawptr[llvmlite.ParamNode] = fields
cnt_ef: int = 0
while fld is not None:
# 步数守卫:字段链表成环(内存损坏)导致无限遍历挂起
# (G3 LinkedNode.prepend 挂起候选),超限立即报错终止
cnt_ef += 1
if cnt_ef > 100000:
VLogger.error("字段链表疑似成环(_ensure_field_types_in_module),强制终止", "STRUCT")
stdlib.exit(1)
if fld.Ty is not None:
# 值类型子结构体(如 FILETIME)需要完整定义
match fld.Ty:
case llvmlite.LLVMType.Struct(f2, fc2, n2):
self.ensure_struct_def_in_module(pool, mod, fld.Ty)
case _:
pass
self._ensure_field_types_in_module(pool, mod, fld.Ty)
fld = fld.Next
case llvmlite.LLVMType.Ptr(pointee):
# 指针类型:pointee 只需 opaque 声明,不需要完整定义
# 但如果 pointee 是 Struct,确保 opaque 声明存在
if pointee is not None:
llvmlite.module_ensure_opaque_for_type(mod, pool, ty)
case llvmlite.LLVMType.Array(elem_ty, acount):
# 数组类型:元素如果是值类型结构体,需要完整定义(Array 元素只递归
# 字段不发射定义 → llc "use of undefined type named '...'")。
if elem_ty is not None:
match elem_ty:
case llvmlite.LLVMType.Struct(_, _, _):
self.ensure_struct_def_in_module(pool, mod, elem_ty)
case _:
pass
self._ensure_field_types_in_module(pool, mod, elem_ty)
case _:
pass
def get_field_name_ptr(self, fe: GenTypes.FieldEntry*) -> str:
"""获取 FieldEntry 的字段名,返回 None=未设置"""
if fe is None:
return None
return fe.Name
def get_field_type_ptr(self, fe: GenTypes.FieldEntry*) -> llvmlite.LLVMType*:
"""获取 FieldEntry 的字段类型,返回 None=未设置"""
if fe is None:
return None
return fe.Ty
def get_field_default_ptr(self, fe: GenTypes.FieldEntry*) -> ast.AST*:
"""获取 FieldEntry 的默认值 AST 节点,返回 None=无默认值"""
if fe is None:
return None
return fe.DefaultVal
def get_field_annot_class_name(self, fe: GenTypes.FieldEntry*) -> str:
"""获取 FieldEntry 的原始类型注解类名(Spec.SourceName),返回 None=未设置
[S4] 原 FieldEntry.AnnotClassName 字段已删除,注解名承载在 Spec.SourceName
(由 add_field 经 SpecWithSource 写入)。
"""
if fe is None:
return None
return TSpec.SpecSourceName(fe.Spec)
def field_is_volatile(self, fe: GenTypes.FieldEntry*) -> int:
"""字段是否 volatile(FieldEntry.IsVolatile 承载),1=是 / 0=否
由 ClassGenDef 收集字段注解时检测 t.CVolatile 写入;消费点(成员读写的
load/store)据此加 LLVM volatile 限定符。
"""
if fe is None:
return 0
return fe.IsVolatile
def field_pointee_is_volatile(self, fe: GenTypes.FieldEntry*) -> int:
"""字段所指物是否 volatile(FieldEntry.PointeeVolatile 承载),1=是 / 0=否
字段注解为 rawptr[t.CVolatile | T](等价 C 的 volatile T*)时由 ClassGenDef
写入;消费点(经该字段解引用 self.ptr[i] / self.ptr.field 的 load/store)
据此加 LLVM volatile 限定符。
"""
if fe is None:
return 0
return fe.PointeeVolatile
def field_is_const(self, fe: GenTypes.FieldEntry*) -> int:
"""字段是否 const 对象(FieldEntry.IsConst 承载),1=是 / 0=否
字段注解含 t.CConst 且字段类型非指针时由 ClassGenDef 写入;消费点
(obj.f = ... / self.f = ... 的字段赋值)据此 fail-fast。
"""
if fe is None:
return 0
return fe.IsConst