Компиляторы

Семантические проходы и lowering

Rustc проходит по коду пять раз прежде чем сгенерировать машинный код: парсинг, desugaring, type checking, borrow checking, codegen. Каждый проход упрощает задачу следующего. Это как разборка сложного IKEA furniture: сначала читаем инструкцию, потом раскладываем детали, потом собираем - не пытаясь делать всё одновременно.

  • **Rustc MIR** был введён в 2016 (RFC 1211) и позволил реализовать NLL (Non-Lexical Lifetimes) - более точный borrow checker, который принял миллионы ранее отвергаемых корректных программ.
  • **TypeScript** десугарит `async/await` в state machine при компиляции в ES5 - миллионы строк асинхронного кода работают на браузерах без поддержки async, потому что компилятор делает это автоматически.
  • **C++20 constexpr** позволяет вычислять lookup tables при компиляции: хеш-таблицы, таблицы тригонометрии, CRC-таблицы. LLVM сам активно использует это для встроенных таблиц архитектурных инструкций.

Desugaring

Desugaring - преобразование синтаксического сахара в более простые конструкции языка. Компилятор сначала парсит высокоуровневый синтаксис, затем раскрывает его в эквивалентные примитивные конструкции. После этого все последующие фазы работают только с ядром языка.

Kotlin desugars operator overloading: `a + b` -> `a.plus(b)`, `a[i]` -> `a.get(i)`, `a in b` -> `b.contains(a)`. Scala desugars for-comprehensions в `flatMap`/`map`/`filter`. TypeScript desugars `async/await` в state machine на Promise. Это позволяет добавлять синтаксический сахар без усложнения core языка и backend компилятора.

В чём главное преимущество desugaring для архитектуры компилятора?

Lowering

Lowering - снижение уровня абстракции: перевод высокоуровневого AST в более низкоуровневое промежуточное представление (IR). В отличие от desugaring (AST -> AST одного языка), lowering переходит к другому IR: Rustc AST -> HIR -> MIR -> LLVM IR.

MIR в Rustc был введён в 2016 году (RFC 1211) и радикально упростил borrow checker: вместо анализа сложного AST borrow checker работает с простым CFG. MIR также открыл путь к `const fn` выполнению при компиляции - MIRI (MIR interpreter) вычисляет константные выражения прямо на MIR.

Почему введение MIR в Rust упростило borrow checker?

AST Annotation

AST annotation - процесс добавления дополнительной информации к узлам AST после semantic analysis. После type checking каждый узел выражения несёт свой тип; после name resolution - DefId. Аннотированный AST служит входом для IR generation.

Clang хранит всю семантическую информацию в ASTContext - единственном объекте, живущем всё время компиляции. Это включает типы, implicit conversions, template instantiations. Размер ASTContext для крупного C++ проекта может достигать нескольких гигабайт в памяти - поэтому Unity Build (объединение .cpp файлов) существенно ускоряет компиляцию, переиспользуя один ASTContext.

Что хранится в аннотированном AST после type checking?

Constant Evaluation

Constant evaluation (const eval) - вычисление значений во время компиляции. Включает: размеры массивов, enum discriminants, `constexpr` в C++, `const fn` в Rust, `inline` и `readonly` в TypeScript. Позволяет переносить вычисления из runtime в compile time.

C++ комитет продолжает расширять `constexpr`: в C++20 разрешено использование `std::vector` и `std::string` в constexpr контексте - значения аллоцируются и вычисляются при компиляции, потом результат встраивается в константный массив. GCC и Clang вычисляют constexpr через отдельный interpreter, Rust использует MIRI. Это создаёт интересный эффект: сложные вычисления могут значительно замедлить компиляцию.

Чем `consteval` в C++20 отличается от `constexpr`?

Ключевые идеи

  • **Desugaring** раскрывает синтаксический сахар в примитивные конструкции. `for`, `?`, `async/await` - всё это сахар поверх loop/match/closure.
  • **Lowering** снижает уровень абстракции: AST -> HIR -> MIR -> LLVM IR в Rustc. Каждый уровень проще и ближе к машине.
  • **AST annotation** добавляет типы и DefIds к узлам после semantic analysis. Аннотированный AST - полная информация для codegen.
  • **Constant evaluation** вычисляет значения при компиляции через interpreter (MIRI в Rust, встроенный interpreter в GCC/Clang для constexpr).

Связанные темы

Семантические проходы соединяют frontend с backend компилятора:

  • Промежуточное представление (IR) — Lowering производит IR - входные данные для оптимизаций и codegen
  • Проверка типов — Type checking предшествует desugaring в некоторых компиляторах (Rustc: HIR level) и следует в других
  • Семантические ошибки — Большинство семантических ошибок фиксируются в ходе semantic passes до lowering

Вопросы для размышления

  • Rustc проходит по коду пять раз (AST, HIR, MIR, LLVM IR, asm). Почему не объединить все фазы в один проход - это ускорило бы компиляцию?
  • TypeScript десугарит `async/await` в state machine при таргете ES5, но не при ES2017+. Как компилятор решает, какой уровень desugaring применять?
  • Constant evaluation в C++ позволяет запускать произвольный код во время компиляции. Какие риски это создаёт - и как компиляторы их ограничивают?

Связанные уроки

  • plt-14-operational-semantics
Семантические проходы и lowering

0

1

Войти