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

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

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

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

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

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

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

Tech Talks @NSU

October 06, 2015
Tweet

More Decks by Tech Talks @NSU

Other Decks in Education

Transcript

  1. Чуть детальнее • Разбор лексем и синтаксический анализ • Построение

    AST программы • Преобразования (магия) • Генерация объектного файла • Линковка исполняемого файла
  2. 5 Если серьезно • Развитие computer science не стоит на

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

    • Оптимизация всего и вся • Целевая архитектура, наборы инструкций • Аллокация и распределение регистров • Интероперабельность — системные вызовы, FFI, работа с библиотеками • Генерация исполняемого файла, линковка • Отладочная информация
  4. 8 Как угодить всем? • Языки разные. Очень. • Разное

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

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

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

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

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

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

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

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

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

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

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

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

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

    Узлы — базовые блоки • Ребра — инструкции переходов • Базовый блок — линейный участок кода (a) (c) (b) (d) * https://en.wikipedia.org/wiki/Control_flow_graph
  21. 30 Подсчет суммы массива int sum_array(int* input, int length) {

    int sum = 0; for (int i = 0; i < length; ++i) sum += input[i]; return sum; }
  22. 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 }
  23. 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
  24. 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
  25. 35 Основные идеи оптимизаций • Не делать то, что никому

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

    скважину». Локальные оптимизации в пределах базового блока • Внутрипроцедурные оптимизации • Межпроцедурные оптимизации • Оптимизации во время линковки
  27. 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; }
  28. 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; }
  29. 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; }
  30. 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; }
  31. 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; }
  32. 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; }