diff --git a/App/lib/Stub/StubGen/VpiReader.vp b/App/lib/Stub/StubGen/VpiReader.vp index fab64c9..82ed0a6 100644 --- a/App/lib/Stub/StubGen/VpiReader.vp +++ b/App/lib/Stub/StubGen/VpiReader.vp @@ -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)): -# - `%"." = type { ... }` 是**展平后**的完整布局(ClassGenDef 的 -# 字段展平:__vtable__ 首字段 + 父类字段 + 本类字段); -# - `@.....vtable` 全局的类型槽数 = 该类的虚方法数 +# 布局**校验**真源 = {sha1}.meta(由 StubMergerIR.WriteModuleMeta 从翻译期模型落盘, +# 取代原先扫描 .stub.ll 文本 —— 单端 MLIR 后 .stub.ll 里已无 `%"k" = type {…}` 定义行): +# - `STRUCT . FIELDS PACKED <0|1>` = **展平后**的字段数 +# (ClassGenDef 的字段展平:__vtable__ 首字段 + 父类字段 + 本类字段); +# `STRUCT OPAQUE` = 该具名类型是前向声明(无 body ⇒ 无字段数可比); +# - `VTABLE . SLOTS ` = 该类的虚方法数 # (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),随后 " diff --git a/App/lib/core/Repl.vp b/App/lib/core/Repl.vp index 82fa649..1357a91 100644 --- a/App/lib/core/Repl.vp +++ b/App/lib/core/Repl.vp @@ -104,7 +104,7 @@ _REPL_FN_NAMES_CAP: t.CDefine = 1024 _REPL_DIAG_CAP: t.CDefine = 128 # 签名渲染缓冲容量("(int, str) -> int" 最长 ≈ 8×(5+2)+8+8 < 96) _REPL_SIG_TEXT_CAP: t.CDefine = 192 -# 片段符号名/探针缓冲容量("__repl_frag_" / "define i32 @(i8* %__repl_ctx__") +# 片段符号名/探针缓冲容量("__repl_frag_" / "llvm.func @(%__repl_ctx__: !llvm.ptr) -> i32") _REPL_NAME_CAP: t.CDefine = 64 _REPL_NEEDLE_CAP: t.CDefine = 128 # 临时文件路径缓冲容量(目录/源文件路径) @@ -192,11 +192,11 @@ novirtual class ReplFuncSlotView: # ============================================================ -# ReplSigOut - 会话函数签名解析结果(M3;宿主从片段 IR 的 define 行解析) +# ReplSigOut - 会话函数签名解析结果(M3;宿主从片段**模型**的 Function 头解析) # -# 出处:定义片段 translate 后 dump 的 IR 里 -# `define @f(i8* %__repl_ctx__, %p1, ...)` -# 是定义端 ABI 的唯一真相(注解 → LLVM 类型由 codegen 解析)。宿主据此填 +# 出处:定义片段 translate 后的 LLVM 模块**模型**(不是 IR 文本),其 MLIR 文本形态为 +# `llvm.func @f(%__repl_ctx__: !llvm.ptr, %p1: ) -> ` +# 是定义端 ABI 的唯一真相(注解 → 类型由 codegen 解析)。宿主据此填 # ReplFuncEntry 的签名 token;调用片段再用 token 重建同型签名 ⇒ 两侧一致。 # 形参/返回的 token 数组写进会话 scratch(ReplSigTags/ReplSigBits)。 # ============================================================ @@ -322,7 +322,7 @@ ReplBlockLen: int = 0 ReplInBlock: int = 0 # 完整模块 IR 文本(dump_ir OUTPUT_FULL 的输出缓冲) ReplIrBuf: bytes = None -# IR 探针 "define i32 @(i8* %__repl_ctx__"(确认导出函数签名与调用约定一致) +# IR 探针 "llvm.func @(%__repl_ctx__: !llvm.ptr) -> i32"(确认导出函数签名与调用约定一致) ReplNeedleBuf: bytes = None # ctx 会话缓冲(宿主与片段共享;字节视图,作为片段首实参传递) ReplCtxBuf: bytes = None @@ -2459,14 +2459,16 @@ def ReplExecFragment(mb: memhub.MemBuddy*, src: str) -> int: if string.strstr(ReplIrBuf, ReplNeedleBuf) is not None: needs_implib = 1 - # ⑧ 导出符号探针:签名必须是 (i8* %__repl_ctx__) 且返回 i32。 + # ⑧ 导出符号探针(单端 MLIR 文本):签名必须是 `(%__repl_ctx__: !llvm.ptr) -> i32`。 + # MLIR 记法(includes/mlirlite/README.md §2.2):`llvm.func @名(%形参: 类型) -> 返回 {` # 不含该形参 ⇒ 包裹模板/调用约定不一致,或片段含 raise 使函数被追加 # __eh_msg_out__/__eh_code_out__ 两个出参——此时宿主按单参调用是未定义行为, # 故直接明确报错(不加载、不调用)。 - viperlib.snprintf(ReplNeedleBuf, _REPL_NEEDLE_CAP, "define i32 @%s(i8* %%%s", + viperlib.snprintf(ReplNeedleBuf, _REPL_NEEDLE_CAP, + "llvm.func @%s(%%%s: !llvm.ptr) -> i32", ReplNameBuf, GenTypes.REPL_CTX_PARAM) if string.strstr(ReplIrBuf, ReplNeedleBuf) is None: - stdio.printf("片段 %s: 未生成约定的导出函数(期望 `define i32 @%s(i8* %%__repl_ctx__)`);" + stdio.printf("片段 %s: 未生成约定的导出函数(期望 `llvm.func @%s(%%__repl_ctx__: !llvm.ptr) -> i32`);" "含 raise 的片段会追加异常出参,M2 不支持\n", ReplNameBuf, ReplNameBuf) return 1