ARM la C convenție de apel, registre pentru a salva

A trecut ceva timp de când am fost ultimul asamblor de brațe codate și sunt puțin ruginit în privința detaliilor. Dacă numesc o funcție C din braț, trebuie doar să-mi fac griji pentru salvarea r0-r3 și lr, nu? Dacă funcția C folosește orice alte registre, este responsabilă pentru salvarea celor din stivă și restaurarea lor? Cu alte cuvinte, compilatorul ar genera codul pentru a face acest lucru pentru funcțiile C. De exemplu, dacă folosesc r10 într-o funcție de asamblare, nu trebuie să-i împing valoarea pe stivă sau în memorie și să-l restaurez după un apel C, nu-i așa?

Aceasta este pentru arm-eabi-gcc 4.3.0.

Îmi dau seama că aș putea citi întregul EABI, dar apoi scurtarea RTFM este ceea ce este SO, nu? :-)

0
Iată o legătură externă care poate fi utilă. Introducere APCS , mai ales unele nume diferite pentru utilizarea register .
adăugat autor artless noise, sursa

5 răspunsuri

To add up missing info on NEON registers:

Din AAPCS , §5.1.1 Registrele de bază:

  • r0-r3 are the argument and scratch registers; r0-r1 are also the result registers
  • r4-r8 are callee-save registers
  • r9 might be a callee-save register or not (on some variants of AAPCS it is a special register)
  • r10-r11 are callee-save registers
  • r12-r15 are special registers

Din AAPCS, §5.1.2.1 Convențiile de utilizare a registrelor VFP:

  • s16–s31 (d8–d15, q4–q7) must be preserved
  • s0–s15 (d0–d7, q0–q3) and d16–d31 (q8–q15) do not need to be preserved

Original post:
arm-to-c-calling-convention-neon-registers-to-save

0
adăugat

For 64-bit ARM, A64 (from Procedure Call Standard for the ARM 64-bit Architecture)

Există registre de treizeci și unu, de 64 de biți, cu scop general (întreg), vizibile setului de instrucțiuni A64; acestea sunt etichetate r0-r30 . Într-un context pe 64 de biți, aceste registre se referă în mod normal la utilizarea denumirilor x0-x30 ; într-un context pe 32 de biți, registrele sunt specificate utilizând w0-w30 . În plus, poate fi utilizat un registru cu indicatori de stivă, SP , cu un număr restrâns de instrucțiuni.

  • SP The Stack Pointer
  • r30 LR The Link Register
  • r29 FP The Frame Pointer
  • r19…r28 Callee-saved registers
  • r18 The Platform Register, if needed; otherwise a temporary register.
  • r17 IP1 The second intra-procedure-call temporary register (can be used by call veneers and PLT code); at other times may be used as a temporary register.
  • r16 IP0 The first intra-procedure-call scratch register (can be used by call veneers and PLT code); at other times may be used as a temporary register.
  • r9…r15 Temporary registers
  • r8 Indirect result location register
  • r0…r7 Parameter/result registers

Primele opt registre, r0-r7 , sunt folosite pentru a transfera valorile argumentului într-o subrutină și pentru a returna valorile rezultate dintr-o funcție. Ele pot fi, de asemenea, utilizate pentru a menține valori intermediare în cadrul unei rutine (dar, în general, numai între apelurile subrutine).

Registrele r16 (IP0) și r17 (IP1) pot fi utilizate de un linker ca registru de zgârieturi între o rutină și orice subrutină pe care o apelează. Ele pot fi de asemenea folosite într-o rutină pentru a menține valori intermediare între apelurile subrutine.

Rolul registrului r18 este specific platformei. Dacă o platformă ABI are nevoie de un registru general dedicat pentru a purta o stare inter-procedurală (de exemplu contextul firului), atunci ar trebui să utilizeze acest registru în acest scop. Dacă platforma ABI nu are astfel de cerințe, atunci ar trebui să utilizeze r18 ca registru temporar suplimentar. Specificația platformei ABI trebuie să documenteze utilizarea pentru acest registru.

SIMD

Arhitectura ARM pe 64 de biți are, de asemenea, încă treizeci și doi de registre, v0-v31 , care pot fi utilizate de operațiile SIMD și Floating Point. Numele exact al registrului se va schimba indicând mărimea accesului.

Note: Unlike in AArch32, in AArch64 the 128-bit and 64-bit views of a SIMD and Floating-Point register do not overlap multiple registers in a narrower view, so q1, d1 and s1 all refer to the same entry in the register bank.

Primele opt registre, v0-v7 , sunt folosite pentru a transfera valorile argumentului într-o subrutină și pentru a returna valorile rezultate dintr-o funcție. Ele pot fi, de asemenea, utilizate pentru a menține valori intermediare în cadrul unei rutine (dar, în general, numai între apelurile subrutine).

Registrele v8-v15 trebuie să fie păstrate de un apelant în cadrul apelurilor subrutine; registrele rămase ( v0-v7, v16-v31 ) nu trebuie să fie păstrate (sau ar trebui să fie păstrate de apelant). În plus, trebuie păstrate numai cele 64 de biți de jos ale fiecărei valori stocate în v8-v15 ; este responsabilitatea apelantului de a păstra valori mai mari.

