Scopul acestui concurs este familiarizarea cu API-ul pus la dispoziție de framework-ul LLVM. Folosind acest API trebuie să implementați o serie de pași de analiză/optimizare de bitcode LLVM. Tema este compusă din patru părți:
Premisa acestul concurs este că avem la dispoziție o arhitectură fictivă care execută instrucțiuni LLVM IR. Pentru a executa o instrucțiune LLVM IR pe procesorul fictiv este necesar un anumit număr de cicluri de ceas. Astfel, putem estima cât de costisitoare este execuția unei instrucțiuni.
Pentru aceasta fiecare pas va folosi un fișier de intrare care conține pe fiecare linie numele unei instrucțiuni LLVM IR și un cost/scor asociat. Exemplu:
br 10 add 7 sub 7 alloca 10 default 1
Token-ul special default
este folosit pentru a asocia un cost implicit pentru instrucțiunile care nu sunt menționate explicit în fișier. Dacă linia cu token-ul default
lipsește, costul implicit este 0
.
Fiecare pas LLVM implementat trebuie să citească costurile asociate instrucțiunilor din fișierul cpl-score-file.txt
. Pașii trebuie să poată permită folosirea unui alt fișier cu scoruri folosind argumentul -cpl-score-file
. Exemplu:
# run the optimization pass with the default score file opt -load=/path/to/libLLVMCpl.so -cpl-opt test.bc > out.bc # run the analysis/instrumentation pass with a custom score file opt -load=/path/to/libLLVMCpl.so -cpl-dynamic -cpl-score-file=my-custom-score-file.txt test.bc > out.bc
Pentru a rezolva această cerință va trebui să implementați un pas LLVM care, pe baza unui fișier de intrare precum cel de mai sus, să afișeze suma scorurilor tuturor instrucțiunilor din program.
Analiza trebuie să nu țină cont de instrucțiunile de control-flow întâlnite, de evaluarea unor condiții, de bucle etc. Cu alte cuvinte, tot ce trebuie să faceți este să iterați pe fiecare instrucțiune din fiecare funcție a programului, să îi contorizați scorul asociat iar la final să afișați scorul/costul total.
Pasul NU trebuie să analizeze conținutul funcțiilor implicite, al constructorilor sau al destructorilor.
Funcțiile implicite (intrinsics) sunt folosite intern de către LLVM. Aceste funcții pot fi identificate prin faptul că numele lor începe cu prefixul “llvm.*”. Se poate verifica ușor dacă o funcție este sau nu implicită folosind API-ul
Constructorii și destructorii sunt menținuți în câte un array global. Funcțiile din llvm.global_ctors
vor fi apelate atunci când modulul LLVM este încărcat (înainte de începerea efectivă a execuției programului), în ordinea ascendentă a priorității asociate. Funcțiile din llvm.global_dtors
sunt apelate atunci când modulul LLVM este descărcat (după terminarea efectivă a execuției programului), în ordinea descendentă a priorității.
Pentru lucrul cu contructori, puteți folosi clasa CtorUtils
$ opt -load=/path/to/libLLVMCpl.so -cpl-static -stats -cpl-score-file=cpl-score-file.txt test.bc > out.bc ===-------------------------------------------------------------------------=== ... Statistics Collected ... ===-------------------------------------------------------------------------=== 263 cpl - Static score of all instructions
Scorul static ne poate da o idee despre cât de “costisitoare” este execuția unui program, însă nu reprezintă o estimare realistă pentru toate cazurile de execuție. In practică, o instrucțiune se poate executa de mai multe ori (de exemplu dacă face parte dintr-o buclă) sau niciodată (de exemplu dacă face parte dintr-o funcție neapelată). De asemenea căile de execuție depind de multe ori de setul de date de intrare al programului. Calculul static al scorului nu ia în considerare aceste cazuri.
Pentru a rezolva a doua parte a temei va trebui să implementați un pas LLVM care instrumentează un program astfel încât costul execuției acestuia să fie calculat la runtime. Codul original trebuie modficat astfel: în interiorul fiecărui basic block trebuie adăugat cod care adună scorul total al BasicBlock-ului curent. Scorul total al BasicBlock-ului este egal cu suma scorurilor instrucțiunilor din care acesta este compus.
64
de biți.llvm.global_dtors
. Această funcție trebuie să afișeze scorul menținut pe parcursul rulării programului.
$ opt -load=/path/to/libLLVMCpl.so -cpl-dynamic -cpl-score-file=cpl-score-file.txt test.bc > out.bc $ clang out.bc -o out $ ./out 1113 cpl - Dynamic score for execution
Arhitectura pe care lucrăm are o particularitate: costul execuției instrucțiunilor br
și switch
(atât condiționale cât și necondiționale) este foarte ridicat.
Pentru a rezolva a treia parte a temei, trebuie să creați un pas de optimizare care să micșoreze costul de execuție reducând numărul de instrucțiuni br
executate (ex: mai puține bucle).
Evaluarea se va face folosind un set de benchmark-uri: costul de execuție pentru arhivarea/dezarhivarea unui anumit set de date, folosind utilitarele zip/unzip. Costul de execuție se calculează instrumentând varianta optimizată a programului de test folosind pass-ul implementat în partea a doua.
br
executate, crescând însă dimensiunea codului.mem2reg
) și de propagarea constantelor (constprop
), împreună cu pașii impliciți.
Regula este simplă: cine obține cel mai mic scor pentru benchmark-ul dat, câștigă. Pentru a micșora costul de execuție puteți implementa orice pași de optimizare doriți, pe care îi puteți apela în orice ordine. Atenție însă: nu aveți voie să folosiți pașii LLVM, nici direct (nu aveți voie să îi apelați), nici indirect (nu aveți voie să preluați cod - verificați ce înseamnă temă copiată). Aceeași regulă se aplică și pentru pașii de analiză.
Pentru implementarea pașilor LLVM, puteți urmării pașii de mai jos:
zip concurs-arhiva.zip
)mkdir build; cd build
)cmake
cu calea directorului unde ați dezarhivat arhiva (Exemplu: cmake ..
)make
)libLLVMCpl.so
în directorul Cpl