Я пишу RPC-библиотеку для AVR, и мне нужно передать адрес функции некоторому встроенному коду ассемблера и вызвать функцию из кода ассемблера. Однако ассемблер жалуется, когда я пытаюсь вызвать функцию напрямую.
Этот минимальный пример test.cpp иллюстрирует проблему (в данном случае я передаю аргументы, а функция является экземпляром статического члена шаблонного класса):
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)
Я перепробовал все даже отдаленно разумные ограничения на параметр для встроенного ассемблера, которые я мог найти Вот но никто из тех, кого я пробовал, не работал.
Я представляю, если часть 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-as не жалуется на мусор.
Обратите внимание, что call
ожидает постоянного значения, известного во время сборки. "p"
ограничение не включает эту семантику; это также позволило бы указатель от переменной (например, char* x
), который call
не выдерживаю. (Кажется, я помню, что иногда gcc достаточно умен, чтобы оптимизировать таким образом, чтобы «p» работал здесь — но это в основном недокументированное и недетерминированное поведение, поэтому лучше не рассчитывать на него.)
Если функция, которую вы вызываете, на самом деле является постоянной времени компиляции, вы можете использовать "i" (bar)
, Если это не так, то у вас нет другого выбора, кроме как использовать icall
как вы уже поняли.
Кстати, раздел AVR https://gcc.gnu.org/onlinedocs/gcc/Machine-Constraints.html#Machine-Constraints документирует еще некоторые, специфичные для AVR ограничения.
Я пробовал различные способы передачи имени функции C во встроенный код ASM без успеха. Однако я нашел обходной путь, который кажется обеспечить желаемый результат.
Ответ на вопрос:
Как объяснено на https://www.nongnu.org/avr-libc/user-manual/inline_asm.html Вы можете назначить имя ASM функции C в объявлении прототипа:
void bar (void) asm ("ASM_BAR"); // any name possible here
void bar (void)
{
return;
}
Затем вы можете легко вызвать функцию из своего кода ASM:
asm volatile("call ASM_BAR");
Используйте с библиотечными функциями:
Этот подход не работает с библиотечными функциями, потому что они имеют свои собственные объявления прототипов. Чтобы вызвать функцию как system_tick()
из time.h
библиотека более эффективно из ISR, вы можете объявить вспомогательную функцию. К сожалению, GCC не применяет встроенную настройку к вызовам из кода ASM.
inline void asm_system_tick(void) asm ("ASM_SYSTEM_TICK") __attribute__((always_inline));
void asm_system_tick(void)
{
system_tick();
}
В следующем примере GCC генерирует только инструкции push / pop для окружающего кода, но не для вызова функции! Обратите внимание, что 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");
}
}
Поскольку встроенный атрибут не работает, каждый вызов функции занимает 8 дополнительных циклов процессора. По сравнению с 5632 циклами ЦП, необходимыми для операций push / pull с обычным вызовом функции (44 цикла ЦП для каждого запуска ISR), это все еще очень впечатляющее улучшение.