44
LLVM: Как приручить дракона? Дмитрий Каш цын, HDSoft и и

Как приручить дракона: введение в LLVM

Embed Size (px)

Citation preview

Page 1: Как приручить дракона: введение в LLVM

LLVM: Как приручить дракона?

Дмитрий Каш цын, HDSoftии

Page 2: Как приручить дракона: введение в LLVM

Как работает компилятор

1. Прочитать текст программы из файла

2. Записать исполняемый файл

Page 3: Как приручить дракона: введение в LLVM

Чуть детальнее

● Разбор лексем и синтаксический анализ

● Построение AST программы

● Преобразования (магия)

● Генерация объектного файла

● Линковка исполняемого файла

Page 4: Как приручить дракона: введение в LLVM

4

Но зачем?!*

* http://www.linux.org.ru/gallery/workplaces/10931314

Page 5: Как приручить дракона: введение в LLVM

5

Если серьезно

● Развитие computer science не стоит на месте

● Новые идеи — новые языки

● Новые языки — новые компиляторы

● Успех Rust и Go говорит сам за себя

Page 6: Как приручить дракона: введение в LLVM

6

Мой уютный компилятор™

● Парсинг исходников

● Представление программы?

● Оптимизация всего и вся

● Целевая архитектура, наборы инструкций

● Аллокация и распределение регистров

● Интероперабельность — системные вызовы, FFI, работа с библиотеками

● Генерация исполняемого файла, линковка

● Отладочная информация

Page 7: Как приручить дракона: введение в LLVM

7

Структура команды x86 (OMG)

http://penberg.blogspot.ru/2010/04/short-introduction-to-x86-instruction.html

Page 8: Как приручить дракона: введение в LLVM

8

Как угодить всем?

● Языки разные. Очень.

● Разное отношение к данным

● Разные модели памяти

● Разные целевые архитектуры

● Разные наборы инструкций

Page 9: Как приручить дракона: введение в LLVM

9

Решение LLVM

● Запись программы в виде, не зависящем от всего вышеперечисленного

● Промежуточное представление — IR код

● Обобщенная система команд

Page 10: Как приручить дракона: введение в LLVM

10

Решение LLVM

● Запись программы в виде, не зависящем от всего вышеперечисленного

● Промежуточное представление — IR код

● Обобщенная система команд

Внезапно: LLVM — это не VM o_O

Page 11: Как приручить дракона: введение в LLVM

11

Разбор исходного текста

● LLVM считает, что AST уже есть

● Чтобы его получить, нужно провести лексический и синтаксический анализ

● К счастью, это не надо делать вручную

● На помощь придут

– Flex/Bison

– ANTLR

– И другие

Page 12: Как приручить дракона: введение в LLVM

12

Так видит программу человек

int gcd(int a, int b) {

while (b != 0) { if (a > b) a = a − b; else b = b − a; }

return a;}

Page 13: Как приручить дракона: введение в LLVM

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

Page 14: Как приручить дракона: введение в LLVM

14

Лексический анализ

● Первичный разбор текста программы

● Удаление лишнего шума

● Преобразование потока входных символов в последовательность токенов

Page 15: Как приручить дракона: введение в LLVM

15

Лексический анализатор flex

● Позволяет записать правила разбора в текстовом, человеко-читаемом виде

● Регулярные выражения — это сила

● На выходе — токены

Page 16: Как приручить дракона: введение в LLVM

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; }

Page 17: Как приручить дракона: введение в LLVM

17

Синтаксический анализатор bison

● На вход получает поток токенов от лексера и файл описания грамматики

● На основании правил грамматики производит разбор

● На выходе дает древовидное представление программы с которым может работать компилятор

Page 18: Как приручить дракона: введение в LLVM

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

Page 19: Как приручить дракона: введение в LLVM

19

IR код

● Intermediate Representation

● Запись графа управления в привычном текстовом виде…

Page 20: Как приручить дракона: введение в LLVM

20

IR код

● Intermediate Representation

● Запись графа управления в привычном текстовом виде…

но с плюшками:

– Asm-подобный синтаксис, но с параметризованными функциями

– Строгая типизация

– Структуры данных, указатели

– Const, Volatile модификаторы

– Нотация SSA

Page 21: Как приручить дракона: введение в LLVM

21

Нотация SSA

● Static Single Assignment

● Переменных — нет о_О

● Все имена встречаются только единожды

● Функциональный стиль описания данных

● Императивный стиль описания операций

Page 22: Как приручить дракона: введение в LLVM

22

Что хорошего в SSA?

X ← 1

X ← 2

Y ← X

Page 23: Как приручить дракона: введение в LLVM

23

Что хорошего в SSA?

X ← 1

X ← 2

Y ← X

X1←1

X2←2

Y1←X2

Page 24: Как приручить дракона: введение в LLVM

24

Что хорошего в SSA?

X ← 1

X ← 2

Y ← X

X1←1 (RIP)

X2←2

Y1←X2

Page 25: Как приручить дракона: введение в LLVM

25

Что хорошего в SSA?

X ← 1

X ← 2

Y ← X

X1←1 (RIP)

X2←2 (RIP)

Y1←2

Page 26: Как приручить дракона: введение в LLVM

26

Граф потока управления*

● Control flow graph (CFG)

● Узлы — базовые блоки

● Ребра — инструкции переходов

● Базовый блок — линейный участок кода

(a)

(c)

(b)

(d)

* https://en.wikipedia.org/wiki/Control_flow_graph

Page 27: Как приручить дракона: введение в LLVM

27

Циклы и ветвления в SSA

Page 28: Как приручить дракона: введение в LLVM

28

Циклы и ветвления в SSA

Page 29: Как приручить дракона: введение в LLVM

29

Циклы и ветвления в SSA

Page 30: Как приручить дракона: введение в LLVM

30

Подсчет суммы массива

int sum_array(int* input, int length) { int sum = 0; for (int i = 0; i < length; ++i) sum += input[i]; return sum;}

Page 31: Как приручить дракона: введение в LLVM

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 }

Page 32: Как приручить дракона: введение в LLVM

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

Page 33: Как приручить дракона: введение в LLVM

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

Page 34: Как приручить дракона: введение в LLVM

34

Оптимизации

● Наблюдаемое поведение

● Точки следования

● Эквивалентные преобразования

Page 35: Как приручить дракона: введение в LLVM

35

Основные идеи оптимизаций

● Не делать то, что никому не нужно

● Не делать дважды то, что можно сделать один раз (а лучше не делать вообще)

● Если можно получить тот же результат, но меньшими усилиями — это нужно сделать

● Сокращение издержек на всех уровнях

Page 36: Как приручить дракона: введение в LLVM

36

Виды оптимизаций

● Peephole оптимизации — буквально «через замочную скважину». Локальные оптимизации в пределах базового блока

● Внутрипроцедурные оптимизации

● Межпроцедурные оптимизации

● Оптимизации во время линковки

Page 37: Как приручить дракона: введение в LLVM

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;}

Page 38: Как приручить дракона: введение в LLVM

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;}

Page 39: Как приручить дракона: введение в LLVM

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;}

Page 40: Как приручить дракона: введение в LLVM

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;}

Page 41: Как приручить дракона: введение в LLVM

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;}

Page 42: Как приручить дракона: введение в LLVM

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;}

Page 43: Как приручить дракона: введение в LLVM

43

Что можно почитать

● blog.llvm.org

● llvm.org/docs

● halt.habrahabr.ru/topics/

● llst.org

Page 44: Как приручить дракона: введение в LLVM

Спасибо за внимание!