749 lines
48 KiB
Plaintext
749 lines
48 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(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
|