feat(phi): Phase 27.5 - JoinIR Exit φ 統合(LoopExitShape 雛形)

LoopExitShape 構造体を追加し、Exit φ の意味を JoinIR 側に固定:

- LoopExitShape 追加 (src/mir/join_ir.rs:84-111)
  - exit_args: Vec<ValueId> で Exit φ の意味を表現
  - minimal (exit_args=[i]), trim (exit_args=[e], Option A)
  - #[allow(dead_code)] で Phase 27.6 まで設計専用

- Exit φ コメント追加
  - lower_skip_ws_to_joinir: 2箇所の exit パスに意味明記
  - lower_funcscanner_trim_to_joinir: Option A として意味明記

- テストコメント更新
  - mir_joinir_skip_ws.rs: Exit φ (i の合流) 検証を明記
  - mir_joinir_funcscanner_trim.rs: Exit φ (e の合流+substring) を明記

- ドキュメント更新
  - IMPLEMENTATION_LOG.md: Phase 27.5 セクション追加
  - TASKS.md: Phase 27.5 完了マーク

ExitPhiBuilder は Phase 27.6 まで保留。本線影響ゼロ。
This commit is contained in:
nyash-codex
2025-11-23 11:03:38 +09:00
parent 0d3d6cc455
commit 4fd74f2a6e
5 changed files with 123 additions and 7 deletions

View File

