fix(tpc): 成员偏移以「实际 GEP 用的 struct 实例」为准,并报出双实例不一致
类别:a(编译器缺陷 —— 偏移与 GEP 不同源,整类字段偏移平移)
现场:TransPyV/App/lib/core/Gen/Class/ClassGenGeneric.vp:346
return ast.Constant(pool, cnst.const_kind, cnst.int_val, cnst.float_val,
cnst.str_val, cnst.lineno, cnst.col_offset)
报「调用参数类型不匹配:指针类型 %"c772afbbfd495aca.MemManager"* 不能隐式转换为
整数参数 i32」,逐项列表显示实参 [6](= cnst.lineno)求值成 MemManager*。
取证(TransPyV/temp/5df907d3a02a0bac.stub.ll:21/:23):
%"…Constant" = { [0]i8*(vtable) [1]AST*(parent) [2]MemManager*(pool)
[3]i32(lineno) [4]i32(col_offset) … }
__new__(Constant*, MemManager*, i32, i64, double, i8*, i32, i32)
成员表扁平序为 [parent, pool, lineno, col_offset, …] ⇒ base=1 时 lineno 偏移 3
(正确),base=0 时偏移 2 = pool,正是报出的那个指针类型;而 col_offset 偏移
3 落到 lineno 仍是 i32,所以**只有 [6] 报错** —— 与现象逐位吻合。
根因:FuncGen._member_base 的布局取自**类键** self.structs[cls],而 GEP 用的是
**取值处** ObjVal.type.pointee 那一份实例。同名类双注册(或一份已从 stub 加载完整、
另一份仍 opaque)时两份布局不同 ⇒ 用一份算偏移、用另一份读,整类字段偏移平移。
_class_has_vtable 对跨模块 AST 类恒为 False(其 docstring 已记录该已知静默路径),
base 只能靠 delta + 类型校准救回,两实例不同源时即失守。
改动:
1. FuncGen._member_base(ClassName, StructHint=None):hint 为**同类**(短名相同)的
struct 实例时,以它计算 delta / 首元素 vtable 判据 / 类型校准;当它与类键上的
实例不是同一对象时,每类一次 vlog().warning 报出两侧名字与元素数(opaque 或
元素数),使"双注册"这一事实不再静默。
2. FuncGen._get_member_offset(..., StructHint=None) → _BaseNow() 透传(base 仍现算,
不缓存)。
3. HandlesExprAttr._handle_attr_name:把 ObjVal.type.pointee 作为 hint 传入。
不传 hint 时行为与改前逐字一致;其余偏移消费点(字段写入 / 构造初始化等)本轮未动。
This commit is contained in:
1 parent
935a5b372b
commit
415916b3dd
2 files changed
+51
-5
No files matched your search
@@ -708,7 +708,17 @@ class ExprAttrHandle(BaseHandle):
|
||||
Gen.class_member_bitoffsets[ClassName][AttrName] = bo
|
||||
return self._load_bitfield_member(ObjVal, ClassName, AttrName)
|
||||
|
||||
offset = Gen._get_member_offset(AttrName, ClassName)
|
||||
# 偏移与 GEP 必须**同源**:把"实际将被索引的那个 struct 实例"传给偏移计算。
|
||||
# 同一类键上可能存在两个实例(同名类 sha1 冲突双注册 / 一方仍 opaque),
|
||||
# 用类键上那份算偏移、用 ObjVal 这份做 GEP ⇒ 整类字段偏移平移
|
||||
# (现场:`Constant.lineno` 偏移 2 落到 `pool`(MemManager*) → 第 6 个实参被报
|
||||
# "指针不能隐式转整数 i32")。_member_base 收到不同实例时会报出事实。
|
||||
_StructHint: Any = None
|
||||
if isinstance(ObjVal.type, ir.PointerType):
|
||||
_ObjPointee: Any = ObjVal.type.pointee
|
||||
if isinstance(_ObjPointee, ir.IdentifiedStructType):
|
||||
_StructHint = _ObjPointee
|
||||
offset = Gen._get_member_offset(AttrName, ClassName, _StructHint)
|
||||
# 检查 offset 是否超出实际结构体
|
||||
if isinstance(ObjVal.type, ir.PointerType):
|
||||
actual_pointee: Any = ObjVal.type.pointee
|
||||
|
||||
@@ -351,9 +351,17 @@ class FuncGenMixin:
|
||||
_Cls = _hit
|
||||
return _Cls
|
||||
|
||||
def _member_base(self, ClassName: str | None) -> int:
|
||||
def _member_base(self, ClassName: str | None, StructHint: Any = None) -> int:
|
||||
"""类 struct 的成员偏移基址(vtable 占位槽数)——**全仓唯一真源**。
|
||||
|
||||
`StructHint`(可选):调用方手里**实际要用于 GEP 的那个 struct 实例**
|
||||
(通常就是 `ObjVal.type.pointee`)。同一类键上可能同时存在两个实例
|
||||
(同名类 sha1 冲突的双注册,或一个已从 stub 加载完整、另一个仍 opaque),
|
||||
而偏移基址必须与"实际 GEP 用的那份布局"一致 —— 用键上那份算、用另一份读,
|
||||
会让整类字段偏移平移(现场:`Constant.lineno` 偏移 2 落到 `pool`(MemManager*),
|
||||
实参第 6 项被报"指针不能隐式转整数 i32")。传 hint 时以 hint 为准并**报出**
|
||||
两侧差异(每类一次),不传时行为与改前逐字一致。
|
||||
|
||||
`_get_member_offset` 与所有"自算 base 再 GEP 字段"的消费点(构造/初始化、
|
||||
字段默认值、继承方法 this 调整等)都必须走本方法,禁止各自直查
|
||||
`class_vtable`/`_cross_module_vtable_classes`——旧实现只认这两个装饰器集合,
|
||||
@@ -393,7 +401,34 @@ class FuncGenMixin:
|
||||
_vlog().warning(f"补齐类成员展平失败: {_e}", "Exception")
|
||||
_ByInheritVtable: bool = self._class_has_vtable(_Cls)
|
||||
base: int = 1 if _ByInheritVtable else 0
|
||||
_StructBase: ir.IdentifiedStructType | None = self.structs.get(_Cls)
|
||||
# 布局来源:优先调用方**实际 GEP 用的那个实例**(StructHint)——偏移必须与
|
||||
# 真正被索引的那份 body 同源。只在 hint 不可用时才回落到类键上的实例。
|
||||
_RegBase: ir.IdentifiedStructType | None = self.structs.get(_Cls)
|
||||
_StructBase: ir.IdentifiedStructType | None = _RegBase
|
||||
if isinstance(StructHint, ir.IdentifiedStructType):
|
||||
# 仅接受**同类**的 hint:接收者静态类型是基类、实际指向派生类实例时,
|
||||
# hint 的布局属于派生类,拿它配 `class_members[_Cls]` 会算错 delta。
|
||||
_HintShort: str = self._extract_short_name(getattr(StructHint, 'name', '') or '')
|
||||
if _HintShort == _Cls:
|
||||
_StructBase = StructHint
|
||||
if _RegBase is not None and _RegBase is not StructHint:
|
||||
# 同一类键上两个 struct 实例:报出事实(每类一次),避免"用一份算、
|
||||
# 用另一份读"的静默偏移平移(本函数此处即该平移的判定源)。
|
||||
_Rep: set = getattr(self, '_MemberBaseInstMismatch', None)
|
||||
if _Rep is None:
|
||||
_Rep = set()
|
||||
self._MemberBaseInstMismatch = _Rep
|
||||
if _Cls not in _Rep:
|
||||
_Rep.add(_Cls)
|
||||
_RegN: str = (f"{len(_RegBase.elements)} 元素"
|
||||
if _RegBase.elements is not None else 'opaque')
|
||||
_HintN: str = (f"{len(StructHint.elements)} 元素"
|
||||
if StructHint.elements is not None else 'opaque')
|
||||
_vlog().warning(
|
||||
f"[成员偏移] 类 '{_Cls}' 存在两个 struct 实例:类键上 "
|
||||
f"{_RegBase.name}({_RegN}) vs 取值处 {StructHint.name}({_HintN})"
|
||||
f";偏移基址已按取值处实例计算(两者布局不同会让字段偏移平移)",
|
||||
"MemberBase")
|
||||
if (isinstance(_StructBase, ir.IdentifiedStructType)
|
||||
and _StructBase.elements is not None and len(_StructBase.elements) > 0):
|
||||
_NumElems: int = len(_StructBase.elements)
|
||||
@@ -445,7 +480,8 @@ class FuncGenMixin:
|
||||
base = _BestB
|
||||
return base
|
||||
|
||||
def _get_member_offset(self, field_name: str, ClassName: str | None = None) -> int | None:
|
||||
def _get_member_offset(self, field_name: str, ClassName: str | None = None,
|
||||
StructHint: Any = None) -> int | None:
|
||||
# base 判定用未归一的原始类名:_member_base 内部自行做同一套归一
|
||||
# (_NormalizeMemberClass),二者结果逐字一致(避免二次归一在
|
||||
# "rawptr[X] 展开命中 sha1 前缀 struct 键"这一角落改变取值)。
|
||||
@@ -500,7 +536,7 @@ class FuncGenMixin:
|
||||
# 偏移整体差 1(ast.ExceptHandler.children 落到 end_col_offset(i32),
|
||||
# len 分派拿到 i32)。
|
||||
def _BaseNow() -> int:
|
||||
return self._member_base(_MemberBaseKey)
|
||||
return self._member_base(_MemberBaseKey, StructHint)
|
||||
# 预热(与原行为逐字一致):先触发一次 EnsureClassMembersFlattened,
|
||||
# 再决定下面的懒加载。
|
||||
_BaseNow()
|
||||
|
||||
Reference in new issue
Block a user