1278 lines
84 KiB
Plaintext
1278 lines
84 KiB
Plaintext
import lib.core.Gen.GenTypes as GenTypes
|
||
import t, c
|
||
from stdint import *
|
||
import ast
|
||
import mlirlite
|
||
import memhub
|
||
import string
|
||
import viperlib
|
||
import lib.core.Gen.Translator as HT
|
||
import lib.core.Gen.Type.TypeSpec as TSpec
|
||
|
||
|
||
import lib.core.Gen.Expr.ExprGenBase as ExprGenBase
|
||
|
||
# ExprGenOps - ExprGenMixin split (stage D): compare / unary / bool / binop operations
|
||
# ============================================================
|
||
novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
|
||
"""Comparison/unary/boolean/binop operations"""
|
||
|
||
# translate ternary IfExp(test, body, orelse): emit `cond ? body : orelse` with then/else blocks
|
||
# branching to a merge block whose phi is [body_val, then_blk], [orelse_val, else_blk].
|
||
def translate_ifexp(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, node: ast.AST*, trans: HT.Translator* = None) -> mlirlite.Value*:
|
||
"""Translate a ternary (body if test else orelse) into a phi Value."""
|
||
if node is None or builder is None or trans is None: return None
|
||
ie: ast.IfExp* = ast.IfExp*(node)
|
||
if ie is None: return None
|
||
|
||
func: mlirlite.Function* = trans._cur_func
|
||
if func is None: return None
|
||
|
||
# 1. Evaluate the condition
|
||
cond_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, ie.test, None, 0, trans)
|
||
if cond_val is None: return None
|
||
# Convert to i1 (prefer dispatching __bool__)
|
||
cond_i1: mlirlite.Value* = self.coerce_cond_to_bool(pool, builder, mod, cond_val, trans)
|
||
|
||
# 2. Create the three basic blocks
|
||
cnt: int = trans._label_counter
|
||
trans._label_counter = cnt + 1
|
||
name_buf: str = pool.alloc(32)
|
||
if name_buf is None: return None
|
||
viperlib.snprintf(name_buf, 32, "ifexp.then.%d", cnt)
|
||
then_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
viperlib.snprintf(name_buf, 32, "ifexp.else.%d", cnt)
|
||
else_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
viperlib.snprintf(name_buf, 32, "ifexp.merge.%d", cnt)
|
||
merge_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
|
||
if then_bb is None or else_bb is None or merge_bb is None: return None
|
||
|
||
# 3. Conditional branch
|
||
mlirlite.build_cond_br(builder, cond_i1, then_bb, else_bb)
|
||
|
||
# 4. then branch
|
||
mlirlite.position_at_end(builder, then_bb)
|
||
body_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, ie.body, None, 0, trans)
|
||
then_blk_for_phi: mlirlite.BasicBlock* = trans._cur_builder.CurBlock
|
||
if body_val is None:
|
||
# fail-fast: ternary then-branch translation failed; abort compilation (expose the upstream root cause)
|
||
self.Gen.TypeGenErr.fatal_error(ie.body, "ternary expression branch translation failed", "TypeError")
|
||
# Do not br yet; br after type alignment
|
||
|
||
# 5. else branch. When the then-branch is a pointer, pass its type as the else target-type context: a single-quoted
|
||
# 1-char literal (CONST_CHAR, e.g. '?') materializes as a 1-char string, otherwise as i32 -> Coerce CastError.
|
||
orelse_target_ty: mlirlite.LLVMType* = None
|
||
if self.is_ptr_type(body_val.Ty) != 0: orelse_target_ty = body_val.Ty
|
||
mlirlite.position_at_end(builder, else_bb)
|
||
orelse_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, ie.orelse, None, 0, trans, orelse_target_ty)
|
||
else_blk_for_phi: mlirlite.BasicBlock* = trans._cur_builder.CurBlock
|
||
if orelse_val is None:
|
||
# fail-fast: ternary else-branch translation failed; abort compilation (expose the upstream root cause)
|
||
self.Gen.TypeGenErr.fatal_error(ie.orelse, "ternary expression branch translation failed", "TypeError")
|
||
# Do not br yet; br after type alignment
|
||
|
||
# Type alignment (the two branches may differ): coerce_to_type must run inside each branch so the merge block's
|
||
# PHI is its first instruction (otherwise sext/zext/ptrtoint/inttoptr land before the PHI, violating LLVM rules).
|
||
body_bits: int = self.get_llvm_type_bits(body_val.Ty)
|
||
orelse_bits: int = self.get_llvm_type_bits(orelse_val.Ty)
|
||
body_is_ptr: int = self.is_ptr_type(body_val.Ty)
|
||
orelse_is_ptr: int = self.is_ptr_type(orelse_val.Ty)
|
||
# Case 0: one side is a pointer and the other an integer (e.g. i64 vs i8* null); must be handled first since
|
||
# Ptr.bits == 0 skips the next three branches. body_val.Ty becomes phi_ty and orelse_val is coerced to it.
|
||
if body_is_ptr != orelse_is_ptr:
|
||
mlirlite.position_at_end(builder, else_blk_for_phi)
|
||
orelse_val = self.coerce_to_type(builder, orelse_val, body_val.Ty, node)
|
||
elif body_bits > orelse_bits and orelse_bits > 0:
|
||
# Coerce orelse_val in the else block
|
||
mlirlite.position_at_end(builder, else_blk_for_phi)
|
||
orelse_val = self.coerce_to_type(builder, orelse_val, body_val.Ty, node)
|
||
elif orelse_bits > body_bits and body_bits > 0:
|
||
# Back in the then block, coerce body_val
|
||
mlirlite.position_at_end(builder, then_blk_for_phi)
|
||
body_val = self.coerce_to_type(builder, body_val, orelse_val.Ty, node)
|
||
elif body_bits == orelse_bits and body_bits > 0:
|
||
# Same bits but different types (e.g. i64 vs Ptr): null cannot be used in an i64 PHI.
|
||
# This branch should not trigger (case 0 covers it); kept as a defensive guard.
|
||
if self.is_ptr_type(body_val.Ty) != self.is_ptr_type(orelse_val.Ty):
|
||
mlirlite.position_at_end(builder, else_blk_for_phi)
|
||
orelse_val = self.coerce_to_type(builder, orelse_val, body_val.Ty, node)
|
||
|
||
# Now br to merge from the then/else blocks (after type coercion)
|
||
mlirlite.position_at_end(builder, then_blk_for_phi)
|
||
mlirlite.build_br(builder, merge_bb)
|
||
mlirlite.position_at_end(builder, else_blk_for_phi)
|
||
mlirlite.build_br(builder, merge_bb)
|
||
|
||
# 6. merge block: phi (the PHI is the merge block's first instruction)
|
||
mlirlite.position_at_end(builder, merge_bb)
|
||
|
||
phi_ty: mlirlite.LLVMType* = body_val.Ty
|
||
if phi_ty is None:
|
||
# fail-fast: ternary branch type missing; abort compilation (expose the upstream root cause)
|
||
self.Gen.TypeGenErr.fatal_error(node, "ternary expression branch type missing", "TypeError")
|
||
|
||
inc1: mlirlite.PhiIncoming* = mlirlite.new_phi_incoming(pool, body_val, then_blk_for_phi)
|
||
inc2: mlirlite.PhiIncoming* = mlirlite.new_phi_incoming(pool, orelse_val, else_blk_for_phi)
|
||
if inc1 is None or inc2 is None: return None
|
||
# List order: inc1 (head) -> inc2 (tail). Writing inc2.Next = inc1 by mistake left inc1.Next as None, emitting a
|
||
# single phi entry and triggering LLVM verification: "PHINode should have one entry for each predecessor".
|
||
inc1.Next = inc2
|
||
return mlirlite.build_phi(builder, phi_ty, inc1, 2)
|
||
# translate_compare - Compare(left, ops, comparators)
|
||
# ============================================================
|
||
def should_zext_widen(self, pool: memhub.MemBuddy*, narrow_val: mlirlite.Value*, wide_val: mlirlite.Value*, narrow_node: ast.AST* = None, trans: HT.Translator* = None) -> int:
|
||
"""Decide integer widening: 1 = zext (unsigned semantics), 0 = sext."""
|
||
if narrow_val is None or narrow_val.Ty is None: return 0
|
||
narrow_bits: int = self.get_llvm_type_bits(narrow_val.Ty)
|
||
if narrow_bits == 1: return 1
|
||
if narrow_bits == 8:
|
||
# i8 defaults to unsigned-byte handling (systems convention), but an explicitly signed source annotation
|
||
# must sext to preserve sign: `int32(v)` with v: int8 = -50 must stay -50, not become 206.
|
||
if self._node_type_signed(pool, narrow_node, trans) != 0: return 0
|
||
return 1
|
||
# Wide-side constant heuristic
|
||
if wide_val is not None and wide_val.IsConst == 1:
|
||
wide_bits: int = self.get_llvm_type_bits(wide_val.Ty)
|
||
if wide_bits > narrow_bits and narrow_bits < 64:
|
||
signed_max: int64 = (int64(1) << (narrow_bits - 1)) - 1
|
||
if wide_val.IntVal > signed_max: return 1
|
||
# Narrow side resolves to an unsigned type (e.g. t.CUIntXXT literal / cast). trans must be threaded down: for
|
||
# Name/Subscript, unsignedness comes from trans.SymTab; without trans it returns 0 and sext is wrongly used.
|
||
if narrow_node is not None:
|
||
if self._node_type_unsigned(pool, narrow_node, trans) != 0: return 1
|
||
# A CDefine constant with the high bit set (0x80000000..0xFFFFFFFF) -> zext. CDefine is a compile-time value,
|
||
# so _node_type_unsigned finds no annotation; the negative-signed range means an unsigned sentinel (e.g. 0xFFFFFFFF).
|
||
if narrow_node is not None and narrow_node.kind() == ast.ASTKind.Name:
|
||
cdef_nm_zw: ast.Name* = ast.Name*(narrow_node)
|
||
if cdef_nm_zw.id is not None:
|
||
cdv_zw: int64 = self.Gen.TypeGenCDef.lookup_cdefine_constant(cdef_nm_zw.id)
|
||
if self.Gen.TypeGenCDef.is_cdefine_found() != 0:
|
||
if cdv_zw > 0x7FFFFFFF: return 1
|
||
return 0
|
||
def _node_type_unsigned(self, pool: memhub.MemBuddy*, node: ast.AST*, trans: HT.Translator* = None) -> int:
|
||
"""Return 1 if the AST node resolves to an unsigned integer type, else 0."""
|
||
if node is None: return 0
|
||
k: int = node.kind()
|
||
if k == ast.ASTKind.Call:
|
||
cl: ast.Call* = ast.Call*(node)
|
||
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
|
||
at: ast.Attribute* = ast.Attribute*(cl.func)
|
||
if at.value is not None and at.value.kind() == ast.ASTKind.Name:
|
||
mod_nm: ast.Name* = ast.Name*(at.value)
|
||
if mod_nm.id is not None and string.strcmp(mod_nm.id, "t") == 0 and at.attr is not None:
|
||
if self._type_name_unsigned(at.attr) != 0: return 1
|
||
return 0
|
||
if k == ast.ASTKind.Name:
|
||
nm: ast.Name* = ast.Name*(node)
|
||
if nm.id is not None:
|
||
# 1) The name itself is an unsigned type name (e.g. the UInt32T alias)
|
||
if self._type_name_unsigned(nm.id) != 0: return 1
|
||
# 2) The variable's annotation type is unsigned (query SymTab when trans is available)
|
||
if trans is not None:
|
||
ve: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)
|
||
if ve is not None:
|
||
# [S4 read] An explicit unsigned Spec (TS_UINT) takes priority over the string check
|
||
if self._entry_spec_unsigned(ve.Spec) != 0: return 1
|
||
vcn_u: str = self.Gen.VarGen.var_annot_class_name(ve)
|
||
if vcn_u is not None:
|
||
if self._type_name_unsigned(vcn_u) != 0: return 1
|
||
return 0
|
||
if k == ast.ASTKind.BinOp:
|
||
bop: ast.BinOp* = ast.BinOp*(node)
|
||
if self._node_type_unsigned(pool, bop.left, trans) != 0: return 1
|
||
if self._node_type_unsigned(pool, bop.right, trans) != 0: return 1
|
||
return 0
|
||
if k == ast.ASTKind.UnaryOp:
|
||
uop: ast.UnaryOp* = ast.UnaryOp*(node)
|
||
if self._node_type_unsigned(pool, uop.operand, trans) != 0: return 1
|
||
return 0
|
||
if k == ast.ASTKind.Subscript:
|
||
# Array/pointer subscript: md5_buf[i] (t.CArray[uint8, N]) or buf[i] (param uint8*) has an unsigned element -> zext.
|
||
sub_su: ast.Subscript* = ast.Subscript*(node)
|
||
if sub_su.value is not None and sub_su.value.kind() == ast.ASTKind.Name:
|
||
snm_su: ast.Name* = ast.Name*(sub_su.value)
|
||
if snm_su.id is not None and trans is not None:
|
||
ve_su: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, snm_su.id)
|
||
if ve_su is not None:
|
||
# [S4 read] An explicit unsigned Spec wins; for union annotations the Spec is still Ptr/Array -> returns 0 and falls back to the string path.
|
||
if self._entry_spec_unsigned(ve_su.Spec) != 0: return 1
|
||
scn_u: str = self.Gen.VarGen.var_annot_class_name(ve_su)
|
||
if scn_u is not None:
|
||
if self._annot_str_unsigned(scn_u) != 0: return 1
|
||
return 0
|
||
if k == ast.ASTKind.Attribute:
|
||
# Struct field (e.g. viperio.Buf's self.capacity/length: sizet): get the class name from the receiver's AnnotClassName, then look up the field annotation to decide unsignedness. Read-only; returns 0 on failure, so find_struct + manual field walk is used instead of lookup_field_by_class.
|
||
at_u: ast.Attribute* = ast.Attribute*(node)
|
||
if at_u.value is not None and at_u.attr is not None and trans is not None:
|
||
cls_nm_u: str = None
|
||
if at_u.value.kind() == ast.ASTKind.Name:
|
||
vn_u: ast.Name* = ast.Name*(at_u.value)
|
||
if vn_u.id is not None:
|
||
ve_u: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, vn_u.id)
|
||
if ve_u is not None: cls_nm_u = self.Gen.VarGen.var_annot_class_name(ve_u)
|
||
if cls_nm_u is not None:
|
||
# Take the bare class name (before the dot): look up by bare name only, avoiding module-qualified resolution
|
||
bare_u: str = cls_nm_u
|
||
dot_u: str = self.Gen.StructGen._last_dot_outside_brackets(cls_nm_u)
|
||
if dot_u is not None: bare_u = dot_u + 1
|
||
if bare_u is not None:
|
||
st_u: GenTypes.StructEntry* = self.Gen.StructGen.find_struct(bare_u)
|
||
if st_u is not None:
|
||
for fi_u in range(int(st_u.FieldCount)):
|
||
fe_u: GenTypes.FieldEntry* = self.Gen.StructGen._get_field_entry(st_u, fi_u)
|
||
if fe_u is not None and fe_u.Name is not None and string.strcmp(fe_u.Name, at_u.attr) == 0:
|
||
# [S4 read] An explicit unsigned field Spec wins
|
||
if self._entry_spec_unsigned(fe_u.Spec) != 0: return 1
|
||
fcn_u: str = self.Gen.StructGen.get_field_annot_class_name(fe_u)
|
||
if fcn_u is not None:
|
||
if self._annot_str_unsigned(fcn_u) != 0: return 1
|
||
break
|
||
return 0
|
||
return 0
|
||
def _node_type_signed(self, pool: memhub.MemBuddy*, node: ast.AST*, trans: HT.Translator* = None) -> int:
|
||
"""Return 1 if the source node is explicitly a signed integer, else 0."""
|
||
if node is None: return 0
|
||
k: int = node.kind()
|
||
if k == ast.ASTKind.Call:
|
||
cl: ast.Call* = ast.Call*(node)
|
||
if cl.func is not None and cl.func.kind() == ast.ASTKind.Attribute:
|
||
at: ast.Attribute* = ast.Attribute*(cl.func)
|
||
if at.value is not None and at.value.kind() == ast.ASTKind.Name:
|
||
mod_nm: ast.Name* = ast.Name*(at.value)
|
||
if mod_nm.id is not None and string.strcmp(mod_nm.id, "t") == 0 and at.attr is not None:
|
||
if self._spec_sign_from_annot(at.attr) == TSpec.TS_SIGN_SIGNED: return 1
|
||
return 0
|
||
if k == ast.ASTKind.Name:
|
||
nm: ast.Name* = ast.Name*(node)
|
||
if nm.id is not None:
|
||
if self._spec_sign_from_annot(nm.id) == TSpec.TS_SIGN_SIGNED: return 1
|
||
if trans is not None:
|
||
ve: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm.id)
|
||
if ve is not None:
|
||
ncn_s: str = self.Gen.VarGen.var_annot_class_name(ve)
|
||
if ncn_s is not None:
|
||
if self._spec_sign_from_annot(ncn_s) == TSpec.TS_SIGN_SIGNED: return 1
|
||
return 0
|
||
if k == ast.ASTKind.Subscript:
|
||
sub_s: ast.Subscript* = ast.Subscript*(node)
|
||
if sub_s.value is not None and sub_s.value.kind() == ast.ASTKind.Name:
|
||
snm: ast.Name* = ast.Name*(sub_s.value)
|
||
if snm.id is not None and trans is not None:
|
||
ve_s: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, snm.id)
|
||
if ve_s is not None:
|
||
scn_s: str = self.Gen.VarGen.var_annot_class_name(ve_s)
|
||
if scn_s is not None:
|
||
if self._spec_sign_from_annot(scn_s) == TSpec.TS_SIGN_SIGNED: return 1
|
||
return 0
|
||
return 0
|
||
# S4 de-stringification (unsigned detection): the single source of truth is TypeGenCType.spec_sign_of_annot; the old
|
||
# strstr-substring fallbacks were deleted (they caused the A4/A5 false positives). UNKNOWN means no opinion -> return 0.
|
||
def _spec_sign_from_annot(self, annot: str) -> int:
|
||
"""Map an annotation to TS_SIGN_*; UNKNOWN takes no position."""
|
||
return self.Gen.TypeGenCType.spec_sign_of_annot(annot, 0)
|
||
# _entry_spec_unsigned - whether a variable/field entry's Spec is an explicitly unsigned integer. `Kind == TS_UINT`
|
||
# is a strong signal and takes priority; `Kind == TS_INT` is ambiguous, so only the unsigned direction is switched.
|
||
def _entry_spec_unsigned(self, sp: TSpec.TypeSpec*) -> int:
|
||
"""Return 1 if the entry Spec is explicitly unsigned (TS_UINT), else 0."""
|
||
if sp is None: return 0
|
||
if sp.Kind == TSpec.TS_UINT: return 1
|
||
return 0
|
||
def _type_name_unsigned(self, type_name: str) -> int:
|
||
"""Return 1 if the type name is unsigned; UNKNOWN means 0."""
|
||
if type_name is not None:
|
||
if self._spec_sign_from_annot(type_name) == TSpec.TS_SIGN_UNSIGNED: return 1
|
||
return 0
|
||
def _annot_str_unsigned(self, annot: str) -> int:
|
||
"""Return 1 if the annotation string is unsigned; UNKNOWN means 0."""
|
||
if annot is not None:
|
||
if self._spec_sign_from_annot(annot) == TSpec.TS_SIGN_UNSIGNED: return 1
|
||
return 0
|
||
def translate_compare(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, node: ast.AST*, trans: HT.Translator* = None) -> mlirlite.Value*:
|
||
"""Translate a comparison expression and return an i1 result."""
|
||
cmp: ast.Compare* = ast.Compare*(node)
|
||
if cmp is None: return None
|
||
|
||
ops_list: list[int]* = (list[int]*)(cmp.ops)
|
||
if ops_list is None or len(ops_list) == 0: return None
|
||
op: int = ops_list.get(0)
|
||
|
||
comparators: list[ast.AST*]* = cmp.comparators
|
||
if comparators is None or len(comparators) == 0: return None
|
||
rhs_node: ast.AST* = comparators.get(0)
|
||
rhs: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, rhs_node, None, 0, trans)
|
||
if rhs is None: return None
|
||
|
||
# === None comparison optimization: x == None / x != None use identity comparison (icmp eq/ne null) ===
|
||
# Avoids emitting invalid __eq__/__ne__ calls for struct pointers; Python treats == None as identity comparison.
|
||
lhs_node: ast.AST* = cmp.left
|
||
cmp_with_none: int = 0
|
||
if op == ast.OpKind.Eq or op == ast.OpKind.Ne or op == ast.OpKind.Is or op == ast.OpKind.IsNot:
|
||
if rhs_node is not None and rhs_node.kind() == ast.ASTKind.Constant:
|
||
rhs_cn: ast.Constant* = ast.Constant*(rhs_node)
|
||
if rhs_cn is not None and rhs_cn.const_kind == ast.CONST_NONE: cmp_with_none = 1
|
||
if cmp_with_none == 0 and lhs_node is not None and lhs_node.kind() == ast.ASTKind.Constant:
|
||
lhs_cn: ast.Constant* = ast.Constant*(lhs_node)
|
||
if lhs_cn is not None and lhs_cn.const_kind == ast.CONST_NONE: cmp_with_none = 1
|
||
|
||
# === Pointer null-check pattern: ptr != 0 / ptr == 0. Skip operator overload and use identity comparison
|
||
# (icmp null); otherwise cross-module classes without __eq__/__ne__ get ghost calls -> undefined reference. ===
|
||
cmp_zero: int = 0
|
||
if op == ast.OpKind.Eq or op == ast.OpKind.Ne:
|
||
if rhs_node is not None and self._is_zero_int_literal(rhs_node) != 0: cmp_zero = 1
|
||
if cmp_zero == 0 and lhs_node is not None and self._is_zero_int_literal(lhs_node) != 0: cmp_zero = 1
|
||
|
||
# === Compare operator overload path 1: lhs is a Name bound to a struct variable ===
|
||
# For value-type variables (e.g. cnt: Counter) try the overload with the alloca pointer; None comparison is skipped.
|
||
if cmp_with_none == 0 and lhs_node is not None and lhs_node.kind() == ast.ASTKind.Name and trans is not None:
|
||
nm: ast.Name* = ast.Name*(lhs_node)
|
||
if nm is not None and nm.id is not None:
|
||
lhs_alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
|
||
if lhs_alloca is not None:
|
||
if cmp_zero == 0 or self.get_ptr_level(lhs_alloca.Ty) == 0:
|
||
ovl_self1: mlirlite.Value* = lhs_alloca
|
||
# Ptr(Ptr(Struct)) object storage slot: load the object pointer as self (same as translate_binop path 1) to avoid using the slot address as the object address.
|
||
if lhs_alloca.Ty is not None and lhs_alloca.Ty.Pointee != 0 and self.Gen.ExprGenBase.is_ptr_type(lhs_alloca.Ty.Pointee) != 0:
|
||
load_self1: mlirlite.Value* = mlirlite.build_load(builder, lhs_alloca.Ty.Pointee, lhs_alloca)
|
||
if load_self1 is not None: ovl_self1 = load_self1
|
||
ovl_result: mlirlite.Value* = self.try_operator_overload(pool, builder, mod, ovl_self1, rhs, op, trans, 1)
|
||
if ovl_result is not None: return ovl_result
|
||
|
||
# Translate lhs normally
|
||
lhs: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, cmp.left, None, 0, trans)
|
||
if lhs is None: return None
|
||
|
||
# === in / not in: the container rhs calls __contains__ (aligned with TPC HandlesExprOps.py L261-281).
|
||
# x in y -> y.__contains__(x); x not in y -> !y.__contains__(x). With no __contains__, return i1 0 (always False).
|
||
if op == ast.OpKind.In or op == ast.OpKind.NotIn:
|
||
cn_st: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type_from_value(rhs)
|
||
if cn_st is not None:
|
||
cn_cls: str = self.Gen.StructGen.get_class_name_by_type(pool, cn_st)
|
||
if cn_cls is not None:
|
||
cn_lookup: str = pool.alloc(128)
|
||
if cn_lookup is not None:
|
||
viperlib.snprintf(cn_lookup, 128, "%s.__contains__", cn_cls)
|
||
cn_fn: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(mod, cn_lookup)
|
||
if cn_fn is not None:
|
||
cn_ret: mlirlite.LLVMType* = mlirlite.function_get_ret_ty(cn_fn)
|
||
cn_fn_name: str = mlirlite.function_get_name(cn_fn)
|
||
if cn_ret is not None and cn_fn_name is not None:
|
||
# Align the self argument (container) to __contains__'s first param type
|
||
cn_self: mlirlite.Value* = rhs
|
||
cn_first: mlirlite.Param* = mlirlite.function_get_param_head(cn_fn)
|
||
if cn_first is not None:
|
||
cn_self_ty: mlirlite.LLVMType* = mlirlite.param_get_ty(cn_first)
|
||
if cn_self_ty is not None: cn_self = mlirlite.build_bitcast(builder, rhs, cn_self_ty)
|
||
if cn_self is not None:
|
||
# Param list: self -> key (separately null-terminated)
|
||
cn_self.Next = lhs
|
||
lhs.Next = None
|
||
cn_res: mlirlite.Value* = mlirlite.build_call(builder, cn_fn_name, cn_self, 2, cn_ret, 0)
|
||
if cn_res is not None:
|
||
# Normalize to i1 (__contains__ may return int/i32 etc.)
|
||
cn_i1: mlirlite.Value* = cn_res
|
||
match cn_ret:
|
||
case mlirlite.LLVMType.Int(cn_bits):
|
||
if cn_bits > 1:
|
||
cn_zero: mlirlite.Value* = mlirlite.ConstInt(pool, cn_ret, 0, "0")
|
||
if cn_zero is not None: cn_i1 = mlirlite.build_icmp(builder, mlirlite.ICMP_NE, cn_res, cn_zero)
|
||
case _:
|
||
pass
|
||
if op == ast.OpKind.NotIn:
|
||
i1_ty_ni: mlirlite.LLVMType* = mlirlite.Int1(pool)
|
||
if i1_ty_ni is not None and cn_i1 is not None:
|
||
one_i1_ni: mlirlite.Value* = mlirlite.ConstInt(pool, i1_ty_ni, 1, "1")
|
||
if one_i1_ni is not None: cn_i1 = mlirlite.build_xor(builder, cn_i1, one_i1_ni)
|
||
if cn_i1 is not None: return cn_i1
|
||
# No __contains__: TPC semantics are always False (i1 0)
|
||
i1_ty_in: mlirlite.LLVMType* = mlirlite.Int1(pool)
|
||
if i1_ty_in is not None: return mlirlite.ConstInt(pool, i1_ty_in, 0, "0")
|
||
return None
|
||
|
||
# === Compare operator overload path 2: lhs is Ptr(Struct) ===
|
||
# Is/IsNot are unmapped so they return None and fall back to native comparison; None and pointer null-checks skip it.
|
||
if cmp_with_none == 0 and (cmp_zero == 0 or self.get_ptr_level(lhs.Ty) == 0):
|
||
ovl_result2: mlirlite.Value* = self.try_operator_overload(pool, builder, mod, lhs, rhs, op, trans, 1)
|
||
if ovl_result2 is not None: return ovl_result2
|
||
|
||
# === Unsigned comparison (A5): unsigned int operands (CUInt64T/CUInt32T etc.) use ULT/ULE/UGT/UGE.
|
||
# A fixed signed predicate made uint64 address comparisons wrong across 2^63 (A5 root cause); reuse _node_type_unsigned.
|
||
unsigned_l: int = 0
|
||
unsigned_r: int = 0
|
||
if lhs_node is not None: unsigned_l = self._node_type_unsigned(pool, lhs_node, trans)
|
||
if rhs_node is not None: unsigned_r = self._node_type_unsigned(pool, rhs_node, trans)
|
||
cmp_unsigned: int = 1 if (unsigned_l != 0 or unsigned_r != 0) else 0
|
||
|
||
predicate: int = mlirlite.ICMP_SLT
|
||
if op == ast.OpKind.Lt: predicate = mlirlite.ICMP_ULT if cmp_unsigned != 0 else mlirlite.ICMP_SLT
|
||
elif op == ast.OpKind.Le: predicate = mlirlite.ICMP_ULE if cmp_unsigned != 0 else mlirlite.ICMP_SLE
|
||
elif op == ast.OpKind.Gt: predicate = mlirlite.ICMP_UGT if cmp_unsigned != 0 else mlirlite.ICMP_SGT
|
||
elif op == ast.OpKind.Ge: predicate = mlirlite.ICMP_UGE if cmp_unsigned != 0 else mlirlite.ICMP_SGE
|
||
elif op == ast.OpKind.Eq: predicate = mlirlite.ICMP_EQ
|
||
elif op == ast.OpKind.Ne: predicate = mlirlite.ICMP_NE
|
||
elif op == ast.OpKind.Is:
|
||
# `x is y` -> pointer/value equality comparison
|
||
predicate = mlirlite.ICMP_EQ
|
||
elif op == ast.OpKind.IsNot:
|
||
# `x is not y` -> pointer/value inequality comparison
|
||
predicate = mlirlite.ICMP_NE
|
||
|
||
# Type safety: number vs number allowed (sext-aligned by bit width); one-level pointer vs one-level pointer allowed (address compare); number vs pointer(1+) forbidden (fail-fast, except None compare); different-level pointers forbidden. None compare skips this check.
|
||
if cmp_with_none == 0:
|
||
lhs_ptr_level: int = self.get_ptr_level(lhs.Ty)
|
||
rhs_ptr_level: int = self.get_ptr_level(rhs.Ty)
|
||
if lhs_ptr_level != rhs_ptr_level:
|
||
# Pointer vs integer literal 0 (null-check pattern): 0 is treated as NULL and converted to a same-type
|
||
# null pointer (icmp needs matching types), not a type mismatch.
|
||
zero_is_rhs: int = 0
|
||
zero_is_lhs: int = 0
|
||
if rhs_ptr_level == 0 and self._is_zero_int_literal(rhs_node) != 0: zero_is_rhs = 1
|
||
if lhs_ptr_level == 0 and self._is_zero_int_literal(lhs_node) != 0: zero_is_lhs = 1
|
||
if zero_is_rhs != 0 or zero_is_lhs != 0:
|
||
if zero_is_rhs != 0: rhs = mlirlite.ConstNull(pool, lhs.Ty, "null")
|
||
else: lhs = mlirlite.ConstNull(pool, rhs.Ty, "null")
|
||
else:
|
||
# Pointer vs non-zero integer (C sentinel pattern, e.g. INVALID_HANDLE_VALUE = -1): align with TPC
|
||
# HandlesExprOps.py L388-398; ptrtoint the pointer to compare as an integer address.
|
||
if lhs_ptr_level != 0 and rhs_ptr_level == 0: lhs = mlirlite.build_ptrtoint(builder, lhs, rhs.Ty)
|
||
elif rhs_ptr_level != 0 and lhs_ptr_level == 0: rhs = mlirlite.build_ptrtoint(builder, rhs, lhs.Ty)
|
||
else:
|
||
# Different-level pointers (1 vs 2 etc.) -> forbidden (fail-fast)
|
||
self.Gen.TypeGenErr.fatal_error(cmp, f"comparison type mismatch: pointers of different levels compared (lhs level={lhs_ptr_level}, rhs level={rhs_ptr_level})", "TypeError")
|
||
else:
|
||
# None comparison: the None side is implicitly 0/null, its form decided by the other side.
|
||
# pointer -> same-type null pointer (icmp eq/ne); integer -> 0 (x == 0); struct value -> never None (== false, != true).
|
||
lhs_is_none_lit: int = 0
|
||
if lhs_node is not None and lhs_node.kind() == ast.ASTKind.Constant:
|
||
lhs_none_cn: ast.Constant* = ast.Constant*(lhs_node)
|
||
if lhs_none_cn is not None and lhs_none_cn.const_kind == ast.CONST_NONE: lhs_is_none_lit = 1
|
||
lhs_ptr_level_none: int = self.get_ptr_level(lhs.Ty)
|
||
rhs_ptr_level_none: int = self.get_ptr_level(rhs.Ty)
|
||
if lhs_is_none_lit != 0:
|
||
# None on the left (`None is x` / `None == x`): the rhs type decides None's form
|
||
if rhs_ptr_level_none == 0:
|
||
rhs_is_struct_none: int = 0
|
||
match rhs.Ty:
|
||
case mlirlite.LLVMType.Struct(_, _, _):
|
||
rhs_is_struct_none = 1
|
||
case _:
|
||
rhs_is_struct_none = 0
|
||
if rhs_is_struct_none != 0:
|
||
if op == ast.OpKind.Eq or op == ast.OpKind.Is: return mlirlite.const_int(pool, 1, 0)
|
||
return mlirlite.const_int(pool, 1, 1)
|
||
# rhs is numeric -> convert the None side to integer 0 (rhs unchanged)
|
||
lhs = mlirlite.const_int32(pool, 0)
|
||
else:
|
||
# None on the right (`x is None` / `x == None`): the lhs type decides None's form
|
||
if lhs_ptr_level_none == 0:
|
||
# Check whether lhs is a struct value type (a non-pointer Struct)
|
||
lhs_is_struct_none: int = 0
|
||
match lhs.Ty:
|
||
case mlirlite.LLVMType.Struct(_, _, _):
|
||
lhs_is_struct_none = 1
|
||
case _:
|
||
lhs_is_struct_none = 0
|
||
if lhs_is_struct_none != 0:
|
||
# A struct value is never None: ==/is return false, !=/is not return true
|
||
if op == ast.OpKind.Eq or op == ast.OpKind.Is: return mlirlite.const_int(pool, 1, 0)
|
||
else: # Ne, IsNot
|
||
return mlirlite.const_int(pool, 1, 1)
|
||
# lhs is numeric and rhs is i8* null -> convert rhs to numeric 0 (lhs unchanged)
|
||
rhs = mlirlite.const_int32(pool, 0)
|
||
|
||
# Type alignment: icmp requires matching types on both sides. Only numeric types are aligned (same-level pointers icmp directly); i1 uses zext (1 stays 1).
|
||
lhs_bits: int = self.get_llvm_type_bits(lhs.Ty)
|
||
rhs_bits: int = self.get_llvm_type_bits(rhs.Ty)
|
||
# C 语义整数提升(与算术同一口径):两侧都窄于 32 位时先提升到 i32 再比较,避免 8/16 位
|
||
# 直接比较在符号/边界组合下语义模糊(如 signed i8 与 unsigned u8 直接比)。
|
||
# 提升后把 bits 记为 32,下面的等宽化不再重复处理。
|
||
if lhs_bits != 0 and rhs_bits != 0 and lhs_bits < 32 and rhs_bits < 32:
|
||
cmp_i32_pr: mlirlite.LLVMType* = mlirlite.Int32(pool)
|
||
cmp_probe: mlirlite.Value* = mlirlite.const_int32(pool, 0)
|
||
if self.should_zext_widen(pool, lhs, cmp_probe, cmp.left, trans) != 0: lhs = mlirlite.build_zext(builder, lhs, cmp_i32_pr)
|
||
else: lhs = mlirlite.build_sext(builder, lhs, cmp_i32_pr)
|
||
if self.should_zext_widen(pool, rhs, cmp_probe, rhs_node, trans) != 0: rhs = mlirlite.build_zext(builder, rhs, cmp_i32_pr)
|
||
else: rhs = mlirlite.build_sext(builder, rhs, cmp_i32_pr)
|
||
lhs_bits = 32
|
||
rhs_bits = 32
|
||
if lhs_bits != 0 and rhs_bits != 0 and lhs_bits != rhs_bits:
|
||
if lhs_bits < rhs_bits:
|
||
if self.should_zext_widen(pool, lhs, rhs, cmp.left, trans) != 0: lhs = mlirlite.build_zext(builder, lhs, rhs.Ty)
|
||
else: lhs = mlirlite.build_sext(builder, lhs, rhs.Ty)
|
||
else:
|
||
if self.should_zext_widen(pool, rhs, lhs, rhs_node, trans) != 0: rhs = mlirlite.build_zext(builder, rhs, lhs.Ty)
|
||
else: rhs = mlirlite.build_sext(builder, rhs, lhs.Ty)
|
||
|
||
# Pointer type alignment: same-level pointers with different pointee types are unified by bitcast (e.g. i8* vs Struct*).
|
||
if lhs_bits == 0 and rhs_bits == 0:
|
||
lhs_is_ptr_now: int = self.is_ptr_type(lhs.Ty)
|
||
rhs_is_ptr_now: int = self.is_ptr_type(rhs.Ty)
|
||
if lhs_is_ptr_now != 0 and rhs_is_ptr_now != 0:
|
||
# Both are pointers; compare type strings via TypePrint
|
||
lhs_ty_buf: str = pool.alloc_tagged(4096, memhub.DETSITE_EXPRGENOPS_500)
|
||
rhs_ty_buf: str = pool.alloc_tagged(4096, memhub.DETSITE_EXPRGENOPS_501)
|
||
if lhs_ty_buf is not None and rhs_ty_buf is not None:
|
||
mlirlite.TypePrint(lhs_ty_buf, 4096, lhs.Ty, pool)
|
||
mlirlite.TypePrint(rhs_ty_buf, 4096, rhs.Ty, pool)
|
||
if string.strcmp(lhs_ty_buf, rhs_ty_buf) != 0: rhs = mlirlite.build_bitcast(builder, rhs, lhs.Ty)
|
||
|
||
# Float comparison: use fcmp instead of icmp when either operand is floating point
|
||
lhs_fbits: int = self.get_llvm_float_bits(lhs.Ty)
|
||
rhs_fbits: int = self.get_llvm_float_bits(rhs.Ty)
|
||
if lhs_fbits != 0 or rhs_fbits != 0:
|
||
# Float type alignment (fpext/fptrunc)
|
||
if lhs_fbits != 0 and rhs_fbits != 0 and lhs_fbits != rhs_fbits:
|
||
if lhs_fbits < rhs_fbits: lhs = mlirlite.build_fpext(builder, lhs, rhs.Ty)
|
||
else: rhs = mlirlite.build_fptrunc(builder, rhs, lhs.Ty)
|
||
elif lhs_fbits != 0 and rhs_fbits == 0:
|
||
# int → float
|
||
rhs = mlirlite.build_si2fp(builder, rhs, lhs.Ty)
|
||
elif rhs_fbits != 0 and lhs_fbits == 0: lhs = mlirlite.build_si2fp(builder, lhs, rhs.Ty)
|
||
# Map the comparison predicate to fcmp
|
||
fpredicate: int = mlirlite.FCMP_OEQ
|
||
if op == ast.OpKind.Eq or op == ast.OpKind.Is: fpredicate = mlirlite.FCMP_OEQ
|
||
elif op == ast.OpKind.Ne or op == ast.OpKind.IsNot: fpredicate = mlirlite.FCMP_ONE
|
||
elif op == ast.OpKind.Lt: fpredicate = mlirlite.FCMP_OLT
|
||
elif op == ast.OpKind.Le: fpredicate = mlirlite.FCMP_OLE
|
||
elif op == ast.OpKind.Gt: fpredicate = mlirlite.FCMP_OGT
|
||
elif op == ast.OpKind.Ge: fpredicate = mlirlite.FCMP_OGE
|
||
return mlirlite.build_fcmp(builder, fpredicate, lhs, rhs)
|
||
|
||
return mlirlite.build_icmp(builder, predicate, lhs, rhs)
|
||
# translate_unaryop - UnaryOp(op, operand)
|
||
# ============================================================
|
||
def translate_unaryop(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, node: ast.AST*, trans: HT.Translator* = None) -> mlirlite.Value*:
|
||
"""Translate unary operations (-x, +x, not x, ~x)."""
|
||
uo: ast.UnaryOp* = ast.UnaryOp*(node)
|
||
if uo is None: return None
|
||
|
||
operand: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, uo.operand, None, 0, trans)
|
||
if operand is None: return None
|
||
|
||
# === Unary operator overload: -obj/+obj/~obj call __neg__/__pos__/__invert__ (only USub/UAdd/Invert have overloads).
|
||
if uo.op == ast.OpKind.USub or uo.op == ast.OpKind.UAdd or uo.op == ast.OpKind.Invert:
|
||
ovl_u: mlirlite.Value* = self.try_unary_operator_overload(pool, builder, mod, operand, uo.op, trans)
|
||
if ovl_u is not None: return ovl_u
|
||
|
||
# C 语义整数提升(与算术/比较同一口径):一元 +/-/~ 先把窄于 32 位的整数操作数提升到 i32,
|
||
# 这样 `0 - x` / `x ^ -1` 的常量与操作数同宽(否则是 i32 常量对 i8/i16 操作数)。
|
||
# `not x` 不需要:结果为 i1,操作数宽度不影响语义。
|
||
if uo.op == ast.OpKind.USub or uo.op == ast.OpKind.UAdd or uo.op == ast.OpKind.Invert:
|
||
u_bits: int = self.get_llvm_type_bits(operand.Ty)
|
||
if u_bits != 0 and u_bits < 32:
|
||
u_i32_ty: mlirlite.LLVMType* = mlirlite.Int32(pool)
|
||
u_probe: mlirlite.Value* = mlirlite.const_int32(pool, 0)
|
||
if self.should_zext_widen(pool, operand, u_probe, uo.operand, trans) != 0: operand = mlirlite.build_zext(builder, operand, u_i32_ty)
|
||
else: operand = mlirlite.build_sext(builder, operand, u_i32_ty)
|
||
|
||
if uo.op == ast.OpKind.USub:
|
||
# -x
|
||
# Float type uses fneg (emit fneg directly to avoid 0.0 - x type mismatch)
|
||
operand_fbits: int = self.get_llvm_float_bits(operand.Ty)
|
||
if operand_fbits > 0:
|
||
# 编译期常量折叠:-c 直接产出常量并保持 IsConst
|
||
if operand.IsConst == 1: return mlirlite.ConstFloat(pool, operand.Ty, -operand.FloatVal)
|
||
return mlirlite.build_fneg(builder, operand)
|
||
# 编译期常量折叠:-c 直接产出常量(IsConst=1)。否则 `v: int8 = -50` 会被当成运行时 i32 值,
|
||
# 触发“隐式窄化禁止”,尽管其数值完全落在 i8 内(隐式窄化的“编译期常量”豁免失效)。
|
||
operand_bits: int = self.get_llvm_type_bits(operand.Ty)
|
||
if operand_bits != 0 and operand.IsConst == 1:
|
||
return mlirlite.const_int(pool, operand_bits, -operand.IntVal)
|
||
# Integer: -x = 0 - x, choosing the zero constant by bit width (avoid i32/i64 mismatch)
|
||
zero: mlirlite.Value* = mlirlite.const_int32(pool, 0)
|
||
if operand_bits == 64: zero = mlirlite.const_int64(pool, 0)
|
||
elif operand_bits > 0 and operand_bits != 32:
|
||
# Other widths (i8/i16 etc.): use i32 zero; operand will be coerced
|
||
zero = mlirlite.const_int32(pool, 0)
|
||
return mlirlite.build_sub(builder, zero, operand)
|
||
elif uo.op == ast.OpKind.UAdd:
|
||
# +x = x
|
||
return operand
|
||
elif uo.op == ast.OpKind.Not:
|
||
# not x = (bool(x) == 0), returns i1; prefer dispatching __bool__
|
||
not_cond: mlirlite.Value* = self.coerce_cond_to_bool(pool, builder, mod, operand, trans)
|
||
if not_cond is None: return None
|
||
false_val: mlirlite.Value* = mlirlite.const_int(pool, 1, 0)
|
||
return mlirlite.build_icmp(builder, mlirlite.ICMP_EQ, not_cond, false_val)
|
||
elif uo.op == ast.OpKind.Invert:
|
||
# ~x = x ^ -1
|
||
operand_bits_inv: int = self.get_llvm_type_bits(operand.Ty)
|
||
# 编译期常量折叠:~c 直接产出常量并保持 IsConst(与 -c 相同的窄化豁免口径)
|
||
if operand_bits_inv != 0 and operand.IsConst == 1:
|
||
return mlirlite.const_int(pool, operand_bits_inv, operand.IntVal ^ -1)
|
||
neg1: mlirlite.Value* = mlirlite.const_int32(pool, -1)
|
||
if operand_bits_inv == 64: neg1 = mlirlite.const_int64(pool, -1)
|
||
return mlirlite.build_xor(builder, operand, neg1)
|
||
elif uo.op == ast.OpKind.Deref:
|
||
# *p -> load by the pointee type (aligned with translate_c_deref)
|
||
if self.Gen.ExprGenBase.is_ptr_type(operand.Ty) != 0:
|
||
d_pointee: mlirlite.LLVMType* = operand.Ty.Pointee
|
||
if d_pointee is not None: return mlirlite.build_load(builder, d_pointee, operand)
|
||
# Non-pointer means type inference degraded: fail-fast, never fall back to reading as i8
|
||
self.Gen.TypeGenErr.fatal_error(node, "the operand of unary '*' dereference must be a pointer type (type inference degraded)", "TypeError")
|
||
return None
|
||
elif uo.op == ast.OpKind.AddrOf:
|
||
# &x -> lvalue address (no load): Name alloca / module global / module function / Attribute field GEP / Subscript element address.
|
||
if trans is None: return None
|
||
if uo.operand.kind() == ast.ASTKind.Name:
|
||
nm_ao: ast.Name* = ast.Name*(uo.operand)
|
||
if nm_ao.id is not None:
|
||
# P2 willFree:对已失效变量取地址 → fail-fast(与读取路径同一口径)
|
||
if self.Gen.VarGen._FreedCheck(self.Gen.VarGen.lookup_var(trans.SymTab, nm_ao.id)) != 0:
|
||
self.Gen.TypeGenErr.fatal_error(node, f"cannot take the address of '{nm_ao.id}': already released by a @life(willFree(...)) call", "LifetimeError")
|
||
# self/cls object reference: return the object pointer itself (Ptr(Struct)), not the pointer variable's address (Ptr(Ptr(Struct))).
|
||
if string.strcmp(nm_ao.id, "self") == 0 or string.strcmp(nm_ao.id, "cls") == 0:
|
||
se_ao: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm_ao.id)
|
||
if se_ao is not None and se_ao.Alloca is not None and se_ao.Alloca.Ty is not None:
|
||
match se_ao.Alloca.Ty:
|
||
case mlirlite.LLVMType.Ptr(sa1):
|
||
match sa1:
|
||
case mlirlite.LLVMType.Ptr(sa2):
|
||
obj_ty_sa: mlirlite.LLVMType* = mlirlite.Ptr(pool, sa2)
|
||
return mlirlite.build_load(builder, obj_ty_sa, se_ao.Alloca)
|
||
case _:
|
||
pass
|
||
case _:
|
||
pass
|
||
v_ao: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm_ao.id)
|
||
if v_ao is None and mod is not None:
|
||
# Module global variable: convert via make_global_ref into a quoted Ptr(Ty) reference
|
||
gv_ao: mlirlite.GlobalVariable* = self.Gen.ExprGenAttr.find_global_in_module(mod, nm_ao.id)
|
||
if gv_ao is not None and gv_ao.Name is not None and gv_ao.Ty is not None: v_ao = self.Gen.ExprGenAttr.make_global_ref(pool, gv_ao.Name, gv_ao.Ty)
|
||
if v_ao is not None and v_ao.Ty is not None:
|
||
# Object-pointer variable (alloca is Ptr(Ptr(Struct))): the variable is the object, return the object pointer (load once), aligned with translate_c_addr.
|
||
match v_ao.Ty:
|
||
case mlirlite.LLVMType.Ptr(ao1):
|
||
match ao1:
|
||
case mlirlite.LLVMType.Ptr(ao2):
|
||
match ao2:
|
||
case mlirlite.LLVMType.Struct(_, _, _):
|
||
obj_ty_ao: mlirlite.LLVMType* = mlirlite.Ptr(pool, ao2)
|
||
return mlirlite.build_load(builder, obj_ty_ao, v_ao)
|
||
case _:
|
||
pass
|
||
case _:
|
||
pass
|
||
case _:
|
||
pass
|
||
return v_ao
|
||
# Module function address: the function symbol itself
|
||
if v_ao is None and mod is not None:
|
||
f_ao: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(mod, nm_ao.id)
|
||
if f_ao is not None:
|
||
fname_ao: str = mlirlite.function_get_name(f_ao)
|
||
if fname_ao is not None:
|
||
# The param chain is a Param chain but mlirlite.Func's type builder uses ParamNode -> rebuild via _FuncTyFromFunction (same as CallGenCBuiltin).
|
||
fn_ty_ao: mlirlite.LLVMType* = self.Gen.CallGenBase._FuncTyFromFunction(pool, f_ao)
|
||
# Quote the function reference name when needed (sha1-prefixed names starting with a digit or containing '.')
|
||
fnlen_ao: sizet = string.strlen(fname_ao)
|
||
needs_q_ao: int = self.Gen.ClassGenVTable._vtable_name_needs_quote(fname_ao)
|
||
fbuf_ao: str = pool.alloc(fnlen_ao + 4)
|
||
if fbuf_ao is not None and fn_ty_ao is not None:
|
||
if needs_q_ao != 0: viperlib.snprintf(fbuf_ao, fnlen_ao + 4, "@\"%s\"", fname_ao)
|
||
else: viperlib.snprintf(fbuf_ao, fnlen_ao + 4, "@%s", fname_ao)
|
||
return mlirlite.SSAValue(pool, mlirlite.Ptr(pool, fn_ty_ao), fbuf_ao)
|
||
elif uo.operand.kind() == ast.ASTKind.Attribute:
|
||
# Field address (GEP, no load)
|
||
fptr_ao: mlirlite.Value* = self.Gen.ExprGenAttr.get_attribute_ptr(builder, pool, mod, uo.operand, trans)
|
||
if fptr_ao is not None: return fptr_ao
|
||
elif uo.operand.kind() == ast.ASTKind.Subscript:
|
||
# Element address (GEP, no load)
|
||
sptr_ao: mlirlite.Value* = self.Gen.ExprGenName.get_subscript_ptr(builder, pool, mod, uo.operand, trans)
|
||
if sptr_ao is not None: return sptr_ao
|
||
elif uo.operand.kind() == ast.ASTKind.Call:
|
||
# &<call>() -> take the address of an rvalue. Reuse the already-evaluated operand (never re-evaluate:
|
||
# a ctor call would re-run __before_init__/__init__). See _addrof_of_call.
|
||
ac_ao: mlirlite.Value* = self._addrof_of_call(builder, pool, operand)
|
||
if ac_ao is not None: return ac_ao
|
||
self.Gen.TypeGenErr.fatal_error(node, "the operand of unary '&' address-of must be an lvalue (Name/Attribute/Subscript) or a constructor/function call result", "TypeError")
|
||
return None
|
||
return operand
|
||
|
||
def _addrof_of_call(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, call_val: mlirlite.Value*) -> mlirlite.Value*:
|
||
"""Return the address of a call result (&<call>()), reusing the evaluated value."""
|
||
if call_val is None or call_val.Ty is None: return None
|
||
if self.Gen.ExprGenBase.is_ptr_type(call_val.Ty) != 0 and call_val.Ty.Pointee is not None:
|
||
pointee_is_struct: int = 0
|
||
match call_val.Ty.Pointee:
|
||
case mlirlite.LLVMType.Struct(_, _, _):
|
||
pointee_is_struct = 1
|
||
case _:
|
||
pointee_is_struct = 0
|
||
if pointee_is_struct != 0: return call_val
|
||
tmp_slot_ao: mlirlite.Value* = mlirlite.build_alloca_at_entry(builder, call_val.Ty)
|
||
if tmp_slot_ao is None: return None
|
||
mlirlite.build_store(builder, call_val, tmp_slot_ao)
|
||
return tmp_slot_ao
|
||
# translate_boolop - BoolOp(op, values): short-circuit evaluation returning i1.
|
||
# and: a false short-circuits to merge (returns a's i1); or: a true short-circuits; the last value is evaluated then branches to merge; merge phis all i1 edges.
|
||
def translate_boolop(self, builder: mlirlite.IRBuilder*, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, node: ast.AST*, funcs_ptr: t.CPtr = None, func_count: int = 0, trans: HT.Translator* = None) -> mlirlite.Value*:
|
||
"""Translate boolean and/or with short-circuit evaluation, returning i1."""
|
||
bo: ast.BoolOp* = ast.BoolOp*(node)
|
||
if bo is None: return None
|
||
|
||
op: int = bo.op
|
||
values: list[ast.AST*]* = bo.values
|
||
if values is None: return None
|
||
count: sizet = len(values)
|
||
if count == 0: return None
|
||
if count == 1:
|
||
return self.Gen.ExprGen.translate_value(builder, pool, mod, values.get(0), funcs_ptr, func_count, trans)
|
||
|
||
func: mlirlite.Function* = builder.Func
|
||
if func is None: return None
|
||
|
||
# Create the merge BB (unique label from builder.Counter; position_at_end's move_to_end keeps BB text order matching control-flow order).
|
||
cnt: int = builder.Counter
|
||
builder.Counter = cnt + 1
|
||
name_buf: str = pool.alloc(32)
|
||
if name_buf is None: return None
|
||
viperlib.snprintf(name_buf, 32, "bool.merge.%d", cnt)
|
||
merge_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
|
||
# phi incoming list head/tail
|
||
phi_head: mlirlite.PhiIncoming* = None
|
||
phi_tail: mlirlite.PhiIncoming* = None
|
||
phi_count: int = int(count - 1) # loop iteration count
|
||
|
||
# Evaluate and short-circuit each value except the last
|
||
for i in range(int(count - 1)):
|
||
val_node: ast.AST* = values.get(i)
|
||
val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, val_node, funcs_ptr, func_count, trans)
|
||
if val is None: return None
|
||
|
||
# Convert to i1 (use directly if already i1; else coerce_cond_to_bool, preferring __bool__)
|
||
val_i1: mlirlite.Value* = self.coerce_cond_to_bool(pool, builder, mod, val, trans)
|
||
|
||
# Create the next BB (evaluate the next value)
|
||
cnt = builder.Counter
|
||
builder.Counter = cnt + 1
|
||
viperlib.snprintf(name_buf, 32, "bool.next.%d", cnt)
|
||
next_bb: mlirlite.BasicBlock* = mlirlite.create_block(pool, func, name_buf)
|
||
|
||
# and: false -> merge, true -> next; or: true -> merge, false -> next
|
||
if op == ast.OpKind.And: mlirlite.build_cond_br(builder, val_i1, next_bb, merge_bb)
|
||
else: mlirlite.build_cond_br(builder, val_i1, merge_bb, next_bb)
|
||
|
||
# Add the phi incoming edge: (val_i1, the BB holding cond_br)
|
||
cur_bb: mlirlite.BasicBlock* = builder.CurBlock
|
||
inc: mlirlite.PhiIncoming* = mlirlite.new_phi_incoming(pool, val_i1, cur_bb)
|
||
if phi_head is None:
|
||
phi_head = inc
|
||
phi_tail = inc
|
||
else:
|
||
phi_tail.Next = inc
|
||
phi_tail = inc
|
||
|
||
# Move to the next BB and evaluate the next value
|
||
mlirlite.position_at_end(builder, next_bb)
|
||
|
||
# Last value: evaluate then branch straight to merge
|
||
last_node: ast.AST* = values.get(count - 1)
|
||
last_val: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, last_node, funcs_ptr, func_count, trans)
|
||
if last_val is None: return None
|
||
last_i1: mlirlite.Value* = self.coerce_cond_to_bool(pool, builder, mod, last_val, trans)
|
||
|
||
mlirlite.build_br(builder, merge_bb)
|
||
|
||
cur_bb = builder.CurBlock
|
||
inc = mlirlite.new_phi_incoming(pool, last_i1, cur_bb)
|
||
if phi_head is None: phi_head = inc
|
||
else: phi_tail.Next = inc
|
||
phi_count += 1
|
||
|
||
# Move to merge and create the phi merging all incoming edges
|
||
mlirlite.position_at_end(builder, merge_bb)
|
||
i1_ty: mlirlite.LLVMType* = mlirlite.Int1(pool)
|
||
return mlirlite.build_phi(builder, i1_ty, phi_head, phi_count)
|
||
# emit_pow - `**` exponentiation for basic types (aligned with TPC ExprGen.emit_binary_op's Pow branch).
|
||
# Float -> llvm.pow; int -> compute in double and fptosi back when the exponent is a non-negative constant. Custom types go through __pow__.
|
||
def emit_pow(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, lhs: mlirlite.Value*, rhs: mlirlite.Value*, node: ast.AST* = None, trans: HT.Translator* = None) -> mlirlite.Value*:
|
||
"""Emit `**` exponentiation (basic types go through llvm.pow)."""
|
||
if lhs is None or rhs is None: return None
|
||
lhs_fbits: int = self.get_llvm_float_bits(lhs.Ty)
|
||
rhs_fbits: int = self.get_llvm_float_bits(rhs.Ty)
|
||
f64_ty: mlirlite.LLVMType* = mlirlite.Double(pool)
|
||
# Float path: either operand floating -> float result
|
||
if lhs_fbits != 0 or rhs_fbits != 0:
|
||
target_f: mlirlite.LLVMType* = f64_ty
|
||
if lhs_fbits == 32 and rhs_fbits == 32: target_f = mlirlite.Float(pool)
|
||
lf: mlirlite.Value* = self.coerce_to_type(builder, lhs, target_f, node, trans)
|
||
rf: mlirlite.Value* = self.coerce_to_type(builder, rhs, target_f, node, trans)
|
||
return self._call_llvm_pow(pool, builder, mod, lf, rf)
|
||
# Integer path: promote to double and compute
|
||
li: mlirlite.Value* = self.coerce_to_type(builder, lhs, f64_ty, node, trans)
|
||
ri: mlirlite.Value* = self.coerce_to_type(builder, rhs, f64_ty, node, trans)
|
||
res: mlirlite.Value* = self._call_llvm_pow(pool, builder, mod, li, ri)
|
||
if res is None: return None
|
||
# Non-negative integer exponent constant -> fptosi back to lhs type (Python int ** int = int)
|
||
rhs_bits: int = self.get_llvm_type_bits(rhs.Ty)
|
||
if rhs_bits != 0 and rhs.IsConst == 1 and rhs.IntVal >= 0: return self.coerce_to_type(builder, res, lhs.Ty, node, trans)
|
||
return res
|
||
|
||
def _call_llvm_pow(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, lf: mlirlite.Value*, rf: mlirlite.Value*) -> mlirlite.Value*:
|
||
"""Call the llvm.pow.f64 intrinsic and truncate an f32 result back to float."""
|
||
if lf is None or rf is None: return None
|
||
f64_ty: mlirlite.LLVMType* = mlirlite.Double(pool)
|
||
pow_fn: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(mod, "llvm.pow.f64")
|
||
if pow_fn is None:
|
||
pow_fn = mlirlite.create_declare(pool, mod, "llvm.pow.f64", f64_ty)
|
||
if pow_fn is not None:
|
||
mlirlite.add_param(pool, pow_fn, f64_ty, "x")
|
||
mlirlite.add_param(pool, pow_fn, f64_ty, "y")
|
||
lf_d: mlirlite.Value* = lf
|
||
if self.get_llvm_float_bits(lf.Ty) != 64: lf_d = mlirlite.build_fpext(builder, lf, f64_ty)
|
||
rf_d: mlirlite.Value* = rf
|
||
if self.get_llvm_float_bits(rf.Ty) != 64: rf_d = mlirlite.build_fpext(builder, rf, f64_ty)
|
||
if lf_d is None or rf_d is None: return None
|
||
lf_d.Next = rf_d
|
||
rf_d.Next = None
|
||
res: mlirlite.Value* = mlirlite.build_call(builder, "llvm.pow.f64", lf_d, 2, f64_ty, 0)
|
||
if res is None: return None
|
||
# f32 target: llvm.pow is always f64, truncate the result back to float
|
||
if self.get_llvm_float_bits(lf.Ty) == 32 and self.get_llvm_float_bits(rf.Ty) == 32: res = mlirlite.build_fptrunc(builder, res, mlirlite.Float(pool))
|
||
return res
|
||
|
||
# _binop_to_dunder - BinOp op -> dunder method name
|
||
# ============================================================
|
||
def _binop_to_dunder(self, op: int) -> str:
|
||
"""Map a BinOp operator to its dunder method name, or None if unmapped."""
|
||
if op == ast.OpKind.Add: return "__add__"
|
||
if op == ast.OpKind.Sub: return "__sub__"
|
||
if op == ast.OpKind.Mult: return "__mul__"
|
||
if op == ast.OpKind.Div: return "__truediv__"
|
||
if op == ast.OpKind.Mod: return "__mod__"
|
||
if op == ast.OpKind.Pow: return "__pow__"
|
||
if op == ast.OpKind.BitAnd: return "__and__"
|
||
if op == ast.OpKind.BitOr: return "__or__"
|
||
if op == ast.OpKind.BitXor: return "__xor__"
|
||
if op == ast.OpKind.LShift: return "__lshift__"
|
||
if op == ast.OpKind.RShift: return "__rshift__"
|
||
if op == ast.OpKind.FloorDiv: return "__floordiv__"
|
||
return None
|
||
# _rbinop_to_dunder - BinOp op -> reverse dunder name (__radd__ etc.): dispatch rhs's reverse dunder
|
||
# when lhs lacks the normal dunder (a + b -> b.__radd__(a) if a.__add__ is missing).
|
||
def _rbinop_to_dunder(self, op: int) -> str:
|
||
"""Map a BinOp operator to its reverse dunder method name, or None."""
|
||
if op == ast.OpKind.Add: return "__radd__"
|
||
if op == ast.OpKind.Sub: return "__rsub__"
|
||
if op == ast.OpKind.Mult: return "__rmul__"
|
||
if op == ast.OpKind.Div: return "__rtruediv__"
|
||
if op == ast.OpKind.FloorDiv: return "__rfloordiv__"
|
||
if op == ast.OpKind.Mod: return "__rmod__"
|
||
if op == ast.OpKind.Pow: return "__rpow__"
|
||
if op == ast.OpKind.BitAnd: return "__rand__"
|
||
if op == ast.OpKind.BitOr: return "__ror__"
|
||
if op == ast.OpKind.BitXor: return "__rxor__"
|
||
if op == ast.OpKind.LShift: return "__rlshift__"
|
||
if op == ast.OpKind.RShift: return "__rrshift__"
|
||
return None
|
||
# _cmpop_to_dunder - Compare op -> dunder method name
|
||
# ============================================================
|
||
def _cmpop_to_dunder(self, op: int) -> str:
|
||
"""Map a Compare operator to its dunder method name, or None if unmapped."""
|
||
if op == ast.OpKind.Eq: return "__eq__"
|
||
if op == ast.OpKind.Ne: return "__ne__"
|
||
if op == ast.OpKind.Lt: return "__lt__"
|
||
if op == ast.OpKind.Le: return "__le__"
|
||
if op == ast.OpKind.Gt: return "__gt__"
|
||
if op == ast.OpKind.Ge: return "__ge__"
|
||
return None
|
||
# _unaryop_to_dunder - UnaryOp op -> dunder method name
|
||
# ============================================================
|
||
def _unaryop_to_dunder(self, op: int) -> str:
|
||
"""Map a UnaryOp operator to its dunder method name, or None if unmapped."""
|
||
if op == ast.OpKind.USub: return "__neg__"
|
||
if op == ast.OpKind.UAdd: return "__pos__"
|
||
if op == ast.OpKind.Invert: return "__invert__"
|
||
return None
|
||
# try_operator_overload - attempt operator overloading. If lhs is a struct pointer, look up the dunder method
|
||
# in its class (including the inheritance chain) and emit lhs.__dunder__(rhs); otherwise return None to fall back.
|
||
def try_operator_overload(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, lhs: mlirlite.Value*, rhs: mlirlite.Value*, op: int, trans: HT.Translator*, is_compare: int) -> mlirlite.Value*:
|
||
"""Call lhs's dunder method when lhs is a struct pointer; else return None."""
|
||
if lhs is None or rhs is None: return None
|
||
# Map the operator to a dunder method name
|
||
dunder: str = None
|
||
if is_compare != 0: dunder = self._cmpop_to_dunder(op)
|
||
else: dunder = self._binop_to_dunder(op)
|
||
if dunder is None: return None
|
||
|
||
# Check whether lhs is a struct pointer
|
||
struct_ty: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type_from_value(lhs)
|
||
if struct_ty is None: return None
|
||
|
||
# Get the class name
|
||
class_name: str = self.Gen.StructGen.get_class_name_by_type(pool, struct_ty)
|
||
if class_name is None: return None
|
||
|
||
# Look up the dunder method in the inheritance chain
|
||
lookup_name: str = pool.alloc(128)
|
||
if lookup_name is None: return None
|
||
viperlib.snprintf(lookup_name, 128, "%s.%s", class_name, dunder)
|
||
found_func: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(mod, lookup_name)
|
||
|
||
search_class: str = class_name
|
||
if found_func is None:
|
||
# Multiple inheritance: walk the MRO ancestor chain (earlier-declared parent first)
|
||
mro_ptr: sizet* = self.Gen.StructGen.get_mro_chain(pool, class_name)
|
||
mi: int = 1
|
||
while mro_ptr is not None and mro_ptr[mi] != 0:
|
||
anc: str = rawptr(mro_ptr[mi])
|
||
if anc is not None:
|
||
parent_lookup: str = pool.alloc(128)
|
||
if parent_lookup is not None:
|
||
viperlib.snprintf(parent_lookup, 128, "%s.%s", anc, dunder)
|
||
parent_func: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(mod, parent_lookup)
|
||
if parent_func is not None:
|
||
found_func = parent_func
|
||
search_class = anc
|
||
break
|
||
mi += 1
|
||
|
||
# When the method is not in the current module, check whether the class (or a parent) has a SHA1 (stub may be
|
||
# uninjected). Prefer locating the entry by type pointer to get the SHA1.
|
||
cls_sha1: str = None
|
||
op_entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_type(struct_ty)
|
||
if op_entry is not None: cls_sha1 = op_entry.ModuleSha1
|
||
if cls_sha1 is None: cls_sha1 = self.Gen.StructGen.get_struct_sha1(search_class)
|
||
if cls_sha1 is None:
|
||
mro_p: sizet* = self.Gen.StructGen.get_mro_chain(pool, search_class)
|
||
mj: int = 1
|
||
while mro_p is not None and mro_p[mj] != 0 and cls_sha1 is None:
|
||
anc_p: str = rawptr(mro_p[mj])
|
||
if anc_p is not None: cls_sha1 = self.Gen.StructGen.get_struct_sha1(anc_p)
|
||
mj += 1
|
||
|
||
# Neither a function definition nor a SHA1 -> the class has no such dunder; return None to fall back to native.
|
||
if found_func is None and cls_sha1 is None: return None
|
||
|
||
# The method really does not exist in its defining module (same-module class without the dunder): return None
|
||
# and never emit a ghost call, which would leave a side-effecting `call void @...__div__` and double-emit sdiv.
|
||
if found_func is None and trans is not None and trans.ModuleSha1 is not None:
|
||
if cls_sha1 is not None and string.strcmp(cls_sha1, trans.ModuleSha1) == 0: return None
|
||
# Cross-module class: the stub may be uninjected, so method existence relies on the dependency module's vpi.
|
||
# A vpi miss falls back to native comparison (icmp); a ghost call would link as undefined (SysLibTest root cause).
|
||
vpi_dup: mlirlite.LLVMType* = self.Gen.CallGenBase._lookup_method_ret_ty_via_vpi(pool, cls_sha1, search_class, dunder)
|
||
if vpi_dup is None: return None
|
||
|
||
# Build the extra_args array (just the one rhs argument)
|
||
extra_args: sizet* = pool.alloc(8)
|
||
if extra_args is None: return None
|
||
extra_args[0] = sizet(rhs)
|
||
|
||
# Call the method: lhs.__dunder__(rhs)
|
||
return self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, search_class, dunder, lhs, extra_args, 1, trans)
|
||
# try_unary_operator_overload - attempt unary operator overloading. If operand is a struct pointer, look up
|
||
# __neg__/__pos__/__invert__ in its class (with inheritance) and emit operand.__dunder__(); else return None.
|
||
def try_unary_operator_overload(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, operand: mlirlite.Value*, op: int, trans: HT.Translator*) -> mlirlite.Value*:
|
||
"""Call operand's dunder method when operand is a struct pointer; else None."""
|
||
if operand is None: return None
|
||
dunder: str = self._unaryop_to_dunder(op)
|
||
if dunder is None: return None
|
||
return self._try_named_dunder(pool, builder, mod, operand, dunder, trans)
|
||
|
||
def _resolve_dunder_by_class(self, pool: memhub.MemBuddy*, mod: mlirlite.LLVMModule*, class_name: str, dunder: str, trans: HT.Translator*, struct_ty: mlirlite.LLVMType* = None) -> str:
|
||
"""Resolve the class (including MRO ancestors) that defines the dunder, or None."""
|
||
if class_name is None or dunder is None: return None
|
||
|
||
# Look up the dunder method in the inheritance chain
|
||
lookup_name: str = pool.alloc(128)
|
||
if lookup_name is None: return None
|
||
viperlib.snprintf(lookup_name, 128, "%s.%s", class_name, dunder)
|
||
found_func: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(mod, lookup_name)
|
||
|
||
search_class: str = class_name
|
||
if found_func is None:
|
||
# Multiple inheritance: walk the MRO ancestor chain (earlier-declared parent first)
|
||
mro_ptr: sizet* = self.Gen.StructGen.get_mro_chain(pool, class_name)
|
||
mi: int = 1
|
||
while mro_ptr is not None and mro_ptr[mi] != 0:
|
||
anc: str = rawptr(mro_ptr[mi])
|
||
if anc is not None:
|
||
parent_lookup: str = pool.alloc(128)
|
||
if parent_lookup is not None:
|
||
viperlib.snprintf(parent_lookup, 128, "%s.%s", anc, dunder)
|
||
parent_func: mlirlite.Function* = self.Gen.CallGenBase.find_func_in_module(mod, parent_lookup)
|
||
if parent_func is not None:
|
||
found_func = parent_func
|
||
search_class = anc
|
||
break
|
||
mi += 1
|
||
|
||
# When the method is not in the current module, check whether the class (or a parent) has a SHA1 (stub may be uninjected)
|
||
cls_sha1: str = None
|
||
if struct_ty is not None:
|
||
uop_entry: GenTypes.StructEntry* = self.Gen.StructGen.find_struct_by_type(struct_ty)
|
||
if uop_entry is not None: cls_sha1 = uop_entry.ModuleSha1
|
||
if cls_sha1 is None: cls_sha1 = self.Gen.StructGen.get_struct_sha1(search_class)
|
||
if cls_sha1 is None:
|
||
mro_p: sizet* = self.Gen.StructGen.get_mro_chain(pool, search_class)
|
||
mj: int = 1
|
||
while mro_p is not None and mro_p[mj] != 0 and cls_sha1 is None:
|
||
anc_p: str = rawptr(mro_p[mj])
|
||
if anc_p is not None: cls_sha1 = self.Gen.StructGen.get_struct_sha1(anc_p)
|
||
mj += 1
|
||
|
||
# Neither a function definition nor a SHA1 -> the class has no such dunder method
|
||
if found_func is None and cls_sha1 is None: return None
|
||
|
||
# Same-module class without the dunder: return None and fall back (consistent with try_operator_overload) to avoid a ghost call.
|
||
if found_func is None and trans is not None and trans.ModuleSha1 is not None:
|
||
if cls_sha1 is not None and string.strcmp(cls_sha1, trans.ModuleSha1) == 0: return None
|
||
# Cross-module class: existence relies on the dependency module's vpi; a vpi miss falls back (no ghost call, avoiding undefined).
|
||
if cls_sha1 is not None:
|
||
vpi_dup: mlirlite.LLVMType* = self.Gen.CallGenBase._lookup_method_ret_ty_via_vpi(pool, cls_sha1, search_class, dunder)
|
||
if vpi_dup is None: return None
|
||
return search_class
|
||
|
||
def _try_named_dunder(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, operand: mlirlite.Value*, dunder: str, trans: HT.Translator*, extra_args: sizet* = None, extra_count: int = 0) -> mlirlite.Value*:
|
||
"""Look up and call a dunder method by name on operand's class (including MRO)."""
|
||
if operand is None or dunder is None: return None
|
||
|
||
# Check whether operand is a struct pointer
|
||
struct_ty: mlirlite.LLVMType* = self.Gen.StructGen.get_struct_type_from_value(operand)
|
||
if struct_ty is None: return None
|
||
|
||
# Get the class name
|
||
class_name: str = self.Gen.StructGen.get_class_name_by_type(pool, struct_ty)
|
||
if class_name is None: return None
|
||
|
||
search_class: str = self._resolve_dunder_by_class(pool, mod, class_name, dunder, trans, struct_ty)
|
||
if search_class is None: return None
|
||
|
||
# Call the method: operand.__dunder__(extra_args...)
|
||
return self.Gen.CallGenCall._call_method_on_ptr(pool, builder, mod, search_class, dunder, operand, extra_args, extra_count, trans)
|
||
|
||
# coerce_cond_to_bool - normalize a condition value to i1 (the single implementation for if/while/and/or/ternary).
|
||
# Already-i1 returns as-is; __bool__ is called if defined; pointer != null; otherwise != 0.
|
||
def coerce_cond_to_bool(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, val: mlirlite.Value*, trans: HT.Translator*) -> mlirlite.Value*:
|
||
"""Normalize a condition to i1, preferring __bool__ and else comparing with 0/null."""
|
||
if val is None: return None
|
||
bits: int = self.get_llvm_type_bits(val.Ty)
|
||
if bits == 1: return val
|
||
# Class instance: prefer calling __bool__ (pointer to a struct whose class defines it)
|
||
bres: mlirlite.Value* = self._try_named_dunder(pool, builder, mod, val, "__bool__", trans)
|
||
if bres is not None:
|
||
b_bits: int = self.get_llvm_type_bits(bres.Ty)
|
||
if b_bits == 1: return bres
|
||
if self.is_ptr_type(bres.Ty) != 0:
|
||
null_b: mlirlite.Value* = mlirlite.ConstNull(pool, bres.Ty, "null")
|
||
return mlirlite.build_icmp(builder, mlirlite.ICMP_NE, bres, null_b)
|
||
zero_b: mlirlite.Value* = mlirlite.const_int32(pool, 0)
|
||
return mlirlite.build_icmp(builder, mlirlite.ICMP_NE, bres, zero_b)
|
||
if self.is_ptr_type(val.Ty) != 0:
|
||
# Pointer type: compare with null (not integer 0, which llc rejects as a type mismatch)
|
||
null_val: mlirlite.Value* = mlirlite.ConstNull(pool, val.Ty, "null")
|
||
return mlirlite.build_icmp(builder, mlirlite.ICMP_NE, val, null_val)
|
||
zero: mlirlite.Value* = mlirlite.const_int32(pool, 0)
|
||
return mlirlite.build_icmp(builder, mlirlite.ICMP_NE, val, zero)
|
||
# fail_if_double_ptr_binop - double-pointer arithmetic fail-fast (shared by BinOp and AugAssign). Fires when the op
|
||
# is arithmetic/bitwise/shift AND both operands are pointers; such IR (`add i8*, i8*`) is invalid, so fail before emitting.
|
||
def fail_if_double_ptr_binop(self, pool: memhub.MemBuddy*, op: int, lhs: mlirlite.Value*, rhs: mlirlite.Value*, node: ast.AST* = None, is_aug: int = 0) -> int:
|
||
"""Fail fast on double-pointer arithmetic; returns 0 when not triggered."""
|
||
if lhs is None or rhs is None: return 0
|
||
if self.is_ptr_type(lhs.Ty) == 0 or self.is_ptr_type(rhs.Ty) == 0: return 0
|
||
op_ch: str = None
|
||
if op == ast.OpKind.Add: op_ch = "+"
|
||
elif op == ast.OpKind.Sub: op_ch = "-"
|
||
elif op == ast.OpKind.Mult: op_ch = "*"
|
||
elif op == ast.OpKind.Div: op_ch = "/"
|
||
elif op == ast.OpKind.FloorDiv: op_ch = "//"
|
||
elif op == ast.OpKind.Mod: op_ch = "%"
|
||
elif op == ast.OpKind.Pow: op_ch = "**"
|
||
elif op == ast.OpKind.BitAnd: op_ch = "&"
|
||
elif op == ast.OpKind.BitOr: op_ch = "|"
|
||
elif op == ast.OpKind.BitXor: op_ch = "^"
|
||
elif op == ast.OpKind.LShift: op_ch = "<<"
|
||
elif op == ast.OpKind.RShift: op_ch = ">>"
|
||
else: return 0
|
||
op_lbl: str = op_ch
|
||
aug_note: str = ""
|
||
if is_aug != 0:
|
||
# Augmented assignment: build the "+=" form with an f-string (this language's str has no `+` concatenation).
|
||
op_lbl = f"{op_ch}="
|
||
aug_note = "(augmented assignment)"
|
||
lty_buf: str = pool.alloc_tagged(4096, memhub.DETSITE_EXPRGENOPS_1059)
|
||
rty_buf: str = pool.alloc_tagged(4096, memhub.DETSITE_EXPRGENOPS_1060)
|
||
lty_lbl: str = "?"
|
||
rty_lbl: str = "?"
|
||
if lty_buf is not None:
|
||
mlirlite.TypePrint(lty_buf, 4096, lhs.Ty, pool)
|
||
lty_lbl = lty_buf
|
||
if string.strcmp(lty_buf, "i8*") == 0: lty_lbl = "str(i8*)"
|
||
if rty_buf is not None:
|
||
mlirlite.TypePrint(rty_buf, 4096, rhs.Ty, pool)
|
||
rty_lbl = rty_buf
|
||
if string.strcmp(rty_buf, "i8*") == 0: rty_lbl = "str(i8*)"
|
||
col_s: int = 0
|
||
if node is not None: col_s = node.col_offset + 1
|
||
self.Gen.TypeGenErr.fatal_error(node, f"[VPC] illegal double-pointer arithmetic type: op = '{op_lbl}'{aug_note}, " f"left operand type = {lty_lbl}, right operand type = {rty_lbl}. " f"in this language str is i8*, with no `+` concatenation semantics (`str + str` and pointer +/- pointer " f"both degrade to raw pointer add/sub => illegal IR: opt reports " f"invalid operand type for instruction). " f"silent degradation is forbidden, compilation aborted. " f"fix: build strings with f-string interpolation, string.strcat, or viperlib.snprintf.", "TypeError", col_s, col_s)
|
||
return 1
|
||
# binop_needs_int_promotion - whether this op takes C-style integer promotion (to >= 32 bits)
|
||
def binop_needs_int_promotion(self, op: int) -> int:
|
||
"""C 语义整数提升的适用范围:**算术与移位**为 1(窄于 32 位先提升再算,避免 8/16 位回绕),
|
||
**位运算(& | ^)为 0**——按既有约定"两侧同宽就不提升"(WORD | WORD 保持 u16)。"""
|
||
if op == ast.OpKind.Add or op == ast.OpKind.Sub or op == ast.OpKind.Mult: return 1
|
||
if op == ast.OpKind.Div or op == ast.OpKind.FloorDiv or op == ast.OpKind.Mod: return 1
|
||
if op == ast.OpKind.LShift or op == ast.OpKind.RShift: return 1
|
||
return 0
|
||
|
||
# translate_binop - binary operations with automatic type promotion
|
||
# ============================================================
|
||
def translate_binop(self, pool: memhub.MemBuddy*, builder: mlirlite.IRBuilder*, mod: mlirlite.LLVMModule*, node: ast.AST*, trans: HT.Translator* = None) -> mlirlite.Value*:
|
||
"""Translate a binary operation with automatic type promotion."""
|
||
binop: ast.BinOp* = ast.BinOp*(node)
|
||
if binop is None: return None
|
||
|
||
lhs_node: ast.AST* = binop.left
|
||
rhs_node: ast.AST* = binop.right
|
||
op: int = binop.op
|
||
|
||
# Translate rhs first (only once, to avoid repeating side effects)
|
||
rhs: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, rhs_node, None, 0, trans)
|
||
if rhs is None: return None
|
||
|
||
# === Operator overload path 1: lhs is a Name bound to a struct variable ===
|
||
# For value-type variables (e.g. cnt: Counter) translate_value loads a Struct value, but dunder needs Ptr(Struct) as self; try the overload with the alloca pointer.
|
||
if lhs_node is not None and lhs_node.kind() == ast.ASTKind.Name and trans is not None:
|
||
nm: ast.Name* = ast.Name*(lhs_node)
|
||
if nm is not None and nm.id is not None:
|
||
lhs_alloca: mlirlite.Value* = self.Gen.VarGen.lookup_var(trans.SymTab, nm.id)
|
||
if lhs_alloca is not None:
|
||
ovl_self1: mlirlite.Value* = lhs_alloca
|
||
# When the alloca is Ptr(Ptr(Struct)) (an object storage slot), load the object pointer as self; using the
|
||
# slot address as self reads the object pointer as a field -> garbage (MultiInheritTest root cause).
|
||
if lhs_alloca.Ty is not None and lhs_alloca.Ty.Pointee != 0 and self.Gen.ExprGenBase.is_ptr_type(lhs_alloca.Ty.Pointee) != 0:
|
||
load_self1: mlirlite.Value* = mlirlite.build_load(builder, lhs_alloca.Ty.Pointee, lhs_alloca)
|
||
if load_self1 is not None: ovl_self1 = load_self1
|
||
ovl_result: mlirlite.Value* = self.try_operator_overload(pool, builder, mod, ovl_self1, rhs, op, trans, 0)
|
||
if ovl_result is not None: return ovl_result
|
||
|
||
# Translate lhs normally
|
||
lhs: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, lhs_node, None, 0, trans)
|
||
if lhs is None: return None
|
||
|
||
# === Operator overload path 2: lhs is Ptr(Struct) (e.g. after loading a Counter* variable) ===
|
||
ovl_result2: mlirlite.Value* = self.try_operator_overload(pool, builder, mod, lhs, rhs, op, trans, 0)
|
||
if ovl_result2 is not None: return ovl_result2
|
||
|
||
# === Operator overload path 3: reverse operator - dispatch rhs.__rX__(lhs) when lhs has no dunder ===
|
||
# (Python semantics: a + b -> a.__add__(b), else b.__radd__(a)). Skip early if rhs is not a struct.
|
||
r_dunder: str = self._rbinop_to_dunder(op)
|
||
if r_dunder is not None:
|
||
if self.Gen.StructGen.get_struct_type_from_value(rhs) is not None:
|
||
rev_extra: sizet* = pool.alloc(8)
|
||
if rev_extra is not None:
|
||
rev_extra[0] = sizet(lhs)
|
||
r_ovl: mlirlite.Value* = self._try_named_dunder(pool, builder, mod, rhs, r_dunder, trans, rev_extra, 1)
|
||
if r_ovl is not None: return r_ovl
|
||
|
||
# `**` exponentiation: basic types -> llvm.pow (int result written back per the exponent constant). Handled before the float/int split and after overloads.
|
||
if op == ast.OpKind.Pow: return self.emit_pow(pool, builder, mod, lhs, rhs, node, trans)
|
||
|
||
# Fetch operand type information
|
||
lhs_bits: int = self.get_llvm_type_bits(lhs.Ty)
|
||
rhs_bits: int = self.get_llvm_type_bits(rhs.Ty)
|
||
lhs_fbits: int = self.get_llvm_float_bits(lhs.Ty)
|
||
rhs_fbits: int = self.get_llvm_float_bits(rhs.Ty)
|
||
|
||
# Float operation: use float instructions when either operand is float (must precede pointer arithmetic, since float has 0 int bits and would be mistaken for a pointer).
|
||
if lhs_fbits != 0 or rhs_fbits != 0:
|
||
# Determine the target float type (use the larger bit width)
|
||
target_fbits: int = lhs_fbits
|
||
if rhs_fbits > target_fbits: target_fbits = rhs_fbits
|
||
target_float_ty: mlirlite.LLVMType* = mlirlite.Double(pool)
|
||
if target_fbits == 32: target_float_ty = mlirlite.Float(pool)
|
||
# Convert both operands to the target float type (int->float via si2fp, float->float via fpext/fptrunc)
|
||
lhs = self.coerce_to_type(builder, lhs, target_float_ty, node)
|
||
rhs = self.coerce_to_type(builder, rhs, target_float_ty, node)
|
||
if op == ast.OpKind.Add: return mlirlite.build_fadd(builder, lhs, rhs)
|
||
if op == ast.OpKind.Sub: return mlirlite.build_fsub(builder, lhs, rhs)
|
||
if op == ast.OpKind.Mult: return mlirlite.build_fmul(builder, lhs, rhs)
|
||
if op == ast.OpKind.Div: return mlirlite.build_fdiv(builder, lhs, rhs)
|
||
if op == ast.OpKind.Mod: return mlirlite.build_frem(builder, lhs, rhs)
|
||
return None
|
||
|
||
# Pointer arithmetic: ptr + int / ptr - int / int + ptr -> getelementptr (scaled by element size). int - ptr has no GEP form (illegal in C).
|
||
if (lhs_bits == 0 and rhs_bits != 0) or (lhs_bits != 0 and rhs_bits == 0):
|
||
if op == ast.OpKind.Add:
|
||
if lhs_bits == 0: return self.translate_ptr_arith(pool, builder, lhs, rhs, op)
|
||
# int + ptr (swap operands)
|
||
return self.translate_ptr_arith(pool, builder, rhs, lhs, op)
|
||
if op == ast.OpKind.Sub and lhs_bits == 0: return self.translate_ptr_arith(pool, builder, lhs, rhs, op)
|
||
|
||
# Double-pointer arithmetic (including AugAssign) fail-fast: the single implementation is fail_if_double_ptr_binop.
|
||
# Pointer-int (GEP) is handled by translate_ptr_arith above; double-pointer Add/Sub/... is illegal here (str is i8*).
|
||
self.fail_if_double_ptr_binop(pool, op, lhs, rhs, node, 0)
|
||
|
||
# Widening direction (zext/sext) and the div/mod predicates both need "is the operand unsigned"; evaluate once here and thread trans down.
|
||
binop_unsigned: int = 0
|
||
if lhs_node is not None:
|
||
if self._node_type_unsigned(pool, lhs_node, trans) != 0: binop_unsigned = 1
|
||
if binop_unsigned == 0 and rhs_node is not None:
|
||
if self._node_type_unsigned(pool, rhs_node, trans) != 0: binop_unsigned = 1
|
||
|
||
# C 语义整数提升(仅算术/移位;位运算 & | ^ 保持"同宽不提升"):
|
||
# 两侧都窄于 32 位时先提升到 32 位再算,避免 8/16 位回绕(如两个 16 位 define 的
|
||
# `MAX_INCLUDES * MAX_REL_PATH_LEN` 会算成 0)。提升后把 lhs_bits/rhs_bits 记为 32,
|
||
# 让下面的等宽化逻辑不再重复处理(否则会对同宽 i32 再做一次无意义转换)。
|
||
if self.binop_needs_int_promotion(op) != 0:
|
||
if lhs_bits != 0 and rhs_bits != 0 and lhs_bits < 32 and rhs_bits < 32:
|
||
i32_ty_pr: mlirlite.LLVMType* = mlirlite.Int32(pool)
|
||
# 加宽方向与下面的等宽化同一判据(i1/i8 默认 zext、带符号注解 sext、无符号 typedef zext);
|
||
# probe 只是给 should_zext_widen 提供"宽侧类型",其常量值不参与判据。
|
||
i32_probe: mlirlite.Value* = mlirlite.const_int32(pool, 0)
|
||
if self.should_zext_widen(pool, lhs, i32_probe, lhs_node, trans) != 0: lhs = mlirlite.build_zext(builder, lhs, i32_ty_pr)
|
||
else: lhs = self.coerce_to_type(builder, lhs, i32_ty_pr, node, trans)
|
||
if self.should_zext_widen(pool, rhs, i32_probe, rhs_node, trans) != 0: rhs = mlirlite.build_zext(builder, rhs, i32_ty_pr)
|
||
else: rhs = self.coerce_to_type(builder, rhs, i32_ty_pr, node, trans)
|
||
lhs_bits = 32
|
||
rhs_bits = 32
|
||
|
||
if lhs_bits > rhs_bits:
|
||
# 宽侧是「值能无损落进窄侧」的编译期常量时,把**常量**降到窄侧类型(C 语义:常量优先适配另一侧),
|
||
# 否则 `crc(u32) ^ 0xFFFFFFFF(i64)` 会把 crc 抬成 64 位,结果再赋回 32 位时报隐式窄化。
|
||
if self._const_fits_int_type(lhs, rhs.Ty) != 0: lhs = self.coerce_to_type(builder, lhs, rhs.Ty, node, trans)
|
||
elif self.should_zext_widen(pool, rhs, lhs, rhs_node, trans) != 0: rhs = mlirlite.build_zext(builder, rhs, lhs.Ty)
|
||
else: rhs = self.coerce_to_type(builder, rhs, lhs.Ty, node, trans)
|
||
elif rhs_bits > lhs_bits:
|
||
if self._const_fits_int_type(rhs, lhs.Ty) != 0: rhs = self.coerce_to_type(builder, rhs, lhs.Ty, node, trans)
|
||
elif self.should_zext_widen(pool, lhs, rhs, lhs_node, trans) != 0: lhs = mlirlite.build_zext(builder, lhs, rhs.Ty)
|
||
else: lhs = self.coerce_to_type(builder, lhs, rhs.Ty, node, trans)
|
||
|
||
# Bitwise type safety: when both sides are pointers (lhs_bits==0 and rhs_bits==0), bitwise ops are illegal (LLVM
|
||
# requires integer operands). Common trigger: a failed CDefine lookup returns pointer null, then `|` with a pointer.
|
||
if lhs_bits == 0 and rhs_bits == 0:
|
||
if op == ast.OpKind.BitOr or op == ast.OpKind.BitAnd or op == ast.OpKind.BitXor or op == ast.OpKind.LShift or op == ast.OpKind.RShift:
|
||
# fail-fast: illegal bitwise operand types; abort compilation (expose the upstream root cause)
|
||
self.Gen.TypeGenErr.fatal_error(node, "illegal bitwise operand types: both lhs/rhs are non-integer types (pointer or struct), cannot perform bitwise operations", "TypeError")
|
||
|
||
# 移位:计数静态可确定时做范围检查(与 AugAssign 的 <<= / >>= 共用);
|
||
# 计数宽度本身豁免,按提升/等宽化后的左操作数有效位宽比较(C 语义下 u8 << 8 合法)。
|
||
if op == ast.OpKind.LShift or op == ast.OpKind.RShift:
|
||
self.Gen.AssignGen.require_shift_count_in_range(pool, trans, node, rhs_node, self.get_llvm_type_bits(lhs.Ty))
|
||
|
||
if op == ast.OpKind.Add: return mlirlite.build_add(builder, lhs, rhs)
|
||
elif op == ast.OpKind.Sub: return mlirlite.build_sub(builder, lhs, rhs)
|
||
elif op == ast.OpKind.Mult: return mlirlite.build_mul(builder, lhs, rhs)
|
||
elif op == ast.OpKind.Div:
|
||
# Unsigned operands -> udiv (binop_unsigned from _node_type_unsigned, covering sizet/uint64 and annotated
|
||
# variable/array elements). An unconditional sdiv gave wrong results for unsigned values >= 2^63.
|
||
if binop_unsigned != 0: return mlirlite.build_udiv(builder, lhs, rhs)
|
||
return mlirlite.build_sdiv(builder, lhs, rhs)
|
||
elif op == ast.OpKind.FloorDiv:
|
||
if binop_unsigned != 0: return mlirlite.build_udiv(builder, lhs, rhs)
|
||
return mlirlite.build_sdiv(builder, lhs, rhs)
|
||
elif op == ast.OpKind.Mod:
|
||
if binop_unsigned != 0: return mlirlite.build_urem(builder, lhs, rhs)
|
||
return mlirlite.build_srem(builder, lhs, rhs)
|
||
elif op == ast.OpKind.BitAnd: return mlirlite.build_and(builder, lhs, rhs)
|
||
elif op == ast.OpKind.BitOr: return mlirlite.build_or(builder, lhs, rhs)
|
||
elif op == ast.OpKind.BitXor: return mlirlite.build_xor(builder, lhs, rhs)
|
||
elif op == ast.OpKind.LShift: return mlirlite.build_shl(builder, lhs, rhs)
|
||
elif op == ast.OpKind.RShift:
|
||
# Unsigned >> -> lshr, signed >> -> ashr (aligned with TPC ExprGen.emit_binary_op: i8/i16 default unsigned, others check operand signedness).
|
||
if lhs_bits == 8 or lhs_bits == 16: return mlirlite.build_lshr(builder, lhs, rhs)
|
||
if self._node_type_unsigned(pool, lhs_node, trans) != 0: return mlirlite.build_lshr(builder, lhs, rhs)
|
||
return mlirlite.build_ashr(builder, lhs, rhs)
|
||
return None |