0
adăugat

Depinde de ABI pentru platforma pe care o compilați. Pe Linux, există două ABI-uri ARM; cel vechi și cel nou. AFAIK, cel nou (EABI) este de fapt ARAP AAPCS. Definițiile complete ale EABI în prezent trăiesc aici aici pe ARM infocentru .

De la AAPCS, §5.1.1 :

  • r0-r3 are the argument and scratch registers; r0-r1 are also the result registers
  • r4-r8 are callee-save registers
  • r9 might be a callee-save register or not (on some variants of AAPCS it is a special register)
  • r10-r11 are callee-save registers
  • r12-r15 are special registers

Un registru de salvare trebuie salvat de apelant (în opoziție cu un registru de salvare, în cazul în care apelantul salvează registrul); dacă aceasta este ABI-ul pe care îl utilizați, nu trebuie să salvați r10 înainte de a apela altă funcție (cealaltă funcție este responsabilă pentru salvarea acesteia).

Edit: Which compiler you are using makes no difference; gcc in particular can be configured for several different ABIs, and it can even be changed on the command line. Looking at the prologue/epilogue code it generates is not that useful, since it is tailored for each function and the compiler can use other ways of saving a register (for instance, saving it in the middle of a function).

0
adăugat
"Puteți descărca întreaga specificație ABI și documentele sale justificative și exemplul de cod ca o arhivă ZIP din această pagină." Arhiva zip: infocenter.arm.com/help/topic /com.arm.doc.ihi0036b/bsabi.zip
adăugat autor jww, sursa
Mulțumesc, pare că sună niște clopote. Cred că primul "r0-r4" din lista dvs. este o tipo, nu? +1 (și probabil cel mai bun răspuns dacă nu există o întoarcere radicală)
adăugat autor richq, sursa
Eu doar trec prin acest document PCS și am această îndoială, registrele variabile v1-v8 sunt folosite pentru salvarea variabilelor locale, dacă da, ce se întâmplă când aloce mai multe variabile locale? Nu pot conecta stivă și aceste registre ...
adăugat autor Xavier Geoffrey, sursa
Da, a fost o greșeală (și nu singura, dar i-am fixat pe celelalte înainte de a da lovitura pentru prima dată - sau așa sper).
adăugat autor CesarB, sursa
Pentru a rezuma: Atunci când apelați o funcție C, registrele r0-r3, r12 (și poate r9) trebuie să fie salvate. Din experiența mea, gcc folosește r12 ca registru de zgârieturi în interiorul unei funcții și, prin urmare, nu este salvat de callee chiar dacă brațul/interlucrarea cu degetul mare nu este utilizat. În cazul interconectării, linkerul va genera un cod de adeziv care utilizează r12 dacă o funcție a brațului operează o funcție degetul mare.
adăugat autor Sven, sursa
Comentariul lui Alex este confuz, deoarece este din punctul de vedere al calleei. Întrebarea discutată aici este din punctul de vedere al apelantului. Un apelant NU trebuie să salveze r4-r11 atunci când apelează o funcție C. Funcția C (apelantul) va salva aceste registre. De asemenea, de ce nu clarifică nimeni dacă R9 trebuie să fie salvat de apelant sau nu? Cred că pentru un braț-eabi-gcc toolchain, r9 este salvat și callee. Cine poate indica o sursă de informații care rezolvă problema R9?
adăugat autor Sven, sursa
Cred că este mult mai ușor să vă amintiți că trebuie să salvați și să restaurați r4-r11 în cazul în care intenționați să le folosiți; de aceea sunt salvați prin callee.
adăugat autor amc, sursa
Pentru a extinde comentariul lui amorenoc: r4-r11 (poate cu excepția r9 ) poate fi considerat "sigur" atunci când apelează o funcție. r0-r3 nu va fi probabil păstrat după apelul funcției și în funcție de modul în care se face legătura nu va fi r12 (care poate fi folosit ca registru de zgârieturi).
adăugat autor Leo, sursa

Răspunsurile lui CesarB și Pavel au oferit citate de la AAPCS, dar rămân probleme deschise. Îi salvează pe raliu? Ce zici de r12? Ce zici de r14? În plus, răspunsurile au fost foarte generale și nu erau specifice pentru brațul de unelte arm-eabi așa cum a fost solicitat. Iată o abordare practică pentru a afla care registru sunt salvate și care nu sunt.

Următorul cod C conține un bloc de asamblare inline, care pretinde că modifică registrele r0-r12 și r14. Compilatorul va genera codul pentru a salva registrele solicitate de ABI.

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

Utilizați linia de comandă arm-eabi-gcc-4.7 -O2 -S -o-foo.c și adăugați comutatoarele pentru platformă (de exemplu, -mcpu = arm7tdmi ). Comanda va imprima codul de asamblare generat pe STDOUT. Poate arata cam asa:

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

