Какви регистри да запишете в конвенцията за повикване на ARM C?

Мина известно време, откакто за последен път кодирах рамен асемблер и съм малко ръждясал в детайлите. Ако извикам C функция от arm, трябва да се тревожа само за запазването на r0-r3 и lr, нали?

Ако функцията C използва други регистри, тя отговаря ли за запазването на тези в стека и възстановяването им? С други думи, компилаторът ще генерира код, за да направи това за C функции.

Например, ако използвам r10 в асемблерна функция, не е нужно да натискам нейната стойност в стека или в паметта и да я изскачам/възстановявам след C извикване, нали?

Това е за arm-eabi-gcc 4.3.0.


person richq    schedule 04.11.2008    source източник
comment
Ето външна връзка, която може да бъде полезна. Въведение в APCS, особено някои различни имена за register употреба.   -  person artless noise    schedule 15.04.2013


Отговори (5)


Зависи от 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 и дори може да бъде променен от командния ред. Гледането на кода на пролога/епилога, който генерира, не е толкова полезно, тъй като той е пригоден за всяка функция и компилаторът може да използва други начини за записване на регистър (например, записването му в средата на функция).


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

person CesarB    schedule 04.11.2008
comment
Благодаря, това изглежда звучи добре. Мисля, че първото r0-r4 в списъка ви е печатна грешка, нали? +1 (и вероятно най-добрият отговор, освен ако няма радикален обрат) - person richq; 04.11.2008
comment
Да, това беше правописна грешка (и не единствената, но поправих другите, преди да натисна бутона за изпращане за първи път - или поне така се надявам). - person CesarB; 04.11.2008
comment
Можете да изтеглите цялата ABI спецификация и нейните подкрепящи документи и примерен код като ZIP архив от тази страница. Zip архив: infocenter.arm.com/help/topic /com.arm.doc.ihi0036b/bsabi.zip - person jww; 23.06.2011
comment
Мисля, че е много по-лесно да запомните, че трябва да запазите и възстановите r4-r11 в случай, че ще ги използвате; затова те са спасени от призован. - person ; 27.03.2012
comment
За да разширим коментара на amorenoc: r4-r11 (може би с изключение на r9) може да се счита за безопасно при извикване на функция. r0-r3 вероятно няма да се запази след извикването на функцията и в зависимост от това как е направено свързването, нито r12 (което може да се използва като скреч регистър). - person Leo; 14.04.2012
comment
Коментарът на Алекс е объркващ, тъй като е от гледна точка на обаждания. Обсъжданият тук въпрос е от гледна точка на обаждащия се. Повикващият НЕ трябва да запазва r4-r11, когато извиква C функция. Функцията C (извикваният) ще запази тези регистри. Освен това защо никой не изяснява дали r9 трябва да бъде запазен от обаждащия се или не? Вярвам, че за arm-eabi-gcc инструментална верига, r9 също се запазва от callee. Кой може да посочи източник на информация, който разрешава проблема с r9? - person Sven; 13.08.2013
comment
За да обобщим: Когато извиквате C функция, регистрите r0-r3,r12 (и може би r9) трябва да бъдат запазени. От моя опит, gcc използва r12 като скреч регистър вътре във функция и следователно не се запазва при извикване, дори ако не се използва взаимодействие между ръка и палец. В случай на взаимодействие, линкерът ще генерира слепващ код, който използва r12, ако функция arm извиква функция thumb. - person Sven; 13.08.2013
comment
Просто преглеждам този PCS документ и имам това съмнение, че променливите регистри v1-v8 се използват за запазване на локални променливи, ако е така, какво се случва, когато разпределя повече локални променливи? Не мога да свържа стека и тези регистри... - person Xavier Geoffrey; 12.10.2015

Конвенциите за 32-битови ARM извиквания са определени от AAPCS

From the AAPCS, §5.1.1 Core registers:

  • 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


Конвенциите за 64-битови ARM извиквания са определени от AAPCS64

General-purpose Registers section specifies what registers need be preserved.

  • r0-r7 са регистри за параметър/резултат
  • r9-r15 са временни регистри
  • r19-r28 са регистри, запазени от извиквания.
  • Всички останали (r8, r16-r18, r29, r30, SP) имат специално значение и някои може да се третират като временни регистри.

SIMD и регистри с плаваща запетая определя Neon и регистри с плаваща запетая.

person Pavel P    schedule 29.03.2011

За 64-битов ARM, A64 (от Procedure Call Standard за ARM 64-битова архитектура)

Има тридесет и един 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 и операции с плаваща запетая. Точното име на регистъра ще се промени, като се посочи размера на достъпа.

Забележка: За разлика от AArch32, в AArch64 128-битовите и 64-битовите изгледи на SIMD и регистър с плаваща запетая не припокриват множество регистри в по-тесен изглед, така че q1, d1 и s1 всички се отнасят до един и същ запис в регистъра банка.

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

Регистрите v8-v15 трябва да бъдат запазени от извиквания при извиквания на подпрограми; останалите регистри (v0-v7, v16-v31) не трябва да се запазват (или трябва да се запазват от повикващия). Освен това само долните 64 бита на всяка стойност, съхранена в v8-v15, трябва да бъдат запазени; отговорност на повикващия е да запази по-големите стойности.

