SlideShare a Scribd company logo
 Построение компилятора на базе LLVM — Павел Сычев
Построение компилятора на
базе LLVM
Сычев Павел (luckygeck@yandex-team.ru)
Чем будем заниматься
Построение компилятора на базе LLVM
Чем будем заниматься
〉Расскажу вкратце про компиляторы и их архитектуру
〉Обзор LLVM - что это такое и зачем он нам нужен
〉Написание компилятора простого Языка Программирования
4
Компилятор
Построение компилятора на базе LLVM
Компилятор
Вход: описание программы на исходном языке компилятора
Выход: описание той же программы, но на другом языке
(зачастую в машинном коде или assembler-е)
6
Чтобы не переписывать компилятор “с нуля” под каждую целевую платформу -
используется трехэтапная компиляция программ
Трехэтапная компиляция
7
Обработка и
генерация дерева
разбора
Оптимизатор
Генератор
машинного кода
Исходный код Машинный код
Frontend Backend
Трехэтапная компиляция
8
Haskell Frontend Оптимизатор
x86 Backend
Haskell
x86
PowerPC
Backend
PowerPC
ARM Backend
ARM
C++ Frontend
C++
Rust Frontend
Rust
LLVM-based compiler
9
Построение компилятора на базе LLVM
Обработка и генерация
дерева разбора
11
1. Лексический анализ (производится Лексером)
2. Синтаксический анализ (производится Парсером)
Обработка и генерация дерева разбора
Лексер
12
Lexer:
>> (3 + 4.1) * a
[«(», «3», «+», «4.1», «)», «*», «a»]
<< (LPAR) (INT, «3») (PLUS) (FLOAT, «4.1») (RPAR) (MUL) (ID, «a»)
1. Лексический анализ:
〉 Разбиение текста программы на токены
Можно использовать генераторы лексеров: Lex, Flex, JLex
Парсер
2. Синтаксический анализ:
〉 Преобразуем последовательность токенов в дерево
разбора (AST) в соответствии с грамматикой языка.
13
Можно использовать генераторы парсеров: Yacc, Bison, JavaCC
*
a+
3 4.1
(LPAR) (INT, «3») (PLUS) (FLOAT, «4.1») (RPAR) (MUL) (ID, «a»)
LLVM
Построение компилятора на базе LLVM
LLVM
LLVM (Low Level Virtual Machine, compiler infrastructure)
〉Набор модулей и инструментов для разработки компиляторов
〉В основе LLVM лежит промежуточное представление (Intermediate
Representation, IR) кода - типизированный трёхадресный код в SSA-форме
〉Реализует VM c RISC-подобными инструкциями и бесконечным количеством
регистров
〉Есть API для написания frontend-а на С++ и OCaml
15
Возможности LLVM
〉Оптимизация промежуточного представления кода
〉Компилятор байт-кода в машинный код
〉 x86, x86-64, ARM, PowerPC, SPARC, MIPS, IA-64, Alpha
〉Интерпретация и JIT-компиляция байт-кода
〉 x86, x86_64, PowerPC, MIPS
〉Имеет множество frontend-ов: С, C++, Objective-C, Fortran, Ada, Haskell, Java,
Python, Ruby, JavaScript, GLSL
16
Типы данных в LLVM
Построение компилятора на базе LLVM
Простые типы
Целые числа произвольной разрядности
〉 i1, i32, i17, i256, …
Числа с плавающей точкой
〉 float, double, …
Пустое значение
〉 void
18
Сложные типы
Указатели (тип*)
〉 i1*, i32*, float*, ...
Массивы ([число элементов х
тип])
〉 [10 x float], [2 x i32]
Вектор (для упрощения SIMD
операций)
〉 <4 x i32>
Структуры
〉 {i1, i32, double}
Функции:
〉i32 (i32, i32)
〉float ({float, float}, i1*)
19
Операции над стандартными
типами в LLVM
Построение компилятора на базе LLVM
Операции
〉 Полный набор арифметических операций
〉 Тип операндов нужно всегда указывать явно
〉 Есть операции приведения типов (аналоги static_cast<> и reinterpret_cast<>)
21
; x = (a + b) * c - d / e
%tmp1 = add float %a, %b
%tmp2 = mul float %tmp1, %c
%tmp3 = fdiv float %d, %e
%x = sub float %tmp2, %tmp3
Операции - 2
Инструкции для передачи управления
Инструкции работы с памятью
〉 load, store, malloc, alloca
Работа с исключениями
〉 invoke, unwind
Работа с указателями
〉 getelementptr, extractvalue, insertvalue
22
Оптимизация кода в LLVM
Построение компилятора на базе LLVM
Общая схема работы алгоритма оптимизации
〉Ищем определенный шаблон в коде для преобразования
〉Проверяем, что преобразование ничего не сломает
〉Проводим преобразование
24
Простая оптимизация SimplifySubInst
〉Ищет выражения вида:
〉 X - X, X - 0, …
〉Проверяем, что преобразование ничего не сломает
〉 Если X - целое число, то данные выражения всегда можно оптимизировать
〉Проводим преобразование
〉 X - X = 0;
〉 X - 0 = X;
25
Встроенные алгоритмы оптимизации
〉Удаление неиспользуемого кода (dead code elimination)
〉Выделение одинаковых подвыражений (common subexpression elimination)
〉Распространение констант (constant propagation, condition propagation)
〉Inline-подстановка функций
〉Раскрутка и размыкание циклов, вынос инвариантов за пределы цикла
〉Оптимизация хвостовой рекурсии
26
Вспомогательные оптимизации
Преобразование может быть не только оптимизирующим,
но и использоваться для анализа и инструментации
〉 Вывод графа потока управления в формате Graphviz
27
mkdir llvm_calc
Построение компилятора на базе LLVM
Постановка задачи
$> echo '2*2' | ./llvm_calc
$< 4
$> echo '(1+1)*123/(6-3)' | ./llvm_calc
$< 82
$> echo 'blah-blah' | ./llvm_calc
$< Error: syntax error
Код лексера [Flex]
Построение компилятора на базе LLVM
Лексер [Flex]
31
%{
#include <string>
#include "parser.hpp"
#define SAVE_TOKEN yylval.string = new std::string(yytext, yyleng)
#define TOKEN(t) (yylval.token = t)
%}
%option noyywrap
32
%%
[ tn] ;
[0-9]+.[0-9]* SAVE_TOKEN; return TDOUBLE;
[0-9]+ SAVE_TOKEN; return TINT;
"(" return TOKEN(TLPAREN);
")" return TOKEN(TRPAREN);
"+" return TOKEN(TPLUS);
"-" return TOKEN(TMINUS);
"*" return TOKEN(TMUL);
"/" return TOKEN(TDIV);
. printf("Unknown token!n"); yyterminate();
%%
Код парсера [Bison]
Построение компилятора на базе LLVM
%%
program : expr { ROOT_NODE = $1; };
numeric : TINT { $$ = new TInteger(atol($1->c_str())); delete $1; }
| TDOUBLE { $$ = new TDouble(atof($1->c_str())); delete $1; }
;
expr : numeric
| expr TMUL expr { $$ = new TBinaryOperator(*$1, $2, *$3); }
| expr TDIV expr { $$ = new TBinaryOperator(*$1, $2, *$3); }
| expr TPLUS expr { $$ = new TBinaryOperator(*$1, $2, *$3); }
| expr TMINUS expr { $$ = new TBinaryOperator(*$1, $2, *$3); }
| TLPAREN expr TRPAREN { $$ = $2; }
;
%%
Код llvm-fronted [C++]
Построение компилятора на базе LLVM
LLVM AST
36
Value* TInteger::codeGen(TCodeGenContext& context) {
return ConstantInt::get(
Type::getInt64Ty(getGlobalContext()), value
);
}
Value* TDouble::codeGen(TCodeGenContext& context) {
return ConstantFP::get(
Type::getDoubleTy(getGlobalContext()), value
);
}
37
Value* TBinaryOperator::codeGen(TCodeGenContext& context) {
Instruction::BinaryOps instr;
switch (op) {
case TPLUS: instr = Instruction::Add; break;
case TMINUS: instr = Instruction::Sub; break;
case TMUL: instr = Instruction::Mul; break;
case TDIV: instr = Instruction::SDiv; break;
default: return nullptr;
}
return BinaryOperator::Create(
instr, lhs.codeGen(context), rhs.codeGen(context),
"op", context.mainBlock
);
}
Генерация промежуточного представления кода
38
void TCodeGenContext::GenerateCode() {
std::vector<Type*> argTypes;
FunctionType* ftype = FunctionType::get(
Type::getInt64Ty(getGlobalContext()), makeArrayRef(argTypes),false
);
mainFunction = Function::Create(ftype, GlobalValue::InternalLinkage,
"main", mainModule.get());
mainBlock = BasicBlock::Create(getGlobalContext(), "entry",
mainFunction, 0);
Value* result = root.codeGen(context);
ReturnInst::Create(getGlobalContext(), result, mainBlock);
}
39
$> echo '(1+1)*123/(6/2)' | ./llvm_calc
; ModuleID = 'module'
define internal i64 @main() {
entry:
%0 = add i64 1, 1
%1 = mul i64 %0, 123
%2 = sdiv i64 6, 2
%3 = sdiv i64 %1, %2
ret i64 %3
}
Main returned: 82
Оптимизация генерируемого кода
40
void TCodeGenContext::GenerateCode() {
...
ReturnInst::Create(getGlobalContext(), result, mainBlock);
FunctionPassManager passManager(mainModule.get());
// Ликвидация общих подвыражений.
passManager.add(createGVNPass());
passManager.doInitialization();
passManager.run(*mainFunction);
}
41
$> echo '(1+1)*123/(6/2)' | ./llvm_calc
; ModuleID = 'module'
define internal i64 @main() {
entry:
ret i64 82
}
Main returned: 82
Полезные ссылки по теме
Построение компилятора на базе LLVM
Полезные ссылки по теме
Lexer & Parser
〉http://ds9a.nl/lex-yacc/cvs/lex-yacc-howto.html
〉http://masters.donntu.org/2014/fknt/ianushkevych/ind/index.htm
LLVM Overview
〉http://habrahabr.ru/post/47878/
〉http://www.aosabook.org/en/llvm.html
LLVM Tutorial
〉http://llvm.org/docs/tutorial/index.html
〉http://habrahabr.ru/post/119850/
HOWTO: Write your own toy compiler
〉http://gnuu.org/2009/09/18/writing-your-own-toy-compiler/all/1/
〉https://github.com/lsegal/my_toy_compiler
43
Спасибо за внимание!
45
Павел Сычев
Разработчик
Контакты
luckygeck@yandex-team.ru

