## 実装内容(Step 1-3 完全達成) ### Step 1: src/mir/join_ir.rs 型定義追加 - **JoinFuncId / JoinContId**: 関数・継続ID型 - **JoinFunction**: 関数(引数 = φノード) - **JoinInst**: Call/Jump/Ret/Compute 最小命令セット - **MirLikeInst**: 算術・比較命令ラッパー - **JoinModule**: 複数関数保持コンテナ - **単体テスト**: 型サニティチェック追加 ### Step 2: テストケース追加 - **apps/tests/joinir_min_loop.hako**: 最小ループ+breakカナリア - **src/tests/mir_joinir_min.rs**: 手書きJoinIR構築テスト - MIR → JoinIR手動構築で型妥当性確認 - #[ignore] で手動実行専用化 - NYASH_JOINIR_EXPERIMENT=1 トグル制御 ### Step 3: 環境変数トグル実装 - **NYASH_JOINIR_EXPERIMENT=1**: 実験モード有効化 - **デフォルト挙動**: 既存MIR/LoopForm経路のみ(破壊的変更なし) - **トグルON時**: JoinIR手書き構築テスト実行 ## Phase 26-H スコープ遵守 ✅ 型定義のみ(変換ロジックは未実装) ✅ 最小限の命令セット ✅ Debug 出力で妥当性確認 ✅ 既存パイプライン無影響 ## テスト結果 ``` $ NYASH_JOINIR_EXPERIMENT=1 cargo test --release mir_joinir_min_manual_construction -- --ignored --nocapture [joinir/min] MIR module compiled, 3 functions [joinir/min] JoinIR module constructed: [joinir/min] ✅ JoinIR型定義は妥当(Phase 26-H) test result: ok. 1 passed; 0 failed ``` ## JoinIR理論の実証 - **φノード = 関数引数**: `fn loop_step(i, k_exit)` - **merge = join関数**: 分岐後の合流点 - **ループ = 再帰関数**: `loop_step` 自己呼び出し - **break = 継続呼び出し**: `k_exit(i)` ## 次フェーズ (Phase 27.x) - LoopForm v2 → JoinIR 自動変換実装 - break/continue ハンドリング - Exit PHI の JoinIR 引数化 🌟 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: ChatGPT <noreply@openai.com>
Phase 26-G — Exit Liveness MIR Scan (実装フェーズ)
Status: planning
ゴール(何を達成するフェーズか)
- ExitLivenessProvider の本実装(MIR 命令列スキャン版)を作り、FuncScanner のカナリア
mir_funcscanner_skip_ws_direct_vmmir_funcscanner_parse_params_trim_min_verify_and_vmを緑にする。
- 26-F で用意した差し替え口(MirScanExitLiveness)に、use/def ベースの liveness を実装する。
NYASH_EXIT_LIVE_ENABLE=1で MIR スキャン版を有効化し、デフォルトは従来挙動のまま安全側。
スコープ(このフェーズでやること)
- LoopFormOps / LoopBuilder / JSON bridge (LoopFormJsonOps) に「MirFunction への参照を返す API」を追加する設計と最小実装。
- MirScanExitLiveness を
MirQueryベースの use/def スキャンに置き換える。 - 代表テスト(FuncScanner カナリア)で緑を確認するところまで。
実装ステップ(順序)
Step 1: API 設計と LoopFormOps 拡張
- 目的: Exit 側から MirFunction(または MirQuery)に到達できるようにする。
- 具体案:
LoopFormOpsに「MirFunction への参照を返すメソッド」を追加する:- 例:
fn mir_function(&self) -> &MirFunction;
- 例:
- 実装箇所:
impl<'a> LoopFormOps for LoopBuilder<'a>:self.parent_builder.current_function.as_ref().expect(...)経由で &MirFunction を返す。
impl LoopFormOps for LoopFormJsonOps<'_>:- 既に
f: &mut MirFunctionを保持しているので、その参照を返す。
- 既に
- テスト用
MockOpsなど:- 最小限の MirFunction スタブを持つ or
unimplemented!()でテストを段階的に更新。
- 最小限の MirFunction スタブを持つ or
Step 2: ExitPhiBuilder に MirQuery フックを追加
- 目的: Exit PHI 生成時に MirQuery にアクセスできるようにする。
- 具体案:
ExitPhiBuilder::build_exit_phisのシグネチャを拡張:- 現状:
fn build_exit_phis<O: LoopFormOps>(..., exit_snapshots, pinned, carrier) -> Result<(), String> - 追加:
mir_query: &dyn MirQueryを引数に足す、または内部でops.mir_function()からMirQueryBoxを組み立てる。
- 現状:
- 検討:
- a)
build_exit_phisに&dyn MirQueryを直渡し → テストで差し替えやすい。 - b)
LoopFormOpsにmir_function()を足し、ExitPhiBuilder側でMirQueryBox::new(ops.mir_function())を作る。
- a)
- このフェーズでは b) 案を優先(既に LoopBuilder/JSON bridge は MirFunction を握っているため)。
Step 3: MirScanExitLiveness の本実装(use/def スキャン)
- 前提: Step 2 で
MirQueryBoxを構築できる。 - 実装方針:
- 入口:
MirScanExitLiveness::compute_live_at_exit(header_vals, exit_snapshots)の中でMirQueryBoxとexit_blocksを受け取れるようにする(必要ならExitLivenessProviderトレイトのシグネチャ拡張も検討)。
- 最小アルゴリズム:
- スキャン対象ブロック集合:
- exit ブロック(LoopShape.exit)+ exit_snapshots に現れる break 元ブロック。
- 初期 live 集合:
- 対象ブロックの命令を後ろから走査し、
reads_of(inst)を live に追加。
- 対象ブロックの命令を後ろから走査し、
- 1-step backward 伝播:
- それぞれのブロックで
writes_of(inst)で kill、reads_of(inst)で add。 succs(bb)が対象集合に含まれている場合、succ の live を bb に流す。
- それぞれのブロックで
- 固定点反復:
- live 集合が変わらなくなるまで 2〜3 を繰り返す。
- 名前へのマッピング:
- header_vals / exit_snapshots に現れる
(name, ValueId)を逆引きテーブルに集約し、 live に含まれる ValueId に対応する name だけをlive_at_exitに含める。
- header_vals / exit_snapshots に現れる
- スキャン対象ブロック集合:
- 返り値:
BTreeSet<String>(BodyLocalPhiBuilder からそのまま使える)。
- 既存ロジックとの整合:
- BodyLocalInternal でも「exit後で本当に live」なものだけが rescue されることが期待される。
- 入口:
Step 4: ExitPhiBuilder / BodyLocalPhiBuilder 統合確認
- 目的: 新しい live_at_exit を使っても PhiInvariantsBox で落ちないことを確認する。
- 作業:
BodyLocalPhiBuilder::filter_exit_phi_candidatesがclass.needs_exit_phi()(Pinned/Carrier/BodyLocalExit) とlive_at_exit+is_available_in_allの OR を行っていることを再確認。
PhiInvariantsBox::ensure_exit_phi_availabilityが- 「選ばれた変数はすべての exit pred で定義済み」であることを保証し、
- それでも穴があれば即 Fail-Fast で教えてくれることを前提に、MirScan 側のロジックを調整。
Step 5: カナリア検証
- コマンド:
NYASH_EXIT_LIVE_ENABLE=1 cargo test --release --lib mir_funcscanner_skip_ws_direct_vmNYASH_EXIT_LIVE_ENABLE=1 cargo test --release --lib mir_funcscanner_parse_params_trim_min_verify_and_vm
- 期待:
- MirVerifier が
use of undefined valueを報告しない。 - VM 実行で RC が期待通り(skip_ws の sentinel が正しく動き、trim/parse_params も undefined を出さない)。
- MirVerifier が
Step 6: ドキュメント更新
loopform_ssot.mdに:- MirQuery / MirQueryBox の役割と ExitLiveness との関係。
- 「MirScanExitLiveness は 26-G で use/def スキャン実装済み」と記載。
phase-26-G/README.md自体も「実装完了」セクションを追記。
やらないこと
- Loop 形状や PHI 生成ロジックの意味変更(ExitPhiBuilder/BodyLocalPhiBuilder のアルゴリズム変更はしない)。
- env 名の変更や追加(既存の
NYASH_EXIT_LIVE_ENABLEを継続利用)。
受け入れ条件
NYASH_EXIT_LIVE_ENABLE=0/未設定で従来のテスト結果を維持。NYASH_EXIT_LIVE_ENABLE=1で FuncScanner カナリアが緑(MirVerifier/VM)。- docs 更新: 26-F/loopform_ssot に「MIR スキャン実装済み」を追記。