person auselen    schedule 13.04.2015

Отговорите на 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 са запазени от callee
  • r12 (псевдоним ip) не е запазен от callee
  • r13 (псевдоним sp) е запазен за извиквания
  • r14 (псевдоним lr) не е запазен за извиквания
  • r15 (псевдоним pc) е програмният брояч и е зададен на стойността на lr преди извикването на функцията

Това важи поне за настройките по подразбиране на arm-eabi-gcc. Има превключватели на командния ред (по-специално превключвателят -mabi), които могат да повлияят на резултатите.

person Sven    schedule 13.08.2013
comment
Вашият анализ е неправилен; lr е изваден като pc за по-бърз начин за връщане. Отговорът на вашия r9 въпрос е в APCS. В този документ се нарича статична база и разделът Код за повторно влизане срещу код за повторно влизане е относителен. APCS поддържа няколко конфигурации, но gcc обикновено е re-entrant без ограничения на стека. По-специално Има специални роли за sb/r9 и sl/r10 в някои варианти на APCS. В други варианти те могат да се използват като регистри, запазени от извиквания - person artless noise; 28.10.2013
comment
Вижте ARM връзка и указател на рамка за подробности относно pc и lr. r12 е известен също като ip и може да се използва по време на пролог и епилог. Това е нестабилен регистър. Това е важно за рутинни процедури, които анализират стека/кадрите за повиквания. - person artless noise; 28.10.2013
comment
В какъв смисъл анализът ми относно lr е неправилен? Мисля, че ме разбрахте погрешно. Както и да е, представях втория асемблерен кодов фрагмент, тъй като първият изглеждаше така, сякаш lr беше запазен извиканият. Аз обаче мисля, че не е така. Да, във втория фрагмент lr се показва като pc като по-бърз начин за връщане и аз не обясних това, но смисълът на представянето на втория фрагмент беше, че той показва, че lr не е запазен извикваем. - person Sven; 27.08.2014
comment
Вярно е, че lr се възстановява на pc. Но не е вярно, че може да се очаква, че стойността на lr се възстановява. Не виждам как това може да е грешно. Това, че стойността завършва в регистър, който не е lr, е напълно без значение за въпроса дали lr се възстановява или не. Вие сте прав, че наборът от регистри, който е възстановен и не е възстановен, може да се промени, когато опцията -mabi се промени. - person Sven; 30.08.2014
comment
Виждам, че имате право в хипотетичен смисъл; Можете да напишете някакъв асемблер, който връща указател на функция в lr. Въпреки това, не виждам какво би направило за вас оригиналната стойност на lr. Изпълнявате кода, който е бил в lr при връщане, така че първоначалната му стойност е изрична от кода, който се изпълнява. Добре обобщено от Павел като r12-r15 са специални регистри. Стойността на lr при повикване ще бъде стойността на pc при изход. Въпросът дали lr е възстановен или не ми се струва странен. Зависи къде и дали лист или не. - person artless noise; 30.08.2014
comment
В момента пиша обвивка на асемблер за вложени прекъсвания на ARM7TDMI. Важно е дали стойността на lr е запазена чрез извикване на C-код, тъй като стойността на lr трябва да бъде възстановена до предишната си стойност от асемблера warpper. Така че знанието дали lr е запазено от callee не е толкова хипотетично. - person Sven; 02.04.2015
comment
А, регистрите са резервирани за прекъсвания точно поради тази причина. Както и да е, съгласни сме да не сме съгласни. - person artless noise; 02.04.2015
comment
Това е точно това, което търсих -- начин да разбера кои регистри се запазват от специфичните настройки на компилатора, които използвам за моя проект. Благодаря ти! - person TonyK; 07.02.2017
comment
@artlessnoise: Ако имаше конвенция, че функциите трябва да връщат със същата стойност в LR, с която са били извикани, тогава нещо като if (foo) do { } while(bar()); може да се обработи като bl foo / test r0,r0 / bne bar, а връщането от bar ще прехвърли изпълнението обратно към инструкцията след bl. На практика ситуациите, при които гарантирането на стойността в r14 при връщане на функция би било полезно, са достатъчно редки, така че е по-добре да му се позволи да съдържа произволна стойност. - person supercat; 06.11.2020

Има разлика поне в архитектурата Cortex M3 за извикване на функция и прекъсване.

Ако възникне прекъсване, то ще направи автоматично избутване на R0-R3,R12,LR,PC към стека и при връщане от IRQ автоматичен POP. Ако използвате други регистри в IRQ рутина, трябва ръчно да ги избутате/поставите в стека.

Не мисля, че това автоматично PUSH и POP е направено за извикване на функция (инструкция за прескачане). Ако конвенцията казва, че R0-R3 може да се използва само като аргумент, резултат или скреч регистри, така че няма нужда да ги съхранявате преди извикване на функция, защото не трябва да има стойност, използвана по-късно след връщане на функцията. Но също както при прекъсване, вие трябва да съхранявате всички други регистри на процесора, ако ги използвате във вашата функция.

person Frik    schedule 13.07.2017