1510 lines
103 KiB
Plaintext
1510 lines
103 KiB
Plaintext
import lib.core.Gen.GenTypes as GenTypes
|
||
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:
|
||
# 宽侧是「能无损落进窄侧」的编译期常量时,把**常量**降到窄侧(与二元运算同一口径),
|
||
# 避免 `tab[..] if c else '='`(i8 元素 vs i32 字符字面量)被抬成 i32、再赋回 i8 时误报窄化。
|
||
if self._const_fits_int_type(body_val, orelse_val.Ty) != 0:
|
||
mlirlite.position_at_end(builder, then_blk_for_phi)
|
||
body_val = self.coerce_to_type(builder, body_val, orelse_val.Ty, node)
|
||
else:
|
||
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:
|
||
# 同口径:宽侧(orelse)是能落进窄侧的常量时,把它降下来(在 else 块内完成强转)
|
||
if self._const_fits_int_type(orelse_val, body_val.Ty) != 0:
|
||
mlirlite.position_at_end(builder, else_blk_for_phi)
|
||
orelse_val = self.coerce_to_type(builder, orelse_val, body_val.Ty, node)
|
||
else:
|
||
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
|
||
# 编译期 CDefine 常量(裸名 `N` 或模块属性 `mod.N`)单独判定,必须**早于**下方「i8 默认 zext」:
|
||
# · 负值 → sext(有符号语义,如 `define Z_DEFAULT_COMPRESSION = (-1)` 必须变 i32 -1,而非 255);
|
||
# · 非负 → zext(无符号位模式,如 `define WINDOW_SIZE = (1 << 15)` 必须变 32768,而非 -32768)。
|
||
# 否则这两类常量拓宽时必有一类被弄错(i8 位型走「默认 zext」;i16 位型若判不出无符号则走 sext)。
|
||
if narrow_node is not None:
|
||
cd_v_zw: int64 = self._cdefine_int_of(narrow_node)
|
||
if self.Gen.TypeGenCDef.is_cdefine_found() != 0:
|
||
if cd_v_zw < 0: return 0
|
||
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 non-negative CDefine whose minimal width's high bit is set (0x80000000 in 32 bits, or
|
||
# 50000 in 16 bits) is materialized as an unsigned bit pattern -> zext. Covered by
|
||
# _node_type_unsigned below (which delegates to _cdefine_unsigned_bitpattern); keep return 0.
|
||
return 0
|
||
# _cdefine_int_of - 取节点引用的 CDefine 常量值。支持两种引用形态:
|
||
# · 裸名 `N`
|
||
# · 模块属性 `mod.N`(跨模块经 .vpi 注册;常量名就是 at.attr)
|
||
# 未命中时 `Gen.TypeGenCDef.is_cdefine_found()` 为 0 —— 本函数每次都刷新该标志(空名也走一次 lookup)。
|
||
def _cdefine_int_of(self, node: ast.AST*) -> int64:
|
||
"""CDefine integer value referenced by node (bare name or `mod.N`); check is_cdefine_found()."""
|
||
nm_cd: str = ""
|
||
if node is not None:
|
||
if node.kind() == ast.ASTKind.Name:
|
||
n_cd: ast.Name* = ast.Name*(node)
|
||
if n_cd.id is not None: nm_cd = n_cd.id
|
||
elif node.kind() == ast.ASTKind.Attribute:
|
||
a_cd: ast.Attribute* = ast.Attribute*(node)
|
||
if a_cd.attr is not None: nm_cd = a_cd.attr
|
||
return self.Gen.TypeGenCDef.lookup_cdefine_constant(nm_cd)
|
||
# _cdefine_unsigned_bitpattern - 1 if node 是**非负 CDefine 常量**,且按它的最小可容纳位宽 W_min
|
||
# 取无符号位模式时 high bit 已置位(如 50000 → W_min=16 → 0xC350)。规范:非负数按无符号位模式
|
||
# 物化、负值按有符号解释(Debugger/define-constants.md §7.2)。这类常量在**宽化**与**比较**
|
||
# (for-range 边界)时必须按无符号处理,否则 32768..65535 的常量会被读成负数(range 变 0 次)。
|
||
def _cdefine_unsigned_bitpattern(self, node: ast.AST*) -> int:
|
||
"""1 = node is a non-negative CDefine whose minimal width's high bit is set; else 0."""
|
||
v_cd: int64 = self._cdefine_int_of(node)
|
||
if self.Gen.TypeGenCDef.is_cdefine_found() == 0: return 0
|
||
if v_cd < 0: return 0
|
||
w_cd: int = self.Gen.AssignGen.define_min_width(v_cd)
|
||
# 该位宽的有符号上限 2^(w-1)-1;超过即 high bit 置位(只写安全字面量,避免 1<<63 / 4294967295 之类)
|
||
if w_cd == 8:
|
||
if v_cd > 127: return 1
|
||
elif w_cd == 16:
|
||
if v_cd > 32767: return 1
|
||
elif w_cd == 32:
|
||
if v_cd > 2147483647: 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: return 0
|
||
# Plain function call: widen by the callee's **declared return type**. Without this,
|
||
# `uint64(f())` with `def f() -> uint32` emitted `sext i32 -> i64` (verified in the
|
||
# generated IR), so a u32 with bit 31 set grew 0xFFFFFFFF in the high half — a real
|
||
# value 0x88691F61 became 0xFFFFFFFF88691F61 (stackmap pointers were mangled this way).
|
||
if cl.func is not None and cl.func.kind() == ast.ASTKind.Name:
|
||
func_nm_u: ast.Name* = ast.Name*(cl.func)
|
||
if func_nm_u.id is not None:
|
||
fe_fn_u: GenTypes.FuncEntry* = self.Gen.CallGenLeafKit.find_func_entry_global(func_nm_u.id)
|
||
if fe_fn_u is not None and fe_fn_u.Spec is not None:
|
||
if TSpec.SpecIsUnsigned(fe_fn_u.Spec.Ret) != 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
|
||
# 3) 非负 CDefine 常量:按最小可容纳位宽取无符号位模式(Debugger/define-constants.md §7.2)。
|
||
# 例:define N = 50000 → W_min=16、值在 32768..65535 ⇒ 宽化/比较必须按无符号。
|
||
if self._cdefine_unsigned_bitpattern(node) != 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): decide unsignedness from the
|
||
# *field's* annotation, resolved through the receiver's struct class name. The receiver is not
|
||
# limited to a plain variable name: it may be a nested field (a.b.f), an index/deref
|
||
# (arr[i].f / ptr[i].f) or a module-qualified global (pkg.mod.g[i].f). Read-only; 0 = unresolved.
|
||
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 = self._attr_receiver_class_name(pool, at_u.value, trans)
|
||
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
|
||
# _attr_receiver_class_name - struct class name of an attribute receiver expression. Resolves by the
|
||
# receiver's *type*, not by shape: variable/field annotation class name (Name / nested Attribute), the
|
||
# same resolution one array/pointer level down for an index/deref base (arr[i] / ptr[i]), or the global's
|
||
# element type for a module-qualified global (Mod.g / pkg.mod.g). None when unresolved.
|
||
def _attr_receiver_class_name(self, pool: memhub.MemBuddy*, node: ast.AST*, trans: HT.Translator*) -> str:
|
||
"""Struct class name of an attribute receiver (Name / Attribute / Subscript); None if unresolved."""
|
||
if node is None or trans is None: return None
|
||
k: int = node.kind()
|
||
if k == ast.ASTKind.Subscript:
|
||
# arr[i].f / ptr[i].f: the element sits one array/pointer level below the base, and the base's
|
||
# class-name resolution already denotes that element struct (the levels are transparent here),
|
||
# so resolve the subscript base directly.
|
||
sub_rc: ast.Subscript* = ast.Subscript*(node)
|
||
if sub_rc.value is None: return None
|
||
return self._attr_receiver_class_name(pool, sub_rc.value, trans)
|
||
if k == ast.ASTKind.Attribute:
|
||
# Module-qualified global (Mod.g / pkg.mod.g): the class name comes from the global's element type.
|
||
gc_rc: str = self._module_global_element_class(pool, trans, ast.Attribute*(node))
|
||
if gc_rc is not None: return gc_rc
|
||
# Object field chain (a.b.f): the field's annotation class name (recursive).
|
||
return self.Gen.ExprGenAttr._resolve_attr_field_class(pool, trans, node, 1)
|
||
if k == ast.ASTKind.Name:
|
||
nm_rc: ast.Name* = ast.Name*(node)
|
||
if nm_rc.id is None: return None
|
||
ve_rc: GenTypes.VarEntry* = self.Gen.VarGen.lookup_var_entry(trans.SymTab, nm_rc.id)
|
||
if ve_rc is None: return None
|
||
return self.Gen.VarGen.var_annot_class_name(ve_rc)
|
||
return None
|
||
# _module_global_element_class - element class name of a module-qualified global `Mod.g`. The module path
|
||
# may be multi-component (e.g. `intr.idt.idt`: package intr -> submodule idt -> global idt). Mirrors
|
||
# ExprGenAttr._resolve_module_global_class_name but accepts a dotted module path and also unwraps an array
|
||
# element, so a module-global array such as `idt: [256]idt_entry` resolves to 'idt_entry'. None otherwise.
|
||
def _module_global_element_class(self, pool: memhub.MemBuddy*, trans: HT.Translator*, at: ast.Attribute*) -> str:
|
||
"""Element class name of the module-qualified global `at.value.at.attr`; None when not a global."""
|
||
if at is None or at.value is None or at.attr is None or trans is None: return None
|
||
if trans._imported_modules is None or trans.Module is None: return None
|
||
mod_path: str = self.Gen.ExprGenAttr._attr_module_path_str(pool, at.value)
|
||
if mod_path is None: return None
|
||
# restore import aliases (import X.Y as Z) so the import/SHA1 checks resolve
|
||
if trans._from_imports is not None:
|
||
orig_mg: str = self.Gen.ImportsGen.lookup_from_import(trans._from_imports, mod_path, 0)
|
||
if orig_mg is not None:
|
||
oml_mg: sizet = 0
|
||
while orig_mg[oml_mg] != ' ' and orig_mg[oml_mg] != '\0' and orig_mg[oml_mg] != ':': oml_mg += 1
|
||
if oml_mg > 0:
|
||
om_buf_mg: str = pool.alloc(oml_mg + 1)
|
||
if om_buf_mg is not None:
|
||
string.strncpy(om_buf_mg, orig_mg, oml_mg)
|
||
om_buf_mg[oml_mg] = '\0'
|
||
mod_path = om_buf_mg
|
||
# package submodules (`intr.idt`) count as known when the package is imported
|
||
if self.Gen.ImportsGen.is_module_imported_or_known(trans._imported_modules, mod_path) == 0: return None
|
||
gv: mlirlite.GlobalVariable* = self.Gen.ExprGenAttr.find_global_in_module(trans.Module, self.Gen.FuncGenBase._mangle_name_with_sha1(self.Gen.ExprGenAttr._lookup_module_sha1(mod_path), at.attr))
|
||
if gv is None: gv = self.Gen.ExprGenAttr.find_global_in_module(trans.Module, at.attr)
|
||
if gv is None or gv.Ty is None: return None
|
||
# a global is usually `Element*` / `Element**` or an array `[N]Element`: descend to the leaf type
|
||
# and take its registered struct class name.
|
||
elem_ty: mlirlite.LLVMType* = gv.Ty
|
||
lvl_mg: int = 0
|
||
while elem_ty is not None and lvl_mg < 4:
|
||
match elem_ty:
|
||
case mlirlite.LLVMType.Ptr(inner_mg): elem_ty = inner_mg
|
||
case mlirlite.LLVMType.Array(elem_mg, _): elem_ty = elem_mg
|
||
case _: return self.Gen.StructGen.get_class_name_by_type(pool, elem_ty)
|
||
lvl_mg += 1
|
||
return None
|
||
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: 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, target_ty: mlirlite.LLVMType* = 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
|
||
|
||
# 一元 +/-/~ 的整数提升按**符号性**分档(见 wiki/Implicit.md):
|
||
# · **u 类(无符号)不提升**:保持原位宽(`~uint16` 仍是 uint16、`-uint8` 在 u8 内取反),
|
||
# 位图/掩码写法因此能「同宽运算」;
|
||
# · **i 类(有符号)提升到 i32**:防负数陷阱(如 int8 -128 取负会溢出,提升后 = 128)。
|
||
# 未标注符号时按 i 类处理(保守,守住负数陷阱)。`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_is_uns: int = self._node_type_unsigned(pool, uo.operand, trans)
|
||
if u_is_uns == 0:
|
||
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:
|
||
# 目标类型是 32 位浮点(`float(-3.5)` / `x: float = -3.5`)→ 直接出 f32 常量,
|
||
# 不再"f64 常量 + fptrunc"(与 ExprGenConst.translate_constant 的浮点字面量同口径)。
|
||
# 位宽安全:ConstFloat 的十六进制位数只由 MlirTypeText(ty) 决定(与 MLIR 渲染器
|
||
# 取类型文本同一个函数),故不会再出现"f32 类型配 16 位文本"那类位宽错配。
|
||
if target_ty is not None and self.get_llvm_float_bits(target_ty) == 32:
|
||
return mlirlite.ConstFloat(pool, target_ty, -operand.FloatVal)
|
||
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;0 常量必须与**当前**操作数同宽(u 类不提升后可能是 i8/i16/i32/i64)
|
||
zero: mlirlite.Value* = mlirlite.const_int32(pool, 0)
|
||
if operand_bits != 0 and operand_bits != 32: zero = mlirlite.const_int(pool, operand_bits, 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)
|
||
# -1 掩码必须与**当前**操作数同宽(u 类不提升后可能是 i8/i16/i32/i64)
|
||
neg1: mlirlite.Value* = mlirlite.const_int32(pool, -1)
|
||
if operand_bits_inv != 0 and operand_bits_inv != 32: neg1 = mlirlite.const_int(pool, operand_bits_inv, -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 mlirlite.LLVMType.Func(_, _, _):
|
||
# 函数指针变量(嵌套函数名在符号表里是 Ptr(Func)):变量本身就是
|
||
# 那个函数地址,`&f` 应给出它的值 Ptr(Func)(一级),而不是变量
|
||
# 地址 Ptr(Ptr(Func))(二级)——否则传给 callback: rawptr 会因
|
||
# 级数不符报 CastError(source level 2 -> target level 1)。
|
||
fp_ty_ao: mlirlite.LLVMType* = mlirlite.Ptr(pool, ao2)
|
||
return mlirlite.build_load(builder, fp_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: rawptr = 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_work_bits - 二元运算的「工作位宽」(**不做 C 式整数提升**)
|
||
# 本语言取消了 C 的「小整型先提升到 int(i32)」语义:i8/i16 不再预先抬到 32 位再算。
|
||
# · 有上下文目标位宽(赋值/形参/返回目标)→ 以它为基础;
|
||
# · 无目标 → 取两侧操作数位宽的较大者,等宽化逻辑随后把**较小**的一侧拓宽到此位宽(结果位宽 = 较大者);
|
||
# · 移位 → **计数(rhs)不代表结果位宽**,不参与比较;结果位宽 = 左操作数位宽(无目标时)。
|
||
# 任一侧位宽为 0(未知/未定)会天然退化为另一侧(0 不抬高 max);只有连目标与两侧位宽**全都缺失**
|
||
# (类型信息完全缺失)时才回退到 32 —— 这只是可编译性兜底,不代表"提升"。
|
||
# 注意:本 helper 的 max 不含「常量豁免」——「能无损落进另一侧位宽的常量不抬高工作位宽」这条规则
|
||
# 由等宽化处的 _const_operand_demotes 实施(见下方),因为本语言非移位运算的实际结果位宽就是
|
||
# 「两侧中较宽的一侧」,等宽化处才是决定点;移位本就不让计数参与,故与此无关。
|
||
def _binop_work_bits(self, op: int, tgt_bits: int, lhs_bits: int, rhs_bits: int) -> int:
|
||
"""Working bit width for translate_binop (no C-style integer promotion)."""
|
||
w: int = tgt_bits
|
||
if lhs_bits > w: w = lhs_bits
|
||
if op != ast.OpKind.LShift and op != ast.OpKind.RShift:
|
||
if rhs_bits > w: w = rhs_bits
|
||
if w == 0: w = 32
|
||
return w
|
||
|
||
# _const_operand_demotes - 位宽上更宽的一侧是编译期常量时,它能否**降为另一侧位宽**,
|
||
# 从而不抬高运算的工作位宽(常量适配的唯一收口处,位运算与算术共用)。
|
||
# 用户规则:常量应隐式适配另一侧位宽——`baseport(u16) + 7` 的 `7` 默认是 i32,但应适配 u16,
|
||
# 使整个加法保持在 u16(结果位宽 16),而不是把 u16 变量抬到 i32 后在赋值/传参处报隐式窄化。
|
||
# 判定:
|
||
# 1) 常量值必须能**无损**落进另一侧位宽(复用 _const_fits_int_type);否则常量照常计入 max
|
||
# (如 `u8 变量 + 0x1234`:0x1234 落不进 u8,仍把变量拓宽到 i32,该报窄化就照旧报)。
|
||
# 2) 位运算 & | ^:无进位、结果不会超出参与值位宽,常量总能无损降——既有行为,保持不变。
|
||
# 3) 算术 + - * / %:另一侧也是常量时**不降**——`(MEMBUDDY_MAX_ORDERS + 1) * 8` 这类窄常量
|
||
# 乘积若逐个降宽会在 i8 回绕(老 bug:`i8(32)+1 → 33`、`×8` 溢出成 8,见 includes/memhub.vp),
|
||
# 必须保持「不降宽」的既有行为。
|
||
# 4) 算术:常量位宽不高于目标位宽时也不降——此时 max(目标位宽, 另一侧位宽) 已不小于常量位宽,
|
||
# 常量并没有抬高工作位宽(见 _binop_work_bits),无需也不应改变结果。
|
||
def _const_operand_demotes(self, is_bitwise: int, const_val: mlirlite.Value*, const_bits: int, other_val: mlirlite.Value*, other_ty: mlirlite.LLVMType*, tgt_bits: int) -> int:
|
||
"""1 = the wider-side constant may be demoted to the other side's type (no work-width raise)."""
|
||
if const_val is None or other_val is None: return 0
|
||
if const_val.IsConst != 1: return 0
|
||
if self._const_fits_int_type(const_val, other_ty) == 0: return 0
|
||
if is_bitwise != 0: return 1
|
||
if other_val.IsConst == 1: return 0
|
||
if const_bits <= tgt_bits: return 0
|
||
return 1
|
||
|
||
# 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, target_ty: mlirlite.LLVMType* = 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).
|
||
# target_ty 下传:内层运算(含移位)据此采用「目标感知工作位宽」。
|
||
rhs: mlirlite.Value* = self.Gen.ExprGen.translate_value(builder, pool, mod, rhs_node, None, 0, trans, target_ty)
|
||
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, target_ty)
|
||
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)
|
||
|
||
# 纯常量整型算式(两侧都是编译期整型常量):按**精确值**折叠并按值定宽,
|
||
# 禁止按操作数的最小宽度回绕(现场:`1024 * 1024 * 4` 按 i16 算成 0 ⇒
|
||
# WINBUF_MAX_BYTES=0 ⇒ 窗口缓冲只剩几字节 ⇒ 内核 memcpy 240KB 越界读/三重故障)。
|
||
if lhs_bits > 0 and rhs_bits > 0 and lhs.IsConst == 1 and rhs.IsConst == 1:
|
||
cf_ok: int = 1
|
||
cf_v: int64 = 0
|
||
if op == ast.OpKind.Add: cf_v = lhs.IntVal + rhs.IntVal
|
||
elif op == ast.OpKind.Sub: cf_v = lhs.IntVal - rhs.IntVal
|
||
elif op == ast.OpKind.Mult: cf_v = lhs.IntVal * rhs.IntVal
|
||
elif op == ast.OpKind.BitOr: cf_v = lhs.IntVal | rhs.IntVal
|
||
elif op == ast.OpKind.BitAnd: cf_v = lhs.IntVal & rhs.IntVal
|
||
elif op == ast.OpKind.BitXor: cf_v = lhs.IntVal ^ rhs.IntVal
|
||
elif op == ast.OpKind.Div or op == ast.OpKind.FloorDiv or op == ast.OpKind.Mod:
|
||
# 除法/整除/取模也做静态折叠(与 + - * 完全同口径:算精确值,再由下面按值自动提升位宽)。
|
||
# 语义**与运行期一致**:Div/FloorDiv → sdiv(向零截断),Mod → srem(余数符号随被除数)
|
||
# —— 宿主侧的 `//` / `%` 正是这两个语义(本语言把 FloorDiv 也映射成 sdiv,
|
||
# 见本文件运行期分支;AssignGenMixin 的常量求值器同款)。无符号位型常量按规范
|
||
# 恒为非负,负值常量必属有符号操作数集 ⇒ 与运行期 udiv/urem 的取值一致。
|
||
# 除零:绝不交给宿主(Python 会直接把编译器打挂)——先关掉折叠再 fail-fast
|
||
# (诊断文本与 AssignGenMixin 的常量求值器一致)。
|
||
if rhs.IntVal == 0:
|
||
cf_ok = 0
|
||
self.Gen.TypeGenErr.fatal_error(node, "constant expression division by zero: the right operand of '/', '//' or '%' evaluates to 0 at compile time.", "ValueError")
|
||
elif op == ast.OpKind.Mod: cf_v = lhs.IntVal % rhs.IntVal
|
||
else: cf_v = lhs.IntVal // rhs.IntVal
|
||
elif op == ast.OpKind.LShift or op == ast.OpKind.RShift:
|
||
if rhs.IntVal >= 0 and rhs.IntVal < 64:
|
||
if op == ast.OpKind.LShift: cf_v = lhs.IntVal << rhs.IntVal
|
||
else: cf_v = lhs.IntVal >> rhs.IntVal
|
||
else: cf_ok = 0
|
||
else: cf_ok = 0
|
||
if cf_ok != 0:
|
||
# 定宽:下限取操作数较宽者,只有精确值装不下才加宽(避免把 `A + 1` 这类
|
||
# 常量算式整体抬成 i64,引出新的“隐式窄化”报错)。
|
||
cf_w: int = lhs_bits if lhs_bits > rhs_bits else rhs_bits
|
||
if cf_v != 0:
|
||
t_cf: int64 = cf_v
|
||
if cf_v < 0: t_cf = cf_v ^ int64(-1)
|
||
vw_cf: int = 1 # 符号位
|
||
# 固定 64 次上界(i64 最多 64 位):不用 `while t != 0` —— 某些代码生成下
|
||
# i64 的移位/比较不收敛,会死循环(现场:主编译器卡死在 ExprGenOps 死循环里)。
|
||
bi_cf: int = 0
|
||
while bi_cf < 64 and t_cf > 0:
|
||
vw_cf += 1
|
||
t_cf = t_cf >> 1
|
||
bi_cf += 1
|
||
if vw_cf > cf_w: cf_w = vw_cf
|
||
if cf_w <= 8: cf_w = 8
|
||
elif cf_w <= 16: cf_w = 16
|
||
elif cf_w <= 32: cf_w = 32
|
||
else: cf_w = 64
|
||
return mlirlite.const_int(pool, cf_w, cf_v)
|
||
|
||
# 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 式整数提升**:小整型(i8/i16 等)不再像 C 那样先抬到 int(i32) 再运算。
|
||
# · 两侧位宽相同 → 直接按该位宽运算(`uint16 + uint16` 结果仍是 i16);
|
||
# · 两侧位宽不同 → 由下方「等宽化」把**较小**的一侧拓宽到较大的一侧(结果位宽 = 较大者)。
|
||
# 既有能力保留:**上下文目标位宽感知**(有明确赋值目标时工作位宽取其位宽)与
|
||
# **位运算常量无损适配**(& | ^ 时能无损落进另一侧位宽的常量去适配另一侧,见下)。
|
||
|
||
# 运算的「工作位宽」:优先取上下文(赋值/形参/返回目标)位宽;无上下文时取两侧操作数的较大位宽
|
||
# (移位只取左操作数位宽,计数不参与)。于是 `s = A ^ B`(s: ushort)直接算到 16 位,不会先被
|
||
# 抬成 32 位、再在赋值处撞「隐式窄化」;而 `m: uint = (uchar(x) << 16) | …` 目标为 uint,按 32 位算。
|
||
tgt_bits: int = 0
|
||
if target_ty is not None and self.is_ptr_type(target_ty) == 0: tgt_bits = self.get_llvm_type_bits(target_ty)
|
||
work_bits: int = self._binop_work_bits(op, tgt_bits, lhs_bits, rhs_bits)
|
||
|
||
# 移位:结果位宽取**左操作数**位宽(计数宽度不代表结果类型,不参与)。有目标位宽时把左操作数
|
||
# 拓宽到目标位宽,使 `m: uint = uchar(x) << 16` 这类写法移出的位不丢(与目标感知工作位宽一致);
|
||
# 无目标时 work_bits = 左操作数位宽,故不再有「先抬到 i32」的旧行为。
|
||
if op == ast.OpKind.LShift or op == ast.OpKind.RShift:
|
||
if lhs_bits != 0 and lhs_bits < work_bits:
|
||
work_ty_sh: mlirlite.LLVMType* = self._int_type_of_bits(pool, work_bits)
|
||
sh_probe: mlirlite.Value* = mlirlite.const_int32(pool, 0)
|
||
if self.should_zext_widen(pool, lhs, sh_probe, lhs_node, trans) != 0: lhs = mlirlite.build_zext(builder, lhs, work_ty_sh)
|
||
else: lhs = self.coerce_to_type(builder, lhs, work_ty_sh, node, trans)
|
||
lhs_bits = work_bits
|
||
# 计数一律适配到左操作数类型:常量按「无损」适配;运行时更宽的值按 C 语义截到该宽度
|
||
# (计数不是"值",不受隐式窄化规则约束)。
|
||
if lhs_bits != 0 and rhs_bits != 0 and rhs.Ty != lhs.Ty:
|
||
if rhs_bits < lhs_bits or self._const_fits_int_type(rhs, lhs.Ty) != 0: rhs = self.coerce_to_type(builder, rhs, lhs.Ty, node, trans)
|
||
else: rhs = mlirlite.build_trunc(builder, rhs, lhs.Ty)
|
||
rhs_bits = lhs_bits
|
||
else:
|
||
# 「常量适配」:一侧是能无损落进另一侧位宽的编译期常量时,把**常量**降到另一侧位宽,使其
|
||
# 不抬高工作位宽(用户规则:常量应隐式适配另一侧,`baseport(u16) + 7` 结果仍是 u16)。
|
||
# 也覆盖既有的位运算(& | ^)常量无损适配,二者共用 _const_operand_demotes 一处判定;
|
||
# 算术的两处例外(另一侧是常量 / 常量未超过目标位宽)见该 helper 的注释。
|
||
is_bitwise_op: int = 0
|
||
if op == ast.OpKind.BitAnd or op == ast.OpKind.BitOr or op == ast.OpKind.BitXor: is_bitwise_op = 1
|
||
if lhs_bits > rhs_bits:
|
||
if self._const_operand_demotes(is_bitwise_op, lhs, lhs_bits, rhs, rhs.Ty, tgt_bits) != 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_operand_demotes(is_bitwise_op, rhs, rhs_bits, lhs, lhs.Ty, tgt_bits) != 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 的 <<= / >>= 共用);计数宽度本身豁免,
|
||
# 按等宽化后的左操作数有效位宽比较。注意:不再做整数提升后,`uchar(x) << 8` 的左操作数是 i8,
|
||
# 计数 8 >= 8 会被如实拦下(这是新语义下的真实越界,而非旧 C 语义"先提升到 i32"后的合法移位)。
|
||
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 |