Kompilator - zwijanie stałych

12 Lutego 2019

Podczas kompilacji dochodzi do wielu optymalizacji. Jest wiele operacji, które można wykonać raz podczas kompilacji, zamiast podczas każdego wykonania programu. Poniżej opiszę zwijanie stałych i propagację stałych.

Zwijanie stałych (z ang. constant folding) to proces, podczas którego wykonujemy operacje na stałych, aby uprościć kod wykonywalny bez zmiany logiki.

Jeśli napotkamy następujący kawałek kodu

int f(int x) {
    int twoHours = 2 * 60 * 60 * 1000;
    return twoHours + x;
}

To w wyniku zwijania stałych otrzymamy

int f(int x) {
    int twoHours = 7200000;
    return twoHours + x;
}

Analogicznie będzie to wyglądało dla pozostałych operacji algebraicznych. Co więcej, mnożenie i dodawanie jest przemienne, więc

y = 2 + 4 + x + 6;

Można bez problemu sprowadzić do

y = x + 12;

W przypadku odejmowania i dzielenia jest trochę ciężej, aczkolwiek

x - 10 - y - 5   ===   x - (y + 15)
x / 100 / 7 / y  ===   x / (700 * y)

Również można popełnić optymalizację wyrażeń boolowskich

true  && x === x
false || x === x

W mojej implementacji dokonuję linearyzacji wyrażeń o tym samym operatorze i zbieram do kupy stałe, a potem odbudowywuję drzewo wyrażeń.

Propagacja stałych

Kolejnym elementem jest propagacja stałych. Właściwie propagacja stałych powinna się zdarzyć przed zwijaniem.

int f(int x) {
    int y = 5;
    int z = y + y + 5;
    int w = x - z;
    return w;
}

Możemy spamiętywać wartość zmiennej, jeśli jest stała, dopóki nie zostanie zmieniona i wpisywać ją bezpośrednio w wyrażenia.

int f(int x) {
    int y = 5;
    int z = 5 + 5 + 5;  //i zwijamy
    int w = x - 15;
    return w;
}

Tutaj logika propagacji może być nieco bardziej złożona. Jak rozpatrujemy if ... else ... to nawet jeśli w bloku prawdy nastąpi zmiana to w bloku fałszu możemy używać stałej wartości. W pętli while przed wejściem sprawdzamy, które zmienne są zmienane i usuwamy je z listy stałych.

W dodatku możemy nieco zacząć porządkować kod

if(true) I1 else I2  >=> I1
if(false) I1 else I2 >=> I2
while(false) I       >=> 

Również w przypadku pracy z obiektami mamy dodatkowe profity. A dokładniej zgłaszamy błąd jeśli próbujemy odwołać się do nulla.

String x = null;
print(x.subString(0,1));
      ^ ERROR expression is always null
Kod

W moim kompilatorze powyższe optymalizacje znajdują się w jednym module ConstantFolder.