# Phase 26-H — JoinIR / 関数正規化フェーズ設計図 目的: これまで「構文 → LoopForm → PHI」で説明してきた制御構造を、もう一段抽象度を上げて「関数呼び出し+継続」に正規化する中間層(JoinIR / LoopFnIR)として整理し直すこと。 最終的には「ループや if の合流点で悩む」のではなく、「関数の引数と戻り先で意味が決まる」世界に寄せ、箱の数と責務を減らしていく。 このフェーズ 26‑H ではあくまで「設計とミニ実験」に留め、スモークや本線は既存の MIR/LoopForm ルートのまま維持する。 --- ## 1. 現状: LoopForm 正規化ベースの世界 現在のパイプライン(概略): ```text AST → MIR / LoopForm v2 → VM / LLVM ``` LoopForm v2 / PHI 周辺には、だいたい次のような箱が存在している: - 構造系 - `LoopFormBuilder` / `LoopFormOps` - `ControlForm`(If/Loop の形と preds) - PHI 生成系 - `HeaderPhiBuilder` - `ExitPhiBuilder` - `BodyLocalPhiBuilder` - `IfBodyLocalMergeBox` - `PhiBuilderBox`(If φ 統合) - `PhiInvariantsBox`(Fail-Fast チェック) - 解析/分類系 - `LoopVarClassBox`(Pinned / Carrier / BodyLocal*) - `LoopExitLivenessBox`(ExitLiveness、実装は段階的) - `LocalScopeInspectorBox` - if 解析系(`if_phi.rs` の補助群) これらの箱が「どの変数がループをまたぐか」「どこで φ が必要か」「Exit で何を Live とみなすか」を決めているが、その分、箱の数と責務が多く、ループの形を変えるたびに PHI 側の負担が増えている。 --- ## 2. 代案: 「関数を呼ぶ回数=ループ」というモデル 発想の転換: - 今: 構文を LoopForm に正規化し、ループ構造(header/body/latch/exit)を中心に世界を説明している。 - 代案: 構文を「関数呼び出し」に正規化し、**関数を繰り返し呼ぶこと自体がループ**というモデルに寄せる。 ### 2.1 ループの例 元のコード(擬似 Nyash): ```hako var x = 0 loop { x = x + 1 if x >= 10 { break } } print(x) ``` 関数ループモデルで見ると: ```hako // ループ一歩ぶんの関数(Box) step(x, k_exit) { if x >= 10 { k_exit(x) // ループ終了して「先」に進む } else { step(x + 1, k_exit) // もう一周 } } // ループの「先」の処理 k_exit = (v) => { print(v) } // 実行開始 step(0, k_exit) ``` - ループ = `step` を何回も呼ぶこと - `break` = `k_exit(...)` を呼ぶこと - `continue` = `step(...)` を呼ぶこと - φ / LoopCarried 変数 = `step` の引数 ここでは「ループヘッダの φ で悩む」のではなく、「step の引数・k_exit の引数をどう定義するか」に責務が集中する。 ### 2.2 パイプラインの再構成案 現在: ```text AST → MIR / LoopForm v2 → VM/LLVM ``` ここに 1 段挟む: ```text AST → MIR / LoopForm v2 → ★LoopFnIR(関数ループ層) → VM/LLVM ``` この LoopFnIR/JoinIR 層で: - 各 LoopForm について「ループ関数(step) + 継続関数(k_exit)」を合成。 - ループの PHI / carrier / exit φ はすべて `step` / `k_exit` の引数として表現。 - 下流(VM / LLVM)は「関数呼び出し(および再帰のループ化や展開)」だけを見ればよい。 結果として: - LoopForm v2 は「LoopFnIR を作る前段」に役割縮小。 - BodyLocal / Exit φ の詳細設計は「引数に何を持っていくか?」という関数インターフェース設計に吸収される。 --- ## 4. このフェーズで実装する箱 / 概念ラベル - 実装として増やす(26-H 内で手を動かすもの) - `join_ir.rs`: JoinIR 型(関数/ブロック/命令)+ダンプ - LoopForm→JoinIR のミニ変換(1 ケース限定で OK) - 実験トグル(例: `NYASH_JOINIR_EXPERIMENT=1`)で JoinIR をダンプするフック - 概念ラベル(27.x 以降に検討) - MirQuery のようなビュー層(reads/writes/succs を trait 化) - LoopFnLoweringBox / JoinIRBox の分割や最適化パス - VM/LLVM への統合 ※ このフェーズでは「設計+ミニ実験のみ」で、本線スモークは既存 MIR/LoopForm 経路を維持する。 --- ## 3. 箱の数と最終形のイメージ ### 3.1 現在の PHI/Loop 周辺の箱(概略) ざっくりカテゴリ分けすると: - 構造: - `LoopFormBuilder` - `ControlForm` - PHI 生成: - `HeaderPhiBuilder` - `ExitPhiBuilder` - `BodyLocalPhiBuilder` - `IfBodyLocalMergeBox` - `PhiBuilderBox` - `PhiInvariantsBox` - 解析: - `LoopVarClassBox` - `LoopExitLivenessBox` - `LocalScopeInspectorBox` - if 解析系(IfAnalysisBox 的なもの) 関数正規化前提で進むと、最終的には: - PHI を直接扱う箱は「LoopForm→LoopFnIR に変換する前段」に閉じ込める。 - LoopFnIR 導入後の本線では、次のような少数の箱が中心になる: - `LoopFnLoweringBox`(LoopForm → LoopFnIR / JoinIR) - `JoinIRBox`(JoinIR の保持・最適化) - 既存の VM/LLVM バックエンド(JoinIR からのコード生成側) という構造に寄せられる見込み。 このフェーズ 26‑H では、「最終的にそこに寄せるための設計図」を書くところまでを目標とする。 --- ## 4. 26-H でやること(スコープ) - JoinIR / LoopFnIR の設計ドキュメント作成 - 命令セット(call / ret / jump / 継続)の最小定義。 - if / loop / break / continue / return を JoinIR に落とす書き換え規則。 - φ = 関数引数、merge = join 関数、loop = 再帰関数+exit 継続、という対応表。 - 最小 1 ケースの手書き変換実験(MIR → JoinIR) - ループ+break を含む簡単な関数を 1 例だけ JoinIR に落とし、形を確認。 - MirQueryBox 経由で必要な MIR ビュー API の確認 - reads/writes/succs など、JoinIR 変換に必要な情報がすでに `MirQuery` で取れるかチェック。 - すべてトグル OFF で行い、本線(MIR/LoopForm ルート)のスモークには影響させない。 --- ## 5. やらないこと(26-H では保留) - 既存ルート(MIR/LoopForm/VM/LLVM)を JoinIR で置き換える。 - スモーク / CI のデフォルト経路変更。 - Loop/PHI 既存実装の削除(これは 27.x 以降の段階で検討)。 --- ## 6. 実験計画(段階) 1. **設計シート** - `docs/development/architecture/join-ir.md` に命令セット・変換規則・対応表を記述(φ=引数, merge=join, loop=再帰)。 2. **ミニ変換実験 1 ケース** - 最小ループ(例: `loop(i < 3) { if i >= 2 { break } i = i + 1 } return i`)を MIR → JoinIR へ手書き変換し、テストでダンプを確認。VM/LLVM 実行までは行わない。 3. **トランスレータ骨格** - `src/mir/join_ir.rs` などに型定義だけ追加(未配線、トグル OFF)。MirQueryBox(reads/writes/succs)で必要なビューが揃っているか確認。 4. **トグル付き実験** - `NYASH_JOINIR_EXPERIMENT=1` などのトグルで最小ケースを JoinIR 変換・ダンプするルートを作る(デフォルト OFF でスモーク影響なし)。 --- ## 7. 受け入れ基準(このフェーズ) - docs に JoinIR / LoopFnIR の設計と変換規則が明記されている。 - 最小 1 ケースの JoinIR 変換がテストでダンプできる(join/step/k_exit の形になっている)。 - 本線スモーク(既存 MIR ルート)は影響なし(トグル OFF)。 --- ## 8. 次フェーズへの橋渡し - 変換器を拡張して FuncScanner / Stage‑B などカナリアを JoinIR で通す(トグル付き)。 - ExitLiveness や BodyLocal PHI の一部を LoopFnIR 側に吸収し、PHI/Loop 周辺の箱を徐々に減らす。 - VM/LLVM 実行経路に JoinIR を統合するのは 27.x 以降を想定し、当面は「設計+ミニ実験」に留める。