RFC-031:Уровни оптимизации и менеджер проходов
Справка:
Аннотация
В данном документе предлагается введение в YaoXiang системы уровней оптимизации и менеджера проходов, которые изменят компиляционную оптимизацию с модели «всё или ничего» на настраиваемые пакеты оптимизации. Уровни оптимизации (O0-O3) определяют различные комбинации стратегий оптимизации, а менеджер проходов отвечает за выполнение проходов оптимизации в порядке с учётом зависимостей. Документ также определяет стандартный интерфейс для проходов оптимизации, закладывая архитектурную основу для будущих расширений (мономорфизация, встраивание, свёртка констант и др.).
Основная цель: предоставить пользователям возможность делать осознанный выбор между скоростью компиляции, размером бинарного файла и производительностью выполнения.
Мотивация
Зачем нужны уровни оптимизации?
Текущий компилятор не имеет конфигурации оптимизации — весь код проходит через одинаковый процесс обработки. Это приводит к следующим проблемам:
- Плохой опыт отладки: при отладке оптимизация не нужна, но её нельзя отключить
- Невозможность контролировать размер бинарного файла: мономорфизация обобщённых типов раздувает бинарник, но её нельзя отключить
- Неконтролируемая скорость компиляции: нельзя выбрать между «быстрой компиляцией» и «глубокой оптимизацией» в зависимости от сценария
- Беспорядочное выполнение проходов оптимизации: в будущем несколько проходов оптимизации будут иметь зависимости между собой, что требует единого управления
Текущие проблемы
# Текущее состояние: весь код проходит одинаковую обработку
# - При отладке: оптимизация не нужна, но отключить нельзя
# - В продакшене: нужна оптимизация, но нельзя настроить глубину
# - Для обобщённых функций: генерируется несколько копий кода, но нельзя управлять
identity: (T: Type) -> (x: T) -> T = (x) => x
x = identity(42) # сгенерирует identity_Int
s = identity("hello") # сгенерирует identity_String
# Пользователь не может выбрать «не мономорфизировать» (режим стирания типов)Ценность уровней оптимизации
| Сценарий | Требование | Уровень оптимизации |
|---|---|---|
| Разработка и отладка | Быстрая компиляция, сохранение отладочной информации | O0 |
| Повседневная разработка | Базовая оптимизация, баланс скорости компиляции | O1 |
| Тестирование/CI | Стандартная оптимизация, проверка продакшен-поведения | O2 |
| Продакшен-релиз | Глубокая оптимизация, максимальная производительность | O3 |
| Скрипты/быстрое прототипирование | Автоматический выбор (в зависимости от целевой платформы) | Auto |
Предложение
Основной дизайн
1. Определение уровней оптимизации
/// Уровень оптимизации
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum OptLevel {
/// O0: без оптимизации (режим отладки)
/// - Сохраняется вся отладочная информация
/// - Никаких оптимизационных преобразований
/// - Максимальная скорость компиляции
/// - Применение: разработка, быстрая итерация
O0,
/// O1: базовая оптимизация (по умолчанию)
/// - Мономорфизация по требованию (не генерируются неиспользуемые специализации)
/// - Базовая свёртка констант
/// - Базовое удаление мёртвого кода
/// - Применение: повседневная разработка
#[default]
O1,
/// O2: стандартная оптимизация
/// - Мономорфизация по требованию
/// - Полная свёртка констант
/// - Полное удаление мёртвого кода
/// - Встраивание маленьких функций
/// - Оптимизация хвостовых вызовов
/// - Применение: тестирование, CI, продакшен
O2,
/// O3: агрессивная оптимизация
/// - Полная мономорфизация (предварительная генерация всех возможных комбинаций типов)
/// - Агрессивное встраивание
/// - Все проходы оптимизации
/// - Возможно увеличение времени компиляции и размера бинарного файла
/// - Применение: требования к максимальной производительности
O3,
/// Auto: автоматический выбор
/// - Автоматический выбор стратегии оптимизации в зависимости от целевой платформы и доступных ресурсов
/// - Применение: скрипты, быстрое прототипирование
Auto,
}2. Интерфейс прохода оптимизации
/// Интерфейс прохода оптимизации
pub trait OptimizationPass {
/// Имя прохода (для логов и объявления зависимостей)
fn name(&self) -> &str;
/// Выполнить проход
fn run(&self, module: &mut ModuleIR, config: &PassConfig) -> PassResult;
/// От каких проходов зависит выполнение данного
fn dependencies(&self) -> Vec<&str> {
vec![]
}
/// Должен ли проход выполняться при текущей конфигурации
fn should_run(&self, config: &PassConfig) -> bool {
true
}
}
/// Конфигурация прохода
#[derive(Debug, Clone)]
pub struct PassConfig {
/// Уровень оптимизации
pub opt_level: OptLevel,
/// Включена ли отладочная информация
pub debug_info: bool,
/// Целевая платформа
pub target_platform: TargetPlatform,
}
/// Результат выполнения прохода
#[derive(Debug, Default)]
pub struct PassResult {
/// Изменён ли IR
pub changed: bool,
/// Статистика
pub stats: PassStats,
}
/// Статистика прохода
#[derive(Debug, Default)]
pub struct PassStats {
/// Количество встроенных функций
pub functions_inlined: usize,
/// Количество мономорфизированных функций
pub functions_monomorphized: usize,
/// Количество удалённого мёртвого кода
pub dead_code_removed: usize,
/// Количество свёрнутых констант
pub constants_folded: usize,
}3. Менеджер проходов
/// Оптимизатор
pub struct Optimizer {
/// Список зарегистрированных проходов (отсортирован по зависимостям)
passes: Vec<Box<dyn OptimizationPass>>,
}
impl Optimizer {
/// Создать оптимизатор для заданного уровня оптимизации
pub fn for_opt_level(level: OptLevel) -> Self {
let passes = Self::create_passes_for_level(level);
Self { passes }
}
/// Создать список проходов для заданного уровня
fn create_passes_for_level(level: OptLevel) -> Vec<Box<dyn OptimizationPass>> {
match level {
OptLevel::O0 => {
vec![
// Режим отладки: минимум оптимизации, только необходимая очистка
Box::new(ConstFoldPass::minimal()),
]
}
OptLevel::O1 => {
vec![
// Базовая оптимизация
Box::new(ConstFoldPass::basic()),
Box::new(MonomorphizePass::on_demand()),
Box::new(DcePass::basic()),
]
}
OptLevel::O2 => {
vec![
// Стандартная оптимизация
Box::new(ConstFoldPass::full()),
Box::new(MonomorphizePass::on_demand()),
Box::new(DcePass::full()),
Box::new(InlinePass::small_functions()),
Box::new(TcoPass::new()),
]
}
OptLevel::O3 => {
vec![
// Агрессивная оптимизация
Box::new(ConstFoldPass::full()),
Box::new(MonomorphizePass::full()),
Box::new(InlinePass::aggressive()),
Box::new(DcePass::full()),
Box::new(TcoPass::new()),
// Дополнительные агрессивные оптимизации...
]
}
OptLevel::Auto => {
// Автоматический выбор: определяется целевой платформой
Self::create_passes_for_level(OptLevel::O1)
}
}
}
/// Выполнить все проходы оптимизации
pub fn run(&self, module: &mut ModuleIR, config: &PassConfig) -> OptimizerResult {
let mut total_stats = OptimizerStats::default();
for pass in &self.passes {
if !pass.should_run(config) {
continue;
}
let result = pass.run(module, config);
total_stats.merge(result.stats);
}
OptimizerResult {
module: module.clone(),
stats: total_stats,
}
}
}Примеры
Использование в командной строке
# Режим отладки: без оптимизации
yaoxiang build --opt-level O0
# Повседневная разработка: базовая оптимизация (по умолчанию)
yaoxiang build
# Продакшен-релиз: стандартная оптимизация
yaoxiang build --opt-level O2
# Максимальная производительность: агрессивная оптимизация
yaoxiang build --opt-level O3
# Автоматический выбор
yaoxiang build --opt-level AutoКонфигурационный файл
{
"optimization_level": "O2",
"mono": {
"enabled": true,
"strategy": "OnDemand"
},
"debug_info": false
}Использование API
use yaoxiang::frontend::{Compiler, CompileConfig, OptLevel};
// Режим отладки
let config = CompileConfig::new()
.with_opt_level(OptLevel::O0);
let mut compiler = Compiler::with_config(config);
// Продакшен-режим
let config = CompileConfig::new()
.with_opt_level(OptLevel::O2);
let mut compiler = Compiler::with_config(config);Синтаксические изменения
Синтаксических изменений нет. Уровень оптимизации — это конфигурация компилятора, не влияющая на синтаксис языка.
Детальный дизайн
Соответствие уровней оптимизации и проходов
| Проход | O0 | O1 | O2 | O3 | Описание |
|---|---|---|---|---|---|
| Свёртка констант | Мин. | Баз. | Полная | Полная | Вычисление константных выражений на этапе компиляции |
| Мономорфизация | ❌ | По требов. | По требов. | Полная | Специализация обобщённых функций |
| Удаление мёртвого кода | ❌ | Баз. | Полное | Полное | Удаление недостижиемого или неиспользуемого кода |
| Встраивание функций | ❌ | ❌ | Мелкие ф-ции | Агрессивное | Вставка тела функции в место вызова |
| Оптимизация хвостовых вызовов | ❌ | ❌ | ✅ | ✅ | Преобразование хвостовой рекурсии в цикл |
| Анализ побега | ❌ | ❌ | ❌ | ✅ | Определение распределения в стеке/куче |
| Оптимизация циклов | ❌ | ❌ | ❌ | ✅ | Развёртка циклов, вынос инвариантов |
Стратегии мономорфизации
/// Стратегия мономорфизации
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
pub enum MonoStrategy {
/// Не мономорфизировать — стирание типов, обобщённые функции имеют только одну копию кода
/// Преимущества: маленький бинарник, быстрая компиляция
/// Недостатки: накладные расходы динамической диспетчеризации в runtime
Erased,
/// Мономорфизация по требованию — генерация кода только для реально используемых комбинаций типов
/// Преимущества: нулевая стоимость абстракции, без накладных расходов в runtime
/// Недостатки: бинарник может раздуться
#[default]
OnDemand,
/// Полная мономорфизация — предварительная генерация всех возможных комбинаций типов
/// Преимущества: все вызовы определены на этапе компиляции
/// Недостатки: медленная компиляция, большой бинарник
Full,
}
/// Конфигурация мономорфизации
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct MonoConfig {
/// Включена ли мономорфизация
#[serde(default = "default_true")]
pub enabled: bool,
/// Стратегия мономорфизации
#[serde(default)]
pub strategy: MonoStrategy,
/// Включено ли DCE (удаление мёртвого кода)
#[serde(default = "default_true")]
pub dce_enabled: bool,
/// Максимальная глубина специализации (для предотвращения бесконечной рекурсии в обобщениях)
#[serde(default = "default_max_mono_depth")]
pub max_depth: usize,
}
impl Default for MonoConfig {
fn default() -> Self {
Self {
enabled: true,
strategy: MonoStrategy::OnDemand,
dce_enabled: true,
max_depth: 100,
}
}
}Интеграция в процесс компиляции
// src/frontend/pipeline.rs
impl Pipeline {
fn run_ir_generation(
&mut self,
source_name: &str,
source: &str,
ast: &Module,
type_result: &TypeCheckResult,
phase_durations: &mut Vec<(CompilationPhase, u64)>,
) -> IRResult {
let start = Instant::now();
// 1. Генерация базового IR
let mut ir = middle::generate_ir(ast, type_result)?;
// 2. Выполнение проходов оптимизации в соответствии с уровнем оптимизации
let optimizer = Optimizer::for_opt_level(self.config.optimization_level);
let pass_config = PassConfig {
opt_level: self.config.optimization_level,
debug_info: self.config.generate_debug_info,
target_platform: TargetPlatform::detect(),
};
let result = optimizer.run(&mut ir, &pass_config);
let duration = start.elapsed().as_millis() as u64;
phase_durations.push((CompilationPhase::Optimization, duration));
IRResult::success(result.module)
}
}Влияние на систему типов
Нет прямого влияния. Проходы оптимизации выполняются на уровне IR и не затрагивают систему типов.
Поведение во время выполнения
| Уровень оптимизации | Поведение во время выполнения |
|---|---|
| O0 | Без оптимизации, сохранение всей отладочной информации |
| O1 | Базовая оптимизация, сохранение основной отладочной информации |
| O2 | Стандартная оптимизация, без отладочной информации |
| O3 | Агрессивная оптимизация, без отладочной информации |
Ключевой момент: изменения в runtime не требуются. Проходы оптимизации влияют только на уровень IR и генерацию кода, runtime выполняет поиск и выполнение функций по имени/ID и не осведомлён о процессе оптимизации.
Изменения в компиляторе
| Компонент | Изменение |
|---|---|
frontend/config.rs | Добавлены перечисление OptLevel и структура MonoConfig |
frontend/pipeline.rs | Интеграция менеджера проходов |
middle/passes/optimizer/ | Добавлен новый модуль проходов оптимизации |
middle/passes/mono/ | Рефакторинг в стандартный интерфейс проходов |
| CLI | Добавлен параметр --opt-level |
Обратная совместимость
- ✅ Полная обратная совместимость
- Уровень оптимизации по умолчанию — O1, поведение соответствует текущему
- Пользователь может явно указать уровень оптимизации для переопределения поведения по умолчанию
Компромиссы
Преимущества
- Гибкость: пользователи могут выбирать стратегию оптимизации под свой сценарий
- Расширяемость: стандартный интерфейс проходов упрощает добавление новых оптимизаций
- Предсказуемость: чётко определено поведение каждого уровня оптимизации
- Удобство отладки: режим O0 сохраняет полную отладочную информацию
Недостатки
- Увеличение сложности: необходимо поддерживать несколько уровней оптимизации
- Увеличение матрицы тестирования: требуется тестировать поведение на каждом уровне оптимизации
- Документационная нагрузка: необходимо объяснять значение каждого уровня оптимизации
Альтернативные варианты
| Вариант | Причина отклонения |
|---|---|
| Только два состояния: вкл/выкл | Невозможность тонкой настройки глубины оптимизации |
Использование стиля GCC/LLVM с цифрами -O | Не соответствует конфигурационной системе YaoXiang |
| Независимое переключение каждого прохода оптимизации | Пользователю требуется знание деталей каждого прохода, использование слишком сложное |
| Отложить до v2.0 | Мономорфизация уже реализована, но не интегрирована — сначала нужно решить архитектурные проблемы |
Стратегия реализации
Разделение на этапы
- Этап 1 (текущий): Определение уровней оптимизации и интерфейса проходов
- Этап 2: Реализация прохода мономорфизации (на основе существующего модуля
mono/) - Этап 3: Реализация проходов свёртки констант и удаления мёртвого кода
- Этап 4: Реализация проходов встраивания функций и оптимизации хвостовых вызовов
- Этап 5: Реализация агрессивных проходов оптимизации (анализ побега, оптимизация циклов)
Зависимости
- Зависит от RFC-011 (система обобщённых типов) — модуль мономорфизации
- Зависит от RFC-028 (JIT-компилятор) — интерфейс проходов оптимизации
- С RFC-018 (LLVM AOT)共享优化 Pass 设计 — общий дизайн проходов оптимизации
Риски
- Регрессия производительности: проходы оптимизации могут внести ошибки, ведущие к снижению производительности
- Увеличение времени компиляции: проходы оптимизации увеличивают время компиляции
- Раздувание бинарного файла: мономорфизация может значительно увеличить размер бинарного файла
Открытые вопросы
- [ ] Должен ли уровень O3 по умолчанию включать анализ побега? (@晨煦:需要 данные бенчмарков производительности)
- [ ] Нужны ли уровни
Os(оптимизация размера) иOz(максимальная оптимизация размера)? - [ ] Должен ли уровень оптимизации влиять на степень детализации отладочной информации?
- [ ] Как обрабатывать циклические зависимости между проходами оптимизации?
Приложение A: Журнал архитектурных решений
| Решение | Решение | Дата | Записал |
|---|---|---|---|
| Именование уровней оптимизации | Использование O0-O3 + Auto | 2026-06-16 | 晨煦 |
| Уровень оптимизации по умолчанию | O1 (базовая оптимизация) | 2026-06-16 | 晨煦 |
| Стратегия мономорфизации | Поддержка Erased/OnDemand/Full | 2026-06-16 | 晨煦 |
| Дизайн интерфейса проходов | trait + объявление зависимостей | 2026-06-16 | 晨煦 |
Приложение B: Глоссарий
| Термин | Определение |
|---|---|
| Проход оптимизации | Независимый модуль, выполняющий одно преобразование IR |
| Мономорфизация | Стратегия генерации кода, при которой обобщённые функции специализируются для конкретных типов |
| Свёртка констант | Вычисление константных выражений на этапе компиляции |
| Удаление мёртвого кода | Удаление недостижимого или неиспользуемого кода |
| Встраивание функций | Вставка тела функции в место вызова для избежания накладных расходов на вызов |
| Оптимизация хвостовых вызовов | Преобразование хвостовой рекурсии в цикл для избежания переполнения стека |
| Анализ побега | Анализ того, покидает ли переменная область видимости, для определения распределения в стеке/куче |
Список литературы
- Оптимизации компилятора Rust
- Уровни оптимизации GCC
- Менеджер проходов LLVM
- Оптимизационный конвейер V8 TurboFan
Жизненный цикл и судьба
Данный RFC определяет архитектурный дизайн уровней оптимизации, закладывая единый фреймворк для будущих проходов оптимизации.
Связь с мономорфизацией: мономорфизация является одним из проходов оптимизации и будет реализована первой после принятия данного RFC.
