diff --git a/App/lib/core/Gen/Expr/ExprGenOps.vp b/App/lib/core/Gen/Expr/ExprGenOps.vp index aaf6a97..366c0ad 100644 --- a/App/lib/core/Gen/Expr/ExprGenOps.vp +++ b/App/lib/core/Gen/Expr/ExprGenOps.vp @@ -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))