În LLVM optimizările sunt implementate sub formă de Pass-uri care traversează programul pentru a-l analiza și a-l transforma. Obținerea de informații despre program prin analiza fluxului de date sau de control constituie un pas important în implementarea optimizărilor.
Pentru a aplica o anumită selecție de optimizări se poate folosi tool-ul opt prezentat și aici. Pentru a integra o optimizare nouă în sursele LLVM (fără să mai fim nevoiți să specificăm biblioteca dinamică la runtime) se va folosi sistemul de build din llvm și pașii descriși în exemplul de aici.
Informațiile de debug, pot fi afișate rulând utilitarul opt
cu parametrul -debug
. Pentru a filtra informațiile de debug doar pentru un anumit pas, se folosește parametrul -debug-only=<nume_pass>
. De exemplu, pentru a filtra doar log-urile optimizării hello, se rulează comanda:
opt -hello -debug-only=hello hello.bc
Un program este reprezentat în forma SSA dacă fiecărei variabile i se atribuie o valoare doar o singură dată şi fiecare folosire a variabilei este dominată de definiţia ei.
Un program poate fi convertit în forma SSA prin:
Exemple
Program inițial | Program în forma SSA |
---|---|
V = 4; = V + 5; V = 6; = V + 7; | V0 = 4; = V0 + 5; V1 = 6; = V1 + 7; |
if (...) X = 5; else X = 3; Y = X; | if (...) X0 = 5; else X1 = 3; X2 = O(X0, X1); Y0 = X2; |
j = 1; while (j < X) ++j; N = j; care se mai poate scrie şi j = 1; if (j >= X) goto E; S: j = j + 1; if (j < X) goto S; E: N = j; | j0 = 1; if (j0 >= X0) goto E; S: j1 = O(j0, j2); j2 = j1 + 1; if (j2 < X0) goto S; E: j3 = O(j0, j2); N0 = j3; |
Într-un basic block B având N predecesori P1, P2, …, PN, prin X = O(V1, V2, …, Vn) se înțelege că variabila X
va avea valoarea Vj dacă fluxul de control intră în blocul B din blocul Pj, 1⇐j⇐N.
Mai jos este un exemplu de instrucțiune (Instruction). Ea este şi utilizator (User) ale variabilelor (Value) a şi b. În acelaşi timp reprezintă şi definirea variabilei (Value) c.
%c = add i32 %a, %b
Aici este un exemplu de cum pot fi parcurşi toţi utilizatorii unei variabile.
Aici este un exemplu de cum pot fi parcurse toate basic block-urile dintr-o funcţie.
Aici este un exemplu de cum pot fi parcurse toate instrucţiunile dintr-un basic block.
Aici este un exemplu de cum pot fi parcurse toate instrucţiunile dintr-o funcţie.
Aici este un exemplu de cum pot fi parcurşi toţi predecesorii şi succesorii unui basic block.
Aici este un exemplu de cast folosit la exerciţiul 3.
Aici este o scurtă descriere a structurii de date ValueMap folosită la exerciţiul 3.
Aici este o scurtă descriere a structurii de date BitVector folosită la exerciţiul 3.
Compilati exercitiile cu -O0 pentru a nu lasa compilatorul sa aplice optimizari.
Scrieti un Pass de LLVM care sa elimine basic block-urile trivially dead. Hint: Un basic block este trivially dead daca nu exista niciun jump care sa duca catre acesta; cu alte cuvinte nu are predecesori.
Scrieti un pass care optimizeaza cazuri de tipul jump to jump. Un caz de jump to jump avem in momentul in care singura intructiune dintr-un basic block este un salt neconditionat intr-un alt bloc. In acest caz toate instructiunile de salt din blocul initial pot fi optimizate pentru a duce direct la blocul destinatie. Blocul initial este in acest moment dead code si poate fi eliminat cu pass-ul de la exercitiul anterior.
#include<stdio.h> #include<time.h> #include<stdlib.h> void main(void) { int x = rand() % 100 - 10; if ( x < 0 ) { return; } else { printf("Not negative\n"); } printf("End\n"); }
Folosind codul din fişierul Hello.cpp
din arhiva laboratorului, urmăriţi modul în care poate fi implementată simple constant propagation în LLVM.
test.c
din arhiva laboratorului.