This shows you the differences between two versions of the page.
cpl:teme:tema4 [2016/01/10 12:07] silviu.grigore [Registre] |
— (current) | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Tema de casa 4 - Backend ====== | ||
- | În cadrul acestei teme veți implementa un backend în LLVM pentru arhitectura **Cpl**. | ||
- | Deoarece implementarea unui backend necesită un volum mare de muncă, veți porni tema de la o {{:cpl:teme:cpl.tar.gz|arhivă de start}}. | ||
- | ===== Informaţii organizatorice ===== | ||
- | * **Deadline**: Termenul limită până când se pot trimite temele fără depunctări de întârziere este ** duminică, 17 ianuarie 2016, ora 23:59**. Pentru mai multe detalii, consultaţi [[:cpl:teme:general|regulamentul]] aferent temelor de casă. | ||
- | * **Colaborare**: Tema va fi rezolvată [[http://ocw.cs.pub.ro/courses/cpl/meta/notare#penalizare_pentru_teme_copiate|individual]]. | ||
- | * **Punctare**: 100p pentru implementarea completă şi corectă | ||
- | |||
- | ===== Enunț ===== | ||
- | Va trebui să implementați în limbajul C++ porțiuni din cod responsabile cu generarea de generare de cod pentru arhitectura **Cpl**. | ||
- | Programul vostru va trebui să primească la intrare output-ul temei 3 și să genereze un fișier cu cod în limbaj de asamblare. | ||
- | Output-ul programului realizat de voi poate fi asamblat și link-at cu **gcc-arm-linux-gnueabi** și rulat cu ajutorul unui emulator ([[cpl:labs:07|laboratorul 7]]). | ||
- | ===== Arhitectură ===== | ||
- | În cadrul acestei teme se va folosi o arhitectură care definește un subset al instrucțiunilor de **ARMv3**. | ||
- | |||
- | ==== Registre ==== | ||
- | Arhitectura dispune de un set de 16 registre de 32 biți (**R0**-**R15**) și un registru de stare (**CPSR**): | ||
- | * **R0**-**R12** registre generale folosite de alocator | ||
- | * **R13** stack pointer (**SP**) | ||
- | * **R14** link register (**LR**, adresa de return) | ||
- | * **R15** program counter (**PC**) | ||
- | |||
- | Registrul **R14** (**LR**) conține adresa de return și este inițializat la intrarea in funcție. | ||
- | El poate fi folosit de alocator, daca adresa de return este salvată pe stivă sau în alt registru. | ||
- | Registrele **R13** (**SP**) si **R15** (**PC**) sunt rezervate și nu vor fi folosite de alocator. | ||
- | |||
- | Registrul de stare este scris de către instrucțiunile de comparație și citit de către instrucțiunile de salt condiționat. | ||
- | |||
- | === Registrul R15 === | ||
- | Registrul **R15** (program counter) poate fi scris pentru a sări la o anumită adresă. | ||
- | Citind acest registru, obținem adresa instrucțiunii curente + 8 octeți (2 instrucțiuni în față relativ la instrucțiunea curentă). | ||
- | Dacă acest registru nu este scris de o instrucțiune, el este incrementat automat cu 4, pentru a executa instrucțiunea urmatoare. | ||
- | |||
- | ==== Calling convention ==== | ||
- | Parametrii unei funcții sunt transmiși prin intermediul registrelor **R0**-**R3**. | ||
- | Restul parametrilor sunt transmiși prin intermediul stivei. | ||
- | |||
- | Rezultatul funcției, dacă este cazul, este întors prin registrul **R0**. | ||
- | |||
- | Adresa de return este transmisă prin registrul **R14**. | ||
- | |||
- | Registrele **R4**-**R11** trebuie salvate de funcția apelată. | ||
- | |||
- | ==== Stivă ==== | ||
- | Vârful stivei este ținut în registrul **R13**. | ||
- | Pentru a aloca spațiu pe stivă, acest registru este decrementat. | ||
- | Similar, pentru a elibera stiva vom incrementa acest registru. | ||
- | Valorea din **R13** reprezintă o locație validă pe stivă (i.e. putem să stocăm folosind instrucțiunea ''str r14, [r13, #0]''). | ||
- | |||
- | Nu există instrucțiuni de **push** și **pop** pentru lucrul cu stiva. | ||
- | |||
- | ==== Setul de instrucțiuni ==== | ||
- | |||
- | Setul de instrucțiuni cuprinde: | ||
- | |||
- | ^ Tip ^ Sintaxă ^ Efect ^ Observații ^ | ||
- | | ALU | add rd, rs1, rs2 | rd = rs1 + rs2 | | | ||
- | | ::: | add rd, rs1, i8 | rd = rs1 + i8 | | | ||
- | | ::: | sub rd, rs1, rs2 | rd = rs1 - rs2 | | | ||
- | | ::: | sub rd, rs1, i8 | rd = rs1 - i8 | | | ||
- | | ::: | mul rd, rs1, rs2 | rd = rs1 * rs2 | rd != rs1 | | ||
- | | ::: | and rd, rs1, rs2 | rd = rs1 AND rs2 | | | ||
- | | ::: | and rd, rs1, i8 | rd = rs1 AND i8 | | | ||
- | | ::: | orr rd, rs1, rs2 | rd = rs1 OR rs2 | | | ||
- | | ::: | orr rd, rs1, i8 | rd = rs1 OR i8 | | | ||
- | | ::: | eor rd, rs1, rs2 | rd = rs1 XOR rs2 | | | ||
- | | ::: | eor rd, rs1, i8 | rd = rs1 XOR i8 | | | ||
- | | Transfer | mov rd, rs | rd = rs | | | ||
- | | ::: | mvn rd, rs | rd = NOT rs | | | ||
- | | ::: | mov rd, i8 | rd = zero_extend i8 | | | ||
- | | ::: | mvn rd, i8 | rd = NOT zero_extend i8 | | | ||
- | | Transfer condiționat | moveq rd, i8 | if eq then rd = zero_extend i8 | | | ||
- | | ::: | movne rd, i8 | if ne then rd = zero_extend i8 | | | ||
- | | ::: | movge rd, i8 | if ge then rd = zero_extend i8 | | | ||
- | | ::: | movgt rd, i8 | if gt then rd = zero_extend i8 | | | ||
- | | ::: | movle rd, i8 | if le then rd = zero_extend i8 | | | ||
- | | ::: | movlt rd, i8 | if lt then rd = zero_extend i8 | | | ||
- | | 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_address; pc = 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 | | | ||
- | |||
- | ===== Detalii de implementare ===== | ||
- | |||
- | ==== Flux de control ==== | ||
- | Pentru arhitectura **Cpl**, program counter-ul este registrul **R15**. | ||
- | Deoarece nu există o instrucțiune dedicată de return, intoarcere din funcția curentă se face punând în **R15** adresa de return. | ||
- | La intrarea în funcție, adresa de return este disponibilă în registrul **R14**. | ||
- | Valoarea acestui registru este mutată (prin pseudo instrucțiunea COPY) într-un registru virtual. | ||
- | Ieșirea din funcție se face mutând această valoare din registrul virtual în **R15**, folosind o instrucțiune specială. | ||
- | Această instrucțiune trebuie marcată cu ''isTerminator'' pentru a nu fi eliminată de pașii de optimizare. | ||
- | |||
- | ==== Imediat pe 32 de biți ==== | ||
- | Pentru o valoare pe 8 biți, aceasta se poate pune într-un registru folosind instrucțiunea ''MOV''. | ||
- | Dacă avem o valoare mai mare (sau un simbol), avem nevoie de un alt mecanism de inițializare a registrului. | ||
- | |||
- | Recomandăm folosirea unei instrucțiunii de load pentru această inițializare. | ||
- | |||
- | ==== Comparații ==== | ||
- | Pentru **if-uri** și bucle putem folosi branch-uri condiționate. | ||
- | Aceste pattern-uri se pot defini direct. | ||
- | Pentru o obține rezultatul unei comparații (e.g. ''return a < b;'') avem nevoie de instrucțiuni de transfer condiționate. | ||
- | |||
- | Recomandăm definirea unor pseudo instrucțiuni de comparație care au ca rezultat **0** sau **0xFFFFFFFF**, în funcție de operația relațională. | ||
- | Aceste pseudo instrucțiuni pot fi înlocuite în metoda ''expandPostRAPseudo'' cu o instrucțiune de comparație și 2 transferuri, unul condiționat (sau ambele). | ||
- | |||
- | Pentru funcția următoare: | ||
- | <code> | ||
- | int lt(int a, int b) { | ||
- | return a < b; | ||
- | } | ||
- | </code> | ||
- | |||
- | Codul generat ar putea fi următorul: | ||
- | <code> | ||
- | cmp r0, r1 | ||
- | mvn r0, #0 | ||
- | movge r0, #0 | ||
- | mov r1, #1 | ||
- | and r0, r0, r1 | ||
- | orr r15, r14, r14 @ Return | ||
- | </code> | ||
- | |||
- | |||
- | ==== Stiva ==== | ||
- | Adresele de pe stivă sunt reprezentate printr-un operand special, numit **Frame Index**. | ||
- | Acest operand este un întreg (poate avea și valori negative) care caracterizeaza o intrare pe stiva: offset, dimensiune, aliniament. | ||
- | |||
- | În partea de generare de cod (fișiere td), **Frame Index**-ul este identificat printr-un **ComplexPattern** numit **frame_addr**. | ||
- | Pentru instrucțiunile care accesează memoria, acest **frame_addr** se pune in locul registrului de adresă, iar imediatul este pus pe 0. | ||
- | După pasul de alocare de registre, acești operanzi sunt înlocuiți cu **R13** iar offset-ul se pune în locul imediatului. | ||
- | |||
- | În prologul și epilogurile funcției se introduc instrucțiuni care modifică stiva cu cantitatea necesară funcției. | ||
- | |||
- | ===== Task-uri ===== | ||
- | Funcționalitatea se va puncta folosind testele din arhivă. | ||
- | Puteți totuși să vă ghidați după următoarele task-uri pentru rezolvarea temei de backend. | ||
- | |||
- | Fiecare task este marcat în cod prin intermediul comentariilor cu **TODO**. | ||
- | |||
- | Arhiva de start nu se poate compila. | ||
- | |||
- | ==== Task 1 ==== | ||
- | Definiți instrucțiunile care compun setul de instrucțiuni corespunzător arhitecturii Cpl. | ||
- | Acest lucru se realizează în fișierul **CplInstrInfo.td** prin instanțierea claselor definite în fișierul **CplInstrFormat.td**. | ||
- | Instrucțiunile de comparație, salt, apel de funcție și/sau condiționate trebuie marcate corespunzător. Un exemplu în acest sens în constituie instrucțiunea ORR_RET care este marcată ca fiind instrucțiune de întoarcere din funcție prin setarea câmpului //isReturn//. | ||
- | De asemenea, trebuie să definiți în **CplPatterns.td** și patternurile care descriu transfomările de la noduri din graful de selecție la instrucțiuni specifice arhitecturii Cpl. | ||
- | |||
- | ==== Task 2 ==== | ||
- | Pentru a putea transmite parametri unor funcții și pentru a primi rezultatul corect este necesar ca toate funcțiile să respecte un **calling convention**. | ||
- | Descrierea acestui **calling convention** pentru target-ul **Cpl** o găsiți în secțiunea de [[cpl:teme:tema4##calling_convention|arhitectură]]. | ||
- | |||
- | Trebuie să modificați în fișierul **CplCallingConv.td** registrele folosite pentru transmiterea parametrilor, rezultatului și registrele salvate de funcția apelată. | ||
- | |||
- | |||
- | ==== Task 3 ==== | ||
- | Limbajul intermediar LLVM conține o instrucțiune de **select**. | ||
- | Această instrucțiune primește 3 operanzi, primul de tip **i1** și întoarce al 2-lea sau al 3-lea operand în funcție de valoarea primului operand. | ||
- | |||
- | Această instrucțiune se poate genera foarte ușor din următorul cod **C**: | ||
- | <code> | ||
- | int f(bool c, int a, int b) { | ||
- | return c ? a : b; | ||
- | } | ||
- | </code> | ||
- | |||
- | ==== Task 4 ==== | ||
- | După terminarea pasului de alocare de registre, trebuie alocat spațiu pe stivă. | ||
- | Acest spațiu poate fi necesar pentru spill-uri, pentru parametrii transmiși unor funcții sau pentru variable a căror adresă este folosită explicit. | ||
- | Funcția **adjustStackPtr** este apelată la intrarea în funcție pentru a aloca spațiu pe stiva și la ieșirile din funcție pentru a elibera acest spațiu. | ||
- | În cadrul acestei metode, trebuie să adăugați cod necesar pentru modificarea stivei (R13). | ||
- | |||
- | Tot după alocarea de registre, pot să existe unele transferuri. | ||
- | Acestea pot sa provină din alocare, sau din calling convention. | ||
- | În metoda **CplInstrInfo::copyPhysReg**, trebuie să adăugațti cod care să copieze valoarea unui registru în alt registru. | ||
- | |||
- | ==== Task 5 ==== | ||
- | Pentru apeluri de funcție indirecte (cu adresa funcției într-un registru) va trebui să generați o secvență de cod care să salveze **LR**-ul și să modifice **PC**-ul. | ||
- | În cadrul selecției de instrucțiuni, se generează o pseudo-instrucțiune **CALLR**, care va trebui înlocuită. | ||
- | |||
- | Pentru **return**-ul unei funcții, se generează o pseudo-instrucțiune **RET**. | ||
- | Aceasta va trebui înlocuită cu **ORR_RET**, pentru a transfera **LR** în **PC**. | ||
- | |||
- | ==== Task 6 ==== | ||
- | În cadrul acestui task, trebuie sa generați cod pentru secvențe de forma: | ||
- | <code> | ||
- | int lt(int a, int b) { | ||
- | return a < b; | ||
- | } | ||
- | </code> | ||
- | |||
- | Deoarece nu există o instrucțiune care să pună într-un registru valoarea unei comparații, este necesară o secvență de instrucțiuni. | ||
- | Pentru a simplifica generarea de cod mașină, recomandăm să se folosească o pseudo-instrucțiune care sa fie înlocuită cu o secvență validă de instrucțiuni în metoda **expandPostRAPseudo**. | ||
- | Căutați **TODO 6** în codul sursă. | ||
- | |||
- | Consultați secțiunea [[cpl:teme:tema4#comparatii|comparații]] pentru detalii de implementare. | ||
- | |||
- | ===== Testare ===== | ||
- | Testarea se va face folosind următoarea {{:cpl:teme:tema4-teste.tar.gz|arhivă}} | ||
- | |||
- | Setați în variabila de mediu ''CPL_LLC'' calea către executabilul de **llc**, care suportă target-ul **cpl**. | ||
- | |||
- | Testele se rulează folosind comanda: | ||
- | |||
- | <code> | ||
- | ./run_all.sh | ||
- | </code> | ||
- | |||
- | Modul în care este distribuit punctajul pentru această temă este următorul: | ||
- | * **Testele publice (80p)** | ||
- | * 20 de teste, fiecare valorând 4 puncte | ||
- | * **Calitatea implementării (20p)** | ||
- | * Organizarea codului sursă | ||
- | * Comentariile din cod | ||
- | * Explicațiile din README - acestea trebuie să conţină o prezentare extinsă a modului de implementare a temei şi a problemelor întâmpinate pe parcurs. | ||
- | ===== Resurse ===== | ||
- | * {{:cpl:teme:cpl.tar.gz | Arhiva de start}} | ||
- | * {{:cpl:teme:tema4-teste.tar.gz | Arhiva de testare}} |