fix(LLVMCG/Handles): GEP 基址 opaque 时按同短名找已具化孪生体并 bitcast;守卫报文补实例清单

现场:includes/ast/astaux.vp:165 `self.arg = _copy_str(pool, arg)` 的基址 pointee 是
opaque 的 `%"<sha1>.Keyword"*` —— 同一类名常有两份 struct 实例(短名 'Keyword' /
sha1 全名 '<sha1>.Keyword'),"函数签名里的 self 用哪一份"与"StructGen 具化的是哪一份"
并不保证一致 ⇒ GEP 无法在编译期算元素类型(llvmlite 抛裸 TypeError),
_GuardIrBuilder 的 opaque 守卫先行 fail-fast。

改动:
1) 新增 BaseGen._FindConcreteTwin:按**同短名**(IR 全名相同者优先)在 structs 里取一份
   已具化实例(元素最多者),与既有"同名实例取字段最多版本"(HandlesAssign:1156-1163)
   同一判据;返回 None 表示确无孪生体(真因 = "body 从未 set_body")。
2) 写入路径 HandlesAssign(`self.字段 = ...`):pointee opaque 时 bitcast 到孪生体,
   并把它同时用作 struct_type 与 `_get_member_offset` 的 hint ⇒ 偏移与 GEP 同源。
3) 读取路径 HandlesExprAttr._gep_and_Load_member 共用同一工具函数(读写两路同判据,
   避免"写修了读没修"再吃一轮)。
4) opaque-GEP 守卫报文补"同短名 struct 实例(key(IR 名)=槽数)"清单:清单含已具化项
   ⇒ 属"偏移与 GEP 未同源";全是 opaque ⇒ 属"body 从未 set_body"——一次报文即可判定是哪一侧。

找不到孪生体时**不兜底**:仍由守卫 fail-fast 并报出实例清单(本仓"不许静默兜底"约定)。

验证:py_compile 通过(BaseGen / HandlesAssign / HandlesExprAttr 三文件)。
This commit is contained in:
TermiNexus committed 2026-09-30 11:57:07 +08:00
1 parent 55cbf1d261
commit 2aa55f36b9
3 files changed
+110

No files matched your search

