diff --git a/docs/development/roadmap/phases/phase-25.1n/README.md b/docs/development/roadmap/phases/phase-25.1n/README.md new file mode 100644 index 00000000..cd0ba3a8 --- /dev/null +++ b/docs/development/roadmap/phases/phase-25.1n/README.md @@ -0,0 +1,98 @@ +# Phase 25.1n — MirBuilder Self‑Host 移植ライン(Rust SSOT → .hako 実装) + +Status: planning(設計フェーズ。実装は 25.2 系と並行で段階移行) + +## ゴール + +- Rust 側で固めた **SSA/PHI SSOT(LoopForm v2 / IfForm / BodyLocal / PhiBuilderBox)** を、 + `.hako` 側の `MirBuilderBox` / `LoopFormBox` / `PhiBuilderBox` に「構造そのまま」移植できるようにするフェーズだよ。 +- このフェーズでは: + - Rust `MirBuilder` を **唯一のオラクル** として扱い、 + - その挙動を「表(ケース表+制御構造の形)」と「テスト」で固定する。 + - `.hako` 側はその表とテストを見ながら、同じ SSA/PHI を組み立てる実装に寄せていく。 +- 25.1/26‑E まででやってきた **LoopForm v2 / ExitPhiBuilder / BodyLocalPhiBuilder / IfForm / PhiBuilderBox** の成果を、 + Self‑Host 実装に届けるための「橋渡しフェーズ」だよ。 + +## スコープ(25.1n でやること) + +### N‑A: SSA/PHI SSOT の「表」化(Rust 側設計をテーブルに落とす) + +- ファイル候補: + - `docs/development/architecture/loops/loopform_ssot.md`(既存の A/B/C/D ケース表を拡張) + - `docs/development/architecture/ssa/phi_cases_stage1.md`(新規) +- やること: + - すでに存在する LoopForm ケース表(Case A/B/C/D)に対して、 + - `LoopVarClass`(Pinned / Carrier / BodyLocalExit / BodyLocalInternal)× + - LoopCase (A/B/C/D) × + - place(header / exit / body‑if‑merge) + を軸に「どこに PHI を張るか」を表にする。 + - If についても: + - `then/else` の到達可否(break/continue/early‑return)と、 + - 変数のクラス(Pinned/Carrier/BodyLocal) + から、「PHI / direct bind / pre 値そのまま」の 3パターンを表で決める。 + - これらを Rust コードに依存しない形で書き下し、 + - 「Rust 実装はこの表を実現しているだけ」という関係にする(SSOT = docs + テスト)。 + +### N‑B: Rust MirBuilder オラクルテストの整備 + +- ファイル候補: + - `src/tests/mir_loopform_conditional_reassign.rs` + - `src/tests/mir_stage1_using_resolver_verify.rs` + - `src/tests/mir_stage1_cli_emit_program_min.rs` +- やること: + - 代表的な構造(LoopCase A〜D / Stage‑1 UsingResolver / Stage‑B fib/defs)について、 + - `.hako` 入力 → Rust `MirCompiler` → `MirVerifier` の結果を **MIR テキストとして固定**するテスト(golden テストに近い)を 1〜2 本ずつ用意する。 + - これらのテストは「Rust MirBuilder の挙動を凍結する」役割のみを持ち、 + - `.hako` 側実装が追いつくまでは「期待値 = Rust 実装」の位置付けにする。 + - 将来は、`.hako` 実装の MIR と diff を取る比較テストに発展させる(本フェーズでは準備だけ)。 + +### N‑C: .hako MirBuilderBox への API 設計(移植用インターフェース定義) + +- ファイル候補: + - `lang/src/compiler/mir/mir_builder_box.hako`(仮) + - `lang/src/compiler/mir/loopform_box.hako`(仮) + - `lang/src/compiler/mir/phi_builder_box.hako`(仮) +- やること: + - Rust の `MirBuilder` / `LoopFormBuilder` / `PhiBuilderBox` の公開インターフェースから、 + - `.hako` 側で必要になる API を抜き出し、Nyash の Box としてのシグネチャだけ先に決める。 + - 例: + - `MirBuilderBox.emit_block(fn_name, ast)` → MirModule にブロック/関数を追加。 + - `LoopFormBox.build_loop(condition, body_ast)` → LoopForm v2 構造を Nyash 側で組み立て。 + - `PhiBuilderBox.emit_if_phi(pre_snapshot, then_snapshot, else_snapshot, control_form)` → 既存表に沿って PHI を配置。 + - このフェーズでは **実装はまだ書かず**、I/F と責務コメント、簡単な docs のみを `.hako` 側に置く。 + +### N‑D: Self‑Host 用ミニパイプラインの設計 + +- ファイル候補: + - `docs/development/runtime/cli-hakorune-stage1.md` + - `docs/development/architecture/mir-selfhost-pipeline.md`(新規) +- やること: + - Self‑Host MVP のパイプラインを定義する: + - Stage‑0 Rust CLI → Stage‑B (.hako) → Stage‑1 MirBuilderBox (.hako) → MIR(JSON) → VM 実行。 + - MVP では: + - 1〜2 の代表ケース(fib/defs, minimal_program)だけを対象とし、 + - `.hako` MirBuilder は Rust MirBuilder の完全互換ではなく「代表ケースに十分」な subset に留める。 + - これを Phase 25.2 以降の実装フェーズのターゲットとして書き切る。 + +## このフェーズで「やらない」こと + +- Rust MirBuilder 実装のロジック変更: + - 25.1n はあくまで「Rust 実装の挙動を SSOT として表+テストに落とす」フェーズであり、 + MirBuilder/LoopForm/IfForm/BodyLocalPhiBuilder のロジック変更は 26.x までに終わっていることを前提にする。 +- `.hako` MirBuilder 実装の本格実装: + - ここでは Box のシグネチャと責務、テスト用の I/F だけを決める。実装は 25.2/25.2b などのフェーズで段階的に行う。 +- GC や Region/RefSlotKind の統合: + - 25.1l の Region 観測レイヤーはあくまで Rust 側のみ。 + `.hako` 側 GC/寿命管理は別フェーズ(25.1m 以降)の仕事とし、MirBuilder Self‑Host とは分離する。 + +## 受け入れ条件(25.1n) + +- Docs: + - LoopForm/IfForm/BodyLocal/PhiBuilder について、SSA/PHI の挙動が表形式で整理されている(Rust コードを読まずに「この形ならどの PHI が立つか」が分かる)。 + - Self‑Host 用 MirBuilderBox / LoopFormBox / PhiBuilderBox の .hako 側 I/F が定義されている(未実装でも良い)。 +- テスト: + - Rust MirBuilder オラクルテストが 2〜3 本(LoopForm ケース / UsingResolver / Stage‑1 CLI minimal)追加され、安定して緑になっている。 + - これらのテストは「将来 .hako 実装と比較する」前提で、MIR 構造を固定する役割を持つ。 +- 実装範囲: + - Rust 側の MirBuilder ロジックには手を入れていない(設計とテストの “凍結フェーズ” として完了できている)。 + diff --git a/src/mir/loop_builder.rs b/src/mir/loop_builder.rs index 3d131e10..6d666eae 100644 --- a/src/mir/loop_builder.rs +++ b/src/mir/loop_builder.rs @@ -1147,9 +1147,23 @@ impl<'a> LoopBuilder<'a> { let mut ops = Ops(self); + // Phase 26-F: BodyLocalPhiBuilder setup for if-merge PHI filtering + // Purpose: Filter out BodyLocalInternal variables (defined in only some branches) + let inspector = crate::mir::phi_core::local_scope_inspector::LocalScopeInspectorBox::new(); + let classifier = crate::mir::phi_core::loop_var_classifier::LoopVarClassBox::new(); + let body_local_builder = + crate::mir::phi_core::body_local_phi_builder::BodyLocalPhiBuilder::new( + classifier, + inspector, + ); + // Phase 26-E: PhiBuilderBox SSOT統合(If PHI生成) // Legacy: merge_modified_with_control() → New: PhiBuilderBox::generate_phis() let mut phi_builder = crate::mir::phi_core::phi_builder_box::PhiBuilderBox::new(); + + // Phase 26-F: Set BodyLocal filter for PHI generation + phi_builder.set_body_local_filter(body_local_builder); + let post_snapshots = if let Some(ref else_map) = else_var_map_end_opt { vec![then_var_map_end.clone(), else_map.clone()] } else { diff --git a/src/mir/phi_core/body_local_phi_builder.rs b/src/mir/phi_core/body_local_phi_builder.rs index 447370b4..8ee2b12b 100644 --- a/src/mir/phi_core/body_local_phi_builder.rs +++ b/src/mir/phi_core/body_local_phi_builder.rs @@ -37,6 +37,7 @@ use crate::mir::BasicBlockId; /// // Generate exit PHI /// } /// ``` +#[derive(Clone)] pub struct BodyLocalPhiBuilder { /// Variable classifier classifier: LoopVarClassBox, @@ -151,6 +152,86 @@ impl BodyLocalPhiBuilder { .collect() } + /// Filter variables for if-merge PHI generation (Phase 26-F) + /// + /// # Purpose + /// IfForm body内のif-merge地点でPHI生成すべき変数をフィルタリング。 + /// BodyLocalInternal変数(一部ブランチでのみ定義)はPHI候補から除外。 + /// + /// # Arguments + /// * `pre_if` - if直前のvariable_map + /// * `then_end` - thenブランチ終端時のvariable_map + /// * `else_end_opt` - elseブランチ終端時のvariable_map(なければNone) + /// * `reachable_preds` - mergeに到達するpredブロック一覧(breakで終わるブランチは含めない) + /// + /// # Returns + /// PHI生成すべき変数名のリスト + /// + /// # Classification Logic + /// - Pinned: 常にPHI生成(ループ外から来る変数) + /// - Carrier: 常にPHI生成(ループ内で更新される変数) + /// - BodyLocalExit: 全ブランチで定義 → PHI生成 + /// - BodyLocalInternal: 一部ブランチでのみ定義 → PHI生成しない + /// + /// # Example + /// ```ignore + /// // if (cond) { ch = read() } else { /* ch未定義 */ } + /// // この場合、chはBodyLocalInternal → PHI候補から除外 + /// + /// use std::collections::BTreeMap; + /// let pre_if = BTreeMap::new(); + /// let mut then_end = BTreeMap::new(); + /// then_end.insert("ch".to_string(), ValueId(5)); + /// let else_end_opt = Some(BTreeMap::new()); // chなし + /// + /// let phi_vars = builder.filter_if_merge_candidates( + /// &pre_if, + /// &then_end, + /// &else_end_opt, + /// &[BasicBlockId(2), BasicBlockId(3)], + /// ); + /// // Result: [] - "ch"はBodyLocalInternalなので除外 + /// ``` + pub fn filter_if_merge_candidates( + &self, + pre_if: &std::collections::BTreeMap, + then_end: &std::collections::BTreeMap, + else_end_opt: &Option>, + reachable_preds: &[BasicBlockId], + ) -> Vec { + use std::collections::BTreeSet; + + // 1. 全ての変数名を収集(pre_if + then_end + else_end_opt) + let mut all_vars = BTreeSet::new(); + all_vars.extend(pre_if.keys().cloned()); + all_vars.extend(then_end.keys().cloned()); + if let Some(else_end) = else_end_opt { + all_vars.extend(else_end.keys().cloned()); + } + + // 2. 各変数を分類してBodyLocalInternal以外を残す + all_vars + .into_iter() + .filter(|var_name| { + // LoopVarClassBox::classify を使用 + // pinned_vars/carrier_varsは空(if内のローカル変数のみ対象) + let class = self.classifier.classify( + var_name, + &[], // pinned_vars(if-merge時は通常空) + &[], // carrier_vars(if-merge時は通常空) + &self.inspector, + reachable_preds, + ); + + // BodyLocalInternalはスキップ、それ以外はPHI生成 + match class { + LoopVarClass::BodyLocalInternal => false, + _ => true, + } + }) + .collect() + } + /// Get mutable reference to inspector /// /// # Purpose diff --git a/src/mir/phi_core/phi_builder_box.rs b/src/mir/phi_core/phi_builder_box.rs index 0a4a14bf..a693cc7f 100644 --- a/src/mir/phi_core/phi_builder_box.rs +++ b/src/mir/phi_core/phi_builder_box.rs @@ -55,11 +55,19 @@ pub struct PhiBuilderBox { loop_context: Option, } -/// If PHI生成コンテキスト(将来拡張用) -#[derive(Debug, Clone)] +/// If PHI生成コンテキスト(Phase 26-F拡張) +#[derive(Clone)] struct IfPhiContext { - /// 予約済み(Phase 2で実装) - _reserved: (), + /// Phase 26-F: BodyLocal変数フィルター(BodyLocalInternal除外用) + body_local_filter: Option, +} + +impl std::fmt::Debug for IfPhiContext { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("IfPhiContext") + .field("body_local_filter", &self.body_local_filter.is_some()) + .finish() + } } /// Loop PHI生成コンテキスト(将来拡張用) @@ -78,6 +86,30 @@ impl PhiBuilderBox { } } + /// Set BodyLocal filter for If PHI generation (Phase 26-F) + /// + /// # Purpose + /// If-merge PHI生成時にBodyLocalInternal変数をフィルタリング。 + /// BodyLocalPhiBuilderを使って、一部ブランチでのみ定義された変数を除外。 + /// + /// # Arguments + /// * `filter` - BodyLocalPhiBuilder instance for filtering + /// + /// # Usage + /// ```ignore + /// let mut phi_builder = PhiBuilderBox::new(); + /// phi_builder.set_body_local_filter(body_local_builder); + /// phi_builder.generate_phis(&mut ops, &form, &pre, &post)?; + /// ``` + pub fn set_body_local_filter( + &mut self, + filter: crate::mir::phi_core::body_local_phi_builder::BodyLocalPhiBuilder, + ) { + self.if_context = Some(IfPhiContext { + body_local_filter: Some(filter), + }); + } + /// ControlFormベースの統一PHI生成エントリーポイント /// /// # Arguments @@ -162,7 +194,12 @@ impl PhiBuilderBox { } // Compute modified variables (決定的順序: BTreeSet使用) - let modified_vars = self.compute_modified_names_if(pre_snapshot, then_end, &else_end_opt); + let modified_vars = self.compute_modified_names_if( + pre_snapshot, + then_end, + &else_end_opt, + if_shape, + ); for var_name in modified_vars { // Conservative strategy: get values with void fallback @@ -209,16 +246,24 @@ impl PhiBuilderBox { Ok(()) } - /// Compute modified variable names for If (決定的順序) + /// Compute modified variable names for If (決定的順序 + Phase 26-F filtering) + /// + /// # Arguments + /// * `pre_snapshot` - if直前の変数スナップショット + /// * `then_end` - thenブランチ終端の変数スナップショット + /// * `else_end_opt` - elseブランチ終端の変数スナップショット(なければNone) + /// * `if_shape` - IfShape(reachable_preds取得用) /// /// # Returns /// /// ソート済みの変更変数名リスト(BTreeSetにより決定的) + /// Phase 26-F: BodyLocalInternal変数はフィルタリングで除外 fn compute_modified_names_if( &self, pre_snapshot: &BTreeMap, then_end: &BTreeMap, else_end_opt: &Option<&BTreeMap>, + if_shape: &IfShape, ) -> Vec { use std::collections::BTreeSet; @@ -248,6 +293,39 @@ impl PhiBuilderBox { } } + // Phase 26-F: BodyLocalPhiBuilderフィルター適用 + if let Some(ref ctx) = self.if_context { + if let Some(ref filter) = ctx.body_local_filter { + // reachable_preds取得(then_block と else_block) + let mut reachable_preds = Vec::new(); + if let Some(then_block) = if_shape.then_block.into() { + reachable_preds.push(then_block); + } + if let Some(else_block) = if_shape.else_block { + reachable_preds.push(else_block); + } + + // else_end_optをOptionに変換 + let else_end_owned = else_end_opt.map(|m| m.clone()); + + // BodyLocalPhiBuilderでフィルタリング + changed = filter.filter_if_merge_candidates( + pre_snapshot, + then_end, + &else_end_owned, + &reachable_preds, + ); + + // Debug trace + if std::env::var("NYASH_IF_TRACE").ok().as_deref() == Some("1") { + eprintln!( + "[PhiBuilderBox/if] BodyLocal filtering applied, {} candidates", + changed.len() + ); + } + } + } + changed }