Files
ViperC/App/lib/core/Gen/Expr/ExprGenBase.vp
T

749 lines
48 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import lib.core.Gen.Generator as Generator
import t, c
from stdint import *
import ast
import mlirlite
import memhub
import string
import stdlib
import viperlib
import lib.core.Gen.Translator as HT
import lib.Projectrans.Utils as Utils
import lib.core.Gen.Type.TypeSpec as TSpec
import lib.core.Gen.GenApi as GenApi
import lib.core.Gen.GenTypes as GenTypes
# Pointer-to-pointer cast reason codes (coerce_to_type ptr -> ptr branch).
# PC_OK allowed; PC_DEPTH depth mismatch; PC_NONBASIC non-basic pointee; PC_LOSSY lossy.
define PC_OK = 0
define PC_DEPTH = 1
define PC_NONBASIC = 2
define PC_LOSSY = 3
# ExprGenBase - expression basics split out of ExprGenMixin (stage D):
# type checks / pointer level / coerce.
novirtual class ExprGenBase:
"""Expression basics: type checks / pointer levels / coercion."""
# --- docstring registry fields (__doc__ magic attribute support) ---
Gen: Generator.Generator*
DocKeys: bytes
DocValues: bytes
DocCount: int
# --- OOM diag: cross-module CDefine lookup counter (suspected CONST_INT loop) ---
DiagCDefineCount: int
# --- OOM diag: translate_name_value call counter (source of 14593 lookups) ---
DiagNameValueCount: int
# --- OOM diag: translate_value call counter (locates CONST_INT call loop) ---
DiagTranslateValueCount: int
# P3 migrated methods (bodies from HandlesExpr.vp module functions;
# self. for local calls, module prefix for external ones).
# Extract the function name from an AST node.
def get_func_name(self, func_node: ast.AST*) -> str:
"""Extract the function name from an AST node."""
if func_node is None: return None
k: int = func_node.kind()
if k == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(func_node)
return nm.id
elif k == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(func_node)
return at.attr
return None
# Escape an LLVM IR string literal.
def escape_llvm_string(self, pool: memhub.MemBuddy*, src: str) -> str:
"""Convert a string into an LLVM IR string literal."""
if src is None: return None
slen: sizet = string.strlen(src)
alloc_size: sizet = 4 * slen + 8
buf: str = pool.alloc(alloc_size)
if buf is None: return None
pos: sizet = 0
buf[pos] = 'c'
pos += 1
buf[pos] = '"'
pos += 1
for i in range(int(slen)):
ch: char = src[i]
if ch == '\0': break
if ch == '\"' or ch == '\\' or ch < 0x20:
buf[pos] = '\\'
pos += 1
hi: int = (ch >> 4) & 0xF
lo: int = ch & 0xF
buf[pos] = Utils.nibble_to_hex_char(hi, 1)
pos += 1
buf[pos] = Utils.nibble_to_hex_char(lo, 1)
pos += 1
else:
buf[pos] = ch
pos += 1
buf[pos] = '\\'
pos += 1
buf[pos] = '0'
pos += 1
buf[pos] = '0'
pos += 1
buf[pos] = '"'
pos += 1
buf[pos] = '\0'
return buf
# Get the bit width of an LLVM integer type.
# Delegates to TSpec.SpecTypeBits (single source; avoids diverging duplicates).
def get_llvm_type_bits(self, ty: mlirlite.LLVMType*) -> int:
"""Bit width of an LLVM integer type; 0 for non-integers (delegates to SpecTypeBits)."""
return TSpec.SpecTypeBits(ty)
# Get the bit width of an LLVM floating-point type.
def get_llvm_float_bits(self, ty: mlirlite.LLVMType*) -> int:
"""Bit width of an LLVM float type; 0 for non-floats."""
if ty is None: return 0
match ty:
case mlirlite.LLVMType.Float(bits):
return bits
case _:
return 0
# is_ptr_type - check whether a type is Ptr (1=yes, 0=no);
# a widely used type-check helper (50+ call sites).
def is_ptr_type(self, ty: mlirlite.LLVMType*) -> int:
"""Check whether ty is a Ptr type; returns 1=yes / 0=no."""
if ty is None: return 0
match ty:
case mlirlite.LLVMType.Ptr(pointee):
return 1
case _:
return 0
# get_ptr_level - compute pointer level (0=non-ptr, 1/2/3+=ptr depth);
# used by comparison checks to allow matching levels and reject mixed ones.
def get_ptr_level(self, ty: mlirlite.LLVMType*) -> int:
"""Compute pointer level (0=non-ptr, 1=ptr, 2=ptr-to-ptr); delegates to TSpec.SpecTypePtrLevel."""
return TSpec.SpecTypePtrLevel(ty)
# _const_fits_int_type - 编译期整型常量的值能否**无损**落进目标整型位宽。
# 有符号或无符号任一解释放得下即算「放得下」(位型不变);用于二元运算按常量适配另一侧位宽。
def _const_fits_int_type(self, val: mlirlite.Value*, target_ty: mlirlite.LLVMType*) -> int:
"""1 = the integer constant's value fits the target integer width losslessly; else 0."""
if val is None or target_ty is None or val.IsConst != 1: return 0
bits: int = self.get_llvm_type_bits(target_ty)
if bits <= 0 or bits > 63: return 0
v: int64 = val.IntVal
lo: int64 = -(int64(1) << (bits - 1))
hi: int64 = (int64(1) << (bits - 1)) - 1
if v >= lo and v <= hi: return 1
if v >= 0:
if bits == 63: return 1
if v <= (int64(1) << bits) - 1: return 1
return 0
# _int_type_of_bits - 合成指定位宽的 LLVM 整型(1/8/16/32/64 快路径,其余按 LLVMType.Int 合成)。
def _int_type_of_bits(self, pool: memhub.MemBuddy*, bits: int) -> mlirlite.LLVMType*:
"""LLVM integer type of the given bit width."""
if bits == 1: return mlirlite.Int1(pool)
if bits == 8: return mlirlite.Int8(pool)
if bits == 16: return mlirlite.Int16(pool)
if bits == 32: return mlirlite.Int32(pool)
if bits == 64: return mlirlite.Int64(pool)
ty: mlirlite.LLVMType* = pool.alloc(mlirlite.LLVMType.__sizeof__())
if ty is None: return None
string.memset(ty, 0, mlirlite.LLVMType.__sizeof__())
*ty = *mlirlite.LLVMType.Int(bits)
return ty
# apply_byte_order_bswap - byte-order bswap primitive (TPC parity: BaseGen._apply_bswap_if_big,
# extended to judge BOTH markers against the target's native order).
# Swapping is needed only when the declared encoding differs from the target's native order,
# so: BigEndian field on a little-endian target -> swap; LittleEndian field on a big-endian
# target -> swap; declared order == target order -> no swap. byte_order==0 (unspecified)
# means native and is always zero-cost: no IR is emitted at all. 16/32/64-bit integers only,
# matching TPC (i8/floats/pointers/aggregates are never swapped).
# Call name: bare "llvm.bswap" (no width suffix). build_call emits `call i32 @llvm.bswap(i32 %x)`;
# __function.vp:2607 detects a bare `@llvm.*` (non-`llvm.mlir.`) callee and renders it as
# `llvm.call_intrinsic "llvm.bswap"(...)`, and mlir-translate mangles the name from the operand
# type to `llvm.bswap.i32` (README.md:367-378, the same path used by CallGenVa.vp:123 `llvm.va_start`
# -> `llvm.va_start.p0`). Passing `llvm.bswap.i32` explicitly is unnecessary and would break the
# uniform mangle path, so the un-suffixed name is used.
def apply_byte_order_bswap(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, val: mlirlite.Value*, byte_order: int) -> mlirlite.Value*:
"""Return llvm.bswap(val) when val's declared byte_order differs from the target order; else val."""
if byte_order == 0: return val
# 1=BigEndian, 2=LittleEndian; compare against the target's native order.
want_big: int = 0
if byte_order == 1: want_big = 1
if want_big == self.Gen.TypeGenResolve.target_is_big_endian(): return val
if val is None or val.Ty is None: return val
bits: int = self.get_llvm_type_bits(val.Ty)
if bits != 16 and bits != 32 and bits != 64: return val
# Same-type in-and-out (iN -> iN); ret_ty=val.Ty keeps the call well-typed before mangling.
swapped: mlirlite.Value* = mlirlite.build_call(builder, "llvm.bswap", val, 1, val.Ty, 0)
if swapped is None: return val
return swapped
# _is_zero_int_literal - is the AST node the integer literal 0?
# Used for ptr != 0 / ptr == 0, where 0 means NULL and is allowed.
def _is_zero_int_literal(self, node: ast.AST*) -> int:
"""Is the node the integer literal 0; returns 1=yes / 0=no."""
if node is None: return 0
if node.kind() != ast.ASTKind.Constant: return 0
cn: ast.Constant* = ast.Constant*(node)
if cn is None or cn.const_kind != ast.CONST_INT: return 0
if cn.int_val == 0: return 1
return 0
# is_array_type - check whether a type is Array (1=yes, 0=no).
def is_array_type(self, ty: mlirlite.LLVMType*) -> int:
"""Check whether ty is an Array type; returns 1=yes / 0=no."""
if ty is None: return 0
match ty:
case mlirlite.LLVMType.Array(_, _):
return 1
case _:
return 0
# _get_custom_struct_cls_nm - class name if ty (or Ptr(ty)) is a registered struct.
# Used by subscript to decide __getitem__/__setitem__ forwarding vs raw GEP.
def _get_custom_struct_cls_nm(self, pool: memhub.MemBuddy*, ty: mlirlite.LLVMType*) -> str:
"""Class name if ty or its pointee is a registered struct; else None."""
if ty is None: return None
# Check whether ty itself is a registered struct.
cls_nm_direct: str = self.Gen.StructGen.get_class_name_by_type(pool, ty)
if cls_nm_direct is not None: return cls_nm_direct
# If ty is Ptr, check the pointee.
if self.is_ptr_type(ty) != 0:
pointee_cs: mlirlite.LLVMType* = ty.Pointee
if pointee_cs is not None: return self.Gen.StructGen.get_class_name_by_type(pool, pointee_cs)
return None
# _is_list_subscript_base - is the subscript base a list[T] variable?
# Wrong routing would treat list[T] as a struct array GEP and crash.
def _is_list_subscript_base(self, pool: memhub.MemBuddy*, trans: HT.Translator*, sub_node: ast.AST*) -> int:
"""Is the subscript base a list[T] variable; 1=yes / 0=no."""
if sub_node is None or trans is None: return 0
sub: ast.Subscript* = ast.Subscript*(sub_node)
if sub is None or sub.value is None or sub.value.kind() != ast.ASTKind.Name: return 0
nm: ast.Name* = ast.Name*(sub.value)
if nm.id is None: return 0
alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
if alloca is None or alloca.Ty is None: return 0
if self.is_ptr_type(alloca.Ty) == 0: return 0
pointee: mlirlite.LLVMType* = alloca.Ty.Pointee
if pointee is None: return 0
match pointee:
case mlirlite.LLVMType.Ptr(inner_ty):
lsn_ls: str = None
match inner_ty:
case mlirlite.LLVMType.Struct(_, _, lsn3):
lsn_ls = lsn3
case _:
pass
if lsn_ls is not None and string.strstr(lsn_ls, "list[") is not None: return 1
case _:
pass
return 0
# _sub_base_class_name - resolve the struct class name of a subscript base.
def _sub_base_class_name(self, pool: memhub.MemBuddy*, trans: HT.Translator*, sub_node: ast.AST*) -> str:
"""Resolve the struct class name of a subscript base; None if absent."""
if sub_node is None or trans is None: return None
sub: ast.Subscript* = ast.Subscript*(sub_node)
if sub is None or sub.value is None or sub.value.kind() != ast.ASTKind.Name: return None
nm: ast.Name* = ast.Name*(sub.value)
if nm.id is None: return None
alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
if alloca is None or alloca.Ty is None: return None
if self.is_ptr_type(alloca.Ty) == 0: return None
pointee: mlirlite.LLVMType* = alloca.Ty.Pointee
if pointee is None: return None
if self.is_ptr_type(pointee) != 0:
inner: mlirlite.LLVMType* = pointee.Pointee
if inner is not None: return self.Gen.StructGen.get_class_name_by_type(pool, inner)
return self.Gen.StructGen.get_class_name_by_type(pool, pointee)
# _sub_base_uses_getitem - should the subscript base use __getitem__ semantics?
# list[T] / classes defining __getitem__ yes; plain struct-ptr arrays use GEP.
def _sub_base_uses_getitem(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, trans: HT.Translator*, sub_node: ast.AST*) -> int:
"""Returns 1 if the subscript base uses __getitem__ semantics, else 0 (GEP address)."""
if self._is_list_subscript_base(pool, trans, sub_node) != 0: return 1
cls_ugi: str = self._sub_base_class_name(pool, trans, sub_node)
if cls_ugi is None: return 0
lookup_ugi: str = pool.alloc(128)
if lookup_ugi is None: return 0
viperlib.snprintf(lookup_ugi, 128, "%s.__getitem__", cls_ugi)
if self.Gen.CallGenBase.find_func_in_module(mod, lookup_ugi) is not None: return 1
return 0
# _deref_if_ptr_ptr - if obj_ptr is Ptr(Ptr(...)), load one level to deref.
# Supports attribute access on X* variables while leaving value vars alone.
def _deref_if_ptr_ptr(self, builder: mlirlite.IRBuilder*, obj_ptr: mlirlite.Value*) -> mlirlite.Value*:
"""If obj_ptr is Ptr(Ptr(...)), load once to obtain the inner pointer."""
if obj_ptr is None or obj_ptr.Ty is None: return obj_ptr
if self.is_ptr_type(obj_ptr.Ty) == 0: return obj_ptr
pointee: mlirlite.LLVMType* = obj_ptr.Ty.Pointee
if pointee is None: return obj_ptr
if self.is_ptr_type(pointee) == 0: return obj_ptr
# obj_ptr is Ptr(Ptr(...)); load to dereference.
loaded: mlirlite.Value* = mlirlite.build_load(builder, pointee, obj_ptr)
if loaded is None: return obj_ptr
return loaded
# _value_node_of - drill an outer statement node down to its value sub-expression.
# Fixes error locations pointing at the Assign node instead of the real value.
def _value_node_of(self, node: ast.AST*) -> ast.AST*:
"""Drill a statement node down to its value sub-expression; returns node if none."""
if node is None: return node
k: int = node.kind()
v: ast.AST* = None
if k == ast.ASTKind.Assign: v = (ast.Assign*(node)).value
elif k == ast.ASTKind.AnnAssign: v = (ast.AnnAssign*(node)).value
elif k == ast.ASTKind.AugAssign: v = (ast.AugAssign*(node)).value
elif k == ast.ASTKind.Expr: v = (ast.Expr*(node)).value
elif k == ast.ASTKind.Return: v = (ast.Return*(node)).value
if v is None: return node
return v
# IsNoneValue - is val/node the None (empty) constant? 1=yes / 0=no.
# None carries no type info, so it may be implicitly converted to any target.
def IsNoneValue(self, val: mlirlite.Value*, node: ast.AST* = None) -> int:
"""Is val/node the None constant; returns 1=yes / 0=no."""
# Criterion 1: the AST node is a None literal.
if node is not None and node.kind() == ast.ASTKind.Constant:
cn_nv: ast.Constant* = ast.Constant*(node)
if cn_nv is not None and cn_nv.const_kind == ast.CONST_NONE: return 1
# Criterion 2: an already-built null constant (ptr type + compile-time zero).
if val is not None and val.IsConst == 1 and val.IntVal == 0 and self.is_ptr_type(val.Ty) != 0: return 1
return 0
# _NoneValueAsTarget - materialize None as the target type's zero/empty value.
# Used only when coerce_to_type has detected the source is None.
def _NoneValueAsTarget(self, builder: mlirlite.IRBuilder*, target_ty: mlirlite.LLVMType*) -> mlirlite.Value*:
"""Materialize None as the target type's zero/empty value."""
if target_ty is None: return None
pool: memhub.MemBuddy* = builder.Pool
t_bits: int = self.get_llvm_type_bits(target_ty)
if t_bits != 0: return mlirlite.ConstInt(pool, target_ty, 0, "0")
t_fbits: int = self.get_llvm_float_bits(target_ty)
if t_fbits != 0: return mlirlite.ConstFloat(pool, target_ty, 0.0)
return mlirlite.ConstZero(pool, target_ty)
# _is_universal_ptr_ty - is ty a universal pointer (t.CPtr / i8* = void*)?
# Rule 1: universal pointers are wildcards, freely interconvertible with any T*.
def _is_universal_ptr_ty(self, ty: mlirlite.LLVMType*) -> int:
"""Is ty a universal pointer (i8* / t.CPtr); 1=yes / 0=no."""
if ty is None or self.is_ptr_type(ty) == 0: return 0
if self.get_ptr_level(ty) != 1: return 0
pointee: mlirlite.LLVMType* = ty.Pointee
if pointee is None: return 1
if self.get_llvm_type_bits(pointee) == 8: return 1
return 0
# "str / bytes are not universal pointers" - rule and unified fail-fast.
# They share i8* in IR but are not wildcards, so the annotation source decides.
def _annot_name_is_bare_str(self, txt: str) -> int:
"""Is the annotation text a bare str / bytes; 1=yes / 0=no."""
if txt is None or txt[0] == 0: return 0
if string.strcmp(txt, "str") == 0 or string.strcmp(txt, "bytes") == 0: return 1
return 0
def _annot_is_bare_str(self, annot: ast.AST*) -> int:
"""Is the annotation AST a bare str / bytes Name; 1=yes / 0=no."""
if annot is None or annot.kind() != ast.ASTKind.Name: return 0
nm: ast.Name* = ast.Name*(annot)
if nm is None or nm.id is None: return 0
return self._annot_name_is_bare_str(nm.id)
def _str_rejects_ptr_kind(self, val: mlirlite.Value*) -> int:
"""Is val an aggregate pointer that str/bytes must reject (1/2/3=yes, 0=no); char arrays exempt."""
if val is None or val.Ty is None: return 0
if self.is_ptr_type(val.Ty) == 0: return 0
pointee: mlirlite.LLVMType* = val.Ty.Pointee
if pointee is None: return 0
pk: int = pointee.kind()
if pk == mlirlite.LLVMType.Struct: return 1
if pk == mlirlite.LLVMType.Func: return 3
if pk == mlirlite.LLVMType.Array:
match pointee:
case mlirlite.LLVMType.Array(elem, _):
if self.get_llvm_type_bits(elem) == 8: return 0
case _:
pass
return 2
return 0
def fail_if_str_annot_agg_ptr(self, val: mlirlite.Value*, annot_is_str: int, node: ast.AST* = None) -> int:
"""Fail fast (return 1) when a bare str/bytes annotation holds an aggregate pointer."""
if annot_is_str == 0 or val is None or val.Ty is None: return 0
if self._str_rejects_ptr_kind(val) == 0: return 0
self.Gen.TypeGenErr.fatal_error(self._value_node_of(node), "str/bytes does not accept a pointer to a non-basic type (struct/class/non-char array/function): both are" "i8* at the IR level, but with different semantics (str/bytes is not a general pointer wildcard). If a pointer must be passed, " "use a t.CPtr/rawptr parameter instead, or cast explicitly (e.g. t.CPtr(p))", "CastError")
return 1
def fail_if_str_annot_ast_agg_ptr(self, val: mlirlite.Value*, annot: ast.AST*, node: ast.AST* = None) -> int:
"""AST-annotation entry: fail fast when a bare str/bytes annotation holds an aggregate pointer."""
return self.fail_if_str_annot_agg_ptr(val, self._annot_is_bare_str(annot), node)
# _is_basic_scalar_ty - is ty a basic scalar (int/float)? 1=yes / 0=no.
# Non-basic types (struct/array/func) may not take part in implicit ptr casts.
def _is_basic_scalar_ty(self, ty: mlirlite.LLVMType*) -> int:
"""Is ty a basic scalar (int/float); 1=yes / 0=no."""
if ty is None: return 0
if self.get_llvm_type_bits(ty) != 0: return 1
if self.get_llvm_float_bits(ty) != 0: return 1
return 0
# _scalar_convert_reason - is the basic scalar conversion T -> U lossless?
# Returns PC_OK (lossless/allowed) or PC_LOSSY (lossy/forbidden).
def _scalar_convert_reason(self, src_pt: mlirlite.LLVMType*, dst_pt: mlirlite.LLVMType*) -> int:
"""Implicit basic-scalar conversion code src_pt -> dst_pt (PC_OK / PC_LOSSY)."""
s_int: int = self.get_llvm_type_bits(src_pt)
d_int: int = self.get_llvm_type_bits(dst_pt)
s_flt: int = self.get_llvm_float_bits(src_pt)
d_flt: int = self.get_llvm_float_bits(dst_pt)
# int <-> int: widening is lossless; narrowing (high->low) is lossy -> forbidden.
if s_int != 0 and d_int != 0:
if d_int >= s_int: return PC_OK
return PC_LOSSY
# float <-> float: allowed.
if s_flt != 0 and d_flt != 0: return PC_OK
# int -> float: lossless only when float width >= int width.
if s_int != 0 and d_flt != 0:
if d_flt >= s_int: return PC_OK
return PC_LOSSY
# float -> int: lossy, forbidden.
if s_flt != 0 and d_int != 0: return PC_LOSSY
return PC_OK
# _class_name_of_ptr_pointee - map a pointer's pointee type to a registered class name.
# Reuses StructGen.get_class_name_by_type; returns None for non-struct/unregistered.
def _class_name_of_ptr_pointee(self, pool: memhub.MemBuddy*, pt: mlirlite.LLVMType*) -> str:
"""Map a pointer's pointee type to a registered class name; None on failure."""
if pt is None: return None
return self.Gen.StructGen.get_class_name_by_type(pool, pt)
# _is_inheritance_upcast - Derived* -> Base* (incl. multi-level) exemption of rule 2.
# Allowed when the target class appears after the source in its MRO chain.
def _is_inheritance_upcast(self, pool: memhub.MemBuddy*, src_pt: mlirlite.LLVMType*, dst_pt: mlirlite.LLVMType*) -> int:
"""Derived* -> Base* upcast (incl. multi-level); 1=allowed / 0=no."""
src_name: str = self._class_name_of_ptr_pointee(pool, src_pt)
if src_name is None: return 0
dst_name: str = self._class_name_of_ptr_pointee(pool, dst_pt)
if dst_name is None: return 0
if string.strcmp(src_name, dst_name) == 0: return 0
chain: sizet* = self.Gen.StructGen.get_mro_chain(pool, src_name)
if chain is None: return 0
# chain[0] = self; ancestors follow, terminated by 0. Only look after self.
i: int = 1
while chain[i] != 0:
anc: str = rawptr(chain[i])
if anc is not None and string.strcmp(anc, dst_name) == 0: return 1
i += 1
return 0
# _is_array_decay - [N]T* -> T* one-way array decay exemption of rule 2.
# Matches when the source pointee is a fixed array and the target is its element.
def _is_array_decay(self, pool: memhub.MemBuddy*, src_pt: mlirlite.LLVMType*, dst_pt: mlirlite.LLVMType*) -> int:
"""Source pointee is a fixed array and target is its element type; 1=yes / 0=no."""
if src_pt is None or dst_pt is None: return 0
if self.is_array_type(src_pt) == 0: return 0
elem: mlirlite.LLVMType* = None
match src_pt:
case mlirlite.LLVMType.Array(et, _):
elem = et
case _:
return 0
if elem is None: return 0
return self.Gen.AssignGen._stg_ty_equal(pool, elem, dst_pt)
# _pointee_convert_reason - reason code for pointee T -> U (the T->U of T* -> U*).
# Applies rule 2 with the inheritance-upcast and array-decay exemptions.
def _pointee_convert_reason(self, pool: memhub.MemBuddy*, src_pt: mlirlite.LLVMType*, dst_pt: mlirlite.LLVMType*) -> int:
"""Conversion code for pointee src_pt -> dst_pt (PC_OK/PC_NONBASIC/PC_LOSSY/PC_DEPTH)."""
if src_pt is None or dst_pt is None: return PC_OK
s_is_ptr: int = self.is_ptr_type(src_pt)
d_is_ptr: int = self.is_ptr_type(dst_pt)
if s_is_ptr != 0 or d_is_ptr != 0:
# One side ptr, other not (e.g. Foo vs Foo*): non-basic swap, forbidden.
if s_is_ptr == 0 or d_is_ptr == 0: return PC_NONBASIC
# Both sides are pointers: recurse.
return self._ptr_cast_reason(pool, src_pt, dst_pt)
# One side is non-basic (struct/class/union/array/func): generally forbidden.
if self._is_basic_scalar_ty(src_pt) == 0 or self._is_basic_scalar_ty(dst_pt) == 0:
# Exemption 1: Derived* -> Base* inheritance upcast (incl. multi-level).
if self._is_inheritance_upcast(pool, src_pt, dst_pt) != 0: return PC_OK
# Exemption 2: one-way array decay [N]T* -> T* (source pointee is a
# fixed array, target pointee is its element type).
if self._is_array_decay(pool, src_pt, dst_pt) != 0: return PC_OK
return PC_NONBASIC
return self._scalar_convert_reason(src_pt, dst_pt)
# _ptr_cast_reason - reason code for implicit ptr -> ptr casts (single source of truth).
# Rules 1-4: wildcard ptr, lossless pointee, non-basic forbidden, identity allowed.
def _ptr_cast_reason(self, pool: memhub.MemBuddy*, src_ty: mlirlite.LLVMType*, dst_ty: mlirlite.LLVMType*) -> int:
"""Reason code for implicit ptr -> ptr casts (PC_OK means allowed)."""
if src_ty is None or dst_ty is None: return PC_OK
slev: int = self.get_ptr_level(src_ty)
dlev: int = self.get_ptr_level(dst_ty)
if slev == 0 or dlev == 0: return PC_OK
# 豁免 0(必须放在级数检查**之前**):固定数组退让 `[N]T* -> T*`(C 语义:数组名退让成
# 首元素指针)。元素是 i8 时级数不变(1→1,恰好由"全类型指针通配"放行),但元素本身
# 是指针时级数会 +1(`[N]str` -> `str*` 即 `Ptr([N]i8*) -> Ptr(i8*)`:1→2),
# 级数检查会先判成 PC_DEPTH,使下面 _pointee_convert_reason 里那条既有豁免永远够不到
# (死代码)。现场:includes/tomllib `_parse_key_path(s, segs: [N]str, cap)` —— 形参按
# C 语义退让成 `str*`(FuncGenDef 的 Array 形参退让),实参仍是 `[N]str` 的数组地址。
if self._is_array_decay(pool, src_ty.Pointee, dst_ty.Pointee) != 0: return PC_OK
# Different depth: forbidden regardless of pointee.
if slev != dlev: return PC_DEPTH
# Rule 4: identical types (same depth and pointee) are not a conversion.
if self.Gen.AssignGen._stg_ty_equal(pool, src_ty, dst_ty) != 0: return PC_OK
# Rule 1: universal pointers are wildcards (i8* <-> any T*).
if self._is_universal_ptr_ty(src_ty) != 0 or self._is_universal_ptr_ty(dst_ty) != 0: return PC_OK
# Rules 2/3: compare pointee level by level.
return self._pointee_convert_reason(pool, src_ty.Pointee, dst_ty.Pointee)
# _type_text - readable LLVM type text via TypePrint ("?" on failure).
def _type_text(self, pool: memhub.MemBuddy*, ty: mlirlite.LLVMType*) -> str:
"""Readable LLVM type text via TypePrint; "?" on failure."""
if ty is None: return "?"
buf: str = pool.alloc_tagged(4096, memhub.DETSITE_EXPRGENBASE_415)
if buf is None: return "?"
mlirlite.TypePrint(buf, 4096, ty, pool)
if buf[0] == 0: return "?"
return buf
# _ptr_cast_reason_text - human-readable text for a forbidden ptr -> ptr cast.
# Names the level and why, plus source/target types; result is heap-copied.
def _ptr_cast_reason_text(self, code: int, pool: memhub.MemBuddy*, src_ty: mlirlite.LLVMType*, dst_ty: mlirlite.LLVMType*) -> str:
"""Human-readable reason for a forbidden ptr -> ptr cast (includes source/target types)."""
why: str = "unknown reason"
if code == PC_DEPTH: why = f"pointer level differs (source level {self.get_ptr_level(src_ty)} -> target level {self.get_ptr_level(dst_ty)})"
elif code == PC_NONBASIC: why = "the pointee type is a non-basic type (struct/class/union/array etc.): implicit conversion is never allowed between a non-basic type and its pointer, or between pointers of different non-basic types"
elif code == PC_LOSSY: why = "conversion between the pointee types is lossy (int -> float with insufficient width / float -> int)"
s_txt: str = self._type_text(pool, src_ty)
d_txt: str = self._type_text(pool, dst_ty)
return GenApi.escape_fstring_copy(f"implicit pointer-to-pointer conversion is forbidden: {why}; source type {s_txt} -> target type {d_txt}")
# _ctor_call_agg_ty - aggregate type if node is a class/struct constructor call.
# The only exception to rule 3 ("construction materialization"); else None.
def _ctor_call_agg_ty(self, node: ast.AST*) -> mlirlite.LLVMType*:
"""Aggregate type if node (or its value child) is a constructor call; else None."""
n: ast.AST* = self._value_node_of(node)
if n is None or n.kind() != ast.ASTKind.Call: return None
cl: ast.Call* = ast.Call*(n)
if cl.func is None: return None
callee: str = None
if cl.func.kind() == ast.ASTKind.Name:
nm: ast.Name* = ast.Name*(cl.func)
callee = nm.id
elif cl.func.kind() == ast.ASTKind.Attribute:
at: ast.Attribute* = ast.Attribute*(cl.func)
callee = at.attr
if callee is None: return None
ent: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(callee)
if ent is not None and ent.Ty is not None: return ent.Ty
# REnum variant ctor `EnumType.Variant(...)`: the callee is not a struct
# name, but materializes the base enum, so return that aggregate type.
if cl.func.kind() == ast.ASTKind.Attribute:
# Take the segment left of the variant name as the enum class name
# (the qualifier, not the chain root which caused earlier misses).
qual: ast.AST* = (ast.Attribute*(cl.func)).value
base_nm: str = None
if qual is not None:
if qual.kind() == ast.ASTKind.Attribute: base_nm = (ast.Attribute*(qual)).attr
elif qual.kind() == ast.ASTKind.Name: base_nm = (ast.Name*(qual)).id
if base_nm is not None and self.Gen.ClassGenEnum._is_renum_class_name(base_nm) != 0:
rn_ent: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(base_nm)
if rn_ent is not None and rn_ent.Ty is not None: return rn_ent.Ty
return None
# _same_agg_ty - are two aggregate types the same (type match for ctor exemption)?
def _same_agg_ty(self, pool: memhub.MemBuddy*, a: mlirlite.LLVMType*, b: mlirlite.LLVMType*) -> int:
"""Are a and b the same aggregate type; 1=yes / 0=no."""
if a is None or b is None: return 0
return self.Gen.AssignGen._stg_ty_equal(pool, a, b)
# _agg_value_slot_ty - 值语义槽:源表达式的变量槽**自身**持有聚合物(`x: Foo`,Spec.PtrLevel == 0)。
# 真·指针值(`p: Foo*`,以及**裸类形参** `node: T` —— 其 ABI 即 T*,见 ClassGenVTable "objects are
# pointers")的 Spec.PtrLevel >= 1,返回 None,仍必须显式解引用(c.deref(p) / *p)。
# 返回该聚合类型;不适用时 None。
def _agg_value_slot_ty(self, node: ast.AST*, val: mlirlite.Value*, trans: HT.Translator*) -> mlirlite.LLVMType*:
"""Aggregate type when the source is a variable slot holding the aggregate by value; else None."""
n: ast.AST* = self._value_node_of(node)
if n is None or n.kind() != ast.ASTKind.Name: return None
nm: str = (ast.Name*(n)).id
if nm is None or trans is None: return None
ve: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm)
if ve is None or ve.Alloca is None or ve.Spec is None: return None
# 值语义槽:按名解析到该变量的值类型;此处仅做类型级判定(不依赖槽身份,避免别名/重复登记漏判)。
# PtrLevel>=1(含裸类形参 `node: T` 的 ABI 指针):真指针,必须显式解引用。
if ve.Spec.PtrLevel != 0: return None
if self.is_ptr_type(ve.Alloca.Ty) == 0: return None
return ve.Alloca.Ty.Pointee
# Type coercion: int sext/trunc, float fpext/fptrunc, int<->float si2fp/fp2si.
def coerce_to_type(self, builder: mlirlite.IRBuilder*, val: mlirlite.Value*, target_ty: mlirlite.LLVMType*, node: ast.AST* = None, trans: HT.Translator* = None) -> mlirlite.Value*:
"""Coerce val to target_ty: int sext/trunc, float fpext/fptrunc, int/float conversions."""
if val is None or target_ty is None: return val
# [FIX 2026-09-28] None may convert to any type: it carries no type info,
# so all "real value" implicit-cast bans short-circuit for it (IsNoneValue).
if self.IsNoneValue(val, node) != 0:
if self.is_ptr_type(target_ty) != 0: return mlirlite.build_bitcast(builder, val, target_ty)
return self._NoneValueAsTarget(builder, target_ty)
# A void-typed value may not be used as a value: coerce to a non-void target
# fails fast instead of emitting illegal IR (load void / bitcast void).
if self.Gen.StmtGenDecl._is_void_type(val.Ty) != 0:
if self.Gen.StmtGenDecl._is_void_type(target_ty) == 0:
self.Gen.TypeGenErr.fatal_error(self._value_node_of(node), "a void value cannot take part in any operation (void is only allowed for void* and typedef aliases)", "TypeError")
return val
# 聚合目标(结构体/数组)不接受标量(整数/浮点)初始化或赋值:标量→聚合在
# VPC 无转换语义;旧行为把标量原样交给 build_store,而 build_store 不做类型
# 校验,落成 `store i32 0, ptr %x`——只写聚合首元素字节并抬高对齐,其余部分
# 保持未初始化(历史现场:`l: [10]char = 0` → 打印 "Hello"+乱码)。
# 聚合零初始化必须写 `= None`(上方 IsNoneValue → _NoneValueAsTarget 分支)。
tg_kind_agg: int = target_ty.kind()
if (tg_kind_agg == mlirlite.LLVMType.Struct or tg_kind_agg == mlirlite.LLVMType.Array) and self.is_ptr_type(val.Ty) == 0 and (self.get_llvm_type_bits(val.Ty) != 0 or self.get_llvm_float_bits(val.Ty) != 0):
self.Gen.TypeGenErr.fatal_error(self._value_node_of(node), "a scalar (integer/float) cannot initialize or assign to an aggregate (struct/array): only the first element's bytes would be written (and the alignment raised), leaving the rest of the aggregate uninitialized; for zero-initialization write `= None`, for contents use a list literal `[...]` or assign element by element", "TypeError")
return val
# func -> ptr: bitcast (function Values have Func type, handled before
# the integer/float conversions).
match val.Ty:
case mlirlite.LLVMType.Func(_, _, _):
if self.is_ptr_type(target_ty) != 0: return mlirlite.build_bitcast(builder, val, target_ty)
return val
case _:
pass
# Integer conversion.
val_bits: int = self.get_llvm_type_bits(val.Ty)
target_bits: int = self.get_llvm_type_bits(target_ty)
# int -> ptr: implicit conversion forbidden (FIX 2026-08-30); only the C
# null pointer constant 0 is exempt. Non-constant ints need an explicit cast.
if val_bits != 0 and self.is_ptr_type(target_ty) != 0:
# [FIX 2026-08-31] C null pointer constant exemption: compile-time 0
# assigned to a pointer becomes a null pointer (widen to i64, then inttoptr).
if val.IsConst == 1 and val.IntVal == 0: return mlirlite.build_inttoptr(builder, mlirlite.const_int64(builder.Pool, 0), target_ty)
m_i2p: str = "implicit integer-to-pointer conversion is forbidden: an explicit cast to pointer (e.g. t.CPtr(x)) is required to assign an integer to a pointer type"
self.Gen.TypeGenErr.fatal_error(self._value_node_of(node), m_i2p, "CastError")
return val
# Pointer -> integer: rejected (symmetric with the int -> ptr rule above).
# An explicit cast is required; the old code silently did ptrtoint.
if self.is_ptr_type(val.Ty) != 0 and target_bits != 0 and self.is_ptr_type(target_ty) == 0:
m_p2i: str = "implicit pointer-to-integer conversion is forbidden: an explicit cast (e.g. uint64(p) / int64(p) / int(p)) is required to assign a pointer to an integer type"
self.Gen.TypeGenErr.fatal_error(self._value_node_of(node), m_p2i, "CastError")
return val
if val_bits != 0 and target_bits != 0:
if val_bits == target_bits: return val
# Target i1 (bool / t.CBool): truth normalization `x != 0 -> 1/0`
# (C 6.3.1.2), not trunc; keeps parity with `bool(x)` and condition paths.
if target_bits == 1:
zero_i1: mlirlite.Value* = mlirlite.const_int(builder.Pool, val_bits, 0)
return mlirlite.build_icmp(builder, mlirlite.ICMP_NE, val, zero_i1)
if val_bits < target_bits:
# Unified zext/sext: i1/i8 default zext, unsigned AST nodes zext,
# otherwise sext. trans is required so unsigned annotations resolve.
if self._zext_or_sext_val(builder.Pool, val, node, trans) != 0: return mlirlite.build_zext(builder, val, target_ty)
return mlirlite.build_sext(builder, val, target_ty)
# 隐式窄化**禁止**(规则 2026-10-10 收紧,取代"整数↔整数 所有方向都允许"):
# 位宽高→低会丢高位,必须显式 cast 或加宽目标类型。仅两类豁免:
# ① 目标 i1:真值归一化(x != 0),语义明确,不是位截断(已在上方返回);
# ② 源是**编译期常量且值能落进目标位宽**(如字面量 5 → i32),无信息损失。
if val.IsConst == 1:
iv: int64 = val.IntVal
lo_c: int64 = -(int64(1) << (target_bits - 1))
hi_c: int64 = (int64(1) << target_bits) - 1
if iv < lo_c or iv > hi_c:
self.Gen.TypeGenErr.fatal_error(node, f"narrowing truncation overflow: constant {iv} is outside the {target_bits}-bit representable range [{lo_c}, {hi_c}]; use an explicit cast or widen the target type", "OverflowError")
return mlirlite.build_trunc(builder, val, target_ty)
self.Gen.TypeGenErr.fatal_error(node, f"implicit narrowing is forbidden: a {val_bits}-bit value cannot be implicitly assigned to a {target_bits}-bit type (the high bits would be lost); use an explicit cast (e.g. int32(x) / t.CInt32T(x)) or widen the target type", "CastError")
return val
# Float conversion.
val_fbits: int = self.get_llvm_float_bits(val.Ty)
target_fbits: int = self.get_llvm_float_bits(target_ty)
# float <-> ptr: forbidden (fast-fail to avoid the bogus build_load path).
if (val_fbits != 0 and self.is_ptr_type(target_ty) != 0) or (self.is_ptr_type(val.Ty) != 0 and target_fbits != 0):
self.Gen.TypeGenErr.fatal_error(self._value_node_of(node), "unsupported type conversion: float <-> ptr", "CastError")
return val
if val_fbits != 0 and target_fbits != 0:
if val_fbits == target_fbits: return val
if val_fbits < target_fbits: return mlirlite.build_fpext(builder, val, target_ty)
return mlirlite.build_fptrunc(builder, val, target_ty)
# int -> float
if val_bits != 0 and target_fbits != 0: return mlirlite.build_si2fp(builder, val, target_ty)
# float -> int
if val_fbits != 0 and target_bits != 0: return mlirlite.build_fp2si(builder, val, target_ty)
# Aggregate value -> pointer: spill a temp to the entry block and pass its
# address (VPC never passes aggregates by value).
if self.is_ptr_type(val.Ty) == 0 and self.is_ptr_type(target_ty) != 0:
vk_agg: int = val.Ty.kind()
if vk_agg == mlirlite.LLVMType.Struct or vk_agg == mlirlite.LLVMType.Array:
# Rule 3: implicit value -> ptr for non-basic types (Foo -> Foo*) is
# forbidden, except "construction materialization" (see _ctor_call_agg_ty).
ctor_v2p: mlirlite.LLVMType* = self._ctor_call_agg_ty(node)
if self._same_agg_ty(builder.Pool, ctor_v2p, val.Ty) == 0:
m_v2p: str = "implicit value/pointer conversion for non-basic types is forbidden (except construction materialization): a non-basic value cannot be implicitly converted to a pointer (Foo -> Foo*); to take its address use c.addr(...) / &v explicitly, or cast explicitly"
v2p_txt: str = GenApi.escape_fstring_copy(f"; source value type {self._type_text(builder.Pool, val.Ty)} -> target type {self._type_text(builder.Pool, target_ty)}")
self.Gen.TypeGenErr.fatal_error(self._value_node_of(node), f"{m_v2p}{v2p_txt}", "CastError")
return val
tmp_agg: mlirlite.Value* = self.Gen.VarGen._alloca_at_entry(builder, val.Ty)
if tmp_agg is not None:
mlirlite.build_store(builder, val, tmp_agg)
match target_ty:
case mlirlite.LLVMType.Ptr(pointee_agg):
if self.Gen.AssignGen._stg_ty_equal(builder.Pool, pointee_agg, val.Ty) != 0: return tmp_agg
case _:
pass
bc_agg: mlirlite.Value* = mlirlite.build_bitcast(builder, tmp_agg, target_ty)
if bc_agg is not None: return bc_agg
return tmp_agg
# Pointer -> non-pointer value(聚合目标)。两类隐式 load 被保留:
# ① **指向同型聚合的指针 → 同型聚合值**:值拷贝语义。裸类形参(`elem: T`)的 ABI 就是
# `T*`(对象即指针),把它赋给同型值字段/变量时按值拷贝;`x: T` 这类值语义槽指针同理。
# 拷贝后目标独立、不会悬垂 —— 与「传参用指针」的 ABI 选择互不冲突。
# ② 构造物化(见 _ctor_call_agg_ty)。
# 其余(不同型聚合 / 多级指针)仍 fail-fast,须显式 *p / c.deref(p) 或显式 cast。
if self.is_ptr_type(val.Ty) != 0 and self.is_ptr_type(target_ty) == 0:
target_kind: int = target_ty.kind()
if target_kind == mlirlite.LLVMType.Struct or target_kind == mlirlite.LLVMType.Array:
# ① 同型指针 → 值拷贝(覆盖裸类形参的 `T*` ABI)
if val.Ty.Pointee is not None and self._same_agg_ty(builder.Pool, val.Ty.Pointee, target_ty) != 0:
return mlirlite.build_load(builder, target_ty, val)
ctor_p2v: mlirlite.LLVMType* = self._ctor_call_agg_ty(node)
# ② 值语义槽(`x: Foo`,槽自身持有聚合物):读槽 = 值拷贝
slot_p2v: mlirlite.LLVMType* = self._agg_value_slot_ty(node, val, trans)
if self._same_agg_ty(builder.Pool, slot_p2v, target_ty) != 0:
return mlirlite.build_load(builder, target_ty, val)
if self._same_agg_ty(builder.Pool, ctor_p2v, target_ty) == 0:
m_p2v: str = "implicit value/pointer conversion for non-basic types is forbidden (except construction materialization): a pointer type cannot be implicitly converted to a non-basic value (Foo* -> Foo); to dereference use c.deref(p) / *p explicitly, or cast explicitly"
p2v_txt: str = GenApi.escape_fstring_copy(f"; source type {self._type_text(builder.Pool, val.Ty)} -> target value type {self._type_text(builder.Pool, target_ty)}")
self.Gen.TypeGenErr.fatal_error(self._value_node_of(node), f"{m_p2v}{p2v_txt}", "CastError")
return val
return mlirlite.build_load(builder, target_ty, val)
self.Gen.TypeGenErr.fatal_error(self._value_node_of(node), f"implicit pointer-to-non-pointer-value conversion is forbidden: an explicit dereference (*p / *p) or explicit cast (uint64(p)) is required", "CastError")
return val
# ptr -> ptr: implicit conversion must satisfy the new rules (see
# _ptr_cast_reason); violators fail fast with a CastError.
if self.is_ptr_type(val.Ty) != 0 and self.is_ptr_type(target_ty) != 0:
pc: int = self._ptr_cast_reason(builder.Pool, val.Ty, target_ty)
if pc != PC_OK:
reason: str = self._ptr_cast_reason_text(pc, builder.Pool, val.Ty, target_ty)
self.Gen.TypeGenErr.fatal_error(self._value_node_of(node), reason, "CastError")
return val
return mlirlite.build_bitcast(builder, val, target_ty)
return val
def _zext_or_sext_val(self, pool: memhub.MemBuddy*, val: mlirlite.Value*, node: ast.AST* = None, trans: HT.Translator* = None) -> int:
"""Unified integer widening direction: 1=zext (unsigned), 0=sext; delegates to ExprGenOps.should_zext_widen."""
if val is None or val.Ty is None: return 0
return self.Gen.ExprGenOps.should_zext_widen(pool, val, None, node, trans)
# translate_ptr_arith: ptr +/- int via getelementptr (index scaled by pointee
# size); array pointees ([N x ty]*) decay to element pointers first.
def translate_ptr_arith(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, ptr_val: mlirlite.Value*, int_val: mlirlite.Value*, op: int) -> mlirlite.Value*:
"""Pointer arithmetic: ptr +/- int via GEP with the index scaled by element size."""
if ptr_val is None or int_val is None or ptr_val.Ty is None: return None
# Extract the pointee type (non-pointer returns None).
elem_ty: mlirlite.LLVMType* = None
match ptr_val.Ty:
case mlirlite.LLVMType.Ptr(pointee):
elem_ty = pointee
case _:
return None
if elem_ty is None: return None
# Normalize the index to i64.
i64_ty: mlirlite.LLVMType* = mlirlite.Int64(pool)
idx: mlirlite.Value* = self.coerce_to_type(builder, int_val, i64_ty, None)
if idx is None: return None
# ptr - int -> negative index (LLVM GEP indices are additive).
if op == ast.OpKind.Sub:
neg_idx: mlirlite.Value* = mlirlite.build_sub(builder, mlirlite.const_int64(pool, 0), idx)
if neg_idx is None: return None
idx = neg_idx
# Array pointee: decay to an element pointer first (GEP 0,0).
if self.is_array_type(elem_ty) != 0:
inner_ty: mlirlite.LLVMType* = None
match elem_ty:
case mlirlite.LLVMType.Array(inner, _):
inner_ty = inner
case _:
return None
if inner_ty is None: return None
decayed: mlirlite.Value* = mlirlite.build_gep_array(builder, elem_ty, inner_ty, ptr_val, mlirlite.const_int64(pool, 0))
if decayed is None: return None
return mlirlite.build_gep(builder, inner_ty, decayed, idx)
return mlirlite.build_gep(builder, elem_ty, ptr_val, idx)
def InitExprGen(self) -> int:
"""Initialize ExprGen family fields (called by Generator at construction)."""
if self is None: return 1
self.DocKeys = None
self.DocValues = None
self.DocCount = 0
self.DiagCDefineCount = 0
self.DiagNameValueCount = 0
self.DiagTranslateValueCount = 0
return 0