Какие регистры нужно сохранить в соглашении о вызовах ARM C?

Прошло довольно много времени с тех пор, как я последний раз собирал кодовые манипуляторы, и я немного заржавел в деталях. Если я вызываю функцию C из руки, мне остается только беспокоиться о сохранении r0-r3 и lr, верно?

Если функция C использует какие-либо другие регистры, отвечает ли она за сохранение этих данных в стеке и их восстановление? Другими словами, компилятор сгенерирует код, чтобы сделать это для C-функций.

Например, если я использую r10 в функции ассемблера, мне не нужно помещать его значение в стек или в память и вставлять/восстанавливать его после вызова C, не так ли?

Это для arm-eabi-gcc 4.3.0.

Ответ 1

Это зависит от ABI для платформы, для которой вы компилируете. В Linux есть два ARI ABI; старый и новый. AFAIK, новый (EABI) на самом деле ARM AAPCS. Полные определения EABI в настоящее время находятся здесь, в информационном центре ARM.

Из AAPCS, §5.1.1:

  • r0-r3 - регистры аргументов и нуля; r0-r1 также являются регистрами результатов
  • r4-r8 - регистры сохранения вызовов
  • r9 может быть регистром сохранения вызываемого абонента или нет (в некоторых вариантах AAPCS это специальный регистр)
  • r10-r11 - регистры сохранения вызываемого абонента
  • r12-r15 - специальные регистры

Регистр сохранения вызываемого абонента должен быть сохранен вызывающим абонентом (в отличие от регистра сохранения вызывающего абонента, где вызывающий сохраняет регистр); поэтому, если вы используете ABI, вам не нужно сохранять r10 перед вызовом другой функции (другая функция отвечает за ее сохранение).

Изменить: Какой компилятор вы используете, не имеет значения; В частности, gcc может быть настроен для нескольких различных ABI, и его можно изменить даже в командной строке. Рассматривать код пролога/эпилога, который он генерирует, не так уж и полезно, поскольку он адаптирован для каждой функции, и компилятор может использовать другие способы сохранения регистра (например, сохранение его в середине функции).


Терминология: "callee-save" является синонимом "энергонезависимой" или "сохраняемой связи": Что такое сохраненные регистры вызываемых и вызывающих абонентов?
Делая вызов функции, вы можете предположить, что значения в r4-r11 (за исключением, может быть, r9) все еще остаются после (сохраненные вызовом), но не для r0-r3 (call-clobbered/volatile).

Ответ 2

Чтобы добавить отсутствующую информацию в регистры NEON:

Из AAPCS, §5.1.1 Основные регистры:

  • r0-r3 - регистры аргументов и царапин; r0-r1 также являются реестрами результатов
  • r4-r8 - регистры сохранения вызываемого абонента
  • r9 может быть регистром сохранения вызываемого абонента или нет (в некоторых вариантах AAPCS это специальный регистр)
  • r10-r11 - регистры сохранения вызываемого абонента
  • r12-r15 - специальные регистры

Из AAPCS, §5.1.2.1 Соглашения об использовании регистра VFP:

  • s16-s31 (d8-d15, q4-q7) должен быть сохранен
  • s0-s15 (d0-d7, q0-q3) и d16-d31 (q8-q15) не нужно сохранять

Оригинальный пост:
arm-to-c-calling-convention-neon-registers-to-save

Ответ 3

Для 64-битного ARM, A64 (из стандартного вызова процедуры для 64-битной архитектуры ARM)

Существует тридцать один, 64-разрядный, универсальный (целочисленный) регистр, видимый для набора команд A64; они помечены r0-r30. В 64-битном контексте эти регистры обычно называются именами x0-x30; в 32-битном контексте регистры задаются с помощью w0-w30. Кроме того, может быть использован регистр указателя стека SP с ограниченным числом инструкций.

  • SP Указатель стека
  • r30 LR Регистр ссылок
  • r29 FP Указатель фрейма
  • r19... r28 Регистры, сохраненные с помощью Callee
  • r18 Регистр платформы, если это необходимо; в противном случае это временный регистр.
  • r17 IP1 Второй временной регистр временного вызова (может использоваться винирами вызова и кодом PLT); в другое время может использоваться как временный регистр.
  • r16 IP0 Первый регистрационный регистр внутри процедуры (может использоваться по вызову виниры и PLT-код); в другое время может использоваться как временный регистр.
  • r9... r15 Временные регистры
  • r8 Непрямой регистр местоположения результата
  • r0... r7 Регистры параметров/результатов

Первые восемь регистров r0-r7 используются для передачи значений аргументов в подпрограмму и для возврата значений результата из функции. Они также могут использоваться для хранения промежуточных значений в рамках процедуры (но, в общем, только между вызовами подпрограмм).

Регистры r16 (IP0) и r17 (IP1) могут использоваться компоновщиком как регистр царапин между подпрограммой и любой подпрограммой, которую он вызывает. Они также могут использоваться в рамках процедуры для хранения промежуточных значений между вызовами подпрограмм.

