Исключения C++ через призму
компиляторных оптимизаций
LLVM
Роман Русяев
Samsung R&D
сompilers developer
rusyaev.rm@gmail.com
План доклада
• Введение в реализацию исключений
• Как исключения поддержаны в LLVM
• Влияние исключений на оптимизации компилятора (на
примере LLVM)
2
Цель доклада
Как оптимизирующий компилятор работает с исключениями C++, и
как это может отразиться на производительности ваших
приложений:
• насколько дороги исключения, даже если они не выбрасываются?
3
Цель доклада
Как оптимизирующий компилятор работает с исключениями C++, и
как это может отразиться на производительности ваших
приложений:
• насколько дороги исключения, даже если они не выбрасываются?
• когда лучше исключения не использовать
4
Цель доклада
Как оптимизирующий компилятор работает с исключениями C++, и
как это может отразиться на производительности ваших
приложений:
• насколько дороги исключения, даже если они не выбрасываются?
• когда лучше исключения не использовать
• noexcept везде, где можно
5
noexcept везде, где можно
• const везде, где можно
6
noexcept везде, где можно
• const везде, где можно
• Практично ли это?
• синтаксический мусор
• с появлением семантики перемещения все стало сложнее
7
noexcept везде, где можно
• const везде, где можно
• Практично ли это?
• синтаксический мусор
• с появлением семантики перемещения все стало сложнее
• const – всегда, когда нужно (ссылки, указатели, функции члены
etc)
8
noexcept везде, где можно
• const везде, где можно
• Практично ли это?
• синтаксический мусор
• с появлением семантики перемещения все стало сложнее
• const – всегда, когда нужно (ссылки, указатели, функции члены
etc)
• А что с noexcept?
9
Введение в реализацию исключений
10
Zero-Cost Exception Handling (0eh)
• придуман для IA-64 (Itanium)
• спецификация описана в Itanium C++ ABI:
https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html
11
Zero-Cost Exception Handling (0eh)
12
не выполняется дополнительного кода, если
исключение не выбрасывается
Терминология
• спецификация исключений функции:
void foo() noexcept
void foo() throw() // deprecated
void foo() throw(type1, type2, ...) // deprecated
• cleanup
• обработчик исключения
• stack unwinding
13
stack unwinding
• stack unwinding: осуществляет вызов деструкторов локальных
объектов каждого стекового фрейма, пока не будет найден фрейм
с обработчиком исключения, соответствующим объекту
брошенного исключения.
14
stack unwinding
• stack unwinding: осуществляет вызов деструкторов локальных
объектов каждого стекового фрейма, пока не будет найден фрейм
с обработчиком исключения, соответствующим объекту
брошенного исключения. Состоит из 2х этапов:
• search phase: поиск обработчика исключения
• clean up phase: вызов деструкторов локальных объектов и передача на
обработчик исключения
15
stack unwinding, cleanup
• stack unwinding: осуществляет вызов деструкторов локальных
объектов каждого стекового фрейма, пока не будет найден фрейм
с обработчиком исключения, соответствующим объекту
брошенного исключения. Состоит из 2х этапов:
• search phase: поиск обработчика исключения
• clean up phase: вызов деструкторов локальных объектов и передача на
обработчик исключения
• cleanup: выполняет вызовы деструкторов локальных объектов в
процессе stack unwinding
16
stack unwinding, cleanup, обработчики исключений
• stack unwinding: осуществляет вызов деструкторов локальных
объектов каждого стекового фрейма, пока не будет найден фрейм
с обработчиком исключения, соответствующим объекту
брошенного исключения. Состоит из 2х этапов:
• search phase: поиск обработчика исключения
• clean up phase: вызов деструкторов локальных объектов и передача на
обработчик исключения
• cleanup: выполняет вызовы деструкторов локальных объектов в
процессе stack unwinding
• обработчик исключения: код, отвечающий за обработку
выброшенного исключения
17
std::terminate
• исключение не ловится
• не соблюден noexcept спецификатор функции
• исключение бросается из cleanup
• … (еще ~10 случаев)
18
Что происходит при бросании исключения?
19
foo
phase 1: search
…
user code
C++ runtime code
…
Что происходит при бросании исключения?
20
foo
phase 1: search
phase 2: clean up
…
user code
…
std::terminate
no handler/error
C++ runtime code
Что происходит при бросании исключения?
21
foo
phase 1: search
phase 2: clean up
… handler/clean up
user code
…
std::terminate
no handler/error
no handler/error
C++ runtime code
Что происходит при бросании исключения?
22
foo
phase 1: search
phase 2: clean up
… handler/clean up
…
user code
…
std::terminate
no handler/error
no handler/error
C++ runtime code
Что происходит при бросании исключения?
23
foo
phase 1: search
phase 2: clean up
… handler/clean up
…
user code
…
std::terminate
no handler/error
no handler/error
C++ runtime code
Что происходит при бросании исключения?
24
foo
phase 1: search
phase 2: clean up
… handler/clean up
…
user code
…
std::terminate
no handler/error
no handler/error
std::terminate
функция с noexcept
вызывает не noexcept
C++ runtime code
Поддержка исключений в LLVM
25
Введение в LLVM IR
• IR (Intermediate Representation) - структура данных или язык,
используемые внутри компилятора для отображения исходного
языка
• Будем использовать урезанный вариант LLVM IR
26
Пример инструкций на LLVM IR псевдокоде
%val ; обозначение локальной переменной или метки
alloca type ; выделить память для объекта типа type
call func_name ; вызов функции с именем func_name
ret ; инструкция возврата из функции
27
Граф потока управления (Control Flow
Graph – CFG)
BB1
BB2
BB3 BB4
BB5
…
…
28
Базовый блок (Basic Block)
instr1
instr2
…
instrN
terminator BB2
… …
BB2
…
29
Поддержка исключений в LLVM: invoke
• вызов функции с неявным переходом на участок кода, если
бросили исключение
invoke foo()
30
Поддержка исключений в LLVM: invoke
• вызов функции с неявным переходом на участок кода, если
бросили исключение
invoke foo() to label %1
31
Поддержка исключений в LLVM: invoke
• вызов функции с неявным переходом на участок кода, если
бросили исключение
invoke foo() to label %1 unwind label %2
32
Поддержка исключений в LLVM: invoke, landing pad
• вызов функции с неявным переходом на участок кода, если
бросили исключение
invoke foo() to label %1 unwind label %2
• участок кода, ответственный за обработку исключения
landingpad
33
Поддержка исключений в LLVM: invoke, landing pad, resume
• вызов функции с неявным переходом на участок кода, если
бросили исключение
invoke foo() to label %1 unwind label %2
• участок кода, ответственный за обработку исключения
landingpad
• инструкция, продолжающая раскрутку стека
resume
34
Влияние исключений на компиляторные
оптимизации
35
Накладные расходы (с точки зрения
оптимизатора)
• увеличение размера кода функции за счет создания
дополнительного кода для cleanup
• усложнение потока управления, появляющегося в результате
наличия инструкции invoke
36
Накладные расходы
Как побороть?
37
PruneEH
• преобразует инструкции invoke в call
• ставит признак nounwind для функций, которые не бросают
исключения
38
PruneEH
void extF() noexcept;
struct S {
~S() { extF(); }
};
void bar() {
extF();
}
void foo() {
S s;
bar();
}
39
void bar() {
call extF()
ret
}
PruneEH
void extF() noexcept;
struct S {
~S() { extF(); }
};
void bar() {
extF();
}
void foo() {
S s;
bar();
}
40
void bar() {
call extF()
ret
}
void foo() {
%1 = alloca S
invoke bar() to label %4 unwind label %5
…
}
PruneEH
void extF() noexcept;
struct S {
~S() { extF(); }
};
void bar() {
extF();
}
void foo() {
S s;
bar();
}
41
void bar() {
call extF()
ret
}
void foo() {
%1 = alloca S
invoke bar() to label %4 unwind label %5
; <label>:4:
call ~S(%1)
ret
; <label>:5:
landingpad ; cleanup
call ~S(%1)
resume
}
PruneEH
void foo() {
%1 = alloca S
call bar()
call ~S(%1)
ret
}
42
void foo() {
%1 = alloca S
invoke bar() to label %4 unwind label %5
; <label>:4:
call ~S(%1)
ret
; <label>:5:
landingpad ; cleanup
call ~S(%1)
resume
}
Остальные оптимизации
•Simplify the CFG
•Global Variable Optimizer
•Instruction combining
43
Накладные расходы
А где побороть не удается?
44
Inline
void foo() {
// foo actions
bar();
}
void bar() {
// bar actions
}
void foo() {
// foo actions
// bar actions
}
45
Inline: эвристики
• llvm/Analysis/InlineCost.h
• llvm/lib/Analysis/InlineCost.cpp
CallAnalyzer::analyzeBlock(…) {
…
addCost(…); // for each instruction add cost
…
}
46
Inline: эвристики
void extF();
struct MyStruct {
~MyStruct() { extF(); }
};
void bar() {
extF();
}
void bar() {
call extF()
ret
}
47
Inline: эвристики
void extF();
struct MyStruct {
~MyStruct() { extF(); }
};
void bar() {
extF();
}
void foo() {
MyStruct obj;
bar();
}
void bar() {
call extF()
ret
}
void foo() {
%0 = alloca MyStruct
invoke bar() to label %4 unwind label %5
…
}
48
Inline: эвристики
void extF();
struct MyStruct {
~MyStruct() { extF(); }
};
void bar() {
extF();
}
void foo() {
MyStruct obj;
bar();
}
void bar() {
call extF()
ret
}
void foo() {
%0 = alloca MyStruct
invoke bar() to label %4 unwind label %5
; <label>:4:
call ~MyStruct(%0)
ret
; <label>:5:
landingpad ; cleanup
call ~MyStruct(%0)
resume
}
49
Inline: эвристики
void foo() {
MyStruct obj1;
…
bar();
…
if (…) {
MyStruct obj2;
bar();
…
else {
MyStruct obj3;
bar();
…
bar();
} 50
Inline: invoke
• все инструкции call без noexcept  invoke
• поток управления от новых инструкций invoke  landing pad проинлайненной
инструкции invoke
51
invoke f2()
f1()
lpad …
before after
Inline: invoke
• все инструкции call без noexcept  invoke
• поток управления от новых инструкций invoke  landing pad проинлайненной
инструкции invoke
52
invoke f2()
f1()
call f3()
f2()
lpad …
before after
Inline: invoke
• все инструкции call без noexcept  invoke
• поток управления от новых инструкций invoke  landing pad проинлайненной
инструкции invoke
53
invoke f2()
f1()
call f3()
f2()
lpad …
invoke f3()
f1()
lpad …
before after
Inline: invoke
• все инструкции call без noexcept  invoke
• поток управления от новых инструкций invoke  landing pad проинлайненной
инструкции invoke
void foo() {
%0 = alloca MyStruct
invoke extF() to label %4 unwind label %6
; <label>:4:
call ~MyStruct(%0)
ret
; <label>:6:
landingpad
call ~MyStruct(%0)
resume
}
54
void extF();
struct MyStruct {
~MyStruct() { extF(); }
};
void bar() {
extF();
}
void foo() {
MyStruct obj;
bar();
}
Inline: resume
void extF();
struct MyStruct {
~MyStruct() { extF(); }
};
void bar() {
extF();
}
void foo() {
MyStruct obj;
bar();
}
void bar() {
call extF()
ret
}
void foo() {
%0 = alloca MyStruct
invoke bar() to label %4 unwind label %5
; <label>:4:
call ~MyStruct(%0)
ret
; <label>:5:
landingpad ; cleanup
call ~MyStruct(%0)
resume
}
55
Inline: resume
void extF();
struct MyStruct {
~MyStruct() { extF(); }
};
void bar() {
MyStruct obj;
extF();
}
void foo() {
MyStruct obj;
bar();
} 56
Inline: resume
void extF();
struct MyStruct {
~MyStruct() { extF(); }
};
void bar() {
MyStruct obj;
extF();
}
void foo() {
MyStruct obj;
bar();
}
void bar() {
call extF()
ret
}
57
void bar() {
%0 = alloca MyStruct
invoke extF() to label %4 unwind label %5
; <label>:4:
call ~MyStruct(%0)
ret
; <label>:5:
landingpad ; cleanup
call ~MyStruct(%0)
resume
}
• Инструкции resume подставляемой функции  на landing pad
проинлайненной инструкции invoke
58
Inline: resume
invoke f2()
f1() f2()
lpad1 …
before after
invoke f3()
lpad2 …
• Инструкции resume подставляемой функции  на landing pad
проинлайненной инструкции invoke
59
Inline: resume
invoke f2()
f1() f2()
lpad1 …
invoke f3()
f1()
lpad2 …
before after
invoke f3()
lpad2 …
lpad1
Inline: resume void foo() {
%1 = alloca MyStruct
%2 = alloca MyStruct
invoke extF() to label %7 unwind label %5
; <label>:5:
landingpad
call ~MyStruct(%1)
call ~MyStruct(%2)
resume
; <label>:7:
call ~MyStruct(%1)
call ~MyStruct(%2)
}
60
void bar() {
%0 = alloca MyStruct
invoke extF() to label %4 unwind label %5
; <label>:4:
call ~MyStruct(%0)
ret
; <label>:5:
landingpad ; cleanup
call ~MyStruct(%0)
resume
}
Tail Call
void foo() {
goto bar_label;
}
void bar() {
bar_label:
// bar actions
}
void foo() {
bar();
}
void bar() {
// bar actions
}
61
Tail Call
Не применима к инструкциям invoke
62
Loop Fusion
for (int i = 0; i < n; i++)
a[i] = ...;
for (int j = 0; j < n; j++)
b[j] = ...;
for (int i = 0; i < n; i++) {
a[i] = ...;
b[i] = ...;
}
63
Loop Fusion
• Если в цикле есть call, который может бросать
исключение, то цикл не рассматривается как кандидат
для оптимизации
64
LICM (Loop Invariant Code Motion)
int y = foo();
int x;
for (int i = 0; i < n; i++) {
x = y;
a[i] = x + ...;
}
int y = foo();
int x = y;
for (int i = 0; i < n; i++) {
a[i] = x + ...;
}
65
LICM (Loop Invariant Code Motion)
• не работает для инструкций invoke
• не работает для инструкций call, потенциально
бросающих исключения
66
ADCE (Aggressive Dead Code Elimination)
int global;
void f () {
int i;
i = 1;
global = 1;
global = 2;
}
67
int global;
void f () {
global = 2;
}
ADCE (Aggressive Dead Code Elimination)
bool AggressiveDeadCodeElimination::isAlwaysLive(Instruction &Inst) {
if (Inst.isEHPad() || …) { // landing pad
…
return true;
}
if (Inst.isTerminator() == false) // invoke is terminator instruction
return false;
if (isa<BranchInst>(Inst) || isa<SwitchInst>(Inst)) // invoke is not included here
return false;
return true;
}
68
Sinking
• Переносит инструкции в базовые блоки преемники, чтобы убрать
лишнее исполнение инструкций
69
def -> val
use val
before
Sinking
• Переносит инструкции в базовые блоки преемники, чтобы убрать
лишнее исполнение инструкций
70
def -> val
use val
before after
def -> val
use val
Sinking
71
bool isSafeToMove(Instruction *Inst, …) {
if (… || Inst->mayThrow())
return false;
…
}
Merged Load/Store Motion
• Объединяет инструкции записи в память по одному адресу, для
уменьшения статического размера кода
72
store addr1
before
store addr1
Merged Load/Store Motion
• Объединяет инструкции записи в память по одному адресу, для
уменьшения статического размера кода
73
store addr1
before after
store addr1
store addr1
Merged Load/Store Motion
74
/// True when instruction is a sink barrier for a store
bool MergedLoadStoreMotion::isStoreSinkBarrierInRange(const Instruction &Start,
const Instruction &End) {
for (const Instruction &Inst : make_range(Start.getIterator(), End.getIterator())) {
if (Inst.mayThrow())
return true;
}
…
}
GVNHoist
• Объединяет инструкции записи в память по одному адресу, для
уменьшения статического размера кода и сокращения критического
пути
75
load addr1
before
load addr1
GVNHoist
• Объединяет инструкции записи в память по одному адресу, для
уменьшения статического размера кода и сокращения критического
пути
76
load addr1
before after
load addr1
load addr1
GVNHoist
bool isGuaranteedToTransferExecutionToSuccessor(const Instruction *I) {
…
// Calls can throw, or contain an infinite loop, or kill the process.
If (auto CS = ImmutableCallSite(I)) {
// Call sites that throw have implicit non-local control flow.
If (!CS.doesNotThrow())
return false;
…
}
77
LICM Loop Versioning
• Дублирование циклов с проверкой на пересечение адресов и
применение LICM к копии цикла
78
runtime memcheck
LOOP LOOP COPY
LICM Loop Versioning
bool LoopVersioningLICM::instructionSafeForVersioning(Instruction *I) {
…
// Avoid loops with possiblity of throw
if (I->mayThrow())
return false;
…
}
79
Остальные оптимизации
• mayHaveSideEffects
• mayThrow
• doesNotThrow
80
Выводы
• zero-cost далеко не всегда нулевой, даже если исключение не
бросается
81
Выводы
• zero-cost далеко не всегда нулевой, даже если исключение не
бросается:
• современные компиляторы имеют специальные оптимизации для
обработки исключений
82
Выводы
• zero-cost далеко не всегда нулевой, даже если исключение не
бросается:
• современные компиляторы имеют специальные оптимизации для
обработки исключений
• если вы разрабатываете библиотеку, стоит подумать об отказе от
исключений
83
Выводы
• zero-cost далеко не всегда нулевой, даже если исключение не
бросается:
• современные компиляторы имеют специальные оптимизации для
обработки исключений
• если вы разрабатываете библиотеку, стоит подумать об отказе от
исключений
• noexcept везде, где можно
84
Выводы
• zero-cost далеко не всегда нулевой, даже если исключение не
бросается:
• современные компиляторы имеют специальные оптимизации для
обработки исключений
• если вы разрабатываете библиотеку, стоит подумать об отказе от
исключений
• noexcept везде, где можно:
• аккуратно, т.к. это часть интерфейса
85
Q & A
Роман Русяев
Samsung R&D
сompilers developer
rusyaev.rm@gmail.com
86
APPENDIX
87
setjmp/longjmp (sjlj)
• setjmp – запомнить, куда нужно прыгнуть
• longjmp – эмулирует throw
Очень непроизводительно:
• много дополнительных структур данных
• много дополнительного выполняемого кода вне зависимости от
факта бросания исключения
• Нарушение главного принципа C++ - “you only pay for what you
use”
88
Библиотека поддержки исключений
• LLVM:
• libcxxabi
• libunwind
• GCC:
• libsupc++
• libgcc
89
Библиотека поддержки исключений: throw
• _cxa_allocate_exception
• __cxa_throw:
• _Unwind_RaiseException
• …
90
Библиотека поддержки исключений: catch
• __cxa_begin_catch
• __cxa_end_catch
91
Что такое LLVM
• инфраструктура для разработки компиляторов
• компилятор и инструменты, основанные на LLVM IR
http://llvm.org
92
Введение в LLVM
• LLVM IR
• множество проектов, использующих инфраструктуру LLVM
• компилятор на основе LLVM – clang
• LLVM библиотеки (support library, command line library, …)
• алгоритмы над LLVM IR (трансформации, оптимизации, …)
• инструменты для работы с LLVM IR
• …
93
Введение в LLVM IR: основные концепции
• Модули
• Функции
• Глобальные переменные
• Метаданные
• …
94
Основы работы компилятора
frontend
middle-end
backend
compiler
input
output
95
Middle-end
• Работает с IR
• Выполняет:
• анализы
• трансформации
• оптимизации
• Проход компилятора (pass) – выполнение над IR анализа,
трансформации или оптимизации
96

Исключения C++ через призму компиляторных оптимизаций. Роман Русяев ➠ CoreHard Autumn 2019

  • 1.
    Исключения C++ черезпризму компиляторных оптимизаций LLVM Роман Русяев Samsung R&D сompilers developer rusyaev.rm@gmail.com
  • 2.
    План доклада • Введениев реализацию исключений • Как исключения поддержаны в LLVM • Влияние исключений на оптимизации компилятора (на примере LLVM) 2
  • 3.
    Цель доклада Как оптимизирующийкомпилятор работает с исключениями C++, и как это может отразиться на производительности ваших приложений: • насколько дороги исключения, даже если они не выбрасываются? 3
  • 4.
    Цель доклада Как оптимизирующийкомпилятор работает с исключениями C++, и как это может отразиться на производительности ваших приложений: • насколько дороги исключения, даже если они не выбрасываются? • когда лучше исключения не использовать 4
  • 5.
    Цель доклада Как оптимизирующийкомпилятор работает с исключениями C++, и как это может отразиться на производительности ваших приложений: • насколько дороги исключения, даже если они не выбрасываются? • когда лучше исключения не использовать • noexcept везде, где можно 5
  • 6.
    noexcept везде, гдеможно • const везде, где можно 6
  • 7.
    noexcept везде, гдеможно • const везде, где можно • Практично ли это? • синтаксический мусор • с появлением семантики перемещения все стало сложнее 7
  • 8.
    noexcept везде, гдеможно • const везде, где можно • Практично ли это? • синтаксический мусор • с появлением семантики перемещения все стало сложнее • const – всегда, когда нужно (ссылки, указатели, функции члены etc) 8
  • 9.
    noexcept везде, гдеможно • const везде, где можно • Практично ли это? • синтаксический мусор • с появлением семантики перемещения все стало сложнее • const – всегда, когда нужно (ссылки, указатели, функции члены etc) • А что с noexcept? 9
  • 10.
  • 11.
    Zero-Cost Exception Handling(0eh) • придуман для IA-64 (Itanium) • спецификация описана в Itanium C++ ABI: https://itanium-cxx-abi.github.io/cxx-abi/abi-eh.html 11
  • 12.
    Zero-Cost Exception Handling(0eh) 12 не выполняется дополнительного кода, если исключение не выбрасывается
  • 13.
    Терминология • спецификация исключенийфункции: void foo() noexcept void foo() throw() // deprecated void foo() throw(type1, type2, ...) // deprecated • cleanup • обработчик исключения • stack unwinding 13
  • 14.
    stack unwinding • stackunwinding: осуществляет вызов деструкторов локальных объектов каждого стекового фрейма, пока не будет найден фрейм с обработчиком исключения, соответствующим объекту брошенного исключения. 14
  • 15.
    stack unwinding • stackunwinding: осуществляет вызов деструкторов локальных объектов каждого стекового фрейма, пока не будет найден фрейм с обработчиком исключения, соответствующим объекту брошенного исключения. Состоит из 2х этапов: • search phase: поиск обработчика исключения • clean up phase: вызов деструкторов локальных объектов и передача на обработчик исключения 15
  • 16.
    stack unwinding, cleanup •stack unwinding: осуществляет вызов деструкторов локальных объектов каждого стекового фрейма, пока не будет найден фрейм с обработчиком исключения, соответствующим объекту брошенного исключения. Состоит из 2х этапов: • search phase: поиск обработчика исключения • clean up phase: вызов деструкторов локальных объектов и передача на обработчик исключения • cleanup: выполняет вызовы деструкторов локальных объектов в процессе stack unwinding 16
  • 17.
    stack unwinding, cleanup,обработчики исключений • stack unwinding: осуществляет вызов деструкторов локальных объектов каждого стекового фрейма, пока не будет найден фрейм с обработчиком исключения, соответствующим объекту брошенного исключения. Состоит из 2х этапов: • search phase: поиск обработчика исключения • clean up phase: вызов деструкторов локальных объектов и передача на обработчик исключения • cleanup: выполняет вызовы деструкторов локальных объектов в процессе stack unwinding • обработчик исключения: код, отвечающий за обработку выброшенного исключения 17
  • 18.
    std::terminate • исключение неловится • не соблюден noexcept спецификатор функции • исключение бросается из cleanup • … (еще ~10 случаев) 18
  • 19.
    Что происходит прибросании исключения? 19 foo phase 1: search … user code C++ runtime code …
  • 20.
    Что происходит прибросании исключения? 20 foo phase 1: search phase 2: clean up … user code … std::terminate no handler/error C++ runtime code
  • 21.
    Что происходит прибросании исключения? 21 foo phase 1: search phase 2: clean up … handler/clean up user code … std::terminate no handler/error no handler/error C++ runtime code
  • 22.
    Что происходит прибросании исключения? 22 foo phase 1: search phase 2: clean up … handler/clean up … user code … std::terminate no handler/error no handler/error C++ runtime code
  • 23.
    Что происходит прибросании исключения? 23 foo phase 1: search phase 2: clean up … handler/clean up … user code … std::terminate no handler/error no handler/error C++ runtime code
  • 24.
    Что происходит прибросании исключения? 24 foo phase 1: search phase 2: clean up … handler/clean up … user code … std::terminate no handler/error no handler/error std::terminate функция с noexcept вызывает не noexcept C++ runtime code
  • 25.
  • 26.
    Введение в LLVMIR • IR (Intermediate Representation) - структура данных или язык, используемые внутри компилятора для отображения исходного языка • Будем использовать урезанный вариант LLVM IR 26
  • 27.
    Пример инструкций наLLVM IR псевдокоде %val ; обозначение локальной переменной или метки alloca type ; выделить память для объекта типа type call func_name ; вызов функции с именем func_name ret ; инструкция возврата из функции 27
  • 28.
    Граф потока управления(Control Flow Graph – CFG) BB1 BB2 BB3 BB4 BB5 … … 28
  • 29.
    Базовый блок (BasicBlock) instr1 instr2 … instrN terminator BB2 … … BB2 … 29
  • 30.
    Поддержка исключений вLLVM: invoke • вызов функции с неявным переходом на участок кода, если бросили исключение invoke foo() 30
  • 31.
    Поддержка исключений вLLVM: invoke • вызов функции с неявным переходом на участок кода, если бросили исключение invoke foo() to label %1 31
  • 32.
    Поддержка исключений вLLVM: invoke • вызов функции с неявным переходом на участок кода, если бросили исключение invoke foo() to label %1 unwind label %2 32
  • 33.
    Поддержка исключений вLLVM: invoke, landing pad • вызов функции с неявным переходом на участок кода, если бросили исключение invoke foo() to label %1 unwind label %2 • участок кода, ответственный за обработку исключения landingpad 33
  • 34.
    Поддержка исключений вLLVM: invoke, landing pad, resume • вызов функции с неявным переходом на участок кода, если бросили исключение invoke foo() to label %1 unwind label %2 • участок кода, ответственный за обработку исключения landingpad • инструкция, продолжающая раскрутку стека resume 34
  • 35.
    Влияние исключений накомпиляторные оптимизации 35
  • 36.
    Накладные расходы (сточки зрения оптимизатора) • увеличение размера кода функции за счет создания дополнительного кода для cleanup • усложнение потока управления, появляющегося в результате наличия инструкции invoke 36
  • 37.
  • 38.
    PruneEH • преобразует инструкцииinvoke в call • ставит признак nounwind для функций, которые не бросают исключения 38
  • 39.
    PruneEH void extF() noexcept; structS { ~S() { extF(); } }; void bar() { extF(); } void foo() { S s; bar(); } 39 void bar() { call extF() ret }
  • 40.
    PruneEH void extF() noexcept; structS { ~S() { extF(); } }; void bar() { extF(); } void foo() { S s; bar(); } 40 void bar() { call extF() ret } void foo() { %1 = alloca S invoke bar() to label %4 unwind label %5 … }
  • 41.
    PruneEH void extF() noexcept; structS { ~S() { extF(); } }; void bar() { extF(); } void foo() { S s; bar(); } 41 void bar() { call extF() ret } void foo() { %1 = alloca S invoke bar() to label %4 unwind label %5 ; <label>:4: call ~S(%1) ret ; <label>:5: landingpad ; cleanup call ~S(%1) resume }
  • 42.
    PruneEH void foo() { %1= alloca S call bar() call ~S(%1) ret } 42 void foo() { %1 = alloca S invoke bar() to label %4 unwind label %5 ; <label>:4: call ~S(%1) ret ; <label>:5: landingpad ; cleanup call ~S(%1) resume }
  • 43.
    Остальные оптимизации •Simplify theCFG •Global Variable Optimizer •Instruction combining 43
  • 44.
    Накладные расходы А гдепобороть не удается? 44
  • 45.
    Inline void foo() { //foo actions bar(); } void bar() { // bar actions } void foo() { // foo actions // bar actions } 45
  • 46.
    Inline: эвристики • llvm/Analysis/InlineCost.h •llvm/lib/Analysis/InlineCost.cpp CallAnalyzer::analyzeBlock(…) { … addCost(…); // for each instruction add cost … } 46
  • 47.
    Inline: эвристики void extF(); structMyStruct { ~MyStruct() { extF(); } }; void bar() { extF(); } void bar() { call extF() ret } 47
  • 48.
    Inline: эвристики void extF(); structMyStruct { ~MyStruct() { extF(); } }; void bar() { extF(); } void foo() { MyStruct obj; bar(); } void bar() { call extF() ret } void foo() { %0 = alloca MyStruct invoke bar() to label %4 unwind label %5 … } 48
  • 49.
    Inline: эвристики void extF(); structMyStruct { ~MyStruct() { extF(); } }; void bar() { extF(); } void foo() { MyStruct obj; bar(); } void bar() { call extF() ret } void foo() { %0 = alloca MyStruct invoke bar() to label %4 unwind label %5 ; <label>:4: call ~MyStruct(%0) ret ; <label>:5: landingpad ; cleanup call ~MyStruct(%0) resume } 49
  • 50.
    Inline: эвристики void foo(){ MyStruct obj1; … bar(); … if (…) { MyStruct obj2; bar(); … else { MyStruct obj3; bar(); … bar(); } 50
  • 51.
    Inline: invoke • всеинструкции call без noexcept  invoke • поток управления от новых инструкций invoke  landing pad проинлайненной инструкции invoke 51 invoke f2() f1() lpad … before after
  • 52.
    Inline: invoke • всеинструкции call без noexcept  invoke • поток управления от новых инструкций invoke  landing pad проинлайненной инструкции invoke 52 invoke f2() f1() call f3() f2() lpad … before after
  • 53.
    Inline: invoke • всеинструкции call без noexcept  invoke • поток управления от новых инструкций invoke  landing pad проинлайненной инструкции invoke 53 invoke f2() f1() call f3() f2() lpad … invoke f3() f1() lpad … before after
  • 54.
    Inline: invoke • всеинструкции call без noexcept  invoke • поток управления от новых инструкций invoke  landing pad проинлайненной инструкции invoke void foo() { %0 = alloca MyStruct invoke extF() to label %4 unwind label %6 ; <label>:4: call ~MyStruct(%0) ret ; <label>:6: landingpad call ~MyStruct(%0) resume } 54 void extF(); struct MyStruct { ~MyStruct() { extF(); } }; void bar() { extF(); } void foo() { MyStruct obj; bar(); }
  • 55.
    Inline: resume void extF(); structMyStruct { ~MyStruct() { extF(); } }; void bar() { extF(); } void foo() { MyStruct obj; bar(); } void bar() { call extF() ret } void foo() { %0 = alloca MyStruct invoke bar() to label %4 unwind label %5 ; <label>:4: call ~MyStruct(%0) ret ; <label>:5: landingpad ; cleanup call ~MyStruct(%0) resume } 55
  • 56.
    Inline: resume void extF(); structMyStruct { ~MyStruct() { extF(); } }; void bar() { MyStruct obj; extF(); } void foo() { MyStruct obj; bar(); } 56
  • 57.
    Inline: resume void extF(); structMyStruct { ~MyStruct() { extF(); } }; void bar() { MyStruct obj; extF(); } void foo() { MyStruct obj; bar(); } void bar() { call extF() ret } 57 void bar() { %0 = alloca MyStruct invoke extF() to label %4 unwind label %5 ; <label>:4: call ~MyStruct(%0) ret ; <label>:5: landingpad ; cleanup call ~MyStruct(%0) resume }
  • 58.
    • Инструкции resumeподставляемой функции  на landing pad проинлайненной инструкции invoke 58 Inline: resume invoke f2() f1() f2() lpad1 … before after invoke f3() lpad2 …
  • 59.
    • Инструкции resumeподставляемой функции  на landing pad проинлайненной инструкции invoke 59 Inline: resume invoke f2() f1() f2() lpad1 … invoke f3() f1() lpad2 … before after invoke f3() lpad2 … lpad1
  • 60.
    Inline: resume voidfoo() { %1 = alloca MyStruct %2 = alloca MyStruct invoke extF() to label %7 unwind label %5 ; <label>:5: landingpad call ~MyStruct(%1) call ~MyStruct(%2) resume ; <label>:7: call ~MyStruct(%1) call ~MyStruct(%2) } 60 void bar() { %0 = alloca MyStruct invoke extF() to label %4 unwind label %5 ; <label>:4: call ~MyStruct(%0) ret ; <label>:5: landingpad ; cleanup call ~MyStruct(%0) resume }
  • 61.
    Tail Call void foo(){ goto bar_label; } void bar() { bar_label: // bar actions } void foo() { bar(); } void bar() { // bar actions } 61
  • 62.
    Tail Call Не применимак инструкциям invoke 62
  • 63.
    Loop Fusion for (inti = 0; i < n; i++) a[i] = ...; for (int j = 0; j < n; j++) b[j] = ...; for (int i = 0; i < n; i++) { a[i] = ...; b[i] = ...; } 63
  • 64.
    Loop Fusion • Еслив цикле есть call, который может бросать исключение, то цикл не рассматривается как кандидат для оптимизации 64
  • 65.
    LICM (Loop InvariantCode Motion) int y = foo(); int x; for (int i = 0; i < n; i++) { x = y; a[i] = x + ...; } int y = foo(); int x = y; for (int i = 0; i < n; i++) { a[i] = x + ...; } 65
  • 66.
    LICM (Loop InvariantCode Motion) • не работает для инструкций invoke • не работает для инструкций call, потенциально бросающих исключения 66
  • 67.
    ADCE (Aggressive DeadCode Elimination) int global; void f () { int i; i = 1; global = 1; global = 2; } 67 int global; void f () { global = 2; }
  • 68.
    ADCE (Aggressive DeadCode Elimination) bool AggressiveDeadCodeElimination::isAlwaysLive(Instruction &Inst) { if (Inst.isEHPad() || …) { // landing pad … return true; } if (Inst.isTerminator() == false) // invoke is terminator instruction return false; if (isa<BranchInst>(Inst) || isa<SwitchInst>(Inst)) // invoke is not included here return false; return true; } 68
  • 69.
    Sinking • Переносит инструкциив базовые блоки преемники, чтобы убрать лишнее исполнение инструкций 69 def -> val use val before
  • 70.
    Sinking • Переносит инструкциив базовые блоки преемники, чтобы убрать лишнее исполнение инструкций 70 def -> val use val before after def -> val use val
  • 71.
    Sinking 71 bool isSafeToMove(Instruction *Inst,…) { if (… || Inst->mayThrow()) return false; … }
  • 72.
    Merged Load/Store Motion •Объединяет инструкции записи в память по одному адресу, для уменьшения статического размера кода 72 store addr1 before store addr1
  • 73.
    Merged Load/Store Motion •Объединяет инструкции записи в память по одному адресу, для уменьшения статического размера кода 73 store addr1 before after store addr1 store addr1
  • 74.
    Merged Load/Store Motion 74 ///True when instruction is a sink barrier for a store bool MergedLoadStoreMotion::isStoreSinkBarrierInRange(const Instruction &Start, const Instruction &End) { for (const Instruction &Inst : make_range(Start.getIterator(), End.getIterator())) { if (Inst.mayThrow()) return true; } … }
  • 75.
    GVNHoist • Объединяет инструкциизаписи в память по одному адресу, для уменьшения статического размера кода и сокращения критического пути 75 load addr1 before load addr1
  • 76.
    GVNHoist • Объединяет инструкциизаписи в память по одному адресу, для уменьшения статического размера кода и сокращения критического пути 76 load addr1 before after load addr1 load addr1
  • 77.
    GVNHoist bool isGuaranteedToTransferExecutionToSuccessor(const Instruction*I) { … // Calls can throw, or contain an infinite loop, or kill the process. If (auto CS = ImmutableCallSite(I)) { // Call sites that throw have implicit non-local control flow. If (!CS.doesNotThrow()) return false; … } 77
  • 78.
    LICM Loop Versioning •Дублирование циклов с проверкой на пересечение адресов и применение LICM к копии цикла 78 runtime memcheck LOOP LOOP COPY
  • 79.
    LICM Loop Versioning boolLoopVersioningLICM::instructionSafeForVersioning(Instruction *I) { … // Avoid loops with possiblity of throw if (I->mayThrow()) return false; … } 79
  • 80.
  • 81.
    Выводы • zero-cost далеконе всегда нулевой, даже если исключение не бросается 81
  • 82.
    Выводы • zero-cost далеконе всегда нулевой, даже если исключение не бросается: • современные компиляторы имеют специальные оптимизации для обработки исключений 82
  • 83.
    Выводы • zero-cost далеконе всегда нулевой, даже если исключение не бросается: • современные компиляторы имеют специальные оптимизации для обработки исключений • если вы разрабатываете библиотеку, стоит подумать об отказе от исключений 83
  • 84.
    Выводы • zero-cost далеконе всегда нулевой, даже если исключение не бросается: • современные компиляторы имеют специальные оптимизации для обработки исключений • если вы разрабатываете библиотеку, стоит подумать об отказе от исключений • noexcept везде, где можно 84
  • 85.
    Выводы • zero-cost далеконе всегда нулевой, даже если исключение не бросается: • современные компиляторы имеют специальные оптимизации для обработки исключений • если вы разрабатываете библиотеку, стоит подумать об отказе от исключений • noexcept везде, где можно: • аккуратно, т.к. это часть интерфейса 85
  • 86.
    Q & A РоманРусяев Samsung R&D сompilers developer rusyaev.rm@gmail.com 86
  • 87.
  • 88.
    setjmp/longjmp (sjlj) • setjmp– запомнить, куда нужно прыгнуть • longjmp – эмулирует throw Очень непроизводительно: • много дополнительных структур данных • много дополнительного выполняемого кода вне зависимости от факта бросания исключения • Нарушение главного принципа C++ - “you only pay for what you use” 88
  • 89.
    Библиотека поддержки исключений •LLVM: • libcxxabi • libunwind • GCC: • libsupc++ • libgcc 89
  • 90.
    Библиотека поддержки исключений:throw • _cxa_allocate_exception • __cxa_throw: • _Unwind_RaiseException • … 90
  • 91.
    Библиотека поддержки исключений:catch • __cxa_begin_catch • __cxa_end_catch 91
  • 92.
    Что такое LLVM •инфраструктура для разработки компиляторов • компилятор и инструменты, основанные на LLVM IR http://llvm.org 92
  • 93.
    Введение в LLVM •LLVM IR • множество проектов, использующих инфраструктуру LLVM • компилятор на основе LLVM – clang • LLVM библиотеки (support library, command line library, …) • алгоритмы над LLVM IR (трансформации, оптимизации, …) • инструменты для работы с LLVM IR • … 93
  • 94.
    Введение в LLVMIR: основные концепции • Модули • Функции • Глобальные переменные • Метаданные • … 94
  • 95.
  • 96.
    Middle-end • Работает сIR • Выполняет: • анализы • трансформации • оптимизации • Проход компилятора (pass) – выполнение над IR анализа, трансформации или оптимизации 96