From 2b47f47061895f314bf581069160d5c1e847e46d Mon Sep 17 00:00:00 2001 From: nyash-codex Date: Tue, 25 Nov 2025 23:42:35 +0900 Subject: [PATCH] =?UTF-8?q?refactor(phi=5Fcore):=20F-2.1=20-=20=E6=97=A9?= =?UTF-8?q?=E6=9C=9F=E3=82=B0=E3=83=AB=E3=83=BC=E3=83=97PHI=E7=AE=B1?= =?UTF-8?q?=E5=89=8A=E9=99=A4=EF=BC=88=E7=B4=842,500=E8=A1=8C=E5=89=8A?= =?UTF-8?q?=E6=B8=9B=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 削除したファイル - header_phi_builder.rs (~628行) - バイパス関数を loopform_builder.rs に移動 - exit_phi_builder.rs (~1000行) - バイパス関数を loopform_builder.rs に移動 - body_local_phi_builder.rs (~550行) - 依存なし - loop_phi.rs (~288行) - LoopPhiOps実装も削除 ## 移動した関数 loopform_builder.rs に以下を移動: - get_loop_bypass_flags() / LoopBypassFlags struct - is_joinir_header_bypass_target() - joinir_exit_bypass_enabled() - is_joinir_exit_bypass_target() ## 修正したファイル - loop_builder.rs: バイパス関数の参照先変更 + LoopPhiOps impl削除 - mod.rs: モジュール宣言削除 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- docs/private | 2 +- src/mir/loop_builder.rs | 119 +-- src/mir/phi_core/body_local_phi_builder.rs | 557 ------------ src/mir/phi_core/exit_phi_builder.rs | 960 --------------------- src/mir/phi_core/header_phi_builder.rs | 627 -------------- src/mir/phi_core/loop_phi.rs | 287 ------ src/mir/phi_core/loopform_builder.rs | 74 ++ src/mir/phi_core/mod.rs | 10 +- 8 files changed, 87 insertions(+), 2549 deletions(-) delete mode 100644 src/mir/phi_core/body_local_phi_builder.rs delete mode 100644 src/mir/phi_core/exit_phi_builder.rs delete mode 100644 src/mir/phi_core/header_phi_builder.rs delete mode 100644 src/mir/phi_core/loop_phi.rs diff --git a/docs/private b/docs/private index 6eee62b6..6932ae78 160000 --- a/docs/private +++ b/docs/private @@ -1 +1 @@ -Subproject commit 6eee62b646b262eefdf1ce6aa322065372d285dd +Subproject commit 6932ae78573177d67ea1266199bf089da4ff3f5c diff --git a/src/mir/loop_builder.rs b/src/mir/loop_builder.rs index 21f1362a..b5b26931 100644 --- a/src/mir/loop_builder.rs +++ b/src/mir/loop_builder.rs @@ -66,8 +66,6 @@ pub struct LoopBuilder<'a> { } impl<'a> LoopBuilder<'a> { - // Implement phi_core LoopPhiOps on LoopBuilder for in-place delegation - /// Find the source value of a Copy instruction in a given block /// If `dst` is defined by a Copy instruction `dst = copy src`, return Some(src) /// Otherwise return None @@ -303,7 +301,7 @@ impl<'a> LoopBuilder<'a> { .unwrap_or_default(); let bypass_flags = - crate::mir::phi_core::header_phi_builder::get_loop_bypass_flags(&fn_name); + crate::mir::phi_core::loopform_builder::get_loop_bypass_flags(&fn_name); if bypass_flags.header { // Phase 27.4-C: JoinIR 実験経路では Header φ を生成しない。 @@ -608,7 +606,7 @@ impl<'a> LoopBuilder<'a> { // Phase 27.4C Refactor: Header φ バイパスフラグを統一取得(seal_phis に渡す) // Note: fn_name は既に line 299-304 で取得済み、String として保持されている let bypass_flags_for_seal = - crate::mir::phi_core::header_phi_builder::get_loop_bypass_flags(&fn_name); + 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 フラグも渡す @@ -674,8 +672,8 @@ impl<'a> LoopBuilder<'a> { .map(|f| f.signature.name.as_str()) .unwrap_or(""); - let exit_bypass = crate::mir::phi_core::exit_phi_builder::joinir_exit_bypass_enabled() - && crate::mir::phi_core::exit_phi_builder::is_joinir_exit_bypass_target(fn_name); + 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 φ を生成しない。 @@ -971,34 +969,7 @@ impl<'a> LoopBuilder<'a> { ) -> 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 comparison operands to slots so repeated uses across blocks are safe - if crate::config::env::mir_pre_pin_compare_operands() { - if let ASTNode::BinaryOp { - operator, - left, - right, - .. - } = &condition - { - use crate::ast::BinaryOperator as BO; - match operator { - BO::Equal - | BO::NotEqual - | BO::Less - | BO::LessEqual - | BO::Greater - | BO::GreaterEqual => { - if let Ok(lhs_v) = self.parent_builder.build_expression((**left).clone()) { - let _ = self.parent_builder.pin_to_slot(lhs_v, "@loop_if_lhs"); - } - if let Ok(rhs_v) = self.parent_builder.build_expression((**right).clone()) { - let _ = self.parent_builder.pin_to_slot(rhs_v, "@loop_if_rhs"); - } - } - _ => {} - } - } - } + // 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(); @@ -1263,84 +1234,8 @@ impl<'a> LoopBuilder<'a> { } } -// Implement phi_core LoopPhiOps on LoopBuilder for in-place delegation -impl crate::mir::phi_core::loop_phi::LoopPhiOps for LoopBuilder<'_> { - fn new_value(&mut self) -> ValueId { - self.new_value() - } - fn emit_phi_at_block_start( - &mut self, - block: BasicBlockId, - dst: ValueId, - inputs: Vec<(BasicBlockId, ValueId)>, - ) -> Result<(), String> { - self.emit_phi_at_block_start(block, dst, inputs) - } - fn update_var(&mut self, name: String, value: ValueId) { - self.update_variable(name, value) - } - fn get_variable_at_block(&mut self, name: &str, block: BasicBlockId) -> Option { - // Call the inherent method (immutable borrow) to avoid recursion - LoopBuilder::get_variable_at_block(self, name, block) - } - fn debug_verify_phi_inputs( - &mut self, - merge_bb: BasicBlockId, - inputs: &[(BasicBlockId, ValueId)], - ) { - if let Some(ref func) = self.parent_builder.current_function { - crate::mir::phi_core::common::debug_verify_phi_inputs(func, merge_bb, inputs); - } - } - - fn emit_copy_at_preheader( - &mut self, - preheader_id: BasicBlockId, - dst: ValueId, - src: ValueId, - ) -> Result<(), String> { - let dbg = std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1"); - if dbg { - eprintln!( - "[DEBUG] emit_copy_at_preheader: preheader={}, dst=%{}, src=%{}", - preheader_id, dst.0, src.0 - ); - } - if let Some(ref mut function) = self.parent_builder.current_function { - if let Some(block) = function.get_block_mut(preheader_id) { - if dbg { - eprintln!( - "[DEBUG] Adding Copy instruction to block {}", - preheader_id - ); - } - block.add_instruction_with_span( - MirInstruction::Copy { dst, src }, - self.parent_builder.current_span, - ); - Ok(()) - } else { - if dbg { - eprintln!("[DEBUG] ❌ Preheader block {} not found!", preheader_id); - } - Err(format!("Preheader block {} not found", preheader_id)) - } - } else { - if dbg { - eprintln!("[DEBUG] ❌ No current function!"); - } - Err("No current function".to_string()) - } - } - - fn add_predecessor_edge( - &mut self, - block: BasicBlockId, - pred: BasicBlockId, - ) -> Result<(), String> { - self.add_predecessor(block, pred) - } -} +// 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> { diff --git a/src/mir/phi_core/body_local_phi_builder.rs b/src/mir/phi_core/body_local_phi_builder.rs deleted file mode 100644 index 1a4c562b..00000000 --- a/src/mir/phi_core/body_local_phi_builder.rs +++ /dev/null @@ -1,557 +0,0 @@ -//! Body Local PHI Builder - BodyLocal変数PHI生成専門Box -//! -//! Phase 26-B-2: BodyLocalPhiBuilder実装 -//! - BodyLocal変数のPHI生成判定 -//! - BodyLocalInternal変数のスキップ -//! - Exit PHI候補のフィルタリング -//! -//! Phase 26-F-4: Exit Liveness統合(環境変数制御) -//! - live_at_exit パラメータ追加(デフォルト: 空集合) -//! - `NYASH_EXIT_LIVE_ENABLE=1` で将来のMIRスキャン実装を有効化 -//! -//! Box-First理論: BodyLocal変数処理の責任を明確に分離し、テスト可能な箱として提供 - -use super::local_scope_inspector::LocalScopeInspectorBox; -use super::loop_var_classifier::{LoopVarClass, LoopVarClassBox}; -use crate::mir::BasicBlockId; - -/// BodyLocal変数PHI生成専門Box -/// -/// # Purpose -/// - BodyLocalExit/BodyLocalInternal変数を判定 -/// - Exit PHI生成の要否を決定 -/// - Exit PHI候補のフィルタリング -/// -/// # Responsibility Separation -/// - Classifier: LoopVarClassBox(変数分類) -/// - Inspector: LocalScopeInspectorBox(定義位置追跡) -/// - Builder: このBox(PHI生成判定) -/// -/// # Usage -/// ```ignore -/// let inspector = LocalScopeInspectorBox::new(); -/// let classifier = LoopVarClassBox::new(); -/// let mut builder = BodyLocalPhiBuilder::new(classifier, inspector); -/// -/// // Record definitions -/// builder.inspector_mut().record_definition("ch", block_id); -/// -/// // Check if variable needs exit PHI -/// if builder.should_generate_exit_phi("ch", &pinned, &carrier, &exit_preds) { -/// // Generate exit PHI -/// } -/// ``` -#[derive(Clone)] -pub struct BodyLocalPhiBuilder { - /// Variable classifier - classifier: LoopVarClassBox, - - /// Definition location tracker - inspector: LocalScopeInspectorBox, -} - -impl BodyLocalPhiBuilder { - /// Create a new BodyLocalPhiBuilder - /// - /// # Arguments - /// * `classifier` - LoopVarClassBox for variable classification - /// * `inspector` - LocalScopeInspectorBox for definition tracking - /// - /// # Returns - /// New builder ready to classify variables - pub fn new(classifier: LoopVarClassBox, inspector: LocalScopeInspectorBox) -> Self { - Self { - classifier, - inspector, - } - } - - /// Check if a variable needs exit PHI - /// - /// # Arguments - /// * `var_name` - Variable name to check - /// * `pinned_vars` - Known loop-crossing parameters - /// * `carrier_vars` - Known loop-modified variables - /// * `exit_preds` - All exit predecessor blocks - /// - /// # Returns - /// - `true` if variable needs exit PHI (Pinned/Carrier/BodyLocalExit) - /// - `false` if variable should skip exit PHI (BodyLocalInternal) - /// - /// # Classification Logic - /// - Pinned: Always needs exit PHI - /// - Carrier: Always needs exit PHI - /// - BodyLocalExit: Defined in ALL exit preds → needs exit PHI - /// - BodyLocalInternal: Defined in SOME exit preds → NO exit PHI - /// - /// # Example - /// ```ignore - /// // skip_whitespace scenario: - /// // ch is defined only in block 5, but exit preds are [block 2, block 5] - /// let needs_phi = builder.should_generate_exit_phi( - /// "ch", - /// &[], // not pinned - /// &[], // not carrier - /// &[BasicBlockId(2), BasicBlockId(5)], - /// ); - /// assert!(!needs_phi); // BodyLocalInternal → skip exit PHI! - /// ``` - pub fn should_generate_exit_phi( - &self, - var_name: &str, - pinned_vars: &[String], - carrier_vars: &[String], - exit_preds: &[BasicBlockId], - ) -> bool { - let class = self.classifier.classify( - var_name, - pinned_vars, - carrier_vars, - &self.inspector, - exit_preds, - ); - - // BodyLocalInternal → Skip exit PHI - class.needs_exit_phi() - } - - /// Filter variables to get only those needing exit PHI - /// - /// # Arguments - /// * `all_vars` - All variable names to consider - /// * `pinned_vars` - Known loop-crossing parameters - /// * `carrier_vars` - Known loop-modified variables - /// * `exit_preds` - All exit predecessor blocks - /// * `live_at_exit` - Phase 26-F-4: Variables that are actually used after exit - /// - /// # Returns - /// Filtered list of variable names that need exit PHI - /// - /// # Phase 26-F-4: OR判定による統合 - /// - class.needs_exit_phi() == true → 既存ロジック(Pinned/Carrier/BodyLocalExit) - /// - OR live_at_exit に含まれる → 新規ロジック(BodyLocalInternal でも救う) - /// - /// # Example - /// ```ignore - /// // Phase 26-F-4: skip_whitespace scenario(保守的live+全pred定義が条件) - /// let all_vars = vec!["s", "idx", "ch", "n"]; - /// let pinned = vec!["s", "idx"]; - /// let carrier = vec![]; - /// let live_at_exit = BTreeSet::from(["s", "idx", "ch", "n"]); // 保守的近似 - /// - /// let phi_vars = builder.filter_exit_phi_candidates( - /// &all_vars, - /// &pinned, - /// &carrier, - /// &exit_preds, - /// &live_at_exit, // Phase 26-F-4: 追加 - /// ); - /// // Result (現行ロジック): ["s", "idx", "n"] - /// // - "ch" は BodyLocalInternal かつ exit_preds 全てで定義されていないため除外される - /// ``` - pub fn filter_exit_phi_candidates( - &self, - all_vars: &[String], - pinned_vars: &[String], - carrier_vars: &[String], - exit_preds: &[BasicBlockId], - live_at_exit: &std::collections::BTreeSet, // Phase 26-F-4: 追加 - ) -> Vec { - // 環境変数ガード:既定は従来挙動(BodyLocalInternal を救済しない) - let enable_live_rescue = - std::env::var("NYASH_EXIT_LIVE_ENABLE").ok().as_deref() == Some("1"); - - all_vars - .iter() - .filter(|var_name| { - let class = self.classifier.classify( - var_name, - pinned_vars, - carrier_vars, - &self.inspector, - exit_preds, - ); - - // Phase 26-F-4: OR判定(ただし BodyLocalInternal は「全predで定義される」場合に限定) - // - Pinned/Carrier/BodyLocalExit → 既存ロジック - // - BodyLocalInternal でも live_at_exit に含まれ、かつ全predで定義されるなら exit PHI 候補 - if class.needs_exit_phi() { - return true; - } - - if enable_live_rescue { - if matches!( - class, - super::loop_var_classifier::LoopVarClass::BodyLocalInternal - ) && live_at_exit.contains(*var_name) - && self.inspector.is_available_in_all(var_name, exit_preds) - { - return true; - } - } - - false - }) - .cloned() - .collect() - } - - // Phase 26-F-2: この filter_if_merge_candidates() は削除 - // 理由: LoopVarClassBox(ループ全体スコープ分析)と if-merge専用処理が混在 - // 代替: if_body_local_merge.rs の IfBodyLocalMergeBox を使用 - - /// Get mutable reference to inspector - /// - /// # Purpose - /// Allow caller to record variable definitions - /// - /// # Returns - /// Mutable reference to LocalScopeInspectorBox - /// - /// # Example - /// ```ignore - /// builder.inspector_mut().record_definition("ch", BasicBlockId(5)); - /// ``` - pub fn inspector_mut(&mut self) -> &mut LocalScopeInspectorBox { - &mut self.inspector - } - - /// Get immutable reference to inspector - /// - /// # Purpose - /// Allow caller to query variable definitions - /// - /// # Returns - /// Immutable reference to LocalScopeInspectorBox - pub fn inspector(&self) -> &LocalScopeInspectorBox { - &self.inspector - } - - /// Get classification for a variable - /// - /// # Arguments - /// * `var_name` - Variable name to classify - /// * `pinned_vars` - Known loop-crossing parameters - /// * `carrier_vars` - Known loop-modified variables - /// * `exit_preds` - All exit predecessor blocks - /// - /// # Returns - /// LoopVarClass classification - /// - /// # Example - /// ```ignore - /// let class = builder.classify_variable("ch", &pinned, &carrier, &exit_preds); - /// assert_eq!(class, LoopVarClass::BodyLocalInternal); - /// ``` - pub fn classify_variable( - &self, - var_name: &str, - pinned_vars: &[String], - carrier_vars: &[String], - exit_preds: &[BasicBlockId], - ) -> LoopVarClass { - self.classifier.classify( - var_name, - pinned_vars, - carrier_vars, - &self.inspector, - exit_preds, - ) - } -} - -// ============================================================================ -// Unit Tests -// ============================================================================ - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_pinned_variable_needs_exit_phi() { - let classifier = LoopVarClassBox::new(); - let inspector = LocalScopeInspectorBox::new(); - let builder = BodyLocalPhiBuilder::new(classifier, inspector); - - // Pinned variable always needs exit PHI - let needs_phi = builder.should_generate_exit_phi( - "s", - &["s".to_string()], // pinned - &[], - &[BasicBlockId(1), BasicBlockId(2)], - ); - - assert!(needs_phi); - } - - #[test] - fn test_carrier_variable_needs_exit_phi() { - let classifier = LoopVarClassBox::new(); - let inspector = LocalScopeInspectorBox::new(); - let builder = BodyLocalPhiBuilder::new(classifier, inspector); - - // Carrier variable always needs exit PHI - let needs_phi = builder.should_generate_exit_phi( - "i", - &[], - &["i".to_string()], // carrier - &[BasicBlockId(1), BasicBlockId(2)], - ); - - assert!(needs_phi); - } - - #[test] - fn test_body_local_exit_needs_phi() { - let classifier = LoopVarClassBox::new(); - let mut inspector = LocalScopeInspectorBox::new(); - - // Variable defined in ALL exit predecessors - inspector.record_definition("x", BasicBlockId(1)); - inspector.record_definition("x", BasicBlockId(2)); - - let builder = BodyLocalPhiBuilder::new(classifier, inspector); - - // BodyLocalExit: defined in all exit preds → needs exit PHI - let needs_phi = - builder.should_generate_exit_phi("x", &[], &[], &[BasicBlockId(1), BasicBlockId(2)]); - - assert!(needs_phi); - } - - #[test] - fn test_body_local_internal_skip_phi() { - let classifier = LoopVarClassBox::new(); - let mut inspector = LocalScopeInspectorBox::new(); - - // Variable defined in SOME exit predecessors (not all) - inspector.record_definition("ch", BasicBlockId(5)); - - let builder = BodyLocalPhiBuilder::new(classifier, inspector); - - // BodyLocalInternal: defined only in block 5, but exit preds are [2, 5] - // → NO exit PHI (Option C fix!) - let needs_phi = - builder.should_generate_exit_phi("ch", &[], &[], &[BasicBlockId(2), BasicBlockId(5)]); - - assert!(!needs_phi); // ← Skip exit PHI! - } - - #[test] - fn test_filter_exit_phi_candidates() { - let classifier = LoopVarClassBox::new(); - let mut inspector = LocalScopeInspectorBox::new(); - - // s, idx: pinned (defined before loop) - // ch: body-local, only in block 5 - // n: body-local, in all exit preds (block 2, 5) - inspector.record_definition("n", BasicBlockId(2)); - inspector.record_definition("n", BasicBlockId(5)); - inspector.record_definition("ch", BasicBlockId(5)); - - let builder = BodyLocalPhiBuilder::new(classifier, inspector); - - let all_vars = vec![ - "s".to_string(), - "idx".to_string(), - "ch".to_string(), - "n".to_string(), - ]; - - let pinned = vec!["s".to_string(), "idx".to_string()]; - let carrier: Vec = vec![]; - - // Phase 26-F-4: empty live_at_exit(live情報なし) - let live_at_exit = std::collections::BTreeSet::new(); - - let phi_vars = builder.filter_exit_phi_candidates( - &all_vars, - &pinned, - &carrier, - &[BasicBlockId(2), BasicBlockId(5)], - &live_at_exit, // Phase 26-F-4 - ); - - // Expected: s, idx, n (ch is BodyLocalInternal → filtered out) - assert_eq!(phi_vars.len(), 3); - assert!(phi_vars.contains(&"s".to_string())); - assert!(phi_vars.contains(&"idx".to_string())); - assert!(phi_vars.contains(&"n".to_string())); - assert!(!phi_vars.contains(&"ch".to_string())); // ← Filtered out! - } - - #[test] - fn test_skip_whitespace_scenario() { - // Real-world scenario from skip_whitespace function - // Parameters: s, idx (pinned) - // Loop carrier: idx - // Body-local: ch (only defined in some exit paths) - - let classifier = LoopVarClassBox::new(); - let mut inspector = LocalScopeInspectorBox::new(); - - // ch is defined only in block 5 (after condition check) - inspector.record_definition("ch", BasicBlockId(5)); - - let builder = BodyLocalPhiBuilder::new(classifier, inspector); - - let all_vars = vec!["s".to_string(), "idx".to_string(), "ch".to_string()]; - let pinned = vec!["s".to_string(), "idx".to_string()]; - let carrier: Vec = vec![]; - - // Exit preds: [block 2 (early break), block 5 (after ch definition)] - let exit_preds = vec![BasicBlockId(2), BasicBlockId(5)]; - - // Phase 26-F-4: empty live_at_exit(live情報なし) - let live_at_exit = std::collections::BTreeSet::new(); - - let phi_vars = builder.filter_exit_phi_candidates( - &all_vars, - &pinned, - &carrier, - &exit_preds, - &live_at_exit, - ); - - // Expected: s, idx (ch filtered out!) - assert_eq!(phi_vars.len(), 2); - assert!(phi_vars.contains(&"s".to_string())); - assert!(phi_vars.contains(&"idx".to_string())); - assert!(!phi_vars.contains(&"ch".to_string())); // ← Option C fix! - } - - #[test] - fn test_classify_variable() { - let classifier = LoopVarClassBox::new(); - let mut inspector = LocalScopeInspectorBox::new(); - - inspector.record_definition("ch", BasicBlockId(5)); - - let builder = BodyLocalPhiBuilder::new(classifier, inspector); - - let class = builder.classify_variable("ch", &[], &[], &[BasicBlockId(2), BasicBlockId(5)]); - - assert_eq!(class, LoopVarClass::BodyLocalInternal); - assert!(!class.needs_exit_phi()); - } - - #[test] - fn test_inspector_mut_access() { - let classifier = LoopVarClassBox::new(); - let inspector = LocalScopeInspectorBox::new(); - let mut builder = BodyLocalPhiBuilder::new(classifier, inspector); - - // Test mutable access - builder - .inspector_mut() - .record_definition("test", BasicBlockId(10)); - - // Verify recording worked - let class = builder.classify_variable("test", &[], &[], &[BasicBlockId(10)]); - - assert_eq!(class, LoopVarClass::BodyLocalExit); - } - - #[test] - fn test_pin_temporary_variables_filtered() { - // __pin$ temporary variables should always be BodyLocalInternal - // and thus filtered out from exit PHI candidates - - let classifier = LoopVarClassBox::new(); - let mut inspector = LocalScopeInspectorBox::new(); - - // Record __pin$ temporary - inspector.record_definition("__pin$42$@binop_lhs", BasicBlockId(5)); - - let builder = BodyLocalPhiBuilder::new(classifier, inspector); - - // Should be classified as BodyLocalInternal - let class = builder.classify_variable("__pin$42$@binop_lhs", &[], &[], &[BasicBlockId(5)]); - - assert_eq!(class, LoopVarClass::BodyLocalInternal); - assert!(!class.needs_exit_phi()); - - // Should be filtered out from exit PHI candidates - let all_vars = vec!["__pin$42$@binop_lhs".to_string(), "s".to_string()]; - let pinned = vec!["s".to_string()]; - - // Phase 26-F-4: empty live_at_exit(live情報なし) - let live_at_exit = std::collections::BTreeSet::new(); - - let phi_vars = builder.filter_exit_phi_candidates( - &all_vars, - &pinned, - &[], - &[BasicBlockId(5)], - &live_at_exit, - ); - - // Only s should remain - assert_eq!(phi_vars.len(), 1); - assert!(phi_vars.contains(&"s".to_string())); - assert!(!phi_vars.contains(&"__pin$42$@binop_lhs".to_string())); - } - - #[test] - fn test_empty_variables() { - let classifier = LoopVarClassBox::new(); - let inspector = LocalScopeInspectorBox::new(); - let builder = BodyLocalPhiBuilder::new(classifier, inspector); - - // Phase 26-F-4: empty live_at_exit(live情報なし) - let live_at_exit = std::collections::BTreeSet::new(); - - let phi_vars = builder.filter_exit_phi_candidates(&[], &[], &[], &[], &live_at_exit); - - assert_eq!(phi_vars.len(), 0); - } - - #[test] - fn test_all_pinned_variables() { - let classifier = LoopVarClassBox::new(); - let inspector = LocalScopeInspectorBox::new(); - let builder = BodyLocalPhiBuilder::new(classifier, inspector); - - let all_vars = vec!["a".to_string(), "b".to_string(), "c".to_string()]; - let pinned = vec!["a".to_string(), "b".to_string(), "c".to_string()]; - - // Phase 26-F-4: empty live_at_exit(live情報なし) - let live_at_exit = std::collections::BTreeSet::new(); - - let phi_vars = builder.filter_exit_phi_candidates( - &all_vars, - &pinned, - &[], - &[BasicBlockId(1), BasicBlockId(2)], - &live_at_exit, - ); - - // All should need exit PHI - assert_eq!(phi_vars.len(), 3); - } - - #[test] - fn test_all_carrier_variables() { - let classifier = LoopVarClassBox::new(); - let inspector = LocalScopeInspectorBox::new(); - let builder = BodyLocalPhiBuilder::new(classifier, inspector); - - let all_vars = vec!["i".to_string(), "j".to_string()]; - let carrier = vec!["i".to_string(), "j".to_string()]; - - // Phase 26-F-4: empty live_at_exit(live情報なし) - let live_at_exit = std::collections::BTreeSet::new(); - - let phi_vars = builder.filter_exit_phi_candidates( - &all_vars, - &[], - &carrier, - &[BasicBlockId(1), BasicBlockId(2)], - &live_at_exit, - ); - - // All should need exit PHI - assert_eq!(phi_vars.len(), 2); - } -} diff --git a/src/mir/phi_core/exit_phi_builder.rs b/src/mir/phi_core/exit_phi_builder.rs deleted file mode 100644 index ff0003e4..00000000 --- a/src/mir/phi_core/exit_phi_builder.rs +++ /dev/null @@ -1,960 +0,0 @@ -//! Exit PHI Builder - Exit時のPHI生成専門Box -//! -//! Phase 26-D: ExitPhiBuilder実装 -//! - Exit PHI生成の完全分離 -//! - Phantom block除外ロジック -//! - BodyLocal変数フィルタリング(BodyLocalPhiBuilder活用) -//! -//! Phase 26-F-4: LoopExitLivenessBox統合 -//! - live_at_exit 変数の計算(LoopExitLivenessBox) -//! - BodyLocalInternal変数の救済(OR判定) -//! - 4箱構成による責務分離完成 -//! -//! Box-First理論: Exit PHI生成という最も複雑な責任を明確に分離し、テスト可能な箱として提供 - -use crate::mir::{BasicBlockId, ValueId}; -use std::collections::{BTreeMap, BTreeSet}; - -use super::body_local_phi_builder::BodyLocalPhiBuilder; -use super::loop_exit_liveness::{ExitLivenessProvider, LoopExitLivenessBox}; // Phase 26-F-4 -use super::loop_snapshot_merge::LoopSnapshotMergeBox; -use super::phi_input_collector::PhiInputCollector; -use super::phi_invariants::PhiInvariantsBox; - -/// Exit PHI生成専門Box -/// -/// # Purpose -/// - Exit時のPHI node生成 -/// - Exit predecessors検証 -/// - Phantom block除外 -/// - Body-local変数の適切な処理 -/// -/// # Responsibility Separation -/// - このBox: Exit PHI生成・Phantom除外 -/// - LoopSnapshotMergeBox: PHI入力生成(static function) -/// - BodyLocalPhiBuilder: Body-local変数判定 -/// - PhiInputCollector: PHI input最適化 -/// -/// # Usage -/// ```ignore -/// let body_local_builder = BodyLocalPhiBuilder::new(classifier, inspector); -/// let mut exit_builder = ExitPhiBuilder::new(body_local_builder); -/// -/// exit_builder.build_exit_phis( -/// ops, -/// exit_id, -/// header_id, -/// branch_source_block, -/// header_vals, -/// exit_snapshots, -/// pinned_vars, -/// carrier_vars, -/// )?; -/// ``` -pub struct ExitPhiBuilder { - /// Body-local variable builder - body_local_builder: BodyLocalPhiBuilder, - /// Exit liveness provider (legacy by default, swappable for MIR scan) - liveness_provider: Box, -} - -impl ExitPhiBuilder { - /// Create a new ExitPhiBuilder - /// - /// # Arguments - /// * `body_local_builder` - BodyLocalPhiBuilder for body-local detection - /// - /// # Returns - /// New ExitPhiBuilder instance - /// - /// # Example - /// ```ignore - /// let classifier = LoopVarClassBox::new(); - /// let inspector = LocalScopeInspectorBox::new(); - /// let body_builder = BodyLocalPhiBuilder::new(classifier, inspector); - /// let exit_builder = ExitPhiBuilder::new(body_builder); - /// ``` - pub fn new(body_local_builder: BodyLocalPhiBuilder) -> Self { - // 環境変数で簡易 MirScan 版を opt-in できるようにする - let use_scan = std::env::var("NYASH_EXIT_LIVE_ENABLE").ok().as_deref() == Some("1"); - if use_scan { - Self::with_liveness( - body_local_builder, - Box::new(crate::mir::phi_core::loop_exit_liveness::MirScanExitLiveness), - ) - } else { - Self::with_liveness(body_local_builder, Box::new(LoopExitLivenessBox::new())) - } - } - - /// Create ExitPhiBuilder with a custom liveness provider (for tests / future MIR scan) - pub fn with_liveness( - body_local_builder: BodyLocalPhiBuilder, - liveness_provider: Box, - ) -> Self { - Self { - body_local_builder, - liveness_provider, - } - } - - /// [LoopForm] Build Exit PHIs - /// - /// # Arguments - /// * `ops` - LoopFormOps trait implementation - /// * `exit_id` - Exit block ID - /// * `header_id` - Loop header block ID - /// * `branch_source_block` - Branch source block ID (early exit path, Case A) - /// * `header_vals` - Header variable values (parameter values) - /// * `exit_snapshots` - Exit predecessor snapshots (from break statements) - /// * `pinned_vars` - Pinned variable names (loop-invariant parameters) - /// * `carrier_vars` - Carrier variable names (loop-modified variables) - /// * `mir_func` - Underlying MIR function (for MirQuery) - /// - /// # Returns - /// Result: Ok(()) on success, Err(msg) on failure - /// - /// # [LoopForm] Process - /// 1. Get exit predecessors (CFG validation) - determines Case A/B - /// 2. Filter phantom blocks (Step 5-5-H) - /// 3. Record definitions in inspector - /// 4. [LoopForm] Generate PHI inputs using LoopSnapshotMergeBox::merge_exit_with_classification - /// - Case A: header+break paths included - /// - Case B: break paths only (header not a predecessor) - /// 5. For each variable, use PhiInputCollector to optimize and generate PHI nodes - /// - /// # Example - /// ```ignore - /// exit_builder.build_exit_phis( - /// ops, - /// BasicBlockId(10), - /// BasicBlockId(5), - /// BasicBlockId(7), - /// &header_vals, - /// &exit_snapshots, - /// &["s", "idx"], - /// &["ch"], - /// )?; - /// ``` - pub fn build_exit_phis( - &mut self, - ops: &mut O, - exit_id: BasicBlockId, - header_id: BasicBlockId, - branch_source_block: BasicBlockId, - header_vals: &BTreeMap, - exit_snapshots: &[(BasicBlockId, BTreeMap)], - pinned_vars: &[String], - carrier_vars: &[String], - ) -> Result<(), String> { - ops.set_current_block(exit_id)?; - - // [LoopForm] 1. Exit predecessorsを取得(CFG検証)- Case A/B判定のキー - // BTreeSet で決定性を確保 - let exit_preds_set = ops.get_block_predecessors(exit_id); - let mut exit_preds: Vec = exit_preds_set.iter().copied().collect(); - exit_preds.sort_by_key(|bb| bb.0); - - // [LoopForm] 2. Phantom blockをフィルタリング(Step 5-5-H) - let filtered_snapshots = self.filter_phantom_blocks(exit_snapshots, &exit_preds_set, ops); - - // [LoopForm] 3. Inspectorに定義を記録(変数分類の基盤) - let inspector = self.body_local_builder.inspector_mut(); - for pinned_name in pinned_vars { - inspector.record_definition(pinned_name, header_id); - } - for carrier_name in carrier_vars { - inspector.record_definition(carrier_name, header_id); - } - for (block_id, snapshot) in &filtered_snapshots { - inspector.record_snapshot(*block_id, snapshot); - } - if exit_preds_set.contains(&branch_source_block) { - inspector.record_snapshot(branch_source_block, header_vals); - } - - // [LoopForm] 4. exit φ 対象変数を決定(BodyLocalInternal を除外) - let mut required_vars: BTreeSet = BTreeSet::new(); - required_vars.extend(header_vals.keys().cloned()); - for (_, snap) in &filtered_snapshots { - required_vars.extend(snap.keys().cloned()); - } - required_vars.extend(pinned_vars.iter().cloned()); - required_vars.extend(carrier_vars.iter().cloned()); - - // Phase 26-F/G: ExitLivenessProvider で live_at_exit を計算(MirQuery 経由) - let query = crate::mir::MirQueryBox::new(ops.mir_function()); - let live_at_exit = self.liveness_provider.compute_live_at_exit( - &query, - exit_id, - header_vals, - exit_snapshots, - ); - - let phi_vars = self.body_local_builder.filter_exit_phi_candidates( - &required_vars.iter().cloned().collect::>(), - pinned_vars, - carrier_vars, - &exit_preds, - &live_at_exit, // Phase 26-F-4: live_at_exit 追加 - ); - - // Fail-Fast invariant(共通箱経由): - // - exit φ 対象に選ばれた変数は、すべての exit predecessor で「定義済み」でなければならない。 - // (Pinned/Carrier/BodyLocalExit のみ / BodyLocalInternal は候補から除外済み) - PhiInvariantsBox::ensure_exit_phi_availability( - &phi_vars, - &exit_preds, - exit_id, - header_id, - self.body_local_builder.inspector(), - )?; - - // Dev trace: which vars became exit-phi candidates - if std::env::var("NYASH_IF_HOLE_TRACE").ok().as_deref() == Some("1") { - eprintln!( - "[exit-phi] exit={:?} header={:?} phi_vars={:?} live_at_exit={:?}", - exit_id, header_id, phi_vars, live_at_exit - ); - } - - let include_header_input = exit_preds_set.contains(&header_id) || exit_preds.is_empty(); - - // 5. PHI生成(PhiInputCollectorで最適化適用) - for var_name in phi_vars { - let mut inputs_map: BTreeMap = BTreeMap::new(); - - if include_header_input { - if let Some(&val) = header_vals.get(&var_name) { - inputs_map.insert(header_id, val); - } - } - - for (bb, snap) in &filtered_snapshots { - if let Some(&val) = snap.get(&var_name) { - inputs_map.insert(*bb, val); - } - } - - // Dev trace: exit φ inputs - if std::env::var("NYASH_IF_HOLE_TRACE").ok().as_deref() == Some("1") { - eprintln!( - "[exit-phi] var='{}' preds={:?} header_included={} inputs={:?}", - var_name, exit_preds, include_header_input, inputs_map - ); - } - - let mut inputs: Vec<(BasicBlockId, ValueId)> = inputs_map.into_iter().collect(); - LoopSnapshotMergeBox::sanitize_inputs(&mut inputs); - - // incoming 0 → header で直接バインド(最低限定義を保証) - if inputs.is_empty() { - if let Some(&val) = header_vals.get(&var_name) { - ops.update_var(var_name, val); - } - continue; - } - - let mut collector = PhiInputCollector::new(); - collector.add_snapshot(&inputs); - - // Sanitize + Optimize - collector.sanitize(); - if let Some(same_val) = collector.optimize_same_value() { - // 同値PHI → 直接バインド - ops.update_var(var_name, same_val); - } else { - // 異なる値 → PHI生成 - let final_inputs = collector.finalize(); - let phi_id = ops.new_value(); - ops.emit_phi(phi_id, final_inputs)?; - ops.update_var(var_name, phi_id); - } - } - - Ok(()) - } - - /// Filter phantom blocks - /// - /// # Arguments - /// * `exit_snapshots` - Raw exit predecessor snapshots - /// * `exit_preds` - Actual CFG exit predecessors - /// * `ops` - LoopFormOps for block existence check - /// - /// # Returns - /// Filtered snapshots (phantom blocks removed) - /// - /// # Phantom Block Definition - /// - Non-existent block (removed during optimization) - /// - Not a CFG predecessor (no edge to exit block) - /// - /// # Example - /// ```ignore - /// let filtered = exit_builder.filter_phantom_blocks( - /// &snapshots, - /// &exit_preds_set, - /// ops, - /// ); - /// // Removed phantom blocks - /// ``` - fn filter_phantom_blocks( - &self, - exit_snapshots: &[(BasicBlockId, BTreeMap)], - exit_preds: &BTreeSet, - ops: &O, - ) -> Vec<(BasicBlockId, BTreeMap)> { - let mut filtered = Vec::new(); - for (block_id, snapshot) in exit_snapshots { - if !ops.block_exists(*block_id) { - continue; // Non-existent block - } - if !exit_preds.contains(block_id) { - continue; // Not a CFG predecessor - } - filtered.push((*block_id, snapshot.clone())); - } - filtered - } - - /// Get mutable reference to body local builder (for testing) - #[cfg(test)] - pub fn body_local_builder_mut(&mut self) -> &mut BodyLocalPhiBuilder { - &mut self.body_local_builder - } -} - -/// LoopFormOps trait - Operations needed by ExitPhiBuilder -/// -/// # Purpose -/// - Abstract MIR builder operations for testability -/// - Enable mock implementation for unit tests -pub trait LoopFormOps { - /// Set current block - fn set_current_block(&mut self, block_id: BasicBlockId) -> Result<(), String>; - - /// Get block predecessors - fn get_block_predecessors(&self, block_id: BasicBlockId) -> BTreeSet; - - /// Check if block exists - fn block_exists(&self, block_id: BasicBlockId) -> bool; - - /// Create new value ID - fn new_value(&mut self) -> ValueId; - - /// Emit PHI instruction - fn emit_phi( - &mut self, - phi_id: ValueId, - inputs: Vec<(BasicBlockId, ValueId)>, - ) -> Result<(), String>; - - /// Update variable binding - fn update_var(&mut self, var_name: String, value_id: ValueId); - - /// Access underlying MirFunction (for MirQuery) - fn mir_function(&self) -> &crate::mir::MirFunction; -} - -// Bridge: allow any LoopFormOps (loopform_builder版) to be used here -impl LoopFormOps for T { - fn set_current_block(&mut self, block_id: BasicBlockId) -> Result<(), String> { - crate::mir::phi_core::loopform_builder::LoopFormOps::set_current_block(self, block_id) - } - - fn get_block_predecessors(&self, block_id: BasicBlockId) -> BTreeSet { - crate::mir::phi_core::loopform_builder::LoopFormOps::get_block_predecessors(self, block_id) - .into_iter() - .collect() - } - - fn block_exists(&self, block_id: BasicBlockId) -> bool { - crate::mir::phi_core::loopform_builder::LoopFormOps::block_exists(self, block_id) - } - - fn new_value(&mut self) -> ValueId { - crate::mir::phi_core::loopform_builder::LoopFormOps::new_value(self) - } - - fn emit_phi( - &mut self, - phi_id: ValueId, - inputs: Vec<(BasicBlockId, ValueId)>, - ) -> Result<(), String> { - crate::mir::phi_core::loopform_builder::LoopFormOps::emit_phi(self, phi_id, inputs) - } - - fn update_var(&mut self, var_name: String, value_id: ValueId) { - crate::mir::phi_core::loopform_builder::LoopFormOps::update_var(self, var_name, value_id) - } - - fn mir_function(&self) -> &crate::mir::MirFunction { - crate::mir::phi_core::loopform_builder::LoopFormOps::mir_function(self) - } -} - -// ============================================================================ -// Phase 27.6-2: JoinIR Exit φ バイパス用ヘルパー関数 -// ============================================================================ - -/// JoinIR Exit φ 縮退実験トグル -/// -/// - NYASH_JOINIR_EXPERIMENT=1 -/// - NYASH_JOINIR_EXIT_EXP=1 -/// -/// の両方が立っているときだけ true。 -pub(crate) fn joinir_exit_bypass_enabled() -> bool { - crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_EXPERIMENT") - && crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_EXIT_EXP") -} - -/// Exit φ バイパス対象の関数かどうか -/// -/// 当面は minimal/trim の 2 本だけ: -/// - Main.skip/1 -/// - FuncScannerBox.trim/1 -pub(crate) fn is_joinir_exit_bypass_target(func_name: &str) -> bool { - matches!(func_name, "Main.skip/1" | "FuncScannerBox.trim/1") -} - -// ============================================================================ -// Unit Tests -// ============================================================================ - -#[cfg(test)] -mod tests { - use super::*; - use crate::mir::phi_core::local_scope_inspector::LocalScopeInspectorBox; - use crate::mir::phi_core::loop_var_classifier::LoopVarClassBox; - - /// Mock LoopFormOps for testing - struct MockOps { - current_block: Option, - blocks: BTreeSet, - predecessors: BTreeMap>, - next_value_id: u32, - emitted_phis: Vec<(ValueId, Vec<(BasicBlockId, ValueId)>)>, - var_bindings: BTreeMap, - func: crate::mir::MirFunction, - } - - impl MockOps { - fn new() -> Self { - // Minimal function for testing (1 block) - let sig = crate::mir::function::FunctionSignature { - name: "mock".to_string(), - params: vec![], - return_type: crate::mir::MirType::Void, - effects: crate::mir::effect::EffectMask::PURE, - }; - let func = crate::mir::MirFunction::new(sig, BasicBlockId(0)); - Self { - current_block: None, - blocks: BTreeSet::new(), - predecessors: BTreeMap::new(), - next_value_id: 100, - emitted_phis: Vec::new(), - var_bindings: BTreeMap::new(), - func, - } - } - - fn add_block(&mut self, block_id: BasicBlockId) { - self.blocks.insert(block_id); - } - - fn add_predecessor(&mut self, block_id: BasicBlockId, pred: BasicBlockId) { - self.predecessors - .entry(block_id) - .or_insert_with(BTreeSet::new) - .insert(pred); - } - } - - impl LoopFormOps for MockOps { - fn set_current_block(&mut self, block_id: BasicBlockId) -> Result<(), String> { - self.current_block = Some(block_id); - Ok(()) - } - - fn get_block_predecessors(&self, block_id: BasicBlockId) -> BTreeSet { - self.predecessors - .get(&block_id) - .cloned() - .unwrap_or_default() - } - - fn block_exists(&self, block_id: BasicBlockId) -> bool { - self.blocks.contains(&block_id) - } - - fn new_value(&mut self) -> ValueId { - let id = ValueId(self.next_value_id); - self.next_value_id += 1; - id - } - - fn emit_phi( - &mut self, - phi_id: ValueId, - inputs: Vec<(BasicBlockId, ValueId)>, - ) -> Result<(), String> { - self.emitted_phis.push((phi_id, inputs)); - Ok(()) - } - - fn update_var(&mut self, var_name: String, value_id: ValueId) { - self.var_bindings.insert(var_name, value_id); - } - - fn mir_function(&self) -> &crate::mir::MirFunction { - &self.func - } - } - - #[test] - fn test_new() { - let classifier = LoopVarClassBox::new(); - let inspector = LocalScopeInspectorBox::new(); - let body_builder = BodyLocalPhiBuilder::new(classifier, inspector); - let _exit_builder = ExitPhiBuilder::new(body_builder); - - assert!(true, "ExitPhiBuilder created successfully"); - } - - #[test] - fn test_filter_phantom_blocks_all_valid() { - let classifier = LoopVarClassBox::new(); - let inspector = LocalScopeInspectorBox::new(); - let body_builder = BodyLocalPhiBuilder::new(classifier, inspector); - let exit_builder = ExitPhiBuilder::new(body_builder); - - let mut ops = MockOps::new(); - ops.add_block(BasicBlockId(1)); - ops.add_block(BasicBlockId(2)); - - let mut exit_preds = BTreeSet::new(); - exit_preds.insert(BasicBlockId(1)); - exit_preds.insert(BasicBlockId(2)); - - let mut snapshot1 = BTreeMap::new(); - snapshot1.insert("x".to_string(), ValueId(10)); - - let mut snapshot2 = BTreeMap::new(); - snapshot2.insert("x".to_string(), ValueId(20)); - - let snapshots = vec![ - (BasicBlockId(1), snapshot1.clone()), - (BasicBlockId(2), snapshot2.clone()), - ]; - - let filtered = exit_builder.filter_phantom_blocks(&snapshots, &exit_preds, &ops); - - assert_eq!(filtered.len(), 2); - assert_eq!(filtered[0].0, BasicBlockId(1)); - assert_eq!(filtered[1].0, BasicBlockId(2)); - } - - #[test] - fn test_filter_phantom_blocks_non_existent() { - let classifier = LoopVarClassBox::new(); - let inspector = LocalScopeInspectorBox::new(); - let body_builder = BodyLocalPhiBuilder::new(classifier, inspector); - let exit_builder = ExitPhiBuilder::new(body_builder); - - let mut ops = MockOps::new(); - ops.add_block(BasicBlockId(1)); - // Block 2 does not exist - - let mut exit_preds = BTreeSet::new(); - exit_preds.insert(BasicBlockId(1)); - exit_preds.insert(BasicBlockId(2)); - - let mut snapshot1 = BTreeMap::new(); - snapshot1.insert("x".to_string(), ValueId(10)); - - let mut snapshot2 = BTreeMap::new(); - snapshot2.insert("x".to_string(), ValueId(20)); - - let snapshots = vec![ - (BasicBlockId(1), snapshot1.clone()), - (BasicBlockId(2), snapshot2.clone()), // Phantom block - ]; - - let filtered = exit_builder.filter_phantom_blocks(&snapshots, &exit_preds, &ops); - - // Only block 1 remains - assert_eq!(filtered.len(), 1); - assert_eq!(filtered[0].0, BasicBlockId(1)); - } - - #[test] - fn test_filter_phantom_blocks_not_predecessor() { - let classifier = LoopVarClassBox::new(); - let inspector = LocalScopeInspectorBox::new(); - let body_builder = BodyLocalPhiBuilder::new(classifier, inspector); - let exit_builder = ExitPhiBuilder::new(body_builder); - - let mut ops = MockOps::new(); - ops.add_block(BasicBlockId(1)); - ops.add_block(BasicBlockId(2)); - - let mut exit_preds = BTreeSet::new(); - exit_preds.insert(BasicBlockId(1)); - // Block 2 is not a predecessor - - let mut snapshot1 = BTreeMap::new(); - snapshot1.insert("x".to_string(), ValueId(10)); - - let mut snapshot2 = BTreeMap::new(); - snapshot2.insert("x".to_string(), ValueId(20)); - - let snapshots = vec![ - (BasicBlockId(1), snapshot1.clone()), - (BasicBlockId(2), snapshot2.clone()), // Not a predecessor - ]; - - let filtered = exit_builder.filter_phantom_blocks(&snapshots, &exit_preds, &ops); - - // Only block 1 remains - assert_eq!(filtered.len(), 1); - assert_eq!(filtered[0].0, BasicBlockId(1)); - } - - #[test] - fn test_filter_phantom_blocks_empty() { - let classifier = LoopVarClassBox::new(); - let inspector = LocalScopeInspectorBox::new(); - let body_builder = BodyLocalPhiBuilder::new(classifier, inspector); - let exit_builder = ExitPhiBuilder::new(body_builder); - - let ops = MockOps::new(); - let exit_preds = BTreeSet::new(); - - let snapshots = vec![]; - - let filtered = exit_builder.filter_phantom_blocks(&snapshots, &exit_preds, &ops); - - assert_eq!(filtered.len(), 0); - } - - #[test] - fn test_build_exit_phis_simple_pinned() { - let classifier = LoopVarClassBox::new(); - let inspector = LocalScopeInspectorBox::new(); - let body_builder = BodyLocalPhiBuilder::new(classifier, inspector); - let mut exit_builder = ExitPhiBuilder::new(body_builder); - - let mut ops = MockOps::new(); - - // Setup blocks - let header_id = BasicBlockId(5); - let exit_id = BasicBlockId(10); - let branch_source = BasicBlockId(7); - - ops.add_block(header_id); - ops.add_block(exit_id); - ops.add_block(branch_source); - - ops.add_predecessor(exit_id, branch_source); - - // Header vals (pinned variable) - let mut header_vals = BTreeMap::new(); - header_vals.insert("s".to_string(), ValueId(1)); - - let exit_snapshots = vec![]; - let pinned_vars = vec!["s".to_string()]; - let carrier_vars = vec![]; - - let _func = ops.func.clone(); - let result = exit_builder.build_exit_phis( - &mut ops, - exit_id, - header_id, - branch_source, - &header_vals, - &exit_snapshots, - &pinned_vars, - &carrier_vars, - ); - - assert!(result.is_ok()); - - // Check current block is set - assert_eq!(ops.current_block, Some(exit_id)); - - // Check variable binding - assert!(ops.var_bindings.contains_key("s")); - } - - #[test] - fn test_build_exit_phis_with_carrier() { - let classifier = LoopVarClassBox::new(); - let inspector = LocalScopeInspectorBox::new(); - let body_builder = BodyLocalPhiBuilder::new(classifier, inspector); - let mut exit_builder = ExitPhiBuilder::new(body_builder); - - let mut ops = MockOps::new(); - - // Setup blocks - let header_id = BasicBlockId(5); - let exit_id = BasicBlockId(10); - let branch_source = BasicBlockId(7); - let exit_pred1 = BasicBlockId(8); - - ops.add_block(header_id); - ops.add_block(exit_id); - ops.add_block(branch_source); - ops.add_block(exit_pred1); - - ops.add_predecessor(exit_id, branch_source); - ops.add_predecessor(exit_id, exit_pred1); - - // Header vals - let mut header_vals = BTreeMap::new(); - header_vals.insert("s".to_string(), ValueId(1)); - header_vals.insert("idx".to_string(), ValueId(2)); - - // Exit snapshot (idx modified) - let mut snapshot1 = BTreeMap::new(); - snapshot1.insert("s".to_string(), ValueId(1)); - snapshot1.insert("idx".to_string(), ValueId(10)); // Modified - - let exit_snapshots = vec![(exit_pred1, snapshot1)]; - let pinned_vars = vec!["s".to_string()]; - let carrier_vars = vec!["idx".to_string()]; - - let _func = ops.func.clone(); - let result = exit_builder.build_exit_phis( - &mut ops, - exit_id, - header_id, - branch_source, - &header_vals, - &exit_snapshots, - &pinned_vars, - &carrier_vars, - ); - - assert!(result.is_ok()); - - // Check variables are bound - assert!(ops.var_bindings.contains_key("s")); - assert!(ops.var_bindings.contains_key("idx")); - } - - #[test] - fn test_build_exit_phis_skip_whitespace_scenario() { - // Realistic skip_whitespace scenario - let classifier = LoopVarClassBox::new(); - let inspector = LocalScopeInspectorBox::new(); - let body_builder = BodyLocalPhiBuilder::new(classifier, inspector); - let mut exit_builder = ExitPhiBuilder::new(body_builder); - - let mut ops = MockOps::new(); - - // Setup blocks - let header_id = BasicBlockId(5); - let exit_id = BasicBlockId(10); - let branch_source = BasicBlockId(7); // Early break - let exit_pred1 = BasicBlockId(8); // After loop body - - ops.add_block(header_id); - ops.add_block(exit_id); - ops.add_block(branch_source); - ops.add_block(exit_pred1); - - ops.add_predecessor(exit_id, branch_source); - ops.add_predecessor(exit_id, exit_pred1); - - // Header vals (parameters) - let mut header_vals = BTreeMap::new(); - header_vals.insert("s".to_string(), ValueId(1)); - header_vals.insert("idx".to_string(), ValueId(2)); - - // Exit pred 1: idx modified, ch defined - let mut snapshot1 = BTreeMap::new(); - snapshot1.insert("s".to_string(), ValueId(1)); - snapshot1.insert("idx".to_string(), ValueId(10)); // Modified - snapshot1.insert("ch".to_string(), ValueId(15)); // Body-local - - let exit_snapshots = vec![(exit_pred1, snapshot1)]; - let pinned_vars = vec!["s".to_string()]; - let carrier_vars = vec!["idx".to_string()]; - - let _func = ops.func.clone(); - let result = exit_builder.build_exit_phis( - &mut ops, - exit_id, - header_id, - branch_source, - &header_vals, - &exit_snapshots, - &pinned_vars, - &carrier_vars, - ); - - assert!(result.is_ok()); - - // Check variables are bound - assert!(ops.var_bindings.contains_key("s")); - assert!(ops.var_bindings.contains_key("idx")); - } - - #[test] - fn test_build_exit_phis_phantom_block_filtered() { - let classifier = LoopVarClassBox::new(); - let inspector = LocalScopeInspectorBox::new(); - let body_builder = BodyLocalPhiBuilder::new(classifier, inspector); - let mut exit_builder = ExitPhiBuilder::new(body_builder); - - let mut ops = MockOps::new(); - - // Setup blocks - let header_id = BasicBlockId(5); - let exit_id = BasicBlockId(10); - let branch_source = BasicBlockId(7); - let phantom_block = BasicBlockId(99); // Does not exist - - ops.add_block(header_id); - ops.add_block(exit_id); - ops.add_block(branch_source); - // phantom_block NOT added - - ops.add_predecessor(exit_id, branch_source); - - // Header vals - let mut header_vals = BTreeMap::new(); - header_vals.insert("x".to_string(), ValueId(1)); - - // Phantom snapshot (should be filtered) - let mut phantom_snapshot = BTreeMap::new(); - phantom_snapshot.insert("x".to_string(), ValueId(999)); - - let exit_snapshots = vec![(phantom_block, phantom_snapshot)]; - let pinned_vars = vec!["x".to_string()]; - let carrier_vars = vec![]; - - let _func = ops.func.clone(); - let result = exit_builder.build_exit_phis( - &mut ops, - exit_id, - header_id, - branch_source, - &header_vals, - &exit_snapshots, - &pinned_vars, - &carrier_vars, - ); - - assert!(result.is_ok()); - - // Phantom block should be filtered, so x should be bound - assert!(ops.var_bindings.contains_key("x")); - } - - #[test] - fn test_build_exit_phis_empty_snapshots() { - let classifier = LoopVarClassBox::new(); - let inspector = LocalScopeInspectorBox::new(); - let body_builder = BodyLocalPhiBuilder::new(classifier, inspector); - let mut exit_builder = ExitPhiBuilder::new(body_builder); - - let mut ops = MockOps::new(); - - // Setup blocks - let header_id = BasicBlockId(5); - let exit_id = BasicBlockId(10); - let branch_source = BasicBlockId(7); - - ops.add_block(header_id); - ops.add_block(exit_id); - ops.add_block(branch_source); - - ops.add_predecessor(exit_id, branch_source); - - // Header vals - let mut header_vals = BTreeMap::new(); - header_vals.insert("x".to_string(), ValueId(1)); - - let exit_snapshots = vec![]; - let pinned_vars = vec!["x".to_string()]; - let carrier_vars = vec![]; - - let _func = ops.func.clone(); - let result = exit_builder.build_exit_phis( - &mut ops, - exit_id, - header_id, - branch_source, - &header_vals, - &exit_snapshots, - &pinned_vars, - &carrier_vars, - ); - - assert!(result.is_ok()); - - // No snapshots, x should be bound directly - assert!(ops.var_bindings.contains_key("x")); - } - - #[test] - fn test_build_exit_phis_no_predecessors() { - let classifier = LoopVarClassBox::new(); - let inspector = LocalScopeInspectorBox::new(); - let body_builder = BodyLocalPhiBuilder::new(classifier, inspector); - let mut exit_builder = ExitPhiBuilder::new(body_builder); - - let mut ops = MockOps::new(); - - // Setup blocks - let header_id = BasicBlockId(5); - let exit_id = BasicBlockId(10); - let branch_source = BasicBlockId(7); - - ops.add_block(header_id); - ops.add_block(exit_id); - ops.add_block(branch_source); - - // No predecessors added - - // Header vals - let mut header_vals = BTreeMap::new(); - header_vals.insert("x".to_string(), ValueId(1)); - - let exit_snapshots = vec![]; - let pinned_vars = vec!["x".to_string()]; - let carrier_vars = vec![]; - - let _func = ops.func.clone(); - let result = exit_builder.build_exit_phis( - &mut ops, - exit_id, - header_id, - branch_source, - &header_vals, - &exit_snapshots, - &pinned_vars, - &carrier_vars, - ); - - assert!(result.is_ok()); - - // No predecessors, x should still be bound - assert!(ops.var_bindings.contains_key("x")); - } - - #[test] - fn test_accessors() { - let classifier = LoopVarClassBox::new(); - let inspector = LocalScopeInspectorBox::new(); - let body_builder = BodyLocalPhiBuilder::new(classifier, inspector); - let mut exit_builder = ExitPhiBuilder::new(body_builder); - - // Test accessors - let _builder_ref = exit_builder.body_local_builder_mut(); - - assert!(true, "Accessors work correctly"); - } -} diff --git a/src/mir/phi_core/header_phi_builder.rs b/src/mir/phi_core/header_phi_builder.rs deleted file mode 100644 index 5f1f5283..00000000 --- a/src/mir/phi_core/header_phi_builder.rs +++ /dev/null @@ -1,627 +0,0 @@ -//! Header PHI Builder - Header PHI生成専門Box -//! -//! Phase 26-C-2: HeaderPhiBuilder実装 -//! - Loop header PHI node生成の専門化 -//! - Preheader入力設定 -//! - Latch値更新(seal時) -//! -//! Box-First理論: Header PHI生成の責任を明確に分離し、テスト可能な箱として提供 -//! -//! # Phase 27.4 移行計画 -//! -//! **Header φ の責務は JoinIR の loop_step 引数に順次移していく。** -//! Rust 側の φ は当面互換のため残すが、JoinIR 経路では以下の方針で縮退していく: -//! -//! 1. **Phase 27.4-A**: JoinIR 側で Pinned/Carrier を loop_step 引数として表現(完了) -//! 2. **Phase 27.4-B**: HeaderPhiBuilder に JoinIR 実験フラグを追加(現在) -//! 3. **Phase 27.4-C**: JoinIR 経路で Header φ をスキップ可能にする(将来) -//! 4. **Phase 27.5+**: HeaderPhiBuilder を JoinIR 前段に完全統合(長期目標) -//! -//! **原則**: 本線 MIR/LoopForm → VM の挙動は一切変えない。JoinIR はトグル付き実験経路。 - -use crate::mir::{BasicBlockId, ValueId}; -use std::collections::HashMap; - -/// Phase 27.4C Cleanup: この関数は削除され、get_loop_bypass_flags() に統合されました。 -/// 直接 get_loop_bypass_flags() を使用してください。 -/// -/// 理由: 単一の呼び出し元しかなく、API サーフェスを縮小するため。 - -/// Phase 27.4C Cleanup: JoinIR Header φ バイパス対象関数リスト -/// -/// Phase 27.4-C のスコープは以下の 2 関数のみ: -/// - `Main.skip/1` (minimal_ssa_skip_ws.hako) -/// - `FuncScannerBox.trim/1` (funcscanner_trim_min.hako) -/// -/// **重要**: 他の関数では Header φ を絶対にスキップしないこと。 -const JOINIR_HEADER_BYPASS_TARGETS: &[&str] = &["Main.skip/1", "FuncScannerBox.trim/1"]; - -/// Phase 27.4-C: JoinIR Header φ バイパス対象関数かチェック -/// -/// `JOINIR_HEADER_BYPASS_TARGETS` に含まれる関数のみ true を返す。 -pub(crate) fn is_joinir_header_bypass_target(fn_name: &str) -> bool { - JOINIR_HEADER_BYPASS_TARGETS.contains(&fn_name) -} - -/// Phase 27.4-C Refactor: JoinIR Loop φ バイパスフラグ統合 -/// -/// Header φ と Exit φ のバイパスフラグを一箇所で管理。 -/// 将来的に Exit φ バイパス(Phase 27.6-2)とも統合可能。 -#[derive(Debug, Clone, Copy, Default)] -pub(crate) struct LoopBypassFlags { - /// Header φ バイパスが有効か - pub header: bool, - // Phase 30: exit フィールド削除(完全未使用、将来 JoinIR で代替予定) -} - -/// Phase 27.4-C Refactor: JoinIR Loop φ バイパスフラグを取得 -/// -/// **用途**: Header/Exit φ バイパスの判定を一元化。 -/// 複数箇所での重複したトグル判定を避ける。 -/// -/// # Arguments -/// - `fn_name` - 関数名(例: "Main.skip/1") -/// -/// # Returns -/// - `LoopBypassFlags` - Header/Exit バイパスの有効状態 -/// -/// # Example -/// ```rust -/// let flags = get_loop_bypass_flags("Main.skip/1"); -/// if flags.header { -/// // Header φ 生成をスキップ -/// } -/// ``` -pub(crate) fn get_loop_bypass_flags(fn_name: &str) -> LoopBypassFlags { - // Phase 27.4C Cleanup: joinir_header_experiment_enabled() をインライン化 - let joinir_exp = crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_EXPERIMENT"); - let header_exp = crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_HEADER_EXP"); - - LoopBypassFlags { - header: joinir_exp && header_exp && is_joinir_header_bypass_target(fn_name), - // Phase 30: exit フィールド削除済み - } -} - -/// Header PHI生成専門Box -/// -/// # Purpose -/// - Loop headerでのPHI node生成 -/// - Pinned/Carrier変数の区別管理 -/// - Seal時のlatch/continue入力追加 -/// -/// # Responsibility Separation -/// - このBox: Header PHI生成・seal処理 -/// - PhiInputCollector: PHI入力収集・最適化 -/// - LoopSnapshotManager: Snapshot管理 -/// -/// # Usage -/// ```ignore -/// let mut builder = HeaderPhiBuilder::new(); -/// -/// // Pinned変数のPHI準備 -/// builder.prepare_pinned_phi( -/// "x".to_string(), -/// ValueId(10), // phi_id -/// ValueId(1), // param_value -/// ValueId(2), // preheader_copy -/// ); -/// -/// // Carrier変数のPHI準備 -/// builder.prepare_carrier_phi( -/// "i".to_string(), -/// ValueId(20), // phi_id -/// ValueId(0), // init_value -/// ValueId(3), // preheader_copy -/// ); -/// -/// // PHI情報を取得してemit -/// for phi_info in builder.pinned_phis() { -/// // emit_phi(phi_info.phi_id, ...) -/// } -/// ``` -#[derive(Debug, Clone, Default)] -pub struct HeaderPhiBuilder { - /// Pinned変数のPHI情報 - pinned_phis: Vec, - /// Carrier変数のPHI情報 - carrier_phis: Vec, -} - -/// Pinned変数PHI情報 -/// -/// # Fields -/// - `var_name`: 変数名 -/// - `phi_id`: Header PHIのValueId -/// - `param_value`: 元のパラメータValueId -/// - `preheader_copy`: PreheaderでのCopy ValueId -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct PinnedPhiInfo { - /// Variable name - pub var_name: String, - /// PHI node ValueId - pub phi_id: ValueId, - /// Original parameter ValueId - pub param_value: ValueId, - /// Preheader copy ValueId - pub preheader_copy: ValueId, -} - -/// Carrier変数PHI情報 -/// -/// # Fields -/// - `var_name`: 変数名 -/// - `phi_id`: Header PHIのValueId -/// - `init_value`: 初期ValueId -/// - `preheader_copy`: PreheaderでのCopy ValueId -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct CarrierPhiInfo { - /// Variable name - pub var_name: String, - /// PHI node ValueId - pub phi_id: ValueId, - /// Initial ValueId - pub init_value: ValueId, - /// Preheader copy ValueId - pub preheader_copy: ValueId, -} - -impl HeaderPhiBuilder { - /// Create a new HeaderPhiBuilder - /// - /// # Returns - /// New builder with empty PHI lists - /// - /// # Example - /// ```ignore - /// let builder = HeaderPhiBuilder::new(); - /// ``` - pub fn new() -> Self { - // Phase 27.4-B/27.4C Cleanup: JoinIR 実験フラグのチェック(ログ出力のみ、挙動変更なし) - if crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_HEADER_EXP") { - eprintln!( - "[HeaderPhiBuilder] JoinIR experiment flag is ON (NYASH_JOINIR_HEADER_EXP=1)" - ); - } - Self::default() - } - - /// Prepare pinned variable PHI - /// - /// # Arguments - /// * `var_name` - Variable name - /// * `phi_id` - PHI node ValueId - /// * `param_value` - Original parameter ValueId - /// * `preheader_copy` - Preheader copy ValueId - /// - /// # Example - /// ```ignore - /// builder.prepare_pinned_phi( - /// "x".to_string(), - /// ValueId(10), - /// ValueId(1), - /// ValueId(2), - /// ); - /// ``` - pub fn prepare_pinned_phi( - &mut self, - var_name: String, - phi_id: ValueId, - param_value: ValueId, - preheader_copy: ValueId, - ) { - self.pinned_phis.push(PinnedPhiInfo { - var_name, - phi_id, - param_value, - preheader_copy, - }); - } - - /// Prepare carrier variable PHI - /// - /// # Arguments - /// * `var_name` - Variable name - /// * `phi_id` - PHI node ValueId - /// * `init_value` - Initial ValueId - /// * `preheader_copy` - Preheader copy ValueId - /// - /// # Example - /// ```ignore - /// builder.prepare_carrier_phi( - /// "i".to_string(), - /// ValueId(20), - /// ValueId(0), - /// ValueId(3), - /// ); - /// ``` - pub fn prepare_carrier_phi( - &mut self, - var_name: String, - phi_id: ValueId, - init_value: ValueId, - preheader_copy: ValueId, - ) { - self.carrier_phis.push(CarrierPhiInfo { - var_name, - phi_id, - init_value, - preheader_copy, - }); - } - - /// Get pinned PHI information - /// - /// # Returns - /// Slice of pinned PHI info - /// - /// # Example - /// ```ignore - /// for phi_info in builder.pinned_phis() { - /// println!("Pinned PHI: {} → {:?}", phi_info.var_name, phi_info.phi_id); - /// } - /// ``` - pub fn pinned_phis(&self) -> &[PinnedPhiInfo] { - &self.pinned_phis - } - - /// Get carrier PHI information - /// - /// # Returns - /// Slice of carrier PHI info - /// - /// # Example - /// ```ignore - /// for phi_info in builder.carrier_phis() { - /// println!("Carrier PHI: {} → {:?}", phi_info.var_name, phi_info.phi_id); - /// } - /// ``` - pub fn carrier_phis(&self) -> &[CarrierPhiInfo] { - &self.carrier_phis - } - - /// Get pinned PHI count - /// - /// # Returns - /// Number of pinned PHIs - pub fn pinned_phi_count(&self) -> usize { - self.pinned_phis.len() - } - - /// Get carrier PHI count - /// - /// # Returns - /// Number of carrier PHIs - pub fn carrier_phi_count(&self) -> usize { - self.carrier_phis.len() - } - - /// Get total PHI count - /// - /// # Returns - /// Total number of PHIs (pinned + carrier) - pub fn total_phi_count(&self) -> usize { - self.pinned_phi_count() + self.carrier_phi_count() - } - - /// Find pinned PHI by variable name - /// - /// # Arguments - /// * `var_name` - Variable name to search - /// - /// # Returns - /// Option containing pinned PHI info if found - /// - /// # Example - /// ```ignore - /// if let Some(phi_info) = builder.find_pinned_phi("x") { - /// println!("Found pinned PHI: {:?}", phi_info.phi_id); - /// } - /// ``` - pub fn find_pinned_phi(&self, var_name: &str) -> Option<&PinnedPhiInfo> { - self.pinned_phis.iter().find(|phi| phi.var_name == var_name) - } - - /// Find carrier PHI by variable name - /// - /// # Arguments - /// * `var_name` - Variable name to search - /// - /// # Returns - /// Option containing carrier PHI info if found - /// - /// # Example - /// ```ignore - /// if let Some(phi_info) = builder.find_carrier_phi("i") { - /// println!("Found carrier PHI: {:?}", phi_info.phi_id); - /// } - /// ``` - pub fn find_carrier_phi(&self, var_name: &str) -> Option<&CarrierPhiInfo> { - self.carrier_phis - .iter() - .find(|phi| phi.var_name == var_name) - } - - /// Build PHI inputs for sealing (helper for actual seal implementation) - /// - /// # Arguments - /// * `var_name` - Variable name - /// * `preheader_id` - Preheader block ID - /// * `preheader_copy` - Preheader copy ValueId - /// * `latch_id` - Latch block ID - /// * `latch_value` - Latch ValueId - /// * `continue_snapshots` - Continue snapshot list - /// - /// # Returns - /// PHI inputs as (BasicBlockId, ValueId) pairs - /// - /// # Note - /// This is a helper method. Actual PHI emission should be done by the caller. - pub fn build_phi_inputs_for_seal( - &self, - var_name: &str, - preheader_id: BasicBlockId, - preheader_copy: ValueId, - latch_id: BasicBlockId, - latch_value: ValueId, - continue_snapshots: &[(BasicBlockId, HashMap)], - ) -> Vec<(BasicBlockId, ValueId)> { - use crate::mir::phi_core::phi_input_collector::PhiInputCollector; - - let mut collector = PhiInputCollector::new(); - - // Add preheader input - collector.add_preheader(preheader_id, preheader_copy); - - // Add continue snapshot inputs - for (cid, snapshot) in continue_snapshots { - if let Some(&value) = snapshot.get(var_name) { - collector.add_snapshot(&[(*cid, value)]); - } - } - - // Add latch input - collector.add_latch(latch_id, latch_value); - - // Sanitize and optimize - collector.sanitize(); - - // Check for same-value optimization - if let Some(_same_value) = collector.optimize_same_value() { - // All inputs are the same → PHI can be eliminated - // Return empty vec to signal PHI elimination - return Vec::new(); - } - - collector.finalize() - } - - /// Clear all PHI information - /// - /// # Purpose - /// Reset builder to initial state (useful for testing or reuse) - /// - /// # Example - /// ```ignore - /// builder.clear(); - /// assert_eq!(builder.total_phi_count(), 0); - /// ``` - pub fn clear(&mut self) { - self.pinned_phis.clear(); - self.carrier_phis.clear(); - } -} - -// ============================================================================ -// Unit Tests -// ============================================================================ - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_new() { - let builder = HeaderPhiBuilder::new(); - assert_eq!(builder.pinned_phi_count(), 0); - assert_eq!(builder.carrier_phi_count(), 0); - assert_eq!(builder.total_phi_count(), 0); - } - - #[test] - fn test_prepare_pinned_phi() { - let mut builder = HeaderPhiBuilder::new(); - builder.prepare_pinned_phi("x".to_string(), ValueId(10), ValueId(1), ValueId(2)); - - assert_eq!(builder.pinned_phi_count(), 1); - let phi = &builder.pinned_phis()[0]; - assert_eq!(phi.var_name, "x"); - assert_eq!(phi.phi_id, ValueId(10)); - assert_eq!(phi.param_value, ValueId(1)); - assert_eq!(phi.preheader_copy, ValueId(2)); - } - - #[test] - fn test_prepare_carrier_phi() { - let mut builder = HeaderPhiBuilder::new(); - builder.prepare_carrier_phi("i".to_string(), ValueId(20), ValueId(0), ValueId(3)); - - assert_eq!(builder.carrier_phi_count(), 1); - let phi = &builder.carrier_phis()[0]; - assert_eq!(phi.var_name, "i"); - assert_eq!(phi.phi_id, ValueId(20)); - assert_eq!(phi.init_value, ValueId(0)); - assert_eq!(phi.preheader_copy, ValueId(3)); - } - - #[test] - fn test_total_phi_count() { - let mut builder = HeaderPhiBuilder::new(); - builder.prepare_pinned_phi("x".to_string(), ValueId(10), ValueId(1), ValueId(2)); - builder.prepare_pinned_phi("y".to_string(), ValueId(11), ValueId(3), ValueId(4)); - builder.prepare_carrier_phi("i".to_string(), ValueId(20), ValueId(0), ValueId(5)); - - assert_eq!(builder.pinned_phi_count(), 2); - assert_eq!(builder.carrier_phi_count(), 1); - assert_eq!(builder.total_phi_count(), 3); - } - - #[test] - fn test_find_pinned_phi() { - let mut builder = HeaderPhiBuilder::new(); - builder.prepare_pinned_phi("x".to_string(), ValueId(10), ValueId(1), ValueId(2)); - builder.prepare_pinned_phi("y".to_string(), ValueId(11), ValueId(3), ValueId(4)); - - let phi = builder.find_pinned_phi("x"); - assert!(phi.is_some()); - assert_eq!(phi.unwrap().phi_id, ValueId(10)); - - let not_found = builder.find_pinned_phi("z"); - assert!(not_found.is_none()); - } - - #[test] - fn test_find_carrier_phi() { - let mut builder = HeaderPhiBuilder::new(); - builder.prepare_carrier_phi("i".to_string(), ValueId(20), ValueId(0), ValueId(3)); - builder.prepare_carrier_phi("j".to_string(), ValueId(21), ValueId(0), ValueId(4)); - - let phi = builder.find_carrier_phi("i"); - assert!(phi.is_some()); - assert_eq!(phi.unwrap().phi_id, ValueId(20)); - - let not_found = builder.find_carrier_phi("k"); - assert!(not_found.is_none()); - } - - #[test] - fn test_clear() { - let mut builder = HeaderPhiBuilder::new(); - builder.prepare_pinned_phi("x".to_string(), ValueId(10), ValueId(1), ValueId(2)); - builder.prepare_carrier_phi("i".to_string(), ValueId(20), ValueId(0), ValueId(3)); - - assert_eq!(builder.total_phi_count(), 2); - - builder.clear(); - - assert_eq!(builder.pinned_phi_count(), 0); - assert_eq!(builder.carrier_phi_count(), 0); - assert_eq!(builder.total_phi_count(), 0); - } - - #[test] - fn test_default() { - let builder = HeaderPhiBuilder::default(); - assert_eq!(builder.total_phi_count(), 0); - } - - #[test] - fn test_build_phi_inputs_for_seal_simple() { - let builder = HeaderPhiBuilder::new(); - - // Simple case: preheader + latch only - let inputs = builder.build_phi_inputs_for_seal( - "x", - BasicBlockId(1), // preheader_id - ValueId(10), // preheader_copy - BasicBlockId(2), // latch_id - ValueId(20), // latch_value - &[], // no continue snapshots - ); - - // Should have 2 inputs: preheader and latch - assert_eq!(inputs.len(), 2); - assert!(inputs.contains(&(BasicBlockId(1), ValueId(10)))); - assert!(inputs.contains(&(BasicBlockId(2), ValueId(20)))); - } - - #[test] - fn test_build_phi_inputs_for_seal_with_continue() { - let builder = HeaderPhiBuilder::new(); - - // Continue snapshot - let mut continue_vars = HashMap::new(); - continue_vars.insert("x".to_string(), ValueId(15)); - - let inputs = builder.build_phi_inputs_for_seal( - "x", - BasicBlockId(1), // preheader_id - ValueId(10), // preheader_copy - BasicBlockId(3), // latch_id - ValueId(20), // latch_value - &[(BasicBlockId(2), continue_vars)], - ); - - // Should have 3 inputs: preheader, continue, latch - assert_eq!(inputs.len(), 3); - assert!(inputs.contains(&(BasicBlockId(1), ValueId(10)))); - assert!(inputs.contains(&(BasicBlockId(2), ValueId(15)))); - assert!(inputs.contains(&(BasicBlockId(3), ValueId(20)))); - } - - #[test] - fn test_build_phi_inputs_for_seal_same_value_optimization() { - let builder = HeaderPhiBuilder::new(); - - // All inputs are the same → PHI elimination - let inputs = builder.build_phi_inputs_for_seal( - "x", - BasicBlockId(1), // preheader_id - ValueId(10), // preheader_copy (same as latch) - BasicBlockId(2), // latch_id - ValueId(10), // latch_value (same!) - &[], - ); - - // Should return empty vec (PHI eliminated) - assert_eq!(inputs.len(), 0); - } - - #[test] - fn test_skip_whitespace_scenario() { - // skip_whitespaceシナリオ: s, idx (pinned) - let mut builder = HeaderPhiBuilder::new(); - - builder.prepare_pinned_phi( - "s".to_string(), - ValueId(100), // phi_id - ValueId(1), // param_value - ValueId(2), // preheader_copy - ); - - builder.prepare_pinned_phi( - "idx".to_string(), - ValueId(101), // phi_id - ValueId(3), // param_value - ValueId(4), // preheader_copy - ); - - assert_eq!(builder.pinned_phi_count(), 2); - assert_eq!(builder.carrier_phi_count(), 0); - - let s_phi = builder.find_pinned_phi("s"); - assert!(s_phi.is_some()); - assert_eq!(s_phi.unwrap().phi_id, ValueId(100)); - - let idx_phi = builder.find_pinned_phi("idx"); - assert!(idx_phi.is_some()); - assert_eq!(idx_phi.unwrap().phi_id, ValueId(101)); - } - - #[test] - fn test_mixed_pinned_and_carrier() { - let mut builder = HeaderPhiBuilder::new(); - - // Pinned: function parameters - builder.prepare_pinned_phi("s".to_string(), ValueId(100), ValueId(1), ValueId(2)); - builder.prepare_pinned_phi("idx".to_string(), ValueId(101), ValueId(3), ValueId(4)); - - // Carrier: loop-modified locals - builder.prepare_carrier_phi("i".to_string(), ValueId(200), ValueId(0), ValueId(5)); - builder.prepare_carrier_phi("sum".to_string(), ValueId(201), ValueId(0), ValueId(6)); - - assert_eq!(builder.pinned_phi_count(), 2); - assert_eq!(builder.carrier_phi_count(), 2); - assert_eq!(builder.total_phi_count(), 4); - } -} diff --git a/src/mir/phi_core/loop_phi.rs b/src/mir/phi_core/loop_phi.rs deleted file mode 100644 index 65d83744..00000000 --- a/src/mir/phi_core/loop_phi.rs +++ /dev/null @@ -1,287 +0,0 @@ -/*! - * phi_core::loop_phi – loop-specific PHI management (legacy scaffold) - * - * - 25.1e / 25.1q / 25.2 で LoopForm v2 + LoopSnapshotMergeBox に切り替え済み。 - * - 現在の Run 時間経路(AST / JSON front)は `loopform_builder.rs` を SSOT としており、 - * 本モジュールは互換レイヤ(歴史的な隊列や分析用)としてのみ残している。 - * - Phase 31.x 以降で段階的に削除予定。新しい PHI 実装をここに追加してはいけない。 - */ - -use crate::ast::ASTNode; -use crate::mir::{BasicBlockId, ValueId}; - -/// Loop-local placeholder of an incomplete PHI (header-time declaration). -/// Moved from loop_builder to centralize PHI-related types. -#[derive(Debug, Clone)] -pub struct IncompletePhi { - pub phi_id: ValueId, - pub var_name: String, - pub known_inputs: Vec<(BasicBlockId, ValueId)>, -} - -/// Common snapshot type used for continue/exit points -pub type VarSnapshot = std::collections::HashMap; -pub type SnapshotAt = (BasicBlockId, VarSnapshot); - -#[derive(Default)] -pub struct LoopPhiManager; - -impl LoopPhiManager { - pub fn new() -> Self { - Self::default() - } -} - -/// Operations required from a loop builder to finalize PHIs. -pub trait LoopPhiOps { - fn new_value(&mut self) -> ValueId; - fn emit_phi_at_block_start( - &mut self, - block: BasicBlockId, - dst: ValueId, - inputs: Vec<(BasicBlockId, ValueId)>, - ) -> Result<(), String>; - fn update_var(&mut self, name: String, value: ValueId); - fn get_variable_at_block(&mut self, name: &str, block: BasicBlockId) -> Option; - fn debug_verify_phi_inputs( - &mut self, - _merge_bb: BasicBlockId, - _inputs: &[(BasicBlockId, ValueId)], - ) { - } - - /// PHI UseBeforeDef修正: preheaderブロックでCopy命令を先行生成 - fn emit_copy_at_preheader( - &mut self, - preheader_id: BasicBlockId, - dst: ValueId, - src: ValueId, - ) -> Result<(), String>; - - /// Optionally declare a predecessor edge pred -> block in CFG. - /// Default no-op for backends that maintain CFG elsewhere. - fn add_predecessor_edge( - &mut self, - _block: BasicBlockId, - _pred: BasicBlockId, - ) -> Result<(), String> { - Ok(()) - } -} - -/// Finalize PHIs at loop exit (merge of break points and header fall-through). -/// Behavior mirrors loop_builder's create_exit_phis using the provided ops. -/// -/// Note: -/// - 25.1e 以降の新規実装では LoopFormBuilder + LoopFormOps 側をSSOTとし、 -/// 本APIは legacy 経路からの利用に限定する(構造設計上の deprecated 扱い)。 -pub fn build_exit_phis_with( - ops: &mut O, - header_id: BasicBlockId, - exit_id: BasicBlockId, - header_vars: &std::collections::HashMap, - exit_snapshots: &[(BasicBlockId, VarSnapshot)], -) -> Result<(), String> { - // 1) Collect all variable names possibly participating in exit PHIs(決定的順序のためBTreeSet使用) - let mut all_vars = std::collections::BTreeSet::new(); - for var_name in header_vars.keys() { - all_vars.insert(var_name.clone()); - } - for (_bid, snapshot) in exit_snapshots.iter() { - for var_name in snapshot.keys() { - all_vars.insert(var_name.clone()); - } - } - - // 2) For each variable, gather incoming values(アルファベット順で決定的) - for var_name in all_vars { - let mut phi_inputs: Vec<(BasicBlockId, ValueId)> = Vec::new(); - - if let Some(&hv) = header_vars.get(&var_name) { - phi_inputs.push((header_id, hv)); - } - for (block_id, snapshot) in exit_snapshots.iter() { - if let Some(&v) = snapshot.get(&var_name) { - phi_inputs.push((*block_id, v)); - } - } - - // Sanitize inputs: deduplicate by predecessor, stable sort by bb id - sanitize_phi_inputs(&mut phi_inputs); - // Ensure CFG has edges pred -> exit for all incoming preds (idempotent) - for (pred_bb, _v) in &phi_inputs { - let _ = ops.add_predecessor_edge(exit_id, *pred_bb); - } - match phi_inputs.len() { - 0 => {} // nothing to do - 1 => { - // single predecessor: direct binding - ops.update_var(var_name, phi_inputs[0].1); - } - _ => { - let dst = ops.new_value(); - ops.debug_verify_phi_inputs(exit_id, &phi_inputs); - ops.emit_phi_at_block_start(exit_id, dst, phi_inputs)?; - ops.update_var(var_name, dst); - } - } - } - Ok(()) -} - -/// Seal a header block by completing its incomplete PHIs with values from -/// continue snapshots and the latch block. -/// -/// Note: -/// - LoopForm PHI v2 では LoopFormBuilder 側で header/latch の PHI 完成を行うため、 -/// 本関数は legacy LoopBuilder のみから呼ばれる経路として扱う。 -pub fn seal_incomplete_phis_with( - ops: &mut O, - block_id: BasicBlockId, - latch_id: BasicBlockId, - mut incomplete_phis: Vec, - continue_snapshots: &[(BasicBlockId, VarSnapshot)], -) -> Result<(), String> { - for mut phi in incomplete_phis.drain(..) { - // from continue points - for (cid, snapshot) in continue_snapshots.iter() { - if let Some(&v) = snapshot.get(&phi.var_name) { - phi.known_inputs.push((*cid, v)); - let _ = ops.add_predecessor_edge(block_id, *cid); - } - } - // from latch - let value_after = ops - .get_variable_at_block(&phi.var_name, latch_id) - .ok_or_else(|| format!("Variable {} not found at latch block", phi.var_name))?; - - // 🔧 ループ不変変数の自己参照PHI問題を修正 - // value_afterがPHI自身の場合(ループ内で変更されていない変数)は、 - // preheaderの値を使用する - let latch_value = if value_after == phi.phi_id { - // ループ不変変数:preheaderの値を使用 - phi.known_inputs[0].1 // preheaderからの初期値 - } else { - value_after - }; - phi.known_inputs.push((latch_id, latch_value)); - let _ = ops.add_predecessor_edge(block_id, latch_id); - - sanitize_phi_inputs(&mut phi.known_inputs); - ops.debug_verify_phi_inputs(block_id, &phi.known_inputs); - ops.emit_phi_at_block_start(block_id, phi.phi_id, phi.known_inputs)?; - ops.update_var(phi.var_name.clone(), phi.phi_id); - } - Ok(()) -} - -/// Prepare loop header PHIs by declaring one IncompletePhi per variable found -/// in `current_vars` (preheader snapshot), seeding each with (preheader_id, val) -/// and rebinding the variable to the newly allocated Phi result in the builder. -/// -/// Note: -/// - 25.1e 以降、新しいLoopForm実装では LoopFormBuilder::prepare_structure を正とし、 -/// 本APIは legacy 互換のための読み取り専用設計として扱う(新規利用は避ける)。 -pub fn prepare_loop_variables_with( - ops: &mut O, - header_id: BasicBlockId, - preheader_id: BasicBlockId, - current_vars: &std::collections::HashMap, -) -> Result, String> { - // 🎯 修正: current_varsをpreheader時点の値のみに限定(header blockで定義された値を除外) - // これにより、UseBeforeDef(PHI inputsにheader内で定義された値が含まれる)を防ぐ - - let mut incomplete_phis: Vec = Vec::new(); - for (var_name, &value_before) in current_vars.iter() { - // Phase 25.1b fix: Include pinned variables in loop header PHIs - // Previously pinned variables were skipped, causing "use of undefined value" - // errors when receivers were used after loops. Pinned variables need PHIs - // at both header and exit points to properly merge values across control flow. - - // Materialize the incoming value at preheader to satisfy UseBeforeDef constraints - // even when `value_before` was defined in a different block (e.g., previous loop header). - let pre_copy = ops.new_value(); - ops.emit_copy_at_preheader(preheader_id, pre_copy, value_before)?; - - let phi_id = ops.new_value(); - let inc = IncompletePhi { - phi_id, - var_name: var_name.clone(), - known_inputs: vec![(preheader_id, pre_copy)], // ensure def at preheader - }; - // Insert an initial PHI at header with only the preheader input so that - // the header condition reads the PHI value (first iteration = preheader). - // Later sealing will update the PHI inputs to include latch/continue preds. - ops.emit_phi_at_block_start(header_id, phi_id, inc.known_inputs.clone())?; - // Rebind variable to PHI now so that any header-time use (e.g., loop condition) - // refers to the PHI value. - ops.update_var(var_name.clone(), phi_id); - incomplete_phis.push(inc); - } - // Ensure CFG has preheader -> header edge recorded (idempotent) - let _ = ops.add_predecessor_edge(header_id, preheader_id); - Ok(incomplete_phis) -} - -/// Collect variables assigned within a loop body and detect control-flow -/// statements (break/continue). Used for lightweight carrier hinting. -pub fn collect_carrier_assigns(node: &ASTNode, vars: &mut Vec, has_ctrl: &mut bool) { - match node { - ASTNode::Assignment { target, .. } => { - if let ASTNode::Variable { name, .. } = target.as_ref() { - if !vars.iter().any(|v| v == name) { - vars.push(name.clone()); - } - } - } - ASTNode::Break { .. } | ASTNode::Continue { .. } => { - *has_ctrl = true; - } - ASTNode::If { - then_body, - else_body, - .. - } => { - let tp = ASTNode::Program { - statements: then_body.clone(), - span: crate::ast::Span::unknown(), - }; - collect_carrier_assigns(&tp, vars, has_ctrl); - if let Some(eb) = else_body { - let ep = ASTNode::Program { - statements: eb.clone(), - span: crate::ast::Span::unknown(), - }; - collect_carrier_assigns(&ep, vars, has_ctrl); - } - } - ASTNode::Program { statements, .. } => { - for s in statements { - collect_carrier_assigns(s, vars, has_ctrl); - } - } - _ => {} - } -} - -/// Save a block-local variable snapshot into the provided store. -pub fn save_block_snapshot( - store: &mut std::collections::HashMap, - block: BasicBlockId, - snapshot: &VarSnapshot, -) { - store.insert(block, snapshot.clone()); -} - -/// Deduplicate PHI inputs by predecessor and sort by block id for stability -fn sanitize_phi_inputs(inputs: &mut Vec<(BasicBlockId, ValueId)>) { - use std::collections::HashMap; - let mut map: HashMap = HashMap::new(); - for (bb, v) in inputs.iter().cloned() { - // Later entries override earlier ones (latch should override preheader when duplicated) - map.insert(bb, v); - } - let mut vec: Vec<(BasicBlockId, ValueId)> = map.into_iter().collect(); - vec.sort_by_key(|(bb, _)| bb.as_u32()); - *inputs = vec; -} diff --git a/src/mir/phi_core/loopform_builder.rs b/src/mir/phi_core/loopform_builder.rs index 933e8a37..6c70b6ea 100644 --- a/src/mir/phi_core/loopform_builder.rs +++ b/src/mir/phi_core/loopform_builder.rs @@ -22,6 +22,80 @@ pub(crate) fn is_loopform_debug_enabled() -> bool { std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() } +// ============================================================================ +// Phase 30 F-2.1: JoinIR バイパスフラグ(header_phi_builder から移動) +// ============================================================================ + +/// Phase 27.4C Cleanup: JoinIR Header φ バイパス対象関数リスト +/// +/// Phase 27.4-C のスコープは以下の 2 関数のみ: +/// - `Main.skip/1` (minimal_ssa_skip_ws.hako) +/// - `FuncScannerBox.trim/1` (funcscanner_trim_min.hako) +/// +/// **重要**: 他の関数では Header φ を絶対にスキップしないこと。 +const JOINIR_HEADER_BYPASS_TARGETS: &[&str] = &["Main.skip/1", "FuncScannerBox.trim/1"]; + +/// Phase 27.4-C: JoinIR Header φ バイパス対象関数かチェック +/// +/// `JOINIR_HEADER_BYPASS_TARGETS` に含まれる関数のみ true を返す。 +pub(crate) fn is_joinir_header_bypass_target(fn_name: &str) -> bool { + JOINIR_HEADER_BYPASS_TARGETS.contains(&fn_name) +} + +/// Phase 27.4-C Refactor: JoinIR Loop φ バイパスフラグ統合 +/// +/// Header φ と Exit φ のバイパスフラグを一箇所で管理。 +/// 将来的に Exit φ バイパス(Phase 27.6-2)とも統合可能。 +#[derive(Debug, Clone, Copy, Default)] +pub(crate) struct LoopBypassFlags { + /// Header φ バイパスが有効か + pub header: bool, + // Phase 30: exit フィールド削除(完全未使用、将来 JoinIR で代替予定) +} + +/// Phase 27.4-C Refactor: JoinIR Loop φ バイパスフラグを取得 +/// +/// **用途**: Header/Exit φ バイパスの判定を一元化。 +/// 複数箇所での重複したトグル判定を避ける。 +/// +/// # Arguments +/// - `fn_name` - 関数名(例: "Main.skip/1") +/// +/// # Returns +/// - `LoopBypassFlags` - Header/Exit バイパスの有効状態 +pub(crate) fn get_loop_bypass_flags(fn_name: &str) -> LoopBypassFlags { + let joinir_exp = crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_EXPERIMENT"); + let header_exp = crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_HEADER_EXP"); + + LoopBypassFlags { + header: joinir_exp && header_exp && is_joinir_header_bypass_target(fn_name), + } +} + +// ============================================================================ +// Phase 30 F-2.1: Exit バイパスフラグ(exit_phi_builder から移動) +// ============================================================================ + +/// JoinIR Exit φ バイパスが有効かどうか +/// +/// - NYASH_JOINIR_EXPERIMENT=1 +/// - NYASH_JOINIR_EXIT_EXP=1 +/// +/// の両方が立っているときだけ true。 +pub(crate) fn joinir_exit_bypass_enabled() -> bool { + crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_EXPERIMENT") + && crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_EXIT_EXP") +} + +/// Exit φ バイパス対象の関数かどうか +/// +/// 当面は minimal/trim の 2 本だけ: +/// - Main.skip/1 +/// - FuncScannerBox.trim/1 +pub(crate) fn is_joinir_exit_bypass_target(func_name: &str) -> bool { + matches!(func_name, "Main.skip/1" | "FuncScannerBox.trim/1") +} + /// 📦 LoopForm Context - Box-First理論に基づくパラメータ予約明示化 /// /// ValueId割り当ての境界を明確にし、パラメータ予約を明示的に管理。 diff --git a/src/mir/phi_core/mod.rs b/src/mir/phi_core/mod.rs index ac1c2bd9..1ca5316e 100644 --- a/src/mir/phi_core/mod.rs +++ b/src/mir/phi_core/mod.rs @@ -10,7 +10,7 @@ pub mod common; pub mod conservative; pub mod if_phi; -pub mod loop_phi; +// Phase 30 F-2.1: loop_phi 削除(LoopFormBuilder が SSOT) pub mod loop_snapshot_merge; pub mod loopform_builder; @@ -19,15 +19,15 @@ pub mod local_scope_inspector; pub mod loop_var_classifier; // Phase 26-B: Box-First Refactoring -pub mod body_local_phi_builder; +// Phase 30 F-2.1: body_local_phi_builder 削除(LoopScopeShape で代替) pub mod phi_input_collector; -// Phase 26-C: Loop Snapshot & Header PHI Management -pub mod header_phi_builder; +// Phase 26-C: Loop Snapshot Management +// Phase 30 F-2.1: header_phi_builder 削除(JoinIR loop_step で代替) pub mod loop_snapshot_manager; // Phase 26-D: Exit PHI Management -pub mod exit_phi_builder; +// Phase 30 F-2.1: exit_phi_builder 削除(JoinIR k_exit で代替、バイパス関数は loopform_builder に移動) // Phase 26-E: PHI SSOT Unification - PhiBuilderBox pub mod phi_builder_box;