AAPCS — соглашение о вызовах Arm
Функция на C, вызванная из ассемблера, библиотека от другого компилятора, обработчик прерывания без единого атрибута — всё это работает потому, что код для Arm подчиняется одному соглашению о вызовах. Оно называется AAPCS и определяет, в каких регистрах передаются аргументы и какие регистры функция обязана вернуть нетронутыми.
Обновлено Редакция GAW
AAPCS (Procedure Call Standard for the Arm Architecture, 32-битная версия — AAPCS32) — соглашение Arm о вызове функций. Оно определяет, через какие регистры передаются аргументы и возвращается результат, какие регистры вызванная функция обязана сохранить, как выровнен стек. Код, который его соблюдает, можно смешивать в одной программе: объектные файлы разных компиляторов, библиотеки и ассемблерные функции. Спецификацию Arm публикует в открытом репозитории ARM-software/abi-aa.
Роли регистров
| Регистры | Роль по AAPCS | Кто сохраняет |
|---|---|---|
| r0–r3 | аргументы; r0 и r1 — результат | вызывающая функция |
| r4–r8, r10, r11 | локальные переменные | вызванная функция |
| r9 | платформенный регистр или ещё одна переменная | зависит от платформы |
| r12 (IP) | рабочий регистр; может портить компоновщик | вызывающая функция |
| r13 (SP), r14 (LR), r15 (PC) | стек, адрес возврата, счётчик команд | SP — вызванная функция |
| s0–s15 FPU | аргументы и рабочие | вызывающая функция |
| s16–s31 FPU | сохраняемые | вызванная функция |
По разделам Core registers и VFP register usage conventions AAPCS32. Аргументы, которым не хватило r0–r3, передаются через стек. Результат меньше 4 байт расширяется до слова и возвращается в r0, 64-битные значения вроде long long и double — в паре r0 и r1. При аппаратном FPU есть вариант соглашения VFP: числа с плавающей точкой передаются и возвращаются в регистрах FPU (s0, d0), и код этого варианта совместим с базовым, только пока функции не обмениваются такими значениями. Флаги N, Z, C, V и Q на входе и выходе функции считаются неопределёнными — полагаться на них между вызовами нельзя.
Стек
На входе в публичную функцию указатель стека должен быть кратен 8: SP mod 8 = 0. Это нужно, чтобы данные размером 8 байт в кадре функции оставались выровненными. Внутри функции SP может быть выровнен только на 4, но перед следующим вызовом выравнивание восстанавливают. Начальное значение стека задаёт таблица векторов, и его тоже берут кратным 8 — об этом в статье «Код запуска микроконтроллера».
AAPCS и исключения Cortex-M
Модель исключений Cortex-M построена под AAPCS. При входе в исключение ядро аппаратно сохраняет восемь слов: R0–R3, R12, LR, адрес возврата и xPSR (Armv7-M ARM, разд. B1.5.6). Это в точности регистры, которые по AAPCS имеет право испортить любая функция, — значит, обработчик может быть обычной функцией на C, а r4–r8, r10 и r11 он сохранит сам, если использует; r9 — тоже, если платформа отводит его под переменную, а не под свой особый регистр. Так как исключение приходит в произвольный момент, SP может оказаться не кратным 8, и архитектура предусматривает выравнивание стека при входе в исключение. Подробно о том, что лежит на стеке при входе, — в термине «Кадр стека исключения», о самих регистрах — в термине «Регистры ядра Cortex-M».
Частые вопросы
Какие регистры можно портить в ассемблерной функции, вызываемой из C?
По AAPCS свободно портить можно r0–r3 и r12, а также флаги: их сохраняет вызывающая сторона, если они ей нужны. Регистры r4–r8, r10, r11 и SP функция обязана вернуть такими, какими получила; обычно их кладут в стек командой PUSH в начале и снимают POP в конце. С FPU то же правило для s16–s31: их сохраняет вызванная функция.
Почему обработчик прерывания Cortex-M можно написать как обычную функцию на C?
Потому что при входе в исключение ядро само кладёт на стек ровно те регистры, которые AAPCS разрешает портить: R0–R3, R12, LR, адрес возврата и xPSR (Armv7-M ARM, разд. B1.5.6). Остальные регистры обработчик-функция сохранит сам, как любая функция по AAPCS. Поэтому на Cortex-M обработчику прерывания не нужно специальное ключевое слово. Атрибут GCC __attribute__((interrupt)) при этом существует, но на Armv7-M тип прерывания в нём игнорируется, а сам атрибут лишь означает, что функцию могут вызвать с указателем стека, выровненным только на 4 байта (руководство GCC, разд. 6.4.2.4 «ARM Attributes»).
Зачем стек выравнивают на 8 байт?
AAPCS требует, чтобы на входе в публичную функцию SP делился на 8: функции полагаются на естественное выравнивание 8-байтовых данных — long long и double — в своём кадре. Прерывание может прийти в момент, когда SP выровнен только на 4, поэтому Armv7-M умеет при входе в исключение дополнительно выравнивать стек. Если выравнивание нарушить, ломается код, который рассчитывает на выровненные 8-байтовые данные в кадре.
Источники
- Arm. Procedure Call Standard for the Arm Architecture (AAPCS32), IHI 0042, release 2026Q3, ARM-software/abi-aa — разд. Core registers, табл. «Core registers and AAPCS usage»: r0–r3 (a1–a4) — аргументы и рабочие регистры, r0 и r1 — ещё и результат; r4–r8, r10, r11 (v1–v5, v7, v8) — регистры переменных; r9 — платформенный регистр (SB, TR) или v6; r11 — указатель кадра в части вариантов; r12 — IP, рабочий регистр между вызовами, который может портить компоновщик; r13 SP, r14 LR, r15 PC; подпрограмма обязана сохранить r4–r8, r10, r11 и SP (и r9, если он v6); флаги N, Z, C, V, Q и GE[3:0] не определены на входе и выходе публичного интерфейса; разд. VFP register usage conventions: s16–s31 (d8–d15) сохраняются через вызов, s0–s15 (d0–d7) нет; разд. Stack constraints at a public interface: SP mod 8 = 0; разд. Parameter Passing: базовый стандарт передаёт аргументы в r0–r3 и через стек; разд. The Standard Variants, VFP: значения с плавающей точкой передаются и возвращаются в регистрах VFP (s0, d0, q0), код варианта VFP совместим с базовым, если нет аргументов и результатов с плавающей точкой; разд. Result return: тип меньше 4 байт расширяется до слова и возвращается в r0, слово — в r0, двойное слово (long long, double) — в r0 и r1, составной тип не больше 4 байт — в r0
- Arm. Armv7-M Architecture Reference Manual (ARM DDI 0403E.e, ID021621) — разд. B1.5.6 Exception entry behavior: сохранённый на стеке контекст поддерживает AAPCS, поэтому обработчик исключения может быть AAPCS-совместимой процедурой; при входе аппаратура сохраняет восемь 32-битных слов: xPSR, адрес возврата, LR (R14), R12, R3, R2, R1, R0; с FP-расширением — ещё состояние FPU или место под него; разд. B1.5.7 Stack alignment on exception entry: исключение может прийти, когда SP не выровнен на 8 байт, а AAPCS требует 8-байтового выравнивания на входе в функцию, поэтому система должна обеспечить выравнивание стека; функция по AAPCS сохраняет естественное выравнивание данных размером 1, 2, 4 и 8 байт
- Free Software Foundation. Using the GNU Compiler Collection (GCC), 6.4.2.4 ARM Attributes (GNU FDL) — атрибут interrupt (псевдоним isr) — функция-обработчик, компилятор создаёт для неё подходящие пролог и эпилог; на ARMv7-M тип прерывания игнорируется, а атрибут означает, что функцию могут вызвать с указателем стека, выровненным на слово
Нашли ошибку?
Смотрите также
- Регистры ядра Cortex-MR0–R12, SP, LR, PC, xPSR и специальные регистры: что в каждом
- Кадр стека исключенияВосемь регистров, которые Cortex-M сам сохраняет в стек при входе в исключение
- СтекОбласть ОЗУ для адресов возврата и локальных переменных
- FPU (блок плавающей точки)Аппаратные операции с float в Cortex-M4F: регистры, CPACR, ленивое сохранение
- Код запускаТаблица векторов, Reset_Handler, .data и .bss, VTOR, FPU, .initN avr-libc