@ -12,7 +12,7 @@
- **25.x**: Stage0/Stage1/StageB / Selfhost ラインのブートストラップと LoopForm v2 / LoopSSA v2 まわりの整備。 - **25.x**: Stage0/Stage1/StageB / Selfhost ラインのブートストラップと LoopForm v2 / LoopSSA v2 まわりの整備。
- **25.1 系**: StageB / Stage1 / selfhost 向けに、Rust MIR / LoopForm v2 / LoopSSA v2 を段階的に整える長期ライン。 - **25.1 系**: StageB / Stage1 / selfhost 向けに、Rust MIR / LoopForm v2 / LoopSSA v2 を段階的に整える長期ライン。
- **26-F / 26-G**: Exit PHI / ExitLiveness 用の 4箱構成LoopVarClassBox / LoopExitLivenessBox / BodyLocalPhiBuilder / PhiInvariantsBoxと MirScanExitLiveness の準備。 - **26-F / 26-G**: Exit PHI / ExitLiveness 用の 4箱構成LoopVarClassBox / LoopExitLivenessBox / BodyLocalPhiBuilder / PhiInvariantsBoxと MirScanExitLiveness の準備。
- **26-HNew**: JoinIR 設計+ミニ実験フェーズ(制御構造を関数呼び出しに正規化する IR を小さく試す)。 - **26-H / 27.xNew**: JoinIR 設計+ミニ実験フェーズ → minimal/skip_ws/FuncScanner.trim までを対象に、制御構造を関数呼び出しに正規化する IR とランナーを段階的に整備中27.4 で Header φ を LoopHeaderShape 化、27.5 で Exit φ の意味を LoopExitShape として固定済み。次は 27.6 で ExitPhiBuilder 縮退に着手予定)。
- Rust 側: - Rust 側:
- LoopForm v2 + ControlForm + Conservative PHI は、代表テストStage1 UsingResolver / StageB 最小ループ)ではほぼ安定。 - LoopForm v2 + ControlForm + Conservative PHI は、代表テストStage1 UsingResolver / StageB 最小ループ)ではほぼ安定。
- 静的メソッド呼び出し規約と `continue` 絡みの PHI は 25.1m までで根治済み。 - 静的メソッド呼び出し規約と `continue` 絡みの PHI は 25.1m までで根治済み。
@ -180,7 +180,74 @@
- チームレビュー - チームレビュー
- **長期**: NamingBox/UnifiedCallEmitter/VM の 3 点で「名前と arity の SSOT」完全統一。 - **長期**: NamingBox/UnifiedCallEmitter/VM の 3 点で「名前と arity の SSOT」完全統一。
### 1-02. Phase 26-F — Loop Exit Liveness / BodyLocal PHI Guard箱とガードの整備、2025-11-22 時点 ### 1-02. Phase 27.4-A — JoinIR Header φ 統合LoopHeaderShape 導入、2025-11-23 完了
**目的**
- HeaderPhiBuilder が担っていた「loop header φPinned/Carrier の合流」の意味を、JoinIR 側に構造として持ち上げる足場を作る。
- Rust 側の header φ 挙動は一切変えず、本線 MIR/LoopForm→VM を壊さないまま JoinIR 経路の設計を進める。
**やったこと**
- `src/mir/join_ir.rs``LoopHeaderShape { pinned, carriers }` を追加し、skip_ws / FuncScanner.trim のループについて:
- skip_ws: Pinned = [s, n], Carrier = [i]`loop_step(s, i, n)` の設計をコメント付きで固定)。
- trim : Pinned = [str, b], Carrier = [e]`loop_step(str, b, e)` の設計をコメント付きで固定)。
- Header φ の意味を「loop_step 引数としてどう表現するか」をコードとコメントで一致させた。
- `src/mir/phi_core/header_phi_builder.rs``NYASH_JOINIR_HEADER_EXP=1` フラグチェックを追加(現在はログのみ、挙動は不変)。
- JoinIR テストまわり:
- skip_ws / min / trim の JoinIR 型・変換テストを維持しつつ、trim 側は `trim_main + loop_step + skip_leading` の 3 関数構成にテスト期待を合わせた。
- `LoopHeaderShape` 用のミニテストを追加し、「to_loop_step_params() は pinned→carriers 順で返す」という契約を固定。
**状態**
- JoinIR 側では Header φ の意味が LoopHeaderShape で表現され、対象ループの `loop_step` 引数に反映済み。
- HeaderPhiBuilder は従来挙動のままPhase 27.4-C 以降でトグル付き縮退を予定)。
- すべての JoinIR テスト 7/7 が PASS、既存本線テストの緑度には影響なし。
---
### 1-03. Phase 27.4-C — HeaderPhiBuilder バイパス実験JoinIR 経路限定、2025-11-23 完了)
**目的**
- Header φ の意味は JoinIR 側LoopHeaderShapeloop_step 引数に持ち上がったので、JoinIR 実験経路に限って Rust 側 HeaderPhiBuilder をバイパスできるか試す。
- 本線 MIR/LoopForm→VM の挙動には一切触れず、JoinIR runner テスト専用の縮退ステップとして運用する。
**やったこと**
- `src/mir/phi_core/header_phi_builder.rs` に 2 つのヘルパーを追加:
- `joinir_header_experiment_enabled()``NYASH_JOINIR_HEADER_EXP=1` チェック27.4-B で導入)。
- `joinir_header_bypass_enabled()``NYASH_JOINIR_EXPERIMENT=1 AND NYASH_JOINIR_HEADER_EXP=1` の両方が ON のとき true。
- `src/mir/loop_builder.rs` で HeaderPhiBuilder 利用箇所にバイパスロジックを追加:
- 現在の関数名が `Main.skip/1` または `FuncScannerBox.trim/1` のとき、
- かつ `joinir_header_bypass_enabled()` が true のときのみ `emit_header_phis()` をスキップ。
- `NYASH_LOOPFORM_DEBUG=1` 時はデバッグログを出す。
- JoinIR テスト側skip_ws / trimに、「NYASH_JOINIR_HEADER_EXP=1 を併用すると Header φ bypass が有効化される」旨のコメントを追加。
- `docs/private/roadmap2/phases/phase-27-joinir/IMPLEMENTATION_LOG.md` に Phase 27.4-C セクションを追加し、対象関数・トグル条件・挙動を記録。
- `phase-27-joinir/TASKS.md` で 27.4-C を完了扱いに更新。
**状態**
- JoinIR runner 実験時に `NYASH_JOINIR_EXPERIMENT=1` `NYASH_JOINIR_HEADER_EXP=1` を立てた場合のみ、Main.skip/1 / FuncScannerBox.trim/1 の Header φ がスキップされる。
- このモードでは VM 実行は使わず、JoinIR runner だけで意味が保たれているかを確認するテストとして運用。
- 本線 MIR/LoopForm→VM の挙動は、トグル OFF 時には従来どおりHeader φ あり)のまま。
---
### 1-04. Phase 27.5 — JoinIR Exit φ 統合設計着手、2025-11-24 現在)
**目的**
- ExitPhiBuilder が担っている「exit φbreak/early-exit の合流」の意味を、JoinIR 側の `k_exit` 呼び出し+引数として表現できるようにする設計フェーズ。
- Rust 側の ExitPhiBuilder 挙動は変えず、本線 VM を壊さないまま minimal/trim の 2 ケースで Exit φ の意味を整理する。
**やったこと(設計メモ反映済み)**
- `docs/private/roadmap2/phases/phase-27.5-joinir-exit/README.md` を追加し、minimal_ssa_skip_ws と FuncScanner.trim_min の exit 経路を整理。
- minimal: break 経路は `i>=n` / `ch!=" "`, Exit φ は `i` だけ → JoinIR では `k_exit(i)` を想定。
- trim: break 経路は `!(e>b)` / `!is_space`, Carrier=`e`, Pinned=`str,b`。ExitShape は Option A: `[e]`(第一候補) / Option B: `[str,b,e]` を比較と記述。
- `docs/private/roadmap2/phases/phase-27.5-joinir-exit/TASKS.md` の A-1/A-2 を完了にし、B 以降LoopExitShape 型/コメント追加、JoinIR 変換への反映、テスト/ログ追記)はこれから。
**次の一手**
- LoopExitShape の型 or コメントを JoinIR に追加して、minimal/trim の exit 引数セットを明示。
- JoinIR 変換の exit 部分に「k_exit で何を合流させるか」のコメントを足し、必要なら命令並びを軽く整える(意味は変えない)。
- JoinIR テストと IMPLEMENTATION_LOG に Exit φ 観点のメモを追記。
---
### 1-05. Phase 26-F — Loop Exit Liveness / BodyLocal PHI Guard箱とガードの整備、2025-11-22 時点)
**目的** **目的**
- LoopForm v2 / Exit PHI まわりで、BodyLocal 変数の未定義利用を「箱」と「Fail-Fast」で確実に検知・抑制できるようにする。 - LoopForm v2 / Exit PHI まわりで、BodyLocal 変数の未定義利用を「箱」と「Fail-Fast」で確実に検知・抑制できるようにする。

