Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

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

Дмитрий Кашицын (HDsoft) дает обзор LLVM.

В этом докладе мы кратко расскажем о таком звере, как LLVM, о котором много кто слышал, но немногие щупали. Что такое компилятор на самом деле? Чем LLVM отличается от других компиляторов? Как в LLVM происходит компиляция программы, как работают оптимизации? Наконец, какой путь проходит программа от разбора исходного текста до генерации исполняемого файла?

Лекция будет обзорной и не потребует от слушателей глубоких знаний теории компиляторов.

Подробности — на http://techtalks.nsu.ru

E51d363aa46f4d059d54a15e0bcd8e6f?s=128

Tech Talks @NSU

October 06, 2015
Tweet

Transcript

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

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

    Записать исполняемый файл
  3. Чуть детальнее • Разбор лексем и синтаксический анализ • Построение

    AST программы • Преобразования (магия) • Генерация объектного файла • Линковка исполняемого файла
  4. 4 Но зачем?!* * http://www.linux.org.ru/gallery/workplaces/10931314

  5. 5 Если серьезно • Развитие computer science не стоит на

    месте • Новые идеи — новые языки • Новые языки — новые компиляторы • Успех Rust и Go говорит сам за себя
  6. 6 Мой уютный компилятор™ • Парсинг исходников • Представление программы?

    • Оптимизация всего и вся • Целевая архитектура, наборы инструкций • Аллокация и распределение регистров • Интероперабельность — системные вызовы, FFI, работа с библиотеками • Генерация исполняемого файла, линковка • Отладочная информация
  7. 7 Структура команды x86 (OMG) http://penberg.blogspot.ru/2010/04/short-introduction-to-x86-instruction.html

  8. 8 Как угодить всем? • Языки разные. Очень. • Разное

    отношение к данным • Разные модели памяти • Разные целевые архитектуры • Разные наборы инструкций
  9. 9 Решение LLVM • Запись программы в виде, не зависящем

    от всего вышеперечисленного • Промежуточное представление — IR код • Обобщенная система команд
  10. 10 Решение LLVM • Запись программы в виде, не зависящем

    от всего вышеперечисленного • Промежуточное представление — IR код • Обобщенная система команд Внезапно: LLVM — это не VM o_O
  11. 11 Разбор исходного текста • LLVM считает, что AST уже

    есть • Чтобы его получить, нужно провести лексический и синтаксический анализ • К счастью, это не надо делать вручную • На помощь придут – Flex/Bison – ANTLR – И другие
  12. 12 Так видит программу человек int gcd(int a, int b)

    { while (b != 0) { if (a > b) a = a − b; else b = b − a; } return a; }
  13. 13 Так видит программу компилятор condition body else-body if-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. 14 Лексический анализ • Первичный разбор текста программы • Удаление

    лишнего шума • Преобразование потока входных символов в последовательность токенов
  15. 15 Лексический анализатор flex • Позволяет записать правила разбора в

    текстовом, человеко-читаемом виде • Регулярные выражения — это сила • На выходе — токены
  16. 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. 17 Синтаксический анализатор bison • На вход получает поток токенов

    от лексера и файл описания грамматики • На основании правил грамматики производит разбор • На выходе дает древовидное представление программы с которым может работать компилятор
  18. 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. 19 IR код • Intermediate Representation • Запись графа управления

    в привычном текстовом виде…
  20. 20 IR код • Intermediate Representation • Запись графа управления

    в привычном текстовом виде… но с плюшками: – Asm-подобный синтаксис, но с параметризованными функциями – Строгая типизация – Структуры данных, указатели – Const, Volatile модификаторы – Нотация SSA
  21. 21 Нотация SSA • Static Single Assignment • Переменных —

    нет о_О • Все имена встречаются только единожды • Функциональный стиль описания данных • Императивный стиль описания операций
  22. 22 Что хорошего в SSA? X ← 1 X ←

    2 Y ← X
  23. 23 Что хорошего в SSA? X ← 1 X ←

    2 Y ← X X1 ←1 X2 ←2 Y1 ←X2
  24. 24 Что хорошего в SSA? X ← 1 X ←

    2 Y ← X X1 ←1 (RIP) X2 ←2 Y1 ←X2
  25. 25 Что хорошего в SSA? X ← 1 X ←

    2 Y ← X X1 ←1 (RIP) X2 ←2 (RIP) Y1 ←2
  26. 26 Граф потока управления* • Control flow graph (CFG) •

    Узлы — базовые блоки • Ребра — инструкции переходов • Базовый блок — линейный участок кода (a) (c) (b) (d) * https://en.wikipedia.org/wiki/Control_flow_graph
  27. 27 Циклы и ветвления в SSA

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

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

  30. 30 Подсчет суммы массива int sum_array(int* input, int length) {

    int sum = 0; for (int i = 0; i < length; ++i) sum += input[i]; return sum; }
  31. 31 Листинг IR кода 1 ; Function Attrs: nounwind readonly

    2 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 ; новое значение sum 16 %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.ph 21 }
  32. 32 Результат clang -O1 -m32 sum_array(int const*, int): mov ecx,

    dword ptr [esp + 8] xor eax, eax test ecx, ecx jle .LBB0_3 mov edx, dword ptr [esp + 4] .LBB0_2: # %.lr.ph add eax, dword ptr [edx] add edx, 4 dec ecx jne .LBB0_2 .LBB0_3: # %._crit_edge ret
  33. 33 Результат clang -O1 (x64) sum_array(int const*, int): xor eax,

    eax test esi, esi jle .LBB0_2 .LBB0_1: # %.lr.ph add eax, dword ptr [rdi] add rdi, 4 dec esi jne .LBB0_1 .LBB0_2: # %._crit_edge ret
  34. 34 Оптимизации • Наблюдаемое поведение • Точки следования • Эквивалентные

    преобразования
  35. 35 Основные идеи оптимизаций • Не делать то, что никому

    не нужно • Не делать дважды то, что можно сделать один раз (а лучше не делать вообще) • Если можно получить тот же результат, но меньшими усилиями — это нужно сделать • Сокращение издержек на всех уровнях
  36. 36 Виды оптимизаций • Peephole оптимизации — буквально «через замочную

    скважину». Локальные оптимизации в пределах базового блока • Внутрипроцедурные оптимизации • Межпроцедурные оптимизации • Оптимизации во время линковки
  37. 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. 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. 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. 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. 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. 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. 43 Что можно почитать • blog.llvm.org • llvm.org/docs • halt.habrahabr.ru/topics/

    • llst.org
  44. Спасибо за внимание!