> store(int*, int, int):
> slli a1,a1,2 // writes to a1
> add a0,a0,a1 // writes to a0
> sw a2,0(a0)
> ret
Three points:
1) you could argue that's bad code generation, which could easily be fixed in the compiler, as `a1` could be used as the result of the `add`
2) since late 2021 RISC-V has the `sh2add` instruction (and shifts of 1 and 3, and also versions that widen and shift a 32 bit unsigned value on a 64 bit CPU) that replaces the `slli` and `add`, thus naturally clobbering only one register. These instructions are already present in the popular RISC-V boards that use the JH7110 SoC (VisionFive 2, Star64, Mars, PineTab-V, Roma)
3) using a function like this to prove anything actually verges on dishonest (and erincandescent did the same thing) because no real program where anyone cares about performance will have such a function in hot code. If you care about the performance it's going to be because it's in a loop, and the function will be inlined, and strength-reduced, and the actual code will look like:
sw a2,(a0)
addi a0,a0,4
I have in the past proposed adding simple reg+reg indexing to loads (only), as that fits within the 2r1w framework and loads are more numerous than stores. Similarly, stores could write the effective address back to the base register, which would also fit in the 2r1w framework.
This would allow something like ...
void arrAdd(int *dst, int *src1, int *src2, int n) {
for (int i=0; i<n; ++i)
dst[i] = src1[i] + src2[i];
}
... to be compiled as ...
arrAdd:
sh2add a3,a3,a0 // just past end of dst
addi a0,a0,-4 // bias for incr by 4 in the store
sub a1,a1,a0 // bias for indexed addressing
sub a2,a2,a0
1:
lw a4,(a1,a0)
lw a5,(a2,a0)
add a4,a4,a5
swwb a4,4(a0) // store AND a0 = a0+4
blt a0,a3,1b
ret
This is the same number of instructions as now (no code size saving) but three fewer instructions in the loop.
> For any implementation above the purely educational level, 3+ register file read ports is the norm, but AFAIK the RISC-V authors have claimed that the base integer ISA will never require more than two source operands. I think that this philosophy weighs very heavily - more so than optimal performance.
It gives optimal price-performance. Having three read ports is rather expensive, and nowhere near justified by the number of integer instructions that would ever actually make use of the 3rd port, even if you added indexed stores, conditional selects, double-width shifts with dynamic shift count, multiply-add. Three read ports are justified in FP code as multiply-add is the fundamental and by far most popular instruction in FP code and besides IEEE rules require a single rounding in a multiply-add.