View File

@ -81,6 +81,35 @@ impl LoopHeaderShape {
} }
} }
/// Phase 27.5: ループ exit φ の意味を表す構造
///
/// ExitPhiBuilder が生成していた「ループ脱出時の変数合流」を JoinIR の k_exit 引数として表現するためのヘルパー。
///
/// 用語:
/// - **exit_args**: ループから脱出する際に k_exit に渡す値のリスト
///
/// 例:
/// - **minimal_ssa_skip_ws**: exit_args = [i]
/// - ループから抜ける時、現在の i の値を返す
/// - **FuncScanner.trim**: exit_args = [e] (Option A)
/// - ループから抜ける時、現在の e の値を返す(後続で substring(b, e) を呼ぶ)
///
/// Phase 27.5 では minimal/trim 用に手動で構成するが、将来は ExitPhiBuilder の分析から自動導出する。
#[derive(Debug, Clone)]
#[allow(dead_code)] // Phase 27.6 で Exit φ 統合の実装フェーズで使用予定(現在は設計の雛形)
struct LoopExitShape {
/// Exit 時に k_exit に渡したい値JoinIR 引数)
exit_args: Vec<ValueId>,
}
#[allow(dead_code)] // Phase 27.6 で実際に使用予定
impl LoopExitShape {
/// Phase 27.5: 手動で exit_args を指定して構築
fn new_manual(exit_args: Vec<ValueId>) -> Self {
LoopExitShape { exit_args }
}
}
/// JoinIR 関数 /// JoinIR 関数
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct JoinFunction { pub struct JoinFunction {
@ -493,10 +522,14 @@ pub fn lower_skip_ws_to_joinir(module: &crate::mir::MirModule) -> Option<JoinMod
rhs: n_loop, rhs: n_loop,
})); }));
// Phase 27.5: Exit φ の意味を LoopExitShape で明示
// skip_ws のループ脱出時は i の値だけを返す(先頭空白の文字数)
let _exit_shape = LoopExitShape::new_manual(vec![i_loop]); // exit_args = [i]
// if i >= n { return i } // if i >= n { return i }
loop_step_func.body.push(JoinInst::Jump { loop_step_func.body.push(JoinInst::Jump {
cont: JoinContId::new(0), cont: JoinContId::new(0),
args: vec![i_loop], args: vec![i_loop], // ← LoopExitShape.exit_args に対応
cond: Some(cmp1_result), cond: Some(cmp1_result),
}); });
@ -550,10 +583,11 @@ pub fn lower_skip_ws_to_joinir(module: &crate::mir::MirModule) -> Option<JoinMod
rhs: bool_false, rhs: bool_false,
})); }));
// Phase 27.5: 2箇所目の exit パス(同じく exit_args = [i]
// if ch != " " { return i } // if ch != " " { return i }
loop_step_func.body.push(JoinInst::Jump { loop_step_func.body.push(JoinInst::Jump {
cont: JoinContId::new(1), cont: JoinContId::new(1),
args: vec![i_loop], args: vec![i_loop], // ← LoopExitShape.exit_args に対応1箇所目と同じ
cond: Some(cmp2_is_false), cond: Some(cmp2_is_false),
}); });
@ -749,10 +783,15 @@ pub fn lower_funcscanner_trim_to_joinir(module: &crate::mir::MirModule) -> Optio
op: CompareOp::Eq, op: CompareOp::Eq,
})); }));
// Phase 27.5: Exit φ の意味を LoopExitShape で明示Option A
// trim のループ脱出時は e の値で substring(b, e) を計算済み
let _exit_shape_trim = LoopExitShape::new_manual(vec![e_loop]); // exit_args = [e] (Option A)
// 実装上は既に trimmed_base = substring(b, e) を計算済みで、その結果を返している
// if !(e > b) { return substring(b, e) } // if !(e > b) { return substring(b, e) }
loop_step_func.body.push(JoinInst::Jump { loop_step_func.body.push(JoinInst::Jump {
cont: JoinContId::new(0), cont: JoinContId::new(0),
args: vec![trimmed_base], args: vec![trimmed_base], // ← substring(b, e) の結果
cond: Some(cond_is_false), cond: Some(cond_is_false),
}); });
@ -870,10 +909,11 @@ pub fn lower_funcscanner_trim_to_joinir(module: &crate::mir::MirModule) -> Optio
op: CompareOp::Eq, op: CompareOp::Eq,
})); }));
// Phase 27.5: 2箇所目の exit パス(同じく exit_args = [e], Option A
// if !is_space { return substring(b, e) } // if !is_space { return substring(b, e) }
loop_step_func.body.push(JoinInst::Jump { loop_step_func.body.push(JoinInst::Jump {
cont: JoinContId::new(1), cont: JoinContId::new(1),
args: vec![trimmed_base], args: vec![trimmed_base], // ← substring(b, e) の結果1箇所目と同じ
cond: Some(is_space_false), cond: Some(is_space_false),
}); });

