Table of Contents

Concurs

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:

  1. Analiză statică
  2. Analiză dinamică/instrumentare
  3. Optimizare
  4. Concurs

Informații organizatorice

Infrastructură

Enunț

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

Puteți considera că fișierul de intrare este întotdeauna corect formatat.

Analiză statică (30 puncte)

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

Hints/FAQ:

  • Q: Cum pot prelua o opțiune din linia de comandă în cadrul pasului LLVM?
  • A: Folosind API-ul CommandLine
  • Q: Cum afișez scorul final?
  • A: Folosind clasa Statistic

  • Exemplu de rulare:
$ 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

Analiză dinamică / Instrumentare (40 puncte)

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.

Hints/FAQ:

  • Q: Unde stochez scorul?
  • A: Va trebui să folosiți o variabilă globală pe care trebuie să o adăugați modulului LLVM IR.
  • Q: Ce tip trebuie să aibă variabila globală în care stochez scorul total?
  • A: Folosiți o variabilă de tipul întreg, fără semn, pe 64 de biți.
  • Q: Trebuie să tratez accesul concurent la variabila globală?
  • A: Nu.
  • Q: Cum afișez scorul final?
  • A: Folosind destructori. Destructorii sunt apelați după ce execuția unui program se încheie. Trebuie să creați și să inserați o funcție destructor în variabila globală llvm.global_dtors. Această funcție trebuie să afișeze scorul menținut pe parcursul rulării programului.

  • Exemplu de rulare:
$ 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

Optimizare (30 puncte)

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.

Hints/FAQ

  • Q: Cum minimizez numărul de branch-uri?
  • A: Un număr mare de instrucțiuni de control-flow sunt executate în cadrul buclelor. Va trebui să implementați o variantă de “loop unrolling”. Această optimizare țintită pe bucle reduce numărul de instrucțiuni br executate, crescând însă dimensiunea codului.
  • Q: Cum primesc punctajul pentru această parte?
  • A: Veți primi punctajul complet dacă pasul vostru de optimizare a) realizează desfacerea unor bucle, b) respectă regulamentul de realizare a temelor și c) scade costul de rulare a benchmark-ului sub un anumit prag.
  • Q: Ce pași de analiză LLVM am voie să folosesc?
  • A: Puteți folosi optimizările de promovare în regiștrii (mem2reg) și de propagarea constantelor (constprop), împreună cu pașii impliciți.
  • Q: Ce pași de analiză LLVM NU am voie să folosesc?
  • A: NU puteți folosi niciun alt pas care nu a fost menționat explicit.

Concurs

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ă.

Implementare

Pentru implementarea pașilor LLVM, puteți urmării pașii de mai jos:

  1. Descărcați arhiva de start în afara surselor LLVM și dezarhivați-o (Exemplu: zip concurs-arhiva.zip)
  2. Creați un director de build și intrați în el (Exemplu: mkdir build; cd build)
  3. Rulați cmake cu calea directorului unde ați dezarhivat arhiva (Exemplu: cmake ..)
  4. Compilați pasul (Exemplu: make)
  5. Dacă totul s-a compilat cu succes, ar trebui să se genereze biblioteca libLLVMCpl.so în directorul Cpl

Resurse