feat(gen): 二元运算采用目标感知工作位宽(仿 C:算到目标位宽;移位只提升左操作数),隐式窄化仍禁止

This commit is contained in:
TermiNexus committed 2026-10-12 18:53:50 +08:00
1 parent 9d02cc4684
commit 4a66aa7fd2
3 files changed
+54 -28

No files matched your search

+13
View File
@@ -136,6 +136,19 @@ novirtual class ExprGenBase:
if bits == 63: return 1
if v <= (int64(1) << bits) - 1: return 1
return 0
# _int_type_of_bits - 合成指定位宽的 LLVM 整型(1/8/16/32/64 快路径,其余按 LLVMType.Int 合成)。
def _int_type_of_bits(self, pool: memhub.MemBuddy*, bits: int) -> mlirlite.LLVMType*:
"""LLVM integer type of the given bit width."""
if bits == 1: return mlirlite.Int1(pool)
if bits == 8: return mlirlite.Int8(pool)
if bits == 16: return mlirlite.Int16(pool)
if bits == 32: return mlirlite.Int32(pool)
if bits == 64: return mlirlite.Int64(pool)
ty: mlirlite.LLVMType* = pool.alloc(mlirlite.LLVMType.__sizeof__())
if ty is None: return None
string.memset(ty, 0, mlirlite.LLVMType.__sizeof__())
*ty = *mlirlite.LLVMType.Int(bits)
return ty
# _is_zero_int_literal - is the AST node the integer literal 0?
# Used for ptr != 0 / ptr == 0, where 0 means NULL and is allowed.
def _is_zero_int_literal(self, node: ast.AST*) -> int:
+1 -1
View File
@@ -46,7 +46,7 @@ novirtual class ExprGenMixin(ExprGenBase.ExprGenBase):
rv: mlirlite.Value* = self.Gen.ExprGenConst.translate_constant(builder, pool, mod, node, trans, target_ty)
return rv
elif k == ast.ASTKind.Name: return self.Gen.ExprGenName.translate_name_value(builder, pool, node, trans, target_ty, use_ctx)
elif k == ast.ASTKind.BinOp: return self.Gen.ExprGenOps.translate_binop(pool, builder, mod, node, trans)
elif k == ast.ASTKind.BinOp: return self.Gen.ExprGenOps.translate_binop(pool, builder, mod, node, trans, target_ty)
elif k == ast.ASTKind.Call: return self.Gen.CallGen.translate_call(pool, builder, mod, node, funcs_ptr, func_count, trans)
# M2.4 `new stack|heap|memgr(x) [slife|hlife] T(args)`:只换"取存储",其余复用构造序列
elif k == ast.ASTKind.New: return self.Gen.CallGenCtor._translate_new_expr(pool, builder, mod, ast.New*(node), trans)
+40 -27
View File
@@ -1119,7 +1119,7 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
# 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*:
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
@@ -1128,8 +1128,9 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
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)
# 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 ===
@@ -1149,7 +1150,7 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
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)
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) ===
@@ -1229,30 +1230,42 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
lhs_bits = 32
rhs_bits = 32
# 移位:C 语义先按**左操作数**做整数提升到 int(与右操作数宽度无关)。
# 否则 `uchar(x) << 8`(8 位左操作数 + 32 位计数字面量)在下面等宽化里若被“常量适配”改了方向,
# 会误报「移位数 8 >= 左操作数位宽 8」。
if (op == ast.OpKind.LShift or op == ast.OpKind.RShift) and lhs_bits != 0 and lhs_bits < 32:
i32_ty_sh: mlirlite.LLVMType* = mlirlite.Int32(pool)
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, i32_ty_sh)
else: lhs = self.coerce_to_type(builder, lhs, i32_ty_sh, node, trans)
lhs_bits = 32
# 运算的「工作位宽」:优先取上下文(赋值/形参/返回目标)位宽;无上下文时按 C 语义用 int(32)。
# 于是 `s = A ^ B`(s: ushort)直接算到 16 位,不会先被抬成 32 位、再在赋值处撞「隐式窄化」;
# 而 `m: uint = (uchar(x) << 16) | …` 按 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 = 32
if tgt_bits != 0: work_bits = tgt_bits
if lhs_bits > work_bits: work_bits = lhs_bits
if rhs_bits > work_bits: work_bits = rhs_bits
# 移位运算的右操作数是「移位数」,其宽度不参与结果类型判定(C 语义:左操作数提升到 int 即可)。
is_shift_op: int = 0
if op == ast.OpKind.LShift or op == ast.OpKind.RShift: is_shift_op = 1
if lhs_bits > rhs_bits:
# 宽侧是「值能无损落进窄侧」的编译期常量时,把**常量**降到窄侧类型(C 语义:常量优先适配另一侧),
# 否则 `crc(u32) ^ 0xFFFFFFFF(i64)` 会把 crc 抬成 64 位,结果再赋回 32 位时报隐式窄化。
if is_shift_op == 0 and 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 is_shift_op == 0 and 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)
# 移位:C 语义只提升**左操作数**到工作位宽(计数的宽度不参与结果类型),避免 `uchar(x) << 8`
# 之类因计数位宽不足而误报「移位数 >= 左操作数位宽」。
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:
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.