Skip to content

RFC-008:Runtime 並列処理モデルとスケジューラ分離設計

⚠️ 整合に関する注意:本文書はRFC-024 新並列処理モデル と整合しています。旧式の全プログラムDAG分析、@block/@eagerアノテーション、L1/L2/L3レイヤーモデルは spawn {}ブロック並列プリミティブに置き換えられました。DAG分析は現在はspawn {}ブロック内部でのみ機能します。

参考:

概要

本文書はRuntimeアーキテクチャの重要な設計を定義します:

  1. ランタイム三層アーキテクチャ:Embedded(即時実行) → Standard(spawn + DAGスケジューリング) → Full(ワークスティーリング)
  2. コンパイルと実行の分離:コンパイル段階はすべてのモードで同一で、実行時の実行方式のみ異なります
  3. デュアルバックエンドモデル:VM(開発デバッグ)とLLVM AOT(本番リリース)、動作は完全一致
  4. スケジューラ = 静的ライブラリ:AOTコンパイル時にスケジューラをexeにリンク、約200-500KB、GCなし
  5. 同期はスケジューリングの特殊ケース:num_workers=1で同期モード

重要な澄清:これはJavaではない

Java:   .java → .class → JVM(解釈/JIT + GC)        ← 常に仮想マシンが必要
YaoXiang 開発: .yx → IR → VM実行(高速反復、ステップデバッグ)
YaoXiang 本番: .yx → IR → LLVM → ネイティブexe(スケジューラが静的リンク)

VMは開発ツールであり、ランタイムの本質ではありません。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   │ │ Fullスケジューラ  │
│ 同期実行         │ │ spawnブロック内│ │ 並列最適化       │
│ spawnサポートなし │ │ 自動並列処理   │ │ ワークスティーリング│
└──────────────────┘ └───────────────┘ └──────────────────┘
段階EmbeddedStandardFull
コンパイル同一同一同一
実行モード同期spawnブロック内並列
メモリ使用量
並列処理能力なしspawnブロック内spawnブロック内+並列
spawnサポート
DAG分析なしspawnブロック内spawnブロック内
WorkStealerなしなし

Embedded Runtime:WASM/ゲームスクリプト/ルールエンジン向け。即時実行器、spawnサポートなし、高性能低消費。

Standard Runtime:Webサービス/データパイプライン向け。spawn {} ブロックをサポートし、spawnブロック内でDAG分析と自動並列処理を実行。num_workers=1でシングルスレッド非同期。

Full Runtime:科学計算/大規模並列向け。Standard + WorkStealerによる負荷分散。

2. スケジューラ分離:ジェネリクス + 注入

基本原则:VMは具体的なスケジューラに直接依存せず、ジェネリクスパラメータ[S]を通じて呼び出します。

yaoxiang
# スケジューラインターフェース定義
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())
}

コアポイント

  • コンパイル時多相、ゼロ런タイムオーバーヘッド
  • traitオブジェクト不要
  • ジェネリクス型制約[S: Scheduler]はRFC-011で既に定義済み

3. 同期 = スケジューリングの特殊ケース

❌ 誤解:スケジューラを無効化
✅ 正しい:単一workerのスケジューラを使用

num_workers = 1 → シングルスレッド非同期スケジューリング
num_workers > 1 → マルチスレッド並列スケジューリング

同一のスケジューラインターフェース、設定が異なるだけ。特殊ケースを排除。

4. DAGの地位

重要な変更:DAG分析は全プログラムではなく、spawn {} ブロック内部のみで機能します。通常コード(spawnブロック外)は順序実行され、DAG分析は不要です。

レイヤspawnサポートDAG分析範囲説明
Core Runtimespawnブロック内並列処理コア
Standard Runtimespawnブロック内spawn + DAGスケジューリング
Embedded Runtimeなし即時実行、並列処理なし

5. ボトムアップ実行モデル(spawnブロック内)

重要な変更:ボトムアップDAG分析はspawn {}ブロック内部でのみ実行され、全プログラムに対しては行いません。

ユーザーコード(spawnブロック内並列):
    (a, b) = spawn {
        fetch(url0),
        fetch(url1)
    }
    print(a)

コンパイル時分析(spawnブロック内ボトムアップ):
    fetch(url0) と fetch(url1) は相互依存なし → 並列実行可能
    spawnブロック外のprint(a) → 順序実行、spawn完了を待機

