diff --git a/App/lib/core/Gen/Expr/ExprGenBase.vp b/App/lib/core/Gen/Expr/ExprGenBase.vp index 7d5fa6c..9969ed2 100644 --- a/App/lib/core/Gen/Expr/ExprGenBase.vp +++ b/App/lib/core/Gen/Expr/ExprGenBase.vp @@ -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: diff --git a/App/lib/core/Gen/Expr/ExprGenMixin.vp b/App/lib/core/Gen/Expr/ExprGenMixin.vp index 96363b8..c844359 100644 --- a/App/lib/core/Gen/Expr/ExprGenMixin.vp +++ b/App/lib/core/Gen/Expr/ExprGenMixin.vp @@ -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) diff --git a/App/lib/core/Gen/Expr/ExprGenOps.vp b/App/lib/core/Gen/Expr/ExprGenOps.vp index 7ad7513..5ef9856 100644 --- a/App/lib/core/Gen/Expr/ExprGenOps.vp +++ b/App/lib/core/Gen/Expr/ExprGenOps.vp @@ -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.