RFC-008: Архитектура Runtime с разделением модели конкурентности и планировщика
⚠️ Указание о согласовании: Данный документ согласован с RFC-024 Новая модель конкурентности. Старый анализ DAG всей программы, аннотации
@block/@eager, модель уровней L1/L2/L3 заменены примитивом параллельных блоковspawn {}. Анализ DAG теперь применяется только внутри блоковspawn {}.
Справочные материалы:
Аннотация
Данный документ определяет ключевой дизайн архитектуры Runtime:
- Трёхуровневая архитектура Runtime: Embedded (немедленное выполнение) → Standard (spawn + DAG планирование) → Full (WorkStealing)
- Разделение компиляции и выполнения: Этап компиляции полностью идентичен, различается только способ выполнения в runtime
- Двухбэкендная модель: VM (разработка и отладка) и LLVM AOT (производство), полностью идентичное поведение
- Планировщик = статическая библиотека: При AOT компиляции планировщик линкуется в exe, ~200-500KB, без GC
- Синхронность — частный случай планирования: num_workers=1 означает синхронный режим
Ключевое уточнение: это не Java
Java: .java → .class → JVM (интерпретация/JIT + GC) ← всегда требуется виртуальная машина
YaoXiang разработка: .yx → IR → выполнение в VM (быстрая итерация, пошаговая отладка)
YaoXiang производство: .yx → IR → LLVM → нативный exe (планировщик линкуется внутрь)
VM — инструмент разработки, а не сущность runtime. Как в Go: go run vs go build.
Итоговый exe = ваш нативный код + статическая библиотека планировщика + метаданные рефлексии.
Никакого интерпретатора, никакого JIT, никакого GC.Мотивация
Основное противоречие
| Противоречие | Описание |
|---|---|
| Прозрачность vs контроль | Блоки spawn предоставляют явное управление конкурентностью, обычный код выполняется последовательно |
| Ядро vs опциональность | spawn — это основной примитив параллелизма, WorkStealing — продвинутая возможность при num_workers>1 |
| Однопоточность vs конкурентность | В однопоточном режиме конкурентность проявляется как асинхронность, синхронность — лишь частный случай планирования |
Предложение
1. Трёхуровневая архитектура Runtime
┌──────────────────────────────────────────────────────────────────┐
│ Этап компиляции (одинаков для всех режимов) │
│ │
│ Исходный код → Lexer → Parser → TypeCheck → Codegen → IR │
│ │
│ ⚠️ Единая система разбора, проверки типов, кодогенерации, IR │
└──────────────────────────────────────────────────────────────────┘
│
┌────────────────────┼────────────────────┐
▼ ▼ ▼
┌──────────────────┐ ┌───────────────┐ ┌──────────────────┐
│ 🟢 Embedded │ │ 🔵 Standard │ │ 🟣 Full │
│ Немедленный │ │ spawn + DAG │ │ Полный планировщик│
│ исполнитель │ │ Конкурентность│ │ Параллельная │
│ Синхронное │ │ внутри блоков │ │ оптимизация │
│ выполнение │ │ spawn │ │ WorkStealing │
│ Без поддержки │ │ Автоматическая│ │ │
│ spawn │ │ конкурентность│ │ │
└──────────────────┘ └───────────────┘ └──────────────────┘| Этап | Embedded | Standard | Full |
|---|---|---|---|
| Компиляция | одинакова | одинакова | одинакова |
| Режим | синхронный | конкурентность | параллельный |
| выполнения | в блоках spawn | ||
| Использование | низкое | среднее | высокое |
| памяти | |||
| Конкурентность | нет | внутри блоков | внутри блоков + |
| spawn | параллелизм | ||
| Поддержка spawn | ❌ | ✅ | ✅ |
| Анализ DAG | нет | внутри блоков | внутри блоков |
| spawn | spawn | ||
| WorkStealer | нет | нет | ✅ |
Embedded Runtime: Целевые платформы — WASM/скрипты игр/движки правил. Немедленный исполнитель без поддержки spawn, высокопроизводительный с малым потреблением памяти.
Standard Runtime: Целевые платформы — веб-сервисы/конвейеры данных. Поддержка блоков spawn {} с анализом DAG и автоматической конкурентностью внутри блоков spawn. При num_workers=1 — однопоточный асинхронный режим.
Full Runtime: Целевые платформы — научные вычисления/масштабный параллелизм. Standard + WorkStealing балансировка нагрузки.
2. Разделение планировщика: обобщения + внедрение
Главный принцип: VM не зависит напрямую от конкретного планировщика, использует его через обобщённый параметр [S].
# Определение интерфейса планировщика
Scheduler: Type = {
spawn: (Task) -> TaskId,
await: (TaskId) -> Result,
spawn_with_deps: (Task, List(TaskId)) -> TaskId,
await_all: (List(TaskId)) -> List(Result),
stats: () -> SchedulerStats,
}
# Однопоточный планировщик
SingleThreadScheduler: Scheduler = {
spawn: (task) => { task_queue.push(task); generate_task_id() },
await: (task_id) => { ... },
spawn_with_deps: (task, deps) => { ... },
await_all: (task_ids) => { ... },
stats: () => { queue_size: task_queue.len() },
}
# Многопоточный планировщик
MultiThreadScheduler: Scheduler = {
spawn: (task) => { work_queue.push(task); generate_task_id() },
await: (task_id) => { wait_for_completion(task_id) },
spawn_with_deps: (task, deps) => { ... },
await_all: (task_ids) => { ... },
stats: () => { workers: get_worker_stats() },
}
# VM использует планировщик через обобщения
create_vm: [S: Scheduler](scheduler: S) -> VM = (scheduler) => {
VM(scheduler: scheduler, memory: create_memory(), dag: create_dag())
}Ключевые моменты:
- Полиморфизм на этапе компиляции, нулевые накладные расходы в runtime
- Не требуются объекты-типажи
- Обобщённое ограничение типа
[S: Scheduler]уже определено в RFC-011
3. Синхронность = частный случай планирования
❌ Заблуждение: отключить планировщик
✅ Правильно: использовать планировщик с одним worker
num_workers = 1 → однопоточный асинхронный планировщик
num_workers > 1 → многопоточный параллельный планировщик
Тот же интерфейс планировщика, только разная конфигурация. Устраняем особые случаи.4. Место DAG
Важное изменение: Анализ DAG больше не применяется ко всей программе, а только внутри блоков
spawn {}. Обычный код (вне блоков spawn) выполняется последовательно, анализ DAG не требуется.
| Уровень | Поддержка spawn | Область анализа DAG | Описание |
|---|---|---|---|
| Core Runtime | ✅ | внутри блоков spawn | Ядро конкурентности |
| Standard Runtime | ✅ | внутри блоков spawn | spawn + DAG планирование |
| Embedded Runtime | ❌ | нет | Немедленное выполнение, без конкурентности |
5. Модель выполнения снизу вверх (внутри блоков spawn)
Важное изменение: Анализ DAG снизу вверх выполняется только внутри блоков
spawn {}, больше не применяется ко всей программе.
Пользовательский код (конкурентность внутри блока spawn):
(a, b) = spawn {
fetch(url0),
fetch(url1)
}
print(a)
Компиляционный анализ (снизу вверх внутри блока spawn):
fetch(url0) и fetch(url1) не имеют взаимозависимостей → можно выполнять параллельно
print(a) вне блока spawn → последовательное выполнение, ожидание завершения spawn
Runtime планирование (от листьев внутри блока spawn):
fetch(url0) ┐
├→ параллельное выполнение
fetch(url1) ┘
print(a) ← вне блока spawn, последовательное выполнениеКлючевые моменты:
- Анализ зависимостей снизу вверх ограничен только блоками
spawn {} - Задачи без зависимостей внутри блока spawn выполняются параллельно
- Код вне блока spawn выполняется последовательно, ожидая завершения блока spawn
6. Модель компиляции: два бэкенда + статически линкуемый Runtime
6.1 Два бэкенда, одно поведение
┌─────────────────────┐
│ Компиляционный │
│ фронтенд (единый) │
│ Lexer → Parser │
│ → TypeCheck │
│ → анализ DAG │
│ внутри блоков │
│ spawn │
│ → анализ побега │
│ → обнаружение │
│ циклов │
└──────────┬──────────┘
│
┌────────────┴────────────┐
▼ ▼
┌───────────────────┐ ┌───────────────────┐
│ VM бэкенд │ │ LLVM бэкенд │
│ (разработка) │ │ (производство) │
│ │ │ │
│ Генерация IR/ │ │ Генерация │
│ байткода │ │ нативного кода │
│ VM интерпретирует│ │ Линковка рантайм │
│ Поддержка │ │ статической │
│ пошаговой │ │ библиотеки │
│ отладки │ │ Вывод .exe │
│ Быстрая │ │ Нулевые наклад- │
│ итерация │ │ ные расходы │
└───────────────────┘ │ интерпретации │
│ └───────────────────┘
▼ │
Полностью ▼
идентичное Полностью идентичное
поведение поведениеVM бэкенд: Используется при разработке. Изменил код → сразу запустил → пошаговая отладка → быстрая итерация. Поведение полностью идентично финальному exe.
LLVM бэкенд: Используется при публикации. AOT компиляция в нативный код, планировщик линкуется как статическая библиотека. Никакого интерпретатора, никакого JIT.
6.2 Планировщик = статическая библиотека, не виртуальная машина
Внутренняя структура итогового exe:
┌────────────────────────────────────────────┐
│ Ваш код (нативный машинный код) │
│ ├── План выполнения DAG, определённый │
│ │ на этапе компиляции │
│ ├── Встроенные операции Move/ref/clone │
│ └── Код освобождения RAII │
├────────────────────────────────────────────┤
│ Статическая библиотека Runtime (~200-500KB)│
│ ├── Пул потоков (фиксированный размер = │
│ │ num_workers) │
│ ├── Цикл событий (libuv / io_uring) │
│ ├── Очереди WorkStealing (только Full │
│ │ Runtime) │
│ ├── Аллокатор памяти (jemalloc / │
│ │ mimalloc) │
│ └── Метаданные рефлексии (загружаются по │
│ требованию, не резидентны в памяти) │
├────────────────────────────────────────────┤
│ Отсутствует: │
│ ❌ Интерпретатор байткода │
│ ❌ JIT компилятор │
│ ❌ GC │
│ ❌ Виртуальная машина │
└────────────────────────────────────────────┘Сравнение:
| Язык | Java | Go | YaoXiang |
|---|---|---|---|
| Результат | Байткод | Нативный код | Нативный код |
| компиляции | |||
| Способ | JVM | Прямое | Прямое |
| выполнения | интерпретация/ | выполнение | выполнение |
| JIT | |||
| Размер | ~200MB (JVM) | ~1-2MB (с GC) | ~200-500KB (без GC) |
| runtime | |||
| Управление | GC | GC | RAII (детерминированное) |
| памятью | |||
| Рефлексия | Резидентна | Резидентна | **Хранится в exe, |
| в памяти | в памяти | загружается по требованию** |
6.3 Почему производительность планировщика постоянна
Ключевое понимание: Основная часть работы выполняется на этапе компиляции, в runtime происходит только "выполнение".
Этап компиляции (однократно, не в runtime):
├── Анализ DAG внутри блоков spawn: кто от кого зависит
├── Топологическая сортировка: определение порядка
│ выполнения внутри блоков spawn
├── Идентификация параллельных задач: поддеревья
│ без зависимостей внутри блоков spawn
├── Анализ побега: ref → Rc или Arc
├── Обнаружение циклов: автопонижение до Weak
│ или ошибка
└── Встраивание: мелкие функции просто разворачиваются
Этап выполнения (при каждом запуске, структуры данных фиксированы):
├── Диспетчеризация задач в пул потоков
│ в порядке, определённом компилятором для
│ DAG внутри блоков spawn
├── При I/O → приостановка текущей задачи,
│ цикл событий берёт управление
├── Задача готова → возврат в очередь готовых
└── Вот и всё.Сам планировщик — структура данных фиксированного размера: пул потоков, цикл событий, рабочие очереди. Никакого динамического роста, никакой адаптивной реоптимизации, никакого GC-сканирования. Поведение полностью предсказуемо.
На этапе компиляции уже вычислено, "что планировать" внутри блоков spawn, runtime только "выполняет". Это отличается от tokio — tokio динамически строит цепочки Future в runtime. DAG YaoXiang статический и ограничен блоками spawn.
6.4 Рефлексия: хранится, не резидентна
Метаданные рефлексии генерируются на этапе компиляции, хранятся в отдельном сегменте (section) exe. При запуске программы не загружаются. При первом запросе рефлексии загружаются в память по требованию через mmap. Аналогично:
Структура exe:
.text ← ваш код
.rodata ← константы
.reflect ← метаданные рефлексии (информация о типах,
сигнатуры функций и т.д.)
mmap загрузка по требованию, неиспользуемое
не занимает памятьКомпромисс: Увеличение размера exe (содержит данные рефлексии), но нулевые затраты памяти в runtime, если не обращаться. При первом обращении есть задержка загрузки (аналогично JIT warmup), далее нулевые накладные расходы.
src/
├── lib.rs
├── main.rs
├── backends/ # Бэкенды runtime
│ ├── common/ # Общие для всех бэкендов (значения, куча, опкоды)
│ │ ├── allocator.rs
│ │ ├── heap.rs
│ │ ├── opcode.rs
│ │ └── value.rs
│ ├── dev/ # REPL + отладчик
│ │ ├── debugger.rs
│ │ ├── shell.rs
│ │ └── repl/
│ ├── interpreter/ # 🟢 Древовидный интерпретатор (бывший Embedded/VM)
│ │ ├── ffi.rs
│ │ ├── frames.rs
│ │ ├── registers.rs
│ │ ├── runtime.rs
│ │ └── executor/
│ └── runtime/ # 🔵 Runtime скомпилированной VM
│ ├── engine.rs
│ ├── facade.rs
│ └── task.rs
├── frontend/ # Компиляционный фронтенд (общий для всех бэкендов)
│ ├── compiler.rs
│ ├── config.rs
│ ├── pipeline.rs
│ ├── core/
│ │ ├── lexer/
│ │ ├── parser/
│ │ ├── typecheck/
│ │ │ ├── checker.rs
│ │ │ ├── spawn_placement.rs # ★ Анализ DAG/конкурентности внутри блоков spawn
│ │ │ │ # (перенесён из бывшего frontend/dag/)
│ │ │ ├── inference/
│ │ │ └── traits/
│ │ └── types/
│ ├── events/
│ ├── module/
│ └── pipeline/
├── middle/ # Средний уровень
│ ├── core/ # IR и байткод
│ │ ├── bytecode.rs
│ │ ├── ir.rs # Определение IR (общее для VM и LLVM)
│ │ └── ir_gen.rs
│ └── passes/ # Компиляционные проходы
│ ├── codegen/ # Генерация кода (бывший codegen/)
│ ├── lifetime/ # Анализ времени жизни/заимствования
│ └── mono/ # Мономорфизация
├── lsp/ # Language Server
├── formatter/ # Форматтер исходного кода
├── package/ # Менеджер пакетов
├── std/ # Стандартная библиотека
│ ├── concurrent.rs
│ ├── io.rs
│ ├── list.rs
│ ├── math.rs
│ ├── net.rs
│ ├── string.rs
│ └── weak.rs
└── util/ # Утилиты
├── diagnostic/
├── i18n/
└── config/Пояснение к сопоставлению каталогов (старый → новый):
| Старый каталог | Новое расположение | Описание |
|---|---|---|
frontend/dag/ | frontend/core/typecheck/spawn_placement.rs | Анализ DAG внутри блоков spawn интегрирован |
| в проверку типов | ||
codegen/ | middle/passes/codegen/ | Генерация кода перенесена в средний уровень |
embedded/ | backends/interpreter/ | Древовидный интерпретатор |
runtime/ | backends/runtime/ | Runtime скомпилированной VM |
vm/ | backends/interpreter/ | Объединён с embedded |
full/ | (не реализовано) | Full Runtime + WorkStealing, в будущих |
| версиях | ||
reflect/ | (не реализовано) | Метаданные рефлексии, в будущих версиях |
core/ | backends/common/ | Общие значения/куча/опкоды |
Компромиссы
Преимущества
- Чёткое разделение слоёв: Три уровня — Embedded / Standard / Full
- Переиспользование компиляции: Фронтенд код полностью общий
- Разделённые обобщения: Полиморфизм на этапе компиляции, нулевые накладные расходы
- Согласованность: Синхронность — частный случай планирования
- Дружелюбность к встраиваемым системам: Высокая производительность + малое потребление памяти + быстрый запуск
Недостатки
- Начальная сложность: Требуется определить интерфейс планировщика и несколько вариантов runtime
- Компиляционная привязка: Тип планировщика определяется на этапе компиляции
Журнал решений по дизайну
| Решение | Решение | Дата |
|---|---|---|
| Схема разделения | Обобщения + внедрение | 2025-01-05 |
| планировщика | ||
| Однопоточный | Синхронность — частный случай планирования | 2025-01-05 |
| режим | ||
| Реализация | DAG естественно поддерживает | 2025-01-05 |
| асинхронности | ||
| WorkStealer | Продвинутая возможность Full Runtime | 2025-01-05 |
| Дизайн для | Немедленное выполнение, без DAG планирования | 2025-01-05 |
| встраиваемых | ||
| систем | ||
| Этап компиляции | Все рантаймы используют единый фронтенд | 2025-01-05 |
| Разделение | Embedded / Standard / Full | 2025-01-05 |
| по уровням | ||
| Ограничения | Определены в RFC-011 | 2025-01-25 |
| типов | ||
| Построение | Статический граф зависимостей, | 2025-01-05 |
| графа зависимостей | определяется на этапе компиляции | |
| Двухбэкендная | VM (разработка и отладка) + LLVM AOT | 2026-05-11 |
| модель | (производство), идентичное поведение | |
| Форма | Статическая библиотека, линкуется в exe, | 2026-05-11 |
| планировщика | ~200-500KB, без GC | |
| Метаданные | Компилируются в отдельный сегмент exe, | 2026-05-11 |
| рефлексии | mmap загрузка по требованию | |
| Производительность | Анализ DAG выполняется на этапе компиляции, | 2026-05-11 |
| планировщика | в runtime только выполнение | |
| Согласование | Анализ DAG ограничен блоками spawn, | 2026-06-05 |
| области DAG | согласовано с RFC-024 | |
| Обновление | Embedded без spawn, Standard поддерживает | 2026-06-05 |
| трёхуровневой | spawn | |
| архитектуры |
Список литературы
- Спецификация модели конкурентности (RFC-024)
- RFC-011: Проектирование системы обобщённых типов
- Дизайн async runtime в Rust
- Дизайн планировщика Go
Жизненный цикл и судьба
| Статус | Расположение | Описание |
|---|---|---|
| Черновик | docs/design/rfc/ | Авторский черновик |
| Наreview | docs/design/rfc/ | Открыто для обсуждения в сообществе |
| Принято | docs/design/accepted/ | Официальный документ дизайна |
| Отклонено | docs/design/rfc/ | Сохраняется в каталоге RFC |
