lang: Preserve folded shift count signs
168352d2407d94d280a90b7fbbb7e6cfcc16dfb85f7ef6aa88a946d2f1025ee5
The constant folder masked a shift count from its absolute magnitude. Negative counts therefore selected a different hardware-masked shift than the equivalent runtime expression. Derive folded shift counts from their two’s-complement bit patterns before masking them to the operand width. Assisted-by: Codex:gpt-5.6-sol
1 parent
30590736
lib/std/lang/resolver.rad
+8 -3
| 6285 | 6285 | return -(c.magnitude as i64); |
|
| 6286 | 6286 | } |
|
| 6287 | 6287 | return c.magnitude as i64; |
|
| 6288 | 6288 | } |
|
| 6289 | 6289 | ||
| 6290 | + | /// Convert a [`ConstInt`] to its two's-complement bit pattern. |
|
| 6291 | + | fn constIntToBits(c: ConstInt) -> u64 { |
|
| 6292 | + | return (0 - c.magnitude) if c.negative else c.magnitude; |
|
| 6293 | + | } |
|
| 6294 | + | ||
| 6290 | 6295 | /// Build a [`ConstInt`] from a signed result, preserving bit width and signedness. |
|
| 6291 | 6296 | fn constIntFromSigned(value: i64, bits: u8, signed: bool) -> ConstInt { |
|
| 6292 | 6297 | if value < 0 { |
|
| 6293 | 6298 | // Compute magnitude without signed overflow. |
|
| 6294 | 6299 | let uval = value as u64; |
| 6340 | 6345 | ||
| 6341 | 6346 | match op { |
|
| 6342 | 6347 | // Shift counts are masked to the left operand's width, matching |
|
| 6343 | 6348 | // the runtime word instructions. |
|
| 6344 | 6349 | case ast::BinaryOp::Shl => { |
|
| 6345 | - | let raw = (0 - left.magnitude) if left.negative else left.magnitude; |
|
| 6346 | - | let shamt = right.magnitude % left.bits as u64; |
|
| 6350 | + | let raw = constIntToBits(left); |
|
| 6351 | + | let shamt = constIntToBits(right) % left.bits as u64; |
|
| 6347 | 6352 | return ConstValue::Int(constIntFromBits(raw << shamt, left.bits, left.signed)); |
|
| 6348 | 6353 | }, |
|
| 6349 | 6354 | case ast::BinaryOp::Shr => { |
|
| 6350 | - | let shamt = right.magnitude % left.bits as u64; |
|
| 6355 | + | let shamt = constIntToBits(right) % left.bits as u64; |
|
| 6351 | 6356 | if left.signed { |
|
| 6352 | 6357 | let shifted = constIntToSigned(left) >> shamt as i64; |
|
| 6353 | 6358 | return ConstValue::Int( |
|
| 6354 | 6359 | constIntFromBits(shifted as u64, left.bits, true) |
|
| 6355 | 6360 | ); |
test/tests/const.shift.negative.count.rad
added
+16 -0
| 1 | + | //! returns: 0 |
|
| 2 | + | //! Folded and runtime shifts must interpret negative counts identically. |
|
| 3 | + | ||
| 4 | + | constant ONE: u32 = 1; |
|
| 5 | + | constant FOLDED: u32 = ONE << -1; |
|
| 6 | + | ||
| 7 | + | fn runtime(value: u32) -> u32 { |
|
| 8 | + | return value << -1; |
|
| 9 | + | } |
|
| 10 | + | ||
| 11 | + | @default fn main() -> i32 { |
|
| 12 | + | if FOLDED == runtime(1) { |
|
| 13 | + | return 0; |
|
| 14 | + | } |
|
| 15 | + | return 1; |
|
| 16 | + | } |