RV64 C and Assembly Boundaries: Preserve the Sign-Extended uint32_t Register Contract
An RV64 assembly helper loads a 32-bit unsigned value, passes it to C, and appears correct for small inputs. Values with the top bit set expose the bug: the helper kept the upper half of the argument register zero, while the standard calling convention expects a sign-extended 32-bit representation.
The relevant contract is the RISC-V ELF psABI integer calling convention at commit 3095deae52ae3feecc54b5e185d14eb177e05d04. The instruction explanations below use RV64I version 2.1 in specification library v20260120. A calling convention constrains an interface; it does not prohibit hardware registers from containing other bit patterns.
Follow both stages of scalar widening
The standard integer convention widens a scalar narrower than XLEN according to its type up to 32 bits, then sign-extends it to XLEN. On RV64, the second stage matters even when the original 32-bit type is unsigned.
For a uint32_t argument with value 0x80000000, the canonical argument-register representation is 0xffffffff80000000. For 0xffffffff, it is 0xffffffffffffffff. The low 32 bits still describe the unsigned C value. The ones above them are part of the interface representation, not a change to the source type’s unsigned arithmetic.
An input below the top-bit boundary, such as 0x7fffffff, has representation 0x000000007fffffff. Testing only this region cannot distinguish a correctly sign-extended argument from an incorrectly zero-extended one. This is why ordinary-looking test vectors can conceal an assembly boundary defect.
Choose a load for its destination contract
RV64I LW loads 32 bits and sign-extends the result to the register width. LWU loads the same number of bytes but zero-extends them. Neither is universally the “unsigned C” instruction without considering how the value will be used next.
If the next operation passes that value as a standard uint32_t argument, the interface needs the canonical sign-extended form. A value obtained through another instruction can be canonicalized with ADDIW rd, rs1, 0, which retains the low 32 bits and sign-extends their result. This is also the architectural operation behind the SEXT.W pseudoinstruction.
That instruction is not a blanket patch to apply to every 64-bit value. It discards the upper half before extending the low word. Applied to a genuine pointer or a 64-bit counter, it can destroy useful information. Review the declared interface before inserting it.

Keep a real uint64_t conversion separate
Suppose C converts uint32_t x to uint64_t. The result must preserve the unsigned numeric value. When x is 0x80000000, the resulting 64-bit value is 0x0000000080000000, not the sign-extended argument representation.
A helper implementing that conversion therefore has a different output contract from one returning uint32_t. The former must provide the complete 64-bit numeric result; the latter follows the convention for a 32-bit result. Naming both values “unsigned” does not make their register obligations identical.
Keep a worksheet with three separate columns: the low-word value, its canonical 32-bit ABI representation, and its numerically widened 64-bit value. The first two boundary inputs above make accidental conflation visible without running any hardware.
Understand why full-width unsigned comparisons can work
The RV64I explanation states that full-width unsigned comparisons such as SLTU still work on unsigned 32-bit values under the sign-extension invariant. Values below the top-bit boundary occupy the low region; values at or above it occupy the high sign-extended region, preserving their unsigned ordering.
The condition matters: both operands must follow the same invariant. Comparing one zero-extended top-bit-set value with a sign-extended peer can produce a different ordering. Do not infer that every arbitrary 64-bit encoding of a low word is interchangeable merely because one comparison happened to succeed.
Audit the boundary, not just the load
A proposed review starts with the C declaration seen by each translation unit, the selected standard RV64 ABI and the assembly call or return site. Trace where each register value came from and whether an intervening full-width operation disturbed its canonical form. Inspect the compiler-visible constraints as well as the handwritten instructions when inline assembly is involved.
Use 0x7fffffff, 0x80000000 and 0xffffffff as illustrative review inputs. Check both argument and return paths, then separately review any conversion to a real 64-bit type. No compiler or target execution was performed here. Custom interfaces, SBI calls and RV64ILP32 variants require their own contracts rather than automatic application of this standard RV64 C boundary rule.