我正在为AVR编写RPC库,需要将函数地址传递给某些内联汇编程序代码,并从汇编程序代码中调用该函数。但是当我尝试直接调用函数时,汇编程序会抱怨。
这个最小的例子test.cpp说明了这个问题(在实际情况下我传递的是args,而函数是模板化类的静态成员的实例化):
void bar () {
return;
}
void foo() {
asm volatile (
"call %0" "\n"
:
: "p" (bar)
);
}
使用avr-gcc -S test.cpp -o test.S -mmcu=atmega328p
进行编译工作正常,但当我尝试使用avr-gcc -c test.S -o test.o -mmcu=atmega328p
avr-as抱怨进行汇总时:
test.c: Assembler messages:
test.c:38: Error: garbage at end of line
我不知道它为什么写“test.c”,它所指的文件是test.S,它在第38行包含这个:
call gs(_Z3barv)
我已经尝试了对内联汇编程序的参数的所有甚至远程敏感的约束,我可以找到here,但没有一个我尝试过的。
我想如果删除了gs()部分,一切都应该有效,但所有约束似乎都会添加它。我不知道它的作用。
奇怪的是,像这样的间接调用组合就好了:
void bar () {
return;
}
void foo() {
asm volatile (
"ldi r30, lo8(%0)" "\n"
"ldi r31, hi8(%0)" "\n"
"icall" "\n"
:
: "p" (bar)
);
}
生成的汇编程序如下所示:
ldi r30, lo8(gs(_Z3barv))
ldi r31, hi8(gs(_Z3barv))
icall
而且avr-并没有抱怨任何垃圾。
答案 0 :(得分:1)
请注意call
需要一个常量,已知的汇编时值。 "p"
约束不包括该语义;它还允许来自char* x
无法处理的变量(例如call
)的指针。 (我似乎记得有时候gcc足够聪明,能够以这样的方式进行优化,以便在这里工作 - 但这基本上是无证件的行为和非确定性的,所以最好不要指望它。)
如果您实际调用的函数是编译时常量,则可以使用"i" (bar)
。如果不是,那么除了使用icall
之外别无其他选择。
顺便说一下,https://gcc.gnu.org/onlinedocs/gcc/Machine-Constraints.html#Machine-Constraints的AVR部分记录了一些特定于AVR的约束。
答案 1 :(得分:0)
我尝试了各种方法将C函数名称传递给内联ASM代码但没有成功。但是我确实找到了一种解决方法,似乎可以提供所需的结果。
回答问题:
如https://www.nongnu.org/avr-libc/user-manual/inline_asm.html所述,您可以在原型声明中为C函数指定ASM名称:
void bar (void) asm ("ASM_BAR"); // any name possible here
void bar (void)
{
return;
}
然后您可以从ASM代码轻松调用该函数:
asm volatile("call ASM_BAR");
与库函数一起使用:
这种方法不适用于库函数,因为它们有自己的原型声明。要从ISR更有效地调用system_tick()
库的time.h
函数,您可以声明一个辅助函数。遗憾的是,GCC不会将内联设置应用于来自ASM代码的调用。
inline void asm_system_tick(void) asm ("ASM_SYSTEM_TICK") __attribute__((always_inline));
void asm_system_tick(void)
{
system_tick();
}
在下面的例子中,GCC只生成周围代码的推/弹指令,而不是函数调用!请注意,system_tick()
是专门为ISR_NAKED
设计的,并且可以自行完成所有必需的堆栈操作。
volatile uint8_t tick = 0;
ISR(TIMER2_OVF_vect)
{
tick++;
if (tick > 127)
{
tick = 0;
asm volatile ("call ASM_SYSTEM_TICK");
}
}
因为inline属性不起作用,所以每个函数调用需要8个额外的cpu周期。与通过正常函数调用进行推/拉操作所需的5632个CPU周期(每次运行ISR需要44个CPU周期)相比,它仍然是一个非常令人印象深刻的改进。