From a4bc5dc0ce3c7733b3a79332a6cfa28ac1f6ee55 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Fri, 11 Sep 2026 22:09:18 +0800 Subject: [PATCH] =?UTF-8?q?refactor(tpv):=20=E5=88=A0=E9=99=A4=20IsPtrElem?= =?UTF-8?q?ent=20=E5=AD=97=E6=AE=B5=EF=BC=8C=E6=8C=87=E9=92=88=E5=85=83?= =?UTF-8?q?=E7=B4=A0=E8=AF=AD=E4=B9=89=E6=89=BF=E8=BD=BD=E4=BA=8E=20Spec.E?= =?UTF-8?q?lemBits?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - TypeSpec 抽 _spec_clone(消除 SpecWithSign/Source/ElemBits 三处重复逐字段拷贝) - 新增 SpecWithElemBits:覆盖元素槽位宽(独立副本不驻留,ElemBits 不在驻留键内) - set_var_ptr_element 改写 Spec.ElemBits=64;Spec 不可承载(非指针/缺失)即 fail-fast - 新增 var_is_ptr_element(PtrLevel>=1 且 Elem.Bits==8 且 ElemBits==64,排除真实 i64* 与纯 str) - ExprGenName 4 处下标读写切读该入口;GenTypes 删 VarEntry.IsPtrElement --- App/lib/core/Gen/Expr/ExprGenName.vp | 8 +-- App/lib/core/Gen/GenTypes.vp | 5 +- App/lib/core/Gen/Type/TypeSpec.vp | 98 ++++++++++++++++++---------- App/lib/core/Gen/VarGenMixin.vp | 46 +++++++++++-- 4 files changed, 110 insertions(+), 47 deletions(-) diff --git a/App/lib/core/Gen/Expr/ExprGenName.vp b/App/lib/core/Gen/Expr/ExprGenName.vp index c6eae81..366066a 100644 --- a/App/lib/core/Gen/Expr/ExprGenName.vp +++ b/App/lib/core/Gen/Expr/ExprGenName.vp @@ -920,7 +920,7 @@ class ExprGenName(ExprGenBase.ExprGenBase): if trans is not None: ve_pe_ts: GenTypes.VarEntry | t.CPtr = \ self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id) - if ve_pe_ts is not None and ve_pe_ts.IsPtrElement == 1: + if self.Gen.VarGen.var_is_ptr_element(ve_pe_ts) != 0: # bytes|t.CPtr / str|t.CPtr / T|t.CPtr:变量存指针值(i8*), # elem[i] 语义 = load [指针值 + i*8](8 字节指针元素)。 # 必须先 load alloca 得指针值,再 GEP i8*(步长 8)—— @@ -951,7 +951,7 @@ class ExprGenName(ExprGenBase.ExprGenBase): if trans is not None: ve_pe_rd: GenTypes.VarEntry | t.CPtr = \ self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id) - if ve_pe_rd is not None and ve_pe_rd.IsPtrElement == 1: + if self.Gen.VarGen.var_is_ptr_element(ve_pe_rd) != 0: match pointee: case llvmlite.LLVMType.Int(bits_rd): if bits_rd == 8: @@ -1182,7 +1182,7 @@ class ExprGenName(ExprGenBase.ExprGenBase): if trans is not None: ve_pe: GenTypes.VarEntry | t.CPtr = \ self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id) - if ve_pe is not None and ve_pe.IsPtrElement == 1: + if self.Gen.VarGen.var_is_ptr_element(ve_pe) != 0: # bytes|t.CPtr / str|t.CPtr / T|t.CPtr:变量存指针值, # 下标语义 = [指针值 + i*8](8 字节指针元素)。必须先 load # alloca 得指针值,再 GEP——否则 GEP base 用 alloca(&elem_ptr) @@ -1232,7 +1232,7 @@ class ExprGenName(ExprGenBase.ExprGenBase): if trans is not None: ve_pe_cs: GenTypes.VarEntry | t.CPtr = \ self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id) - if ve_pe_cs is not None and ve_pe_cs.IsPtrElement == 1: + if self.Gen.VarGen.var_is_ptr_element(ve_pe_cs) != 0: # T|t.CPtr(退化为 i8*,如 _list.vp 的 elem_ptr)变量 # 存数据区指针,元素为 8 字节指针:必须先 load alloca # 得指针值,再按 i8* 步长 GEP——否则 GEP base 用 alloca diff --git a/App/lib/core/Gen/GenTypes.vp b/App/lib/core/Gen/GenTypes.vp index bf63442..f9d9116 100644 --- a/App/lib/core/Gen/GenTypes.vp +++ b/App/lib/core/Gen/GenTypes.vp @@ -67,11 +67,12 @@ class VarEntry: Name: str Alloca: llvmlite.Value | t.CPtr Used: int - IsPtrElement: int # 标志: 1=注解为 bytes|t.CPtr 或 str|t.CPtr, 下标按 8 字节步长 # TypeSpec(S3 起为**唯一类型承载**):由 add_var 用 SpecFromLLVMType 从 # alloca.Ty 的 pointee 填充(PermPool 分配 ⇒ 跨文件安全),注解侧再由 # set_var_annot_class_name 经 SpecWithSign / SpecWithSource 补整数符号性与 - # 注解来源名(Spec.SourceName,原 AnnotClassName 字段已并入)。 + # 注解来源名(Spec.SourceName,原 AnnotClassName 字段已并入),由 + # set_var_ptr_element 经 SpecWithElemBits 补「指针元素」语义(原 + # IsPtrElement 字段已并入 Spec.ElemBits)。 Spec: TSpec.TypeSpec | t.CPtr diff --git a/App/lib/core/Gen/Type/TypeSpec.vp b/App/lib/core/Gen/Type/TypeSpec.vp index 60aa1b0..c70ce82 100644 --- a/App/lib/core/Gen/Type/TypeSpec.vp +++ b/App/lib/core/Gen/Type/TypeSpec.vp @@ -836,6 +836,40 @@ def SpecPointedTo(sp: TypeSpec | t.CPtr) -> TypeSpec | t.CPtr: return sp.Elem +# ============================================================ +# _spec_clone - 浅拷贝 TypeSpec(PermPool,**不驻留**) +# +# Elem/Ret/Params 指针共享子实例(子实例自身由 NewTypeXxx 驻留)。 +# 供 SpecWithSign / SpecWithSource / SpecWithElemBits 复用,避免多处重复 +# 逐字段拷贝(漏字段是最容易引入的隐蔽 bug:例如忘了 IsElemPtr)。 +# ============================================================ +def _spec_clone(sp: TypeSpec | t.CPtr) -> TypeSpec | t.CPtr: + """浅拷贝 sp(含全部字段),分配失败返回 None""" + if sp is None: + return None + cp: TypeSpec | t.CPtr = _spec_alloc() + if cp is None: + return None + cp.Kind = sp.Kind + cp.Bits = sp.Bits + cp.IsSigned = sp.IsSigned + cp.PtrLevel = sp.PtrLevel + cp.Elem = sp.Elem + cp.ArrayCount = sp.ArrayCount + cp.BaseName = sp.BaseName + cp.SourceName = sp.SourceName + cp.Ret = sp.Ret + cp.Params = sp.Params + cp.ParamCount = sp.ParamCount + cp.IsVarArg = sp.IsVarArg + cp.Storage = sp.Storage + cp.ElemBits = sp.ElemBits + cp.IsElemPtr = sp.IsElemPtr + cp.DerefBase = sp.DerefBase + cp.Ty = sp.Ty + return cp + + # ============================================================ # SpecWithSign - 复制 Spec 并按注解覆盖整数符号性(TS_INT ↔ TS_UINT) # @@ -845,8 +879,7 @@ def SpecPointedTo(sp: TypeSpec | t.CPtr) -> TypeSpec | t.CPtr: # 的整数会得到相同驻留键 "i%d" 而撞键(同键不同义,返回错误实例)。 # 注解侧知道真实符号性(uint32 等无符号名),须把 TS_INT 切为 TS_UINT。 # -# 浅拷贝结构(Elem/Ret/Params 指针共享已驻留子实例);仅当符号性实际变化才 -# 复制并驻留,否则返回原实例。驻留去重 ⇒ 同「结构+符号」共享一份。 +# 仅当符号性实际变化才复制并驻留,否则返回原实例。驻留去重 ⇒ 同「结构+符号」共享一份。 # ============================================================ def SpecWithSign(arena: TypeSpecArena | t.CPtr, sp: TypeSpec | t.CPtr, @@ -864,29 +897,16 @@ def SpecWithSign(arena: TypeSpecArena | t.CPtr, new_signed = TS_SIGN_SIGNED if new_kind == sp.Kind: return sp - cp: TypeSpec | t.CPtr = _spec_alloc() + cp: TypeSpec | t.CPtr = _spec_clone(sp) if cp is None: return None cp.Kind = new_kind - cp.Bits = sp.Bits cp.IsSigned = new_signed - cp.PtrLevel = sp.PtrLevel - cp.Elem = sp.Elem - cp.ArrayCount = sp.ArrayCount - cp.BaseName = sp.BaseName - # **不复制 SourceName**:本函数走 InternSpec(键 = SpecToText,不含 SourceName), + # **清空 SourceName**:本函数走 InternSpec(键 = SpecToText,不含 SourceName), # 若把带来源名的副本写进表,后续同键查找会把该来源名**污染**给无关条目 # (键相同 ⇒ 语义必须相同 ⇒ 表内实例的 SourceName 恒为 None)。 # 需要来源名时由调用方随后用 SpecWithSource(独立副本)叠加。 - cp.Ret = sp.Ret - cp.Params = sp.Params - cp.ParamCount = sp.ParamCount - cp.IsVarArg = sp.IsVarArg - cp.Storage = sp.Storage - cp.ElemBits = sp.ElemBits - cp.IsElemPtr = sp.IsElemPtr - cp.DerefBase = sp.DerefBase - cp.Ty = sp.Ty + cp.SourceName = None return InternSpec(arena, cp) @@ -927,30 +947,38 @@ def SpecWithSource(sp: TypeSpec | t.CPtr, name: str) -> TypeSpec | t.CPtr: return sp if sp.SourceName is not None and string.strcmp(sp.SourceName, name) == 0: return sp - cp: TypeSpec | t.CPtr = _spec_alloc() + cp: TypeSpec | t.CPtr = _spec_clone(sp) if cp is None: return sp - cp.Kind = sp.Kind - cp.Bits = sp.Bits - cp.IsSigned = sp.IsSigned - cp.PtrLevel = sp.PtrLevel - cp.Elem = sp.Elem - cp.ArrayCount = sp.ArrayCount - cp.BaseName = sp.BaseName - cp.Ret = sp.Ret - cp.Params = sp.Params - cp.ParamCount = sp.ParamCount - cp.IsVarArg = sp.IsVarArg - cp.Storage = sp.Storage - cp.ElemBits = sp.ElemBits - cp.IsElemPtr = sp.IsElemPtr - cp.DerefBase = sp.DerefBase - cp.Ty = sp.Ty if _spec_set_source_name(cp, name) != 0: return sp return cp +# ============================================================ +# SpecWithElemBits - 复制 Spec 并覆盖元素槽位宽(独立副本,**不驻留**) +# +# 用途(S4 的 IsPtrElement 替代):`str | t.CPtr` / `bytes | t.CPtr` / +# `T | t.CPtr` / `64 位标量 | t.CPtr` 在 LLVM 层**都是单层 Ptr(i8)** +# (PtrLevel=1 同时对应两种相反语义),无法据类型区分"1 字节步长"与 +# "8 字节槽步长";区分只能靠注解判定 ⇒ 注解侧把元素槽宽覆盖为 8 字节。 +# +# ElemBits 是派生字段(**不在 SpecToText 键内**)⇒ 必须返回独立副本: +# 若走 InternSpec 会命中键相同但 ElemBits 未覆盖的实例,覆盖丢失。 +# ============================================================ +def SpecWithElemBits(sp: TypeSpec | t.CPtr, elem_bits: int) -> TypeSpec | t.CPtr: + """复制 sp 并设置 ElemBits(独立副本,不驻留);无变化返回原实例""" + if sp is None: + return sp + if sp.ElemBits == elem_bits: + return sp + cp: TypeSpec | t.CPtr = _spec_clone(sp) + if cp is None: + return sp + cp.ElemBits = elem_bits + return cp + + def SpecSourceName(sp: TypeSpec | t.CPtr) -> str: """Spec 的注解来源名(未设置返回 None)""" if sp is None: diff --git a/App/lib/core/Gen/VarGenMixin.vp b/App/lib/core/Gen/VarGenMixin.vp index 75ac0ae..44386cc 100644 --- a/App/lib/core/Gen/VarGenMixin.vp +++ b/App/lib/core/Gen/VarGenMixin.vp @@ -169,21 +169,55 @@ class VarGenMixin: return TSpec.SpecSourceName(entry.Spec) # ============================================================ - # set_var_ptr_element — 标记变量为"指针到 str/bytes"类型 + # set_var_ptr_element — 标记变量为「指针元素(ptr-element)」语义 # - # 注解为 bytes|t.CPtr 或 str|t.CPtr 的变量,其 alloca 类型是 i8*, - # 但下标访问应按 8 字节步长(i8** 语义),而非 1 字节步长(i8* 语义)。 - # 此标志在 HandlesAnnAssign 中根据注解形式设置。 + # 注解为 bytes|t.CPtr / str|t.CPtr / T|t.CPtr / 64位标量|t.CPtr 的变量, + # 其 LLVM 类型退化为 i8*(Ptr(Int8)),但下标访问应按 **8 字节槽步长** + # (i8** 语义),而非 1 字节步长(i8* 语义)。 + # + # [S4] 原 VarEntry.IsPtrElement 布尔字段已删除,改由 Spec.ElemBits 承载: + # 上述四类在 LLVM 层都是 Ptr(Int8)(ElemBits=8,无法区分),故注解侧把 + # 元素槽宽覆盖为 8 字节;消费点经 var_is_ptr_element 读取。 + # 由 AnnAssign 按注解形式(_is_ptr_element_annotation)调用。 # ============================================================ def set_var_ptr_element(self, symtab: GenTypes.SymbolTable | t.CPtr, name: str) -> int: - """标记变量为 ptr_element 类型,返回 0=成功 / 1=失败""" + """标记变量为 ptr-element(Spec.ElemBits=64),返回 0=成功 / 1=失败""" if symtab is None or name is None: return 1 entry: GenTypes.VarEntry | t.CPtr = self.lookup_var_entry(symtab, name) if entry is None: return 1 - entry.IsPtrElement = 1 + # ptr-element 变量的 alloca 是 Ptr(Ptr(Int8)),其值类型 Ptr(Int8) ⇒ Spec 必为 + # PtrLevel>=1。不满足即上游异常(类型缺失/退化),fail-fast 暴露 —— + # 若静默放过,读点 var_is_ptr_element 返回 0 → 下标按 1 字节误算(数据损坏)。 + if entry.Spec is None or entry.Spec.PtrLevel < 1: + self.Gen.TypeGenErr.fatal_error( + None, f"变量 '{name}' 标记为 ptr-element,但其 Spec 不可承载指针语义", + "InternalCompilerError") + return 1 + entry.Spec = TSpec.SpecWithElemBits(entry.Spec, 64) + return 0 + + # ============================================================ + # var_is_ptr_element - 变量是否「指针元素」语义(原 VarEntry.IsPtrElement) + # + # 判据(三元组 ElemBits 承载):PtrLevel>=1 且元素位宽为 8(LLVM 层退化后的 + # i8*)且 ElemBits==64(注解侧覆盖的 8 字节槽)。 + # 后两个条件缺一不可:真实 i64* 变量 ElemBits 也为 64,但其 Elem.Bits=64, + # 不会被误判;纯 str/bytes/char|t.CPtr 变量 ElemBits=8,同样不命中。 + # ============================================================ + def var_is_ptr_element(self, entry: GenTypes.VarEntry | t.CPtr) -> int: + """变量是否为 ptr-element 语义(1=是 / 0=否)""" + if entry is None or entry.Spec is None: + return 0 + sp: TSpec.TypeSpec | t.CPtr = entry.Spec + if sp.PtrLevel < 1 or sp.Elem is None: + return 0 + if sp.Elem.Bits != 8: + return 0 + if sp.ElemBits == 64: + return 1 return 0 # ============================================================