feat(gen): 二元运算取消 C 式整数提升(任何位宽不再默认提升到 i32),改为按 max(两侧)/目标位宽运算

This commit is contained in:
TermiNexus committed 2026-10-13 16:20:02 +08:00
1 parent 97c0a1c6fd
commit 14d8c05a2a
1 file changed
+30 -38
+30 -38
View File
@@ -1159,14 +1159,21 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
if node is not None: col_s = node.col_offset + 1
self.Gen.TypeGenErr.fatal_error(node, f"[VPC] illegal double-pointer arithmetic type: op = '{op_lbl}'{aug_note}, " f"left operand type = {lty_lbl}, right operand type = {rty_lbl}. " f"in this language str is i8*, with no `+` concatenation semantics (`str + str` and pointer +/- pointer " f"both degrade to raw pointer add/sub => illegal IR: opt reports " f"invalid operand type for instruction). " f"silent degradation is forbidden, compilation aborted. " f"fix: build strings with f-string interpolation, string.strcat, or viperlib.snprintf.", "TypeError", col_s, col_s)
return 1
# binop_needs_int_promotion - whether this op takes C-style integer promotion (to >= 32 bits)
def binop_needs_int_promotion(self, op: int) -> int:
"""C 语义整数提升的适用范围:**算术与移位**为 1(窄于 32 位先提升再算,避免 8/16 位回绕),
**位运算(& | ^)为 0**——按既有约定"两侧同宽就不提升"(WORD | WORD 保持 u16)。"""
if op == ast.OpKind.Add or op == ast.OpKind.Sub or op == ast.OpKind.Mult: return 1
if op == ast.OpKind.Div or op == ast.OpKind.FloorDiv or op == ast.OpKind.Mod: return 1
if op == ast.OpKind.LShift or op == ast.OpKind.RShift: return 1
return 0
# _binop_work_bits - 二元运算的「工作位宽」(**不做 C 式整数提升**)
# 本语言取消了 C 的「小整型先提升到 int(i32)」语义:i8/i16 不再预先抬到 32 位再算。
# · 有上下文目标位宽(赋值/形参/返回目标)→ 以它为基础;
# · 无目标 → 取两侧操作数位宽的较大者,等宽化逻辑随后把**较小**的一侧拓宽到此位宽(结果位宽 = 较大者);
# · 移位 → **计数(rhs)不代表结果位宽**,不参与比较;结果位宽 = 左操作数位宽(无目标时)。
# 任一侧位宽为 0(未知/未定)会天然退化为另一侧(0 不抬高 max);只有连目标与两侧位宽**全都缺失**
# (类型信息完全缺失)时才回退到 32 —— 这只是可编译性兜底,不代表"提升"。
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
# translate_binop - binary operations with automatic type promotion
# ============================================================
@@ -1264,38 +1271,22 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
if binop_unsigned == 0 and rhs_node is not None:
if self._node_type_unsigned(pool, rhs_node, trans) != 0: binop_unsigned = 1
# C 语义整数提升(仅算术/移位;位运算 & | ^ 保持"同宽不提升"):
# 两侧都窄于 32 位时先提升到 32 位再算,避免 8/16 位回绕(如两个 16 位 define 的
# `MAX_INCLUDES * MAX_REL_PATH_LEN` 会算成 0)。提升后把 lhs_bits/rhs_bits 记为 32,
# 让下面的等宽化逻辑不再重复处理(否则会对同宽 i32 再做一次无意义转换)。
if self.binop_needs_int_promotion(op) != 0:
if lhs_bits != 0 and rhs_bits != 0 and lhs_bits < 32 and rhs_bits < 32:
i32_ty_pr: mlirlite.LLVMType* = mlirlite.Int32(pool)
# 加宽方向与下面的等宽化同一判据(i1/i8 默认 zext、带符号注解 sext、无符号 typedef zext);
# probe 只是给 should_zext_widen 提供"宽侧类型",其常量值不参与判据。
i32_probe: mlirlite.Value* = mlirlite.const_int32(pool, 0)
if self.should_zext_widen(pool, lhs, i32_probe, lhs_node, trans) != 0: lhs = mlirlite.build_zext(builder, lhs, i32_ty_pr)
else: lhs = self.coerce_to_type(builder, lhs, i32_ty_pr, node, trans)
if self.should_zext_widen(pool, rhs, i32_probe, rhs_node, trans) != 0: rhs = mlirlite.build_zext(builder, rhs, i32_ty_pr)
else: rhs = self.coerce_to_type(builder, rhs, i32_ty_pr, node, trans)
lhs_bits = 32
rhs_bits = 32
# 本语言**不做 C 式整数提升**:小整型(i8/i16 等)不再像 C 那样先抬到 int(i32) 再运算。
# · 两侧位宽相同 → 直接按该位宽运算(`uint16 + uint16` 结果仍是 i16);
# · 两侧位宽不同 → 由下方「等宽化」把**较小**的一侧拓宽到较大的一侧(结果位宽 = 较大者)。
# 既有能力保留:**上下文目标位宽感知**(有明确赋值目标时工作位宽取其位宽)与
# **位运算常量无损适配**(& | ^ 时能无损落进另一侧位宽的常量去适配另一侧,见下)。
# 运算的「工作位宽」:优先取上下文(赋值/形参/返回目标)位宽;无上下文时按 C 语义用 int(32)。
# 于是 `s = A ^ B`(s: ushort)直接算到 16 位,不会先被抬成 32 位、再在赋值处撞「隐式窄化」;
# 而 `m: uint = (uchar(x) << 16) | …` 按 32 位算,移出的位不丢。
# 运算的「工作位宽」:优先取上下文(赋值/形参/返回目标)位宽;无上下文时取两侧操作数的较大位宽
# (移位只取左操作数位宽,计数不参与)。于是 `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 = 32
if tgt_bits != 0: work_bits = tgt_bits
if lhs_bits > work_bits: work_bits = lhs_bits
# 移位:**计数宽度不代表结果位宽**,不能计入工作位宽;否则 32 位计数字面量会把
# `s << 1`(s: ushort)抬成 32 位,顺链把整条表达式抬成 32 位、赋回 ushort 时误报窄化。
if op != ast.OpKind.LShift and op != ast.OpKind.RShift:
if rhs_bits > work_bits: work_bits = rhs_bits
work_bits: int = self._binop_work_bits(op, tgt_bits, lhs_bits, rhs_bits)
# 移位:C 语义只提升**左操作数**到工作位宽(计数的宽度不参与结果类型),避免 `uchar(x) << 8`
# 之类因计数位宽不足而误报「移位数 >= 左操作数位宽」。
# 移位:结果位宽取**左操作数**位宽(计数宽度不代表结果类型,不参与)。有目标位宽时把左操作数
# 拓宽到目标位宽,使 `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)
@@ -1331,8 +1322,9 @@ novirtual class ExprGenOps(ExprGenBase.ExprGenBase):
# fail-fast: illegal bitwise operand types; abort compilation (expose the upstream root cause)
self.Gen.TypeGenErr.fatal_error(node, "illegal bitwise operand types: both lhs/rhs are non-integer types (pointer or struct), cannot perform bitwise operations", "TypeError")
# 移位:计数静态可确定时做范围检查(与 AugAssign 的 <<= / >>= 共用);
# 计数宽度本身豁免,按提升/等宽化后的左操作数有效位宽比较(C 语义下 u8 << 8 合法)。
# 移位:计数静态可确定时做范围检查(与 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))