Rețineți că codul generat de compilator salvează și restabilește r4-r11. Compilatorul nu salvează r0-r3, r12. Că restabilirea r14 (alias lr) este pur accidental, știu din experiență că codul de ieșire poate încărca și lr-ul salvat în r0 și apoi face "bx r0" în loc de "bx lr". Fie prin adăugarea -mcpu = arm7tdmi -mno-thumb-interwork , fie folosind -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}

Din nou, r4-r11 sunt salvate și restaurate. Dar r14 (alias lr) nu este restabilit.

A rezuma:

  • r0-r3 sunt nu salvate salvate
  • r4-r11 sunt salvate în callee
  • r12 (alias ip) este nu salvat-salvat
  • r13 (alias sp) este salvată prin callee
  • r14 (alias lr) este nu salvarea salvată
  • r15 (alias pc) este contorul de programe și este setat la valoarea lui lr înainte de apelul funcției

Aceasta este valabilă cel puțin pentru setările implicite ale arm-eabi-gcc. Există comutatoare de linie de comandă (în special comutatorul -mabi) care pot influența rezultatele.

0
adăugat
Ah, registrele sunt blocate pentru întreruperi din acest motiv. Oricum suntem de acord să nu suntem de acord.
adăugat autor artless noise, sursa
Văd că aveți un punct într-un sens ipotetic; Puteți scrie un asamblator care a returnat un indicator al funcției în lr . Cu toate acestea, nu văd ce ar face pentru dvs. valoarea de bază lr . Executați codul care a fost în lr la întoarcere, deci valoarea inițială este explicită prin executarea codului. Ei bine, sumarizați de Pavel ca "r12-r15" sunt registre speciale . Valoarea lr la apelare va fi valoarea codului pc la ieșire. Întrebarea dacă lr este restaurată sau nu pare bizară pentru mine. Depinde und
adăugat autor artless noise, sursa
Vedeți linkul ARM și indicatorul de cadru pentru detalii despre pc și lr . r12 este, de asemenea, cunoscut ca ip și poate fi folosit în timpul unui prolog și epilog . Este un registru volatil . Acest lucru este important pentru rutinele care parsează stiva/cadrele de apeluri.
adăugat autor artless noise, sursa
Analiza dvs. este in-corectă ; lr este afișat ca pc pentru a vă întoarce mai repede. Răspunsul la întrebarea r9 este în APCS . Se numește baza statică în acest document, iar secțiunea Cod Reentrant vs Non Reentrant este relativă. Aplicația APCS acceptă mai multe configurații, dar gcc este, în general, reîncărcată fără limite de stack . În special, Ex
adăugat autor artless noise, sursa
Aceasta este exact ceea ce căutam - o modalitate de a afla care registre sunt păstrate de setările compilatorului specifice pe care le folosesc pentru proiectul meu. Mulțumesc!
adăugat autor TonyK, sursa
În ce sens este incorect analiza mea referitoare la lr ? Cred că m-ai făcut greșit. Oricum, am prezentat cel de-al doilea fragment de cod de asamblare, primul arătând că lr a fost salvat. Cu toate acestea, cred că nu este. Da, în fragmentul al doilea, lr este afișat ca pc ca o modalitate mai rapidă de returnare și nu am explicat acest lucru, dar punctul de prezentare a celui de-al doilea fragment a fost că arată că lr nu este salvat.
adăugat autor Sven, sursa
Este adevărat că lr este restaurat la pc . Dar nu este adevărat că se poate aștepta ca valoarea lr însăși să fie restaurată. Nu văd cum poate fi greșit. Că valoarea se termină într-un registru care nu este lr este complet irelevant la întrebarea dacă lr este restabilită sau nu. Aveți dreptate că setul de registre care este restabilit și nu este restabilit se poate schimba pe măsură ce se va modifica opțiunea -mabi .
adăugat autor Sven, sursa
Chiar acum, scriu un wrapper de asamblare pentru întreruperi imbricate pe ARM7TDMI. Indiferent dacă valoarea lr este păstrată printr-un apel la codul C este importantă, deoarece valoarea lr trebuie să fie restabilită la valoarea sa anterioară de către warpper-ul asamblorului. Deci, dacă știm dacă lr este salvată de calle, nu este tocmai ipotetică.
adăugat autor Sven, sursa

Există, de asemenea, diferență cel puțin la arhitectura Cortex M3 pentru apelul funcțional și întreruperea.

Dacă se produce o întrerupere, se vor face împingerea automată R0-R3, R12, LR, PC pe Stack și atunci când se va întoarce IRQ automat POP. Dacă folosiți alte registre în rutina IRQ, trebuie să le împingeți/să le arătați manual pe Stack.

Nu cred că această operație automată PUSH și POP este făcută pentru o sesiune de funcții (instrucțiune de salt). Dacă convenția spune că R0-R3 poate fi folosit doar ca registru de rezumat, rezultat sau zgârieturi, nu este necesar să le stocați înainte de apelarea funcției deoarece nu ar trebui să existe nici o valoare mai târziu după returnarea funcției. Dar, la fel ca într-o întrerupere, trebuie să stocați toate celelalte registre ale procesorului dacă le folosiți în funcție.

0
adăugat