1
votes

I'm writing an RPC library for AVR and need to pass a function address to some inline assembler code and call the function from within the assembler code. However the assembler complains when I try to call the function directly.

This minimal example test.cpp illustrates the issue (in the actual case I'm passing args and the function is an instantiation of a static member of templated class):

void bar () {
    return;
}

void foo() {
    asm volatile (
        "call %0" "\n"
        :
        : "p" (bar)
    );
}

Compiling with avr-gcc -S test.cpp -o test.S -mmcu=atmega328p works fine but when I try to assemble with avr-gcc -c test.S -o test.o -mmcu=atmega328p avr-as complains:

test.c: Assembler messages:
test.c:38: Error: garbage at end of line

I have no idea why it writes "test.c", the file it is referring to is test.S, which contains this on line 38:

call gs(_Z3barv)

I have tried all even remotely sensible constraints on the paramter to the inline assembler that I could find here but none of those I tried worked.

I imagine if the gs() part was removed, everything should work, but all constraints seem to add it. I have no idea what it does.

The odd thing is that doing an indirect call like this assembles just fine:

void bar () {
    return;
}

void foo() {
    asm volatile (
        "ldi r30, lo8(%0)" "\n"
        "ldi r31, hi8(%0)" "\n"
        "icall" "\n"
        :
        : "p" (bar)
    );
}

The assembler produced looks like this:

ldi r30, lo8(gs(_Z3barv))
ldi r31, hi8(gs(_Z3barv))
icall

And avr-as doesn't complain about any garbage.

2

2 Answers

1
votes

Note that call expects a constant, known-at-assembly-time value. The "p" constraint does not include that semantics; it would also allow a pointer from a variable (e.g. char* x), which call cannot handle. (I seem to remember that sometimes gcc is clever enough to optimize in such a way that "p" does work here - but that's basically undocumented behavior and non-deterministic, so better not count on it.)

If the function you're calling actually is compile-time constant you can use "i" (bar). If it's not, then you have no other choice than using icall as you already figured out.

Btw, the AVR section of https://gcc.gnu.org/onlinedocs/gcc/Machine-Constraints.html#Machine-Constraints documents some more, AVR-specific constraints.

0
votes

I've tries various ways of passing a C function name to inline ASM code without success. However I did find a workaround, which seems to provide the desired result.


Answer to the question:

As explained on https://www.nongnu.org/avr-libc/user-manual/inline_asm.html you can assign a ASM name to a C function in a prototype declaration:

void bar (void) asm ("ASM_BAR");    // any name possible here
void bar (void)
{
    return;
}

Then you can call the function easily from your ASM code:

asm volatile("call ASM_BAR");

Use with library functions:

This approach does not work with library functions, because they have their own prototype declarations. To call a function like system_tick() of the time.h library more efficiently from an ISR, you can declare a helper function. Unfortunately GCC does not apply the inline setting to calls from ASM code.

inline void asm_system_tick(void) asm ("ASM_SYSTEM_TICK") __attribute__((always_inline));
void asm_system_tick(void)
{
    system_tick();
}

In the following example GCC does only generate push/ pop instructions for the surrounding code, not for the function call! Note that system_tick() is specifically designed for ISR_NAKED and does all required stack operations on its own.

volatile uint8_t tick = 0;
ISR(TIMER2_OVF_vect)
{
    tick++;
    if (tick > 127)
    {
        tick = 0;
        asm volatile ("call ASM_SYSTEM_TICK");
    }
}

Because the inline attribute does not work, each function call takes 8 additional cpu cycles. Compared to 5632 CPU cycles required for push/ pull operations with a normal function call (44 CPU cycles for each run of the ISR) it is still a very impressive improvement.