Differences

This shows you the differences between two versions of the page.

Link to this comparison view

cpl:labs:07 [2015/12/30 14:38]
silviu.grigore [TableGen]
cpl:labs:07 [2016/11/15 02:35] (current)
bogdan.nitulescu [Prototipuri de funcții]
Line 1: Line 1:
-===== 07. LLVM backend (new architectures) ​=====+====== 07. Code generation. Function calls and stack frames ======
  
-===== Nice to read =====+====== ​Apelul de funcţii ​====== 
 +===== Transmiterea parametrilor ===== 
 +În limbajele de nivel înalt, exista câteva modalități de transmitere a parametrilor și de întoarcere a rezultatelor,​ dintre care amintim:  
 +  * apelul prin valoare 
 +  * apelul prin referinta  
 +  * apelul prin rezultat 
 +  * apelul prin valoare-rezultat 
 +  * apelul prin nume. 
 +<​note>​ Folosim termenul de argument sau **argument actual** pentru a ne referi la expresia care este trimisă unei rutine și termenul de parametru sau **parametru formal** pentru a referi variabila căreia această expresie îi este asociată în rutina apelată </​note>​ 
 +==== Apelul prin valoare ==== 
 +Fiecare argument este evaluat și valoarea lui este pusă la dispoziție procedurii apelate în parametrul formal corespunzător. ​
  
