You were close, but "=r"(res) told the compiler that the old value of res was dead, so it didn't materialize it anywhere.
register ... asm("regname") local vars are only guaranteed to do anything when used as operands of an Extended asm statement. If the asm statement itself is asm volatile, then it can't be optimized away even if its inputs are unused. (Don't make the register ... asm variable volatile; that's not useful.)
So, to force the compiler to materialize the result of your expression in a0 (e.g. for a microbenchmark where you want to partially defeat optimization for some reason, otherwise https://gcc.gnu.org/wiki/DontUseInlineAsm):
register int res asm("a0") = (val == 0) ? 1 : val;
// then read that reg var, forcing the compiler to actually materialize it
asm volatile("# just a comment, input picked %0" : : "r"(res));
This is portable to other compilers that support GNU C inline asm, including clang.
Demo on Godbolt (I tweaked the asm template to use a nop in front of the comment so the compiler-explorer comment filter wouldn't remove it. An empty template string wouldn't change anything; that's only there to confirm that the compiler "knows" what register it put res in.)
void foo(int dummy, int val) {
register int res asm("a3") = (val == 0) ? 1 : val;
asm volatile("nop # just a comment, input picked %0" : : "r"(res));
}
I picked a3 and a void function to make sure GCC would have no reason to happen to pick that register by chance, or even to materialize res at all; I'm not returning it.
# RISC-V gcc 10.2 -O3
foo:
mv a3,a1
bne a1,zero,.L2
li a3,1
.L2:
nop # just a comment, input picked a3
# if we had returned res, we'd have mv a0,a3 here
ret
Note that this asm statement has no output operands and no clobbers, so the compiler knows it has no effect on any registers or memory.
If I'd used "=r"(res), that would tell the compiler the original value of res was dead, so there was no need to materialize it in that register before the asm statement executed and produced whatever the new value was going to be. Just like if you'd written res = rand(); or something, the previous value of res wouldn't need to even be calculated if nothing ever read it.
"+r"(res) would tell the compiler that the asm instructions read that register and then write a new value there. You could use this to hide a constant from the optimizer, for example, so the compiler would have to assume that uses of res after this were a totally unknown runtime variable, even if you'd used register int res asm("a0") = 1;
Getting it wrong, without asm()
int bar(int dummy, int val) {
register int res asm("a3") = (val == 0) ? 1 : val;
return res;
}
With a0, happens to "work" only because a0 is the return-value register, and we're returning res (to stop it optimizing away).
bar: # same computation, but without using a3
mv a0,a1
bne a1,zero,.L5
li a0,1
.L5:
ret
And if I didn't return res, it would fully optimize away the calculation of an unused variable. volatile register int res asm("a3") can't change that.