From 07ea8e2fd9702c9faf735e3a49b54725227c5901 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Tue, 29 Sep 2026 11:34:17 +0800 Subject: [PATCH] =?UTF-8?q?fix(tpc):=20=E5=B8=83=E5=B1=80=E6=A0=A1?= =?UTF-8?q?=E9=AA=8C/=E4=BF=AE=E5=A4=8D=E6=8E=92=E9=99=A4=20REnum=20?= =?UTF-8?q?=E9=95=9C=E5=83=8F=E5=B8=83=E5=B1=80=20+=20set=5Fbody=20?= =?UTF-8?q?=E5=A4=B1=E8=B4=A5=E5=B8=A6=E5=9B=9E=E6=BB=9A=E4=B8=8E=E4=B8=8A?= =?UTF-8?q?=E4=B8=8B=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit a 编译器缺陷(承接 b8ed33a2 / 7fb0e498):REnum 的 struct body 是 **max-variant 镜像布局** —— class_members 里的名字是*变体*而非字段,变体嵌套结构体也必须逐字 镜像主结构体。此前两处按"声明类型 vs 槽类型"的校验/修复会把这类布局当成错位: - Step 4 前置校验可能据此清 _elements 重建; - _SyncStructBodyTypes 可能据此改写布局(跨模块 GEP 偏移随即与定义端失配)。 新增 _IsRenumLayoutClass(主 REnum 自身 / `_`,判据走 SymbolTable 的 IsRenum)并在两处排除。 另外:就地修复的 `_elements = None` + `set_body` 一旦被 llvmlite 拒绝 (` is already defined`,现场 fd4cd0ec583f42b9.Generator),原来会抛出这句 毫无上下文的裸 RuntimeError。现改为:捕获后**直接写回原 _elements 回滚**,再抛 FailFastError 带上类名 / struct / 原异常 / 元素数前后值,既不留中间状态也不再含糊。 --- lib/core/LLVMCG/StructGen.py | 53 ++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/lib/core/LLVMCG/StructGen.py b/lib/core/LLVMCG/StructGen.py index 15b74d2..9f1f1d8 100644 --- a/lib/core/LLVMCG/StructGen.py +++ b/lib/core/LLVMCG/StructGen.py @@ -319,6 +319,35 @@ class StructGenMixin: # 判据不可用(缺上下文)时保守放行,避免误清已正确的布局。 return True + def _IsRenumLayoutClass(self, ClassName: str | None) -> bool: + """该类是否属 **REnum 布局**(主 REnum 自身,或其变体嵌套结构体 `_`)。 + + REnum 的 struct body 是 **max-variant 镜像布局**:`class_members` 里那些名字是 + *变体*(不是字段),变体嵌套结构体也必须逐字镜像主结构体。因此**一律不得**用 + class_members 的"声明类型"去改写或校验它们的布局 —— 否则跨模块 GEP 偏移与 + 定义端失配(变体载荷槽本就允许与 max-variant 槽类型不同,取值处靠 bitcast)。 + """ + if not ClassName: + return False + _tbl: Any = getattr(self, 'SymbolTable', None) + if _tbl is None: + return False + try: + _e: Any = _tbl.lookup(ClassName) + except Exception: + return False + if _e is not None and getattr(_e, 'IsRenum', False): + return True + if '_' in ClassName: + _prefix: str = ClassName.rsplit('_', 1)[0] + try: + _pe: Any = _tbl.lookup(_prefix) + except Exception: + _pe = None + if _pe is not None and getattr(_pe, 'IsRenum', False): + return True + return False + def _SyncStructBodyTypes(self, ClassName: str | None) -> None: """把 struct body 中与 `class_members` 声明**不兼容**的槽改回声明类型(就地修复)。 @@ -339,6 +368,10 @@ class StructGenMixin: """ if not ClassName: return + # REnum 布局(主 REnum 及其变体嵌套结构体)是 max-variant 镜像布局,声明成员 + # 不是字段 —— 一律不参与本修复,避免改写其布局导致跨模块偏移失配。 + if self._IsRenumLayoutClass(ClassName): + return _st: Any = self.structs.get(ClassName) if not isinstance(_st, ir.IdentifiedStructType) or _st.elements is None: return @@ -358,8 +391,21 @@ class StructGenMixin: _changed = True if not _changed: return + _backup: list = list(_st.elements) _st._elements = None - _st.set_body(*_elems) + try: + _st.set_body(*_elems) + except Exception as _e: + # 回滚到原布局(直接写回私有字段,不再经 set_body),再响亮失败:把 llvmlite + # 那句含糊的 " is already defined" 变成带类名/元素数/原异常的错误, + # 且保证不留下被改坏的中间状态。 + _st._elements = _backup + from lib.core.LLVMCG.FuncGen import FailFastError + raise FailFastError( + f"struct body 就地修复失败(类 '{ClassName}',struct='{_st}'):" + f"{type(_e).__name__}: {_e};元素数={len(_backup)} → 目标元素数={len(_elems)}," + f"已回滚原布局" + ) from _e def _generate_structs(self) -> None: all_classes: set[str] = set(self.class_methods.keys()) | set(self.class_members.keys()) @@ -548,7 +594,10 @@ class StructGenMixin: # 声明类型**能隐式写进该槽**才算一致 —— 通用指针槽(i8*)照旧放行,不误清。 _slot_types_ok: bool = True _members_pre: list = self.class_members.get(ClassName, []) - if _members_pre: + # REnum 布局(主 REnum 及变体嵌套结构体)是 max-variant 镜像布局, + # class_members 里是*变体*不是字段 ⇒ 不参与逐元素类型校验(见 + # _IsRenumLayoutClass)。 + if _members_pre and not self._IsRenumLayoutClass(ClassName): _base_pre: int = 1 if _has_vtable_precheck else 0 _elems_pre: list = list(existing_st.elements) for _i_pre, _item_pre in enumerate(_members_pre):