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
Alexis Sellier committed ago 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 +
}