Дракон в мешке: от LLVM к C++ и проблемам неопределенного...
-
Upload
platonov-sergey -
Category
Software
-
view
702 -
download
1
Transcript of Дракон в мешке: от LLVM к C++ и проблемам неопределенного...
LLVM, C++ vs UB
Дмитрий Каш цын, HDSoftии
2
Но зачем?!*
* http://www.linux.org.ru/gallery/workplaces/10931314
3
Если серьезно
● Развитие computer science не стоит на месте● Новые идеи — новые языки● Новые языки — новые компиляторы● Успех Rust и Go говорит сам за себя
4
Мой уютный компилятор™
● Парсинг исходников● Представление программы?● Оптимизация всего и вся● Целевая архитектура, наборы инструкций● Аллокация и распределение регистров● Интероперабельность — системные вызовы,
FFI, работа с библиотеками● Генерация исполняемого файла, линковка● Отладочная информация
5
Структура команды x86 (OMG)
http://penberg.blogspot.ru/2010/04/short-introduction-to-x86-instruction.html
6
Как угодить всем?
● Языки разные. Очень.● Разное отношение к данным● Разные модели памяти● Разные целевые архитектуры● Разные наборы инструкций
7
Решение LLVM
● Запись программы в виде, не зависящем от всего вышеперечисленного
● Промежуточное представление — IR код● Обобщенная система команд
8
Решение LLVM
● Запись программы в виде, не зависящем от всего вышеперечисленного
● Промежуточное представление — IR код● Обобщенная система команд
Внезапно: LLVM — это не VM o_O
9
IR код
● Intermediate Representation
● Запись графа управления в привычном текстовом виде…
10
IR код
● Intermediate Representation
● Запись графа управления в привычном текстовом виде…
но с плюшками:
– Asm-подобный синтаксис, но с параметризованными функциями
– Строгая типизация
– Структуры данных, указатели
– Const, Volatile модификаторы
– Нотация SSA
11
Нотация SSA
● Static Single Assignment● Переменных — нет о_О● Все имена встречаются только единожды● Функциональный стиль описания данных● Императивный стиль описания операций
12
Что хорошего в SSA?
X ← 1X ← 2Y ← X
13
Что хорошего в SSA?
X ← 1X ← 2Y ← X
X1←1
X2←2
Y1←X2
14
Что хорошего в SSA?
X ← 1X ← 2Y ← X
X1←1 (RIP)
X2←2
Y1←X2
15
Что хорошего в SSA?
X ← 1X ← 2Y ← X
X1←1 (RIP)
X2←2 (RIP)
Y1←2
16
Граф потока управления
● Control flow graph (CFG)● Узлы — базовые блоки● Ребра — инструкции
переходов
● Базовый блок — линейный участок кода
(a)
(c)
(b)
(d)
17
Циклы и ветвления в SSA
18
Циклы и ветвления в SSA
19
Циклы и ветвления в SSA
20
Подсчет суммы массива
int sum_array(int* input, int length) { int sum = 0; for (int i = 0; i < length; ++i) sum += input[i]; return sum;}
21
Листинг 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 }
22
Результат 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
23
Результат 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
24
Неопределенное поведение (UB)
● Отличается от неуточненного поведения● Спецификация языка не определяет
поведение во всех возможных случаях● Программа, содержащая UB — некорректна● Компилятор всегда считает, что в коде UB
нет (при кодогенерации, оптимизациях, …)
25
Откуда берется UB?
● Простота спецификации● Намеренное опускание «лишних» проверок
в угоду производительности● Упрощение логики и структуры компилятора
26
Примеры UB в C++
● Обращение за границы выделенной области памяти, например: выход за границы массива, использование переменной до инициализации и после уничтожения
● Обращение к уже освобожденной памяти● Сдвиг, переполнение знаковых значений● Неоднократное изменение переменной в пределах точки
следования (печально известный пример i++ + ++i)● Нарушение правил strict aliasing● Передача в placement new неправильно выравненного указателя● Использование #pragma в GCC до версии 1.17● …и еще 100500 ситуаций
27
Последствия UB
Если коротко, то совершенно произвольные:● Падение программы● Выдача произвольных значений ● Выполнение обеих ветвей условия разом● Нашествие инопланетян ● Зомби апокалипсис● Выход Half Life 3…
28
Трюк со стандартом IEEE754
float invert(float f) {
uint32_t* raw = (uint32_t*) &f;
*raw ^= (1 << 31); // flip sign return * (float*) raw;
}
29
Миниатюра в двух частях
В ролях:● Dead Code Eliminator (DCE)● Redundant Null Check Eliminator (RNCE)● Кусочек быдлокода
30
Собственно код
bool test(int* ptr) {
const int value = *ptr;
if (ptr)
return use_ptr(ptr);
else
return false;
}
31
Часть 1. Первым пришел DCE
bool test(int* ptr) {
const int value = *ptr; // ага!
if (ptr)
return use_ptr(ptr);
else
return false;
}
32
RNCE пожал плечами
bool test(int* ptr) {
if (ptr) // логично, черт побери…
return use_ptr(ptr);
else
return false;
}
33
Часть 2. Первым пришел RNCE
bool test(int* ptr) {
const int value = *ptr;
if (ptr true) // так-то!
return use_ptr(ptr);
else
return false;
}
34
DCE машет шашкой
bool test(int* ptr) {
const int value = *ptr;
if (true)
return use_ptr(ptr);
else
return false;
}
35
Занавес!
bool test(int* ptr) {
return use_ptr(ptr);
}
36
Что можно почитать
● blog.llvm.org● llvm.org/docs● halt.habrahabr.ru/topics/● llst.org
Спасибо за внимание!