lang: Mask folded shift counts by width
1361514f621f67b88a853c1f9cbf1354b4f18c45fccf263a349037b2557ac2d9
Constant declarations retained untyped integer metadata, and folded left shifts used the host word count without truncating to the operand width. Typed constant shifts therefore diverged from runtime shifts. Preserve a constant binding’s declared integer representation and fold left shifts from width-truncated bit patterns with masked counts. Assisted-by: Codex:gpt-5.6-sol
1 parent
85cc0364
lib/std/lang/resolver.rad
+30 -5
| 3020 | 3020 | let attrs = resolveAttributes(self, attrList); |
|
| 3021 | 3021 | let bindingTy = try infer(self, typeNode); |
|
| 3022 | 3022 | let valueTy = try checkAssignable(self, valueNode, bindingTy); |
|
| 3023 | 3023 | ||
| 3024 | 3024 | if isConst { |
|
| 3025 | - | let constVal = constValueEntry(self, valueNode); |
|
| 3025 | + | let mut constVal = constValueEntry(self, valueNode); |
|
| 3026 | 3026 | if constVal == nil and not isConstExpr(self, valueNode) { |
|
| 3027 | 3027 | throw emitError(self, valueNode, ErrorKind::ConstExprRequired); |
|
| 3028 | 3028 | } |
|
| 3029 | + | if let val = constVal { |
|
| 3030 | + | if let case ConstValue::Int(int) = val; isNumericType(bindingTy) { |
|
| 3031 | + | set constVal = castConstInt(int, bindingTy); |
|
| 3032 | + | } |
|
| 3033 | + | } |
|
| 3029 | 3034 | try bindConstIdent(self, ident, node, bindingTy, constVal, attrs); |
|
| 3030 | 3035 | } else { |
|
| 3031 | 3036 | if not isConstExpr(self, valueNode) { |
|
| 3032 | 3037 | throw emitError(self, valueNode, ErrorKind::ConstExprRequired); |
|
| 3033 | 3038 | } |
| 6300 | 6305 | signed, |
|
| 6301 | 6306 | negative: false, |
|
| 6302 | 6307 | }; |
|
| 6303 | 6308 | } |
|
| 6304 | 6309 | ||
| 6310 | + | /// Build a [`ConstInt`] from a two's-complement bit pattern. |
|
| 6311 | + | fn constIntFromBits(raw: u64, bits: u8, signed: bool) -> ConstInt { |
|
| 6312 | + | let mask = parser::U64_MAX if bits == 64 else parser::U64_MAX >> (64 - bits) as u64; |
|
| 6313 | + | let truncated = raw & mask; |
|
| 6314 | + | ||
| 6315 | + | if signed { |
|
| 6316 | + | let signBit = (mask >> 1) + 1; |
|
| 6317 | + | if (truncated & signBit) <> 0 { |
|
| 6318 | + | return ConstInt { |
|
| 6319 | + | magnitude: (0 - truncated) & mask, |
|
| 6320 | + | bits, |
|
| 6321 | + | signed, |
|
| 6322 | + | negative: true, |
|
| 6323 | + | }; |
|
| 6324 | + | } |
|
| 6325 | + | } |
|
| 6326 | + | return ConstInt { magnitude: truncated, bits, signed, negative: false }; |
|
| 6327 | + | } |
|
| 6328 | + | ||
| 6305 | 6329 | /// Try to fold a binary operation on two integer constants. |
|
| 6306 | 6330 | /// Returns the resulting constant value if successful. |
|
| 6307 | 6331 | fn foldIntBinOp(op: ast::BinaryOp, left: ConstInt, right: ConstInt) -> ?ConstValue { |
|
| 6308 | 6332 | // Use the wider bit width and propagate signedness. |
|
| 6309 | 6333 | let mut bits = left.bits; |
| 6313 | 6337 | let signed = left.signed or right.signed; |
|
| 6314 | 6338 | let l = constIntToSigned(left); |
|
| 6315 | 6339 | let r = constIntToSigned(right); |
|
| 6316 | 6340 | ||
| 6317 | 6341 | match op { |
|
| 6318 | - | // Shifts operate on unsigned magnitudes directly. |
|
| 6342 | + | // Shift counts are masked to the left operand's width, matching |
|
| 6343 | + | // the runtime word instructions. |
|
| 6319 | 6344 | case ast::BinaryOp::Shl => { |
|
| 6320 | - | return ConstValue::Int(ConstInt { |
|
| 6321 | - | magnitude: left.magnitude << right.magnitude, bits, signed, negative: left.negative, |
|
| 6322 | - | }); |
|
| 6345 | + | let raw = (0 - left.magnitude) if left.negative else left.magnitude; |
|
| 6346 | + | let shamt = right.magnitude % left.bits as u64; |
|
| 6347 | + | return ConstValue::Int(constIntFromBits(raw << shamt, left.bits, left.signed)); |
|
| 6323 | 6348 | }, |
|
| 6324 | 6349 | case ast::BinaryOp::Shr => { |
|
| 6325 | 6350 | return ConstValue::Int(ConstInt { |
|
| 6326 | 6351 | magnitude: left.magnitude >> right.magnitude, bits, signed, negative: left.negative, |
|
| 6327 | 6352 | }); |
test/tests/const.u32.shift.mask.rad
added
+12 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | //! Constant shifts must mask the count to the operand width like runtime shifts. |
|
| 3 | + | ||
| 4 | + | constant ONE: u32 = 1; |
|
| 5 | + | constant SHIFTED: u32 = ONE << 40; |
|
| 6 | + | ||
| 7 | + | @default fn main() -> i32 { |
|
| 8 | + | if SHIFTED == 256 { |
|
| 9 | + | return 0; |
|
| 10 | + | } |
|
| 11 | + | return 1; |
|
| 12 | + | } |