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 的行数)。