-4

I am trying to read ARM registers via C using inline assembly but it doesn't follow the order of execution as it should.

volatile uint32_t var1 = 0;
volatile uint32_t var2 = 0;
volatile uint32_t var3 = 0;
volatile uint32_t var4 = 0;

__asm volatile(
    "mov r0, #5\n\t"
    "mov r1, #6\n\t"
    "mov r2, #7\n\t"
    "mov r3, #8\n\t"
            
    "mov %0, r0\n\t" 
    "mov %0, r1\n\t"
    "mov %0, r2\n\t" 
    "mov %0, r3\n\t"
            
    : "=r" (var1),
    "=r" (var2),
    "=r" (var3),
    "=r" (var4));

What happens is that the output is;

var1 = 8
var2 = 5
var3 = 6
var4 = 7

What I expect is;

var1 = 5
var2 = 6
var3 = 7
var4 = 8

It seems r0 is read last and it starts with r1. Why is that happening?

Note: Ignore the purpose of the code or how variables are defined, it's a copy/paste from a bigger application. It's just this specific register behavior in question.

old_timer
  • 69,149
  • 8
  • 89
  • 168
nurtul
  • 274
  • 1
  • 9
  • 19
  • 5
    Not a big expert in inline assembly, but aren't you supposed to tell the compiler that your assembly is clobbering the r0-r3 ? – Eugene Sh. Feb 10 '22 at 17:46
  • 4
    Not only that, the register allocation where var1..var4 are placed, is completely up to the compiler - one should also write at least `mov %0, r1; mov %1, r2; etc.` Otherwise you are just overwriting the same register 4 times. – Aki Suihkonen Feb 10 '22 at 17:47
  • @AkiSuihkonen Yeah that was the issue, the arguments need to be incremented. It works now, thanks! – nurtul Feb 10 '22 at 17:59
  • @EugeneSh. Clobbering is implicit I think, I added a clobber list at the end but it didn't change anything, I just needed to increment the arguments – nurtul Feb 10 '22 at 18:00
  • 1
    Clobbering is *not* implicit, and even `"r"` read-only inputs are assumed to be read-only. Eugene and zwol are correct: This code will step on GCC's toes in any function that does anything in pure C before/after it. (Including after being inlined into it). See https://stackoverflow.com/tags/inline-assembly/info. And if you say you're using `__attribute__((noinline))` or something and no other C around this, then you might as well just make it a `naked` function. – Peter Cordes Feb 10 '22 at 18:38

2 Answers2

1

GCC-style assembly inserts are designed to insert a single instruction per asm("..."), and they require you to give accurate information about all of the registers involved. In this case, you have not notified the compiler that registers r0, r1, r2, and r3 are used internally, so it probably thinks it's OK to reuse some of those for var1 through var4. You have also reused %0 as the destination of all four of the final mov instructions, so all four of them are actually writing to var1.

Also, it may or may not be your immediate problem, but volatile doesn't do what you think it does and probably isn't accomplishing anything useful here.

How to fix it? Well, first off, there needs to be a really strong reason why you can't just write

uint32_t var1 = 5;
uint32_t var2 = 6;
uint32_t var3 = 7;
uint32_t var4 = 8;

Assuming there is such a reason, then you should instead try writing one instruction per asm, and not using any scratch registers at all...

asm ("mov %0, #5" : "=r" (var1));
asm ("mov %0, #6" : "=r" (var2));
asm ("mov %0, #7" : "=r" (var3));
asm ("mov %0, #8" : "=r" (var4));

If you really absolutely have to do a whole bunch of work in a single asm then the first thing you should consider is putting it in a separate .S file and making it conform to the ABI so that you can call it like a normal function:

    .text
    .globl do_the_thing
    .type do_the_thing, @function
_do_the_thing:
    ; all your actual code here
    bx r14
.size do_the_thing, .-do_the_thing

and then in your C

   rv = do_the_thing(arg1, arg2, ...);

If there's no way to make that work, then, and only then, should you sit down and read the entire "Extended Asm" chapter of the GCC manual and work out how to wedge the complete register-usage behavior of your insert into the constraints. If you need help with that, post a new question in which you show the real assembly language construct you need to insert, rather than a vague example, because every little detail matters.

zwol
  • 135,547
  • 38
  • 252
  • 361
  • I know the code seems a bit ridiculous but it's all for a reason. Variables are updated in an interrupt and checked in another function hence the volatile. This was just a test to see if I can fetch the register values after directly manipulating them, it doesn't serve a purpose beyond that. Thanks for the info – nurtul Feb 10 '22 at 18:08
  • 1
    For the situation you describe, [global register variables](https://gcc.gnu.org/onlinedocs/gcc/Global-Register-Variables.html#Global-Register-Variables) may be a better solution. – zwol Feb 10 '22 at 18:10
  • @nurtul: zwol's answer is correct, but it wouldn't be that hard to fix your asm to still inefficiently do everything in one big asm statement, with clobbers on `"r0", "r1"`, etc. *and* use `%1` and so on for later outputs. (Or named output constraints). Since there are no inputs, you don't need `"=&r" (var2)` early-clobber operands. – Peter Cordes Feb 10 '22 at 18:45
  • @nurtul: If you insist on using hard-coded registers for something in your asm template, you could use asm *local* register variables to force `"=r"(var1)` to pick `r0`, so you don't need to clobber it and copy from a separate register. [ARM inline asm: exit system call with value read from memory](https://stackoverflow.com/a/37363860) – Peter Cordes Feb 10 '22 at 18:46
-1

The arguments passed into the inline assembly need to be incremented;

"mov %0, r0\n\t" 
"mov %1, r1\n\t"
"mov %2, r2\n\t" 
"mov %3, r3\n\t"
nurtul
  • 274
  • 1
  • 9
  • 19
  • 1
    For questions like this, please don't post answers that fix only one of the several problems with the code. – zwol Feb 10 '22 at 18:06
  • There are no other issues with the code. It was just a specific behavior question – nurtul Feb 10 '22 at 18:12
  • 1
    Not true; at the very least you need register clobbers. – zwol Feb 10 '22 at 20:14
  • Maybe if the registers are used more and further instructions are executed that use those registers. As it stands it is not needed – nurtul Feb 11 '22 at 00:57
  • @nurtul: Your question doesn't say you're doing this inside a function with `__attribute((noinline,noipa))`. That's the only case it would be sort of safe to destroy registers without telling the compiler about it. – Peter Cordes Aug 11 '23 at 00:26