EmbDev.net

Forum: ARM programming with GCC/GNU tools gcc optimizer doesn't know swi is a function call?


von Bill B. (auldreekie)


Rate this post
useful
not useful
arm-elf-gcc optimizer thinks swi is basically a nop??

void foo (void)
{
  struct something s;
  s.randomArgument = 1;

  asm volatile ("mov r0,%0" : : \
      "r" (&s) : \
      "r0", "r12", "r14", "cc");
  asm volatile ("swi 13" : : );
}

If this is compiled with optimization-Os, s.randomArgument = 1 is
OMITTED and the software interrupt handler sees garbage inside structure
s.  Why?  I think it's because gcc doesn't know that swi is related to
branch/link.  It thinks swi doesn't need the values to which s points
any more than a nop would, so why assign them in the first place?

The problem is cured by making s a volatile.  But does anyone know if
there is a more elegant way to do this?  I have been trying to hide the
swi's inside function-call wrappers so the code looks like it's calling
an ordinary routine.  Using the volatile approach, I'd have to be sure
that structure addresses are always marked volatile if they're going to
be passed to a swi-based function (but not necessarily a normal
function)  -- ugh.

I could just mark all structures that could ever possibly get passed to
an SWI as volatile at the typedef level but that causes other headaches
and optimization could suffer.

Thanks for any wisdom anyone might have on this!

--Bill

von Bill B. (auldreekie)


Rate this post
useful
not useful
Another way to avoid the problem would be to put the swi call in its own
subroutine and mark it never to be inlined.  But what an annoying load
of overhead that would be...

von Bill B. (auldreekie)


Rate this post
useful
not useful
OK, I found a solution.  It is awful to look at but all the other
permutations I tried failed either when unoptimized, when optimized, or
when gcc 4.1.0 itself crashed during compilation (!!!).


static inline int Do_SWI_9 (int arg1, struct something *arg2)
{
  register long __res ;
  asm volatile ("mov r0,%2\n\tmov r1,%3" : \
      "=X" (*(char *)(long)arg1), "=X" (*(char *)(long)arg2) : \
      "r" ((long)arg1), "r" ((long)arg2) : \
      "r0", "r1");
  asm volatile ("swi 9" : : :
      "r0", "r12", "r14", "cc");
  asm volatile ("mov %0, r0" : "=r" (__res) : );
  return((int)__res);
}

The "=X" *(char *)(long)argx operands tell the compiler that argx points
to something that will be used, and this forces the optimizer to load
contents to the structure before calling Do_SWI_9, rather than optimize
them out in the mistaken belief that those contents are not used by
Do_SWI_9.  The crazy casting makes sure this works even if argx happens
to be a char instead of a long or a pointer (in a more general case than
the example shown here).

When unoptimized, this creates some pretty awful code, as the compiler
loads the values of arg1 and arg2 twice before copying them into r0 and
r1; however when optimized it's better, not great, but better.

I tried all sorts of more elegant approaches, including assigning the
__res variable to r0 and indicating that the swi command wrote a result
to __res, but none of them worked.  Believe it or not the above approach
was the only way I could get reliable compilation under both optimized
and unoptimized conditions.

Hope this helps somebody avoid the pain I just went through.

--Bill

von Bill B. (auldreekie)


Rate this post
useful
not useful
One last revision.  The return value "mov" assignment can be made
non-volatile and this tightens up the optimized code significantly, as
it allows the optimizer to omit assignment of the return value when not
used (which is the case for most of my swi calls).

static inline int Do_SWI_9 (int arg1, struct something *arg2)
{
  register long __res ;
  asm volatile ("mov r0,%2\n\tmov r1,%3" : \
      "=X" (*(char *)(long)arg1), "=X" (*(char *)(long)arg2) : \
      "r" ((long)arg1), "r" ((long)arg2) : \
      "r0", "r1");
  asm volatile ("swi 9" : : :
      "r0", "r12", "r14", "cc");
  asm ("mov %0, r0" : "=r" (__res) : );
  return((int)__res);
}

I tried the same thing with the parameter assignment, cutting it into
two separate non-volatile asm statements (one for each register), but
that crashed gcc.  Probably time for me to quit while I'm ahead.

--Bill

von Bill B. (auldreekie)


Rate this post
useful
not useful
OK there is yet another fix.  The above version doesn't work under
level-2 optimization; sometimes the return argument is not provided.  To
fix it requires REALLY fooling gcc:

static inline int Do_SWI_9 (int arg1, struct something *arg2)
{
  register long __res asm("r0");
  asm volatile ("mov r0,%2\n\tmov r1,%3" : \
      "=X" (*(char *)(long)arg1), "=X" (*(char *)(long)arg2) : \
      "r" ((long)arg1), "r" ((long)arg2) : \
      "r0", "r1");
  asm volatile ("swi 9 @ %0" : "=r" (__res) : :
      "r12", "r14", "cc");
  return((int)__res);
}

What's different?
(1) The __res variable is now explicitly placed in r0.
(2) The swi call is marked as returning a value in __res.
(3) The "@%0" comment after swi FOOLS asm() into thinking swi really
does affect r0.
(4) The "mov %0,r0" after the swi is omitted as it's no longer
necessary.

Is this voodoo or what?  But it appears to work great under level 2
optimization.

Please log in before posting. Registration is free and takes only a minute.
Existing account
Do you have a Google/GoogleMail account? No registration required!
Log in with Google account
No account? Register here.