From 49f0386c3353e531816c8926071c240ceaf7a41f Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Mon, 28 Sep 2026 23:32:23 +0800 Subject: [PATCH] =?UTF-8?q?fix(tpc):=20=E6=9E=84=E9=80=A0=E7=89=A9?= =?UTF-8?q?=E5=8C=96=E8=B1=81=E5=85=8D=E6=8C=89=E3=80=8C=E4=B8=8E=E7=9B=AE?= =?UTF-8?q?=E6=A0=87=E5=90=8C=E4=B8=80=E3=80=8D=E5=8C=B9=E9=85=8D=EF=BC=8C?= =?UTF-8?q?=E8=A6=86=E7=9B=96=20sha1=20=E5=89=8D=E7=BC=80=E5=91=BD?= =?UTF-8?q?=E5=90=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit a 编译器缺陷(承接 fa6b3c2e,仍拦 `*槽 = `):上一版按 callee 候选名直查 `Gen.structs` 取聚合 —— 但跨模块命名隔离下,同一类型的键是 `sha1.ClassName` 全名(stub 里就是 `%"50741ad4b4b0934d.LLVMType"`),裸名键 往往另有一份 opaque 占位条目且**不是同一 IR 类型**。于是 `LLVMType.Struct(...)`(候选名 Struct / LLVMType)取到的是占位/取不到, 与 target_type(`%"50741ad4b4b0934d.LLVMType"`)比对失败 ⇒ 豁免不生效。 修法:不按裸名直查,改为「键等于候选名,或以 `.<候选名>` 结尾」扫描 structs, 并要求取到的聚合与 TargetType **同一**(对象同一或 IR 名相同)才命中。命中后 若源的被指类型与目标仍非同一对象/名字(构造走的是另一份条目),按 DerefAs 的 整对象拷贝语义落地:指针 bitcast 到目标被指类型再 load(指针→指针 bitcast 恒 合法),绝不把指针值写进值槽。 现场(TransPyV/App/lib/core/Gen/Class/ClassGenDef.vp:122): *existing.Ty = llvmlite.LLVMType.Struct(...) → 非基本类型的值/指针隐式转换已禁止(构造物化除外):不能把指针类型 %"50741ad4b4b0934d.LLVMType"* 隐式转为值类型 %"50741ad4b4b0934d.LLVMType" --- lib/core/LLVMCG/MemoryOps.py | 117 +++++++++++++++++++++-------------- 1 file changed, 72 insertions(+), 45 deletions(-) diff --git a/lib/core/LLVMCG/MemoryOps.py b/lib/core/LLVMCG/MemoryOps.py index 201fd03..46d83a0 100644 --- a/lib/core/LLVMCG/MemoryOps.py +++ b/lib/core/LLVMCG/MemoryOps.py @@ -388,53 +388,77 @@ class MemoryOpsMixin: return Node.value return Node - def _CtorCallStructType(self, Node: Any = None) -> Any: + @staticmethod + def _IsSameAggregate(A: Any, B: Any) -> bool: + """两个 LLVM 聚合类型是否指同一类型(对象同一,或 Identified 结构体 IR 名相同)。""" + if A is None or B is None: + return False + if A == B: + return True + return (isinstance(A, ir.IdentifiedStructType) + and isinstance(B, ir.IdentifiedStructType) + and A.name == B.name) + + @staticmethod + def _CtorCalleeNames(Func: Any) -> list[str]: + """构造调用 callee 的候选聚合名(类构造 / REnum 变体构造)。 + + - `Point()` → ['Point'](callee 即类名) + - `Qual.Variant(...)` → ['Variant', 'Qual'] + - `mod.Qual.Variant(...)` → ['Variant', 'Qual'] + 变体短名在前(同名类构造优先,与实际 dispatch 一致),限定名(REnum 类名) + 在后。 + """ + Names: list[str] = [] + if isinstance(Func, ast.Name): + Names.append(Func.id) + elif isinstance(Func, ast.Attribute): + Names.append(Func.attr) + QualNode: Any = getattr(Func, 'value', None) + if isinstance(QualNode, ast.Name): + Names.append(QualNode.id) + elif isinstance(QualNode, ast.Attribute): + Names.append(QualNode.attr) + return Names + + def _CtorCallStructType(self, Node: Any, TargetType: Any) -> Any: """AST 层判定:Node(或其值子节点)是否为「构造调用」右值;是则返回其构造的 聚合类型(IdentifiedStruct/LiteralStruct),否则返回 None。 规则③(非基本类型值 ↔ 指针隐式转换一律禁止)的**唯一例外**是「构造物化」: - 右值来自该类型的构造调用时放行(构造语义,非类型转换)。判据复用本仓既有的 - 「Call 的被调方是构造」口径(见 HandlesExprCall._HandleCallLlvm 的分派顺序): + 右值来自该类型的构造调用时放行(构造语义,非类型转换)。判据(见 + HandlesExprCall._HandleCallLlvm 的分派口径): - callee 裸名/属性名命中已注册聚合(`Gen.structs`)→ 类构造(`Point()`); - - callee 是 `Qual.Variant(...)` / `mod.Qual.Variant(...)` 的**变体**写法 —— - 变体短名(如 `Struct`)不在 `Gen.structs` 中,构造出的聚合类型挂在 - **限定名 Qual**(REnum 类,如 `LLVMType`)下,故再按 Qual 取一次。 - REnum 变体构造返回 **boxed 指针**(`Gen._allocaEntry(RenumType)`, - 见 HandlesExprCall._HandleREnumConstructLlvm),其 pointee 恰是 - `structs[Qual]` —— 正是「构造物化」要放行的形态 - (`*existing.Ty = llvmtype.Struct(...)`,boxed 指针 → 值槽的整对象拷贝)。 + - callee 是 `Qual.Variant(...)`(REnum 变体构造,如 `LLVMType.Struct(...)`) + —— 变体短名不是聚合,构造出的聚合类型挂在**限定名 Qual**(REnum 类)下。 + REnum 变体构造返回 **boxed 指针**(`Gen._allocaEntry(RenumType)`),其 + pointee 恰是该聚合 —— 正是「构造物化」要放行的形态(boxed 指针 → 值槽的 + 整对象拷贝,语义即 load + store)。 - 泛型类模板特化(Box(42))需类型实参推断,静态取不到类型 → 保守返回 None。 - 顺序与 `_HandleCallLlvm` 一致:先按 callee 名判类构造,miss 后再按限定名判 - 变体构造(若同名类存在,实际 dispatch 也走类构造路径)。 + + `Gen.structs` 的键在跨模块命名隔离下可能是 `sha1.ClassName` 全名,且裸名键 + 可能另有一份 opaque 占位条目(两者不是同一 IR 类型)——故不按裸名直查,而是 + 按「键等于候选名,或以 `.<候选名>` 结尾」扫描,并要求取到的聚合与 TargetType + 同一(对象同一或 IR 名相同)。这样无论构造走的是哪份条目、无论结果走 + `_HandleREnumConstructLlvm` 还是 stub 侧同签名的变体工厂函数,都能正确放行。 """ N: Any = MemoryOpsMixin._ValueNodeOf(Node) if not isinstance(N, ast.Call): return None - Func: Any = getattr(N, 'func', None) - ClassName: str | None = None - Qualifier: str | None = None - if isinstance(Func, ast.Name): - ClassName = Func.id - elif isinstance(Func, ast.Attribute): - ClassName = Func.attr - # 变体构造的限定名:`Qual.Variant` 的 Qual(Name)或 `mod.Qual.Variant` - # 的 Qual(Attribute.attr)。 - _QualNode: Any = getattr(Func, 'value', None) - if isinstance(_QualNode, ast.Name): - Qualifier = _QualNode.id - elif isinstance(_QualNode, ast.Attribute): - Qualifier = _QualNode.attr Structs: Any = getattr(self, 'structs', {}) - for _Cand in (ClassName, Qualifier): - if not _Cand: + for Name in MemoryOpsMixin._CtorCalleeNames(getattr(N, 'func', None)): + if not Name: continue - ST: Any = Structs.get(_Cand) - if ST is None: - continue - if isinstance(ST, ir.PointerType): - ST = ST.pointee - if isinstance(ST, (ir.IdentifiedStructType, ir.LiteralStructType)): - return ST + for Key, Val in Structs.items(): + if not isinstance(Key, str): + continue + if Key != Name and not Key.endswith('.' + Name): + continue + ST: Any = Val.pointee if isinstance(Val, ir.PointerType) else Val + if not isinstance(ST, (ir.IdentifiedStructType, ir.LiteralStructType)): + continue + if MemoryOpsMixin._IsSameAggregate(ST, TargetType): + return ST return None def _alloca(self, typ: ir.Type, name: str = '', align: int | None = None, size: ir.Value | None = None) -> ir.AllocaInstr: @@ -623,16 +647,10 @@ class MemoryOpsMixin: # 须显式解引用(c.Deref(p) / *p)或显式强转。基本标量目标已在上方 # 「指针隐式转整数」分支 fail-fast,此处只兜非基本类型。 # **唯一例外=「构造物化」**:右值来自该类型的构造调用时放行,如 - # `p: Point = Point()`(参照 GCC 构造语义:这是构造,不是类型转换)。 - CtorST: Any = self._CtorCallStructType(Node) - CtorExempt: bool = False - if CtorST is not None: - if CtorST == target_type: - CtorExempt = True - elif (isinstance(CtorST, ir.IdentifiedStructType) - and isinstance(target_type, ir.IdentifiedStructType) - and CtorST.name == target_type.name): - CtorExempt = True + # `p: Point = Point()`、`*slot = LLVMType.Struct(...)`(参照 GCC 构造 + # 语义:这是构造,不是类型转换)。判据把 TargetType 传下去,要求 AST 侧 + # 的构造类型与目标类型**同一**(对象同一或 IR 名相同)才算命中。 + CtorExempt: bool = self._CtorCallStructType(Node, target_type) is not None if not CtorExempt: Allowed3: bool Reason3: str @@ -652,6 +670,15 @@ class MemoryOpsMixin: Loaded: ir.Value = self.builder.load(val, name="Load_struct_for_store") if Loaded.type == target_type: return Loaded + if CtorExempt: + # 构造物化命中,但源的被指类型与目标不是同一 LLVM 对象、IR 名也不一致 + # (跨模块命名隔离:`Gen.structs` 里裸名 opaque 占位与 sha1 全名实体 + # 并存,构造走的是另一份条目)⇒ 按整对象拷贝语义落地:先把指针 + # bitcast 到目标被指类型再 load(指针→指针 bitcast 恒合法),确保 + # 不会把指针值写进值槽。 + Src: ir.Value = self.builder.bitcast( + val, ir.PointerType(target_type), name="ctor_ptr_bitcast") + return self.builder.load(Src, name="Load_ctor_for_store") return None def _store(self, val: ir.Value, ptr: ir.Value, align: int | None = None,