fix(tpc): 撤回失效的"事后修复",改为 set_body 前拦截降级布局(不冻结)
a 编译器缺陷。实测证据(07ea8e2f 的探针回执):对本版本 llvmlite,
`st._elements = None` 之后 `st.set_body(*elems)` 仍以
`RuntimeError: <name> is already defined` 拒绝 —— 说明 **`_elements` 不是 set_body 的
判定存储**,即"清 _elements 以便重载/重设 body"这一全仓惯用手段在本版本是**空操作**
(结构体 body 一经设定即不可改)。因此:
- 撤回 7fb0e498 加的 `_SyncStructBodyTypes`(使用点就地修复)及其在
`_get_member_offset` 的调用 —— 它在本版本永不可能成功,只会把编译卡死;
- 改为**事前拦截**:`_generate_structs` 在 `set_body` 前调用
`_HasDegradedMemberTypes`,若本次布局里存在「注解是类引用(`Mod.Class` /
首字母大写裸名,排除 t/c 模块)**却**解析成 `i8**`」的成员,说明该 pass 类型尚未
就绪 ⇒ **不设 body、保持 opaque**,等类型就绪的 pass 再设。这样从根上避免
"降级布局被永久冻结"(现场:Generator 的槽 i8** 而声明 %"…ExprGenFstr"*,
赋值时报「层数不同」)。始终不就绪则在使用点响亮失败(opaqueness / 层数不同),
不再静默产出错布局。
- 新增 `_IsI8DoublePtr` / `_IsClassRefPtrAnnotation` 两个判据助手(保守:只在
"指针包装 + 类引用内层 + i8 系两层指针"三条同时成立时判降级)。
This commit is contained in:
1 parent
07ea8e2fd9
commit
66de269ebf
2 files changed
+75
-60
No files matched your search
@@ -519,14 +519,6 @@ 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:
|
||||
|
||||
@@ -348,64 +348,80 @@ class StructGenMixin:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _SyncStructBodyTypes(self, ClassName: str | None) -> None:
|
||||
"""把 struct body 中与 `class_members` 声明**不兼容**的槽改回声明类型(就地修复)。
|
||||
@staticmethod
|
||||
def _IsI8DoublePtr(T: Any) -> bool:
|
||||
"""是否为 i8 系两层指针(`i8**`)—— 即 `T | t.CPtr` 在 T 的类型丢失后**退化**成的形状。"""
|
||||
return (isinstance(T, ir.PointerType)
|
||||
and isinstance(T.pointee, ir.PointerType)
|
||||
and isinstance(T.pointee.pointee, ir.IntType)
|
||||
and T.pointee.pointee.width == 8)
|
||||
|
||||
背景:llvmlite 的 `set_body` 只能设一次。若某个早期 pass 在 `class_members`
|
||||
尚未被 .vpi / stub 修正、类型已降级(如 `i8**` 顶替 `%X*`)时就把 body 冻住,
|
||||
错位会**永久留存** —— 后续重载只修 `class_members`,不动 body
|
||||
(现场:Generator 第 3/11 槽;一次修好一槽、下一槽仍错,正是时序随机)。
|
||||
Step 4 的前置校验只在**当次** `_generate_structs` 生效,漏"修正晚于冻结"的时序,
|
||||
故在**使用点**(成员偏移查询,即任何 GEP 之前)再校一次并就地修复:
|
||||
def _IsClassRefPtrAnnotation(self, Ann: Any) -> bool:
|
||||
"""注解是否为「指针包装的**类引用**」:`rawptr[Mod.Class]` / `Mod.Class | t.CPtr`。
|
||||
|
||||
- 只替换**存储时必被拒绝**的槽(判据与存储侧同一套 `_MemberSlotAccepts`),
|
||||
其余元素(含通用指针 i8* 槽)原样保留 —— 不做整表重建,避免丢 stub 侧信息;
|
||||
- `_elements` 置空后 `set_body` 重设:命名类型对象不变,故已发出的
|
||||
`getelementptr %"name"*` 不受影响。
|
||||
|
||||
LLVM 具名类型只有一份定义,重设 body 后打印即为修正后的布局,故在模块落盘前
|
||||
修复是安全的;越早(首次字段访问)修复越干净。
|
||||
判据保守(只用于识别"本应是结构体指针"):
|
||||
- 必须是指针包装(`rawptr[T]` / `t.CPtr[T]` / 裸名 `CPtr[T]`,或含 `t.CPtr` 标记的联合);
|
||||
- 内层须是类引用形态:`Attribute` 且根模块不是 `t` / `c`(如 `ExprGenFstr.ExprGenFstr`、
|
||||
`llvmlite.LLVMType`),或首字母大写的裸名(类名书写惯例)。
|
||||
模块名、`rawptr`/`CPtr` 标记、泛型特化实参(内层是 Subscript)一律不算。
|
||||
"""
|
||||
from lib.core.SymbolUtils import (IsRawPtrSubscriptNode, IsCPtrSubscriptNode,
|
||||
IsCPtrMarkerNode)
|
||||
_tbl: Any = getattr(self, 'SymbolTable', None)
|
||||
Inner: Any = None
|
||||
if IsRawPtrSubscriptNode(Ann) or IsCPtrSubscriptNode(Ann):
|
||||
Inner = Ann.slice
|
||||
elif isinstance(Ann, ast.BinOp) and isinstance(Ann.op, ast.BitOr):
|
||||
for _side in (Ann.left, Ann.right):
|
||||
if IsCPtrMarkerNode(_side, _tbl):
|
||||
continue
|
||||
if Inner is not None:
|
||||
return False
|
||||
Inner = _side
|
||||
else:
|
||||
return False
|
||||
if isinstance(Inner, ast.Attribute):
|
||||
_cur: Any = Inner
|
||||
while isinstance(_cur, ast.Attribute):
|
||||
_cur = _cur.value
|
||||
_root: str = _cur.id if isinstance(_cur, ast.Name) else ''
|
||||
return _root not in ('t', 'c')
|
||||
if isinstance(Inner, ast.Name):
|
||||
return bool(Inner.id) and Inner.id[0].isupper()
|
||||
return False
|
||||
|
||||
def _HasDegradedMemberTypes(self, ClassName: str, MemberTypes: list) -> bool:
|
||||
"""本次即将冻结的布局里,是否存在「注解是类引用、类型却退化成 `i8**`」的成员。
|
||||
|
||||
为什么必须在 set_body **之前**拦:llvmlite 的 struct body 只能设一次,且**实测本
|
||||
版本 `_elements = None` 之后 `set_body` 仍以 `"<name> is already defined"` 拒绝
|
||||
重设** —— 一旦按降级类型冻结,后续 pass 把 `class_members` 修好也改不回布局
|
||||
(现场:Generator 的槽是 `i8**` 而声明是 `%"…ExprGenFstr"*`,赋值时报「层数不同」)。
|
||||
故此时**不设 body**、保持 opaque,等类型就绪的 pass 再设;若始终不就绪,会在使用点
|
||||
以「opaque / 层数不同」响亮失败,而不是静默产出错布局。
|
||||
"""
|
||||
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
|
||||
# REnum 镜像布局:class_members 里是变体而非字段,不参与本判据。
|
||||
return False
|
||||
_anns: Any = (getattr(self, 'class_member_annotations', None) or {}).get(ClassName)
|
||||
if not _anns:
|
||||
return False
|
||||
_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
|
||||
return False
|
||||
_offset: int = len(MemberTypes) - len(_members)
|
||||
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
|
||||
_backup: list = list(_st.elements)
|
||||
_st._elements = None
|
||||
try:
|
||||
_st.set_body(*_elems)
|
||||
except Exception as _e:
|
||||
# 回滚到原布局(直接写回私有字段,不再经 set_body),再响亮失败:把 llvmlite
|
||||
# 那句含糊的 "<name> 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
|
||||
_j: int = _offset + _i
|
||||
if _j < 0 or _j >= len(MemberTypes):
|
||||
continue
|
||||
if not (isinstance(_item, tuple) and _item):
|
||||
continue
|
||||
_ann: Any = _anns.get(_item[0])
|
||||
if _ann is None or not self._IsClassRefPtrAnnotation(_ann):
|
||||
continue
|
||||
if StructGenMixin._IsI8DoublePtr(MemberTypes[_j]):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _generate_structs(self) -> None:
|
||||
all_classes: set[str] = set(self.class_methods.keys()) | set(self.class_members.keys())
|
||||
@@ -750,7 +766,14 @@ class StructGenMixin:
|
||||
# 根因:register_method 在 class_members[ClassName]=[] 时立即调用
|
||||
# _generate_structs,此时 member_types 为空。保持 opaque 状态,
|
||||
# 让后续 _TryLoadStructFromStub 能正确设置 body。
|
||||
if member_types:
|
||||
#
|
||||
# 【不冻结降级布局】member_types 里若含「注解是类引用、类型却退化成
|
||||
# i8**」的成员,说明本次 pass 的类型尚未就绪 —— 此时**不设 body**:
|
||||
# llvmlite 的 body 只能设一次(实测本版本 `_elements = None` 后
|
||||
# set_body 仍以 "already defined" 拒绝重设),按降级类型冻结后永远
|
||||
# 改不回来(后续 pass 修好 class_members 也没用)。保持 opaque,等
|
||||
# 类型就绪的 pass 再设;始终不就绪则在使用点响亮失败,而不是静默错布局。
|
||||
if member_types and not self._HasDegradedMemberTypes(ClassName, member_types):
|
||||
struct_type.set_body(*member_types)
|
||||
|
||||
def _create_Vtable_globals(self) -> None:
|
||||
|
||||
Reference in new issue
Block a user