+23
View File
@@ -878,6 +878,29 @@ class AssignHandle(BaseHandle):
SelfPtr, ir.PointerType(struct_type), SelfPtr, ir.PointerType(struct_type),
name=f"concrete_self_{ClassName}") name=f"concrete_self_{ClassName}")
_SelfPointee = struct_type _SelfPointee = struct_type
# 孪生体搜索(治本,2026-09-30):类键实例可能**也是** opaque —— 同一类名
# 常有两份 struct 实例(短名 'Keyword' / sha1 全名 '<sha1>.Keyword'),
# 函数签名里的 self 用哪一份、StructGen 具化的是哪一份**并不保证一致**。
# 现场:includes/ast/astaux.vp:165 `self.arg = _copy_str(pool, arg)` 的
# 基址 pointee 是 opaque 的 `%"<sha1>.Keyword"*`,GEP 无法在编译期算元素类型。
# 修法:按**同短名**取一份已具化实例(工具函数 _FindConcreteTwin,读路径
# HandlesExprAttr 共用同一判据)并 bitcast —— 偏移与 GEP 由此同源。
# 找不到 ⇒ 保持现状,交由 RequirePointerOperand 的 opaque 守卫 fail-fast
# (报出同短名实例清单,暴露"body 从未 set_body"),**不静默兜底**。
if (isinstance(_SelfPointee, ir.IdentifiedStructType)
and _SelfPointee.elements is None):
_Twin: ir.IdentifiedStructType | None = Gen._FindConcreteTwin(_SelfPointee)
if _Twin is None:
try:
_Twin = Gen._FindConcreteTwin(Gen.structs.get(ClassName))
except Exception:
_Twin = None
if _Twin is not None:
SelfPtr = Gen.builder.bitcast(
SelfPtr, ir.PointerType(_Twin),
name=f"concrete_twin_self_{ClassName}")
_SelfPointee = _Twin
struct_type = _Twin
offset: int = Gen._get_member_offset(AttrName, ClassName, _SelfPointee) offset: int = Gen._get_member_offset(AttrName, ClassName, _SelfPointee)
max_offset: int = (len(struct_type.elements) max_offset: int = (len(struct_type.elements)
if isinstance(struct_type, ir.IdentifiedStructType) if isinstance(struct_type, ir.IdentifiedStructType)
+18
View File
@@ -29,6 +29,24 @@ class ExprAttrHandle(BaseHandle):
return None return None
if isinstance(ObjVal.type, ir.PointerType): if isinstance(ObjVal.type, ir.PointerType):
pointee: Any = ObjVal.type.pointee pointee: Any = ObjVal.type.pointee
# opaque pointee:GEP 无法在编译期算元素类型(llvmlite 抛裸 TypeError,
# _GuardIrBuilder 会先 fail-fast)。先按**同短名**找一份已具化实例并 bitcast
# (与写入路径 HandlesAssign 的孪生体搜索共用 _FindConcreteTwin);
# 找不到则保持原样、由守卫报出同短名实例清单(暴露"body 从未 set_body"),
# **不静默兜底**。
if isinstance(pointee, ir.IdentifiedStructType) and pointee.elements is None:
_TwinR: Any = None
try:
_TwinR = Gen._FindConcreteTwin(pointee)
if _TwinR is None:
_TwinR = Gen._FindConcreteTwin(Gen.structs.get(ClassName))
except Exception:
_TwinR = None
if _TwinR is not None:
ObjVal = Gen.builder.bitcast(
ObjVal, ir.PointerType(_TwinR),
name=f"concrete_twin_read_{AttrName}")
pointee = _TwinR
if isinstance(pointee, ir.IdentifiedStructType) and pointee.elements is not None and offset >= len(pointee.elements): if isinstance(pointee, ir.IdentifiedStructType) and pointee.elements is not None and offset >= len(pointee.elements):
# sha1 冲突修复:pointee 字段不足时,尝试 bitcast 到 ST(更完整的版本) # sha1 冲突修复:pointee 字段不足时,尝试 bitcast 到 ST(更完整的版本)
if ST is not None and isinstance(ST, ir.IdentifiedStructType) and ST.elements is not None and offset < len(ST.elements): if ST is not None and isinstance(ST, ir.IdentifiedStructType) and ST.elements is not None and offset < len(ST.elements):
+69
View File
@@ -64,6 +64,30 @@ def RequirePointerOperand(Value: Any, OpName: str, Gen: Any = None, Detail: str
"\"该类 body 从未 set_body\"的根因(不做兜底)") "\"该类 body 从未 set_body\"的根因(不做兜底)")
if Gen is not None and hasattr(Gen, '_get_node_info'): if Gen is not None and hasattr(Gen, '_get_node_info'):
_MsgP += "\n " + Gen._get_node_info() _MsgP += "\n " + Gen._get_node_info()
# 同短名 struct 实例清单(key(IR 名)=槽数):**直接**区分两种根因——
# 清单里存在已具化项 ⇒ "偏移与 GEP 未同源"(应 bitcast 到该项,见
# HandlesAssign 的孪生体搜索);全是 opaque ⇒ "该类 body 从未 set_body"
# (往上查 StructGen 的 set_body 为何没跑)。不必再靠猜。
if Gen is not None and isinstance(getattr(Gen, 'structs', None), dict):
_PShort: str = ''
try:
_PShort = Gen._extract_short_name(getattr(_Pointee, 'name', '') or '')
except Exception:
_PShort = ''
_Kin: list[str] = []
for _k_n, _k_v in list(Gen.structs.items()):
if not isinstance(_k_v, ir.IdentifiedStructType):
continue
try:
_k_s: str = Gen._extract_short_name(getattr(_k_v, 'name', '') or '')
except Exception:
_k_s = ''
if _PShort and _k_s != _PShort:
continue
_k_cnt: Any = (len(_k_v.elements) if _k_v.elements is not None else 'opaque')
_Kin.append(f"{_k_n}({getattr(_k_v, 'name', '?')})={_k_cnt}")
if _Kin:
_MsgP += "\n 同短名 struct 实例(key(IR 名)=槽数): " + ", ".join(_Kin)
raise FailFastError(_MsgP) raise FailFastError(_MsgP)
return return
# 失败路径才导入:热路径(每次 gep/load/store)不承担导入开销,也避免循环导入。 # 失败路径才导入:热路径(每次 gep/load/store)不承担导入开销,也避免循环导入。
@@ -1218,6 +1242,51 @@ class BaseGenMixin:
return self._current_source_lines[lineno - 1].rstrip('\r\n') return self._current_source_lines[lineno - 1].rstrip('\r\n')
return None return None
def _FindConcreteTwin(self, StructType: Any) -> Any:
"""按**同短名**在 `structs` 里找一份**已具化**(elements 非 None)的 struct 实例。
为什么需要:同一类名常有两份 struct 实例(短名 'Keyword' / sha1 全名
'<sha1>.Keyword'),"函数签名里的 self 用哪一份"与"StructGen 具化的是哪一份"
并不保证一致;一旦被 GEP 的那份是 opaque,llvmlite 在编译期算元素类型即抛裸
TypeError(现场:includes/ast/astaux.vp:165 `self.arg = _copy_str(pool, arg)`
的基址 pointee 是 opaque 的 `%"<sha1>.Keyword"*`)。调用方把指针 bitcast 到
本函数返回的实例即可让"偏移与 GEP 同源"——与 HandlesAssign 里
"同短名取字段最多版本"是同一判据。
返回 None 表示**没有**已具化孪生体(真因是"body 从未 set_body")⇒ 调用方不得
兜底,交由 RequirePointerOperand 的 opaque 守卫 fail-fast 并报出实例清单。
"""
if not isinstance(StructType, ir.IdentifiedStructType):
return None
_Short: str = ''
try:
_Short = self._extract_short_name(getattr(StructType, 'name', '') or '')
except Exception:
_Short = ''
if not _Short:
return None
# 第一优先:**IR 全名相同**的实例(同类名两份实例的情形,必为同一类);
# 第二优先:同短名里元素最多的一份(跨模块同名类;与 HandlesAssign
# "同短名取字段最多版本"同判据)。全名相同优先可避免"短名相同但实为另一个
# 模块的同名类"被误选而算出错的偏移。
_Full: str = getattr(StructType, 'name', '') or ''
_Best: Any = None
_BestN: int = -1
for _v in list(getattr(self, 'structs', {}).values()):
if not isinstance(_v, ir.IdentifiedStructType) or _v.elements is None:
continue
if _Full and getattr(_v, 'name', '') == _Full:
return _v
try:
_s: str = self._extract_short_name(getattr(_v, 'name', '') or '')
except Exception:
_s = ''
if _s != _Short:
continue
if len(_v.elements) > _BestN:
_BestN = len(_v.elements)
_Best = _v
return _Best
def _DiskLineno(self, Lineno: int) -> int: def _DiskLineno(self, Lineno: int) -> int:
"""AST 行号 → 磁盘真实行号(减掉文件头自动注入 import 的行数)。 """AST 行号 → 磁盘真实行号(减掉文件头自动注入 import 的行数)。