Какие регистры нужно сохранить в соглашении о вызовах ARM C?
Прошло много времени с тех пор, как я последний раз собирал кодовые манипуляторы, и я немного заржавел в деталях. Если я вызываю функцию C с руки, мне остается только беспокоиться о сохранении r0-r3 и lr, верно?
Если функция C использует какие-либо другие регистры, отвечает ли она за сохранение этих данных в стеке и их восстановление? Другими словами, компилятор сгенерирует код, чтобы сделать это для Си-функций.
Например, если я использую r10 в функции ассемблера, мне не нужно помещать его значение в стек или в память и вставлять / восстанавливать его после вызова C, не так ли?
Это для arm-eabi-gcc 4.3.0.
6 ответов
Это зависит от ABI для платформы, для которой вы компилируете. В Linux есть два ARM ABI; старый и новый. AFAIK, новый (EABI) на самом деле является AAPCS ARM. Полные определения EABI в настоящее время находятся здесь, на информационном центре ARM.
Из AAPCS, §5.1.1:
- r0-r3 - регистры аргумента и нуля; r0-r1 также являются регистрами результатов
- r4-r8 - регистры сохранения вызовов
- r9 может быть регистром сохранения вызываемого абонента или нет (в некоторых вариантах AAPCS это специальный регистр)
- r10-r11 - регистры сохранения вызовов
- R12-R15 являются специальные регистры
Регистр сохранения вызываемого абонента должен быть сохранен вызывающим абонентом (в отличие от регистра сохранения вызывающего абонента, где вызывающий сохраняет регистр); поэтому, если вы используете ABI, вам не нужно сохранять r10 перед вызовом другой функции (другая функция отвечает за ее сохранение).
Изменить: какой компилятор вы используете, не имеет значения; В частности, gcc может быть настроен для нескольких различных ABI и даже может быть изменен в командной строке. Глядя на код пролога / эпилога, который он генерирует, не так уж и полезно, поскольку он адаптирован для каждой функции, и компилятор может использовать другие способы сохранения регистра (например, сохранение его в середине функции).
Чтобы добавить недостающую информацию о регистрах 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) не нуждаются в сохранении
Исходное сообщение:
рука-к-с-вызывающему-конференц-неон-регистров к сэкономить
Для 64-битного ARM, A64 (из Стандарта вызова процедур для 64-битной архитектуры ARM)
Существует тридцать один 64-разрядный регистр общего назначения (целочисленный), видимый для набора команд A64; они помечены r0-r30. В 64-битном контексте эти регистры обычно упоминаются с использованием имен x0-x30; в 32-битном контексте регистры указываются с помощью w0-w30. Кроме того, регистр указателя стека SP может использоваться с ограниченным количеством инструкций.
- SP Stack Pointer
- 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 и с плавающей точкой. Точное имя регистра изменится с указанием размера доступа.
Примечание. В отличие от AArch32, в AArch64 128-разрядное и 64-разрядное представления регистра SIMD и плавающей запятой не перекрывают несколько регистров в более узком представлении, поэтому все q1, d1 и s1 ссылаются на одну и ту же запись в регистре. банка.
Первые восемь регистров, v0-v7, используются для передачи значений аргумента в подпрограмму и для возврата значений результата из функции. Они также могут использоваться для хранения промежуточных значений внутри подпрограммы (но, как правило, только между вызовами подпрограммы).
Регистры v8-v15 должны сохраняться вызываемым абонентом при вызовах подпрограмм; остальные регистры (v0-v7, v16-v31) не должны быть сохранены (или должны быть сохранены вызывающей стороной). Кроме того, должны быть сохранены только младшие 64 бита каждого значения, сохраненного в v8-v15; вызывающая сторона несет ответственность за сохранение больших значений.
Ответы 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 (псевдоним 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 (псевдоним lr) не восстанавливается.
Подвести итоги:
- r0-r3 не сохраняются
- r4-r11 сохранены вызываемыми
- r12 (псевдоним ip) не сохранен
- r13 (псевдоним sp) сохранен вызываемым абонентом
- r14 (псевдоним lr) не сохранен
- r15 (псевдоним pc) является счетчиком программы и устанавливается в значение lr до вызова функции
Это относится по крайней мере к стандартным параметрам arm-eabi-gcc. Есть ключи командной строки (в частности, ключ -mabi), которые могут влиять на результаты.
Также есть разница, по крайней мере, в архитектуре Cortex M3 для вызова функций и прерываний.
В случае прерывания произойдет автоматический толчок R0-R3,R12,LR, ПК в стек и при возврате из IRQ автоматический POP. Если вы используете другие регистры в процедуре IRQ, вы должны вручную вставить их в стек.
Я не думаю, что этот автоматический PUSH и POP сделан для вызова функции (инструкция перехода). Если соглашение гласит, что R0-R3 можно использовать только в качестве аргументов, результатов или чистых регистров, поэтому нет необходимости сохранять их до вызова функции, потому что не должно быть никакого значения, используемого позже после возврата функции. Но так же, как и в случае прерывания, вы должны хранить все остальные регистры процессора, если вы используете их в своей функции.