fix(repl): 片段导出符号探针改用 MLIR 形态 llvm.func;布局恢复文案改指 {sha1}.meta

- Repl.vp ⑧ 探针由 "define i32 @名(i8* %__repl_ctx__" 改为
  "llvm.func @名(%__repl_ctx__: !llvm.ptr) -> i32"(单端后 dump 出的是 MLIR 文本)
- VpiReader.vp REPL 布局恢复的注释/报错/汇总文案 .stub.ll → {sha1}.meta
  (数据源早已是 meta:_repl_stub_collect_module 读 {sha1}.meta,
   _repl_meta_scan_layout 解析 STRUCT/VTABLE;本次只改文案与注释,无逻辑改动)
This commit is contained in:
TermiNexus committed 2026-10-03 19:58:37 +08:00
1 parent cbf76cd468
commit d0434bf114
2 files changed
+61 -53

No files matched your search

+50 -44
View File
@@ -34,15 +34,17 @@ _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)
# 布局元数据真源已由 .stub.ll 文本改为 {sha1}.meta 的 `STRUCT … FIELDS n / OPAQUE`
# 记录(单端 MLIR 后 .stub.ll 里不再有 `%"k" = type {…}` 定义行)。
_REPL_STUB_NOCOUNT_NOCLASS: int = -1 # meta 里没有该 key 的 STRUCT 行(或整份 meta 没读到)
_REPL_STUB_NOCOUNT_OPAQUE: int = -2 # meta 里该 key 是 OPAQUE(无 body)
# 布局"不可用 ⇒ 明确拒绝"的原因码(_repl_stub_warn_nodata):把"数据缺失"
# (上层数据没到位,补存根/重跑即可消除)与"判据拒绝"(数据在但不可信,绝不放行)
# (上层数据没到位,补 meta/重跑即可消除)与"判据拒绝"(数据在但不可信,绝不放行)
# 在文案与汇总计数上分开 —— 两类都会走到"不登记 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_NOSTUB: t.CDefine = 1 # 数据缺失:整个模块没有 {sha1}.meta
_REPL_SU_NOCLASS: t.CDefine = 2 # 判据拒绝:meta 存在但无该类的 STRUCT 行
_REPL_SU_OPAQUE: t.CDefine = 3 # 判据拒绝:meta 里该类为 OPAQUE(无 body)
_REPL_SU_PARENT: t.CDefine = 4 # 上游无数据:父类布局不可用/未在会话注册(走 _repl_stub_warn_parent)
# ReplStubClass.Flags / MethodFlags 位
@@ -70,9 +72,9 @@ class ReplStubClass:
MethodCount: int
Bases: sizet*
BaseCount: int
LayoutCount: int # 存根布局字段数(>=0 = 命中该类的 `= type {…}`;负数 = **哨兵,不是字段数**,
# 见 _REPL_STUB_NOCOUNT_NOCLASS/-OPAQUE:存根里没这个类 / 没读到存根 / opaque)
VtArity: int # 存根 .vtable 全局槽数(0 = 存根无该全局)
LayoutCount: int # 布局字段数(>=0 = 命中 meta 的 `STRUCT … FIELDS n`;负数 = **哨兵,不是字段数**,
# 见 _REPL_STUB_NOCOUNT_NOCLASS/-OPAQUE:meta 里没这个类 / 没读到 meta / opaque)
VtArity: int # meta 的 `VTABLE … SLOTS n`(0 = meta 无该记录)
Names: sizet*
Types: sizet*
Count: int
@@ -2340,7 +2342,7 @@ def _ParseVpiIntoModuleImpl(pool: memhub.MemBuddy*,
max_funcs: int = 1024
# ============================================================
# REPL(ReplStubLayoutMode):先从 .stub.ll 恢复各类的**完整布局**
# REPL(ReplStubLayoutMode):先从 {sha1}.meta 恢复各类的**完整布局**
# (__vtable__ + 父类展平字段 + 本类字段)与虚表方法索引,再走下方既有
# vpi 注入(已登记者只标可见)。详见文件末"REPL:includes 类完整布局与
# 虚表恢复"段;批处理该标志恒 0,本块不执行。
@@ -2774,7 +2776,7 @@ def _inject_class_struct_declare(pool: memhub.MemBuddy*,
string.strcpy(flat_buf_c + p_len + 1, cls_name)
real_name = flat_buf_c
# REPL(ReplStubLayoutMode,见文件末"REPL:includes 类完整布局与虚表恢复"):
# 本类若已由 .stub.ll 驱动的路径接管 ⇒ 这里整类让位——已登记(或本遍就地
# 本类若已由 {sha1}.meta 驱动的路径接管 ⇒ 这里整类让位——已登记(或本遍就地
# 登记成功)只标可见;父类未就绪 ⇒ 本遍不登记(收尾遍补齐);布局不可信 ⇒
# 不登记(片段侧字段访问明确失败,绝不按错偏移静默访问)。
# State=EMPTY 的类(无父类/无虚表/字段类型不可解析)不在此列,仍走下方既有口径。
@@ -3678,29 +3680,32 @@ def _register_ctypedef_from_annassign(pool: memhub.MemBuddy*,
# ============================================================
# REPL:includes 类**完整布局**与虚表恢复(.stub.ll 驱动)
# REPL:includes 类**完整布局**与虚表恢复({sha1}.meta 驱动)
#
# 为什么需要(M4 遗留缺口):会话初始化用 .vpi 注入注册 includes 类,而 .vpi 只含
# "类名 + 本类 AnnAssign 字段 + 方法签名" ⇒ **父类字段**与 **__vtable__ 占位
# 字段**全部缺失 ⇒ 带父类/虚表的类在片段侧的字段索引与宿主真实布局错位
# (静默读错字段)。
#
# 布局真源 = .stub.ll(批处理落盘点:Phase1Main.vp:1078 / Phase2/__init__.vp:1050
# 用 tr.dump_ir(..., mlirlite.OUTPUT_STUB)):
# - `%"<sha1>.<Class>" = type { ... }` 是**展平后**的完整布局(ClassGenDef 的
# 字段展平:__vtable__ 首字段 + 父类字段 + 本类字段);
# - `@...<sha1>.<Class>.vtable` 全局的类型槽数 = 该类的虚方法数
# 布局**校验**真源 = {sha1}.meta(由 StubMergerIR.WriteModuleMeta 从翻译期模型落盘,
# 取代原先扫描 .stub.ll 文本 —— 单端 MLIR 后 .stub.ll 里已无 `%"k" = type {…}` 定义行):
# - `STRUCT <sha1>.<Class> FIELDS <n> PACKED <0|1>` = **展平后**的字段数
# (ClassGenDef 的字段展平:__vtable__ 首字段 + 父类字段 + 本类字段);
# `STRUCT <name> OPAQUE` = 该具名类型是前向声明(无 body ⇒ 无字段数可比);
# - `VTABLE <sha1>.<Class> SLOTS <n>` = 该类的虚方法数
# (ClassGenVTable._generate_vtable 按 _collect_vtable_methods 的顺序发射)。
# 字段**名/类型**不来自 meta,而来自:父类字段(已在会话登记的父条目=展平)
# + 本类字段(本 vpi 的 AnnAssign,走同一条 resolve_annotation_type)。
#
# 本段仅在 ReplStubLayoutMode != 0(REPL 会话初始化)时生效,批处理永不置位:
# ① 在 _inject_class_struct_declare **之前**登记完整布局:字段名/类型与
# ClassGenDef 同源(父类字段来自已在会话登记的父条目=字段展平;本类字段
# 来自本 vpi 的 AnnAssign,走同一条 resolve_annotation_type);
# ② 布局由 .stub.ll 的**字段数**校验:Count(名单) 必须等于存根的字段数
# ② 布局由 meta 的**字段数**校验:Count(名单) 必须等于 meta 的 FIELDS
# (有虚表时 +1 = __vtable__);不符即判"布局不可信";
# ③ 虚表:HasVTable 与虚方法**短名顺序**按 ClassGenDef._detect_vtable_status /
# ClassGenBase._should_method_be_virtual 的规则转写(父类虚方法在前、本类
# 合格方法按类体顺序追加、按短名去重),并用存根 .vtable 槽数校验;
# 合格方法按类体顺序追加、按短名去重),并用 meta 的 VTABLE SLOTS 槽数校验;
# 槽数不符 ⇒ 不登记 VTableMethods(退化为直接调用 {sha1}.{Class}.{m})。
#
# 拒绝策略(能报错就别静默):布局不可信 ⇒ **不登记**该类的 StructEntry ⇒ 片段侧
@@ -3708,7 +3713,7 @@ def _register_ctypedef_from_annassign(pool: memhub.MemBuddy*,
# (self 只是指针,方法体在宿主里按真实布局编译,符号经宿主 import lib 解析)。
#
# 拒绝的两类成因必须分开(见 _REPL_SU_* 与 _repl_stub_warn_nodata):
# - 『数据缺失』= LayoutCount 只是哨兵(本模块没有 .stub.ll 文件 / 存根里查不到该
# - 『数据缺失』= LayoutCount 只是哨兵(本模块没有 {sha1}.meta / meta 里查不到该
# 类),任一侧无数据 ⇒ **不比较**、也就不存在"字段数不符"这回事;文案与汇总计数
# 都写明成因,避免"-1 vs 推断 -1"这种两侧都无数据却像在比较的误导输出;
# - 『判据拒绝』= 数据在但不可信(字段数不符 / opaque / 父类被拒),一律不放行。
@@ -3728,9 +3733,9 @@ 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
ReplStubRefusedNoStub: int = 0 # 拒绝原因分布(_REPL_SU_*):数据缺失(模块无 {sha1}.meta)
ReplStubRefusedNoClass: int = 0 # 判据拒绝:meta 有该模块但无此类
ReplStubRefusedOpaque: int = 0 # 判据拒绝:meta 里该类 OPAQUE
ReplStubRefusedParent: int = 0 # 上游无数据:父类不可用/未注册
ReplStubNoStub: int = 0
ReplStubWarnLeft: int = 0
@@ -4076,24 +4081,25 @@ def _repl_stub_warn_nodata(e: rawptr[ReplStubClass], cause: int) -> None:
return
ReplStubWarnLeft -= 1
if cause == _REPL_SU_NOSTUB:
# 数据缺失:连模块存根文件都没有(LayoutCount = -1 只是"没读到存根"的哨兵)
# 数据缺失:连模块布局元数据都没有(LayoutCount = -1 只是"没读到"的哨兵)
stdio.printf("includes 类布局: %s.%s 布局不可用 ⇒ 明确拒绝(原因=『数据缺失』:"
"模块 %s 没有 .stub.ll 文件;存根字段数=无 / 推断字段数=未计算 ⇒ "
"模块 %s 没有 {sha1}.meta 布局元数据;meta 字段数=无 / 推断字段数=未计算 ⇒ "
"两侧都没数据,『未做比较』,不是字段数不符)。该类字段访问被"
"拒绝(方法调用不受影响);补上该模块存根后用同一 --project 重跑 "
"REPL 即恢复\n", e.Sha1, e.ClassName, e.Sha1)
"拒绝(方法调用不受影响);补上该模块 meta(同一工程先跑一次批处理,"
"Phase A/B 会落盘 {sha1}.meta)后用同一 --project 重跑 REPL 即恢复\n",
e.Sha1, e.ClassName, e.Sha1)
elif cause == _REPL_SU_OPAQUE:
# 判据拒绝:存根里该 key 是 opaque(无 body ⇒ 无字段数可比)
# 判据拒绝:meta 里该 key 是 OPAQUE(无 body ⇒ 无字段数可比)
stdio.printf("includes 类布局: %s.%s 布局不可用 ⇒ 明确拒绝(原因=『判据拒绝』:"
"存根 %s.stub.ll 里键 %s 为 opaque 声明(无 body);存根字段数=不可得 / "
"meta %s.meta 里键 %s 为 OPAQUE 声明(无 body);meta 字段数=不可得 / "
"推断字段数=未计算 ⇒ 『未做比较』)。该类字段访问被拒绝"
"(方法调用不受影响)\n", e.Sha1, e.ClassName, e.Sha1, e.Key)
else:
# 判据拒绝:存根文件在,但没有该类的 `= type` 行(LayoutCount = -1 只是"未找到"的哨兵)
# 判据拒绝:meta 在,但没有该类的 STRUCT 行(LayoutCount = -1 只是"未找到"的哨兵)
stdio.printf("includes 类布局: %s.%s 布局不可用 ⇒ 明确拒绝(原因=『判据拒绝』:"
"存根 %s.stub.ll 存在但其中没有键 %s 的 `= type {…}` 定义行;"
"存根字段数=未找到 / 推断字段数=未计算 ⇒ 『未做比较』)。该类字段访问"
"被拒绝(方法调用不受影响);若该类确实定义于该模块,说明存根陈旧/"
"meta %s.meta 存在但其中没有键 %s 的 `STRUCT … FIELDS n / OPAQUE` 记录;"
"meta 字段数=未找到 / 推断字段数=未计算 ⇒ 『未做比较』)。该类字段访问"
"被拒绝(方法调用不受影响);若该类确实定义于该模块,说明 meta 陈旧/"
"不完整,请重建\n", e.Sha1, e.ClassName, e.Sha1, e.Key)
@@ -4337,8 +4343,8 @@ def _repl_stub_add_class(pool: memhub.MemBuddy*,
from_imports: str) -> int:
"""收集单个 vpi ClassDef 的布局要素并尝试登记(1=已登记/推迟处理,0=跳过)
have_stub:本模块是否读到了 .stub.ll 文本(0 时本模块带父类/虚表的类按
『数据缺失』拒绝,与"存根在但无此类"的『判据拒绝』在文案/计数上分开)。
have_stub:本模块是否读到了 {sha1}.meta 布局元数据(0 时本模块带父类/虚表的类按
『数据缺失』拒绝,与"meta 在但无此类"的『判据拒绝』在文案/计数上分开)。
"""
global ReplStubCount
if pool is None or cd is None or cd.name is None or ReplStubTable is None:
@@ -4482,10 +4488,10 @@ def _repl_stub_add_class(pool: memhub.MemBuddy*,
e.Types = e.OwnTypes
else:
# 布局无数据 ⇒ 明确拒绝(不登记 StructEntry)。两类成因必须分开报:
# ① have_stub == 0:连模块 .stub.ll 都没有(**数据缺失**,补存根即可恢复)
# ② LayoutCount == -2:存根里该类是 opaque(判据拒绝)
# ③ 其余:存根在但没有该类的 `= type` 行(判据拒绝;若该类确实定义于此模块
# 则说明存根陈旧/不完整)
# ① have_stub == 0:连模块 {sha1}.meta 都没有(**数据缺失**,补 meta 即可恢复)
# ② LayoutCount == -2:meta 里该类是 OPAQUE(判据拒绝)
# ③ 其余:meta 在但没有该类的 `STRUCT … FIELDS n` 记录(判据拒绝;若该类确实
# 定义于此模块则说明 meta 陈旧/不完整)
e.State = _REPL_SS_REFUSED
if have_stub == 0:
_repl_stub_warn_nodata(e, _REPL_SU_NOSTUB)
@@ -4598,10 +4604,10 @@ def ReplStubLayoutFinish() -> int:
break
_repl_stub_warn_parent(e2, "父类未在会话注册或其布局被拒", pbad)
stdio.printf("includes 类布局: 完整布局 %d 个(其中虚表索引可用 %d 个)/ "
"被拒 %d 个 / 缺存根模块 %d 个\n",
"被拒 %d 个 / 缺布局元数据模块 %d 个\n",
ReplStubOk, ReplStubVtOk, ReplStubRefused, ReplStubNoStub)
stdio.printf(" 拒绝原因分布(自证数据来源):数据缺失-模块无 .stub.ll %d 个类 / "
"判据拒绝-存根无此类 %d 个 / 判据拒绝-存根为 opaque %d 个 / "
stdio.printf(" 拒绝原因分布(自证数据来源):数据缺失-模块无 {sha1}.meta %d 个类 / "
"判据拒绝-meta 无此类 %d 个 / 判据拒绝-meta 为 opaque %d 个 / "
"上游无数据-父类不可用 %d 个\n",
ReplStubRefusedNoStub, ReplStubRefusedNoClass,
ReplStubRefusedOpaque, ReplStubRefusedParent)
@@ -4609,8 +4615,8 @@ def ReplStubLayoutFinish() -> int:
stdio.printf(" 说明:被拒类的字段访问会报『类未注册/属性访问失败』"
"(不按错偏移静默访问);方法调用不受影响。\n")
if ReplStubRefusedNoStub > 0:
stdio.printf(" 其中『数据缺失』的 %d 个类只是没读到模块存根(本会话临时目录 %s "
"缺这些 .stub.ll):批处理才会落盘 .stub.ll(REPL 分支在 Phase1 之前"
stdio.printf(" 其中『数据缺失』的 %d 个类只是没读到模块布局元数据(本会话临时目录 %s "
"缺这些 {sha1}.meta):批处理才会落盘 meta(REPL 分支在 Phase1 之前"
"返回,故 --repl 自身不生成存根)。修法:用同一个工程跑一次批处理"
"(TransPyV 目录:.\\tpv_build\\output\\TransPyV.exe --project "
".\\project.vpj;includes 有改动时加 --rebuild-includes),随后 "