This shows you the differences between two versions of the page.
cpl:teme-2015:tema4 [2016/10/04 07:17] bogdan.nitulescu created |
cpl:teme-2015:tema4 [2016/10/04 07:19] (current) bogdan.nitulescu |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Tema de casa 3 - Generarea de cod ====== | + | ====== Tema de casa 4 - Backend ====== |
- | + | În cadrul acestei teme veți implementa un backend în LLVM pentru arhitectura **Cpl**. | |
- | În cadrul acestei teme veti implementa generarea de cod pentru limbajul LCPL. | + | 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 ===== | ===== Informaţii organizatorice ===== | ||
- | * **Deadline**: Termenul limită până când se pot trimite temele fără depunctări de întârziere este **sâmbătă, 9 ianuarie 2016, ora 23:59**. Pentru mai multe detalii, consultaţi [[:cpl:teme:general|regulamentul]] aferent temelor de casă. | + | * **Deadline**: Termenul limită până când se pot trimite temele fără depunctări de întârziere este ** miercuri, 20 ianuarie 2016, ora 23:55**. Pentru mai multe detalii, consultaţi [[:cpl:teme:general|regulamentul]] aferent temelor de casă. |
- | * **Colaborare**: Tema va fi rezolvată **individual**. | + | * **Colaborare**: Tema va fi rezolvată [[http://ocw.cs.pub.ro/courses/cpl/meta/notare#penalizare_pentru_teme_copiate|individual]]. |
- | * **Punctare**: | + | * **Punctare**: 100p pentru implementarea completă şi corectă |
- | * 100p pentru implementarea tuturor pasilor necesari | + | |
- | * 200p pentru generarea corectă de cod pentru toate testele publicate | + | |
+ | ===== 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**. | ||
- | ===== Enunţ ===== | + | ==== 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**) | ||
- | Va trebui să realizaţi în limbajul C++, porţiunea responsabilă cu generarea cod LLVM IR, folosind LLVM C++ API. Programul vostru va trebui să primească la intrare output-ul temei 1 şi să genereze un fisier cu cod corect LLVM IR. | + | 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. | ||
- | Documentaţia principală în cadrul acestei teme va fi LLVM C++ API și LLVM IR. Rezultatul programului realizat de voi poate folosi tool-urile LLVM pentru a genera cod pentru x86 si pentru a se executa. | + | Registrul de stare este scris de către instrucțiunile de comparație și citit de către instrucțiunile de salt condiționat. |
- | ===== Generarea de cod ===== | + | === 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. | ||
- | Pentru generarea de cod veti folosi LLVM C++ API, similar cu ceea ce ați învățat în laboratorul 5. De asemenea, vă recomandăm să studiați și tutorialul de generare de cod de aici http://llvm.org/releases/3.6.0/docs/tutorial/LangImpl3.html. | + | ==== Calling convention ==== |
+ | Parametrii unei funcții sunt transmiși prin intermediul registrelor **R0**-**R3**. | ||
+ | Restul parametrilor sunt transmiși prin intermediul stivei. | ||
- | Arhiva de pornire conține codul generat pentru un nod de tip clasa si pentru metode vide. | + | Rezultatul funcției, dacă este cazul, este întors prin registrul **R0**. |
- | Tehnica folosită pentru implementarea generării de cod se bazează pe ASTVisitor. Voi trebuie sa implementați funcțiile **visit** pentru celelalte tipuri de noduri și să le completați pe cele deja puse ca exemplu, dacă este cazul. | + | Adresa de return este transmisă prin registrul **R14**. |
- | Nu sunteti obligați să folosiți abordarea sugerată în arhiva de pornire. Însă, codul generat trebuie sa fie cod LLVM IR valid și trebuie să producă rezultatele corecte. | + | Registrele **R4**-**R11** trebuie salvate de funcția apelată. |
- | Vă recomandam să începeți cu înțelegerea documentației: | + | ==== Stivă ==== |
- | * Semantica și comportamentul programelor LCPL | + | Vârful stivei este ținut în registrul **R13**. |
- | * LLVM IR / C++ API pt generarea de LLVM IR | + | Pentru a aloca spațiu pe stivă, acest registru este decrementat. |
- | * Suportul pentru runtime | + | 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. | ||
- | ==== Detalii de implementare ==== | + | ==== Setul de instrucțiuni ==== |
- | Odată ce ați citit documentația puteți trece la implementarea generării de cod, parucrgând următorii pași: | + | Setul de instrucțiuni cuprinde: |
- | * **Emiterea constantelor globale.** Şirurile de caractere din program sunt constante globale care trebuie alocate în memorie şi iniţializate de către generatorul de cod. | + | |
- | * **Emiterea obiectelor de tip String care reprezinta numele claselor.** Funcția typeName din runtime va returna unul dintre aceste obiecte. | + | ^ 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 | | | ||
- | * **Emiterea informației de runtime (rtti - runtime type information).** Fiecare clasă din programul de intrare va avea o informație de runtime. La crearea unui obiect nou se pasează informația de runtime a clasei către operatorul //new//. | + | ===== Detalii de implementare ===== |
- | * **Emiterea tabelei cu adresele metodelor.** In informația de runtime este inclusă si tabela de funcții virtuale. Această tabelă conține adresele tuturor metodelor dintr-o clasă, precum și pe cele ale clasei părinte. | + | ==== 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. | ||
- | * **Emiterea codului pentru metodele de iniţializare ale claselor.** LCPL permite execuţia unui cod de iniţializare pentru fiecare atribut al unei clase. Acest cod va deveni parte din metoda de iniţializare (constructor) care va fi apelată de operatorul new. | + | ==== 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. | ||
- | * **Emiterea codului pentru fiecare metodă definită de utilizator.** | + | Recomandăm folosirea unei instrucțiunii de load pentru această inițializare. |
- | * **Emiterea metodei ''startup'' care creează obiectul de tip Main si apelează metoda main.** | + | ==== 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. | ||
- | Puteţi organiza generatorul de cod în două etape: în prima se decide layout-ul obiectelor pentru fiecare clasă (deplasamentele atributelor şi ale metodelor), iar în a doua se generează cod pentru fiecare metodă (inclusiv metodele definite implicit pentru iniţializare). | + | 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). | ||
- | ==== Suportul de execuție pentru LCPL ==== | + | Pentru funcția următoare: |
- | + | ||
- | == Reprezentarea datelor == | + | |
- | + | ||
- | * Reprezentarea unui obiect în memorie arată astfel: | + | |
- | + | ||
- | ^ Offset ^ Descriere ^ | + | |
- | | +0 | Pointer către rtti al clasei din care face parte obiectul | | + | |
- | | +4 | Atributele obiectului - întregi pe 4 bytes sau referinţe către alte obiecte. | | + | |
- | + | ||
- | Referinţa la un obiect este adresa din memorie a acelui obiect. | + | |
- | + | ||
- | * Reprezentarea rtti in memorie arată astfel | + | |
- | + | ||
- | ^ Offset ^ Descriere ^ | + | |
- | | +0 | Pointer către obiectul String care reprezintă numele clasei | | + | |
- | | +4 | Dimensiunea in bytes a obiectului, incluzând informația de runtime | | + | |
- | | +8 | Pointer către informația de runtime a clasei parinte, NULL pentru Object | | + | |
- | | +12 | Tabela de metode | | + | |
- | + | ||
- | Pe prima poziție din tabela de metode se va afla adresa constructorului clasei, urmând apoi adresele metodelor din clasa parinte și metodele clasei. | + | |
- | + | ||
- | == Exemple de reprezentare == | + | |
- | + | ||
- | Pentru: | + | |
- | class Main inherits IO | + | |
- | main : | + | |
- | [out "Hello world!"]; | + | |
- | end; | + | |
- | end; | + | |
- | + | ||
- | se genereaza urmatoarele: | + | |
- | + | ||
- | * Obiectul de tip String care reprezintă numele clasei | + | |
- | + | ||
- | @.str = constant [5 x i8] c"Main\00" | + | |
- | @NMain = global %struct.TString { %struct.__lcpl_rtti* @RString, i32 4, i8* getelementptr ([5 x i8]* @.str, i32 0, i32 0) } | + | |
- | + | ||
- | * Informația de runtime pentru clasa Main | + | |
- | + | ||
- | %0 = type { %struct.TString*, i32, %struct.__lcpl_rtti*, [7 x i8*] } | + | |
- | @RMain = global %0 { %struct.TString* @NMain, i32 4, %struct.__lcpl_rtti* @RIO, | + | |
- | [7 x i8*] [ | + | |
- | i8* bitcast (void (%struct.TMain*)* @Main_init to i8*), | + | |
- | i8* bitcast (void (%struct.TObject*)* @M6_Object_abort to i8*), | + | |
- | i8* bitcast (void (%struct.TObject*)* @M6_Object_typeName to i8*), | + | |
- | i8* bitcast (%struct.TIO* (%struct.TObject*)* @M6_Object_copy to i8*), | + | |
- | i8* bitcast (%struct.TString* (%struct.TIO*)* @M2_IO_in to i8*), | + | |
- | i8* bitcast (void (%struct.TIO*, %struct.TString*)* @M2_IO_out to i8*), | + | |
- | i8* bitcast (void (%struct.TMain*)* @M4_Main_main to i8*) | + | |
- | ]} | + | |
- | + | ||
- | Clasa Main nu are atribute proprii, prin urmare dimensiunea ei este de 4 bytes, dimensiunea pointerului la rtti. Main este derivată din clasa IO, deci informația de parent va arata către rtti-ul clasei IO (@RIO). În tabela de funcții a clasei Main, pe prima poziție este adresa constructorului clasei Main_init, apoi urmează metodele clasei părinte IO și metoda main a clasei Main. | + | |
- | + | ||
- | * Constructorul clasei | + | |
- | + | ||
- | define void @Main_init(%struct.TMain* %self) { | + | |
- | %1 = alloca %struct.TMain* | + | |
- | store %struct.TMain* %self, %struct.TMain** %1 | + | |
- | + | ||
- | %2 = load %struct.TMain** %1 | + | |
- | %3 = bitcast %struct.TMain* %2 to %struct.TObject* | + | |
- | call void @Object_init(%struct.TObject* %3) | + | |
- | + | ||
- | ret void | + | |
- | } | + | |
- | + | ||
- | + | ||
- | Din constructorul clasei Main se apelează constructorul clasei părinte, pentru a se inițializa atributele clasei părinte. | + | |
- | + | ||
- | * Metodele clasei | + | |
- | + | ||
- | define void @M4_Main_main(%struct.TMain* %self) { | + | |
- | ; Prologue - save parameters | + | |
- | %1 = alloca %struct.TMain* | + | |
- | store %struct.TMain* %self, %struct.TMain** %1 | + | |
- | ... | + | |
- | } | + | |
- | + | ||
- | + | ||
- | == Convenţia de apel == | + | |
- | + | ||
- | Primul parametru pentru orice funcție generată va conține adresa obiectului de care aceasta aparține (**self**). | + | |
- | Variabilele LCPL pot fi pe stivă sau în registre. Structurile precum tabelele virtuale sau informațiile de runtime sunt variabile globale LLVM IR. | + | |
- | + | ||
- | == Convenţia de nume == | + | |
- | + | ||
- | * Obiectele de tip String care reprezintă numele claselor din programul de intrare vor avea forma ''N<NumeClasă>''. | + | |
- | * Structurile care definesc layoutul obiectelor in memorie vor avea forma ''T<NumeClasă>'' | + | |
- | * Unordered List ItemInformația de runtime va avea forma ''R<NumeClasă>'' | + | |
- | * Metodele de inițializare vor avea forma ''<NumeClasă>_init'' | + | |
- | * Restul metodelor se vor genera pe principiul ''M<N>_<NumeClasă>_<NumeMetodă>'', unde N este numărul de caractere al numelui clasei | + | |
- | + | ||
- | == Funcţii şi date predefinite pentru LCPL == | + | |
- | + | ||
- | Biblioteca de runtime LCPL implementează funcţionalitatea claselor de bază aşa cum este descrisă în {{:cpl:teme:lcpl-manual.pdf | manualul limbajului LCPL}}. În codul generat metodele din bibliotecă pot fi folosite respectând convenţia ca obiectul apelant să fie primul parametru al apelului. Este foarte recomandat să folosiţi această convenţie pentru toate clasele şi metodele, nu doar pentru clasele de bază. | + | |
- | + | ||
- | == Secvenţa de iniţializare == | + | |
- | + | ||
- | Punctul de intrare în program este funcția main din biblioteca de runtime. În ea este apelată funcția startup care: | + | |
- | + | ||
- | * se creeaza un obiect de tip Main | + | |
- | * se apeleaza prin vtable metoda main a acestui obiect | + | |
- | + | ||
- | Deoarece metoda main poate exista in clasa Main sau intr-o clasă din care este derivată clasa Main, nu se poate ști la runtime care este indexul metodei main in tabela de metode a clasei Main. Prin urmare, codul generat de voi va trebui sa conțina si funcția ''startup'' care apelează funcția main prin vtable, cu indexul corect, cunoscut la momentul generării codului. | + | |
- | + | ||
- | ===== Detalii C++ API LLVM ===== | + | |
- | + | ||
- | API-ul complet pentru generarea de cod LLVM il gasiti pe site-ul oficial llvm.org | + | |
- | === Generarea de constante === | + | |
- | * intregi | + | |
<code> | <code> | ||
- | static ConstantInt * ConstantInt::get (LLVMContext &Context, const APInt &V); | + | int lt(int a, int b) { |
- | </code> | + | return a < b; |
- | * string: | + | } |
- | <code> | + | |
- | Constant * ConstantDataArray::getString ( LLVMContext &Context, | + | |
- | StringRef Str, | + | |
- | bool AddNull = true | + | |
- | ) [static] | + | |
</code> | </code> | ||
- | === Generarea tipurilor corespunzatoare claselor definite === | + | Codul generat ar putea fi următorul: |
<code> | <code> | ||
- | ///\ creare structura | + | cmp r0, r1 |
- | static StructType * StructType::create (LLVMContext &Context, StringRef Name) | + | mvn r0, #0 |
- | ///\ adaugare campuri | + | movge r0, #0 |
- | void setBody (ArrayRef< Type * > Elements, bool isPacked=false) | + | mov r1, #1 |
+ | and r0, r0, r1 | ||
+ | orr r15, r14, r14 @ Return | ||
</code> | </code> | ||
- | === Generarea prototipurilor de functii === | + | ==== Stiva ==== |
- | * tipul functiei | + | Adresele de pe stivă sunt reprezentate printr-un operand special, numit **Frame Index**. |
- | <code> | + | Acest operand este un întreg (poate avea și valori negative) care caracterizeaza o intrare pe stiva: offset, dimensiune, aliniament. |
- | static FunctionType * FunctionType::get (Type *Result, ArrayRef< Type * > Params, bool isVarArg); | + | |
- | </code> | + | |
- | exemplu pentru void func(int, int); | + | |
- | <code> | + | |
- | ///\ pregatire parametri | + | |
- | std::vector<llvm::Type*> func_args; | + | |
- | func_args.push_back(llvm::IntegerType::get(mod->getContext(), 32)); | + | |
- | func_args.push_back(llvm::IntegerType::get(mod->getContext(), 32)); | + | |
- | FunctionType* func_type = FunctionType::get( | + | |
- | llvm::Type::getVoidTy(mod->getContext()), // rezultat | + | |
- | func_args, // parametri | + | |
- | false); // isVarArg | + | |
- | </code> | + | Î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. | ||
- | Este recomandat sa creati si pointerul catre FunctionType | + | În prologul și epilogurile funcției se introduc instrucțiuni care modifică stiva cu cantitatea necesară funcției. |
- | <code> | + | |
- | PointerType* pointer_func_type = PointerType::get(func_type, 0); | + | |
- | </code> | + | |
- | * declararea functiei | + | |
- | <code> | + | |
- | static Function * Create (FunctionType *Ty, // pt exemplul de mai sus //func_type// | + | |
- | LinkageTypes Linkage, | + | |
- | const Twine &N="", // numele functiei | + | |
- | Module *M=nullptr) // modulul din care face part | + | |
- | </code> | + | |
- | * calling convention | + | |
- | <code> | + | |
- | void setCallingConv (CallingConv::ID CC) | + | |
- | </code> | + | |
- | In tema //setCallingConv// va primi parametru: | + | |
- | <code> | + | |
- | CallingConv::C | + | |
- | </code> | + | |
- | === Generarea corpului unei functii === | + | ===== 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. | ||
- | === Definirea unui basic block === | + | Fiecare task este marcat în cod prin intermediul comentariilor cu **TODO**. |
- | <code> | + | |
- | static BasicBlock * Create (LLVMContext &Context, | + | |
- | const Twine &Name="", | + | |
- | Function *Parent=0, | + | |
- | BasicBlock *InsertBefore=0) | + | |
- | </code> | + | |
- | === Instructiuni LLVM de care ati putea avea nevoie === | + | Arhiva de start nu se poate compila. |
- | Atentie: Prototipurile de mai jos sunt doar sugestii. Studiati LLVM API si alegeti varianta care considerati ca se potriveste cel mai bine cazului pentru care generati cod. | + | |
- | <code> | + | |
- | // alocare spatiu pe stiva | + | |
- | AllocaInst (const Type *Ty, const Twine &Name, BasicBlock *InsertAtEnd) | + | |
- | // salvare in memorie | + | |
- | StoreInst (Value *Val, Value *Ptr, bool isVolatile, BasicBlock *InsertAtEnd) | + | |
- | // incarca din memorie | + | |
- | LoadInst (Value *Ptr, const char *NameStr, bool isVolatile, BasicBlock *InsertAtEnd) | + | |
- | // incarca un camp dintr-o structura | + | |
- | static GetElementPtrInst * Create (Value *Ptr, | + | |
- | Value *Idx, | + | |
- | const Twine &NameStr, | + | |
- | BasicBlock *InsertAtEnd) | + | |
- | // operatii aritmetice binare | + | ==== Task 1 ==== |
- | static BinaryOperator * Create (BinaryOps Op, // Instruction::Add | Instruction::Sub ... | + | Definiți instrucțiunile care compun setul de instrucțiuni corespunzător arhitecturii Cpl. |
- | Value *S1, | + | Acest lucru se realizează în fișierul **CplInstrInfo.td** prin instanțierea claselor definite în fișierul **CplInstrFormat.td**. |
- | Value *S2, | + | 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//. |
- | const Twine &Name, | + | 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. |
- | BasicBlock *InsertAtEnd) | + | |
- | // operatiile unare in llvm sunt tot operatii binare cu un pseudo operand | + | |
- | static BinaryOperator * CreateNeg (Value *Op, const Twine &Name, BasicBlock *InsertAtEnd) | + | |
- | // comparatii si branch | + | ==== Task 2 ==== |
- | ICmpInst (BasicBlock &InsertAtEnd, Predicate pred, Value *LHS, Value *RHS, const Twine &NameStr="") | + | 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**. |
- | static BranchInst * Create (BasicBlock *IfTrue, | + | Descrierea acestui **calling convention** pentru target-ul **Cpl** o găsiți în secțiunea de [[cpl:teme:tema4##calling_convention|arhitectură]]. |
- | BasicBlock *IfFalse, | + | |
- | Value *Cond, // rezultatul comparatiei (new ICmpInst) | + | |
- | BasicBlock *InsertAtEnd) | + | |
- | </code> | + | |
- | ===== Detalii LLVM IR ===== | + | Trebuie să modificați în fișierul **CplCallingConv.td** registrele folosite pentru transmiterea parametrilor, rezultatului și registrele salvate de funcția apelată. |
- | Fisierul generat de voi este un modul scris în limbajul intermediar LLVM. Acest fişier va fi apoi transformat în asamblare pentru arhitectura target de catre llc, legat cu biblioteca de runtime şi apoi executat pe plaforma target. O descriere exhaustiva a limbajului se găseşte în [[http://llvm.org/docs/LangRef.html|documentaţia oficială]]; mai jos sunt descrise pe scurt elementele de limbaj necesare pentru temă. | ||
- | Un modul LLVM conţine definiţii de funcţii, de variabile globale şi declaraţii de simboluri externe modulului. Un obiect global (funcţie sau variabilă globală) este reprezentat prin adresa lui de memorie. Această adresă este un identificator care începe cu caracterul '@' şi poate conţine litere, cifre, caracterele '.' si '$' . | + | ==== 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. | ||
- | Cateva exemple: | + | Această instrucțiune se poate genera foarte ușor din următorul cod **C**: |
- | + | <code> | |
- | ; Un comentariu LLVM incepe cu caracterul ; | + | int f(bool c, int a, int b) { |
- | + | return c ? a : b; | |
- | ; Definiţia unei variabile globale - şir de caractere | + | } |
- | ; Numele ".str1" este declarat "internal" pentru a nu intra in conflict cu alte nume | + | </code> |
- | ; Variabila @.str1 este "constant" - nu îşi va schimba valoarea pe parcursul programului | + | |
- | @.str1 = internal constant [13 x i8] c"Hello world!\00" | + | |
- | + | ||
- | ; Definiţia unei funcţii | + | |
- | define i32 @addOne(i32 %arg) | + | |
- | { | + | |
- | %1 = add i32 %arg, 1 | + | |
- | ret i32 %1 | + | |
- | } | + | |
- | + | ||
- | ; Declaraţia unei funcţii externe (în cazul nostru, implementată în biblioteca runtime) | + | |
- | declare void @__lcpl_checkNull(i8*) | + | |
- | + | ||
- | == Tipuri şi iniţializări == | + | |
- | Tipurile de bază sunt valori care pot fi tinute în regiştri. In cazul LCPL, acestea sunt valori de tip întreg (i32), caracter (i8) sau adresă de memorie, care este pointer/referinţă la un alt tip (de exemplu i32*). | + | |
- | LLVM permite tipuri complexe: tablouri: [ 5 x i8 ], structuri: { i32, i8*, i8* }, pointeri la funcţii : i32 (i32,i32)* . Constructia %nume=type ''expresie tip'' crează un alias ''%nume'', ce va fi înlocuit cu ''expresie tip'' oriunde apare in program. | + | ==== 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). | ||
- | ; tipul care reprezintă informația de runtime pentru clasa String | + | Tot după alocarea de registre, pot să existe unele transferuri. |
- | %0 = type { %struct.TString*, i32, %struct.__lcpl_rtti*, [7 x i8*] } | + | 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. | |
- | ; Obiectul RMain reprezintă informatia de runtime pentru clasa Main | + | |
- | ; si este de tip %0, definit mai sus | + | |
- | @RMain = global %0 { %struct.TString* @NMain, i32 4, %struct.__lcpl_rtti* @RIO, | + | |
- | ... | + | |
- | O definiţie de variabilă globală trebuie iniţializată cu valori constante. In plus, variabilele globale de tip tablou de i8 pot fi initializate cu siruri de caractere. Urmatoarele caractere nu pot fi reprezentate direct si trebuie inlocuite cu valoarea lor hexazecimala: | + | ==== 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ă. | ||
- | | " | \22 | | + | Pentru **return**-ul unei funcții, se generează o pseudo-instrucțiune **RET**. |
- | | \ | \5C | | + | Aceasta va trebui înlocuită cu **ORR_RET**, pentru a transfera **LR** în **PC**. |
- | | \n | \0A | | + | |
- | | \r | \0D | | + | |
- | | null | \00 | | + | |
- | Inlocuirea se face astfel: | + | ==== Task 6 ==== |
+ | În cadrul acestui task, trebuie sa generați cod pentru secvențe de forma: | ||
<code> | <code> | ||
- | 1) Se fac modificarile scriind un buffer char *<input_string> (C) | + | int lt(int a, int b) { |
- | 2) sprint(<output_string>, "%s", <input_string>) - pentru a ne asigura ca se trateaza celelalte caractere speciale (de exemplu \t) | + | return a < b; |
- | 3) llvm::Constant *const_str = ConstantDataArray::getString(module->getContext(), <output_string>, true); | + | } |
</code> | </code> | ||
- | [[http://llvm.org/docs/LangRef.html#constantexprs|Expresiile folosite la iniţializare]] pot conţine conversii de pointeri, de exemplu: | + | 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ă. | ||
- | | bitcast i32 (i32,i32)* @main.isEven to i8 * | Converteşte adresa funcţiei main.isEven la un pointer la un sir de i8 | | + | Consultați secțiunea [[cpl:teme:tema4#comparatii|comparații]] pentru detalii de implementare. |
- | | i8* getelementptr ([7 x i8]* @s62, i32 0, i32 2) | Citeşte adresa celui de-al treilea element din şirul de 7 caractere s62 | | + | |
- | <note> La prima vedere, getelementptr are un index in plus (i32 0). Aceasta pentru ca s62 poate fi adresa de început a unui tablou de 7 caractere, dar poate fi si un pointer la începutul unui rând dintr-un tablou bidimensional cu 7 coloane. getelementptr întoarce astfel adresa elementului de pe rândul 0, coloana 2. Codul C echivalent este: | + | ===== Testare ===== |
+ | Testarea se va face folosind următoarea {{:cpl:teme:tema4-teste.tar.gz|arhivă}} | ||
- | char (*s62)[7]; | + | Setați în variabila de mediu ''CPL_LLC'' calea către executabilul de **llc**, care suportă target-ul **cpl**. |
- | &(s62[0][2]); | + | |
- | </note> | + | |
- | == Funcţii == | + | Testele se rulează folosind comanda: |
- | O funcţie în LLVM este alcătuită din mai multe basic blocuri. Un basic bloc începe cu un label şi se termină cu o instrucţiune de salt, condiţionat sau nu (de exemplu ''br'' sau ''ret'' ). Instrucţiunea de salt este obligatorie chiar dacă din basic blocul curent se trece direct în basic blocul urmator. Numele unui label este un identificator local funcţiei, ce începe cu %. De exemplu: | + | |
- | + | ||
- | | Cod C | + | |
- | int m(int c) { | + | |
- | if (c>20) | + | |
- | f(); | + | |
- | return c; | + | |
- | } | + | |
- | | Cod LLVM IR | + | |
- | define i32 @m(i32 %c) { | + | |
- | %1 = icmp sgt i32 %c, 20 | + | |
- | br i1 %1, label %L2, label %L3 | + | |
- | L2: | + | |
- | call void @f() | + | |
- | br label %L3 | + | |
- | L3: | + | |
- | ret i32 %c | + | |
- | } | + | |
- | | | + | |
- | + | ||
- | Un simbol local unei funcţii începe cu caracterul %, urmat de un identificator sau de un număr. Numerele trebuie alocate consecutiv şi încep de la 1 pentru fiecare funcţie. Simbolurile locale sunt valori care pot fi tinute în regiştri. | + | |
- | + | ||
- | Fiecare simbol local poate fi scris o singură dată, într-o singură instrucţiune în funcţie. LLVM este o reprezentare în forma SSA (Single Static Assignment). Această restricţie poate cauza probleme in diverse cazuri; pentru LCPL de exemplu | + | |
- | * rezultatul unei expresii de tip ''if'' trebuie calculat pe fiecare ramură. | + | |
- | * o variabilă locală LCPL se află în partea stângă a unei atribuiri în mai multe locuri in program (de exemplu ''x <- 1; x <- x + 1;'' ) | + | |
- | + | ||
- | Pentru a rezolva această problemă, LLVM introduce instrucţiunea phi. Aceasta se poate găsi doar la începutul unui basic bloc, şi atribuie o valoare in funcție de ramura pe care programul a ajuns din basic blocul precedent. De exemplu: | + | |
- | + | ||
- | | Cod C | + | |
- | int m(int c) { | + | |
- | return c > 20 ? f() : g() ; | + | |
- | } | + | |
- | | Cod LLVM | + | |
- | define i32 @m(i32 %c) { | + | |
- | %1 = icmp sgt i32 %c, 20 | + | |
- | br i1 %1, label %L2, label %L4 | + | |
- | L2: | + | |
- | %2 = call i32 @f() | + | |
- | br label %L6 | + | |
- | L4: | + | |
- | %3 = call i32 @g() | + | |
- | br label %L6 | + | |
- | L6: | + | |
- | %4 = phi i32 [ %2, %L2 ], [ %3, %L4 ] | + | |
- | ret i32 %4 | + | |
- | } | + | |
- | | | + | |
- | + | ||
- | Acesta este singurul caz in care aveți nevoie de instrucțiunea phi. În rest se recomandă generarea de simboluri locale temporare care sa țină rezultatele intermediare din evaluarea expresiilor. | + | |
- | În ceea ce privește variabilele definite de utilizator, ele trebuie alocate pe stivă. LLVM pune la dispozitie instrucţiunea ''alloca'' , ce aloca spaţiu pe stivă pentru o variabilă şi întoarce un pointer la acea variabilă. Instrucţiunile ''load'' şi ''store'' transferă o valoare din memorie într-un registru şi înapoi. Un exemplu: | + | |
- | + | ||
- | | | + | |
- | int m() { | + | |
- | int a=42; | + | |
- | return a; | + | |
- | } | + | |
- | | | + | |
- | define i32 @m() { | + | |
- | %a = alloca i32 | + | |
- | store i32 42, i32* %a | + | |
- | %1 = load i32* %a | + | |
- | ret i32 %1 | + | |
- | } | + | |
- | | | + | |
- | + | ||
- | De remarcat în exemplul de mai sus că %a este un registru, ce conţine un pointer către un int aflat pe stivă. %a nu conţine valoarea 42, ci adresa unei locaţii de memorie care conţine valoarea 42! | + | |
- | + | ||
- | (Exemplele folosesc limbajul C, deoarece permite un cod LLVM mai simplu de înţeles decât codul LCPL echivalent.) | + | |
- | + | ||
- | + | ||
- | + | ||
- | ===== Arhiva de pornire ===== | + | |
- | * Biblioteca de runtime LCPL. Aceasta implementează clasele si funcțiile predefinite din LCPL. Pentru a putea folosi această bibliotecă va trebui sa înţelegeţi şi să respectaţi reprezentarea internă a obiectelor LCPL. | + | |
- | * Un framework ce contine analiza semantică și partial generarea de cod. | + | |
- | + | ||
- | ===== Testare ===== | + | |
- | Tema poate fi testata local astfel: | ||
- | - În urma build-ului veți obține un executabil ''lcpl-codegen'': | ||
<code> | <code> | ||
- | ./lcpl-codegen <input_file> <output_file> | + | ./run_all.sh |
- | llvm-as <output_file> -o <output_file>.bc | + | |
- | llvm-link <output_file>.bc runtime.bc -o <output_file>_run.bc | + | |
- | lli <output_file>_run | + | |
</code> | </code> | ||
- | ''output_file'' - conține cod LLVM IR valid | + | Modul în care este distribuit punctajul pentru această temă este următorul: |
- | ===== Testare automata ===== | + | |
- | + | ||
- | Testarea temei de casă va folosi o serie de teste ce vor fi disponibile pe vmchecker. Modul în care este distribuit punctajul pentru această temă este următorul: | + | |
* **Testele publice (80p)** | * **Testele publice (80p)** | ||
- | * Testele ''simple'' (40p) | + | * 20 de teste, fiecare valorând 4 puncte |
- | * Testele ''advanced'' (30p) | + | |
- | * Testele ''complex'' (10p) | + | |
* **Calitatea implementării (20p)** | * **Calitatea implementării (20p)** | ||
* Organizarea codului sursă | * Organizarea codului sursă | ||
* Comentariile din cod | * 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. | * 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. | ||
- | |||
- | ===== Instrucţiuni de predare a temei ===== | ||
- | |||
- | Arhiva trebuie sa aiba structura din arhiva de start: | ||
- | * makefile in radacina | ||
- | * sa produca un executabil **lcpl-codegen** | ||
- | * nu puneti in arhiva fisiere obiect/executabile. | ||
===== Resurse ===== | ===== Resurse ===== | ||
- | * {{:cpl:teme:lcpl-manual.pdf | manualul limbajului LCPL}} | + | * {{:cpl:teme:cpl.tar.gz | Arhiva de start}} |
- | * {{http://llvm.org/releases/3.6.0/docs/tutorial/LangImpl3.html | Tutorial generare de cod folosind LLVM C++ API }} | + | * {{:cpl:teme:tema4-teste.tar.gz | Arhiva de testare}} |
- | * {{:cpl:teme:runtime.zip | Suportul de runtime}} | + | |
- | * {{:cpl:teme:lcpl-codegen.zip | Arhiva de start}} | + | |
- | * {{:cpl:teme:codegen_tests.zip | Arhiva teste}} (actualizată pe 06.01.2016) | + | |
- | ===== F A Q ===== | + | |
- | + | ||
- | ===== Change Log ===== | + | |
- | + |