More Related Content

Построение компилятора на базе LLVM — Павел Сычев

  • 2. Построение компилятора на базе LLVM Сычев Павел (luckygeck@yandex-team.ru)
  • 3. Чем будем заниматься Построение компилятора на базе LLVM
  • 4. Чем будем заниматься 〉Расскажу вкратце про компиляторы и их архитектуру 〉Обзор LLVM - что это такое и зачем он нам нужен 〉Написание компилятора простого Языка Программирования 4
  • 6. Компилятор Вход: описание программы на исходном языке компилятора Выход: описание той же программы, но на другом языке (зачастую в машинном коде или assembler-е) 6 Чтобы не переписывать компилятор “с нуля” под каждую целевую платформу - используется трехэтапная компиляция программ
  • 7. Трехэтапная компиляция 7 Обработка и генерация дерева разбора Оптимизатор Генератор машинного кода Исходный код Машинный код Frontend Backend
  • 8. Трехэтапная компиляция 8 Haskell Frontend Оптимизатор x86 Backend Haskell x86 PowerPC Backend PowerPC ARM Backend ARM C++ Frontend C++ Rust Frontend Rust
  • 10. Построение компилятора на базе LLVM Обработка и генерация дерева разбора
  • 11. 11 1. Лексический анализ (производится Лексером) 2. Синтаксический анализ (производится Парсером) Обработка и генерация дерева разбора
  • 12. Лексер 12 Lexer: >> (3 + 4.1) * a [«(», «3», «+», «4.1», «)», «*», «a»] << (LPAR) (INT, «3») (PLUS) (FLOAT, «4.1») (RPAR) (MUL) (ID, «a») 1. Лексический анализ: 〉 Разбиение текста программы на токены Можно использовать генераторы лексеров: Lex, Flex, JLex
  • 13. Парсер 2. Синтаксический анализ: 〉 Преобразуем последовательность токенов в дерево разбора (AST) в соответствии с грамматикой языка. 13 Можно использовать генераторы парсеров: Yacc, Bison, JavaCC * a+ 3 4.1 (LPAR) (INT, «3») (PLUS) (FLOAT, «4.1») (RPAR) (MUL) (ID, «a»)
  • 15. LLVM LLVM (Low Level Virtual Machine, compiler infrastructure) 〉Набор модулей и инструментов для разработки компиляторов 〉В основе LLVM лежит промежуточное представление (Intermediate Representation, IR) кода - типизированный трёхадресный код в SSA-форме 〉Реализует VM c RISC-подобными инструкциями и бесконечным количеством регистров 〉Есть API для написания frontend-а на С++ и OCaml 15
  • 16. Возможности LLVM 〉Оптимизация промежуточного представления кода 〉Компилятор байт-кода в машинный код 〉 x86, x86-64, ARM, PowerPC, SPARC, MIPS, IA-64, Alpha 〉Интерпретация и JIT-компиляция байт-кода 〉 x86, x86_64, PowerPC, MIPS 〉Имеет множество frontend-ов: С, C++, Objective-C, Fortran, Ada, Haskell, Java, Python, Ruby, JavaScript, GLSL 16
  • 17. Типы данных в LLVM Построение компилятора на базе LLVM
  • 18. Простые типы Целые числа произвольной разрядности 〉 i1, i32, i17, i256, … Числа с плавающей точкой 〉 float, double, … Пустое значение 〉 void 18
  • 19. Сложные типы Указатели (тип*) 〉 i1*, i32*, float*, ... Массивы ([число элементов х тип]) 〉 [10 x float], [2 x i32] Вектор (для упрощения SIMD операций) 〉 <4 x i32> Структуры 〉 {i1, i32, double} Функции: 〉i32 (i32, i32) 〉float ({float, float}, i1*) 19
  • 20. Операции над стандартными типами в LLVM Построение компилятора на базе LLVM
  • 21. Операции 〉 Полный набор арифметических операций 〉 Тип операндов нужно всегда указывать явно 〉 Есть операции приведения типов (аналоги static_cast<> и reinterpret_cast<>) 21 ; x = (a + b) * c - d / e %tmp1 = add float %a, %b %tmp2 = mul float %tmp1, %c %tmp3 = fdiv float %d, %e %x = sub float %tmp2, %tmp3
  • 22. Операции - 2 Инструкции для передачи управления Инструкции работы с памятью 〉 load, store, malloc, alloca Работа с исключениями 〉 invoke, unwind Работа с указателями 〉 getelementptr, extractvalue, insertvalue 22
  • 23. Оптимизация кода в LLVM Построение компилятора на базе LLVM
  • 24. Общая схема работы алгоритма оптимизации 〉Ищем определенный шаблон в коде для преобразования 〉Проверяем, что преобразование ничего не сломает 〉Проводим преобразование 24
  • 25. Простая оптимизация SimplifySubInst 〉Ищет выражения вида: 〉 X - X, X - 0, … 〉Проверяем, что преобразование ничего не сломает 〉 Если X - целое число, то данные выражения всегда можно оптимизировать 〉Проводим преобразование 〉 X - X = 0; 〉 X - 0 = X; 25
  • 26. Встроенные алгоритмы оптимизации 〉Удаление неиспользуемого кода (dead code elimination) 〉Выделение одинаковых подвыражений (common subexpression elimination) 〉Распространение констант (constant propagation, condition propagation) 〉Inline-подстановка функций 〉Раскрутка и размыкание циклов, вынос инвариантов за пределы цикла 〉Оптимизация хвостовой рекурсии 26
  • 27. Вспомогательные оптимизации Преобразование может быть не только оптимизирующим, но и использоваться для анализа и инструментации 〉 Вывод графа потока управления в формате Graphviz 27
  • 29. Постановка задачи $> echo '2*2' | ./llvm_calc $< 4 $> echo '(1+1)*123/(6-3)' | ./llvm_calc $< 82 $> echo 'blah-blah' | ./llvm_calc $< Error: syntax error
  • 30. Код лексера [Flex] Построение компилятора на базе LLVM
  • 31. Лексер [Flex] 31 %{ #include <string> #include "parser.hpp" #define SAVE_TOKEN yylval.string = new std::string(yytext, yyleng) #define TOKEN(t) (yylval.token = t) %} %option noyywrap
  • 32. 32 %% [ tn] ; [0-9]+.[0-9]* SAVE_TOKEN; return TDOUBLE; [0-9]+ SAVE_TOKEN; return TINT; "(" return TOKEN(TLPAREN); ")" return TOKEN(TRPAREN); "+" return TOKEN(TPLUS); "-" return TOKEN(TMINUS); "*" return TOKEN(TMUL); "/" return TOKEN(TDIV); . printf("Unknown token!n"); yyterminate(); %%
  • 33. Код парсера [Bison] Построение компилятора на базе LLVM
  • 34. %% program : expr { ROOT_NODE = $1; }; numeric : TINT { $$ = new TInteger(atol($1->c_str())); delete $1; } | TDOUBLE { $$ = new TDouble(atof($1->c_str())); delete $1; } ; expr : numeric | expr TMUL expr { $$ = new TBinaryOperator(*$1, $2, *$3); } | expr TDIV expr { $$ = new TBinaryOperator(*$1, $2, *$3); } | expr TPLUS expr { $$ = new TBinaryOperator(*$1, $2, *$3); } | expr TMINUS expr { $$ = new TBinaryOperator(*$1, $2, *$3); } | TLPAREN expr TRPAREN { $$ = $2; } ; %%
  • 35. Код llvm-fronted [C++] Построение компилятора на базе LLVM
  • 36. LLVM AST 36 Value* TInteger::codeGen(TCodeGenContext& context) { return ConstantInt::get( Type::getInt64Ty(getGlobalContext()), value ); } Value* TDouble::codeGen(TCodeGenContext& context) { return ConstantFP::get( Type::getDoubleTy(getGlobalContext()), value ); }
  • 37. 37 Value* TBinaryOperator::codeGen(TCodeGenContext& context) { Instruction::BinaryOps instr; switch (op) { case TPLUS: instr = Instruction::Add; break; case TMINUS: instr = Instruction::Sub; break; case TMUL: instr = Instruction::Mul; break; case TDIV: instr = Instruction::SDiv; break; default: return nullptr; } return BinaryOperator::Create( instr, lhs.codeGen(context), rhs.codeGen(context), "op", context.mainBlock ); }
  • 38. Генерация промежуточного представления кода 38 void TCodeGenContext::GenerateCode() { std::vector<Type*> argTypes; FunctionType* ftype = FunctionType::get( Type::getInt64Ty(getGlobalContext()), makeArrayRef(argTypes),false ); mainFunction = Function::Create(ftype, GlobalValue::InternalLinkage, "main", mainModule.get()); mainBlock = BasicBlock::Create(getGlobalContext(), "entry", mainFunction, 0); Value* result = root.codeGen(context); ReturnInst::Create(getGlobalContext(), result, mainBlock); }
  • 39. 39 $> echo '(1+1)*123/(6/2)' | ./llvm_calc ; ModuleID = 'module' define internal i64 @main() { entry: %0 = add i64 1, 1 %1 = mul i64 %0, 123 %2 = sdiv i64 6, 2 %3 = sdiv i64 %1, %2 ret i64 %3 } Main returned: 82
  • 40. Оптимизация генерируемого кода 40 void TCodeGenContext::GenerateCode() { ... ReturnInst::Create(getGlobalContext(), result, mainBlock); FunctionPassManager passManager(mainModule.get()); // Ликвидация общих подвыражений. passManager.add(createGVNPass()); passManager.doInitialization(); passManager.run(*mainFunction); }
  • 41. 41 $> echo '(1+1)*123/(6/2)' | ./llvm_calc ; ModuleID = 'module' define internal i64 @main() { entry: ret i64 82 } Main returned: 82
  • 42. Полезные ссылки по теме Построение компилятора на базе LLVM
  • 43. Полезные ссылки по теме Lexer & Parser 〉http://ds9a.nl/lex-yacc/cvs/lex-yacc-howto.html 〉http://masters.donntu.org/2014/fknt/ianushkevych/ind/index.htm LLVM Overview 〉http://habrahabr.ru/post/47878/ 〉http://www.aosabook.org/en/llvm.html LLVM Tutorial 〉http://llvm.org/docs/tutorial/index.html 〉http://habrahabr.ru/post/119850/ HOWTO: Write your own toy compiler 〉http://gnuu.org/2009/09/18/writing-your-own-toy-compiler/all/1/ 〉https://github.com/lsegal/my_toy_compiler 43