Tema de casa 3 - 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 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 ,marți, 20 decembrie 2016, ora 23:59. Pentru mai multe detalii, consultaţi regulamentul aferent temelor de casă.
  • Colaborare: Tema va fi rezolvată individual.
  • Punctare: 150p 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 (laboratorul 8).

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 tipul push sau 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:

int lt(int a, int b) {
	return a < b;
}

Codul generat ar putea fi următorul:

       cmp r0, r1
       mvn r0, #0
       movge r0, #0
       mov r1, #1
       and r0, r0, r1
       orr r15, r14, r14               @ Return

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 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:

int f(bool c, int a, int b) {
    return c ? a : b;
}

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:

int lt(int a, int b) {
	return a < b;
}

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 comparații pentru detalii de implementare.

Trimiterea temei

Arhiva care conține tema va avea exact aceleași fișiere și structură ca arhiva de pornire - va conține în plus doar un README.

Nu trebuie să adăugați fișiere noi. Conținutul fișierelor CMakeLists.txt nu trebuie modificate.

Testare

Testarea se va face folosind următoarea 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:

./run_all.sh

Modul în care este distribuit punctajul pentru această temă este următorul:

  • Testele publice (120p)
    • 20 de teste, fiecare valorând 6 puncte
  • Calitatea implementării (30p)
    • 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-2016/tema3.txt · Last modified: 2017/11/28 05:45 by bogdan.nitulescu
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