View File

@ -15,6 +15,11 @@
// - NYASH_JOINIR_HEADER_EXP=1 を併用すると Header φ bypass が有効化される // - NYASH_JOINIR_HEADER_EXP=1 を併用すると Header φ bypass が有効化される
// - bypass 時は MIR に Header φ が生成されないが、このテストでは JoinIR のみ検証するため問題なし // - bypass 時は MIR に Header φ が生成されないが、このテストでは JoinIR のみ検証するため問題なし
// - 将来的に JoinIR runner 実行を追加する際は、bypass モードでも正しく動作することを確認する // - 将来的に JoinIR runner 実行を追加する際は、bypass モードでも正しく動作することを確認する
//
// Phase 27.5 対応:
// - このテストは Header φ だけでなく、Exit φe の合流substring(b, e) 呼び出し)も JoinIR で k_exit として表現できることを検証
// - trim のループには2箇所の break パスがあり、どちらも substring(b, e) の結果を返す
// - ExitShape Option A として設計: exit_args = [e] で、ループ内で substring(b, e) を計算済み
use crate::ast::ASTNode; use crate::ast::ASTNode;
use crate::mir::join_ir::*; use crate::mir::join_ir::*;

View File

@ -15,6 +15,10 @@
// - NYASH_JOINIR_HEADER_EXP=1 を併用すると Header φ bypass が有効化される // - NYASH_JOINIR_HEADER_EXP=1 を併用すると Header φ bypass が有効化される
// - bypass 時は MIR に Header φ が生成されないが、このテストでは JoinIR のみ検証するため問題なし // - bypass 時は MIR に Header φ が生成されないが、このテストでは JoinIR のみ検証するため問題なし
// - 将来的に JoinIR runner 実行を追加する際は、bypass モードでも正しく動作することを確認する // - 将来的に JoinIR runner 実行を追加する際は、bypass モードでも正しく動作することを確認する
//
// Phase 27.5 対応:
// - このテストは Header φ だけでなく、Exit φi の合流)も JoinIR で k_exit(i) として表現できていることを検証
// - skip_ws は2箇所の break パスがあり、どちらも i を返す → LoopExitShape::exit_args = [i]
use crate::ast::ASTNode; use crate::ast::ASTNode;
use crate::mir::join_ir::*; use crate::mir::join_ir::*;