ARM inline asm: exit system call with value read from memory

Extended-asm syntax requires writing %% to get a single % in the asm output. e.g. for x86:

asm("inc %eax")                // bad: undeclared clobber
asm("inc %%eax" ::: "eax");    // safe but still useless :P

%r7 is treating r7 as an operand number. As commenters have pointed out, just omit the %s, because you don’t need them for ARM, even with GNU as.


Unfortunately, there doesn’t seem to be a way to request input operands in specific registers on ARM, the way you can for x86. (e.g. "a" constraint means eax specifically).

You can use register int var asm ("r7") to force a var to use a specific register, and then use an "r" constraint and assume it will be in that register. I’m not sure this is always safe, or a good idea, but it appears to work even after inlining. @Jeremy comments that this technique was recommended by the GCC team.

I did get some efficient code generated, which avoids wasting an instruction on a reg-reg move:

See it on the Godbolt Compiler Explorer:

__attribute__((noreturn)) static inline void ASM_EXIT(int status)
{
  register int status_r0 asm ("r0") = status;
  register int callno_r7 asm ("r7") = 1;
  asm volatile("swi  #0\n"
      :
      : "r" (status_r0), "r" (callno_r7)
      : "memory"    // any side-effects on shared memory need to be done before this, not delayed until after
  );
  // __builtin_unreachable();  // optionally let GCC know the inline asm doesn't "return"
}

#define GET_STATUS() (*(int*)(some_address)) //gets an integer from an address

void foo(void) { ASM_EXIT(12); }
    push    {r7}    @            # gcc is still saving r7 before use, even though it sees the "noreturn" and doesn't generate a return
    movs    r0, #12 @ stat_r0,
    movs    r7, #1  @ callno,
    swi  #0
     # yes, it literally ends here, after the inlined noreturn

void bar(int status) { ASM_EXIT(status); }
    push    {r7}    @
    movs    r7, #1  @ callno,
    swi  #0                  # doesn't touch r0: already there as bar()'s first arg.

Since you always want the value read from memory, you could use an "m" constraint and include a ldr in your inline asm. Then you wouldn’t need the register int var asm("r0") trick to avoid a wasted mov for that operand.

The mov r7, #1 might not always be needed either, which is why I used the register asm() syntax for it, too. If gcc wants a 1 constant in a register somewhere else in a function, it can do it in r7 so it’s already there for the ASM_EXIT.


Any time the first or last instructions of a GNU C inline asm statement are mov instructions, there’s probably a way to remove them with better constraints.

Leave a Comment