From 7fb0e49854426c47fca8a6de33e4fdbad1ec9548 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Tue, 29 Sep 2026 00:19:06 +0800 Subject: [PATCH] =?UTF-8?q?fix(tpc):=20=E6=88=90=E5=91=98=E5=81=8F?= =?UTF-8?q?=E7=A7=BB=E6=9F=A5=E8=AF=A2=E7=82=B9=E5=B0=B1=E5=9C=B0=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E9=94=99=E4=BD=8D=20struct=20body=EF=BC=88=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=E6=99=9A=E4=BA=8E=E5=86=BB=E7=BB=93=E7=9A=84=E6=97=B6?= =?UTF-8?q?=E5=BA=8F=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit a 编译器缺陷(承接 b8ed33a2):Step 4 的前置校验只在**当次** _generate_structs 生效, 漏掉"class_members 被 .vpi/stub 修正在后、struct body 冻结在前"的时序 —— 于是 一次只修好一部分槽(实测:第 3 槽修好、第 11 槽仍错),错误逐个前移。 证据链(成员槽来源明细 + 全盘 grep): class_members[Generator] 项数=47 base=0 下标=11 声明槽类型=%"a7b68c79f39cb4d3.ExprGenFstr"* 布局 struct name='fd4cd0ec583f42b9.Generator' 元素数=47 elements[11]=i8** 且**没有任何** stub 文本里含 i8** ⇒ body 是从"当时已降级的 class_members"冻住的, 后来重载只修 class_members,改不了已冻结的 body(llvmlite set_body 只能设一次)。 修法:新增 StructGen._SyncStructBodyTypes —— 在**使用点**(_get_member_offset, 即任何 GEP 之前、成员表已在上方补齐/重载之后)按 class_members 逐槽比对, 只把**存储时必被拒绝**的槽(判据同 _MemberSlotAccepts)改回声明类型,其余元素 (含通用指针 i8* 槽)原样保留;_elements 置空后 set_body 重设(命名类型对象不变, 故已发出的 getelementptr %"name"* 不受影响)。同名类 sha1 冲突路径 (_ResolvedClass != ClassName)跳过,避免用别的同名类成员表写坏布局。 --- lib/core/LLVMCG/FuncGen.py | 8 +++++++ lib/core/LLVMCG/StructGen.py | 42 ++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/lib/core/LLVMCG/FuncGen.py b/lib/core/LLVMCG/FuncGen.py index 51fae57..14997bd 100644 --- a/lib/core/LLVMCG/FuncGen.py +++ b/lib/core/LLVMCG/FuncGen.py @@ -519,6 +519,14 @@ class FuncGenMixin: # 依赖补齐(上面的懒加载)后再确认一次展平(幂等);偏移基址由 _BaseNow() # 在真正 return 处现算,不在此缓存。 _BaseNow() + # 布局自洽修复(成员表已在上方补齐/重载后执行):struct body 若在早期 pass 就 + # 被降级类型冻住(set_body 只能设一次),此处按 class_members 就地把不兼容的槽 + # 改回声明类型,避免"修正晚于冻结"导致后续 GEP 拿到 i8** 这类错位槽 + # (现场:Generator 第 3/11 槽,赋值时 fail-fast"层数不同")。 + # 仅在同名类 sha1 冲突路径之外执行(_ResolvedClass != ClassName 时成员表取自 + # 全名键,短名表可能是别的同名类,拿它去修会写坏布局)。 + if _ResolvedClass == ClassName: + self._SyncStructBodyTypes(ClassName) if _ResolvedClass and _ResolvedClass in self.class_members: for i, (name, _) in enumerate(self.class_members[_ResolvedClass]): if name == field_name: diff --git a/lib/core/LLVMCG/StructGen.py b/lib/core/LLVMCG/StructGen.py index acd99da..15b74d2 100644 --- a/lib/core/LLVMCG/StructGen.py +++ b/lib/core/LLVMCG/StructGen.py @@ -319,6 +319,48 @@ class StructGenMixin: # 判据不可用(缺上下文)时保守放行,避免误清已正确的布局。 return True + def _SyncStructBodyTypes(self, ClassName: str | None) -> None: + """把 struct body 中与 `class_members` 声明**不兼容**的槽改回声明类型(就地修复)。 + + 背景:llvmlite 的 `set_body` 只能设一次。若某个早期 pass 在 `class_members` + 尚未被 .vpi / stub 修正、类型已降级(如 `i8**` 顶替 `%X*`)时就把 body 冻住, + 错位会**永久留存** —— 后续重载只修 `class_members`,不动 body + (现场:Generator 第 3/11 槽;一次修好一槽、下一槽仍错,正是时序随机)。 + Step 4 的前置校验只在**当次** `_generate_structs` 生效,漏"修正晚于冻结"的时序, + 故在**使用点**(成员偏移查询,即任何 GEP 之前)再校一次并就地修复: + + - 只替换**存储时必被拒绝**的槽(判据与存储侧同一套 `_MemberSlotAccepts`), + 其余元素(含通用指针 i8* 槽)原样保留 —— 不做整表重建,避免丢 stub 侧信息; + - `_elements` 置空后 `set_body` 重设:命名类型对象不变,故已发出的 + `getelementptr %"name"*` 不受影响。 + + LLVM 具名类型只有一份定义,重设 body 后打印即为修正后的布局,故在模块落盘前 + 修复是安全的;越早(首次字段访问)修复越干净。 + """ + if not ClassName: + return + _st: Any = self.structs.get(ClassName) + if not isinstance(_st, ir.IdentifiedStructType) or _st.elements is None: + return + _members: list = self.class_members.get(ClassName) or [] + if not _members: + return + _base: int = self._member_base(ClassName) + _elems: list = list(_st.elements) + _changed: bool = False + for _i, _item in enumerate(_members): + _j: int = _base + _i + if _j >= len(_elems): + break + _decl: Any = _item[1] if isinstance(_item, tuple) and len(_item) >= 2 else None + if _decl is not None and not self._MemberSlotAccepts(_decl, _elems[_j]): + _elems[_j] = _decl + _changed = True + if not _changed: + return + _st._elements = None + _st.set_body(*_elems) + def _generate_structs(self) -> None: all_classes: set[str] = set(self.class_methods.keys()) | set(self.class_members.keys())