From the Web
Introduction
In RISC-V assembly, overflow detection for full double-word
(.dword / 64-bit) multiplication depends heavily on whether
your underlying architecture is RV64 or RV32. Because RISC-V does
not have a hardware status register or carry/overflow flags,
overflow must be checked manually using regular assembly instructions.
Here is how to safely multiply two 64-bit values (.dword) and detect overflow for both environments.
Scenario 1: On an RV64 Machine (64-bit Architecture)
On an RV64 machine, a .dword fits into a single 64-bit register.
Multiplying two 64-bit registers yields a 128-bit result. RISC-V
provides instructions to get both the lower 64 bits (mul) and the
upper 64 bits (mulh / mulhu) of that 128-bit product.
An overflow occurs if the true mathematical 128-bit product
cannot fit back into a single 64-bit register without losing its
value.
Unsigned 64-bit Multiply Overflow Detection
For unsigned values, an overflow happens if any part of the product
spills into the upper 64 bits. Therefore, you simply check if the
upper 64 bits are non-zero.
# Inputs: a0 = multiplier, a1 = multiplicand
# Outputs: a0 = lower 64-bit product, t0 = overflow flag (1 if overflow, 0 if not)
mulhu t1, a0, a1 # t1 = upper 64 bits of the 128-bit product
mul a0, a0, a1 # a0 = lower 64 bits of the product
snez t0, t1 # Set t0 to 1 if t1 != 0 (Unsigned Overflow Detected)
bnez t0, handle_overflow
Signed 64-bit Multiply Overflow Detection
For signed values, an overflow happens if the lower 64 bits
do not accurately represent the sign of the full 128-bit result.
Mathematically, the upper 64 bits must be an exact sign-extension
of the lower 64 bits (all 0s if the result is positive, or all
1s if negative).
You can check this by taking the lower 64 bits, sign-extending
them by shifting them mathematically, and comparing the result.
Alternatively, you can use the srai (Shift Right Arithmetic)
instruction on the lower result and see if it equals the upper
result:
# Inputs: a0 = multiplier, a1 = multiplicand
# Outputs: a0 = lower 64-bit product, t0 = overflow flag (1 if overflow, 0 if not)
mulh t1, a0, a1 # t1 = upper 64 bits of the signed product
mul a0, a0, a1 # a0 = lower 64 bits of the signed product
srai t2, a0, 63 # t2 = duplicate the sign bit across all 64 bits
xor t2, t1, t2 # If t1 == t2, xor results in 0 (no overflow)
snez t0, t2 # Set t0 to 1 if t2 != 0 (Signed Overflow Detected)
bnez t0, handle_overflow
Scenario 2: On an RV32 Machine (32-bit Architecture)
On an RV32 machine, a .dword occupies two 32-bit registers (e.g.,
a high register and a low register). Multiplying two 64-bit values
to detect a 64-bit overflow requires an algebraic expansion
similar to long multiplication.
Let:
- (A = (Ahi << 32) + Alo)
- (B = (Bhi << 32) + Blo)
The mathematical product is:
(A X B=(Alo X Blo) + [(Ahi X
Blo) << 32]+[(Bhi X Alo) << 32]+[(Ahi X Bhi) << 64])
Unsigned 32-bit Emulation Rules for 64-bit Overflow:
To avoid overflow beyond 64 bits:
1. (Ahi X Bhi) must be 0. If both numbers
have data in their upper 32-bits, a 64-bit overflow is guaranteed.
2. The upper 32 bits of (Ahi X Blo) and (Bhi X Alo) must be 0. (Tested using mulhu).
3. The sum of the cross terms shifted into the high 32 bits must not overflow the capacity of the final high register.
# Inputs: [a1:a0] = A (hi:lo), [a3:a2] = B (hi:lo)
# Outputs: [a1:a0] = Product, t0 = overflow flag (1 if overflow, 0 if not)
# Step 1: Check if high-by-high term exists
bnez a1, check_b_hi
j cross_terms
check_b_hi:
bnez a3, trigger_overflow # If A_hi != 0 AND B_hi != 0, immediate overflow
cross_terms:
# Step 2: Check high-by-low cross multiplies for individual 32-bit overflows
mulhu t1, a1, a2 # Upper 32-bits of A_hi * B_lo
bnez t1, trigger_overflow
mulhu t2, a3, a0 # Upper 32-bits of B_hi * A_lo
bnez t2, trigger_overflow
# Step 3: Compute the actual lower cross products
mul t1, a1, a2 # t1 = A_hi * B_lo
mul t2, a3, a0 # t2 = B_hi * A_lo
# Step 4: Compute the base 64-bit multiplication parts
mulhu t3, a0, a2 # Mid-section carry from A_lo * B_lo
mul a0, a0, a2 # Final lower 32 bits of the product
# Step 5: Add up the high-register pieces and check for addition carry overflows
add a1, t1, t2 # a1 = (A_hi*B_lo) + (B_hi*A_lo)
sltu t0, a1, t1 # Check if that addition overflowed 32 bits
bnez t0, trigger_overflow
add a1, a1, t3 # Add the carry from the lower multiplication
sltu t0, a1, t3 # Check for carry overflow again
bnez t0, trigger_overflow
li t0, 0 # No overflow!
ret
trigger_overflow:
li t0, 1 # Overflow occurred
ret