[RISC-V] Eliminate redundant zero-extension after known zero-producing instructions
- Dominant language
- C#
- Stars
- 18.3k
- Forks
- 5.6k
- PR merge metrics
- PR metrics pending
Description
### Description
The RV64 backend can emit redundant zero-extension after instructions that already produce a zero-extended result, such as `sltu` and `lbu`.
The IL below illustrates the operations involved; these snippets have **not** yet been verified as standalone reproductions on upstream main. The assembly sequences were observed in a downstream NativeAOT build.
### IL and observed assembly
**Unsigned comparison widened to 64 bits:**
```il
.method public static uint64 Carry(uint64 a, uint64 b) cil managed
{
.maxstack 2
ldarg.0
ldarg.1
add
ldarg.0
clt.un
conv.u8
ret
}
```
Observed sequence within a larger method:
```asm
add x5, x14, x5
sltu x14, x5, x14
slli x14, x14, 32
srli x14, x14, 32
```
`sltu` already writes a full-register 0 or 1. Expected, when there are no intervening control-flow entries:
```asm
add x5, x14, x5
sltu x14, x5, x14
```
**Unsigned byte load widened to 64 bits:**
```il
.method public static uint64 LoadByte(uint8* p) cil managed
{
.maxstack 1
ldarg.0
ldind.u1
conv.u8
ret
}
```
Observed sequence within an indexing expression:
```asm
lbu x10, 0(x31)
slli x10, x10, 32
srli x10, x10, 32
slli x10, x10, 3
```
Expected:
```asm
lbu x10, 0(x31)
slli x10, x10, 3
```
`lbu` already clears the upper bits. If extension source and destination registers differ, a plain copy may still be needed.
### Current upstream source
Source inspected at `3c4631e63b1de4308e2965b149992b992c9f5318`:
- [RV64 `genIntToIntCast`](https://github.com/dotnet/runtime/blob/3c4631e63b1de4308e2965b149992b992c9f5318/src/coreclr/jit/codegenriscv64.cpp#L5714) emits `slli 32; srli 32` for `ZERO_EXTEND_INT` without Zba, or `add.uw` with zero when Zba is available. This path does not check whether the producer already zeroed the upper bits.
- [x64 `AreUpperBitsZero`](https://github.com/dotnet/runtime/blob/3c4631e63b1de4308e2965b149992b992c9f5318/src/coreclr/jit/emitxarch.cpp#L1177) provides a precedent for tracking recent writes to eliminate redundant extensions.
- [ARM64 `IsRedundantMov`](https://github.com/dotnet/runtime/blob/3c4631e63b1de4308e2965b149992b992c9f5318/src/coreclr/jit/emitarm64.cpp#L17342) handles a narrower case immediately after suitable loads.
Could RV64 similarly eliminate extensions after proven zero-producing instructions, for both the baseline and Zba paths? Arbitrary 32-bit arithmetic still needs its required normalization; this request concerns producers whose upper bits are already known to be zero.
### Reproduction status
The observed assembly came from a downstream bflat/NativeAOT compiler. Its correspondence to the upstream commit above has not been established, and the snippets have not been compiled with upstream main. Earlier optimization phases may eliminate some simple cases. This is a code-quality investigation request, not an established regression or a measured improvement on main.
Contributor guide
Research direction
Start by compiling the IL snippets against upstream main to verify whether the redundant extensions reproduce, then inspect genIntToIntCast in src/coreclr/jit/codegenriscv64.cpp. Compare the x64 AreUpperBitsZero and ARM64 IsRedundantMov precedents. Done means eliminating extensions after proven zero-producing instructions on both baseline and Zba paths while preserving required normalization for arbitrary 32-bit arithmetic and copies between registers.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- compilers, performance
- Issue type
- Refactor
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100