Перейти к содержанию

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-байтовые данные в кадре.

Источники

  1. 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
  2. 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 байт
  3. Free Software Foundation. Using the GNU Compiler Collection (GCC), 6.4.2.4 ARM Attributes (GNU FDL) — атрибут interrupt (псевдоним isr) — функция-обработчик, компилятор создаёт для неё подходящие пролог и эпилог; на ARMv7-M тип прерывания игнорируется, а атрибут означает, что функцию могут вызвать с указателем стека, выровненным на слово

Нашли ошибку?