This is an old revision of the document!
În cadrul acestei teme veti implementa generarea de cod pentru limbajul LCPL.
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 2 şi să genereze un fisier cu cod corect LLVM IR, in cazul in care analiza semnatica nu a detectat erori, sau o lista de erori.
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.
Pentru generarea de cod veti folosi LLVM C++ API. Pentru aceasta parte a temei vă veți ajuta de laboratorul 5, precum și de tutorialul de generare de cod de aici http://llvm.org/releases/3.6.0/docs/tutorial/LangImpl3.html. În cazul in care testul a trecut analiza semantică (nu s-au detectat erori), se va parcurge arborele completat și pentru fiecare nod se va genera cod LLVM IR.
Arhiva de pornire conține codul generat pentru un nod de tip clasa si pentru metode vide.
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.
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.
Vă recomandam să începeți cu înțelegerea documentației:
Odată ce ați citit documentația puteți trece la implementarea generării de cod, parucrgând următorii pași:
startup
care creează obiectul de tip Main si apelează metoda main.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).
* 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.
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 ... }
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.
N<NumeClasă>
. T<NumeClasă>
R<NumeClasă>
<NumeClasă>_init
M<N>_<NumeClasă>_<NumeMetodă>
, unde N este numărul de caractere al numelui claseiBiblioteca de runtime LCPL implementează funcţionalitatea claselor de bază aşa cum este descrisă în manual 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ă.
Punctul de intrare în program este funcția main din biblioteca de runtime. În ea este apelată funcția startup care:
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.
API-ul complet pentru generarea de cod LLVM il gasiti pe site-ul oficial llvm.org
static ConstantInt * ConstantInt::get (LLVMContext &Context, const APInt &V);
Constant * ConstantDataArray::getString ( LLVMContext &Context, StringRef Str, bool AddNull = true ) [static]
///\ creare structura static StructType * StructType::create (LLVMContext &Context, StringRef Name) ///\ adaugare campuri void setBody (ArrayRef< Type * > Elements, bool isPacked=false)
static FunctionType * FunctionType::get (Type *Result, ArrayRef< Type * > Params, bool isVarArg);
exemplu pentru void func(int, int);
///\ 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
Este recomandat sa creati si pointerul catre FunctionType
PointerType* pointer_func_type = PointerType::get(func_type, 0);
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
void setCallingConv (CallingConv::ID CC)
In tema setCallingConv va primi parametru:
CallingConv::C
static BasicBlock * Create (LLVMContext &Context, const Twine &Name="", Function *Parent=0, BasicBlock *InsertBefore=0)
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.
// 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 static BinaryOperator * Create (BinaryOps Op, // Instruction::Add | Instruction::Sub ... Value *S1, Value *S2, const Twine &Name, 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 ICmpInst (BasicBlock &InsertAtEnd, Predicate pred, Value *LHS, Value *RHS, const Twine &NameStr="") static BranchInst * Create (BasicBlock *IfTrue, BasicBlock *IfFalse, Value *Cond, // rezultatul comparatiei (new ICmpInst) BasicBlock *InsertAtEnd)
- TBD
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:
simple
(40p)advanced
(30p)complex
(10p) Va trebui sa implementaţi un generator de cod pentru limbajul LCPL care să producă o reprezentare intermediară LLVM. Arhitectura target a generatorului de cod va fi x86 32-bit.
Programul vostru va primi la intrare arborele sintactic (AST) în formatul de ieşire generat de tema precedentă şi va produce un fişier LLVM IR. Tema este considerată corectă dacă programul in limbaj intermediar este echivalent semantic cu programul LCPL de la intrare.
Pentru a putea rula programe LLVM IR trebuie instalat pachetul llvm.
Pentru ca generatorul de cod sa fie corect este necesar să înţelegeţi: * Comportamentul construcţiilor LCPL - descris în manualul limbajului LCPL. * Reprezentarea intermediară LLVM. * Suportul pentru runtime
Pentru realizarea temei, vă punem la dispoziţie: * O bibliotecă 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 parser si un analizor semantic pentru LCPL. * Cod care citește arborele sintactic si construiește structurile de date de la care trebuie pornită generarea de cod.
Este recomandat dar nu obligatoriu să folosiţi sursele de mai sus.
Vă recomandăm să reveniţi la această secţiune dupa ce aţi citit secţiunea despre LLVM şi despre biblioteca de runtime LCPL.
Generatorul de cod trebuie sa execute următorii pași:
Codul generat de voi trebuie să trateze eroarea de apel de metoda pe un obiect null. Pentru a trata această eroare, inainte de a apela o metodă pe un obiect trebuie verificat dacă respectivul obiect este null. Biblioteca de runtime oferă o funcție care face această verificare.
= Pe scurt despre LLVM =
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 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 ' llc example.ir; clang example.ir.s lcpl_runtime.c -o example; ./example
= Testarea automată = Temele trebuiesc submise pe vmchecker . Mai multe detalii găsiți în secțiunea de upload.
= Resurse = * Exemple de cod LLVM-IR pentru testele folosite în checker * Manualul oficial al limbajului LCPL * Limbajul de asamblare LLVM * Suportul de runtime pentru LCPL * Template-ul de pornire pentru temă * Arhiva de pornire pentru rezolvarea temei. * Arhiva cu suportul de runtime pentru LCPL. * Arhiva folosita la corectarea temei.
= Bibliografie = * LLVM Tutorial . Tutorialul nu se aplica direct temei (foloseste un API C++ pentru generarea de cod), insa contine informatii si exemple utile despre cum arata un generator de cod. * Cooper K., Torczon L. - Engineering a Compiler, capitolul Code Shape, în special subcapitolele 1-4. * Aho A., Lam M., Sethi R., Ullman J. - Compilers - Principles, Techniques & Tools, capitolul Intermediate-code generation, subcapitolele 6-9.
= F A Q =
= Change Log =