0
votes

I'm trying to call an external function in c by an asm inline with a branch. I'm compiling to an arm m0 instruction set but it returns bad expression.

The code is:

__asm volatile (
                "   cmp     r3,#0                   \n"                     
                "   b %[my_function]                \n" //Call function
                "   bx r14                          \n"
                : // no output
                : [my_function] "i" (my_function) // input
                : "r0" // clobber
            );

The return is:

/tmp/ccICkDIE.s: Assembler messages:
/tmp/ccICkDIE.s:152: Error: bad expression -- `b #my_function'

What we need to do?

2
Why do you tag my_function as immediate argument? And how would may_function return, as you only branch? The following line will never be executed. - too honest for this site
Create a very short C program that calls my_function. Then compile with the -S option and take a look at the assembly that the compiler generates. - user3386109
This code works at Cortex-M4 instruction set, but not at Cortex-M0. Olaf - Geancarlo Abich
This not solve my problem. user3386109 - Geancarlo Abich
Given that the b mnemonic takes a symbol as an argument, not an immediate, the appropriate constraint would appear to be S, if just writing "b my_function\n" in the asm isn't good enough. The fact that this code won't assemble is at least saving you the fun of debugging the "bogus clobber list" and/or "tail call with unknown return address and without popping the stack frame" issues... - Notlikethat

2 Answers

0
votes

You want the BL instruction. This is "branch and link". It does a jump and stores the return address in r14.

But, you've still got a problem ... When you do BL, it destroys the r14 that you need. You've still got more work to do, even after the following:

stmfd   sp!,{v1-v6,lr}              // preserve caller registers
bl      %[my_function]              // call function
ldmfd   sp!,{v1-v6,pc} @std         // restore caller registers and return

You'll need more investigation. You might want to disassemble the compiled function and see what wrapper goes around your inline asm and adjust accordingly. It may do the stmfd/ldmfd for you. Try marking r14 as a clobber.

You may be better off with just the BL. The BX without restore might produce an infinite loop or unpredictable results. I'd leave it off

0
votes

After composing the below, I remembered the ethernut tutorial. He has virtually the same answer,

asm volatile(
    "mov lr, %1\n\t"
    "bx %0\n\t"
    : : "r" (main), "r" (JMPADDR));

The OP would do well to read this tutorial; even though it is for traditional ARM as opposed to an 'm0'.


You may use the 'r' constraint to place the address in a register and branch to it.

An example can be found on the online compiler godbolt.

extern int my_function(void);

void f(void)
{
__asm volatile (
                "   cmp     r3,#0                   \n"                     
                "   b %[my_function]                \n" //Call function
                "   bx r14                          \n"
                : // no output
                : [my_function] "r" (my_function) // input
                : "r0" // clobber
            );

 }

With the output,

f():
    ldr r3, .L2
       cmp     r3,#0                   
   b r3                
   bx r14                          

    bx  lr
.L2:
    .word   my_function()

We can see several issues with the output. r14 is lr and the b r3 will transfer control directly and return to the caller of f. The cmp r3, #0 seems completely un-needed (given limited context of question).

The sample above answers the question and it could be used for a tail-call macro or other use, but it obviously needs some work. A function pointer like,

int (*g)(void) = my_function;

will also work as the parameter 'my_function' to the GCC extended assembler.

Another method is just to use 'C' macro string concatenation. Here is a starting sample,

#define xstr(s) str(s)
#define str(s) #s
#define TAIL_CALL(func) __asm volatile(" b  " str(func) "\n")

For most code sizes (jump distance), the branch will be able to resolve (4MB?). If you use the function pointer method, then there is no issue.