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:
TermiNexus committed 2026-09-30 12:46:37 +08:00
1 parent 64ddabbe17
commit 49930c56c7
2 files changed
+156 -16

No files matched your search

+82 -16
View File
@@ -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]:
+74
View File
@@ -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 + 成员表下标"会随表漂移