Fix a small instruction selection bug

f1bc2ebd6a8a7a165610be02c4c4e5a97e33d699f67f8e28188bcbd041045f34
Alexis Sellier committed ago 1 parent 7bfd64dd
lib/std/arch/rv64/emit.rad +1 -1
490 490
pub fn emitLoad(e: *mut Emitter, rd: super::Reg, base: super::Reg, offset: i32, typ: il::Type) {
491 491
    let adj = adjustOffset(e, base, offset);
492 492
    match typ {
493 493
        case il::Type::W8 => emit(e, encode::lbu(rd, adj.base, adj.offset)),
494 494
        case il::Type::W16 => emit(e, encode::lhu(rd, adj.base, adj.offset)),
495 -
        case il::Type::W32 => emit(e, encode::lw(rd, adj.base, adj.offset)),
495 +
        case il::Type::W32 => emit(e, encode::lwu(rd, adj.base, adj.offset)),
496 496
        case il::Type::W64 => emit(e, encode::ld(rd, adj.base, adj.offset)),
497 497
    }
498 498
}
499 499
500 500
/// Emit signed load with automatic offset adjustment.
test/tests/load.u32.high.rad added +60 -0
1 +
//! returns: 1
2 +
//! Test that u32 values with bit 31 set are correctly zero-extended
3 +
//! when loaded from memory.
4 +
//!
5 +
//! Regression test: `lw` sign-extends on RV64, producing a negative
6 +
//! 64-bit value for u32 values >= 0x80000000. The correct instruction
7 +
//! is `lwu` which zero-extends.
8 +
9 +
/// A u32 value with bit 31 set, stored in a struct to force a memory load.
10 +
record Pair {
11 +
    lo: u32,
12 +
    hi: u32,
13 +
}
14 +
15 +
/// Test that a u32 field with bit 31 set compares correctly.
16 +
fn testFieldCompare() -> bool {
17 +
    let p = Pair { lo: 1, hi: 0x80000000 };
18 +
19 +
    // With lw (sign-extend), p.hi is loaded as 0xFFFFFFFF80000000
20 +
    // in the register. A subsequent u32 comparison should still work
21 +
    // because the comparison normalizes, but let's test the value
22 +
    // through arithmetic that would expose the sign-extension.
23 +
    let val = p.hi;
24 +
25 +
    // Shift right by 1: if correctly zero-extended (0x80000000),
26 +
    // u32 shift gives 0x40000000.
27 +
    // If sign-extended to 64-bit and then shifted as u32 via `srliw`,
28 +
    // the *w variant only looks at low 32 bits, so this still works.
29 +
    let shifted = val >> 1;
30 +
    if shifted != 0x40000000 {
31 +
        return false;
32 +
    }
33 +
    return true;
34 +
}
35 +
36 +
/// Test u32 max value round-trips through memory correctly.
37 +
fn testMaxU32() -> bool {
38 +
    let arr: [u32; 1] = [0xFFFFFFFF];
39 +
    let val = arr[0];
40 +
41 +
    // If lw sign-extends, val in register is 0xFFFFFFFFFFFFFFFF.
42 +
    // Adding 1 as u32 (via addw): 0xFFFFFFFF + 1 = 0 (wraps). OK either way.
43 +
    // But comparing: val should equal 0xFFFFFFFF as u32.
44 +
    if val != 0xFFFFFFFF {
45 +
        return false;
46 +
    }
47 +
48 +
    // The key test: widening to u64 should give 0x00000000FFFFFFFF,
49 +
    // not 0xFFFFFFFFFFFFFFFF.
50 +
    let wide: u64 = val as u64;
51 +
    if wide != 0xFFFFFFFF {
52 +
        return false;
53 +
    }
54 +
55 +
    return true;
56 +
}
57 +
58 +
@default fn main() -> bool {
59 +
    return testFieldCompare() and testMaxU32();
60 +
}