ランタイムスケジューリング(spawnブロック内、リーフから):
    fetch(url0) ┐
                ├→ 並列実行
    fetch(url1) ┘
    print(a)                       ← spawnブロック外、順序実行

コアポイント

  • ボトムアップ依存分析はspawn {}ブロック内部に限定
  • spawnブロック内で依存のないタスクは並列実行
  • spawnブロック外のコードは順序実行、spawnブロック完了を待機

6. コンパイルモデル:デュアルバックエンド + 静的リンクランタイム

6.1 二つのバックエンド、一つの動作

                      ┌─────────────────────┐
                      │   コンパイルフロントエンド(統一)│
                      │   Lexer → Parser     │
                      │   → TypeCheck        │
                      │   → spawnブロック内DAG分析│
                      │   → エスケープ分析    │
                      │   → サイクル検出      │
                      └──────────┬──────────┘

                    ┌────────────┴────────────┐
                    ▼                         ▼
        ┌───────────────────┐     ┌───────────────────┐
        │   VMバックエンド(開発)│     │  LLVMバックエンド(本番)│
        │                   │     │                   │
        │  IR/バイトコード生成   │     │  ネイティブコード生成    │
        │  VM解釈実行         │     │  ランタイム静的ライブラリ │
        │  ステップデバッグ対応  │     │  .exe出力           │
        │  高速反復           │     │  ゼロ解釈オーバーヘッド │
        └───────────────────┘     └───────────────────┘
                 │                         │
                 ▼                         ▼
           動作完全一致                 動作完全一致

VMバックエンド:開発時に使用。コード修正 → 即時実行 → ステップデバッグ → 高速反復。動作は最終exeと完全一致。

LLVMバックエンド:リリース時に使用。AOTコンパイルでネイティブコード生成、スケジューラは静的ライブラリとしてリンク。解释器なし、JITなし。

6.2 スケジューラ = 静的ライブラリ、仮想マシンではない

最終exeの内部構造:

┌────────────────────────────────────────────┐
│  あなたのコード(ネイティブ機械語)           │
│  ├── コンパイル時に確定したDAG実行計画       │
│  ├── インライン化されたMove/ref/clone操作    │
│  └── RAII解放コード                        │
├────────────────────────────────────────────┤
│  ランタイム静的ライブラリ(~200-500KB)       │
│  ├── スレッドプール(固定サイズ = num_workers)│
│  ├── イベントループ(libuv / io_uring)     │
│  ├── ワークスティーリングキュー(Full Runtimeのみ)│
│  ├── メモリアロケータ(jemalloc / mimalloc)│
│  └── リフレクションメタデータ(オンデマンド読み込み、常駐なし)│
├────────────────────────────────────────────┤
│  なし:                                      │
│  ❌ バイトコード解释器                        │
│  ❌ JITコンパイラ                           │
│  ❌ GC                                      │
│  ❌ 仮想マシン                                │
└────────────────────────────────────────────┘

比較:

言語JavaGoYaoXiang
コンパイル成果物バイト码ネイティブコードネイティブコード
実行方式JVM解釈/JIT直接実行直接実行
ランタイムサイズ~200MB(JVM)~1-2MB(GC含む)~200-500KB(GCなし)
メモリ管理GCGCRAII(决定的)
リフレクション常駐メモリ常駐メモリexe中に保存、オンデマンド

6.3 なぜスケジューラ性能が一定なのか

重要な洞察:ほとんどの作業はコンパイル時に完了し、ランタイムでは「実行」のみを行います。

コンパイル時(一度だけ、ランタイムには含まれない):
    ├── spawnブロック内DAG分析:誰が何に依存するか
    ├── トポロジカルソート:spawnブロック内実行順序確定
    ├── 並列実行可能タスク識別:spawnブロック内の依存なしサブツリー
    ├── エスケープ分析:ref → Rc か Arc か
    ├── サイクル検出:自動降格Weakまたはエラー
    └── インライン:小関数は直接展開

ランタイム(実行のたびに、データ構造は固定):
    ├── コンパイル時に確定したspawnブロックDAG順序でスレッドプールにタスク配布
    ├── I/O遭遇 → 現在のタスクを一時停止、イベントループが接管
    ├── タスク準備完了 → 準備完了キューに戻す
    └── 以上。

スケジューラ自体は固定サイズのデータ構造:スレッドプール、イベントループ、ワークキュー。動的成長なし、アダプティブ再最適化なし、GCスキャンなし。動作は完全に予測可能。

