| I completely agree with you on 0/1 vs 0/-1 for SLT and you can easily find me saying so. For example: https://lists.riscv.org/g/tech-bitmanip/message/496 It was an error, though a rather minor one, to follow the C language so closely. I can and have pointed out other minor mistakes in RISC-V in the past -- none of them serious enough to abandon it and start over. I'll quote myself from there, below. 32 bits is not such a huge instruction. ARM decided it's good enough for their new(ish) 64 bit ISA,
and it's about the average size of x86_64 instructions. Original RISC-V (v1.0) has only and exactly the instructions needed to implement C. That's enough for many or most applications, and will be available as a support option forever. The upcoming RVA22 specification for Applications Processors, which will be ratified before the end of the year includes an SVE-like vector extension and also Bit Manipulation extensions (along with many others). The Zbb (Basic bit-manipulation) extension includes cpop along with clz and ctz and rotate. There is also andn, orn, xnor, max, maxu, min, minu, sext.b, sext.h, zext.h, and rev8 (reverse bytes in a register). Plus a unique instruction orc.b which replaces any non-zero byte in the source operand with all ones. There is also scalar crypto and cache manipulation (prefetch, flush etc). Perhaps RVA22 is your hypothetical Risc-6. ----- There are five reasons you might use SLT / SLTU, in (I think) descending order of how common they are, and the implications had -1 been used instead of 1: 1) to generate a zero/non-zero value. No difference. 2) to generate a mask. Using 0 and -1 is better, saving a NEG or a subtract 1, depending on whether you reverse the condition or not. 3) to generate a value that can be AND / OR / XOT etc with other such values. No difference. 4) to assign to a canonical C/C++ true/false, or mix with them using AND / OR / XOR. Worse -- have to do an ANDI #1 before using the final result. 5) to generate a canonical C true/false and add or subtract it from something. No difference. Just flip add to subtract or vice versa. Interestingly, a time when you do want 0 or 1 is the examples in the original superoptimiser paper from 1987. https://web.stanford.edu/class/cs343/resources/superoptimize... They first considered the function: int signum (int x) {
if(x > 0) return I;
else if(x < 0} return -I;
else return 0;
)
They showed the superoptimiser finding the following unexpected 68020 sequence, making use of the carry flag: (x in dO)
add.l d0,d0 ;add dO to itself
subx.l dl,dl ;subtract (dl + Carry) from dl
negx.l dO ;put (0 - dO - Carry) into dO
addx.l dl,dl ;add (dl + Carry) to dl
(signum(x) in dl} (4 instructions}
This is much more straightforward on RISC-V: (x in a0)
slt a1,a0,zero # a1 = 1 if x is negative, 0 if 0 or positive
slt a0,zero,a0 # a0 = 1 if x is positive, 0 if 0 or negative
sub a0,a0,a1 # 1-0 = 1 if positive, 0-0 = 0 if zero, 0-1 = -1 if negative
----- |
AIUI, if SLT returns 0 or -1 you can then reverse the arguments to SUB and get a correct result. If you return the result in a1 you can also keep the 2-operand compressed form of SUB, so there's effectively no difference. Equivalently, you can keep the SUB insn unchanged (thus using a 2-operand form to return in a0) while flipping the previous SLT insns: SLT a1, zero, a0; SLT a0, a0, zero.