diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index c7cf2c68..24be836f 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -1,65 +1,51 @@ # Current Task -## 🚧 Phase 186: LoopBuilder Hard Freeze (In Progress) +## ✅ Phase 186: LoopBuilder Hard Freeze (Completed - 2025-12-04) -**Status**: 🟡 Design & Investigation Done → Code change pending +**Status**: ✅ **All Tasks Completed** → Phase 187 (Physical Removal) Ready -**Key Finding**: `cf_loop()` からの LoopBuilder 呼び出しにガードが無く、JoinIR にマッチしない全ループが自動的に LoopBuilder にフォールバックしている。JoinIR mainline の前提が満たされていない。 +**Key Achievement**: LoopBuilder は `NYASH_LEGACY_LOOPBUILDER=1` の明示 opt-in がない限り起動しない。代表パスは JoinIR-only が既定となり、silent fallback が排除された。 -### What Phase 186 Discovered +### What Was Fixed -- File: `src/mir/builder/control_flow.rs:45-62` - - `try_cf_loop_joinir(..)` で JoinIR を試した後、**環境変数チェック無しで**: - ```rust - let mut loop_builder = crate::mir::loop_builder::LoopBuilder::new(self); - loop_builder.build_loop(condition, body) - ``` - に落ちる。 -- Result: - - JoinIR ルートは `print_tokens/0` と `ArrayExtBox.filter/2` などごく一部だけ。 - - それ以外のループはすべて LoopBuilder で lowering される。 - - `NYASH_LEGACY_LOOPBUILDER` が存在するのに、一切参照されていない。 +- **Guard insertion at single instantiation point** + - File: `src/mir/builder/control_flow.rs:45-68` + - Behavior: + - JoinIR を試して失敗 → 直ちに `legacy_loopbuilder_enabled()` をチェック。 + - `NYASH_LEGACY_LOOPBUILDER` が無効なら、明示的なエラーを返して停止。 + - 有効なときだけ `LoopBuilder::new(self).build_loop(..)` を呼び出す。 -### Required Fix (for Claude Code) +- **NYASH_LEGACY_LOOPBUILDER semantics** + - Default: OFF(未設定 or 0) + - JoinIR 非対応ループ → エラー(Fail-Fast) + - 代表パスは JoinIR-only でテストされる。 + - ON(=1): + - JoinIR 非対応パターンのみ legacy LoopBuilder にフォールバック(dev/test 用)。 + - Design: Dev 専用、一時的トグル(Phase 187 で除去予定)。 -**Goal**: LoopBuilder は `NYASH_LEGACY_LOOPBUILDER=1` のときだけ有効。既定は JoinIR-only。 +### Verification (Representative Paths) -- Location: `src/mir/builder/control_flow.rs`(`cf_loop` の JoinIR fallback 部分) -- Desired pattern: - ```rust - // Phase 186: Add access control guard - if !crate::config::env::joinir_dev::legacy_loopbuilder_enabled() { - return Err(format!( - "Loop lowering failed: JoinIR does not support this loop pattern, and LoopBuilder is disabled.\n\ - Function: {}\n\ - Hint: Set NYASH_LEGACY_LOOPBUILDER=1 to enable legacy path (dev-only), \ - or implement JoinIR support for this function.", - self.current_function - .as_ref() - .map(|f| f.signature.name.as_str()) - .unwrap_or("") - )); - } - let mut loop_builder = crate::mir::loop_builder::LoopBuilder::new(self); - loop_builder.build_loop(condition, body) - ``` +- JoinIR-only(`NYASH_LEGACY_LOOPBUILDER=0` or unset): + - selfhost 代表ループ: ✅ JoinIR で PASS + - If lowering テスト: ✅ JoinIR 経路のみで PASS(Phase 184/185 済み) +- Legacy mode(`NYASH_LEGACY_LOOPBUILDER=1`): + - JoinIR 非対応パターン: ✅ LoopBuilder fallback により実行可能(dev モードのみ) -### Phase 186 Tasks +### Documentation -1. **Guard insertion** - - Add `legacy_loopbuilder_enabled()` guard before LoopBuilder instantiation in `cf_loop`. - - Ensure error message is actionable (function名と env hint を含む)。 +- `docs/private/roadmap2/phases/phase-180-joinir-unification-before-selfhost/README.md` + - 新たに「6. Phase 186: LoopBuilder Hard Freeze」を追加。 + - アクセス制御ポリシー/検証結果/アーキテクチャ影響を詳細に記録。 +- `docs/private/roadmap2/phases/phase-186-loopbuilder-freeze/README.md` + - Status を Completed に更新。 + - 初期の「問題報告」と、修正後の制御フローを両方残し、経緯を参照できるようにした。 -2. **Representative path re-check** - - With `NYASH_LEGACY_LOOPBUILDER` unset/0: - - `loop_min_while.hako` 他 Phase 181 の代表 loop テストが JoinIR-only で通ること。 - - With `NYASH_LEGACY_LOOPBUILDER=1`: - - 未対応パターンがあっても LoopBuilder fallback で動くこと。 +### Next Step: Phase 187 -3. **Docs update** - - `phase-186-loopbuilder-freeze/README.md` に「ガード挿入済み」を追記(完了後)。 - - `phase-186-loopbuilder-freeze/test-results.md` に再検証結果を追加。 - - 本ファイルの Phase 186 セクションを Completed に更新。 +- 物理削除フェーズ(LoopBuilder モジュール削除)の entry 条件が揃った: + - 代表パスは JoinIR-only でグリーン。 + - LoopBuilder は明示 opt-in がない限り決して呼ばれない。 + - docs 上も LoopBuilder = legacy/dev-only と明記済み。 --- diff --git a/src/mir/builder/control_flow.rs b/src/mir/builder/control_flow.rs index 67b4995e..d805474d 100644 --- a/src/mir/builder/control_flow.rs +++ b/src/mir/builder/control_flow.rs @@ -57,19 +57,14 @@ impl super::MirBuilder { eprintln!("[cf_loop] Current stack (simulated): check build_statement vs build_expression_impl"); } - // Phase 186: LoopBuilder Hard Freeze - Require explicit legacy mode opt-in - if !crate::config::env::joinir_dev::legacy_loopbuilder_enabled() { - return Err(format!( - "[joinir/freeze] Loop lowering failed: JoinIR does not support this pattern, and LoopBuilder is disabled.\n\ - Function: {}\n\ - Hint: Set NYASH_LEGACY_LOOPBUILDER=1 to enable legacy path (dev-only)", - self.current_function.as_ref().map(|f| f.signature.name.as_str()).unwrap_or("") - )); - } - - // Delegate to LoopBuilder for consistent handling (legacy path only) - let mut loop_builder = crate::mir::loop_builder::LoopBuilder::new(self); - loop_builder.build_loop(condition, body) + // Phase 186: LoopBuilder Hard Freeze - Legacy path disabled + // Phase 187-2: LoopBuilder module removed - all loops must use JoinIR + return Err(format!( + "[joinir/freeze] Loop lowering failed: JoinIR does not support this pattern, and LoopBuilder has been removed.\n\ + Function: {}\n\ + Hint: This loop pattern is not supported. All loops must use JoinIR lowering.", + self.current_function.as_ref().map(|f| f.signature.name.as_str()).unwrap_or("") + )); } /// Phase 49: Try JoinIR Frontend for mainline integration diff --git a/src/mir/builder/if_form.rs b/src/mir/builder/if_form.rs index 868d4969..444f1e19 100644 --- a/src/mir/builder/if_form.rs +++ b/src/mir/builder/if_form.rs @@ -280,7 +280,7 @@ impl MirBuilder { // PHI 生成 let phi_count = { let mut ops = ToplevelOps(self); - crate::mir::loop_builder::IfInLoopPhiEmitter::emit_toplevel_phis( + crate::mir::if_in_loop_phi::IfInLoopPhiEmitter::emit_toplevel_phis( &phi_spec, &pre_if_var_map, &then_var_map_end, diff --git a/src/mir/loop_builder/if_in_loop_phi_emitter.rs b/src/mir/if_in_loop_phi/mod.rs similarity index 100% rename from src/mir/loop_builder/if_in_loop_phi_emitter.rs rename to src/mir/if_in_loop_phi/mod.rs diff --git a/src/mir/loop_builder/README.md b/src/mir/loop_builder/README.md deleted file mode 100644 index bb4adc82..00000000 --- a/src/mir/loop_builder/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# loop_builder - -SSA loop lowering for LoopForm v2. This module owns the block layout (preheader/header/body/latch/continue_merge/exit) and delegates PHI construction to `phi_core`. - -## Boundaries -- Handles loop CFG + variable snapshots only; no name解決やコード生成 beyond MIR emission. -- Uses `phi_core` boxes for PHI wiring; avoid duplicating PHI logic here. -- Debug/experimental flags remain centralized in `loop_form.rs`. - -## Submodules -- `control.rs`: break/continue capture + predecessor bookkeeping -- `loop_form.rs`: main loop lowering pipeline -- `statements.rs`: loop-body statement lowering entry point -- `if_lowering.rs`: in-loop `if` lowering with JoinIR/PHI bridge -- `phi_ops.rs`: PHI emit helpers + LoopFormOps/PhiBuilderOps impls diff --git a/src/mir/loop_builder/control.rs b/src/mir/loop_builder/control.rs deleted file mode 100644 index e50f8cc1..00000000 --- a/src/mir/loop_builder/control.rs +++ /dev/null @@ -1,94 +0,0 @@ -use super::{ConstValue, LoopBuilder, ValueId}; -use crate::mir::BasicBlockId; -use crate::runtime::get_global_ring0; - -/// ループ脱出の種類(箱化・共通化のための型) -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(super) enum LoopExitKind { - /// break文(exit blockへジャンプ) - Break, - /// continue文(header blockへジャンプ) - Continue, -} - -impl<'a> LoopBuilder<'a> { - /// Emit a jump to `target` from the current block and record predecessor metadata. - fn jump_with_pred(&mut self, target: BasicBlockId) -> Result<(), String> { - let cur_block = self.current_block()?; - self.emit_jump(target)?; - let _ = crate::mir::builder::loops::add_predecessor(self.parent_builder, target, cur_block); - Ok(()) - } - - /// [LoopForm] 【箱化】ループ脱出の共通処理(break/continue統一化) - /// - /// Phase 25.1o: break と continue の共通パターンを抽出し、 - /// LoopExitKind で振る舞いを切り替える統一メソッド。 - /// - /// # 処理フロー - /// 1. 現在の変数マップをスナップショット - /// 2. [LoopForm] スナップショット保存(Break → exit_snapshots, Continue → continue_snapshots) - /// 3. Void を定義(戻り値用のダミー) - /// 4. [LoopForm] ターゲットブロックへジャンプ(Break → exit, Continue → header/continue_merge) - /// 5. 現在ブロックはジャンプで終端済みのまま維持(新しい unreachable ブロックは作らない) - fn do_loop_exit(&mut self, kind: LoopExitKind) -> Result { - // 1. スナップショット取得(共通処理) - let snapshot = self.get_current_variable_map(); - let cur_block = self.current_block()?; - - // 2. [LoopForm] exit-break path: スナップショット保存(exit PHI入力用) - // [LoopForm] continue-backedge path: スナップショット保存(continue_merge → header) - match kind { - LoopExitKind::Break => { - if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - get_global_ring0().log.debug(&format!( - "[DEBUG/do_break] Saved snapshot from block {:?}, vars: {:?}", - cur_block, - snapshot.keys().collect::>() - )); - } - self.exit_snapshots.push((cur_block, snapshot)); - } - LoopExitKind::Continue => { - self.block_var_maps.insert(cur_block, snapshot.clone()); - self.continue_snapshots.push((cur_block, snapshot)); - } - } - - // 3. 戻り値用のダミーを定義(現在ブロック内で完結させる) - let void_id = self.new_value(); - self.emit_const(void_id, ConstValue::Void)?; - - // 4. ターゲットブロックへジャンプ(kind別処理) - match kind { - LoopExitKind::Break => { - if let Some(exit_bb) = crate::mir::builder::loops::current_exit(self.parent_builder) - { - self.jump_with_pred(exit_bb)?; - } - } - LoopExitKind::Continue => { - // 既定では header にジャンプするが、canonical continue merge を導入した場合は - // continue_target 側を優先する。 - if let Some(target) = self.continue_target.or(self.loop_header) { - self.jump_with_pred(target)?; - } - } - } - - // 5. 現在ブロックは jump で終端済み。新しい unreachable ブロックは作らない。 - Ok(void_id) - } - - /// Handle a `break` statement: jump to loop exit and continue in a fresh unreachable block. - /// 【箱化】do_loop_exit() への thin wrapper - pub(super) fn do_break(&mut self) -> Result { - self.do_loop_exit(LoopExitKind::Break) - } - - /// Handle a `continue` statement: snapshot vars, jump to loop header, then continue in a fresh unreachable block. - /// 【箱化】do_loop_exit() への thin wrapper - pub(super) fn do_continue(&mut self) -> Result { - self.do_loop_exit(LoopExitKind::Continue) - } -} diff --git a/src/mir/loop_builder/if_lowering.rs b/src/mir/loop_builder/if_lowering.rs deleted file mode 100644 index 181b6083..00000000 --- a/src/mir/loop_builder/if_lowering.rs +++ /dev/null @@ -1,298 +0,0 @@ -use super::{LoopBuilder, ValueId}; -use crate::ast::ASTNode; -use crate::mir::control_form::{is_control_form_trace_on, ControlForm, IfShape}; -use crate::mir::utils::{capture_actual_predecessor_and_jump, is_current_block_terminated}; -use crate::mir::{BasicBlockId, ConstValue}; -use std::collections::{BTreeMap, BTreeSet}; - -impl<'a> LoopBuilder<'a> { - /// Lower an if-statement inside a loop, preserving continue/break semantics and emitting PHIs per assigned variable. - pub(super) fn lower_if_in_loop( - &mut self, - condition: ASTNode, - then_body: Vec, - else_body: Option>, - ) -> Result { - // Reserve a deterministic join id for debug region labeling (nested inside loop) - let join_id = self.parent_builder.debug_next_join_id(); - // Pre-pin heuristic was deprecated; leave operands untouched for clarity. - // Evaluate condition and create blocks - let cond_val = self.parent_builder.build_expression(condition)?; - let then_bb = self.new_block(); - let else_bb = self.new_block(); - let merge_bb = self.new_block(); - let pre_branch_bb = self.current_block()?; - self.emit_branch(cond_val, then_bb, else_bb)?; - - // Capture pre-if variable map (used for phi normalization) - let pre_if_var_map = self.get_current_variable_map(); - let trace_if = std::env::var("NYASH_IF_TRACE").ok().as_deref() == Some("1"); - // (legacy) kept for earlier merge style; now unified helpers compute deltas directly. - - // then branch - self.set_current_block(then_bb)?; - // Debug region: join then-branch (inside loop) - self.parent_builder - .debug_push_region(format!("join#{}", join_id) + "/then"); - // Materialize all variables at entry via single-pred Phi (correctness-first) - let names_then: Vec = self - .parent_builder - .variable_map - .keys() - .filter(|n| !n.starts_with("__pin$")) - .cloned() - .collect(); - for name in names_then { - if let Some(&pre_v) = pre_if_var_map.get(&name) { - let phi_val = self.new_value(); - self.emit_phi_at_block_start(then_bb, phi_val, vec![(pre_branch_bb, pre_v)])?; - let name_for_log = name.clone(); - self.update_variable(name, phi_val); - if trace_if { - eprintln!( - "[if-trace] then-entry phi var={} pre={:?} -> dst={:?}", - name_for_log, pre_v, phi_val - ); - } - } - } - for s in then_body.iter().cloned() { - let _ = self.build_statement(s)?; - // フェーズS修正:統一終端検出ユーティリティ使用 - if is_current_block_terminated(self.parent_builder)? { - break; - } - } - let then_var_map_end = self.get_current_variable_map(); - // フェーズS修正:最強モード指摘の「実到達predecessor捕捉」を統一 - let _then_pred_to_merge = - capture_actual_predecessor_and_jump(self.parent_builder, merge_bb)?; - // Pop then-branch debug region - self.parent_builder.debug_pop_region(); - - // else branch - self.set_current_block(else_bb)?; - // Debug region: join else-branch (inside loop) - self.parent_builder - .debug_push_region(format!("join#{}", join_id) + "/else"); - // Materialize all variables at entry via single-pred Phi (correctness-first) - let names2: Vec = self - .parent_builder - .variable_map - .keys() - .filter(|n| !n.starts_with("__pin$")) - .cloned() - .collect(); - for name in names2 { - if let Some(&pre_v) = pre_if_var_map.get(&name) { - let phi_val = self.new_value(); - self.emit_phi_at_block_start(else_bb, phi_val, vec![(pre_branch_bb, pre_v)])?; - let name_for_log = name.clone(); - self.update_variable(name, phi_val); - if trace_if { - eprintln!( - "[if-trace] else-entry phi var={} pre={:?} -> dst={:?}", - name_for_log, pre_v, phi_val - ); - } - } - } - let mut else_var_map_end_opt: Option> = None; - if let Some(es) = else_body.clone() { - for s in es.into_iter() { - let _ = self.build_statement(s)?; - // フェーズS修正:統一終端検出ユーティリティ使用 - if is_current_block_terminated(self.parent_builder)? { - break; - } - } - else_var_map_end_opt = Some(self.get_current_variable_map()); - } - // フェーズS修正:else branchでも統一実到達predecessor捕捉 - let _else_pred_to_merge = - capture_actual_predecessor_and_jump(self.parent_builder, merge_bb)?; - // Pop else-branch debug region - self.parent_builder.debug_pop_region(); - - // Continue at merge - self.set_current_block(merge_bb)?; - // Debug region: join merge (inside loop) - self.parent_builder - .debug_push_region(format!("join#{}", join_id) + "/join"); - - // Phase 25.1: HashSet → BTreeSet(決定性確保) - // Phase 40-4.1: JoinIR経路をデフォルト化(collect_assigned_vars削除) - // Phase 84-5: if_phi.rs 削除 → test_utils に移動 - let _vars: BTreeSet = - crate::mir::phi_core::test_utils::collect_assigned_vars_via_joinir( - &then_body, - else_body.as_ref(), - ); - - // Phase 26-E: PhiBuilderOps trait 実装(箱理論統一) - struct Ops<'b, 'a>(&'b mut LoopBuilder<'a>); - - // Phase 26-E: PhiBuilderOps trait 実装(箱理論統一) - impl<'b, 'a> crate::mir::phi_core::phi_builder_box::PhiBuilderOps for Ops<'b, 'a> { - fn new_value(&mut self) -> ValueId { - self.0.new_value() - } - fn emit_phi( - &mut self, - block: BasicBlockId, - dst: ValueId, - inputs: Vec<(BasicBlockId, ValueId)>, - ) -> Result<(), String> { - self.0.emit_phi_at_block_start(block, dst, inputs) - } - fn update_var(&mut self, name: String, value: ValueId) { - self.0.parent_builder.variable_map.insert(name, value); - } - fn get_block_predecessors(&self, block: BasicBlockId) -> Vec { - if let Some(ref func) = self.0.parent_builder.current_function { - func.blocks - .get(&block) - .map(|bb| bb.predecessors.iter().copied().collect()) - .unwrap_or_default() - } else { - Vec::new() - } - } - fn emit_void(&mut self) -> ValueId { - let void_id = self.0.new_value(); - let _ = self.0.emit_const(void_id, ConstValue::Void); - void_id - } - - // Phase 3-A: Loop PHI生成用メソッド実装 - fn set_current_block(&mut self, block: BasicBlockId) -> Result<(), String> { - self.0.parent_builder.current_block = Some(block); - Ok(()) - } - - fn block_exists(&self, block: BasicBlockId) -> bool { - if let Some(ref func) = self.0.parent_builder.current_function { - func.blocks.contains_key(&block) - } else { - false - } - } - } - - // Phase 25.1h: ControlForm統合版に切り替え - let if_shape = IfShape { - cond_block: pre_branch_bb, - then_block: then_bb, - else_block: Some(else_bb), - merge_block: merge_bb, - }; - let form = ControlForm::from_if(if_shape.clone()); - - // Region/GC 観測レイヤ(Phase 25.1l): - // NYASH_REGION_TRACE=1 のときだけ、Stage‑B 周辺 If 構造の - // Region 情報(entry/exit/slots)をログに出すよ。 - crate::mir::region::observer::observe_control_form(self.parent_builder, &form); - - // Phase 61-1: If-in-loop JoinIR化(開発フラグ制御) - // carrier_namesを作成(両経路で共通) - let carrier_names: BTreeSet = pre_if_var_map - .keys() - .filter(|name| !name.starts_with("__pin$")) // 一時変数除外 - .cloned() - .collect(); - - // Phase 62-B: JoinIRIfPhiSelector箱化(-60行の簡潔化) - let joinir_result = if crate::config::env::joinir_if_select_enabled() { - if let Some(ref func) = self.parent_builder.current_function { - let selector = - super::JoinIRIfPhiSelector::new(func, pre_branch_bb, carrier_names.clone()); - selector.try_lower() - } else { - super::JoinIRResult { - success: false, - phi_spec: None, - join_inst: None, - } - } - } else { - super::JoinIRResult { - success: false, - phi_spec: None, - join_inst: None, - } - }; - - let joinir_success = joinir_result.success; - let joinir_phi_spec_opt = joinir_result.phi_spec; - - let mut ops = Ops(self); - - // Phase 61-3: JoinIR本番経路(IfInLoopPhiEmitter) - if joinir_success { - if let Some(ref joinir_spec) = joinir_phi_spec_opt { - // IfInLoopPhiEmitter を使用してPHI生成 - let else_snap_opt = else_var_map_end_opt.as_ref(); - let phi_count = super::IfInLoopPhiEmitter::emit_header_phis( - joinir_spec, - &pre_if_var_map, - &then_var_map_end, - else_snap_opt, - &carrier_names, - &mut ops, - &if_shape, - )?; - - if crate::config::env::joinir_if_in_loop_dryrun_enabled() { - eprintln!( - "[Phase 61-3] ✅ IfInLoopPhiEmitter generated {} PHIs", - phi_count - ); - } - } - } - - // フォールバック: PhiBuilderBox経路(既存) - if !joinir_success { - // Phase 26-E: PhiBuilderBox SSOT統合(If PHI生成) - // Legacy: merge_modified_with_control() → New: PhiBuilderBox::generate_phis() - // Phase 61-6.1: set_if_context削除、直接IfPhiContextを生成 - let mut phi_builder = crate::mir::phi_core::phi_builder_box::PhiBuilderBox::new(); - phi_builder.if_context = Some(crate::mir::phi_core::phi_builder_box::IfPhiContext { - in_loop_body: true, - loop_carrier_names: carrier_names.clone(), - }); - - // Phase 35-5: if_body_local_merge.rs削除、ロジックはPhiBuilderBox内に統合 - let post_snapshots = if let Some(ref else_map) = else_var_map_end_opt { - vec![then_var_map_end.clone(), else_map.clone()] - } else { - vec![then_var_map_end.clone()] - }; - phi_builder.generate_phis(&mut ops, &form, &pre_if_var_map, &post_snapshots)?; - - // Phase 61-6.2: A/B比較削除(JoinIR経路完全動作確認済み) - // Phase 61-3でJoinIR経路が完全動作したため、観察コード削除 - // SSOT: JoinIR → compute_phi_spec_from_joinir() - } - - // Phase 26-E-4: PHI生成後に variable_map をリセット(ChatGPT/Task先生指示) - // 理由: else_var_map_end_opt が正しい snapshot を保持したまま PHI 生成に渡す必要がある - // 修正前: PHI生成前にリセット → else ブロック内定義変数が消失 → domination error - // 修正後: PHI生成後にリセット → 正しいPHI入力 → SSA保証 - self.parent_builder.variable_map = pre_if_var_map.clone(); - - // ControlForm 観測: 環境フラグ(未設定時は既定ON)のとき IfShape をダンプ - if is_control_form_trace_on() { - form.debug_dump(); - #[cfg(debug_assertions)] - if let Some(ref func) = self.parent_builder.current_function { - if_shape.debug_validate(func); - } - } - let void_id = self.new_value(); - self.emit_const(void_id, ConstValue::Void)?; - // Pop merge debug region - self.parent_builder.debug_pop_region(); - Ok(void_id) - } -} diff --git a/src/mir/loop_builder/joinir_if_phi_selector.rs b/src/mir/loop_builder/joinir_if_phi_selector.rs deleted file mode 100644 index e8c7bee3..00000000 --- a/src/mir/loop_builder/joinir_if_phi_selector.rs +++ /dev/null @@ -1,168 +0,0 @@ -//! Phase 62-B: JoinIR If-PHI Selector -//! -//! If-in-loop の JoinIR lowering 試行と PhiSpec 生成を担当する箱。 -//! -//! ## 箱理論における位置づけ -//! -//! ```text -//! ┌─────────────────────────────────────────────────────────────┐ -//! │ if_lowering.rs(オーケストレーター) │ -//! │ ↓ │ -//! │ ┌─────────────────────┐ ┌─────────────────────────────┐ │ -//! │ │ JoinIRIfPhiSelector │→ │ IfInLoopPhiEmitter │ │ -//! │ │ (試行・PhiSpec生成) │ │ (PHI命令発行) │ │ -//! │ └─────────────────────┘ └─────────────────────────────┘ │ -//! └─────────────────────────────────────────────────────────────┘ -//! ``` -//! -//! ## 責務 -//! -//! - JoinIR lowering の試行(try_lower_if_to_joinir 呼び出し) -//! - PhiSpec の計算(compute_phi_spec_from_joinir 呼び出し) -//! - dry-run モード時のログ出力 -//! - 本番/dry-run 切り替え判定 -//! -//! ## 設計原則 -//! -//! - **Thin Box**: JoinIR/PhiSpec計算は既存関数に委譲 -//! - **状態保持**: 試行結果とPhiSpecを返却 -//! - **ログ制御**: dry-runフラグに応じた詳細ログ - -use crate::mir::join_ir::lowering::if_phi_context::IfPhiContext; -use crate::mir::join_ir::lowering::if_phi_spec::{compute_phi_spec_from_joinir, PhiSpec}; -use crate::mir::join_ir::lowering::try_lower_if_to_joinir; -use crate::mir::join_ir::JoinInst; -use crate::mir::{BasicBlockId, MirFunction}; -use crate::runtime::get_global_ring0; -use std::collections::BTreeSet; - -/// JoinIR If-PHI Selector の試行結果 -#[derive(Debug)] -pub struct JoinIRResult { - /// JoinIR lowering 成功フラグ - pub success: bool, - - /// 計算された PhiSpec(成功時のみ) - pub phi_spec: Option, - - /// lowering された JoinInst(デバッグ用) - pub join_inst: Option, -} - -/// JoinIR If-PHI Selector -/// -/// If-in-loop の JoinIR 経路を試行し、PhiSpec を生成する。 -pub struct JoinIRIfPhiSelector<'a> { - /// If-PHI コンテキスト - context: IfPhiContext, - - /// 現在の MIR 関数 - func: &'a MirFunction, - - /// 分岐前ブロック - pre_branch_bb: BasicBlockId, - - /// dry-run モード - dryrun: bool, -} - -impl<'a> JoinIRIfPhiSelector<'a> { - /// JoinIR If-PHI Selector を作成 - /// - /// # Arguments - /// - /// * `func` - 現在の MIR 関数 - /// * `pre_branch_bb` - 分岐前ブロック - /// * `carrier_names` - ループキャリア変数名 - pub fn new( - func: &'a MirFunction, - pre_branch_bb: BasicBlockId, - carrier_names: BTreeSet, - ) -> Self { - let context = IfPhiContext::for_loop_body(carrier_names); - let dryrun = crate::config::env::joinir_dev_enabled() - && crate::config::env::joinir_if_in_loop_dryrun_enabled(); - - Self { - context, - func, - pre_branch_bb, - dryrun, - } - } - - /// JoinIR lowering を試行 - /// - /// # Returns - /// - /// JoinIR 試行結果(success, phi_spec, join_inst) - pub fn try_lower(&self) -> JoinIRResult { - // JoinIR lowering 試行 - match try_lower_if_to_joinir(self.func, self.pre_branch_bb, false, Some(&self.context)) { - Some(join_inst) => { - if self.dryrun { - get_global_ring0() - .log - .debug(&format!("[Phase 61-2] ✅ If-in-loop lowered via JoinIR: {:?}", join_inst)); - } - - // PhiSpec 計算 - let phi_spec = compute_phi_spec_from_joinir(&self.context, &join_inst); - - // dry-run ログ - if self.dryrun { - self.log_dryrun(&join_inst, &phi_spec); - } - - // 本番経路有効判定(Core + フラグ) - let success = crate::config::env::joinir_core_enabled() - && crate::config::env::joinir_if_in_loop_enable(); - - JoinIRResult { - success, - phi_spec: Some(phi_spec), - join_inst: Some(join_inst), - } - } - None => { - if self.dryrun { - get_global_ring0() - .log - .debug("[Phase 61-2] ⏭️ JoinIR pattern not matched, using fallback"); - } - - JoinIRResult { - success: false, - phi_spec: None, - join_inst: None, - } - } - } - } - - /// dry-run モード時の詳細ログ出力 - fn log_dryrun(&self, join_inst: &JoinInst, phi_spec: &PhiSpec) { - get_global_ring0() - .log - .debug("[Phase 61-2] 🔍 dry-run mode enabled"); - get_global_ring0().log.debug(&format!( - "[Phase 61-2] Carrier variables: {:?}", - self.context.carrier_names - )); - get_global_ring0().log.debug(&format!( - "[Phase 61-2] JoinInst type: {}", - match join_inst { - JoinInst::Select { .. } => "Select", - JoinInst::IfMerge { .. } => "IfMerge", - _ => "Other", - } - )); - get_global_ring0().log.debug(&format!( - "[Phase 61-2] JoinIR PhiSpec: header={}, exit={}", - phi_spec.header_count(), - phi_spec.exit_count() - )); - } -} - -// テストは if_lowering.rs の統合テストで検証 diff --git a/src/mir/loop_builder/loop_form.rs b/src/mir/loop_builder/loop_form.rs deleted file mode 100644 index 205fb904..00000000 --- a/src/mir/loop_builder/loop_form.rs +++ /dev/null @@ -1,578 +0,0 @@ -use super::{ConstValue, LoopBuilder, ValueId}; -use crate::ast::ASTNode; -use crate::mir::control_form::{is_control_form_trace_on, ControlForm, LoopShape}; -use crate::mir::phi_core::loopform_builder::{LoopFormBuilder, LoopFormOps}; -use crate::mir::utils::is_current_block_terminated; -use crate::mir::{BasicBlockId, MirInstruction}; -use std::collections::{BTreeMap, BTreeSet}; - -impl<'a> LoopBuilder<'a> { - /// SSA形式でループを構築 (LoopForm v2 only) - pub fn build_loop( - &mut self, - condition: ASTNode, - body: Vec, - ) -> Result { - // Phase 7-F: Legacy loop builder removed - LoopForm v2 is now the only implementation - self.build_loop_with_loopform(condition, body) - } - - /// SSA形式でループを構築 (LoopFormBuilder implementation) - fn build_loop_with_loopform( - &mut self, - condition: ASTNode, - body: Vec, - ) -> Result { - if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - eprintln!("[build_loop_with_loopform] === ENTRY ==="); - if let Some(ref func) = self.parent_builder.current_function { - eprintln!( - "[build_loop_with_loopform] fn='{}', counter={}, func_ptr={:p}", - func.signature.name, func.next_value_id, func as *const _ - ); - } - eprintln!("[build_loop_with_loopform] condition={:?}", condition); - eprintln!("[build_loop_with_loopform] body.len()={}", body.len()); - } - // Create loop structure blocks following LLVM canonical form - // We need a dedicated preheader block to materialize loop entry copies - let before_loop_id = self.current_block()?; - - // Capture variable snapshot BEFORE creating new blocks (at loop entry point) - let current_vars = self.get_current_variable_map(); - - // DEBUG: Show variable map before guard check - if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - crate::runtime::get_global_ring0().log.debug(&format!( - "[loopform] before_loop_id={:?}, variable_map size={}", - before_loop_id, - current_vars.len() - )); - for (name, value) in ¤t_vars { - crate::runtime::get_global_ring0().log.debug(&format!(" {} -> {:?}", name, value)); - } - } - - // Phase 25.3: GUARD check removed - ValueId(0) is valid for first parameters - // Previous code incorrectly assumed ValueId(0) always meant uninitialized variables, - // but it's actually the correct ID for the first parameter in functions like: - // skip_whitespace(s, idx) -> s=ValueId(0), idx=ValueId(1) - // This caused loops in such functions to be entirely skipped. - - let preheader_id = self.new_block(); - let header_id = self.new_block(); - let body_id = self.new_block(); - let latch_id = self.new_block(); - let exit_id = self.new_block(); - // Phase 25.1q: canonical continue merge block - // All continue 文は一度このブロックに集約してから header へ戻る。 - let continue_merge_id = self.new_block(); - - // Jump from current block to preheader - let entry_block = self.current_block()?; - self.emit_jump(preheader_id)?; - // 📦 Hotfix 6: Add CFG predecessor for preheader (same as legacy version) - crate::mir::builder::loops::add_predecessor( - self.parent_builder, - preheader_id, - entry_block, - )?; - - // Initialize LoopFormBuilder with preheader and header blocks - let mut loopform = LoopFormBuilder::new(preheader_id, header_id); - - // Pass 1: Prepare structure (allocate all ValueIds upfront) - if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - crate::runtime::get_global_ring0().log.debug(&format!( - "[loopform] Block IDs: preheader={:?}, header={:?}, body={:?}, latch={:?}, exit={:?}", - preheader_id, header_id, body_id, latch_id, exit_id - )); - crate::runtime::get_global_ring0().log.debug(&format!( - "[loopform] variable_map at loop entry (size={}):", - current_vars.len() - )); - let mut loop_count = 0; - for (name, value) in ¤t_vars { - loop_count += 1; - crate::runtime::get_global_ring0().log.debug(&format!(" [{}] {} -> {:?}", loop_count, name, value)); - // Phase 26-A-4: ValueIdベース判定に変更(名前ベース → 型安全) - let is_param = self.is_parameter(*value); - crate::runtime::get_global_ring0().log.debug(&format!(" param={}", is_param)); - } - crate::runtime::get_global_ring0().log.debug(&format!("[loopform] iterated {} times", loop_count)); - if let Some(ref func) = self.parent_builder.current_function { - crate::runtime::get_global_ring0().log.debug(&format!( - "[loopform] BEFORE prepare_structure: fn='{}', counter={}, func_ptr={:p}", - func.signature.name, func.next_value_id, func as *const _ - )); - } else { - crate::runtime::get_global_ring0().log.debug("[loopform] BEFORE prepare_structure: current_function=None"); - } - } - loopform.prepare_structure(self, ¤t_vars)?; - if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - if let Some(ref func) = self.parent_builder.current_function { - crate::runtime::get_global_ring0().log.debug(&format!( - "[loopform] AFTER prepare_structure: fn='{}', counter={}, func_ptr={:p}", - func.signature.name, func.next_value_id, func as *const _ - )); - } else { - crate::runtime::get_global_ring0().log.debug("[loopform] AFTER prepare_structure: current_function=None"); - } - } - - // Pass 2: Emit preheader (copies and jump to header) - loopform.emit_preheader(self)?; - // 📦 Hotfix 6: Add CFG predecessor for header from preheader (same as legacy version) - crate::mir::builder::loops::add_predecessor(self.parent_builder, header_id, preheader_id)?; - - // Pass 3: Emit header PHIs (incomplete, only preheader edge) - self.set_current_block(header_id)?; - - // Ensure header block exists before emitting PHIs - self.parent_builder.ensure_block_exists(header_id)?; - - // Phase 27.4-C Refactor: JoinIR Loop φ バイパスフラグ統一取得 - let fn_name = self - .parent_builder - .current_function - .as_ref() - .map(|f| f.signature.name.clone()) - .unwrap_or_default(); - - let bypass_flags = crate::mir::phi_core::loopform_builder::get_loop_bypass_flags(&fn_name); - - if bypass_flags.header { - // Phase 27.4-C: JoinIR 実験経路では Header φ を生成しない。 - // Pinned/Carrier の値は preheader の copy をそのまま使う。 - // - // ⚠️ 重要: このモードでは MIR は不完全(φ 抜け)であり、VM で実行できない。 - // JoinIR runner 専用モードであることに注意。 - if crate::mir::phi_core::loopform_builder::is_loopform_debug_enabled() { - crate::runtime::get_global_ring0().log.debug(&format!("[loopform/27.4-C] Header φ bypass active for: {}", fn_name)); - crate::runtime::get_global_ring0().log.debug("[loopform/27.4-C] Skipping emit_header_phis() - using preheader values directly"); - } - } else { - // 従来どおり HeaderPhiBuilder を使って φ を準備 - loopform.emit_header_phis(self)?; - } - - if crate::mir::phi_core::loopform_builder::is_loopform_debug_enabled() { - crate::runtime::get_global_ring0().log.debug("[loopform] variable_map after emit_header_phis:"); - for (name, value) in self.get_current_variable_map().iter() { - crate::runtime::get_global_ring0().log.debug(&format!(" {} -> {:?}", name, value)); - } - } - - // Set up loop context for break/continue - crate::mir::builder::loops::push_loop_context(self.parent_builder, header_id, exit_id); - self.loop_header = Some(header_id); - // 既定の continue 先を canonical continue_merge ブロックにする。 - // ここを差し替えることで do_loop_exit(Continue) のターゲットを一元化する。 - self.continue_target = Some(continue_merge_id); - self.continue_snapshots.clear(); - self.exit_snapshots.clear(); - - // [LoopForm] header-cond: cond true → body, false → exit (Case A/B) - // - Case A: loop(i < n) → header can branch to exit directly - // - Case B: loop(1 == 1) → header always enters body, exit only via break - if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - crate::runtime::get_global_ring0().log.debug(&format!( - "[loopform/condition] BEFORE build_expression: current_block={:?}", - self.current_block()? - )); - if let Some(ref func) = self.parent_builder.current_function { - crate::runtime::get_global_ring0().log.debug(&format!( - "[loopform/condition] BEFORE: fn='{}', counter={}, func_ptr={:p}", - func.signature.name, func.next_value_id, func as *const _ - )); - } - } - let cond_value = self.parent_builder.build_expression(condition)?; - // Capture the ACTUAL block that emits the branch (might differ from header_id - // if build_expression created new blocks) - let branch_source_block = self.current_block()?; - if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - crate::runtime::get_global_ring0().log.debug(&format!( - "[loopform/condition] AFTER build_expression: branch_source_block={:?}", - branch_source_block - )); - if let Some(ref func) = self.parent_builder.current_function { - crate::runtime::get_global_ring0().log.debug(&format!( - "[loopform/condition] AFTER: fn='{}', counter={}, func_ptr={:p}", - func.signature.name, func.next_value_id, func as *const _ - )); - } - } - self.emit_branch(cond_value, body_id, exit_id)?; - // 📦 Hotfix 6: Add CFG predecessors for branch targets (Cytron et al. 1991 requirement) - // This ensures exit_block.predecessors is populated before Exit PHI generation - if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - crate::runtime::get_global_ring0().log.debug(&format!( - "[loopform/condition] BEFORE add_predecessor: exit_id={:?}, branch_source={:?}", - exit_id, branch_source_block - )); - } - crate::mir::builder::loops::add_predecessor( - self.parent_builder, - body_id, - branch_source_block, - )?; - crate::mir::builder::loops::add_predecessor( - self.parent_builder, - exit_id, - branch_source_block, - )?; - if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - crate::runtime::get_global_ring0().log.debug(&format!( - "[loopform/condition] AFTER emit_branch: current_block={:?}", - self.current_block()? - )); - crate::runtime::get_global_ring0().log.debug(&format!( - "[loopform/condition] Added predecessors: body={:?} exit={:?} from={:?}", - body_id, exit_id, branch_source_block - )); - // Verify predecessors were added - if let Some(ref func) = self.parent_builder.current_function { - if let Some(exit_block) = func.blocks.get(&exit_id) { - crate::runtime::get_global_ring0().log.debug(&format!( - "[loopform/condition] exit_block.predecessors = {:?}", - exit_block.predecessors - )); - } - } - } - - // Lower loop body - self.set_current_block(body_id)?; - for stmt in body { - self.build_statement(stmt)?; - if is_current_block_terminated(self.parent_builder)? { - break; - } - } - - // Capture variable snapshot at end of body (before jumping to latch) - let body_end_vars = self.get_current_variable_map(); - - // Step 5-1: Writes集合収集(選択肢2+3統合: Snapshot比較で再代入検出) - // current_vars (preheader) と body_end_vars を比較し、ValueId が変わった変数を特定 - use std::collections::HashSet; - let mut writes = HashSet::new(); - for (name, &body_value) in &body_end_vars { - // Skip __pin$ temporary variables - they are always BodyLocalInternal - // (Task先生の発見: これらをcarrier扱いすると未定義ValueIdエラーの原因になる) - if name.starts_with("__pin$") && name.contains("$@") { - continue; - } - - if let Some(&base_value) = current_vars.get(name) { - if body_value != base_value { - writes.insert(name.clone()); - } - } - // else: body で新規定義された変数(body-local)、header PHI 不要 - } - - // DEBUG: Log writes collection - if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - crate::runtime::get_global_ring0().log.debug("[loopform/writes] === WRITES COLLECTION (Step 5-1) ==="); - crate::runtime::get_global_ring0().log.debug(&format!( - "[loopform/writes] {} variables modified in loop body", - writes.len() - )); - let mut sorted_writes: Vec<_> = writes.iter().collect(); - sorted_writes.sort(); - for name in &sorted_writes { - crate::runtime::get_global_ring0().log.debug(&format!("[loopform/writes] WRITE: {}", name)); - } - } - - // Jump to latch if not already terminated - let actual_latch_id = if !is_current_block_terminated(self.parent_builder)? { - self.emit_jump(latch_id)?; - latch_id - } else { - // Body is terminated (break/continue), use current block as latch - self.current_block()? - }; - - // Latch: jump back to header - self.set_current_block(latch_id)?; - - // Update variable map with body end values for sealing - for (name, value) in &body_end_vars { - self.update_variable(name.clone(), *value); - } - - self.emit_jump(header_id)?; - // 📦 Hotfix 6: Add CFG predecessor for header from latch (same as legacy version) - crate::mir::builder::loops::add_predecessor(self.parent_builder, header_id, latch_id)?; - - // Phase 25.1c/k: body-local 変数の PHI 生成 - // BreakFinderBox / FuncScannerBox 等で、loop body 内で新規宣言された local 変数が - // loop header に戻った時に undefined になる問題を修正 - // - // Step 5-5-B: EXPERIMENTAL - Body-local Header PHI generation DISABLED - // Reason: Option C design states body-local variables should NOT have header PHIs - // - BodyLocalExit: needs EXIT PHI only, NOT header PHI - // - BodyLocalInternal: needs NO PHI at all - // - // TODO Step 5-3: Integrate Option C classification (LoopScopeShape) here - // - // TEMPORARY DISABLE to test hypothesis that header PHIs are the root cause - let trace_loop_phi = std::env::var("HAKO_LOOP_PHI_TRACE").ok().as_deref() == Some("1"); - - // DISABLED: Body-local header PHI generation - // This code was causing undefined value errors because it created header PHIs - // for variables that should only have exit PHIs (or no PHIs at all) - if false { // Disabled for Step 5-5-B experiment - // [Original code removed - see git history if needed] - } - - // Pass 4: Generate continue_merge PHIs first, then seal header PHIs - // Phase 25.1c/k: canonical continue_merge ブロックで PHI を生成してから seal_phis を呼ぶ - let raw_continue_snaps = self.continue_snapshots.clone(); - - // Step 1: continue_merge ブロックで PHI 生成(merged_snapshot を作る) - // Phase 25.2: LoopSnapshotMergeBox を使って整理 - self.set_current_block(continue_merge_id)?; - - let merged_snapshot: BTreeMap = if !raw_continue_snaps.is_empty() { - if trace_loop_phi { - eprintln!( - "[loop-phi/continue-merge] Generating PHI nodes for {} continue paths", - raw_continue_snaps.len() - ); - } - - // すべての continue snapshot に現れる変数を収集 - let mut all_vars: BTreeMap> = BTreeMap::new(); - for (continue_bb, snapshot) in &raw_continue_snaps { - for (var_name, &value) in snapshot { - all_vars - .entry(var_name.clone()) - .or_default() - .push((*continue_bb, value)); - } - } - - // 各変数について PHI ノードを生成 - // ======================================== - // Phase 59b: PhiInputCollector インライン化 - // ======================================== - let mut merged = BTreeMap::new(); - for (var_name, inputs) in all_vars { - // Step 1: sanitize (BTreeMap で重複削除&ソート) - let mut sanitized: BTreeMap = BTreeMap::new(); - for (bb, val) in &inputs { - sanitized.insert(*bb, *val); - } - let final_inputs: Vec<(BasicBlockId, ValueId)> = sanitized.into_iter().collect(); - - // Step 2: optimize_same_value - let same_value = if final_inputs.is_empty() { - None - } else if final_inputs.len() == 1 { - Some(final_inputs[0].1) - } else { - let first_val = final_inputs[0].1; - if final_inputs.iter().all(|(_, val)| *val == first_val) { - Some(first_val) - } else { - None - } - }; - - // Step 3: PHI 生成 or 同一値を使用 - let result_value = if let Some(same_val) = same_value { - // 全て同じ値 or 単一入力 → PHI 不要 - same_val - } else { - // 異なる値を持つ場合は PHI ノードを生成 - let phi_id = self.new_value(); - - if let Some(ref mut func) = self.parent_builder.current_function { - if let Some(merge_block) = func.blocks.get_mut(&continue_merge_id) { - merge_block.add_instruction(MirInstruction::Phi { - dst: phi_id, - inputs: final_inputs, - type_hint: None, // Phase 63-6 - }); - } - } - - if trace_loop_phi { - eprintln!( - "[loop-phi/continue-merge] Generated PHI for '{}': {:?}", - var_name, phi_id - ); - } - phi_id - }; - - merged.insert(var_name, result_value); - } - - // Note: 変数マップへの反映は seal_phis に委譲(干渉を避ける) - - if trace_loop_phi { - eprintln!( - "[loop-phi/continue-merge] Merged {} variables from {} paths", - merged.len(), - raw_continue_snaps.len() - ); - } - merged - } else { - BTreeMap::new() - }; - - self.emit_jump(header_id)?; - crate::mir::builder::loops::add_predecessor( - self.parent_builder, - header_id, - continue_merge_id, - )?; - - // Step 2: merged_snapshot を使って seal_phis を呼ぶ - // Phase 25.3: Continue merge PHI実装(Task先生の発見!) - // - continueが無いループでも、Latchブロックの値をHeader PHIに伝播する必要がある - // - これにより、Exit PHIがHeader PHI経由で正しい値を受け取れる - let continue_snaps: Vec<(BasicBlockId, BTreeMap)> = { - // まず、merged_snapshot(continue merge PHI結果)を追加 - let mut snaps = if !merged_snapshot.is_empty() { - vec![(continue_merge_id, merged_snapshot.clone())] - } else { - vec![] - }; - - // continueが無い場合でも、Latchブロックのスナップショットを追加 - // これにより、seal_phis()がLatchからの値をHeader PHIに正しく接続できる - if raw_continue_snaps.is_empty() { - // continue文が無い場合、Latchブロックの現在の変数マップをキャプチャ - // Note: このタイミングでは current_block == exit_id だが、 - // variable_map はLatch実行後の状態を保持している - let latch_snapshot = self.get_current_variable_map(); - snaps.push((actual_latch_id, latch_snapshot)); - } - - snaps - }; - - // Phase 27.4C Refactor: Header φ バイパスフラグを統一取得(seal_phis に渡す) - // Note: fn_name は既に line 299-304 で取得済み、String として保持されている - let bypass_flags_for_seal = - crate::mir::phi_core::loopform_builder::get_loop_bypass_flags(&fn_name); - - // Step 5-1/5-2: Pass writes 集合 for PHI縮約 - // Phase 27.4C: header_bypass フラグも渡す - loopform.seal_phis( - self, - actual_latch_id, - &continue_snaps, - &writes, - bypass_flags_for_seal.header, - )?; - - // Step 3: seal body-local PHIs (complete the inputs) - // Step 5-5-A: REMOVED - PHIs now created complete with both inputs upfront - // Old sealing code was overwriting our preheader+latch inputs with latch-only, - // causing "phi pred mismatch" errors. - // - // Body-local PHIs are now created at line 408-456 with BOTH inputs: - // - preheader: poison value (variable doesn't exist yet) - // - latch: actual value from loop body - // - // No further sealing is needed! - - // Exit block - self.set_current_block(exit_id)?; - - // Phase 25.1h: ControlForm統合版に切り替え - // continue / break のターゲットブロックをユニーク化して収集 - // Phase 25.1: HashSet → BTreeSet(決定性確保) - let mut break_set: BTreeSet = BTreeSet::new(); - for (bb, _) in &self.exit_snapshots { - break_set.insert(*bb); - } - // LoopShape の continue_targets は「header への canonical backedge」を表す。 - // continue が一つ以上存在する場合は continue_merge_id を 1 つだけ登録する。 - let continue_targets: Vec = if self.continue_snapshots.is_empty() { - Vec::new() - } else { - vec![continue_merge_id] - }; - let break_targets: Vec = break_set.into_iter().collect(); - - let loop_shape = LoopShape { - preheader: preheader_id, - header: header_id, - body: body_id, - latch: latch_id, - exit: exit_id, - continue_targets, - break_targets, - }; - let form = ControlForm::from_loop(loop_shape.clone()); - - // Region/GC 観測レイヤ(Phase 25.1l): - // NYASH_REGION_TRACE=1 のときだけ、Stage‑B 周辺ループの - // Region 情報(entry/exit/slots)をログに出すよ。 - crate::mir::region::observer::observe_control_form(self.parent_builder, &form); - - // Phase 27.6-2: JoinIR Exit φ バイパスチェック - let fn_name = self - .parent_builder - .current_function - .as_ref() - .map(|f| f.signature.name.as_str()) - .unwrap_or(""); - - let exit_bypass = crate::mir::phi_core::loopform_builder::joinir_exit_bypass_enabled() - && crate::mir::phi_core::loopform_builder::is_joinir_exit_bypass_target(fn_name); - - if exit_bypass { - // Phase 27.6-2: JoinIR 実験経路では Exit φ を生成しない。 - // ループ内で定義された値だけで exit 後を構成する(JoinIR の k_exit 引数として表現)。 - // - // ⚠️ 重要: このモードでは MIR は不完全(Exit φ 抜け)であり、VM で実行できない。 - // JoinIR runner 専用モードであることに注意。 - if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - eprintln!( - "[loopform/exit-bypass] func={} exit={:?} header={:?} (JoinIR experiment only)", - fn_name, exit_id, header_id - ); - } - } else { - // [LoopForm] exit PHI for Case A/B (uses exit_snapshots + exit_preds) - // - Case A: header+break → exit PHI includes both paths - // - Case B: break-only → exit PHI excludes header (not a predecessor) - let exit_snaps = self.exit_snapshots.clone(); - crate::mir::phi_core::loopform_builder::build_exit_phis_for_control( - &loopform, - self, - &form, - &exit_snaps, - branch_source_block, - )?; - } - - // Pop loop context - crate::mir::builder::loops::pop_loop_context(self.parent_builder); - - // ControlForm 観測: 環境フラグ(未設定時は既定ON)のとき LoopShape をダンプ - if is_control_form_trace_on() { - form.debug_dump(); - #[cfg(debug_assertions)] - if let Some(ref func) = self.parent_builder.current_function { - loop_shape.debug_validate(func); - } - } - - // Return void value - let void_dst = self.new_value(); - self.emit_const(void_dst, ConstValue::Void)?; - Ok(void_dst) - } -} diff --git a/src/mir/loop_builder/mod.rs b/src/mir/loop_builder/mod.rs deleted file mode 100644 index b313add5..00000000 --- a/src/mir/loop_builder/mod.rs +++ /dev/null @@ -1,158 +0,0 @@ -/*! - * MIR Loop Builder - SSA形式でのループ構築専用モジュール - * - * Sealed/Unsealed blockとPhi nodeを使った正しいループ実装。 - * - * LoopForm v2 の「形」をここで固定している: - * - preheader: ループに入る直前のブロック(初期値の copy 発生源) - * - header : ループ条件を評価するブロック(`loop(cond)` の `cond` 部分) - * - body : ループ本体(ユーザーコードが書いたブロック) - * - latch : body の末尾から header へ戻る backedge 用ブロック - * - exit : ループ脱出先(`break` / `cond == false` が合流するブロック) - * - * 典型パターン(ControlForm::LoopShape): - * - Case A: header-cond + header→exit + body→exit(`loop(i < n) { if (...) break }`) - * - Case B: constant-true + body→exit のみ(`loop(1 == 1) { if (...) break }`) - * - この場合、header→exit のエッジは存在しないので、exit PHI に header 値を入れてはいけない。 - * - Case C: continue_merge を経由して header に戻る経路あり(`continue` を含むループ)。 - * - * それぞれのケースは ControlForm / LoopSnapshotMergeBox / ExitPhiBuilder に伝搬され、 - * exit PHI の入力選択や BodyLocalInternal 変数の扱いに反映される。 - * - * モジュール構成: - * - control.rs: break/continue 導線の共通化と predecessor 記録 - * - loop_form.rs: LoopForm v2 本体の構築(preheader/header/body/latch/exit) - * - statements.rs: ループ内ステートメントの lowering(if は if_lowering.rs に委譲) - * - if_lowering.rs: ループ内 if の JoinIR/PHI 統合 - * - phi_ops.rs: PHI 生成ヘルパーと LoopForm/PhiBuilder trait 実装 - */ - -mod control; -mod if_in_loop_phi_emitter; -mod if_lowering; -mod joinir_if_phi_selector; -mod loop_form; -mod phi_ops; -mod statements; - -pub use if_in_loop_phi_emitter::IfInLoopPhiEmitter; -pub use joinir_if_phi_selector::{JoinIRIfPhiSelector, JoinIRResult}; - -use super::{BasicBlockId, ConstValue, MirInstruction, ValueId}; -use std::collections::BTreeMap; // Phase 25.1: 決定性確保 - -/// ループビルダー - SSA形式でのループ構築を管理 -pub struct LoopBuilder<'a> { - /// 親のMIRビルダーへの参照 - pub(super) parent_builder: &'a mut super::builder::MirBuilder, - - /// ブロックごとの変数マップ(スコープ管理) - /// Phase 25.1: BTreeMap → BTreeMap(決定性確保) - #[allow(dead_code)] - pub(super) block_var_maps: BTreeMap>, - - /// ループヘッダーID(continue 先の既定値として使用) - pub(super) loop_header: Option, - - /// continue 文がジャンプするターゲットブロック - /// - 既定: header と同一 - /// - 将来: canonical continue merge ブロックに差し替えるためのフック - pub(super) continue_target: Option, - - /// continue文からの変数スナップショット - pub(super) continue_snapshots: Vec<(BasicBlockId, BTreeMap)>, - - /// break文からの変数スナップショット(exit PHI生成用) - pub(super) exit_snapshots: Vec<(BasicBlockId, BTreeMap)>, - // フェーズM: no_phi_modeフィールド削除(常にPHI使用) -} - -impl<'a> LoopBuilder<'a> { - /// 新しいループビルダーを作成 - pub fn new(parent: &'a mut super::builder::MirBuilder) -> Self { - Self { - parent_builder: parent, - block_var_maps: BTreeMap::new(), - loop_header: None, - continue_target: None, - continue_snapshots: Vec::new(), - exit_snapshots: Vec::new(), // exit PHI用のスナップショット - } - } - - pub(super) fn current_block(&self) -> Result { - self.parent_builder - .current_block - .ok_or_else(|| "No current block".to_string()) - } - - pub(super) fn new_block(&mut self) -> BasicBlockId { - self.parent_builder.block_gen.next() - } - - pub(super) fn new_value(&mut self) -> ValueId { - // Use function-local allocator via MirBuilder helper to keep - // ValueId ranges consistent within the current function. - self.parent_builder.next_value_id() - } - - pub(super) fn set_current_block(&mut self, block_id: BasicBlockId) -> Result<(), String> { - self.parent_builder.start_new_block(block_id) - } - - pub(super) fn emit_jump(&mut self, target: BasicBlockId) -> Result<(), String> { - self.parent_builder - .emit_instruction(MirInstruction::Jump { target }) - } - - pub(super) fn emit_branch( - &mut self, - condition: ValueId, - then_bb: BasicBlockId, - else_bb: BasicBlockId, - ) -> Result<(), String> { - // LocalSSA: ensure condition is materialized in the current block - let condition_local = self.parent_builder.local_ssa_ensure(condition, 4); - self.parent_builder - .emit_instruction(MirInstruction::Branch { - condition: condition_local, - then_bb, - else_bb, - }) - } - - pub(super) fn emit_const(&mut self, dst: ValueId, value: ConstValue) -> Result<(), String> { - self.parent_builder - .emit_instruction(MirInstruction::Const { dst, value }) - } - - pub(super) fn get_current_variable_map(&self) -> BTreeMap { - // Phase 25.1: BTreeMap化 - self.parent_builder.variable_map.clone() - } - - pub(super) fn update_variable(&mut self, name: String, value: ValueId) { - if std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1") { - eprintln!( - "[DEBUG] LoopBuilder::update_variable: name={}, value=%{}", - name, value.0 - ); - } - self.parent_builder.variable_map.insert(name, value); - } - - pub(super) fn get_variable_at_block( - &self, - name: &str, - block_id: BasicBlockId, - ) -> Option { - // まずブロックごとのスナップショットを優先 - if let Some(map) = self.block_var_maps.get(&block_id) { - if let Some(v) = map.get(name) { - return Some(*v); - } - } - // フォールバック:現在の変数マップ(単純ケース用) - self.parent_builder.variable_map.get(name).copied() - } -} diff --git a/src/mir/loop_builder/phi_ops.rs b/src/mir/loop_builder/phi_ops.rs deleted file mode 100644 index ea7ec88f..00000000 --- a/src/mir/loop_builder/phi_ops.rs +++ /dev/null @@ -1,333 +0,0 @@ -use super::{LoopBuilder, ValueId}; -use crate::mir::phi_core::loopform_builder::LoopFormOps; -use crate::mir::{BasicBlockId, ConstValue, MirInstruction}; -use crate::runtime::get_global_ring0; - -impl<'a> LoopBuilder<'a> { - /// ブロック先頭に PHI 命令を挿入(不変条件: PHI は常にブロック先頭) - pub(super) fn emit_phi_at_block_start( - &mut self, - block_id: BasicBlockId, - dst: ValueId, - inputs: Vec<(BasicBlockId, ValueId)>, - ) -> Result<(), String> { - let dbg = std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1"); - if dbg { - get_global_ring0().log.debug(&format!( - "[DEBUG] LoopBuilder::emit_phi_at_block_start: block={}, dst=%{}, inputs={:?}", - block_id, dst.0, inputs - )); - } - // Phi nodeをブロックの先頭に挿入 - if let Some(ref mut function) = self.parent_builder.current_function { - if let Some(block) = function.get_block_mut(block_id) { - if dbg { - get_global_ring0().log.debug(&format!( - "[DEBUG] Block {} current instructions count: {}", - block_id, - block.instructions.len() - )); - } - // Phi命令は必ずブロックの先頭に配置。ただし同一dstの既存PHIがある場合は差し替える。 - let mut replaced = false; - let mut idx = 0; - let span = self.parent_builder.current_span; - while idx < block.instructions.len() { - match &mut block.instructions[idx] { - MirInstruction::Phi { - dst: d, - inputs: ins, - .. // Phase 63-6: Ignore type_hint in pattern matching - } if *d == dst => { - *ins = inputs.clone(); - if block.instruction_spans.len() <= idx { - // Backfill missing spans to preserve alignment after legacy inserts. - while block.instruction_spans.len() <= idx { - block.instruction_spans.push(crate::ast::Span::unknown()); - } - } - block.instruction_spans[idx] = span; - replaced = true; - break; - } - MirInstruction::Phi { .. } => { - idx += 1; - } - _ => break, - } - } - if !replaced { - let phi_inst = MirInstruction::Phi { - dst, - inputs: inputs.clone(), - type_hint: None, // Phase 63-6: Legacy path, no type hint - }; - block.instructions.insert(0, phi_inst); - block.instruction_spans.insert(0, span); - } - if dbg { - get_global_ring0() - .log - .debug("[DEBUG] ✅ PHI instruction inserted at position 0"); - get_global_ring0().log.debug(&format!( - "[DEBUG] Block {} after insert instructions count: {}", - block_id, - block.instructions.len() - )); - } - // Verify PHI is still there - if let Some(first_inst) = block.instructions.get(0) { - match first_inst { - MirInstruction::Phi { dst: phi_dst, .. } => { - if dbg { - get_global_ring0().log.debug(&format!( - "[DEBUG] Verified: First instruction is PHI dst=%{}", - phi_dst.0 - )); - } - } - other => { - if dbg { - get_global_ring0().log.warn(&format!( - "[DEBUG] ⚠️ WARNING: First instruction is NOT PHI! It's {:?}", - other - )); - } - } - } - } - Ok(()) - } else { - if dbg { - get_global_ring0() - .log - .warn(&format!("[DEBUG] ❌ Block {} not found!", block_id)); - } - Err(format!("Block {} not found", block_id)) - } - } else { - if dbg { - get_global_ring0() - .log - .warn("[DEBUG] ❌ No current function!"); - } - Err("No current function".to_string()) - } - } -} - -// Phase 30 F-2.1: LoopPhiOps 実装削除(loop_phi.rs 削除に伴う) -// LoopFormOps が SSOT として機能しているため、レガシー互換層は不要 - -// Implement LoopFormOps trait for LoopBuilder to support LoopFormBuilder integration -impl<'a> LoopFormOps for LoopBuilder<'a> { - fn new_value(&mut self) -> ValueId { - // CRITICAL: Must use MirFunction's next_value_id(), not MirBuilder's value_gen - // Otherwise we get SSA violations because the two counters diverge - let id = if let Some(ref mut func) = self.parent_builder.current_function { - let before = func.next_value_id; - let id = func.next_value_id(); - if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - get_global_ring0().log.debug(&format!( - "[LoopFormOps::new_value] fn='{}' counter: {} -> {}, allocated: {:?}", - func.signature.name, before, func.next_value_id, id - )); - } - id - } else { - // Fallback (should never happen in practice) - let id = self.parent_builder.value_gen.next(); - if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - get_global_ring0().log.debug(&format!( - "[LoopFormOps::new_value] FALLBACK value_gen, allocated: {:?}", - id - )); - } - id - }; - id - } - - fn ensure_counter_after(&mut self, max_id: u32) -> Result<(), String> { - if let Some(ref mut func) = self.parent_builder.current_function { - // 📦 Hotfix 1: Consider both parameter count and existing ValueIds - let param_count = func.signature.params.len() as u32; - let min_counter = param_count.max(max_id + 1); - - if func.next_value_id < min_counter { - if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - get_global_ring0().log.debug(&format!( - "[LoopFormOps::ensure_counter_after] fn='{}' params={}, max_id={}, adjusting counter {} -> {}", - func.signature.name, param_count, max_id, func.next_value_id, min_counter - )); - } - func.next_value_id = min_counter; - } - Ok(()) - } else { - Err("No current function to adjust counter".to_string()) - } - } - - fn block_exists(&self, block: BasicBlockId) -> bool { - // 📦 Hotfix 2: Check if block exists in current function's CFG - if let Some(ref func) = self.parent_builder.current_function { - func.blocks.contains_key(&block) - } else { - false - } - } - - fn get_block_predecessors( - &self, - block: BasicBlockId, - ) -> std::collections::BTreeSet { - // 📦 Hotfix 6: Get actual CFG predecessors for PHI validation - // Phase 69-3: Changed to BTreeSet for determinism - if let Some(ref func) = self.parent_builder.current_function { - if let Some(bb) = func.blocks.get(&block) { - bb.predecessors.clone() - } else { - std::collections::BTreeSet::new() // Non-existent blocks have no predecessors - } - } else { - std::collections::BTreeSet::new() - } - } - - /// Phase 26-A-4: ValueIdベースのパラメータ判定(GUARD Bug Prevention) - /// - /// 旧実装(名前ベース)の問題点: - /// - ValueId(0) を「常に未初期化」と誤判定 - /// - パラメータ s=ValueId(0) も弾いてしまうGUARDバグ - /// - /// 新実装(型ベース)の利点: - /// - MirValueKindで型安全判定 - /// - ValueId(0)でもParameter(0)なら正しく判定 - fn is_parameter(&self, value_id: ValueId) -> bool { - // Phase 26-A-4: 型安全なパラメータ判定を使用 - // parent_builder.is_value_parameter() は Phase 26-A-2 で実装済み - let is_param = self.parent_builder.is_value_parameter(value_id); - - if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { - eprintln!( - "[is_parameter] ValueId({}) -> {} (kind = {:?})", - value_id.0, - is_param, - self.parent_builder.get_value_kind(value_id) - ); - } - - is_param - } - - fn set_current_block(&mut self, block: BasicBlockId) -> Result<(), String> { - self.parent_builder.start_new_block(block) - } - - fn emit_copy(&mut self, dst: ValueId, src: ValueId) -> Result<(), String> { - self.parent_builder - .emit_instruction(MirInstruction::Copy { dst, src }) - } - - fn emit_jump(&mut self, target: BasicBlockId) -> Result<(), String> { - self.emit_jump(target) - } - - fn emit_phi( - &mut self, - dst: ValueId, - inputs: Vec<(BasicBlockId, ValueId)>, - ) -> Result<(), String> { - self.emit_phi_at_block_start(self.current_block()?, dst, inputs) - } - - fn update_phi_inputs( - &mut self, - block: BasicBlockId, - phi_id: ValueId, - inputs: Vec<(BasicBlockId, ValueId)>, - ) -> Result<(), String> { - self.parent_builder - .update_phi_instruction(block, phi_id, inputs) - } - - fn update_var(&mut self, name: String, value: ValueId) { - self.parent_builder.variable_map.insert(name, value); - } - - fn get_variable_at_block(&self, name: &str, block: BasicBlockId) -> Option { - // Use the inherent method to avoid recursion - LoopBuilder::get_variable_at_block(self, name, block) - } - - fn mir_function(&self) -> &crate::mir::MirFunction { - self.parent_builder - .current_function - .as_ref() - .expect("LoopBuilder requires current_function") - } -} - -// Phase 26-E-3: PhiBuilderOps 委譲実装(has-a設計) -// -// **Design Rationale:** -// - PhiBuilderOps = 低レベル「PHI命令発行」道具箱 -// - LoopFormOps = 高レベル「ループ構造構築」作業場 -// - 関係: has-a(委譲) - LoopBuilder は両方のインターフェースを提供 -// -// **実装方針:** -// - LoopFormOps の既存実装に委譲(重複なし) -// - HashSet → Vec 変換 + ソート(決定性保証) -// - emit_phi: block 引数差を吸収(current_block 設定) -impl<'a> crate::mir::phi_core::phi_builder_box::PhiBuilderOps for LoopBuilder<'a> { - fn new_value(&mut self) -> ValueId { - // 委譲: LoopFormOps の既存実装を使用 - ::new_value(self) - } - - fn emit_phi( - &mut self, - block: BasicBlockId, - dst: ValueId, - inputs: Vec<(BasicBlockId, ValueId)>, - ) -> Result<(), String> { - // PhiBuilderOps: block 引数あり - // LoopFormOps: block 引数なし(current_block 使用) - // 差を吸収: current_block を設定してから LoopFormOps::emit_phi を呼ぶ - ::set_current_block(self, block)?; - ::emit_phi(self, dst, inputs) - } - - fn update_var(&mut self, name: String, value: ValueId) { - // 委譲: LoopFormOps の既存実装を使用 - ::update_var(self, name, value) - } - - fn get_block_predecessors(&self, block: BasicBlockId) -> Vec { - // LoopFormOps: HashSet 返却 - // PhiBuilderOps: Vec 返却 - // 変換: HashSet → Vec + ソート(決定性保証) - let pred_set = ::get_block_predecessors(self, block); - let mut pred_vec: Vec = pred_set.into_iter().collect(); - pred_vec.sort_by_key(|bb| bb.0); // bb.0 = BasicBlockId の内部値 - pred_vec - } - - fn emit_void(&mut self) -> ValueId { - // void 定数を発行 - let void_id = ::new_value(self); - let _ = self.emit_const(void_id, ConstValue::Void); - void_id - } - - fn set_current_block(&mut self, block: BasicBlockId) -> Result<(), String> { - // 委譲: LoopFormOps の既存実装を使用 - ::set_current_block(self, block) - } - - fn block_exists(&self, block: BasicBlockId) -> bool { - // 委譲: LoopFormOps の既存実装を使用 - ::block_exists(self, block) - } -} diff --git a/src/mir/loop_builder/statements.rs b/src/mir/loop_builder/statements.rs deleted file mode 100644 index 8e78f7c0..00000000 --- a/src/mir/loop_builder/statements.rs +++ /dev/null @@ -1,39 +0,0 @@ -use super::{LoopBuilder, ValueId}; -use crate::ast::ASTNode; -use crate::mir::utils::is_current_block_terminated; -use crate::mir::ConstValue; - -impl<'a> LoopBuilder<'a> { - pub(super) fn build_statement(&mut self, stmt: ASTNode) -> Result { - // Preserve the originating span for loop-local control instructions (break/continue/phi). - self.parent_builder.current_span = stmt.span(); - match stmt { - // Ensure nested bare blocks inside loops are lowered with loop-aware semantics - ASTNode::Program { statements, .. } => { - let mut last = None; - for s in statements.into_iter() { - last = Some(self.build_statement(s)?); - // フェーズS修正:統一終端検出ユーティリティ使用 - if is_current_block_terminated(self.parent_builder)? { - break; - } - } - Ok(last.unwrap_or_else(|| { - let void_id = self.new_value(); - // Emit a void const to keep SSA consistent when block is empty - let _ = self.emit_const(void_id, ConstValue::Void); - void_id - })) - } - ASTNode::If { - condition, - then_body, - else_body, - .. - } => self.lower_if_in_loop(*condition, then_body, else_body), - ASTNode::Break { .. } => self.do_break(), - ASTNode::Continue { .. } => self.do_continue(), - other => self.parent_builder.build_expression(other), - } - } -} diff --git a/src/mir/mod.rs b/src/mir/mod.rs index 5e3dc98f..d3dc93bf 100644 --- a/src/mir/mod.rs +++ b/src/mir/mod.rs @@ -16,7 +16,7 @@ pub mod instruction; pub mod instruction_introspection; // Introspection helpers for tests (instruction names) pub mod instruction_kinds; // small kind-specific metadata (Const/BinOp) pub mod loop_api; // Minimal LoopBuilder facade (adapter-ready) -pub mod loop_builder; // SSA loop construction with phi nodes +pub mod if_in_loop_phi; // Phase 187-2: Minimal if-in-loop PHI emitter (extracted from loop_builder) pub mod naming; // Static box / entry naming rules(NamingBox) pub mod optimizer; pub mod ssot; // Shared helpers (SSOT) for instruction lowering