lang: Mask folded shift counts by width
deffec0ad488f9c9ff2d0d3b1419831cd30c8097f2af466eaf2bace6505f1038
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
98956983
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 | + | } |