[RISCV] Use P extension for narrow unsigned scalar arithmetic
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
[Godbolt link](https://godbolt.org/#g:!((g:!((g:!((h:codeEditor,i:(filename:'1',fontScale:14,fontUsePx:'0',j:1,lang:___c,selection:(endColumn:2,endLineNumber:4,positionColumn:1,positionLineNumber:1,selectionStartColumn:2,selectionStartLineNumber:4,startColumn:1,startLineNumber:1),source:'%23include+%3Cstdint.h%3E%0Auint8_t+f(uint8_t+x,+uint8_t+y)+%7B%0A++++return+x+%2B+y%3B%0A%7D'),l:'5',n:'0',o:'C+source+%231',t:'0')),k:48.512932000521346,l:'4',m:100,n:'0',o:'',s:0,t:'0'),(g:!((h:compiler,i:(compiler:rv32-cclang,filters:(b:'0',binary:'1',binaryObject:'1',commentOnly:'0',debugCalls:'1',demangle:'0',directives:'0',execute:'1',intel:'0',libraryCode:'0',trim:'1',verboseDemangling:'0'),flagsViewOpen:'1',fontScale:14,fontUsePx:'0',j:2,lang:___c,libs:!(),options:'-O3+-menable-experimental-extensions+-march%3Drv32imacp0p21',overrides:!(),selection:(endColumn:12,endLineNumber:4,positionColumn:12,positionLineNumber:4,selectionStartColumn:1,selectionStartLineNumber:1,startColumn:1,startLineNumber:1),source:1),l:'5',n:'0',o:'+RISC-V+rv32gc+clang+(trunk)+(Editor+%231)',t:'0')),header:(),k:51.48706799947866,l:'4',m:100,n:'0',o:'',s:0,t:'0')),l:'2',n:'0',o:'',t:'0')),version:4)
Source:
```c
#include
uint8_t f(uint8_t x, uint8_t y) {
return x + y;
}
```
Flags:
```
-O3 -menable-experimental-extensions -march=rv32imacp0p21
```
Compile with version 98fe06cb648:
```asm
f:
add a0, a0, a1
zext.b a0, a0
ret
```
Missed optimization; the optimal compilation would be:
```asm
f:
padd.b a0, a0, a1
ret
```
This transformation is correct because:
* ABI specifies bits `31:8` of `a0` and `a1` are initially 0 ([reference](https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/e03d44ae2f0e1144f9498c2896b5ae25b0449398/riscv-cc.adoc?plain=1#L182-L184))
* `padd.b` severs the carry-out from bit 7 ([reference](https://github.com/riscv/riscv-p-spec/blob/master/P-ext-proposal.adoc#paddb)).
* Therefore bits `31:8` remain 0 after the packed add, making the `zext.b` superfluous.
As far as I can tell, this applies to unsigned 8- and 16-bit arithmetic for any instruction which has a packed equivalent in the P extension. It's a performance win if `padd.b` has the same latency as `add` and `zext.b`.
The example I gave is a minimal one. More commonly I see this in arithmetic done on `lbu` loads from `uint8_t` struct fields (so the known-zero comes from the load instruction, not from ABI guarantees).
In this example it's size-neutral (assuming Zca + Zcb). In the general case it's often a code size win: `zext.b` is one of the most statically common instructions in embedded firmware, and often misses compression since it's required to be in `x8`-`x15` *and* destructive.
Contributor guide
Research direction
Reproduce the minimal C example from the Godbolt link with the stated LLVM flags and compare the emitted assembly. Trace how the RISC-V backend handles narrow unsigned arithmetic and the P-extension packed equivalent. Done means the applicable cases emit the packed operation without the redundant zero extension, while preserving the described ABI and load-based cases.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c
- Domain
- compilers, embedded-iot
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 46/100