Upload
tech-talks-nsu
View
113
Download
1
Embed Size (px)
Citation preview
LLVM: Как приручить дракона?
Дмитрий Каш цын, HDSoftии
Как работает компилятор
1. Прочитать текст программы из файла
2. Записать исполняемый файл
Чуть детальнее
● Разбор лексем и синтаксический анализ
● Построение AST программы
● Преобразования (магия)
● Генерация объектного файла
● Линковка исполняемого файла
4
Но зачем?!*
* http://www.linux.org.ru/gallery/workplaces/10931314
5
Если серьезно
● Развитие computer science не стоит на месте
● Новые идеи — новые языки
● Новые языки — новые компиляторы
● Успех Rust и Go говорит сам за себя
6
Мой уютный компилятор™
● Парсинг исходников
● Представление программы?
● Оптимизация всего и вся
● Целевая архитектура, наборы инструкций
● Аллокация и распределение регистров
● Интероперабельность — системные вызовы, FFI, работа с библиотеками
● Генерация исполняемого файла, линковка
● Отладочная информация
7
Структура команды x86 (OMG)
http://penberg.blogspot.ru/2010/04/short-introduction-to-x86-instruction.html
8
Как угодить всем?
● Языки разные. Очень.
● Разное отношение к данным
● Разные модели памяти
● Разные целевые архитектуры
● Разные наборы инструкций
9
Решение LLVM
● Запись программы в виде, не зависящем от всего вышеперечисленного
● Промежуточное представление — IR код
● Обобщенная система команд
10
Решение LLVM
● Запись программы в виде, не зависящем от всего вышеперечисленного
● Промежуточное представление — IR код
● Обобщенная система команд
Внезапно: LLVM — это не VM o_O
11
Разбор исходного текста
● LLVM считает, что AST уже есть
● Чтобы его получить, нужно провести лексический и синтаксический анализ
● К счастью, это не надо делать вручную
● На помощь придут
– Flex/Bison
– ANTLR
– И другие
12
Так видит программу человек
int gcd(int a, int b) {
while (b != 0) { if (a > b) a = a − b; else b = b − a; }
return a;}
13
Так видит программу компилятор
condition
body
else-bodyif-body
while
variable
name: b
constant
value: 0
compare
op: ≠
branch
compare
op: >assign
bin op
op: −
assign
bin op
op: −
statement
sequence
return
variable
name: a
variable
name: a
variable
name: a
variable
name: a
variable
name: a
variable
name: b
variable
name: b
variable
name: b
variable
name: b
condition
14
Лексический анализ
● Первичный разбор текста программы
● Удаление лишнего шума
● Преобразование потока входных символов в последовательность токенов
15
Лексический анализатор flex
● Позволяет записать правила разбора в текстовом, человеко-читаемом виде
● Регулярные выражения — это сила
● На выходе — токены
16
Входной файл для flex (огрызок)
whitespace [ \r\n\t]*number [0-9]+identifier [a-zA-Z]+
{whitespace} { /* ignore whitespaces */ }
{number} { sscanf(yytext, "%d", &yylval->value); return TOKEN_NUMBER; }
"+" { return PLUS; }"-" { return MINUS; }"(" { return LPAREN; }")" { return RPAREN; }";" { return SEMICOLON; }"," { return COMMA; }"=" { return EQ; }"!=" { return NEQ; }">" { return GTR; }"if" { return IFSYM; }"while" { return WHILESYM; }
17
Синтаксический анализатор bison
● На вход получает поток токенов от лексера и файл описания грамматики
● На основании правил грамматики производит разбор
● На выходе дает древовидное представление программы с которым может работать компилятор
18
Входной файл для bison (огрызок)
input: | input line;
line: '\n' | exp '\n' { complete("result is %g\n", $1); };
exp: TOKEN_NUMBER { $$ = $1; } | exp '+' exp { $$ = $1 + $3; } | exp '-' exp { $$ = $1 - $3; } | exp '/' exp { $$ = $1 / $3; } | exp '*' exp { $$ = $1 * $3; } | '(' exp ')' { $$ = $2; };
Позволяет разбирать арифметические выражения вида (2 + 3 * 4) * 5
19
IR код
● Intermediate Representation
● Запись графа управления в привычном текстовом виде…
20
IR код
● Intermediate Representation
● Запись графа управления в привычном текстовом виде…
но с плюшками:
– Asm-подобный синтаксис, но с параметризованными функциями
– Строгая типизация
– Структуры данных, указатели
– Const, Volatile модификаторы
– Нотация SSA
21
Нотация SSA
● Static Single Assignment
● Переменных — нет о_О
● Все имена встречаются только единожды
● Функциональный стиль описания данных
● Императивный стиль описания операций
22
Что хорошего в SSA?
X ← 1
X ← 2
Y ← X
23
Что хорошего в SSA?
X ← 1
X ← 2
Y ← X
X1←1
X2←2
Y1←X2
24
Что хорошего в SSA?
X ← 1
X ← 2
Y ← X
X1←1 (RIP)
X2←2
Y1←X2
25
Что хорошего в SSA?
X ← 1
X ← 2
Y ← X
X1←1 (RIP)
X2←2 (RIP)
Y1←2
26
Граф потока управления*
● Control flow graph (CFG)
● Узлы — базовые блоки
● Ребра — инструкции переходов
● Базовый блок — линейный участок кода
(a)
(c)
(b)
(d)
* https://en.wikipedia.org/wiki/Control_flow_graph
27
Циклы и ветвления в SSA
28
Циклы и ветвления в SSA
29
Циклы и ветвления в SSA
30
Подсчет суммы массива
int sum_array(int* input, int length) { int sum = 0; for (int i = 0; i < length; ++i) sum += input[i]; return sum;}
31
Листинг IR кода
1 ; Function Attrs: nounwind readonly2 define i32 @sum_array(int*, int)(i32* nocapture readonly %input, i32 %length) #0 {3 %1 = icmp sgt i32 %length, 0 ; а есть вообще что суммировать?4 br i1 %1, label %.lr.ph, label %._crit_edge
5 ._crit_edge: 6 %sum.0.lcssa = phi i32 [ 0, %0 ], [ %4, %.lr.ph ]7 ret i32 %sum.0.lcssa ; возврат результата
8 .lr.ph: 9 %i.02 = phi i32 [ %5, %.lr.ph ], [ 0, %0 ]10 %sum.01 = phi i32 [ %4, %.lr.ph ], [ 0, %0 ] 11 ; вычисление адреса текущего элемента в массиве и его загрузка в регистр12 %2 = getelementptr inbounds i32, i32* %input, i32 %i.02 13 %3 = load i32, i32* %2, align 4 14 ; аккумулирование суммы и инкремент индекса15 %4 = add nsw i32 %3, %sum.01 ; новое значение sum16 %5 = add nuw nsw i32 %i.02, 1 ; новое значение i 17 ; условие выхода18 %exitcond = icmp eq i32 %5, %length 19 ; проверка условия выхода и переход20 br i1 %exitcond, label %._crit_edge, label %.lr.ph21 }
32
Результат clang -O1 -m32
sum_array(int const*, int): mov ecx, dword ptr [esp + 8]xor eax, eaxtest ecx, ecxjle .LBB0_3mov edx, dword ptr [esp + 4]
.LBB0_2: # %.lr.phadd eax, dword ptr [edx]add edx, 4dec ecxjne .LBB0_2
.LBB0_3: # %._crit_edgeret
33
Результат clang -O1 (x64)
sum_array(int const*, int): xor eax, eaxtest esi, esijle .LBB0_2
.LBB0_1: # %.lr.phadd eax, dword ptr [rdi]add rdi, 4dec esijne .LBB0_1
.LBB0_2: # %._crit_edgeret
34
Оптимизации
● Наблюдаемое поведение
● Точки следования
● Эквивалентные преобразования
35
Основные идеи оптимизаций
● Не делать то, что никому не нужно
● Не делать дважды то, что можно сделать один раз (а лучше не делать вообще)
● Если можно получить тот же результат, но меньшими усилиями — это нужно сделать
● Сокращение издержек на всех уровнях
36
Виды оптимизаций
● Peephole оптимизации — буквально «через замочную скважину». Локальные оптимизации в пределах базового блока
● Внутрипроцедурные оптимизации
● Межпроцедурные оптимизации
● Оптимизации во время линковки
37
Loop Invariant Code Motion (LICM)
● Вынос инвариантных значений за пределы цикла
● Перенос значений из тела цикла в базовый блок возврата
int sum_array(int* input, int length) { int sum = 0; for (int i = 0; i < length; ++i) { if (sum > length * 1024 * 1024) printf("note: limit exceeded");
sum += input[i];}
return sum;}
38
Loop Invariant Code Motion (LICM)
● Вынос инвариантных значений за пределы цикла
● Перенос значений из тела цикла в базовый блок возврата
int sum_array(int* input, int length) { int sum = 0; for (int i = 0; i < length; ++i) { if (sum > length * 1024 * 1024) printf("note: limit exceeded");
sum += input[i];}
return sum;}
39
Loop Invariant Code Motion (LICM)
● Вынос инвариантных значений за пределы цикла
● Перенос значений из тела цикла в базовый блок возврата
int sum_array(int* input, int length) { int sum = 0; const int len = length * 1024 * 1024;
for (int i = 0; i < length; ++i) { if (sum > len) printf("note: limit exceeded");
sum += input[i];}
return sum;}
40
Common subexpression elimination (CSE)
● Удаление общих подвыражений
int sum_array(int* input, int length) { int sum = 0; int max = 0; for (int i = 0; i < length; ++i) { max = (input[i] > max) ? input[i] : max; sum += input[i];
}
printf("max was %d\n", max); return sum;}
41
Common subexpression elimination (CSE)
● Удаление общих подвыражений
int sum_array(int* input, int length) { int sum = 0; int max = 0; for (int i = 0; i < length; ++i) { max = (input[i] > max) ? input[i] : max; sum += input[i];
}
printf("max was %d\n", max); return sum;}
42
Common subexpression elimination (CSE)
● Удаление общих подвыражений
int sum_array(int* input, int length) { int sum = 0; int max = 0; for (int i = 0; i < length; ++i) { const int input_i = input[i];
max = (input_i > max) ? input_i : max; sum += input_i;
}
printf("max was %d\n", max); return sum;}
43
Что можно почитать
● blog.llvm.org
● llvm.org/docs
● halt.habrahabr.ru/topics/
● llst.org
Спасибо за внимание!