RARS - Read a File, One-Buffer-At-A-Time

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:

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