rv64: Canonicalize subword divisors

70604335193f9a6ce9f6cf3f7b7ad5c0570fc65d259991475a9767e102bb2f3e
Division and remainder tested raw immediates for zero and passed
them directly to RV64 instructions. Literals that wrapped at the
operation width could therefore avoid the zero trap or use the
wrong signed value.

Canonicalize divisors to the declared width before both the zero
check and the arithmetic instruction.

Assisted-by: Codex:gpt-5.6-sol
Alexis Sellier committed ago 1 parent 470dd84a
lib/std/arch/rv64/isel.rad +21 -8
231 231
        },
232 232
        case il::Type::W64 => {}
233 233
    }
234 234
}
235 235
236 -
/// Resolve a value, trap if zero (unless known non-zero), and return the register.
237 -
fn resolveAndTrapIfZero(s: *mut Selector, b: il::Val) -> gen::Reg {
238 -
    let rs2 = resolveVal(s, super::SCRATCH2, b);
239 -
    let mut knownNonZero = false;
236 +
/// Resolve a divisor in its declared width, trap if it becomes zero, and
237 +
/// return the canonicalized register.
238 +
fn resolveAndTrapIfZero(
239 +
    s: *mut Selector,
240 +
    b: il::Val,
241 +
    typ: il::Type,
242 +
    signed: bool
243 +
) -> gen::Reg {
244 +
    let mut divisor = b;
240 245
    if let case il::Val::Imm(imm) = b {
246 +
        set divisor = il::Val::Imm(canonicalCmpImm(imm, typ, signed));
247 +
    }
248 +
    let rs2 = resolveVal(s, super::SCRATCH2, divisor);
249 +
    if not isExtendedImm(divisor, typ, signed) {
250 +
        emitCmpExt(s.e, rs2, rs2, typ, signed);
251 +
    }
252 +
    let mut knownNonZero = false;
253 +
    if let case il::Val::Imm(imm) = divisor {
241 254
        set knownNonZero = imm <> 0;
242 255
    }
243 256
    if not knownNonZero {
244 257
        emit::emit(s.e, encode::bne(rs2, super::ZERO, super::INSTR_SIZE * 2));
245 258
        emit::emit(s.e, encode::ebreak());
877 890
                encode::mulw(rd, rs1, rs2)
878 891
                    if typ == il::Type::W32 else
879 892
                encode::mul(rd, rs1, rs2));
880 893
        }
881 894
        case il::BinOp::Sdiv => {
882 -
            let rs2 = resolveAndTrapIfZero(s, b);
895 +
            let rs2 = resolveAndTrapIfZero(s, b, typ, true);
883 896
            emit::emit(s.e,
884 897
                encode::divw(rd, rs1, rs2)
885 898
                    if typ == il::Type::W32 else
886 899
                encode::div(rd, rs1, rs2));
887 900
        }
888 901
        case il::BinOp::Udiv => {
889 -
            let rs2 = resolveAndTrapIfZero(s, b);
902 +
            let rs2 = resolveAndTrapIfZero(s, b, typ, false);
890 903
            emit::emit(s.e,
891 904
                encode::divuw(rd, rs1, rs2)
892 905
                    if typ == il::Type::W32 else
893 906
                encode::divu(rd, rs1, rs2));
894 907
        }
895 908
        case il::BinOp::Srem => {
896 -
            let rs2 = resolveAndTrapIfZero(s, b);
909 +
            let rs2 = resolveAndTrapIfZero(s, b, typ, true);
897 910
            emit::emit(s.e,
898 911
                encode::remw(rd, rs1, rs2)
899 912
                    if typ == il::Type::W32 else
900 913
                encode::rem(rd, rs1, rs2));
901 914
        }
902 915
        case il::BinOp::Urem => {
903 -
            let rs2 = resolveAndTrapIfZero(s, b);
916 +
            let rs2 = resolveAndTrapIfZero(s, b, typ, false);
904 917
            emit::emit(s.e,
905 918
                encode::remuw(rd, rs1, rs2)
906 919
                    if typ == il::Type::W32 else
907 920
                encode::remu(rd, rs1, rs2));
908 921
        }
test/tests/div.u8.immediate.wrap.rad added +10 -0
1 +
//! returns: 133
2 +
//! A u8 divisor literal of 256 wraps to zero and must trap.
3 +
4 +
fn divideByWrappedZero(value: u8) -> u8 {
5 +
    return value / 256;
6 +
}
7 +
8 +
@default fn main() -> u8 {
9 +
    return divideByWrappedZero(10);
10 +
}