37
LLVM, C++ vs UB Дмитрий Каш цын, HDSoft и и

Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

Embed Size (px)

Citation preview

Page 1: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

LLVM, C++ vs UB

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

Page 2: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

2

Но зачем?!*

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

Page 3: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

3

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

● Развитие computer science не стоит на месте● Новые идеи — новые языки● Новые языки — новые компиляторы● Успех Rust и Go говорит сам за себя

Page 4: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

4

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

● Парсинг исходников● Представление программы?● Оптимизация всего и вся● Целевая архитектура, наборы инструкций● Аллокация и распределение регистров● Интероперабельность — системные вызовы,

FFI, работа с библиотеками● Генерация исполняемого файла, линковка● Отладочная информация

Page 5: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

5

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

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

Page 6: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

6

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

● Языки разные. Очень.● Разное отношение к данным● Разные модели памяти● Разные целевые архитектуры● Разные наборы инструкций

Page 7: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

7

Решение LLVM

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

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

Page 8: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

8

Решение LLVM

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

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

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

Page 9: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

9

IR код

● Intermediate Representation

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

Page 10: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

10

IR код

● Intermediate Representation

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

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

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

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

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

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

– Нотация SSA

Page 11: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

11

Нотация SSA

● Static Single Assignment● Переменных — нет о_О● Все имена встречаются только единожды● Функциональный стиль описания данных● Императивный стиль описания операций

Page 12: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

12

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

X ← 1X ← 2Y ← X

Page 13: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

13

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

X ← 1X ← 2Y ← X

X1←1

X2←2

Y1←X2

Page 14: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

14

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

X ← 1X ← 2Y ← X

X1←1 (RIP)

X2←2

Y1←X2

Page 15: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

15

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

X ← 1X ← 2Y ← X

X1←1 (RIP)

X2←2 (RIP)

Y1←2

Page 16: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

16

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

● Control flow graph (CFG)● Узлы — базовые блоки● Ребра — инструкции

переходов

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

(a)

(c)

(b)

(d)

Page 17: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

17

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

Page 18: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

18

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

Page 19: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

19

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

Page 20: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

20

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

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

Page 21: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

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 }

Page 22: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

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

Page 23: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

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

Page 24: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

24

Неопределенное поведение (UB)

● Отличается от неуточненного поведения● Спецификация языка не определяет

поведение во всех возможных случаях● Программа, содержащая UB — некорректна● Компилятор всегда считает, что в коде UB

нет (при кодогенерации, оптимизациях, …)

Page 25: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

25

Откуда берется UB?

● Простота спецификации● Намеренное опускание «лишних» проверок

в угоду производительности● Упрощение логики и структуры компилятора

Page 26: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

26

Примеры UB в C++

● Обращение за границы выделенной области памяти, например: выход за границы массива, использование переменной до инициализации и после уничтожения

● Обращение к уже освобожденной памяти● Сдвиг, переполнение знаковых значений● Неоднократное изменение переменной в пределах точки

следования (печально известный пример i++ + ++i)● Нарушение правил strict aliasing● Передача в placement new неправильно выравненного указателя● Использование #pragma в GCC до версии 1.17● …и еще 100500 ситуаций

Page 27: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

27

Последствия UB

Если коротко, то совершенно произвольные:● Падение программы● Выдача произвольных значений ● Выполнение обеих ветвей условия разом● Нашествие инопланетян ● Зомби апокалипсис● Выход Half Life 3…

Page 28: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

28

Трюк со стандартом IEEE754

float invert(float f) {

uint32_t* raw = (uint32_t*) &f;

*raw ^= (1 << 31); // flip sign return * (float*) raw;

}

Page 29: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

29

Миниатюра в двух частях

В ролях:● Dead Code Eliminator (DCE)● Redundant Null Check Eliminator (RNCE)● Кусочек быдлокода

Page 30: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

30

Собственно код

bool test(int* ptr) {

const int value = *ptr;

if (ptr)

return use_ptr(ptr);

else

return false;

}

Page 31: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

31

Часть 1. Первым пришел DCE

bool test(int* ptr) {

const int value = *ptr; // ага!

if (ptr)

return use_ptr(ptr);

else

return false;

}

Page 32: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

32

RNCE пожал плечами

bool test(int* ptr) {

if (ptr) // логично, черт побери…

return use_ptr(ptr);

else

return false;

}

Page 33: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

33

Часть 2. Первым пришел RNCE

bool test(int* ptr) {

const int value = *ptr;

if (ptr true) // так-то!

return use_ptr(ptr);

else

return false;

}

Page 34: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

34

DCE машет шашкой

bool test(int* ptr) {

const int value = *ptr;

if (true)

return use_ptr(ptr);

else

return false;

}

Page 35: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

35

Занавес!

bool test(int* ptr) {

return use_ptr(ptr);

}

Page 36: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

36

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

● blog.llvm.org● llvm.org/docs● halt.habrahabr.ru/topics/● llst.org

Page 37: Дракон в мешке: от LLVM к C++ и проблемам неопределенного поведения

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