Роль регистра r18 является специфичной для платформы. Если для платформы ABI необходим специальный регистр общего назначения для переноса межпроцедурного состояния (например, контекст потока), то он должен использовать этот регистр для этой цели. Если платформа ABI не имеет таких требований, тогда она должна использовать r18 в качестве дополнительного временного регистра. Спецификация платформы ABI должна документировать использование этого регистра.

SIMD

64-разрядная архитектура ARM также имеет еще тридцать два регистра, v0-v31, которые могут использоваться операциями SIMD и Floating-Point. Точное имя регистра изменится, указав размер доступа.

Примечание.. В отличие от AArch32, в AArch64 128-битные и 64-битные представления регистра SIMD и Floating-Point не перекрывают несколько регистров в более узком представлении, , поэтому q1, d1 и s1 все относятся к одной и той же записи в банке регистров.

Первые восемь регистров v0-v7 используются для передачи значений аргументов в подпрограмму и для возврата значений результата из функции. Они также могут использоваться для хранения промежуточных значений внутри процедуры (но, в общем, только между вызовами подпрограмм).

Регистры v8-v15 должны быть сохранены вызываемым вызовом под вызовом подпрограммы; остальные регистры (v0-v7, v16-v31) не нужно сохранять (или должны быть сохранены вызывающим). Кроме того, необходимо сохранить только нижние 64-бит каждого значения, хранящиеся в v8-v15; ответственность за сохранение больших значений несет вызывающий абонент.

Ответ 4

Ответы CesarB и Pavel предоставили цитаты из AAPCS, но остаются открытыми проблемы. Сохраняет ли заказчик r9? Как насчет r12? Как насчет r14? Кроме того, ответы были очень общими и не были специфическими для инструментальной цепочки arm-eabi в соответствии с запросом. Здесь практический подход, чтобы выяснить, какой регистр спасен, а какие нет.

Следующий код C содержит встроенный блок сборки, который утверждает, что модифицирует регистры r0-r12 и r14. Компилятор будет генерировать код для сохранения регистров, требуемых ABI.

void foo() {
  asm volatile ( "nop" : : : "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7", "r8", "r9", "r10", "r11", "r12", "r14");
}

Используйте командную строку arm-eabi-gcc-4.7 -O2 -S -o - foo.c и добавьте коммутаторы для вашей платформы (например, -mcpu=arm7tdmi). Команда будет печатать сгенерированный код сборки в STDOUT. Это может выглядеть примерно так:

foo:
    stmfd   sp!, {r4, r5, r6, r7, r8, r9, sl, fp, lr}
    nop
    ldmfd   sp!, {r4, r5, r6, r7, r8, r9, sl, fp, lr}
    bx  lr

Обратите внимание, что сгенерированный код компилятора сохраняет и восстанавливает r4-r11. Компилятор не сохраняет r0-r3, r12. То, что он восстанавливает r14 (alias lr), является чисто случайным, поскольку по опыту я знаю, что код выхода также может загрузить сохраненный lr в r0, а затем сделать "bx r0" вместо "bx lr". Либо добавив -mcpu=arm7tdmi -mno-thumb-interwork, либо используя -mcpu=cortex-m4 -mthumb, мы получим немного другой код сборки, который выглядит следующим образом:

foo:
    stmfd   sp!, {r4, r5, r6, r7, r8, r9, sl, fp, lr}
    nop
    ldmfd   sp!, {r4, r5, r6, r7, r8, r9, sl, fp, pc}

Снова, r4-r11 сохраняются и восстанавливаются. Но r14 (alias lr) не восстанавливается.

Подводя итог:

  • r0-r3 не сохранены в памяти
  • r4-r11 сохранены в памяти
  • r12 (alias ip) не сохраняется в памяти
  • r13 (alias sp) сохранен в памяти
  • r14 (alias lr) не сохранен в памяти
  • r15 (alias pc) - это программный счетчик и устанавливается значение lr перед вызовом функции

Это выполняется, по крайней мере, по умолчанию для arm-eabi-gcc. Существуют переключатели командной строки (в частности, переключатель -mabi), которые могут влиять на результаты.

Ответ 5

Существует также разница, по крайней мере, в архитектуре Cortex M3 для вызова функции и прерывания.

Если произойдет Прерывание, он будет автоматически нажимать R0-R3, R12, LR, PC на Stack и при возврате IRQ автоматически POP. Если вы используете другие регистры в процедуре IRQ, вам нужно вручную нажимать/выталкивать их на стек.

Я не думаю, что это автоматический PUSH и POP для вызова функции (команда перехода). Если соглашение говорит, что R0-R3 может использоваться только как регистр аргументов, результатов или царапин, поэтому нет необходимости хранить их перед вызовом функции, потому что не должно быть никакого значения, используемого позже после возврата функции. Но так же, как при прерывании, вы должны хранить все остальные регистры CPU, если используете их в своей функции.