fix(tpc): 成员偏移查询点就地修复错位 struct body(修正晚于冻结的时序)

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)跳过,避免用别的同名类成员表写坏布局。
This commit is contained in:
TermiNexus committed 2026-09-29 00:19:06 +08:00
1 parent b8ed33a200
commit 7fb0e49854
2 files changed
+50

No files matched your search

+8
View File
@@ -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:
+42
View File
@@ -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())