Компиляторы
LLVM: инфраструктура компиляторов
В 2000 году каждый компилятор писал свой backend с нуля: оптимизации, register allocator, code generator для каждой архитектуры. Сегодня Rust, Swift, Kotlin Native, Julia и ещё десяток языков используют один и тот же backend - LLVM. Написать новый язык теперь означает написать frontend, генерирующий LLVM IR - и получить нативный код для x86, ARM, RISC-V, WebAssembly без написания ни строчки кода под каждую платформу.
- **rustc** компилирует весь Rust через LLVM backend: `cargo build --release` вызывает LLVM оптимизации (-O2 эквивалент) и генерирует нативный код для target архитектуры
- **Apple Silicon toolchain**: swiftc, Metal shader compiler, Xcode instruments - все используют LLVM; переход на ARM в 2020 занял меньше года именно потому что LLVM уже поддерживал AArch64
- **Google BOLT**: post-link binary optimizer на основе LLVM - применяется к production бинарникам Chrome и даёт 5-10% speedup без изменения исходного кода
LLVM IR
LLVM IR (Intermediate Representation) - типизированный RISC-подобный язык в SSA-форме. Это связующее звено: frontend (Clang, rustc, swiftc) генерирует IR, backend (x86, ARM, RISC-V, WebAssembly) компилирует IR в машинный код. IR существует в трёх форматах: human-readable .ll текст, бинарный .bc bitcode, in-memory объекты.
LLVM используют: Clang (C/C++/ObjC), rustc (Rust), swiftc (Swift), kotlinc-native (Kotlin Native), Julia JIT, Emscripten (Wasm), XLA (TensorFlow/JAX ML compiler). Общий IR позволяет всем этим языкам получать одни и те же оптимизации бесплатно. LLVM 18 (2024) поддерживает 20+ target архитектур.
Для чего нужна SSA (Static Single Assignment) форма в LLVM IR?
LLVM Pass Infrastructure
LLVM pass - это трансформация или анализ IR. New Pass Manager (NPM, LLVM 13+) организует passes в pipeline: FunctionPass работает над одной функцией, ModulePass - над всем модулем, LoopPass - над циклами. Passes комбинируются в PassManager который применяет их в правильном порядке с кешированием результатов анализа.
LLVM pass API позволяет писать собственные оптимизации как плагины. Clang-Tidy и многие статические анализаторы реализованы как LLVM passes. Intel Embree (ray tracing), Apple Metal compiler и NVIDIA PTX backend - всё это custom LLVM passes. Написать свой pass: ~200 строк C++ + CMake для простого анализа.
Что делает pass mem2reg в LLVM?
LLVM Module System и LTO
LLVM Module - единица компиляции: содержит функции, глобальные переменные, метаданные, target triple. Link Time Optimization (LTO) объединяет модули перед оптимизацией: функции из разных .cpp файлов инлайнятся друг в друга. ThinLTO - распределённый вариант: модули оптимизируются параллельно с summary-based cross-module анализом.
Google Chrome с ThinLTO (2020): бинарник на 10-15% меньше, запуск быстрее на 2-5%. Firefox с LTO: на ARM Android сохранил 10MB от APK. Rust cargo release profile по умолчанию использует ThinLTO через LLVM. LTO особенно выгоден для функций-однострочников в разных .cpp файлах, которые без LTO остаются function calls.
В чём преимущество ThinLTO перед полным LTO?
LLVM Toolchain
LLVM - это не только компилятор, но и полный toolchain: Clang (C/C++), clang-format/clang-tidy (linting), lld (linker, в 5x быстрее GNU ld), lldb (debugger), AddressSanitizer, MemorySanitizer, UBSan, libfuzzer, clang-query, clangd (Language Server).
LLVM 16+ включает Polly (loop optimization для HPC), Flang (Fortran frontend), BOLT (post-link binary optimizer). BOLT использует профиль PGO для реорганизации кода: Google использует BOLT для production бинарников и достигает 5-15% speedup без изменения исходного кода. Apple использует LLVM как основу для всей своей компиляторной инфраструктуры (Xcode).
LLVM - это только Clang (C/C++ компилятор)
LLVM - это компиляторная инфраструктура: IR, pass framework, backend для 20+ архитектур, linker (lld), debugger (lldb), sanitizers, fuzzer и Language Server (clangd)
Clang - лишь один из frontends LLVM. Rust, Swift, Julia, Kotlin Native, Emscripten, XLA и десятки других проектов используют LLVM IR и backend не трогая Clang
Что делает AddressSanitizer (ASan) и каков его overhead?
Итоги
- LLVM IR - типизированный SSA язык, связывающий frontends (Clang/rustc/swiftc) с backends (x86/ARM/Wasm); 20+ target архитектур из одного IR
- Pass infrastructure: ~150 passes в -O2 pipeline; mem2reg строит SSA, instcombine упрощает алгебру, inline встраивает функции
- Полный toolchain: lld (5x быстрее GNU ld), ASan/TSan/UBSan sanitizers, clangd LSP, libFuzzer, BOLT post-link optimizer
Связанные темы
LLVM - основа для многих компиляторных проектов:
- LLVM Passes — Детальное рассмотрение написания собственных function/module passes
- MLIR — MLIR расширяет идеи LLVM IR для ML компиляторов с dialects и progressive lowering
- WebAssembly — Emscripten и wasm32 backend LLVM компилируют C/C++ в .wasm через LLVM IR
Вопросы для размышления
- LLVM IR - это хорошая абстракция для большинства языков, но Julia и Haskell GHC имеют собственные IR (Julia IR, STG). Почему они не используют LLVM IR как единственное представление?
- ThinLTO ускоряет LTO за счёт параллелизации. Какие категории межпроцедурных оптимизаций ThinLTO не может сделать по сравнению с полным LTO?
- BOLT реорганизует бинарный код на основе профиля. Почему компилятор не может сделать то же самое на этапе компиляции без отдельного post-link шага?