fix(LLVMCG/HandlesExprAttr): REnum 变体字段按唯一变体键取槽 + 修 _PointeeMatchesStruct 的「同模块前缀」误判
【a 编译器缺陷】两处根因,同一现场(CallGenDispatch.vp:787
`len_ap: llvmlite.LLVMType* = len_val.Ty.Pointee`):
1) `_PointeeMatchesStruct` 只比 sha1 前缀 ⇒ 把**同模块的兄弟类**当成同一个类
(`50741ad4b4b0934d.ParamNode` 与 `50741ad4b4b0934d.LLVMType` 前缀相同,同出
includes/llvmlite/__types.vp)。结构体分支迭代 Gen.structs 时先命中 ParamNode
(它在 __types.vp 里声明更早)⇒ `_get_member_offset('Pointee','ParamNode')`
直接 fast-fail,报出与真因无关的类名。判据收紧为:**同一对象** 或 **IR 全名逐字相同**
(前缀标识的是模块,不是类)。同短名不同前缀(同一类的另一模块实例)仍由调用点的
`_UniqShortCN`(同短名候选唯一)放行,不受影响。
2) REnum 的变体字段在旧路径上无处可取:'Pointee' 只属变体键 'LLVMType_Ptr',
而主类 'LLVMType' 的成员表为空(HandlesClassDef:716)⇒ 即使接收者判对类名也必 fail-fast。
新增 `FuncGen._RenumVariantFieldOwner`,在 `_get_member_offset` 里当"主类不声明该字段"时改按
变体键取偏移。判据(逐条可查):① 主类经 `_IsRenumLayoutClass`(唯一判据源)判为 REnum;
② 变体键固定 `<主类短名>_<变体名>` 且成员表**首项恒为 `__tag`**(唯一产源:
HandlesClassDef._EmitREnumLlvm 与 HandlesImports 的 REnum 跨模块注册);
③ 恰有**一个**变体声明该字段(多个 ⇒ 不猜,照常 fail-fast);④ 变体 struct 已注册。
槽位正确的依据:变体 struct 的 body 是**主 REnum 镜像** `{i32 __tag, <max_variant_payload>}`,
变体成员表 `[__tag] + 载荷` 与主 REnum 槽位一一对应。
3) 载荷槽按**最大变体**宽度物化('Pointee' 槽是 i64),而字段本身声明为指针 ⇒
新增 `HandlesExprAttr._RenumVariantPayloadType` / `_CoerceVariantPayload`,在
`_gep_and_Load_member` 载入后按字段声明类型收口(int→ptr: inttoptr;ptr→ptr: bitcast;
int→int: trunc/zext;不可转换原样返回,交下游照常 fail-fast)。非 REnum 变体键逐字不变。
冒烟(临时脚本已删):_PointeeMatchesStruct 5 例、_RenumVariantFieldOwner 7 例、
载荷类型与转换 6 例、_get_member_offset 钩子(绑定真实方法)5 例 —— 全部通过。
This commit is contained in:
1 parent
64ddabbe17
commit
49930c56c7
2 files changed
+156
-16
No files matched your search
@@ -22,6 +22,68 @@ class ExprAttrHandle(BaseHandle):
|
||||
val: ir.Value = Gen._load(ElemPtr, name="arr_elem_load")
|
||||
return Gen._apply_bswap_if_big(val, arr_byte_order, "bswap_arr_load")
|
||||
|
||||
def _RenumVariantPayloadType(self, Gen: LlvmCodeGenerator, ClassName: str | None,
|
||||
AttrName: str) -> Any:
|
||||
"""REnum **变体载荷**字段的声明类型(供 `_gep_and_Load_member` 载入后收口)。
|
||||
|
||||
变体 struct 的 body 是**主 REnum 镜像** `{i32 __tag, <max_variant_payload>}`
|
||||
(唯一产源:HandlesClassDef._EmitREnumLlvm:1655-1673 与 HandlesImports 的
|
||||
REnum 跨模块注册 :2024-2031),故载荷槽在 body 里的类型取**所有变体同位置的
|
||||
最大尺寸**(现场 'Pointee' 槽是 i64),而字段本身声明为指针/更窄整数 ⇒
|
||||
载入值须按声明类型收口,否则上游拿到 i64(现场 CallGenDispatch.vp:787
|
||||
`len_ap: llvmlite.LLVMType* = len_val.Ty.Pointee`)。
|
||||
|
||||
判据:类键(或它**唯一**的 REnum 变体键,见 FuncGen._RenumVariantFieldOwner)
|
||||
的成员表**首项是 '__tag'**(REnum 变体不变式)且声明了该字段;
|
||||
不命中返回 None(调用方原样返回,对既有类零变化)。
|
||||
"""
|
||||
if not ClassName or not AttrName:
|
||||
return None
|
||||
_Owners: list[str] = [ClassName]
|
||||
_Variant: str | None = None
|
||||
try:
|
||||
_Variant = Gen._RenumVariantFieldOwner(ClassName, AttrName)
|
||||
except Exception:
|
||||
_Variant = None
|
||||
if _Variant:
|
||||
_Owners.append(_Variant)
|
||||
for _cn in _Owners:
|
||||
_Members: list = (getattr(Gen, 'class_members', {}) or {}).get(_cn) or []
|
||||
if not _Members or _Members[0][0] != '__tag':
|
||||
continue
|
||||
_FieldTy: Any = next((t for n, t in _Members if n == AttrName), None)
|
||||
if _FieldTy is not None:
|
||||
return _FieldTy
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _CoerceVariantPayload(Gen: LlvmCodeGenerator, Value: Any, Want: Any) -> Any:
|
||||
"""payload 槽载入值 → 字段声明类型(REnum 变体载荷收口用)。
|
||||
|
||||
body 里载荷槽常是 i64(max-variant 镜像),而字段声明可能是指针/更窄整数:
|
||||
int → ptr:inttoptr;ptr → ptr:bitcast;int → int:trunc/zext;其余原样返回
|
||||
(不静默造值:类型不可转换时返回原值,让下游照常 fail-fast)。
|
||||
"""
|
||||
_got: Any = getattr(Value, 'type', None)
|
||||
try:
|
||||
if _got is Want:
|
||||
return Value
|
||||
if isinstance(Want, ir.PointerType):
|
||||
if isinstance(_got, ir.IntType):
|
||||
return Gen.builder.inttoptr(Value, Want, name="variant_payload_to_ptr")
|
||||
if isinstance(_got, ir.PointerType):
|
||||
return Gen.builder.bitcast(Value, Want, name="variant_payload_ptr_cast")
|
||||
elif isinstance(Want, ir.IntType) and isinstance(_got, ir.IntType):
|
||||
if Want.width < _got.width:
|
||||
return Gen.builder.trunc(Value, Want, name="variant_payload_trunc")
|
||||
if Want.width > _got.width:
|
||||
return Gen.builder.zext(Value, Want, name="variant_payload_zext")
|
||||
elif isinstance(_got, ir.PointerType) and isinstance(Want, ir.PointerType):
|
||||
return Gen.builder.bitcast(Value, Want, name="variant_payload_ptr_cast")
|
||||
except Exception:
|
||||
return Value
|
||||
return Value
|
||||
|
||||
def _apply_target_struct_hint(self, Gen: LlvmCodeGenerator, FieldName: str,
|
||||
ResolvedKey: str | None) -> str | None:
|
||||
"""左值注解**靶型提示**:解析出的类**不声明**该字段时,用赋值左侧注解指向的类兜一次。
|
||||
@@ -66,20 +128,22 @@ class ExprAttrHandle(BaseHandle):
|
||||
|
||||
@staticmethod
|
||||
def _PointeeMatchesStruct(Pointee: Any, ST: Any) -> bool:
|
||||
"""pointee 与该 struct 实例是否算**同一个类**(判据分级,宽到窄)。
|
||||
"""pointee 与该 struct 实例是否算**同一个类**。
|
||||
|
||||
原始判据只有"身份相同 / IR 全名逐字相同",遇到"同名类的另一份实例"就整条链放弃
|
||||
(现场:Repl.vp:802 `GenApi._g_gen.TypeGenCType.CTypedefNames` 的基座 pointee 是
|
||||
`%"750b8c10137d273e.TypeGenCType"*`,而 Gen.structs(250 键)里没有该 IR 名的实例
|
||||
⇒ 身份与全名都不等 ⇒ 属性链求值为空终止编译)。故补两级退化判据:
|
||||
判据只有两条:① 同一个对象;② IR 全名逐字相同(IR 名形如 `<sha1>.<短名>`,
|
||||
全名相同即同一类的同一份实例)。
|
||||
|
||||
① 同 IR 全名(原判据);
|
||||
② 同 sha1 前缀 —— IR 名形如 `<sha1>.<短名>`,前缀相等即"同一模块的同一类"
|
||||
(sha1 全名正是本仓为同名类冲突引入的命名形态)。
|
||||
**只比 sha1 前缀是错的(2026-09-30 修正)**:前缀标识的是**模块**而不是类——
|
||||
同一模块里的兄弟类(`50741ad4b4b0934d.ParamNode` 与 `50741ad4b4b0934d.LLVMType`
|
||||
同出 includes/llvmlite/__types.vp)前缀相同,只比前缀会把接收者锚到**错误的类**,
|
||||
随后按那份 body 取字段 ⇒ 报出与真因无关的类名。现场:CallGenDispatch.vp:787
|
||||
`len_val.Ty.Pointee` 的基座 pointee 是 LLVMType,却被锚到 ParamNode(2 字段
|
||||
Next/Ty,且 ParamNode 在 __types.vp 里声明更早 ⇒ 迭代先命中)⇒
|
||||
"属性 'Pointee' 在类 'ParamNode' 中找不到"。
|
||||
|
||||
同**短名**但不同 sha1 前缀的情形**不放行**(那可能是别模块的同名类,选错即静默
|
||||
错布局):它由调用点在"同短名候选唯一"时单独放行(见 `_UniqShortCN`)。
|
||||
找不到时仍返回 False(由调用点留痕,不兜底造类型)。
|
||||
同**短名**不同前缀(同一类的另一模块实例)不在此放行,由调用点在
|
||||
"同短名候选唯一"时单独处理(见 `_UniqShortCN`);都判不出时返回 False
|
||||
(调用点留痕,不兜底造类型)。
|
||||
"""
|
||||
if Pointee is ST:
|
||||
return True
|
||||
@@ -88,11 +152,7 @@ class ExprAttrHandle(BaseHandle):
|
||||
return False
|
||||
_pn: str = getattr(Pointee, 'name', '') or ''
|
||||
_sn: str = getattr(ST, 'name', '') or ''
|
||||
if _pn and _pn == _sn:
|
||||
return True
|
||||
_pp: str = _pn.rsplit('.', 1)[0] if '.' in _pn else ''
|
||||
_sp: str = _sn.rsplit('.', 1)[0] if '.' in _sn else ''
|
||||
return bool(_pp and _sp and _pp == _sp)
|
||||
return bool(_pn and _pn == _sn)
|
||||
|
||||
def _gep_and_Load_member(self, Gen: LlvmCodeGenerator, ObjVal: ir.Value, offset: int | None, AttrName: str, ClassName: str, ST: ir.Type | None = None) -> ir.Value | None:
|
||||
"""从结构体指针通过 GEP 取成员,处理 array/struct pointer/class_members 特殊情况"""
|
||||
@@ -153,6 +213,12 @@ class ExprAttrHandle(BaseHandle):
|
||||
if member_name == AttrName and isinstance(member_type, ir.ArrayType):
|
||||
return Gen.builder.bitcast(MemberPtr, ir.PointerType(member_type), name=f"cast_arr_{AttrName}")
|
||||
val: Any = Gen._load(MemberPtr, name=AttrName)
|
||||
# REnum 变体载荷槽收口(判据见 _RenumVariantPayloadType):变体 struct 的 body
|
||||
# 是主 REnum 镜像,载荷槽按**最大变体**宽度物化('Pointee' 槽是 i64),而字段
|
||||
# 声明类型可能是指针 ⇒ 载入后按声明类型转换;非 REnum 变体键逐字不变。
|
||||
_WantTy: Any = self._RenumVariantPayloadType(Gen, ClassName, AttrName)
|
||||
if _WantTy is not None:
|
||||
val = self._CoerceVariantPayload(Gen, val, _WantTy)
|
||||
return self._apply_bswap_on_Load(val, ClassName, AttrName)
|
||||
|
||||
def _try_Load_struct_from_stub(self, Gen: LlvmCodeGenerator, ClassName: str) -> tuple[ir.Type | None, bool]:
|
||||
|
||||
@@ -492,6 +492,49 @@ class FuncGenMixin:
|
||||
base = _BestB
|
||||
return base
|
||||
|
||||
def _RenumVariantFieldOwner(self, ClassName: str | None, field_name: str) -> str | None:
|
||||
"""REnum **变体字段**的归属类键:主 REnum 不声明该字段时,返回**唯一**声明它的变体键。
|
||||
|
||||
判据(逐条可查,不是猜):
|
||||
① 类短名按 `_IsRenumLayoutClass`(全仓唯一判据源)判为 REnum —— 只有 REnum
|
||||
才有变体层;
|
||||
② 变体类键固定为 `<主类短名>_<变体名>`,且成员表**首项恒为 `__tag`**
|
||||
(唯一产源:HandlesClassDef._EmitREnumLlvm:1659-1673 与 HandlesImports
|
||||
的 REnum 跨模块注册 :1975-2015);
|
||||
③ 恰有**一个**变体声明该字段(多个变体同名字段 ⇒ 无法判定 ⇒ None,由调用方
|
||||
照常 fail-fast);
|
||||
④ 该变体键的 struct 已注册(布局可用)。
|
||||
|
||||
用变体键算偏移为什么正确:变体 struct 的 body 是**主 REnum 镜像**
|
||||
`{i32 __tag, <max_variant_payload>}`,而变体成员表 `[__tag] + 载荷` 与主 REnum
|
||||
槽位一一对应 ⇒ `base + 表内下标` 即真实槽位。
|
||||
现场(2026-09-30):CallGenDispatch.vp:787 `len_val.Ty.Pointee` —— 'Pointee' 只属
|
||||
变体键 'LLVMType_Ptr',主类 'LLVMType' 的成员表为空(HandlesClassDef:716)。
|
||||
"""
|
||||
if not ClassName or not field_name:
|
||||
return None
|
||||
_Short: str | None = self._extract_short_name(ClassName)
|
||||
if not _Short:
|
||||
return None
|
||||
if not self._IsRenumLayoutClass(_Short):
|
||||
return None
|
||||
_Owner: str | None = None
|
||||
_Prefix: str = f"{_Short}_"
|
||||
for _Key, _Members in self.class_members.items():
|
||||
if not _Key.startswith(_Prefix):
|
||||
continue
|
||||
_List: list = _Members or []
|
||||
if not _List or _List[0][0] != '__tag':
|
||||
continue
|
||||
if not any(_n == field_name for _n, _ in _List):
|
||||
continue
|
||||
if _Key not in self.structs:
|
||||
continue
|
||||
if _Owner is not None:
|
||||
return None
|
||||
_Owner = _Key
|
||||
return _Owner
|
||||
|
||||
def _get_member_offset(self, field_name: str, ClassName: str | None = None,
|
||||
StructHint: Any = None) -> int | None:
|
||||
# base 判定用未归一的原始类名:_member_base 内部自行做同一套归一
|
||||
@@ -567,6 +610,37 @@ class FuncGenMixin:
|
||||
# 依赖补齐(上面的懒加载)后再确认一次展平(幂等);偏移基址由 _BaseNow()
|
||||
# 在真正 return 处现算,不在此缓存。
|
||||
_BaseNow()
|
||||
# ---- REnum 变体字段:主 REnum 不声明该字段时,改用它**唯一**的变体键继续 ----
|
||||
# 现场(2026-09-30):CallGenDispatch.vp:787 `len_val.Ty.Pointee` —— 'Pointee' 属
|
||||
# LLVMType 的 REnum 变体键 'LLVMType_Ptr',主类 'LLVMType' 成员表为空 ⇒ 旧路径
|
||||
# 直接 fast-fail "属性 'Pointee' 在类 'LLVMType' 中找不到" 终止整个编译。
|
||||
# 判据与槽位正确性见 _RenumVariantFieldOwner(变体 struct body = 主 REnum 镜像)。
|
||||
if field_name and ClassName:
|
||||
_MainMembers: list = (self.class_members.get(_ResolvedClass)
|
||||
or self.class_members.get(ClassName) or [])
|
||||
if not any(_n == field_name for _n, _ in _MainMembers):
|
||||
_VariantOwner: str | None = None
|
||||
try:
|
||||
_VariantOwner = self._RenumVariantFieldOwner(ClassName, field_name)
|
||||
except Exception:
|
||||
_VariantOwner = None
|
||||
if _VariantOwner:
|
||||
_RepRK: set = getattr(self, '_RenumVariantFieldReported', None)
|
||||
if _RepRK is None:
|
||||
_RepRK = set()
|
||||
self._RenumVariantFieldReported = _RepRK
|
||||
_RKey: tuple = (_VariantOwner, field_name)
|
||||
if _RKey not in _RepRK:
|
||||
_RepRK.add(_RKey)
|
||||
_vlog().warning(
|
||||
f"[REnum 变体字段] '{field_name}' 不在主 REnum 类 "
|
||||
f"{_ResolvedClass!r} 中,按唯一变体类 '{_VariantOwner}' 取偏移"
|
||||
f"(变体 struct body = 主 REnum 镜像,槽位一一对应)",
|
||||
"MemberBase")
|
||||
ClassName = _VariantOwner
|
||||
_ResolvedClass = _VariantOwner
|
||||
_MemberBaseKey = _VariantOwner
|
||||
_BaseNow()
|
||||
# ---- 布局真源优先(配对式偏移):偏移只认"运行时真正被 GEP 的那份 struct body" ----
|
||||
# class_members 是**可变**表(继承展平会在父类就绪后前置前缀、跨模块可按需重载),
|
||||
# 而 struct body 一旦 set_body 即**永久冻结** ⇒ "base + 成员表下标"会随表漂移
|
||||
|
||||
Reference in new issue
Block a user