From 2aa55f36b9afd07e06407a6478d32bccb810dfa9 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Wed, 30 Sep 2026 11:57:07 +0800 Subject: [PATCH] =?UTF-8?q?fix(LLVMCG/Handles):=20GEP=20=E5=9F=BA=E5=9D=80?= =?UTF-8?q?=20opaque=20=E6=97=B6=E6=8C=89=E5=90=8C=E7=9F=AD=E5=90=8D?= =?UTF-8?q?=E6=89=BE=E5=B7=B2=E5=85=B7=E5=8C=96=E5=AD=AA=E7=94=9F=E4=BD=93?= =?UTF-8?q?=E5=B9=B6=20bitcast=EF=BC=9B=E5=AE=88=E5=8D=AB=E6=8A=A5?= =?UTF-8?q?=E6=96=87=E8=A1=A5=E5=AE=9E=E4=BE=8B=E6=B8=85=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 现场:includes/ast/astaux.vp:165 `self.arg = _copy_str(pool, arg)` 的基址 pointee 是 opaque 的 `%".Keyword"*` —— 同一类名常有两份 struct 实例(短名 'Keyword' / 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 三文件)。 --- lib/core/Handles/HandlesAssign.py | 23 ++++++++++ lib/core/Handles/HandlesExprAttr.py | 18 ++++++++ lib/core/LLVMCG/BaseGen.py | 69 +++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+) diff --git a/lib/core/Handles/HandlesAssign.py b/lib/core/Handles/HandlesAssign.py index 5dca602..af74153 100644 --- a/lib/core/Handles/HandlesAssign.py +++ b/lib/core/Handles/HandlesAssign.py @@ -878,6 +878,29 @@ class AssignHandle(BaseHandle): SelfPtr, ir.PointerType(struct_type), name=f"concrete_self_{ClassName}") _SelfPointee = struct_type + # 孪生体搜索(治本,2026-09-30):类键实例可能**也是** opaque —— 同一类名 + # 常有两份 struct 实例(短名 'Keyword' / sha1 全名 '.Keyword'), + # 函数签名里的 self 用哪一份、StructGen 具化的是哪一份**并不保证一致**。 + # 现场:includes/ast/astaux.vp:165 `self.arg = _copy_str(pool, arg)` 的 + # 基址 pointee 是 opaque 的 `%".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) max_offset: int = (len(struct_type.elements) if isinstance(struct_type, ir.IdentifiedStructType) diff --git a/lib/core/Handles/HandlesExprAttr.py b/lib/core/Handles/HandlesExprAttr.py index 706d320..a0dc408 100644 --- a/lib/core/Handles/HandlesExprAttr.py +++ b/lib/core/Handles/HandlesExprAttr.py @@ -29,6 +29,24 @@ class ExprAttrHandle(BaseHandle): return None if isinstance(ObjVal.type, ir.PointerType): 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): # 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): diff --git a/lib/core/LLVMCG/BaseGen.py b/lib/core/LLVMCG/BaseGen.py index c08fd69..0af055d 100644 --- a/lib/core/LLVMCG/BaseGen.py +++ b/lib/core/LLVMCG/BaseGen.py @@ -64,6 +64,30 @@ def RequirePointerOperand(Value: Any, OpName: str, Gen: Any = None, Detail: str "\"该类 body 从未 set_body\"的根因(不做兜底)") if Gen is not None and hasattr(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) return # 失败路径才导入:热路径(每次 gep/load/store)不承担导入开销,也避免循环导入。 @@ -1218,6 +1242,51 @@ class BaseGenMixin: return self._current_source_lines[lineno - 1].rstrip('\r\n') return None + def _FindConcreteTwin(self, StructType: Any) -> Any: + """按**同短名**在 `structs` 里找一份**已具化**(elements 非 None)的 struct 实例。 + + 为什么需要:同一类名常有两份 struct 实例(短名 'Keyword' / sha1 全名 + '.Keyword'),"函数签名里的 self 用哪一份"与"StructGen 具化的是哪一份" + 并不保证一致;一旦被 GEP 的那份是 opaque,llvmlite 在编译期算元素类型即抛裸 + TypeError(现场:includes/ast/astaux.vp:165 `self.arg = _copy_str(pool, arg)` + 的基址 pointee 是 opaque 的 `%".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: """AST 行号 → 磁盘真实行号(减掉文件头自动注入 import 的行数)。