fix(tpc): 构造物化豁免按「与目标同一」匹配,覆盖 sha1 前缀命名

a 编译器缺陷(承接 fa6b3c2e,仍拦 `*槽 = <boxed 变体构造>`):上一版按 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"
This commit is contained in:
TermiNexus committed 2026-09-28 23:32:23 +08:00
1 parent 78c5155de9
commit 49f0386c33
1 file changed
+72 -45
+72 -45
View File
@@ -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,