diff --git a/lib/core/LLVMCG/FuncGen.py b/lib/core/LLVMCG/FuncGen.py index e5d60e3..f1ebcc5 100644 --- a/lib/core/LLVMCG/FuncGen.py +++ b/lib/core/LLVMCG/FuncGen.py @@ -576,13 +576,67 @@ class FuncGenMixin: if isinstance(_OffStruct, ir.IdentifiedStructType): _OffEls: Any = _OffStruct.elements _Frozen: Any = getattr(_OffStruct, '_tpc_member_slots', None) - if isinstance(_Frozen, dict) and field_name in _Frozen: + # 冻结表**有效性**判定:表挂在 struct 实例上,而 body 会被"不完整即清除" + # (StructGen 预检 `existing_st.elements = None`)与 stub 加载 + # (HandlesImports._TryLoadStructFromStub 的 set_body)**清空后重设**, + # 重设路径不更新本表 ⇒ 陈旧表把字段指到别的槽。现场:AnnAssign 在 + # "自有 4 字段 + vtable"(5 槽:i8*,AST*,AST*,AST*,i32)时冻结 + # {'value': 3},随后 body 被重设为 12 槽正确布局(value 在 10), + # 查询仍读槽 3(lineno, i32)⇒ `.kind()` 虚分派 GEP 的操作数是 i32。 + # 判据 = 建表时记下的 **body 对象身份**(set_body 每次赋新 tuple): + # 与当前 elements 非同一对象即视为失效,落到按布局求槽的路径。 + _FrozenFresh: bool = (isinstance(_Frozen, dict) + and getattr(_OffStruct, '_tpc_member_body', None) is _OffEls) + if _FrozenFresh and field_name in _Frozen: _FrozenSlot: Any = _Frozen[field_name] if isinstance(_FrozenSlot, int) and (not _OffEls or 0 <= _FrozenSlot < len(_OffEls)): return _FrozenSlot _OffMembers: list = self.class_members.get(_ResolvedClass) or [] _BitFields: Any = getattr(self, 'class_member_bitfields', None) if _OffEls and _OffMembers and not (_BitFields and _BitFields.get(ClassName)): + # ---- 尾部对齐(布局真源之二):表**短于** body 字段区时 ---- + # 展平规则是"父类前缀 + 自有字段"(EnsureClassMembersFlattened 前置前缀、 + # StructGen 按展平表建 body)⇒ 自有字段恒为 body 尾部 len(表) 项。表比 + # 字段区(len(elements) - vtable 前缀)短,即"父类前缀尚未补齐"(父类 + # 未就绪/展平失败):此时 base(0/1) + 表内下标 会把整类字段整体前移 + # (现场:AnnAssign 表仅 4 项自有字段 ⇒ 'value' 读槽 3,见上)。改为按 + # 尾部求前缀数,并**逐项类型比对**自校验(判据与建 body 侧同一套 + # _MemberSlotAccepts):表自洽(长度 == 字段区)时本路径不介入, + # 对既有正确行为零变化;REnum 是 max-variant 镜像布局,不适用。 + _FieldArea: int = len(_OffEls) - _BaseNow() + if (len(_OffMembers) < _FieldArea + and not self._IsRenumLayoutClass(_ResolvedClass)): + _TailBase: int = len(_OffEls) - len(_OffMembers) + _TailOk: bool = _TailBase >= 0 + _iT: int = 0 + while _TailOk and _iT < len(_OffMembers): + _nmT, _mtT = _OffMembers[_iT] + if not self._MemberSlotAccepts(_mtT, _OffEls[_TailBase + _iT]): + _TailOk = False + _iT += 1 + if _TailOk: + _TIdx: int = -1 + for _i5, (_n5, _) in enumerate(_OffMembers): + if _n5 == field_name: + _TIdx = _i5 + break + if _TIdx >= 0: + # 每类只留一次痕(大类的每个字段都会命中本路径) + _TailKey: str = _ResolvedClass + _TailRep: set = getattr(self, '_MemberTailAlignedReported', None) + if _TailRep is None: + _TailRep = set() + self._MemberTailAlignedReported = _TailRep + if _TailKey not in _TailRep: + _TailRep.add(_TailKey) + _vlog().warning( + f"[成员偏移] 类 '{_ResolvedClass}' 的成员表短于 struct 字段区" + f"(表 {len(_OffMembers)} 项 vs 字段区 {_FieldArea} 槽," + f"body {len(_OffEls)} 槽)⇒ 按**尾部对齐**取槽:" + f"字段 '{field_name}' = {_TailBase + _TIdx}" + f"(前缀 {_TailBase} 项 = body 尾部之外的部分)", + "MemberBase") + return _TailBase + _TIdx _Cursor: int = _BaseNow() _ElemN: int = len(_OffEls) _PairedNames: set = set() diff --git a/lib/core/LLVMCG/StructGen.py b/lib/core/LLVMCG/StructGen.py index f2d54d7..b745a4e 100644 --- a/lib/core/LLVMCG/StructGen.py +++ b/lib/core/LLVMCG/StructGen.py @@ -777,6 +777,14 @@ class StructGenMixin: _SlotIdx += 1 struct_type._tpc_member_slots = _SlotMap struct_type._tpc_member_base = 1 if has_vtable else 0 + # 有效性锚点:本表只对"建表这一刻的 body"成立。body 之后可能被 + # 清空重设(本文件上方预检的 `existing_st.elements = None`、 + # HandlesImports._TryLoadStructFromStub 的 set_body),而挂在 + # 实例上的属性不会随之更新 ⇒ 陈旧表会把字段指到别的槽(现场: + # AnnAssign 自有 4 字段时冻结 {'value': 3},body 重设为 12 槽后 + # 'value' 仍读槽 3 = lineno(i32))。记下建表时的 **body 对象** + # (set_body 每次赋新 tuple),查询侧用 `is` 判同一性,O(1) 且无猜测。 + struct_type._tpc_member_body = struct_type.elements def _create_Vtable_globals(self) -> None: # 「本模块有定义(非 declare)」的函数名所蕴含的全部**点分前缀**集合。