コンパイル時にspawnブロック内の「何をスケジューリングするか」を既に計算済みで、ランタイムでは「実行」のみを行います。これはtokioとは異なり、tokioはランタイムで動的にFutureチェーンを構築します。YaoXiangのDAGは静的であり、spawnブロック内に限定されます。

6.4 リフレクション:保存するが常駐させない

リフレクションメタデータはコンパイル時に生成され、exeの独立セクション(section)に保存されます。プログラム起動時に読み込みません。リフレクションが初めてリクエストされたとき、オンデマンドでmmapしてメモリに読み込みます。例えるなら:

exeレイアウト:
  .text     ← あなたのコード
  .rodata   ← 定数
  .reflect  ← リフレクションメタデータ(型情報、関数シグネチャ等)
              mmapでオンデマンド読み込み、不アクセスならメモリ使用なし

トレードオフ:exeサイズが増加(リフレクションデータを含む)が、ランタイムでアクセスしなければメモリコストゼロ。初回アクセス時に読み込み遅延あり(JITウォームアップと同様)、以降はゼロコスト。

src/
├── lib.rs
├── main.rs
├── backends/                          # ランタイムバックエンド
│   ├── 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/                       # 🔵 コンパイル型VMランタイム
│       ├── engine.rs
│       ├── facade.rs
│       └── task.rs
├── frontend/                          # コンパイルフロントエンド(全バックエンド共有)
│   ├── compiler.rs
│   ├── config.rs
│   ├── pipeline.rs
│   ├── core/
│   │   ├── lexer/
│   │   ├── parser/
│   │   ├── typecheck/
│   │   │   ├── checker.rs
│   │   │   ├── spawn_placement.rs     # ★ spawnブロック内DAG/並列分析(元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.rsspawnブロック内DAG分析が型チェックに統合済み
codegen/middle/passes/codegen/コード生成が中間パスの手に移動
embedded/backends/interpreter/ツリーランタイム解释器
runtime/backends/runtime/コンパイル型VMランタイム
vm/backends/interpreter/embeddedと統合
full/(未実装)Full Runtime + ワークスティーリング、後続バージョンで
reflect/(未実装)リフレクションメタデータ、後続バージョンで
core/backends/common/共有値/ヒープ/オペコード

トレードオフ

利点

  • 明確な階層化:Embedded / Standard / Full の三層
  • コンパイル再利用:フロントエンドコードは完全に共用
  • ジェネリクス分離:コンパイル時多相、ゼロオーバーヘッド
  • 一貫性:同步はスケジューリングの特殊ケース
  • 組込み用途に優しい:高性能 + 低メモリ + 高速起動

欠点

  • 初期複雑さ:スケジューラインターフェースと多様なランタイム変体を定義する必要がある
  • コンパイル時バインディング:スケジューラ型はコンパイル時に確定

設計意思決定記録

意思決定決定日付
スケジューラ分離方案ジェネリクス + 注入2025-01-05
シングルスレッドモード同步はスケジューリングの特殊ケース2025-01-05
非同期実装DAGは自然にサポート2025-01-05
WorkStealerFull Runtimeの上級機能2025-01-05
組込み設計即時実行、DAGスケジューリングなし2025-01-05
コンパイル段階全ランタイムで同一フロントエンドを共有2025-01-05
ランタイム階層化Embedded / Standard / Full2025-01-05
型制約RFC-011で既に定義2025-01-25
依存グラフ構築静的依存グラフ、コンパイル時に確定2025-01-05
デュアルバックエンドモデルVM(開発デバッグ)+ LLVM AOT(本番)、動作一致2026-05-11
スケジューラ形態静的ライブラリとしてexeにリンク、~200-500KB、GCなし2026-05-11
リフレクションメタデータexeの独立セクションにコンパイル、mmapでオンデマンド読み込み2026-05-11
スケジューラ性能コンパイル時にDAG分析完了、ランタイムは実行のみ2026-05-11
DAG範囲整合DAG分析はspawnブロック内に限定、RFC-024と整合2026-06-05
三層アーキテクチャ更新Embeddedはspawnなし、Standardはspawnサポート2026-06-05

参考文献


ライフサイクルと归宿

状態所在地説明
草案docs/design/rfc/作者草稿
審査中docs/design/rfc/コミュニティ議論公開
受理済みdocs/design/accepted/正式設計ドキュメント
拒否済みdocs/design/rfc/RFCディレクトリに保存