1

I would like to store the result of a C expression into register a0 in a RISC-V program using GCC (for the sake of the examples below, suppose there is a volatile int val = 42 or similar). I am able to accomplish this with inline assembly when explicitly invoking a mv instruction:

asm ("mv a0, %0\n\t" :: "r" ((val == 0) ? 1 : val) : "a0");

However, the move would be unnecessary if the result would already be calculated "in" the right register. I have tried various things using register variable as described in the documentation but failed. For example, this following gets optimized away completely:

register volatile int res asm("a0") = (val == 0) ? 1 : val;

The same with an "empty" asm statement:

register int res asm("a0") = (val == 0) ? 1 : val;
asm volatile ( "" /* just store */ : "=r" (res) );

Is there a better way to do this? Do I overlook something?

stefanct
  • 2,503
  • 1
  • 28
  • 32
  • Would a simple if statement not suffice instead of a ternary? Or would it not make a difference? – Irelia Jun 06 '21 at 15:04
  • I don't see why this would make a difference? It is also beside the point I think - one could replace that ternary expression with a constant one. @phuclv: I am not sure that the riscv tag is useful here at all. I stated the question more generally for a reason: I am not interested in solutions only applicable to RISC-V. AFAICT the behavior with x86 is the same. – stefanct Jun 06 '21 at 15:55
  • 1
    Your "empty" asm statement attempt has an output-only operand, so the compiler assumes it overwrites the value of `res`, whose previous value is thus dead and need not be computed. What if you make it an input operand instead? – Nate Eldredge Jun 06 '21 at 18:10
  • But anyway, why are you doing this? For all you know, the compiler may overwrite that register with the very next instruction it emits after the asm, so what was the point? If this is part of some larger goal, perhaps you could explain it - sounds like an XY problem as it stands. – Nate Eldredge Jun 06 '21 at 18:11
  • 1
    As Nate says, it's not clear why you are doing this. Pretty sure gcc can generate decent code for all this without asm. As a general run I'd recommend that you [don't use inline asm](https://gcc.gnu.org/wiki/DontUseInlineAsm). That said, how about `asm ("" :"+r" (res));`? You only need the `volatile` if you need to calculate `res` even if you never end up using it (which seems weird). If you don't use res, the optimizer will feel free to delete the asm unless you use volatile. – David Wohlferd Jun 06 '21 at 19:38

1 Answers1

2

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.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847