-  * [[http://​llvm.org/​docs/​CodeGenerator.html|The LLVM Target-Independent Code Generator]] +În timpul execuției procedurii, nu există nicio interacțiune cu variabilele apelantuluiExcepția apare în cazul în care argumentul este un pointer și procedura apelată ar putea folosi valoarea pointerului pentru ​modifica valorile către care indică acesta
-  * [[https://​www.google.com/​url?​sa=t&​rct=j&​q=&​esrc=s&​source=web&​cd=1&​cad=rja&​uact=8&​ved=0CB0QFjAAahUKEwjzt_fS3tbIAhWn_XIKHbiXBkM&​url=http%3A%2F%2Fllvm.org%2Fdevmtg%2F2014-04%2FPDFs%2FTalks%2FBuilding%2520an%2520LLVM%2520backend.pdf&​usg=AFQjCNFzRa244bFKgvM2bPNFE0SHbubtyA&​sig2=CB3kPXTrEvUbHhJ2-jMaBw|Building an LLVM backend]] +
-  * [[http://​llvm.org/​devmtg/​2009-10/​Korobeynikov_BackendTutorial.pdf|Building ​ +
-backend in 24 hours]] +
-  * [[http://​jonathan2251.github.io/​lbd/​|Creating an LLVM Backend for the Cpu0 Architecture]] +
-  * [[http://​llvm.org/​docs/​TableGen/​|TableGen]]+
  
 +Apelul prin valoare se implementează de obicei prin copierea valorii fiecărui argument în parametrul corespunzător la intrarea în procedura apelată. Aceasta modalitate poate fi foarte eficientă în cazul argumentelor care pot fi ținute în regiștri, dar foarte ineficientă pentru tipuri de date compuse - vectori, structuri, obiecte - deoarece presupune un transfer de date semnificativ în memorie.
  
-===== Introducere ===== +Apelul prin valoare este singurul mecanism ​de pasare a parametrilor ​în limbajul ​C. Un parametru poate fi însă ​și adresa unui obiect; se obține astfel efectul apelului prin referință.
-Backend-ul ​de LLVM primește cod scris în limbajul ​intermediar ​și generează asamblare. +
-Binarul este llc.+
  
-Pentru a face trecerea de la codul IR la asamblareeste nevoie ​de 4 transformări:​+În C++, la transferul unui obiect prin valoare se va crea o copie a acelui obiectsi se va apela constructorul ​de copiere, daca acesta exista.
  
-LLVM IR -SelectionDAG -MachineDAG -> MachineInstr -> MCInst+<code>class C; 
 +void f(C a);            // parametrul a se transmite prin valoare 
 +void g() { C x; f(x); } // se apeleaza constructorul C::C(const C&) ce copiaza x in a 
 +</code>
  
-==== SelectionDAG ​==== +==== Apelul prin referință ​==== 
-Reprezintă un graf în care operațiile sunt reprezentate ca noduri. +Realizează o asociere între argumentul actual ​și parametrul corespunzatorProcedura apelată va avea acces total la argument pe toată perioada execuției saleputând să schimbe parametrul actual sau să îl transmită mai departe altor proceduri
-Majoritatea nodurilor sunt independente de arhitectură,​ dar pot exista ​și noduri custom. +
-Fiecare nod are un opcodecare este definit (pentru nodurile independente de arhitectură) în enum-ul [[http://​llvm.org/​docs/​doxygen/​html/​namespacellvm_1_1ISD.html|ISD::​NodeType]] +
-Dacă este nevoie de noduri specifice pentru o arhitectură,​ ele se definesc începând cu ISD::​BUILTIN_OP_END. +
-Se poate observa ca există mai multe opcode-uri decât instrucțiuni llvm.+
  
-Arcele din graf reprezintă dependințe. +Ca implementare tipică, la intrarea în procedura apelată se determină adresa argumentului și aceasta se pune la dispoziția procedurii ca mijloc de accesare a argumentului. Acest mecanism este foarte eficient când vectori ​sunt transmiși ca parametri, dar poate fi ineficient în cazul parametrilor de mărime mică care ar putea fi transmiși prin regiștri
-Ele pot fi: +
-  - de dateo operație folosește un rezultat produs de o altă operație;​ +
-  - chain, ordonează operații; +
-  - glue, operațiile ​sunt împreună.+
  
-Valorile din acest graf au tipuri (e.g. i32).+Apelul prin referință poate cauza un //alias// - o situație în care aceeași locație de memorie poate fi accesată folosind două nume simbolice diferiteUn exemplu (C#) : 
 +<code c">​void f(ref int i, ref int j) { i++; j++; } 
 +void g({ int x = 1; f(x,x); /* x == 3 */ }</​code>​
  
-==== MachineDAG ==== +Pot apărea probleme în cazul în care o constantă este transmisă prin referință;​ procedura apelată ar putea modifica acea constantă, ceea ce este ilegal. Soluția uzuală este copierea constantelor ​în noi locații de memorie și transmiterea acestor adrese când se face apelul prin referință
-Este un graf foarte asemănător cu SelectionDAG, în care nodurile sunt înlocuite cu instrucțiuni+
  
-==== MachineInstr ==== +Apelul prin referința ​este implementat implicit în Fortran, sau explicit via sintaxă specială în C++C# sau PHPAcelași efect se poate obține în C și C++ prin transmiterea adresei ca valoare.
-MachineDAG-ul ​este transformat într-listă de instrucțiuni dependente de arhitectură, cu câteva excepții (e.ginstrucțiunea COPY).+
  
-==== MCInst ==== +<​note>​ 
-Este o reprezentare simplificatăcare conține un opcode ​și niște operanzi+În JavaJavaScript sau Python, parametrii sunt transmiși prin valoare, la fel ca și în C. Acest lucru este valabil atât pentru primitive cât și pentru obiecte.
  
-===== Structura de fișiere ===== +Afirmația **obiectele se transmit prin referință** este greșită. Deoarece variabilele din Java conțin referințe către obiecte și nu obiectele în sine, corect este **referințobiectelor este transmisă prin valoare**
-Pentru ​înregistra un backend, trebuie modificate urmatoarele fișiere: +</note>
-  CMakeLists.txt +
-  ​autoconf/config.sub +
-  autoconf/​configure.ac +
-  configure +
-  include/​llvm/​ADT/​Triple.h +
-  include/​llvm/​Object/​ELFObjectFile.h +
-  include/​llvm/​Support/​ELF.h +
-  lib/​Support/​Triple.cpp +
-  lib/​Target/​LLVMBuild.txt+
  
-Hint: Căutați în aceste fișiere apariții corespunzătoare unui target existent (ex Hexagon).+Codul urmator ​C++ folosește apelul prin referință pentru a interschimba valorile variabilelor a șb. Codul similar Java, însă, folosește apelul prin valoare și lasă variabilele neschimbate.
  
-Implementarea se face în directorul lib/​Target/​X,​ care trebuie să conțină, printre altele și următoarele fișiere: +**C++** 
-^ Nume           ^ Semnificație ​  ^ +<code c++>void swap(Obj &aObj &b{ Obj t=a; a=b; b=t; } 
-| XTargetMachine | Implementarea unei clase care extinde LLVMTargetMachine. Conține ​**XPassConfig** care adaugă pașii de transformare. Cel mai important pas este **createXISelDag** | +void f() { Obj a,b; swap(a,b); } </​code>​
-| XSubtarget ​    | Extinde o clasă generată de tablegen, care definiește un subtarget. | +
-| XISelLowering ​ | Definește modul in care se face lowering la un SelectionDAG legal. | +
-| XISelDAGToDAG ​ | Este responsabil cu trecerea de la SelectionDAG la MachineDAG | +
-| XRegisterInfo ​ | Conține informații despre regiștrii ​(regștrii rezervațispeciali, salvați de calling convention). Metoda care trebuie implementată este **eliminateFrameIndex**,​ care inlocuiește accese abstracte la stivă ​(e.g. FI -3cu o adresă concretă (e.g. -20 (SP)) | +
-| XInstrInfo ​    | Informații și transformări pe instrucțiuni. Cele mai importante metode care trebuiesc implementate sunt **copyPhysReg****storeRegToStackSlot**,​ **loadRegFromStackSlot**,​ **expandPostRAPseudo** | +
-| XFrameLowering | Responsabil cu calcularea stivei folosite și modificarea ei la intrarea ​(**emitPrologue**) și ieșirile (**emitEpilogue**) din funcție. | +
-| XAsmPrinter ​   | Emiterea codului și datelor. Deși conține Asm în numedumparea se face la un stream abstract care poate fi text (pentru asamblaresau obiect (dacă se generează direct elf). |+
  
-===== TableGen ===== +**Java** 
-Informațiile dependente de arhitectură sunt definite în fișiere speciale cu extensia ``.td`` într-o manieră declarativă.+<code java>​void swap(Obj a, Obj b) { Obj t=a; a=b; b=t; } 
 +void f() { Obj a,b; swap(a,b); } </​code>​
  
-Aceste fișiere sunt procesate ​de utilitarul **table-gen**care generează cod C++ pe baza acestor fișiere.+==== Apelul prin rezultat ==== 
 +Este similar celui prin valoare, cu deosebirea că aici se întorc valorile ​de la apelat la apelant. La intrarea în procedura apelată nu se întamplă nimiciar la întoarcere valoarea unui parametru apelat prin rezultat este pusă la dispoziția apelantului prin copierea valorii lui în argumentul asociat. Acest tip de apel apare în cazul parametrilor de tip out din Ada sau C#: 
 +<code c">​ 
 +void a(out int result) 
 +{ result = 7; /* daca lipseste atribuirea in result, da eroare de compilare */ }  
 +[...] 
 +// exemplu de folosire: 
 +int rez; 
 +a(out rez); // acum rez a primit valoarea din a. 
 +</​code>​
  
-Cele 2 concepte importante din limbaj sunt clasele ​și definițiile.+==== Apelul prin valoare-rezultat ==== 
 +Este caracterizat de reuniunea proprietăților celor două tipuri de apel: prin valoare ​și prin rezultat. La intrarea în procedura apelată valoarea argumentului se copiază în parametru, iar la ieșire valoarea parametrului se copiază înapoi în argument. Este implementat în Fortran sau Ada pentru parametri de tip inout, și apare și în cazul apelurilor de procedură pe un alt computer (//RPC - remote procedure calls//). Diferenta față de apelul prin referință este că nu mai apar situații de alias între argumente.
  
-**Clasele** sunt folosite pentru ​grupa caracteristici comune ale resurselorO clasă poate să extindă una sau mai multe clase. Cu excepția unor clase de baza (Instruction,​ Register, CallingConv,​ CalleeSavedRegs, ​ș.a.), care definesc semantica unor resurse, clasele ​nu apar in fișierele C++ generate.+==== Apelul prin nume ==== 
 +Este un mecanism de evaluare întârziată ​argumentelor unei funcții (//lazy evaluation//​)Este similar cu transmiterea prin referință, însă argumentele actuale sunt evaluate de fiecare dată când parametrul formal este folosit ​șnu doar o singură dată la intrarea în procedură.
  
-Pentru a defini un registru R0extindem clasa **Register**:​ +Astfeldacă argumentul este a[i], și valoarea lui i se schimbă între două folosiri ale argumentului,​ la cele două folosiri se vor accesa ​de fapt două elemente diferite ale vectorului.
-<​code>​ +
-// Clasa CplReg extinde clasa Register. +
-// Parametrii sunt transmiși printr-o manieră care seamana cu template-urile din C++. +
-class CplReg<​bits<​5>​ num, string n, list<​string>​ alt = [], +
-                   ​list<​Register>​ alias = []> : Register<​n>​ { +
-  let Namespace = "​Cpl"; ​    // Namespace este un câmp din Register în care +
-                             // vor fi grupate registrele în codul generat. +
-  field bits<​5>​ Num;         // Field al clasei curente în care salvăm numărul registrului +
-  let Aliases = alias; ​      // Listă de alias-uri pentru registru +
-  let HWEncoding{4-0} = num; // Codificarea registrului +
-}+
  
-// Ri - 32-bit integer registers. +Mecanismul este rar în limbajele moderne, dar a fost folosit istoric (ALGOL 60)Un exemplu: 
-// Această clasă nu se reflectă ​în fișierul generat+<code pascal> 
-class Ri<bits<5num, string n, list<​string>​ alt = []> : CplReg<​num,​ n, alt> { +begin 
-  let Num = num; +  integer array a[1..2]; integer i;
-}+
  
-// Definim R0R1 și R2 prin intermediul claselor Ri și DwarfRegNum +  procedure f(xj); 
-def R0 : Ri<0"​r0">, ​ DwarfRegNum<​[0]>​+    ​integer xj
-def R1 Ri<1, "​r1">, ​ DwarfRegNum<​[1]>+    ​begin 
-def R2 : Ri<2, "​r2">​ ​DwarfRegNum<​[2]>+      integer k; 
-//...+      k := x; 
 +      j := j+1
 +      x := j; 
 +      f := k; 
 +    end; 
 + 
 +  i := 1; 
 +  a[1] := 5
 +  a[2] := 8;  
 +  outinteger(a[1]f(a[i],i), a[2])
 +end
 </​code>​ </​code>​
 +În exemplul de mai sus în care i și a[i] sunt transmiși de către programul principal procedurii f, prima folosire a parametrului x aduce valoarea lui a[1], pe cand cea de-a doua folosire seteaza valoarea a[2]. Apelul outinteger() va avea ca rezultat afișarea valorilor „5 5 2”. Dacă s-ar fi folosit apelul prin referința, atunci s-ar fi afișat „5 5 8”.
  
-Pentru ​defini o instrucțiuneextindem clasa **Instruction**:​ +O variantă ​apelului prin nume este folosită în unele limbaje funcționaleîn care evaluarea unui argument se face o singură datădar la prima folosire a acestuiași nu la intrarea în funcție.
-<​code>​ +
-class InstCpl<​dag outsdag insstring asmstr>​ +
-  : Instruction { +
-  let Namespace = "​Cpl";​+
  
-  dag OutOperandList = outs;  // lista operanzilor destinație 
-  dag InOperandList = ins;    // lista operanzilor sursă 
-  let AsmString = asmstr; ​    // string-ul corespunzător sintaxei 
-  let Size = 4;               // dimensiunea în octeți (pentru generare obiect) 
  
-  bits<​32>​ Inst 0         // codificarea binară a instrucțiunii +===== Convenții de apel ===== 
-  ​bits<​32>​ SoftFail; +Apelarea unei proceduri dintr-o altă procedură presupune existența unui '​protocol'​ prin care se trece controlul de la apelant la apelat, prin care parametrii sunt pasați în aceeași direcție iar valoarea rezultatului este pasata de la apelat la apelant. În cazul unui model simplu de runtime, execuția unei proceduri constă în cinci faze, fiecare dintre acestea având mai multe subfaze: 
-}+  * Asamblarea argumentelor ce trebuie transferate procedurii și pasarea controlului. 
 +    * Fiecare argument este evaluat și pus în registrul sau locația de pe stivă corespunzatoareevaluare poate înseamna calcularea adresei lui (pentru cei pasați prin referință), valoarea lui (pentru cei pasați prin valoare), etc. 
 +    * Se stabilește adresa codului procedurii (pentru cele mai multe limbaje însă, ​fost stabilită la compilare sau linkare) 
 +    * Regiștrii care au fost folosiți și vor mai fi și după întoarcerea din procedura apelată, se stochează în memorie dacă protocolul specifică că este datoria apelantului sa facă acest lucru 
 +    * Se salvează adresa de întoarcere și se execută un salt la adresa codului procedurii (de obicei o instrucțiune ''​call''​ face aceste lucruri) 
 +  ​* Prologul procedurii, executat la întrarea în procedură, stabilește mediul necesar adresării și poate salva regiștrii folosiți de procedură în scopuri proprii 
 +  * Se executa procedura, care la rândul ei poate apela alte proceduri 
 +  * Epilogul procedurii restaurează valorile regiștrilor și mediul de adresare al apelantului,​ asamblează valoarea pe care trebuie să o întoarcă și îi redă controlul apelantului 
 +    * Regiștrii salvați de procedura apelată sunt restaurați din memorie 
 +    * Valoarea care trebuie întoarsă se pune în locul corespunzator (dacă procedura întoarce o valoare) 
 +    * Se incarcă adresa de revenire și se executa un salt la această adresă (de obicei, o instrucțiune ''​ret''​ face acest lucru) 
 +  * Codul din procedura apelantă care se afla după apel își termină restaurarea mediului sau de execuție și primește valoarea intoarsă 
 +    * Regiștrii salvați de către procedura apelantă sunt restaurați din memorie 
 +    * Se folosește valoarea întoarsă de procedura apelată.
  
-// Instrucțiunea va fi identificată prin enum-ul Cpl::ADD în codul generat. +===== Cadre de stiva (stack frames=====
-def ADD : InstCpl<​ +
-     (outs IntRegs:$rc)+
-     (ins IntRegs:​$ra,​ IntRegs:​$rb),​ +
-     "​add $rc, $ra, $rb" +
->; +
-</​code>​+
  
-Generarea de cod se face prin intermediul clasei **Pat** care mapează noduri din **SelectionDAG** ​în **MachineDAG**+Deși ar fi ideal ca toți operanzii să fie ținuți ​în regiștri, majoritatea procedurilor necesită spatiu de memorie pentru
-<​code>​ +  * variabilele care fie nu au primit regiștri, fie nu pot fi ținute în regiștri ​pentru ​că le sunt încărcate adresele (explicit sau implicitca in cazul parametrilor transferati prin referință) sau pentru că trebuie să fie indexabile 
-// Nu avem nevoie de nume pentru ​pattern, așcă îl definim anonim+  * oferi un loc "​standard"​ pentru ​salva valorile din regiștri atunci când se execută un apel de procedură 
 +  * a oferi unui debugger o modalitate de a cunoaște lanțul de proceduri active la un moment dat (call stack)
  
-// add -> ADD +Deoarece aceste spații de memorie devin necesare la intrarea într-o procedură și devin inutile la ieșirea din aceasta, ele sunt grupate în cadre de stivă(activation records sau stack frames), care la randul lor sunt organizate sub formă de stiva. Un cadru de stivă poate conține: 
-def Pat<(add IntRegs:$raIntRegs:$rb), (ADD IntRegs:$raIntRegs:$rb)>; +  * valorile parametrilor trimiși rutinei curente care nu încap în regiștrii destinați acestui scop,  
-// ineg -+  * toate sau o parte din variabilele locale,  
-//   ​t ​EOR rara    @ t is zero +  * o zona de salvare a regiștrilor temporari alocați de compilator,​ 
-//   ​res = SUB tra   @ res is zero - ra +  * adresa de întoarcere din procedura etc. 
-def Pat<(inot IntRegs:$ra)(SUB (EOR IntRegs:​$ra,​ IntRegs:$ra), IntRegs:$ra)>;+ 
 +Pentru a putea accesa locatiile din cadrul de stivă curent în timpul execuției, acestora li se asociază distante în unitati de memorie relative la un pointer stocat într-un registru. Pointerul poate fi frame pointer-ul ''​fp''​ care indică prima locație a cadrului curent, sau stack pointer-ul ''​sp''​ care indică vârful stivei, adica imediat dupa ultima locație a cadrului curent. Numeroase compilatoare aranjează în memorie cadrele de stivă astfel încat începutul acestora se află la adrese mai mari de memorie decât sfârșitul acestora. Această alegere face ca deplasamentul față de sp-ul curent în cadrul curent să fie pozitiv, așa cum se vede în figura de mai jos. 
 + 
 +{{ :cpl:labs:​laborator-05-stack-sp.png?​480 |}} 
 + 
 +Unele compilatoare folosesc amândoi pointerii''​sp''​ și fp, având variabile relative la amândoi regiștrii. 
 + 
 +{{ :cpl:​labs:​laborator-05-stack-fp.png?​480 |}} 
 + 
 +Caracteristicile limbajului și cele ale hardware-lui determină alegerea unuia dintre acești registri sau chiar folosirea ambilor pentru accesarea locațiilor de pe stivă. Problemele sunt urmatoarele:​ 
 +  * folosirea a doi regiștri pentru accesul la stivă înseamnă a consuma un registru care ar putea folosi altor scopuri; 
 +  * dacă deplasamentul scurt față de un singur registru oferit de instrucțiunile load (încărcare din memorieși store (salvare în memorie) este suficient pentru a acoperi spatiul de memorie al majorității înregistrărilor de activare (ceea ce se întâmplă pentru cele mai multe  arhitecturi). 
 +  * dacă dimensiunea cadrului de stivă este sau nu cunoscută la momentul compilăriiși dacă se modifică pe parcursul execuției procedurii. 
 + 
 +Dimensiunea unui cadru de stivă nu este cunoscută, de exemplu, dacă trebuie să fie suportate funcții de alocare a memoriei de tipul ''​alloca()''​ din biblioteca C prin care se alocă spațiu în mod dinamic în cadrul de stivă curentși se întoarce un pointer la acel spațiu. 
 + 
 +Prin urmare, este suficient și de preferat să se folosească doar sp-ul, în cazul în care nu trebuie să se ofere suport pentru funcții de tipul ''​alloca()''​. 
 + 
 +Efectul unui apel ''​alloca()''​ este să extindă cadrul de stivă făcând sp-ul să indice la o locație nouă. Prin urmare, deplasamentele relative la sp vor pointa către alte locații. Deoarece în C se poate calcula adresa unei variabile locale și apoi folosi, se impune ca acele cantități accesate relativ la sp să nu poată fi direct adresabile de catre utilizator și se preferă ca acestea să fie valori necesare la apelul unei alte proceduri.  
 + 
 +Astfel, în momentul când există fp, adresarea relativa la sp se foloseste pentru: 
 +  * transmiterea argumentelor unei alte proceduri,  
 +  * salvarea regiștrilor peste un apel de procedură 
 +  * întoarcerea rezultatelor dintr-o altă procedură.  
 + 
 +Pentru a oferi suport pentru ''​alloca()''​ avem nevoie atât de un registru sp cât și de unul fp. Chiar dacă aceasta necesita înca un registru în plus, exista avantajul execuției rapide în cazul apelurilor de proceduri. Astfel, la intrarea în procedură:​ 
 +  * se salvează vechiul fp pe stivă, în cadrul noului cadru, 
 +  * se setează valoarea noului fp la valoarea vechiului sp și 
 +  * adaugă lungimea cadrului curent la vechiul sp pentru a obține noua valoare a lui sp. 
 + 
 +La întoarcerea din procedură, procesul invers este: 
 +  * se setează valoarea noului sp la valoarea vechiului fp 
 +  * se încarcă noul fp de pe stivă 
 + 
 +{{ :​cpl:​labs:​laborator-05-stack-full.png?​480 |}} 
 + 
 +==== Exemplu: funcție minimală ==== 
 +<code c>int add(int a, int b) 
 +
 +      return a + b
 +}</code
 + 
 +== gcc - x86 (CISC) == 
 + 
 +<code asm> 
 +_add:                     ; Identificatorii C sunt prefixati de caracterul _ 
 + 
 +                          ; La intrarea in functiestiva contine: 
 +                          ; ​    | b 
 +                          ;     | a 
 +                          ; esp | Adresa de intoarcere 
 +                           
 +  pushl   ​%ebp            ; Prin conventieregistrul ebp este frame pointer 
 +  ​movl ​   %esp, %ebp      ; catre inceputul cadrului de stiva al unei functii. 
 +                          ; Stiva acum: 
 +                          ; ebp + 12 | b 
 +                          ; ebp +  8 | a 
 +                          ; ebp +  4 | adresa de intoarcere 
 +                          ; ebp      | ebp din functia anterioara 
 +                           
 +                          ; Deoarece stiva creste in jos, parametrii se gasesc la  
 +                          ; deplasamente pozitive ​(ebp+8in timp ce variabilele locale  
 +                          ; se gasesc la deplasamente negative ​(ebp-8). 
 +   
 +  movl    12(%ebp), %eax  ; eax = b [ebp + 12] 
 +  movl    8(%ebp), %edx   edx = a [ebp + 8 ] 
 +                          ; Registrii eax si edx sunt volatili, deci pot fi folositi de  
 +                          ; catre functie fara restrictii. 
 +                           
 +  addl    %edx, %eax      ; eax += edx 
 +                          ; Valoarea intoarsa de functie este plasata in registrul eax. 
 +                           
 +  popl    %ebp 
 +  ret
 </​code>​ </​code>​
  
-Clasa **Pat** extinde clasa **Pattern** și ne permite să definim pattern-uri într-un mod mai simplu.+== gcc – ARM (RISC) == 
 +<code asm> 
 +add: 
 +   ​add ​    r0, r0, r1 
 +   ​bx ​     lr 
 +</​code>​
  
-**Calling convention**-ul se definește tot prin intermediul fișierelor **.td**. Calling convention-ul este un contract ​prin intermediul căruia se specifică modalitatea de transmitere a parametrilor către funcții și întoarcerea rezultatelor de către acestea (folosind registre, stiva sau ambele). ​+Functia nu foloseste stivaAtat parametrii, cat si adresa de intoarcere sunt transmisi ​prin registrii procesorului:​
  
-Se pot defini mai multe calling convention-uri,​ folosing clasa **CallingConvention**. Această clasă primește ca parametru o listă de acținui (**CCAction**) care este folosită pentru ​determina modul de transmitere a parametrilor. ​+| R0 | 
 +| R1 | b | 
 +| LR | Adresa ​de intoarcere |
  
-Pentru lista tuturor acțiunilor,​ inspectați fișierul **include/​llvm/​Target/​TargetCallingConv.td**. Mai jos sunt prezentate câteva din cele mai comune acțiuni, din acest fișier. +  
-<​code>​ +Valoarea intoarsa de functie trebuie plasata in R0Codul generat este astfel echivalent cu:
-/// CCIfType - If the current argument is one of the specified types, apply +
-/// Action A. +
-class CCIfType<​list<​ValueType>​ vts, CCAction A> CCPredicateAction<​A>​ { +
-  list<​ValueType>​ VTs = vts; +
-}+
  
-/// CCPromoteToType - If applied, this promotes the specified current value to +  * R0 = R0 + R1 
-/// the specified type. +  * Salt la LR.
-class CCPromoteToType<​ValueType destTy> : CCAction { +
-  ValueType DestTy = destTy; +
-}+
  
-/// CCAssignToReg - This action matches if there is a register in the specified +== JVM (masina virtuala bazata pe stiva) ==
-/// list that is still available. ​ If so, it assigns the value to the first +
-/// available register and succeeds. +
-class CCAssignToReg<​list<​Register>​ regList> : CCAction { +
-  list<​Register>​ RegList ​regList; +
-}+
  
-/// CCAssignToStack - This action always matches: it assigns the value to a +<code
-/// stack slot of the specified size and alignment on the stack. ​ If size is +public static ​int add(int, int)
-/// zero then the ABI size is used; if align is zero then the ABI alignment +   Stack=2, Locals=2, Args_size=2 
-/// is used - these may depend on the target or subtarget. +   0:   ​iload_0 
-class CCAssignToStack<int size, int align: CCAction { +   ​1: ​  ​iload_1 
-  int Size = size+   ​2: ​  ​iadd 
-  int Align align; +   ​3: ​  ​ireturn
-}+
 </​code>​ </​code>​
  
-Este important de menționat că determinarea modului de transmitere a unui parametru se face parcurgând lista până la prima acțiune de assign care se execută.+Masina virtuala Java nu are registri si foloseste stiva pentru toate operatiile.
  
-Pentru a defini un calling convention care primește toți parametrii pe stivăputem folosi:+Functia are 2 argumente, ce sunt copiate in zona de variabile locale, si foloseste 2 locatii de stiva.
  
 +Variabilele locale sunt copiate pe stiva:
 <​code>​ <​code>​
-def CC_Stack : CallingConv<​+  push local[0]; --> a 
-  ​// Promote values to i32 +  ​push local[1]; --> b 
-  CCIfType<​[i1, i8, i16], CCPromoteToType<i32>>,+</code> 
 +  
 +Instructiunea de adunare inlocuieste doua valori din varful stivei cu suma lor. Codul echivalent intr-o masina cu registri:
  
-  // Use the stack (always works) +<code
-  CCAsignToStack<0, 0+  pop a 
-]>;+  pop b 
 +  c = a + b 
 +  push c
 </​code>​ </​code>​
  
-===== Setul de instrucțiuni ===== +Functia intoarce valoarea ramasa în vârful stivei. 
-În cadrul acestui laborator se va folosi o arhitectură care definește un subset al instrucțiunilor de **ARMv3**.+==== Exemplu: evaluarea unei expresii ​==== 
 +<code c>int add_mul(int a, int b, int c, int d) 
 +
 +      return a b + c d; 
 +}</​code>​
  
-Arhitectura dispune de un set de 16 registre de 32 biți (R0-R15) și un registru de stare (CPSR). Registrele R13-R15 sunt rezervate pentru stack pointer SP (R13)link register LR (R14) și program counter PC (R15). ​+== gcc x86 == 
 +<code asm> 
 +_add_mul: 
 +  pushl   ​%ebp 
 +  movl    %esp%ebp
  
-Registrul de stare este scris de către instrucțiunile de comparație și citit de către instrucțiunile ​de salt condiționat.+; Stiva, dupa executia codului ​de initializare:​ 
 +;    +20  d 
 +;    +16  ​c 
 +;    +12  b 
 +;    +8   a 
 +;    +4   ​Adresa ​de intoarcere 
 +; ebp+0   ebp din functia anterioara
  
-Setul de instrucțiuni cuprinde: ​+; Compilatorul descompune calculul expresiei in instructiuni simple 
 +; Apoi mapeaza variabilele temporare pe registrii hardware ai procesorului
  
-^ Tip ^ Sintaxă ^ Efect ^ +  movl    8(%ebp)%edx  ;   ​T1 ​a       ; edx a 
-| ALU | add rdrs1, rs2 | rd rs1 + rs2 | +  ​movl ​   12(%ebp)%eax ;   ​T2 ​b       ; eax b 
-| ::: | add rd, rs1, i8 | rd rs1 + i8 | +  ​imull ​  %edx%eax     ; ​  ​T3 ​T1 T2 ; eax edx * eax 
-| ::: | sub rdrs1, rs2 | rd rs1 - rs2 | +  ​movl ​   16(%ebp)%ecx ;   ​T4 ​c       ; ecx c 
-| ::: | sub rd, rs1, i8 | rd rs1 - i8 | +  ​movl ​   20(%ebp)%edx ;   ​T5 ​d       ; edx d 
-| ::: | mul rdrs1, rs2 | rd rs1 rs2 | +  ​imull ​  %ecx%edx     ; ​  ​T6 ​T4 * T5 ; edx ecx edx 
-| ::: | and rd, rs1, rs2 | rd rs1 AND rs2 | +  ​addl ​   %edx%eax       T7 T3 T6 ; eax eax edx 
-| ::: | and rdrs1, i8 | rd rs1 AND i8 | +  
-| ::: | orr rd, rs1, rs2 | rd rs1 OR rs2 | +  ​popl ​   %ebp 
-| ::: | orr rdrs1, i8 | rd rs1 OR i8 | +  ret
-| ::: | eor rd, rs1, rs2 | rd rs1 XOR rs2 | +
-| ::: | eor rdrs1, i8 | rd rs1 XOR i8 | +
-| ::: | mul rd, rs1, rs2 | rd rs1 rs2 | +
-| Transfer | mov rdrs | rd = rs | +
-| ::: | mvn rd, rs | rd = NOT rs | +
-| Salt | b i24 | pc = address | +
-| Salt condiționat | beq i24 | if eq then pc = address | +
-| ::: | bne i24 | if ne then pc = address | +
-| ::: | bge i24 | if ge then pc = address | +
-| ::: | bgt i24 | if gt then pc = address | +
-| ::: | ble i24 | if le then pc = address | +
-| ::: | blt i24 | if lt then pc = address | +
-| Apel funcție | bl i24 | lr = next_addresspc address | +
-| Memorie | ldr rd, [ra, i12] | rd = [ra i12] | +
-| ::: | ldrb rd, [ra, i12] | rd zero_extend [ra i12] | +
-| ::: | str rs, [ra, i12] | [ra + i12] = rs | +
-| ::: | strb rs, [ra, i12] | [ra + i12] = rs AND 0xFF | +
-===== Compilare și rulare ===== +
-Următoarele pachete sunt necesare: +
-<​code>​ +
-gcc-arm-linux-gnueabi # cross compiler de arm +
-libc-dev-armel-cross # embedded GNU C library pentru cross-compiling +
-qemu-user # pt qemu-arm +
-android-tools-adb # adb +
-openjdk-7-jre # sau alta varianta de java +
-lib32z1 lib32ncurses5 lib32stdc++6 # lib-uri pe 32 de biti (daca e cazul)+
 </​code>​ </​code>​
  
-De asemenea, este necesar [[http://​dl.google.com/​android/​android-sdk_r24.4.1-linux.tgz|SDK-ul de Android]]. +== JVM ==
-Acesta trebuie dezarhivat, iar calea către directorul obținut în urma dezarhivării trebuie setată în cadrul variabilei de mediu ANDROID_SDK_ROOT. +
-De asemenea trebuie adăugată în PATH calea către directorul $ANDROID_SDK_ROOT/​tools.+
  
-==== Device pentru android ==== +^ public static int add_mul(int,​ int, int, int); ^  Stiva  ^ 
-Pentru ​instala doar target-ul necesar+| Code: | | 
-<​code>​ +| Stack=3, Locals=4, Args_size=4 | ::: | 
-# Recomandam instalarea celor cu API 22 +| 0:   ​iload_0 |  ​ | 
-android list sdk+| 1  iload_1 |  a  | 
 +| ::: |  b  | 
 +| 2:   imul |  a * b  | 
 +| 3:   ​iload_2 |  a * b  | 
 +| ::: |  c  | 
 +| 4:   ​iload_3 |  a * b  | 
 +| ::: |  c  | 
 +| ::: |  d  | 
 +| 5:   imul |  a * b  | 
 +| ::: |  c * d  | 
 +| 6:   iadd |  a * b + c * d  | 
 +| 7:   ​ireturn | | 
 +  
 +Functia intoarce valoarea ramasa pe stiva.
  
-android update sdk --no-ui --filter 4,25,38,tool,​platform-tool +==== Exemplu: apeluri de funcții ==== 
-# Numerele pot diferi in functie de output-ul comenzii de mai sus. In cazul nostru: +<code c>int add_mul2(int aint bint cint d) 
-# 4 - SDK Platform Android 5.1.1API 22revision 2 +{ 
-# 25 - Google APIs, Android API 22, revision 1 +      ​return mul(a,b) + mul(c,d); 
-# 38 - Google APIs ARM EABI v7a System Image, Google Inc. API 22, revision 1+}</​code>​
  
-android list targets+== gcc - x86 == 
 +<code asm> 
 +_add_mul2:​ 
 +        pushl   ​%ebp 
 +        movl    %esp, %ebp 
 +        pushl   ​%ebx 
 +        subl    $20, %esp 
 +; Conform conventiei de apel, registrul ebx nu trebuie modificat de catre functii.  
 +; Pentru ca add_mul2() il foloseste, el este salvat pe stiva la intrarea in functie 
 +; si restaurat la iesire. 
 +
 +; Stiva, dupa executia codului de initializare:​ 
 +
 +; ebp+20 ​ d 
 +; ebp+16 ​ c 
 +; ebp+12 ​ b 
 +; ebp+8   a 
 +; ebp+4   ​Adresa de intoarcere 
 +; ebp+0   ebp din functia anterioara 
 +;         ebx din functia anterioara 
 +;         ​Spatiu pentru variabilele locale. 
 +; esp+4   Al doilea parametru pentru mul(int,​int) 
 +; esp+0   ​Primul parametru pentru mul(int,​int)
  
-android create avd -n myandroid22 \ +        movl    12(%ebp), %eax 
-    ​-t "​Google Inc.:Google APIs:​22"​ \ +        ​movl ​   %eax, 4(%esp) 
-    --abi google_apis/​armeabi-v7a+        movl    8(%ebp), %eax 
 +        movl    %eax, (%esp) 
 +        call    _mul 
 +; Codul de nivel inaltebx = mul ( a, b ) 
 +; Parametrii pentru mul ( a, b ) sunt plasati in spatiul prealocat. 
 + 
 +        movl    %eax, %ebx 
 +; Rezultatul functiei este salvat in ebx. Se foloseste ebx, deoarece, conform 
 +; conventiei, este un registru nonvolatil, prin urmare nu va fi distrus de urmatorul 
 +; apel al functiei mul(). 
 + 
 +        movl    20(%ebp), %eax 
 +        movl    %eax, 4(%esp) 
 +        movl    16(%ebp), %eax 
 +        movl    %eax, (%esp) 
 +        call    _mul 
 +; eax = mul ( c,d ) 
 +; De remarcat ca parametrii unei functii apartin cadrului de stiva al functiei  
 +; anterioare, care o apeleaza.  
 +; In cazul nostru, stiva la apelul lui mul(c,d) : 
 +
 +;                           d 
 +;                           c 
 +;                           b 
 +;                           a 
 +; Cadrul add_mul2() ​-->     ​Adresa de intoarcere din add_mul2() 
 +;                           ebp din functia anterioara 
 +;                           ebx din functia anterioara 
 +;                           ​Spatiu pentru variabilele locale. 
 +;                           d 
 +;                           c 
 +; Cadrul mul()      ​-->     ​Adresa de intoarcere din mul() 
 +;                           ... 
 + 
 +        addl    %eax, %ebx 
 +        movl    %ebx, %eax 
 +; Calculul valorii intoarse de add_mul2():​ 
 +;  ebx += eax 
 +;  eax = ebx 
 + 
 +        addl    $20, %esp 
 +        popl    %ebx 
 +        popl    %ebp 
 +        ret
 </​code>​ </​code>​
  
-==== Compilare ​==== +===== Prototipuri de funcții ===== 
-Pentru ​compila un fișer ''​C''​ vom folosi cross-compiler-ul de ARM ''​arm-linux-gnueabi-gcc''​. +Un prototip de funcție reprezintă o declarare ​funcției care omite corpul funcției, dar specifică numele, tipul parametrilor ​și tipul întorsÎn timp ce definiția unei funcții specifică implementarea unei funcțiiprototipul poate fi descris ca fiind interfața funcțieiAcest lucru este pus în evidență în IDE-urile care au opțiuni diferite “Go to declaration” și “Go to definition”.
-<note important>​ +
-Pentru temă vom genera cod pentru ARM v3. +
-Datorită unui bug în gccvom rula cu opțiunea ''​-march=armv4''​. +
-</​note>​ +
-Opțiunile sunt similare cu cele date pentru gcc. +
-Arhitectura dorită va fi specificată cu ''​-march=armv4''​.+
  
-Pentru ​obține asamblareafolosiț''​-S''​.+Prototipurile sunt folosite de compilator pentru ​determina caracteristicile unei funcții ce urmează a fi apelatapentru a se transmite parametrii și a se primi valoarea întoarsă, fără a avea acces la codul sursă al funcției.
  
-Pentru a putea rula executabilele generate folosind emulatoareletrebuie ca acestea să fie link-ate static (prin transmiterea opțiunii ''​-static''​ către ''​arm-linux-gnueabi-gcc''​)+Dacă prototipul funcției vizibil în locul în care funcția este apelata diferă de prototipul functiei din locul în care funcția este definita, atunci se pot ajunge la apeluri de funcție eronatedatorate incompatibilității între numărul și tipul parametrilor din locul apelării funcției și din funcția propriu-zisă.
  
-==== Emulare ==== +În limbajul Cdacă o funcție nu este declarată, ea va avea automat atribuit prototipul implicit al funcțiilor:​ 
-Pentru testarevom folosi emulatorul de Android și qemu. + 
-=== Emulatorul de Android === +<​code ​c
-<​code>​ +// pentru apeluri ​de tipul 
-emulator -avd myandroid22 & # porniti emulatorul ​de android +func(); x = func(); 
-adb push binar /data/binar  # copiati binarul pe device +// semnătura implicită este 
-adb shell /data/binar       # rulati binarul+int func(void);​ 
 +</code>  
 + 
 +Pentru funcții care sunt apelate cu argumente semnătura implicită este  
 +<code c> 
 +func(x, y, z); 
 +/
 +int func(typeof_x,​ typeof_y, typeof_z);
 </​code>​ </​code>​
-Pentru a putea rula aceste comenzi, trebuie adăugată în ''​PATH''​ calea către directorul ''​$ANDROID_SDK_ROOT/​tools''​. + 
-În cazul în care se folosește un sistem ​de operare pe 32 biți, este nevoie ​de forțarea folosirii binarelor pe 32 de biți prin exportarea variabilei de mediu ANDROID_EMULATOR_FORCE_32BIT=true. +Un exemplu ​de functionare eronată a unui apel de funcție din motive ​de incompatibilitate între prototipurile unei funcții: 
-=== Qemu === +<​code ​c
-<​code>​ +//        fisier1.c 
-qemu-arm binar              # rulati binarul+/* func() nedeclarata ​                  ​prototip implicit 
 +                                        int func(void) */ 
 +int main() 
 +
 +    /* ... */ 
 +    value += func(); 
 +    /* ... */ 
 +}
 </​code>​ </​code>​
 +<code c>
 +// fisier2.c
 +/* prototip int func(int, int) */
 +long long func(int param1, int param2)
 +{
 +    return (long long) param1 + param2;
 +}
 +</​code>​
 +În acest caz, în locul de unde se face apelul (din funcția main) se foloseşte prototipul implicit, care împreună cu un apel greşit de funcție, duce la cod eronat:
 +  * în locul de unde se face apelul, nu se transmite niciun parametru, și se aşteaptă să se întoarcă o valoare de tip int,
 +  * în funcția apelată, se așteapta 2 parametri care vor fi citiți eronat din locațiile corespunzătoare,​ iar valoarea întoarsă va fi un long long. 
  
-===== Exerciții ===== +Este important de observat, totuși, ca majoritatea limbajelor moderne ​(inclusiv C++, versus Cnu acceptă tipuri si prototipuri implicite.
-{{:​cpl:​labs:​lab07.zip|Arhiva}} laboratorului. +
-==== Exercițiul 1 ==== +
-Scrieți un program ''​C''​ care printează ''"​Hello World!"''​. Compilați programul ​și obțineți fișierul .s. Asamblați și link-ați fișierul pentru a obține un fișier ELF. Rulați ELF-ul folosind cele 2 emulatoare ​(Android și qemu)+
-==== Exercițiul 2 ==== +
-Completați funcția ''​sum''​ din fișierul **ex2.s** pentru a întoarce suma elementelor vectorului dat ca parametru. Adresa vectorului este transmisă prin registrul ''​R0'',​ iar numărul elementelor este transmis prin registrul ''​R1''​Rezultatul trebuie intors prin registrul ''​R0''​. Link-ați **ex2.s** împreună cu **ex2.c** și rulați executabilul.<​note important>​ +
-Funcția nu trebuie sa suprascrie niciun registru rezervat pentru program counter, return address și stack pointer. +
-</​note>​ +
-==== Exercițiul 3 ==== +
-Copiați directorul cpl din arhiva în directorul lib/Target al dristribuției de LLVM de mașinile din laborator. +
-Înregistrați target-ul Cpl în cadrul framework-ului de LLVM. +
-După ce ați modificat toate fișierele, compilați **llc** cu suport pentru noul target:+
  
-   cd ~/​packages/​llvm-3.6.2/​build +====== ​Excepții ======
-   cmake -DLLVM_TARGETS_TO_BUILD=Cpl path_to_llvm_src +
-   make -j2 llc +
-Pentru a testa, compilați fișierul ex3.a.ll folosind opțiunea ''​-filetype=null''​ pentru llc. +
-<note warning>​ +
-Compilarea llc va dura în jur de 20 minute. +
-</​note>​ +
-==== Exercițiul 4 ==== +
-Completați în target-ul de Cpl pentru a genera cod pentru următoarele funcții echivalente în ''​C''​. +
-Funcțiile se află în fișierele ''​ex4_<​no>​.ll''​. +
-=== === +
-Va trebui să implementați funcția ''​CplInstPrinter::​printOperand''​. +
-<​code>​ +
-void f() {} +
-</​code>​+
  
-=== ident === + 
-Va trebui ​să completați fisierul ''​CplCallingConv.td''​ +Există anumite situații în care se dorește/​preferă ​să se arunce o excepție. Controlul se transferă către blocul de cod desemnat să se execute în momentul în care se produce excepția. Acest cod se numește handler. Când există un handler pentru excepția aruncată, se spune că excepția a fost prinsă. 
-<​code>​ +Din punctul de vedere al utilizatorului,​ mecanismul de tratare a excepțiilor este format din urmatoarele elemente: blocuri de try, blocuri de catch șexpresii de throw. 
-int ident(int a) { + 
-  return ​a;+<​code ​C> 
 +#include <​iostream>​ 
 +using namespace std; 
 + 
 +int a; 
 + 
 +int main () { 
 +  try 
 +  { 
 +    if (!= 0) 
 +       throw 10; 
 +  } 
 +  catch (int e) 
 +  ​{ 
 +    cout << "A fost aruncata exceptia numarul " << e << '​\n';​ 
 +  } 
 +  return ​0;
 } }
 </​code>​ </​code>​
  
-=== or === +Când mai multe blocuri de try sunt imbricate ​și se produce ​un throw, într-o functie apelată de un bloc try interior, controlul se transferă spre exterior din blocurile try imbricate, până când se gasește primul bloc de catch al cărui argument se potrivește cu argumentul aruncat de excepție. 
-Va trebui să adăugați instructiunea ''​or''​ în fișierul ''​CplInstrInfo.td'' ​și un pattern pentru nodul ''​or''​ în fișierul ''​CplPatterns.td''​ + 
-<​code>​ +<​code ​C
-int or(int a, int b) { +try 
-  ​return a | b;+
 +      func1()
 +      try 
 +      ​
 +             func2(); 
 +      } 
 +      catch (type1_err) { /* ... */ } 
 +      func3();
 } }
 +catch (type2_err) { /* ... */ }
 +// daca nu se arunca nicio exceptie, executia ajunge in acest punct.
 </​code>​ </​code>​
  
-=== second ​=== +În exemplul de mai sus, dacă //​type1_err//​ este aruncat din blocul try interior (din func2()), excepția este prinsă de blocul de catch interior, și, presupunand că acest bloc nu transferă controlul, este apelată func3(). Daca //​type1_err//​ este aruncată după blocul de try interior, de exemplu de către func3(), din cauză că nu există niciun bloc catch care să prindă această excepție, se va apela funcția **terminate()**. 
-Va trebui ​să implementațfuncția ''​CplInstrInfo::​copyPhysReg''​+ 
-<​code>​ +Blocurile de catch conțin rutina de tratare a excepției. Obiectele permise, pe care rutina le poate prinde, sunt declarate în paranteză, după cuvântul cheie catch.  
-int second(int a, int b) { +===== Excepțiile la compile-time ===== 
-  ​return b;+ 
 +Aruncarea unei excepții este de obicei implementată sub forma unui apel la o funcție _throw_ din biblioteca runtime. Numele difera de la o implementare la alta. Pentru ca _throw_ ​să funcționeze corect, are nevoie de o tabela ce listeaza toate blocurile catch ale unei funcțiiTabela este generată de catre compilator. Pentru a fi mai usor de urmarit, mai jos este adaugat si codul echivalent, cu etichete.  
 + 
 +^ Cod cu excepții ^ Cod echivalent ^ 
 +<​code ​C>  
 +processFile(...) { 
 +  ​try { 
 +    f = openFile(...); 
 +  } 
 +  catch (FileError e) { 
 +    log(e); 
 +  }
 } }
 +</​code>​ | <code C> 
 +processFile(...) {
 +try_start:
 +  f = openFile(...);​
 +try_end:
 +  goto try_next;
 +catch_FileError:​
 +  e=*(FileError*)_exception_;​
 +  log(e);
 +try_next:
 +
 +</​code>​ |
 +| <code C>
 +openFile(...) {
 +  x = open(...);
 +  if (x < 0)
 +    throw FileError(x);​
 +}             
 +</​code>​ | <code C>
 +openFile(...) {
 +  x = open(...);
 +  if (x < 0) {
 +     ​FileError tmp(x);
 +     ​_exception_ = &tmp;
 +     ​_throw_(“FileError”);​
 +  }
 +}
 +</​code>​ |
 +
 +Intrarea in tabela generata pentru intervalul try din exemplul de mai sus:
 +
 +^ Interval de aplicabilitate ^ Început catch ^ Tipul excepției ^
 +| try_start … try_end | catch_FileError ​ | FileError |
 +
 +GCC-ul genereaza tabela de exceptii (exception handler framework) in sectiunile .eh_frame / .eh_frame_hdr):​
 +
 +===== Excepțiile la runtime =====
 +
 +Să presupunem că se executa funcția func_ex, care aruncă o excepție. În vârful stivei se află cadrul de stivă al funcției func_ex. În cazul în care excepția a fost aruncată dintr-un bloc de try, se caută un blocul de catch care sa trateze tipul erorii aruncate. În cazul în care acesta se găsește, se începe procesul de stack-unwinding,​ dacă nu se găsește, se va apela funcția terminate().
 +Vom discuta în continuare despre **stack unwinding**,​ pornind de la următorul exemplu:
 +<code C>
 +#include <​iostream>​
 +#include <​string>​
 +
 + void func2()
 + ​{ ​
 +  throw std::​exception();​
 + }
 + 
 + void func1()
 + {
 +   ​std::​string str = "Ana are ..."; ​
 +   ​func2();​
 + }
 + 
 + int main()
 + {
 +   try
 +   {
 +      func1();
 +   ​} ​
 +   ​catch(...) ​
 +   { }
 + }
 </​code>​ </​code>​
  
 +La runtime, în momentul în care func2 aruncă excepția, pe stivă există următoarele cadre: main, func1, func2.
 +Funcția func2 nu prinde aceasta excepție, dar există in program un bloc de catch care sa o prinda, așadar, incepe **stack unwinding**. ​
 +
 +Stack unwinding parcurge lista de cadre de stiva pornind din vârful stivei, și:
 +  - apelează destructorii pentru toate obiectele locale funcției al cărei cadrul se află în vârful stivei;
 +  - face restore la regiștrii callee-saved
 +  - se scoate cadrul de pe stiva
 +  - se revine la pasul 1 (acum, în vârful stivei este funcția care a apelat funcția ce tocmai am scos-o de pe stivă);
 +
 +Revenind la exemplul nostru, func2() nu prinde exceptia, nu are nicio variabilă locală, deci singurul pas va fi scoaterea cadrului de pe stivă. Funția func1() are acum cadrul în vârful stivei, dar nici func1() nu contine blocul de catch, deci, se va apela destructorul lui **str**, apoi cadrul funcției func1() este scos de pe stiva. Se ajunge astfel la funcția main() care prinde excepția.  ​
 +
 +<​note>​În procesul de unwinding, destructorii sunt apelați în ordine inversă constructorilor.</​note>​
 +
 +  ​
 +<​note>​Stack unwinding este folosit și de ''​debugger''​ pentru a afișa stiva de apeluri și pentru a putea reveni cu execuția dintr-un punct anterior. ​
 +</​note>  ​
 +
 +Puteți să vă gandiți și la altă situație în care s-ar putea folosi acelați mecanism? ​
 +
 +
 +====== Exerciții ======
 +{{:​cpl:​labs:​laborator-07-arhiva.zip|Arhiva}} laboratorului.
 +  - Scrieți un program ''​C''​ care să determine sensul stivei (este mărită sau micșorată valoarea adresei vârfului stivei la operații de ''​push''?​).
 +  - În directorul ''​return_struct''​ din arhiva laboratorului aveți implementate două funcții: una care întoarce o structură și alta care primește o structură ca parametru. Rulați ''​make asm''​ pentru a genera un fișier "​.s"​ și explicați:
 +    * modul în care se trimite un argument de tip structură,
 +    * modul în care se întoarce o structură.
 +  - Modificați ''​doar''​ funcția **ask** din programul **ovr** pentru a afișa nota corectă.
 +  - Scrieți o funcție ''​fast_multiply_3''​ în asm care să înmulțească trei numere întregi primite ca argumente. Declarați funcția ca fiind de tip ''​fastcall''​ (Pe arhitecturi Intel x86, folosirea conveției de apel fastcall, presupune că:
 +    * primul argument se află în ''​%ecx''​
 +    * al doilea argument se află în ''​%edx''​
 +    * toți ceilalți parametri se află pe stivă ca la convenția de apel
 +    * Următoarea linie declară o funcție specificându-i convenție de apel de tip ''​fastcall''​
 +    * <code c> __attribute__((fastcall)) int fast_multiply_3(int a, int b, int c); </​code>​
 +    * Apelați funcția definită la pasul anterior dintr-un fișier ''​C''​. Observați și explicați ce se întâmplă dacă în cadrul fișierului ''​C''​ nu apare declarația funcției ''​fast_multiply_3''​ sau dacă acestei declarații îi lipsește decoratorul ''​fastcall''​.
 +    * Pentru operația de înmulțire folosiți instrucțiunea
 +    * <code asm> imull %eax, %ecx # echivalent cu %ecx := %ecx * %eax </​code>​
 +  - Comentați performanța codului ''​perf_exceptii.cpp'',​ din directorul exceptii.
 +  - Executati exemplul ''​exceptii.cpp''​ din directorul exceptii, explicati ce se intampla la runtime si corectati-l.
  
cpl/labs/07.1451479132.txt.gz · Last modified: 2015/12/30 14:38 by silviu.grigore
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0