lang: Preserve folded shift count signs

c6107b5e68889916afe0d89859019f36fb2c9e74acf672597101dd5e6a27a016
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
Alexis Sellier committed ago 1 parent 70604335
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 +
}