feat(gen): 常量操作数隐式适配另一侧位宽(仅另一侧非常量时),修 u16 变量 + 字面量被抬到 i32 导致误报窄化

This commit is contained in:
TermiNexus committed 2026-10-13 17:01:53 +08:00
1 parent a01e9280cc
commit 6cb1f525d9
1 file changed
+32 -5
+32 -5
View File
@@ -1166,6 +1166,9 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
# · 移位 → **计数(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
@@ -1175,6 +1178,29 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
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*:
@@ -1301,17 +1327,18 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
else: rhs = mlirlite.build_trunc(builder, rhs, lhs.Ty)
rhs_bits = lhs_bits
else:
# 「常量适配」只对**位运算(& | ^)**生效:位运算结果不会超出参与值的位宽,把能无损落进另一侧的
# 常量降下来是安全的。**算术(+ - * / %)不做适配**——窄位宽下会回绕(如 define 的
# `(MEMBUDDY_MAX_ORDERS + 1) * 8`:i8(32)+1 → 33、再 ×8 溢出成 8),必须按 C 语义把窄操作数拓宽。
# 「常量适配」:一侧是能无损落进另一侧位宽的编译期常量时,把**常量**降到另一侧位宽,使其
# 不抬高工作位宽(用户规则:常量应隐式适配另一侧,`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 is_bitwise_op != 0 and self._const_fits_int_type(lhs, rhs.Ty) != 0: lhs = self.coerce_to_type(builder, lhs, rhs.Ty, node, trans)
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 is_bitwise_op != 0 and self._const_fits_int_type(rhs, lhs.Ty) != 0: rhs = self.coerce_to_type(builder, rhs, lhs.Ty, node, trans)
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)