- GenApi.fail_if_stack_addr_escape:禁止把本帧栈地址写出帧外(显式 &local,以及局部数组名退让到指针目标,如 return arr with '-> char*');裸聚合名 + 同型聚合目标是值拷贝,不在其列;err_type 记为 LifetimeError。 - StmtGenCifMixin.translate_return:按值返回聚合时,对被指类型与返回聚合类型同一的指针补 load(与多返回值 struct 的 ret 同形),修复 return 局部数组报 CastError。 - ExprGenBase.coerce_to_type:聚合目标不接受标量(旧行为落成 store i32 0, ptr,只写首元素字节并抬高对齐),改为编译期 fail-fast。
654 lines
40 KiB
Plaintext
654 lines
40 KiB
Plaintext
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(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)
|
|
# _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: narrowing and widening are both allowed.
|
|
if s_int != 0 and d_int != 0: return PC_OK
|
|
# 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
|
|
# 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)
|
|
# 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
|
|
# 聚合目标(结构体/数组)不接受标量(整数/浮点)初始化或赋值:标量→聚合在
|
|
# TPV 无转换语义;旧行为把标量原样交给 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)
|
|
# Narrowing diagnostic (_Type.md issue 8): a known constant exceeding the
|
|
# target unsigned range fails fast; negative constants wrap (mod 2^bits).
|
|
if val.IsConst == 1 and val.IntVal > 0:
|
|
target_max_u: uint64 = (uint64(1) << target_bits) - 1
|
|
if uint64(val.IntVal) > target_max_u:
|
|
max_print: int64 = int64(target_max_u)
|
|
self.Gen.TypeGenErr.fatal_error(node, f"narrowing truncation overflow: constant {val.IntVal} exceeds the {target_bits}-bit range [0, {max_print}] of the target type, the assignment would be silently truncated", "OverflowError")
|
|
return mlirlite.build_trunc(builder, val, target_ty)
|
|
# 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 (TPV 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: aggregate targets keep the implicit load only
|
|
# for construction materialization; real Foo* values must be dereferenced.
|
|
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:
|
|
# Rule 3: implicit ptr -> value for non-basic types (Foo* -> Foo) is
|
|
# forbidden, except "construction materialization" (see _ctor_call_agg_ty).
|
|
ctor_p2v: mlirlite.LLVMType* = self._ctor_call_agg_ty(node)
|
|
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
|