diff --git a/src/mir/phi_core/loopform/builder_core.rs b/src/mir/phi_core/loopform/builder_core.rs new file mode 100644 index 00000000..ec19d6fe --- /dev/null +++ b/src/mir/phi_core/loopform/builder_core.rs @@ -0,0 +1,411 @@ +/*! + * loopform::builder_core – Core LoopFormBuilder orchestration + * + * This module contains the main LoopFormBuilder struct and coordinates + * the 4-pass PHI generation pipeline. + * + * The builder manages: + * - Carrier and pinned variable collections + * - Preheader and header block IDs + * - Coordination between the 4 passes + */ + +use crate::mir::phi_core::loop_snapshot_merge::LoopSnapshotMergeBox; +use crate::mir::{BasicBlockId, ValueId}; +use std::collections::BTreeMap; + +use super::variable_models::{CarrierVariable, PinnedVariable}; + +/// LoopForm Meta-Box: Structured representation of loop SSA construction +/// +/// Separates loop-visible variables into classes(25.1e/25.2 スコープモデル): +/// - Carriers: Modified in loop body, need header/exit PHI nodes. +/// - Pinned: Loop-invariant parameters/receivers, need PHI so every header/exit +/// edge has a well-defined value but the logical value never changes. +/// - Invariants: Not tracked here; they keep the preheader ValueId and never +/// participate in PHI construction. +/// - Body-local live-out (BodyLocalInOut): Not stored as dedicated structs, but +/// detected at exit-phi time in `build_exit_phis` and merged via +/// `LoopSnapshotMergeBox::merge_exit`. +/// +/// Key idea: All ValueIds for Carriers/Pinned are allocated upfront before any +/// MIR emission, eliminating circular dependency issues in SSA. +#[derive(Debug)] +pub struct LoopFormBuilder { + pub carriers: Vec, + pub pinned: Vec, + pub preheader_id: BasicBlockId, + pub header_id: BasicBlockId, + /// Step 5-2: Preheader snapshot for ValueId comparison (選択肢3) + /// Used in seal_phis() to detect which variables are truly modified vs. invariant + pub preheader_vars: BTreeMap, +} + +impl LoopFormBuilder { + /// Create a new LoopForm builder with specified block IDs + pub fn new(preheader_id: BasicBlockId, header_id: BasicBlockId) -> Self { + Self { + carriers: Vec::new(), + pinned: Vec::new(), + preheader_id, + header_id, + preheader_vars: BTreeMap::new(), // Will be set in prepare_structure() + } + } + + /// Pass 1: Allocate all ValueIds for loop structure + /// + /// This is the critical innovation: we allocate ALL ValueIds + /// (preheader copies and header PHIs) BEFORE emitting any instructions. + /// This guarantees definition-before-use in SSA form. + pub fn prepare_structure( + &mut self, + ops: &mut O, + current_vars: &BTreeMap, + ) -> Result<(), String> { + super::passes::pass1_discovery::prepare_structure(self, ops, current_vars) + } + + /// Pass 2: Emit preheader block instructions + /// + /// Emits copy instructions for ALL variables in deterministic order: + /// 1. Pinned variables first + /// 2. Carrier variables second + /// + /// This ordering ensures consistent ValueId allocation across runs. + pub fn emit_preheader(&self, ops: &mut O) -> Result<(), String> { + super::passes::pass2_preheader::emit_preheader(self, ops) + } + + /// Pass 3: Emit header block PHI nodes (incomplete) + /// + /// Creates incomplete PHI nodes with only preheader input. + /// These will be completed in seal_phis() after loop body is lowered. + pub fn emit_header_phis(&mut self, ops: &mut O) -> Result<(), String> { + super::passes::pass3_header_phi::emit_header_phis(self, ops) + } + + /// Pass 4: Complete PHI nodes with latch values + /// + /// Seals both pinned and carrier PHI nodes by finding latch values + /// and updating PHI inputs. + pub fn seal_phis( + &mut self, + ops: &mut O, + latch_id: BasicBlockId, + continue_snapshots: &[(BasicBlockId, BTreeMap)], + _writes: &std::collections::HashSet, // Step 5-1/5-2: Reserved for future optimization + header_bypass: bool, // Phase 27.4C: Header φ バイパスフラグ + ) -> Result<(), String> { + super::passes::pass4_seal::seal_phis( + self, + ops, + latch_id, + continue_snapshots, + _writes, + header_bypass, + ) + } + + /// Build exit PHIs for break/continue merge points + /// + /// Similar to header PHIs, but merges: + /// - Header fallthrough (normal loop exit) + /// - Break snapshots (early exit from loop body) + /// + /// # Parameters + /// - `branch_source_block`: The ACTUAL block that emitted the branch to exit + /// (might differ from header_id if condition evaluation created new blocks) + /// Option C: Build exit PHIs with variable classification + /// + /// ## 引数 + /// + /// - `inspector`: LocalScopeInspectorBox(変数定義位置追跡) + /// - 各ブロックでどの変数が定義されているか追跡 + /// - BodyLocalInternal 変数(全exit predsで定義されていない)を検出 + /// - これらの変数は exit PHI を生成しない(PHI pred mismatch防止) + pub fn build_exit_phis( + &self, + ops: &mut O, + exit_id: BasicBlockId, + branch_source_block: BasicBlockId, + exit_snapshots: &[(BasicBlockId, BTreeMap)], + ) -> Result<(), String> { + ops.set_current_block(exit_id)?; + + let debug = std::env::var("NYASH_LOOPFORM_DEBUG").is_ok(); + if debug { + eprintln!("[DEBUG/exit_phi] ====== Exit PHI Generation ======"); + eprintln!( + "[DEBUG/exit_phi] exit_id = {:?}, header_id = {:?}, branch_source = {:?}", + exit_id, self.header_id, branch_source_block + ); + eprintln!( + "[DEBUG/exit_phi] exit_snapshots.len() = {}", + exit_snapshots.len() + ); + for (i, (bb, snap)) in exit_snapshots.iter().enumerate() { + eprintln!( + "[DEBUG/exit_phi] snapshot[{}]: block = {:?}, num_vars = {}", + i, bb, + snap.len() + ); + } + eprintln!( + "[DEBUG/exit_phi] pinned.len() = {}, carriers.len() = {}", + self.pinned.len(), + self.carriers.len() + ); + } + + // Phase 25.2: LoopSnapshotMergeBox を使って exit PHI 統合 + + // 1. header_vals を準備(pinned + carriers) + let mut header_vals = BTreeMap::new(); + for pinned in &self.pinned { + header_vals.insert(pinned.name.clone(), pinned.header_phi); + } + for carrier in &self.carriers { + header_vals.insert(carrier.name.clone(), carrier.header_phi); + } + + // 2. body_local_vars を収集(決定的順序のためBTreeSet使用) + let mut body_local_names = Vec::new(); + let mut body_local_set: std::collections::BTreeSet = + std::collections::BTreeSet::new(); + for (_block_id, snapshot) in exit_snapshots { + // 決定的順序のため、keysをソートしてからイテレート + let mut sorted_keys: Vec<_> = snapshot.keys().collect(); + sorted_keys.sort(); + for var_name in sorted_keys { + // Step 5-5-D: Skip __pin$ temporary variables in exit PHI generation + // These are BodyLocalInternal and should NOT get exit PHIs + if var_name.starts_with("__pin$") && var_name.contains("$@") { + if debug { + eprintln!("[DEBUG/exit_phi] SKIP __pin$ variable: {}", var_name); + } + continue; + } + + let is_pinned = self.pinned.iter().any(|p| &p.name == var_name); + let is_carrier = self.carriers.iter().any(|c| &c.name == var_name); + if !is_pinned && !is_carrier && !body_local_set.contains(var_name) { + body_local_names.push(var_name.clone()); + body_local_set.insert(var_name.clone()); + } + } + } + + if debug && !body_local_names.is_empty() { + eprintln!( + "[DEBUG/exit_phi] Found {} body-local variables", + body_local_names.len() + ); + } + + // Option C: Body-local variables should NOT be added to header_vals! + // They are defined inside the loop body, not in the header. + // The inspector will correctly track which exit preds have them. + + // 📦 Hotfix 6: Filter exit_snapshots to only include valid CFG predecessors + let exit_preds = ops.get_block_predecessors(exit_id); + if debug { + eprintln!("[DEBUG/exit_phi] Exit block predecessors: {:?}", exit_preds); + } + + let mut filtered_snapshots = Vec::new(); + for (block_id, snapshot) in exit_snapshots { + if !ops.block_exists(*block_id) { + if debug { + eprintln!( + "[DEBUG/exit_phi] ⚠️ Skipping non-existent block {:?}", + block_id + ); + } + continue; + } + if !exit_preds.contains(block_id) { + if debug { + eprintln!( + "[DEBUG/exit_phi] ⚠️ Skipping block {:?} (not in CFG predecessors)", + block_id + ); + } + continue; + } + filtered_snapshots.push((*block_id, snapshot.clone())); + } + + // 3. Option C: Record snapshots in inspector for availability checking + // Phase 69-2: LocalScopeInspectorBox 削除完了 + // variable_definitions は LoopScopeShape に移行済み(Phase 48-4) + // inspector.record_*() 呼び出しは不要(LoopScopeShape 内部で処理) + + // 4. Option C: merge_exit_with_classification() でPHI pred mismatch防止 + // pinned/carrier名リストを準備 + let pinned_names: Vec = self.pinned.iter().map(|p| p.name.clone()).collect(); + let carrier_names: Vec = self.carriers.iter().map(|c| c.name.clone()).collect(); + + // exit_preds を Vec に変換 + let exit_preds_vec: Vec = exit_preds.iter().copied().collect(); + + // Phase 69-2: inspector 引数を削除(LoopScopeShape に移行済み) + let all_vars = LoopSnapshotMergeBox::merge_exit_with_classification( + branch_source_block, + &header_vals, + &filtered_snapshots, + &exit_preds_vec, // ← 実際のCFG predecessorsを渡す + &pinned_names, + &carrier_names, + )?; + + // ======================================== + // Phase 59: PHI生成(PhiInputCollectorインライン化) + // ======================================== + // 旧: PhiInputCollector::new() + add_snapshot + sanitize + optimize_same_value + // 新: BTreeMapで直接処理(同等のロジックをインライン展開) + for (var_name, inputs) in all_vars { + // Step 1: sanitize - BTreeMapで重複削除&ソート + let mut sanitized: std::collections::BTreeMap = + std::collections::BTreeMap::new(); + for (bb, val) in &inputs { + sanitized.insert(*bb, *val); + } + let final_inputs: Vec<(BasicBlockId, ValueId)> = sanitized.into_iter().collect(); + + if debug { + eprintln!( + "[DEBUG/exit_phi] Variable '{}': {} inputs", + var_name, + final_inputs.len() + ); + } + + // Step 2: optimize_same_value - 全て同じ値ならPHI不要 + 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 直接バインド + if let Some(same_val) = same_value { + // 全て同じ値 or 単一入力 → PHI 不要 + if debug { + eprintln!( + "[DEBUG/exit_phi] Variable '{}': single/same value, direct binding to {:?}", + var_name, same_val + ); + } + ops.update_var(var_name, same_val); + } else { + // 異なる値を持つ場合は PHI ノードを生成 + let phi_id = ops.new_value(); + if debug { + eprintln!( + "[DEBUG/exit_phi] Creating PHI {:?} for var '{}' with {} inputs", + phi_id, var_name, + final_inputs.len() + ); + for (bb, val) in &final_inputs { + eprintln!("[DEBUG/exit_phi] PHI input: pred={:?} val={:?}", bb, val); + } + } + ops.emit_phi(phi_id, final_inputs)?; + ops.update_var(var_name, phi_id); + } + } + + Ok(()) + } +} + +/// Operations required by LoopFormBuilder +/// +/// This trait abstracts the underlying MIR builder operations, +/// allowing LoopFormBuilder to work with both Rust MIR builder +/// and selfhost compiler's JSON-based approach. +/// Phase 26-E-3: LoopFormOps と PhiBuilderOps の関係(委譲設計) +/// +/// **Design Rationale (ChatGPT + Claude consensus, 2025-11-22):** +/// - PhiBuilderOps = 低レベル「PHI命令発行」道具箱 +/// - LoopFormOps = 高レベル「ループ構造構築」作業場 +/// - 関係: **has-a(委譲)** ではなく is-a(継承) +/// +/// **実装方針:** +/// - LoopFormOps trait: そのまま(継承なし) +/// - PhiBuilderOps 実装: 必要な型に個別実装 +/// - `impl<'a> PhiBuilderOps for LoopBuilder<'a>` で委譲 +/// - HashSet → Vec 変換 + ソートで決定性保証 +/// +/// **段階的統合:** +/// 1. If側: PhiBuilderBox 経由(既に完了) +/// 2. Loop側: LoopFormOps + ExitPhiBuilder/HeaderPhiBuilder(現状維持) +/// 3. 将来: 必要に応じて LoopBuilder に PhiBuilderOps 実装追加 +pub trait LoopFormOps { + /// Allocate a new ValueId + fn new_value(&mut self) -> ValueId; + + /// Ensure MirFunction counter is after the given ValueId to prevent collisions + /// CRITICAL: Must be called before allocating new ValueIds in LoopForm + fn ensure_counter_after(&mut self, max_id: u32) -> Result<(), String>; + + /// 📦 Check if a block exists in the CFG (Hotfix 2: Exit PHI predecessor validation) + /// Used to skip non-existent blocks when building exit PHIs. + fn block_exists(&self, block: BasicBlockId) -> bool; + + /// 📦 Get actual CFG predecessors for a block (Hotfix 6: PHI input validation) + /// Returns the set of blocks that actually branch to this block in the CFG. + /// Used to validate exit PHI inputs against actual control flow. + /// Phase 69-3: Changed to BTreeSet for determinism + fn get_block_predecessors( + &self, + block: BasicBlockId, + ) -> std::collections::BTreeSet; + + /// Phase 26-A-4: ValueIdベースのパラメータ判定(型安全化) + /// + /// 旧実装(名前ベース)から新実装(ValueIdベース)に変更。 + /// MirValueKindによる型安全判定で、GUARDバグを根絶。 + fn is_parameter(&self, value_id: ValueId) -> bool; + + /// Set current block for instruction emission + fn set_current_block(&mut self, block: BasicBlockId) -> Result<(), String>; + + /// Emit a copy instruction: dst = src + fn emit_copy(&mut self, dst: ValueId, src: ValueId) -> Result<(), String>; + + /// Emit a jump instruction to target block + fn emit_jump(&mut self, target: BasicBlockId) -> Result<(), String>; + + /// Emit a PHI node with given inputs + fn emit_phi( + &mut self, + dst: ValueId, + inputs: Vec<(BasicBlockId, ValueId)>, + ) -> Result<(), String>; + + /// Update PHI node inputs (for sealing incomplete PHIs) + fn update_phi_inputs( + &mut self, + block: BasicBlockId, + phi_id: ValueId, + inputs: Vec<(BasicBlockId, ValueId)>, + ) -> Result<(), String>; + + /// Update variable binding in current scope + fn update_var(&mut self, name: String, value: ValueId); + + /// Get variable value at specific block + fn get_variable_at_block(&self, name: &str, block: BasicBlockId) -> Option; + + /// Access underlying MirFunction (for liveness/MirQuery) + fn mir_function(&self) -> &crate::mir::MirFunction; +} diff --git a/src/mir/phi_core/loopform_context.rs b/src/mir/phi_core/loopform/context.rs similarity index 100% rename from src/mir/phi_core/loopform_context.rs rename to src/mir/phi_core/loopform/context.rs diff --git a/src/mir/phi_core/loopform_exit_phi.rs b/src/mir/phi_core/loopform/exit_phi.rs similarity index 92% rename from src/mir/phi_core/loopform_exit_phi.rs rename to src/mir/phi_core/loopform/exit_phi.rs index 18c1f828..a523503b 100644 --- a/src/mir/phi_core/loopform_exit_phi.rs +++ b/src/mir/phi_core/loopform/exit_phi.rs @@ -30,8 +30,8 @@ impl ExitPhiBuilder { /// * `exit_id` - Exit block ID /// * `branch_source_block` - Branch source block /// * `exit_snapshots` - Snapshots from each exit predecessor - pub fn build_exit_phis( - loopform: &super::loopform_builder::LoopFormBuilder, + pub fn build_exit_phis( + loopform: &super::builder_core::LoopFormBuilder, ops: &mut O, exit_id: BasicBlockId, branch_source_block: BasicBlockId, @@ -52,8 +52,8 @@ impl ExitPhiBuilder { /// * `form` - ControlForm containing loop structure /// * `exit_snapshots` - Snapshots from exit predecessors /// * `branch_source_block` - Branch source block - pub fn build_exit_phis_for_control( - loopform: &super::loopform_builder::LoopFormBuilder, + pub fn build_exit_phis_for_control( + loopform: &super::builder_core::LoopFormBuilder, ops: &mut O, form: &ControlForm, exit_snapshots: &[(BasicBlockId, BTreeMap)], diff --git a/src/mir/phi_core/loopform/mod.rs b/src/mir/phi_core/loopform/mod.rs new file mode 100644 index 00000000..e0c4ee2d --- /dev/null +++ b/src/mir/phi_core/loopform/mod.rs @@ -0,0 +1,102 @@ +//! LoopForm PHI Construction System +//! +//! This module implements the LoopForm Meta-Box approach to PHI construction, +//! solving the ValueId circular dependency problem by treating loop structure +//! as a "Meta-Box" with explicit separation of carriers vs. pinned variables. +//! +//! # Architecture: 4-Pass PHI Generation Pipeline +//! +//! The LoopForm system constructs SSA PHI nodes in 4 distinct passes: +//! +//! ## Pass 1: prepare_structure() - Variable Discovery & ValueId Allocation +//! - Classify variables as carriers (modified in loop) or pinned (loop-invariant) +//! - Allocate ValueIds upfront for preheader_copy and header_phi +//! - Ensure counter is after all existing ValueIds +//! - **Key Innovation**: All ValueIds allocated BEFORE any MIR emission +//! +//! ## Pass 2: emit_preheader() - Preheader Copy Generation +//! - Generate copy instructions in preheader block +//! - Order: pinned variables first, then carrier variables (deterministic) +//! - Jump to header block +//! +//! ## Pass 3: emit_header_phis() - Header PHI Construction +//! - Generate incomplete PHI nodes in header block +//! - First input: preheader_copy (known) +//! - Second input: latch value (unknown at this point) +//! +//! ## Pass 4: seal_phis() - PHI Completion +//! - Complete PHI nodes by finding latch values +//! - Separate handling for pinned (seal_pinned_phis) and carrier (seal_carrier_phis) +//! - Update PHI inputs with actual latch values +//! +//! # Module Structure +//! +//! - `context`: ValueId management and counter operations +//! - `variable_models`: CarrierVariable, PinnedVariable, LoopBypassFlags types +//! - `utils`: Debug and bypass utilities +//! - `exit_phi`: Exit PHI builder for loop exits +//! - `builder_core`: Core LoopFormBuilder implementation +//! - `passes`: 4-pass architecture implementation +//! - `pass1_discovery`: Variable discovery and classification +//! - `pass2_header_phi`: Header PHI construction +//! - `pass3_latch`: Latch processing +//! - `pass4_exit_phi`: Exit PHI construction +//! +//! # Example Usage +//! +//! ```ignore +//! let mut builder = LoopFormBuilder::new(preheader, header); +//! builder.prepare_structure(&mut ops, ¤t_vars)?; +//! builder.emit_preheader(&mut ops)?; +//! builder.emit_header_phis(&mut ops)?; +//! // ... loop body construction ... +//! builder.seal_phis(&mut ops, &latch_vars)?; +//! ``` +//! +//! # Status +//! +//! Phase: 25.1b prototype → 25.1m stabilization +//! Status: Always-on (NYASH_LOOPFORM_PHI_V2 for compatibility only) +//! Phase 191: Modularized into separate submodules +//! Phase 193: Complete 4-pass architecture explicit structure + +// Core modules +pub mod context; +pub mod variable_models; +pub mod utils; +pub mod exit_phi; +pub mod builder_core; +pub mod passes; + +// Re-export main types for backward compatibility +pub use builder_core::{LoopFormBuilder, LoopFormOps}; +pub use context::LoopFormContext; +pub use variable_models::{CarrierVariable, PinnedVariable, LoopBypassFlags}; + +// Re-export utility functions +pub use utils::{ + is_loopform_debug_enabled, + get_loop_bypass_flags, + joinir_exit_bypass_enabled, + is_joinir_exit_bypass_target, +}; + +// Re-export exit PHI function for backward compatibility +pub fn build_exit_phis_for_control( + loopform: &LoopFormBuilder, + ops: &mut O, + form: &crate::mir::control_form::ControlForm, + exit_snapshots: &[( + crate::mir::BasicBlockId, + std::collections::BTreeMap, + )], + branch_source_block: crate::mir::BasicBlockId, +) -> Result<(), String> { + exit_phi::ExitPhiBuilder::build_exit_phis_for_control( + loopform, + ops, + form, + exit_snapshots, + branch_source_block, + ) +} diff --git a/src/mir/phi_core/loopform/passes/mod.rs b/src/mir/phi_core/loopform/passes/mod.rs new file mode 100644 index 00000000..27f60f1c --- /dev/null +++ b/src/mir/phi_core/loopform/passes/mod.rs @@ -0,0 +1,67 @@ +//! LoopForm 4-Pass PHI Generation Pipeline +//! +//! This module implements the 4-pass architecture for PHI node construction +//! in loops. Each pass has a specific responsibility in the SSA construction +//! process. +//! +//! # The 4 Passes +//! +//! ## Pass 1: Variable Discovery & ValueId Allocation (`pass1_discovery`) +//! - **Purpose**: Identify all variables in the loop and allocate ValueIds upfront +//! - **Responsibility**: Classify variables as carriers (modified) or pinned (invariant) +//! - **Key Innovation**: All ValueIds allocated BEFORE any MIR emission +//! - **Function**: `prepare_structure()` +//! +//! ## Pass 2: Preheader Copy Generation (`pass2_preheader`) +//! - **Purpose**: Generate copy instructions in preheader block +//! - **Responsibility**: Emit deterministic copy instructions +//! - **Order**: Pinned variables first, then carrier variables +//! - **Function**: `emit_preheader()` +//! +//! ## Pass 3: Header PHI Construction (`pass3_header_phi`) +//! - **Purpose**: Generate incomplete PHI nodes in header block +//! - **Responsibility**: Create PHI nodes with preheader input only +//! - **Note**: Latch input will be added in Pass 4 +//! - **Function**: `emit_header_phis()` +//! +//! ## Pass 4: PHI Sealing (`pass4_seal`) +//! - **Purpose**: Complete PHI nodes by finding latch values +//! - **Responsibility**: Update PHI inputs with actual latch values +//! - **Handles**: Both pinned and carrier variables +//! - **Function**: `seal_phis()` +//! +//! # Usage Example +//! +//! ```ignore +//! // Pass 1: Variable discovery +//! builder.prepare_structure(&mut ops, ¤t_vars)?; +//! +//! // Pass 2: Preheader copies +//! builder.emit_preheader(&mut ops)?; +//! +//! // Pass 3: Header PHIs (incomplete) +//! builder.emit_header_phis(&mut ops)?; +//! +//! // ... loop body construction ... +//! +//! // Pass 4: Seal PHIs with latch values +//! builder.seal_phis(&mut ops, latch_id, &continue_snapshots, &writes, false)?; +//! ``` +//! +//! # Architecture Benefits +//! +//! - **Explicit separation**: Each pass has clear responsibilities +//! - **Testability**: Each pass can be tested independently +//! - **Maintainability**: Easy to understand and modify +//! - **Determinism**: Predictable ValueId allocation and PHI construction + +pub mod pass1_discovery; +pub mod pass2_preheader; +pub mod pass3_header_phi; +pub mod pass4_seal; + +// Re-export main functions for convenience +pub use pass1_discovery::prepare_structure; +pub use pass2_preheader::emit_preheader; +pub use pass3_header_phi::emit_header_phis; +pub use pass4_seal::seal_phis; diff --git a/src/mir/phi_core/loopform/passes/pass1_discovery.rs b/src/mir/phi_core/loopform/passes/pass1_discovery.rs new file mode 100644 index 00000000..f423fbc3 --- /dev/null +++ b/src/mir/phi_core/loopform/passes/pass1_discovery.rs @@ -0,0 +1,156 @@ +//! Pass 1: Variable Discovery & ValueId Allocation +//! +//! This is the critical innovation of the LoopForm system: we allocate ALL +//! ValueIds (preheader copies and header PHIs) BEFORE emitting any instructions. +//! This guarantees definition-before-use in SSA form and eliminates circular +//! dependency issues. +//! +//! # Responsibilities +//! +//! 1. **Variable Classification**: Separate variables into: +//! - **Carriers**: Modified in loop body, need header/exit PHI nodes +//! - **Pinned**: Loop-invariant parameters, need PHI for SSA form +//! +//! 2. **ValueId Pre-allocation**: Allocate all ValueIds upfront: +//! - `preheader_copy`: Copy instruction in preheader +//! - `header_phi`: PHI node in loop header +//! +//! 3. **Counter Management**: Ensure MirFunction counter is ahead of all +//! existing ValueIds to prevent collisions +//! +//! 4. **GUARD Protection**: Skip loop construction if invalid ValueIds detected +//! +//! # Example +//! +//! ```ignore +//! // Input variables: {x: ValueId(1), y: ValueId(2)} +//! builder.prepare_structure(&mut ops, ¤t_vars)?; +//! +//! // After pass 1: +//! // - carriers: [CarrierVariable { name: "x", init: 1, copy: 3, phi: 4 }] +//! // - pinned: [PinnedVariable { name: "y", param: 2, copy: 5, phi: 6 }] +//! ``` + +use crate::mir::{ValueId}; +use std::collections::BTreeMap; + +use crate::mir::phi_core::loopform::builder_core::{LoopFormBuilder, LoopFormOps}; +use crate::mir::phi_core::loopform::variable_models::{CarrierVariable, PinnedVariable}; + +/// Pass 1: Allocate all ValueIds for loop structure +/// +/// This is the critical innovation: we allocate ALL ValueIds +/// (preheader copies and header PHIs) BEFORE emitting any instructions. +/// This guarantees definition-before-use in SSA form. +pub fn prepare_structure( + builder: &mut LoopFormBuilder, + ops: &mut O, + current_vars: &BTreeMap, +) -> Result<(), String> { + let debug_enabled = std::env::var("NYASH_LOOPFORM_DEBUG").is_ok(); + + // Step 5-2: Save preheader snapshot for ValueId comparison in seal_phis() + builder.preheader_vars = current_vars.clone(); + + if debug_enabled { + crate::runtime::get_global_ring0().log.debug(&format!( + "[loopform/prepare] === START prepare_structure() === {} variables", + current_vars.len() + )); + crate::runtime::get_global_ring0().log.debug("[loopform/prepare] Full variable list:"); + let mut sorted_vars: Vec<_> = current_vars.iter().collect(); + sorted_vars.sort_by_key(|(name, _)| name.as_str()); + for (name, value) in &sorted_vars { + crate::runtime::get_global_ring0().log.debug(&format!("[loopform/prepare] - {} = {:?}", name, value)); + } + } + + // GUARD: Detect invalid ValueId in variable map + // ValueId::INVALID (u32::MAX) indicates uninitialized variables + // Skip this loop construction attempt if detected (likely a premature build) + for (name, &value) in current_vars.iter() { + if value == ValueId::INVALID { + if debug_enabled { + crate::runtime::get_global_ring0().log.debug(&format!("[loopform/prepare] ⚠️ GUARD: Skipping loop preparation due to invalid ValueId for variable '{}'", name)); + crate::runtime::get_global_ring0().log.debug("[loopform/prepare] This indicates the loop is being built prematurely before variables are defined"); + crate::runtime::get_global_ring0().log.debug("[loopform/prepare] Returning Ok(()) to allow retry with properly initialized variables"); + } + // Return Ok to skip this attempt without failing the entire compilation + return Ok(()); + } + } + + // CRITICAL FIX: Ensure MirFunction counter is ahead of all existing ValueIds + // Without this, new_value() can return ValueIds that are already in use + let max_existing_id = current_vars.values().map(|v| v.0).max().unwrap_or(0); + + if debug_enabled { + eprintln!( + "[loopform/prepare] Calling ensure_counter_after(max_existing_id={})", + max_existing_id + ); + } + + ops.ensure_counter_after(max_existing_id)?; + + // Count variables by classification for summary + let mut param_count = 0; + let mut carrier_count = 0; + + // Separate variables into carriers and pinned based on parameter status + for (name, &value) in current_vars.iter() { + // Step 5-3: Skip __pin$ temporary variables (BodyLocalInternal) + // These are compiler-generated temporaries that should not have PHI nodes + if name.starts_with("__pin$") && name.contains("$@") { + if debug_enabled { + eprintln!("[loopform/prepare] SKIP __pin$ variable: {}", name); + } + continue; + } + + // Phase 26-A-4: ValueIdベース判定に変更(名前ベース → 型安全) + if ops.is_parameter(value) { + param_count += 1; + // Pinned variable (parameter, not modified in loop) + let pinned = PinnedVariable { + name: name.clone(), + param_value: value, + preheader_copy: ops.new_value(), // Allocate NOW + header_phi: ops.new_value(), // Allocate NOW + }; + if debug_enabled { + eprintln!( + "[loopform/prepare] PINNED: {} -> init={:?}, copy={:?}, phi={:?}", + name, value, pinned.preheader_copy, pinned.header_phi + ); + } + builder.pinned.push(pinned); + } else { + carrier_count += 1; + // Carrier variable (local, modified in loop) + let carrier = CarrierVariable { + name: name.clone(), + init_value: value, + preheader_copy: ops.new_value(), // Allocate NOW + header_phi: ops.new_value(), // Allocate NOW + latch_value: ValueId(0), // Will be set during seal (placeholder) + }; + if debug_enabled { + eprintln!( + "[loopform/prepare] CARRIER: {} -> init={:?}, copy={:?}, phi={:?}", + name, value, carrier.preheader_copy, carrier.header_phi + ); + } + builder.carriers.push(carrier); + } + } + + if debug_enabled { + crate::runtime::get_global_ring0().log.debug("[loopform/prepare] === SUMMARY ==="); + crate::runtime::get_global_ring0().log.debug(&format!("[loopform/prepare] Total vars: {}", current_vars.len())); + crate::runtime::get_global_ring0().log.debug(&format!("[loopform/prepare] Pinned (params): {}", param_count)); + crate::runtime::get_global_ring0().log.debug(&format!("[loopform/prepare] Carriers (locals): {}", carrier_count)); + } + + Ok(()) +} diff --git a/src/mir/phi_core/loopform/passes/pass2_preheader.rs b/src/mir/phi_core/loopform/passes/pass2_preheader.rs new file mode 100644 index 00000000..8074bc33 --- /dev/null +++ b/src/mir/phi_core/loopform/passes/pass2_preheader.rs @@ -0,0 +1,70 @@ +//! Pass 2: Preheader Copy Generation +//! +//! This pass emits copy instructions in the preheader block for all variables +//! that participate in the loop. The copies serve as initial values for the +//! loop header PHI nodes. +//! +//! # Responsibilities +//! +//! 1. **Deterministic Ordering**: Emit copies in consistent order: +//! - Pinned variables first +//! - Carrier variables second +//! +//! 2. **Copy Emission**: For each variable: +//! - Emit: `preheader_copy = init_value` +//! - This creates a stable definition before the loop header +//! +//! 3. **Jump Emission**: Emit jump to loop header block +//! +//! # Why Preheader Copies? +//! +//! The preheader copies serve multiple purposes: +//! - **SSA Definition**: Ensure every loop variable has a single definition point +//! - **PHI Input**: Provide first input to header PHI nodes +//! - **Determinism**: Consistent ValueId allocation across compilations +//! +//! # Example +//! +//! ```ignore +//! // After Pass 1: +//! // - carriers: [{ name: "x", init: 1, copy: 3, phi: 4 }] +//! // - pinned: [{ name: "y", param: 2, copy: 5, phi: 6 }] +//! +//! builder.emit_preheader(&mut ops)?; +//! +//! // Generated MIR in preheader block: +//! // r5 = r2 // pinned first +//! // r3 = r1 // carriers second +//! // jump @header +//! ``` + +use crate::mir::phi_core::loopform::builder_core::{LoopFormBuilder, LoopFormOps}; + +/// Pass 2: Emit preheader block instructions +/// +/// Emits copy instructions for ALL variables in deterministic order: +/// 1. Pinned variables first +/// 2. Carrier variables second +/// +/// This ordering ensures consistent ValueId allocation across runs. +pub fn emit_preheader( + builder: &LoopFormBuilder, + ops: &mut O, +) -> Result<(), String> { + ops.set_current_block(builder.preheader_id)?; + + // Emit copies for pinned variables + for pinned in &builder.pinned { + ops.emit_copy(pinned.preheader_copy, pinned.param_value)?; + } + + // Emit copies for carrier variables + for carrier in &builder.carriers { + ops.emit_copy(carrier.preheader_copy, carrier.init_value)?; + } + + // Jump to header + ops.emit_jump(builder.header_id)?; + + Ok(()) +} diff --git a/src/mir/phi_core/loopform/passes/pass3_header_phi.rs b/src/mir/phi_core/loopform/passes/pass3_header_phi.rs new file mode 100644 index 00000000..6b4ce8d9 --- /dev/null +++ b/src/mir/phi_core/loopform/passes/pass3_header_phi.rs @@ -0,0 +1,106 @@ +//! Pass 3: Header PHI Construction +//! +//! This pass generates incomplete PHI nodes in the loop header block. +//! The PHI nodes initially have only the preheader input; the latch input +//! will be added in Pass 4 after the loop body is lowered. +//! +//! # Responsibilities +//! +//! 1. **Incomplete PHI Generation**: For each variable: +//! - Emit: `header_phi = phi [preheader_copy, preheader]` +//! - Update variable binding to header_phi +//! +//! 2. **Receiver Variable Aliasing** (Hotfix 7): +//! - Handle `me` → `__pin$N$@recv` aliasing +//! - Update all pin levels to point to same PHI +//! - Prevents stale ValueId references in nested loops +//! +//! 3. **Deterministic Order**: Process in consistent order: +//! - Pinned variables first +//! - Carrier variables second +//! +//! # Why Incomplete PHIs? +//! +//! We cannot complete the PHI nodes at this point because: +//! - The loop body hasn't been lowered yet +//! - We don't know the latch values +//! - Continue statements may create additional predecessors +//! +//! Pass 4 will complete these PHIs after loop body construction. +//! +//! # Example +//! +//! ```ignore +//! // After Pass 2 (preheader): +//! // r3 = r1 (carrier preheader copy) +//! // r5 = r2 (pinned preheader copy) +//! // jump @header +//! +//! builder.emit_header_phis(&mut ops)?; +//! +//! // Generated MIR in header block: +//! // r6 = phi [r5, @preheader] // pinned (incomplete) +//! // r4 = phi [r3, @preheader] // carrier (incomplete) +//! // var[y] = r6 +//! // var[x] = r4 +//! ``` + +use crate::mir::phi_core::loopform::builder_core::{LoopFormBuilder, LoopFormOps}; + +/// Pass 3: Emit header block PHI nodes (incomplete) +/// +/// Creates incomplete PHI nodes with only preheader input. +/// These will be completed in seal_phis() after loop body is lowered. +pub fn emit_header_phis( + builder: &mut LoopFormBuilder, + ops: &mut O, +) -> Result<(), String> { + ops.set_current_block(builder.header_id)?; + + // Emit PHIs for pinned variables + for pinned in &builder.pinned { + ops.emit_phi( + pinned.header_phi, + vec![(builder.preheader_id, pinned.preheader_copy)], + )?; + ops.update_var(pinned.name.clone(), pinned.header_phi); + + // 🔧 Hotfix 7 (Enhanced): Update aliases for pinned receiver variables + // When a variable like "me" is pinned to "__pin$N$@recv", both names + // must point to the same PHI value to avoid stale ValueId references. + // This handles: + // 1. Direct receiver: "me" → "__pin$1$@recv" + // 2. Nested loops: "__pin$1$@recv" → "__pin$2$@recv" + // 3. Multiple aliasing scenarios + if pinned.name.contains("@recv") { + // Always update "me" (the canonical receiver name) + ops.update_var("me".to_string(), pinned.header_phi); + + // Also update all previous pin levels (__pin$1$@recv, __pin$2$@recv, etc.) + // Extract the pin counter and update all lower levels + if let Some(idx) = pinned.name.find("$") { + if let Some(end_idx) = pinned.name[idx + 1..].find("$") { + if let Ok(counter) = pinned.name[idx + 1..idx + 1 + end_idx].parse::() + { + // Update all previous pin levels (1 through counter-1) + for i in 1..counter { + let alias = format!("__pin${}$@recv", i); + ops.update_var(alias, pinned.header_phi); + } + } + } + } + } + } + + // Emit PHIs for carrier variables + for carrier in &builder.carriers { + ops.emit_phi( + carrier.header_phi, + vec![(builder.preheader_id, carrier.preheader_copy)], + )?; + ops.update_var(carrier.name.clone(), carrier.header_phi); + } + + Ok(()) +} diff --git a/src/mir/phi_core/loopform/passes/pass4_seal.rs b/src/mir/phi_core/loopform/passes/pass4_seal.rs new file mode 100644 index 00000000..fc474917 --- /dev/null +++ b/src/mir/phi_core/loopform/passes/pass4_seal.rs @@ -0,0 +1,276 @@ +//! Pass 4: PHI Sealing - Complete PHI Nodes with Latch Values +//! +//! This pass completes the PHI nodes created in Pass 3 by adding the latch +//! input (the backedge from loop body to header). It also handles continue +//! statements which create additional predecessors to the header. +//! +//! # Responsibilities +//! +//! 1. **Latch Value Discovery**: Find the final value of each variable at the latch +//! 2. **PHI Input Completion**: Update incomplete PHI nodes with: +//! - Preheader input (already set in Pass 3) +//! - Continue inputs (from continue statements) +//! - Latch input (backedge) +//! 3. **PHI Optimization**: Skip PHI update if all inputs have same value +//! 4. **Header Bypass**: Skip PHI updates in JoinIR experimental path +//! +//! # Algorithm +//! +//! For each variable (pinned and carrier): +//! 1. Collect all PHI inputs: +//! - Preheader: `preheader_copy` +//! - Continues: Variable values at continue blocks +//! - Latch: Variable value at latch block +//! 2. Sanitize inputs: Remove duplicates using BTreeMap +//! 3. Optimize: If all values are same, skip PHI update +//! 4. Update: Call `update_phi_inputs()` with complete input list +//! +//! # Example +//! +//! ```ignore +//! // After Pass 3 (incomplete PHI): +//! // r4 = phi [r3, @preheader] // carrier "x" +//! +//! // Loop body execution: +//! // @body: +//! // r10 = r4 + 1 // x = x + 1 +//! // jump @latch +//! // @latch: +//! // jump @header // backedge +//! +//! builder.seal_phis(&mut ops, latch_id, &[], &HashSet::new(), false)?; +//! +//! // After Pass 4 (complete PHI): +//! // r4 = phi [r3, @preheader], [r10, @latch] +//! ``` + +use crate::mir::{BasicBlockId, ValueId}; +use std::collections::BTreeMap; + +use crate::mir::phi_core::loopform::builder_core::{LoopFormBuilder, LoopFormOps}; +use crate::mir::phi_core::loopform::utils::is_loopform_debug_enabled; + +/// Pass 4: Seal PHI nodes after loop body lowering +/// +/// Completes PHI nodes with latch + continue inputs, converting them from: +/// phi [preheader_val, preheader] +/// to: +/// phi [preheader_val, preheader], [continue_val, continue_bb]..., [latch_val, latch] +/// +/// # Parameters +/// - `latch_id`: The block that closes the canonical backedge to `header`. +/// - `continue_snapshots`: Per-`continue` block variable snapshots. +/// Each entry represents a predecessor of `header` created by `continue`. +/// - `_writes`: Variables modified in loop body (Step 5-1: 選択肢2) +/// Used to distinguish true carriers from loop-invariant variables +/// (Currently unused - PHI optimization uses optimize_same_value() instead) +/// - `header_bypass`: Phase 27.4C: Header φ バイパスフラグ +/// true の場合、Header φ 生成がスキップされているため、φ lookup も行わない +pub fn seal_phis( + builder: &mut LoopFormBuilder, + ops: &mut O, + latch_id: BasicBlockId, + continue_snapshots: &[(BasicBlockId, BTreeMap)], + _writes: &std::collections::HashSet, + header_bypass: bool, +) -> Result<(), String> { + let debug = std::env::var("NYASH_LOOPFORM_DEBUG").is_ok(); + + if debug { + crate::runtime::get_global_ring0().log.debug(&format!( + "[loopform/seal_phis] header={:?} preheader={:?} latch={:?} continue_snapshots={}", + builder.header_id, + builder.preheader_id, + latch_id, + continue_snapshots.len() + )); + } + + // Phase 27.4C Refactor: Delegate to specialized methods + seal_pinned_phis(builder, ops, latch_id, continue_snapshots, header_bypass)?; + seal_carrier_phis(builder, ops, latch_id, continue_snapshots, header_bypass)?; + + Ok(()) +} + +/// Phase 27.4C Refactor: Seal Pinned 変数 PHIs +/// +/// Pinned 変数(ループ不変パラメータ)の PHI ノード入力を finalize する。 +/// Header φ バイパス時は φ lookup をスキップし、preheader_copy をそのまま使用。 +fn seal_pinned_phis( + builder: &LoopFormBuilder, + ops: &mut O, + latch_id: BasicBlockId, + continue_snapshots: &[(BasicBlockId, BTreeMap)], + header_bypass: bool, +) -> Result<(), String> { + let debug = is_loopform_debug_enabled(); + + for pinned in &builder.pinned { + if header_bypass { + // Phase 27.4C: JoinIR 実験経路では Pinned 変数の φ lookup をスキップ + if debug { + eprintln!( + "[loopform/seal_phis/27.4C] SKIP pinned '{}' phi update (header bypass active)", + pinned.name + ); + } + continue; + } + + // ======================================== + // Phase 59: PhiInputCollector インライン化 + // ======================================== + // Step 1: 入力収集 + let mut raw_inputs: Vec<(BasicBlockId, ValueId)> = Vec::new(); + raw_inputs.push((builder.preheader_id, pinned.preheader_copy)); + + for (cid, snapshot) in continue_snapshots { + if let Some(&value) = snapshot.get(&pinned.name) { + raw_inputs.push((*cid, value)); + } + } + + let latch_value = ops + .get_variable_at_block(&pinned.name, latch_id) + .unwrap_or(pinned.header_phi); + raw_inputs.push((latch_id, latch_value)); + + // Step 2: sanitize (BTreeMapで重複削除&ソート) + let mut sanitized: std::collections::BTreeMap = + std::collections::BTreeMap::new(); + for (bb, val) in &raw_inputs { + sanitized.insert(*bb, *val); + } + let inputs: Vec<(BasicBlockId, ValueId)> = sanitized.into_iter().collect(); + + // Step 3: optimize_same_value + let same_value = if inputs.is_empty() { + None + } else if inputs.len() == 1 { + Some(inputs[0].1) + } else { + let first_val = inputs[0].1; + if inputs.iter().all(|(_, val)| *val == first_val) { + Some(first_val) + } else { + None + } + }; + + if let Some(same_val) = same_value { + if debug { + eprintln!( + "[loopform/seal_phis] OPTIMIZED pinned '{}': phi={:?} → same_value={:?} (loop-invariant)", + pinned.name, pinned.header_phi, same_val + ); + } + continue; + } + + if debug { + eprintln!( + "[loopform/seal_phis] pinned '{}' phi={:?} inputs={:?}", + pinned.name, pinned.header_phi, inputs + ); + } + + ops.update_phi_inputs(builder.header_id, pinned.header_phi, inputs)?; + } + + Ok(()) +} + +/// Phase 27.4C Refactor: Seal Carrier 変数 PHIs +/// +/// Carrier 変数(ループ内変数)の PHI ノード入力を finalize する。 +/// Header φ バイパス時は φ lookup をスキップし、preheader_copy をそのまま使用。 +fn seal_carrier_phis( + builder: &mut LoopFormBuilder, + ops: &mut O, + latch_id: BasicBlockId, + continue_snapshots: &[(BasicBlockId, BTreeMap)], + header_bypass: bool, +) -> Result<(), String> { + let debug = is_loopform_debug_enabled(); + + for carrier in &mut builder.carriers { + if header_bypass { + // Phase 27.4C: JoinIR 実験経路では Carrier 変数の φ lookup をスキップ + if debug { + eprintln!( + "[loopform/seal_phis/27.4C] SKIP carrier '{}' phi update (header bypass active)", + carrier.name + ); + } + continue; + } + + carrier.latch_value = ops + .get_variable_at_block(&carrier.name, latch_id) + .ok_or_else(|| { + format!( + "carrier '{}' not found at latch {:?}", + carrier.name, latch_id + ) + })?; + + // ======================================== + // Phase 59: PhiInputCollector インライン化 + // ======================================== + // Step 1: 入力収集 + let mut raw_inputs: Vec<(BasicBlockId, ValueId)> = Vec::new(); + raw_inputs.push((builder.preheader_id, carrier.preheader_copy)); + + for (cid, snapshot) in continue_snapshots { + if let Some(&value) = snapshot.get(&carrier.name) { + raw_inputs.push((*cid, value)); + } + } + + raw_inputs.push((latch_id, carrier.latch_value)); + + // Step 2: sanitize (BTreeMapで重複削除&ソート) + let mut sanitized: std::collections::BTreeMap = + std::collections::BTreeMap::new(); + for (bb, val) in &raw_inputs { + sanitized.insert(*bb, *val); + } + let inputs: Vec<(BasicBlockId, ValueId)> = sanitized.into_iter().collect(); + + // Step 3: optimize_same_value + let same_value = if inputs.is_empty() { + None + } else if inputs.len() == 1 { + Some(inputs[0].1) + } else { + let first_val = inputs[0].1; + if inputs.iter().all(|(_, val)| *val == first_val) { + Some(first_val) + } else { + None + } + }; + + if let Some(same_val) = same_value { + if debug { + eprintln!( + "[loopform/seal_phis] OPTIMIZED carrier '{}': phi={:?} → same_value={:?} (misclassified as carrier, actually loop-invariant)", + carrier.name, carrier.header_phi, same_val + ); + } + continue; + } + + if debug { + eprintln!( + "[loopform/seal_phis] carrier '{}' phi={:?} inputs={:?}", + carrier.name, carrier.header_phi, inputs + ); + } + + ops.update_phi_inputs(builder.header_id, carrier.header_phi, inputs)?; + } + + Ok(()) +} diff --git a/src/mir/phi_core/loopform_utils.rs b/src/mir/phi_core/loopform/utils.rs similarity index 91% rename from src/mir/phi_core/loopform_utils.rs rename to src/mir/phi_core/loopform/utils.rs index 67e73b6c..e269e12e 100644 --- a/src/mir/phi_core/loopform_utils.rs +++ b/src/mir/phi_core/loopform/utils.rs @@ -5,14 +5,14 @@ //! - Bypass flag management //! - Environment variable handling -use super::loopform_variable_models::LoopBypassFlags; +use super::variable_models::LoopBypassFlags; /// Phase 27.4C Cleanup: Check if LoopForm debug logging is enabled /// /// Returns true if the `NYASH_LOOPFORM_DEBUG` environment variable is set. /// This helper avoids duplicate checks across multiple locations. #[inline] -pub(crate) fn is_loopform_debug_enabled() -> bool { +pub fn is_loopform_debug_enabled() -> bool { std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() } @@ -26,7 +26,7 @@ pub(crate) fn is_loopform_debug_enabled() -> bool { /// /// # Returns /// - `LoopBypassFlags` - Header/Exit bypass status -pub(crate) fn get_loop_bypass_flags(_fn_name: &str) -> LoopBypassFlags { +pub fn get_loop_bypass_flags(_fn_name: &str) -> LoopBypassFlags { LoopBypassFlags { // Phase 73: Header φ bypass experiment discontinued (always OFF). // Only verified in LoopScopeShape/JoinIR mainline. @@ -42,7 +42,7 @@ pub(crate) fn get_loop_bypass_flags(_fn_name: &str) -> LoopBypassFlags { /// /// Phase 73: Exit φ bypass experiment discontinued (always OFF). /// JoinIR path is verified in LoopScopeShape/Exit φ mainline. -pub(crate) fn joinir_exit_bypass_enabled() -> bool { +pub fn joinir_exit_bypass_enabled() -> bool { false } @@ -51,7 +51,7 @@ pub(crate) fn joinir_exit_bypass_enabled() -> bool { /// Currently limited to 2 functions: /// - Main.skip/1 /// - FuncScannerBox.trim/1 -pub(crate) fn is_joinir_exit_bypass_target(func_name: &str) -> bool { +pub fn is_joinir_exit_bypass_target(func_name: &str) -> bool { matches!(func_name, "Main.skip/1" | "FuncScannerBox.trim/1") } diff --git a/src/mir/phi_core/loopform_variable_models.rs b/src/mir/phi_core/loopform/variable_models.rs similarity index 100% rename from src/mir/phi_core/loopform_variable_models.rs rename to src/mir/phi_core/loopform/variable_models.rs diff --git a/src/mir/phi_core/loopform_builder.rs b/src/mir/phi_core/loopform_builder.rs deleted file mode 100644 index 81a36938..00000000 --- a/src/mir/phi_core/loopform_builder.rs +++ /dev/null @@ -1,1166 +0,0 @@ -/*! - * phi_core::loopform_builder – LoopForm Meta-Box approach to PHI construction - * - * Solves the ValueId circular dependency problem by treating loop structure - * as a "Meta-Box" with explicit separation of carriers vs. pinned variables. - * - * Phase: 25.1b prototype implementation → 25.1m で安定化 - * Status: Always-on(LoopForm PHI v2 は既定実装。NYASH_LOOPFORM_PHI_V2 は互換目的のみで、挙動切り替えには使われない) - * - * Phase 191: Modularized into separate submodules - * - loopform_context: ValueId management - * - loopform_variable_models: CarrierVariable, PinnedVariable types - * - loopform_utils: Debug and bypass utilities - * - loopform_passes: 4-pass architecture documentation - * - loopform_exit_phi: Exit PHI builder - */ - -use crate::mir::phi_core::loop_snapshot_merge::LoopSnapshotMergeBox; -// Phase 59: PhiInputCollector使用廃止(インライン化完了) -// use crate::mir::phi_core::phi_input_collector::PhiInputCollector; -use crate::mir::{BasicBlockId, ValueId}; -use std::collections::BTreeMap; - -// Phase 191: Import modularized components -pub use super::loopform_context::LoopFormContext; -pub use super::loopform_variable_models::{CarrierVariable, PinnedVariable, LoopBypassFlags}; - -// Import utility functions (keeping them pub(crate) as they were) -use super::loopform_utils::{ - is_loopform_debug_enabled, get_loop_bypass_flags, - joinir_exit_bypass_enabled, is_joinir_exit_bypass_target -}; - -// Phase 191: Utility functions moved to loopform_utils.rs -// Phase 191: LoopFormContext moved to loopform_context.rs -// Phase 191: CarrierVariable and PinnedVariable moved to loopform_variable_models.rs -// Phase 191: LoopBypassFlags moved to loopform_variable_models.rs -// All re-exported above for compatibility - -/// LoopForm Meta-Box: Structured representation of loop SSA construction -/// -/// Separates loop-visible variables into classes(25.1e/25.2 スコープモデル): -/// - Carriers: Modified in loop body, need header/exit PHI nodes. -/// - Pinned: Loop-invariant parameters/receivers, need PHI so every header/exit -/// edge has a well-defined value but the logical value never changes. -/// - Invariants: Not tracked here; they keep the preheader ValueId and never -/// participate in PHI construction. -/// - Body-local live-out (BodyLocalInOut): Not stored as dedicated structs, but -/// detected at exit-phi time in `build_exit_phis` and merged via -/// `LoopSnapshotMergeBox::merge_exit`. -/// -/// Key idea: All ValueIds for Carriers/Pinned are allocated upfront before any -/// MIR emission, eliminating circular dependency issues in SSA. -#[derive(Debug)] -pub struct LoopFormBuilder { - pub carriers: Vec, - pub pinned: Vec, - pub preheader_id: BasicBlockId, - pub header_id: BasicBlockId, - /// Step 5-2: Preheader snapshot for ValueId comparison (選択肢3) - /// Used in seal_phis() to detect which variables are truly modified vs. invariant - pub preheader_vars: BTreeMap, -} - -impl LoopFormBuilder { - /// Create a new LoopForm builder with specified block IDs - pub fn new(preheader_id: BasicBlockId, header_id: BasicBlockId) -> Self { - Self { - carriers: Vec::new(), - pinned: Vec::new(), - preheader_id, - header_id, - preheader_vars: BTreeMap::new(), // Will be set in prepare_structure() - } - } - - /// Pass 1: Allocate all ValueIds for loop structure - /// - /// This is the critical innovation: we allocate ALL ValueIds - /// (preheader copies and header PHIs) BEFORE emitting any instructions. - /// This guarantees definition-before-use in SSA form. - pub fn prepare_structure( - &mut self, - ops: &mut O, - current_vars: &BTreeMap, - ) -> Result<(), String> { - let debug_enabled = std::env::var("NYASH_LOOPFORM_DEBUG").is_ok(); - - // Step 5-2: Save preheader snapshot for ValueId comparison in seal_phis() - self.preheader_vars = current_vars.clone(); - - if debug_enabled { - crate::runtime::get_global_ring0().log.debug(&format!( - "[loopform/prepare] === START prepare_structure() === {} variables", - current_vars.len() - )); - crate::runtime::get_global_ring0().log.debug("[loopform/prepare] Full variable list:"); - let mut sorted_vars: Vec<_> = current_vars.iter().collect(); - sorted_vars.sort_by_key(|(name, _)| name.as_str()); - for (name, value) in &sorted_vars { - crate::runtime::get_global_ring0().log.debug(&format!("[loopform/prepare] - {} = {:?}", name, value)); - } - } - - // GUARD: Detect invalid ValueId in variable map - // ValueId::INVALID (u32::MAX) indicates uninitialized variables - // Skip this loop construction attempt if detected (likely a premature build) - for (name, &value) in current_vars.iter() { - if value == ValueId::INVALID { - if debug_enabled { - crate::runtime::get_global_ring0().log.debug(&format!("[loopform/prepare] ⚠️ GUARD: Skipping loop preparation due to invalid ValueId for variable '{}'", name)); - crate::runtime::get_global_ring0().log.debug("[loopform/prepare] This indicates the loop is being built prematurely before variables are defined"); - crate::runtime::get_global_ring0().log.debug("[loopform/prepare] Returning Ok(()) to allow retry with properly initialized variables"); - } - // Return Ok to skip this attempt without failing the entire compilation - return Ok(()); - } - } - - // CRITICAL FIX: Ensure MirFunction counter is ahead of all existing ValueIds - // Without this, new_value() can return ValueIds that are already in use - let max_existing_id = current_vars.values().map(|v| v.0).max().unwrap_or(0); - - if debug_enabled { - eprintln!( - "[loopform/prepare] Calling ensure_counter_after(max_existing_id={})", - max_existing_id - ); - } - - ops.ensure_counter_after(max_existing_id)?; - - // Count variables by classification for summary - let mut param_count = 0; - let mut carrier_count = 0; - - // Separate variables into carriers and pinned based on parameter status - for (name, &value) in current_vars.iter() { - // Step 5-3: Skip __pin$ temporary variables (BodyLocalInternal) - // These are compiler-generated temporaries that should not have PHI nodes - if name.starts_with("__pin$") && name.contains("$@") { - if debug_enabled { - eprintln!("[loopform/prepare] SKIP __pin$ variable: {}", name); - } - continue; - } - - // Phase 26-A-4: ValueIdベース判定に変更(名前ベース → 型安全) - if ops.is_parameter(value) { - param_count += 1; - // Pinned variable (parameter, not modified in loop) - let pinned = PinnedVariable { - name: name.clone(), - param_value: value, - preheader_copy: ops.new_value(), // Allocate NOW - header_phi: ops.new_value(), // Allocate NOW - }; - if debug_enabled { - eprintln!( - "[loopform/prepare] PINNED: {} -> init={:?}, copy={:?}, phi={:?}", - name, value, pinned.preheader_copy, pinned.header_phi - ); - } - self.pinned.push(pinned); - } else { - carrier_count += 1; - // Carrier variable (local, modified in loop) - let carrier = CarrierVariable { - name: name.clone(), - init_value: value, - preheader_copy: ops.new_value(), // Allocate NOW - header_phi: ops.new_value(), // Allocate NOW - latch_value: ValueId(0), // Will be set during seal (placeholder) - }; - if debug_enabled { - eprintln!( - "[loopform/prepare] CARRIER: {} -> init={:?}, copy={:?}, phi={:?}", - name, value, carrier.preheader_copy, carrier.header_phi - ); - } - self.carriers.push(carrier); - } - } - - if debug_enabled { - crate::runtime::get_global_ring0().log.debug("[loopform/prepare] === SUMMARY ==="); - crate::runtime::get_global_ring0().log.debug(&format!("[loopform/prepare] Total vars: {}", current_vars.len())); - crate::runtime::get_global_ring0().log.debug(&format!("[loopform/prepare] Pinned (params): {}", param_count)); - crate::runtime::get_global_ring0().log.debug(&format!("[loopform/prepare] Carriers (locals): {}", carrier_count)); - } - - Ok(()) - } - - /// Pass 2: Emit preheader block instructions - /// - /// Emits copy instructions for ALL variables in deterministic order: - /// 1. Pinned variables first - /// 2. Carrier variables second - /// - /// This ordering ensures consistent ValueId allocation across runs. - pub fn emit_preheader(&self, ops: &mut O) -> Result<(), String> { - ops.set_current_block(self.preheader_id)?; - - // Emit copies for pinned variables - for pinned in &self.pinned { - ops.emit_copy(pinned.preheader_copy, pinned.param_value)?; - } - - // Emit copies for carrier variables - for carrier in &self.carriers { - ops.emit_copy(carrier.preheader_copy, carrier.init_value)?; - } - - // Jump to header - ops.emit_jump(self.header_id)?; - - Ok(()) - } - - /// Pass 3: Emit header block PHI nodes (incomplete) - /// - /// Creates incomplete PHI nodes with only preheader input. - /// These will be completed in seal_phis() after loop body is lowered. - pub fn emit_header_phis(&mut self, ops: &mut O) -> Result<(), String> { - ops.set_current_block(self.header_id)?; - - // Emit PHIs for pinned variables - for pinned in &self.pinned { - ops.emit_phi( - pinned.header_phi, - vec![(self.preheader_id, pinned.preheader_copy)], - )?; - ops.update_var(pinned.name.clone(), pinned.header_phi); - - // 🔧 Hotfix 7 (Enhanced): Update aliases for pinned receiver variables - // When a variable like "me" is pinned to "__pin$N$@recv", both names - // must point to the same PHI value to avoid stale ValueId references. - // This handles: - // 1. Direct receiver: "me" → "__pin$1$@recv" - // 2. Nested loops: "__pin$1$@recv" → "__pin$2$@recv" - // 3. Multiple aliasing scenarios - if pinned.name.contains("@recv") { - // Always update "me" (the canonical receiver name) - ops.update_var("me".to_string(), pinned.header_phi); - - // Also update all previous pin levels (__pin$1$@recv, __pin$2$@recv, etc.) - // Extract the pin counter and update all lower levels - if let Some(idx) = pinned.name.find("$") { - if let Some(end_idx) = pinned.name[idx + 1..].find("$") { - if let Ok(counter) = pinned.name[idx + 1..idx + 1 + end_idx].parse::() - { - // Update all previous pin levels (1 through counter-1) - for i in 1..counter { - let alias = format!("__pin${}$@recv", i); - ops.update_var(alias, pinned.header_phi); - } - } - } - } - } - } - - // Emit PHIs for carrier variables - for carrier in &self.carriers { - ops.emit_phi( - carrier.header_phi, - vec![(self.preheader_id, carrier.preheader_copy)], - )?; - ops.update_var(carrier.name.clone(), carrier.header_phi); - } - - Ok(()) - } - - /// Pass 4: Seal PHI nodes after loop body lowering - /// - /// Completes PHI nodes with latch + continue inputs, converting them from: - /// phi [preheader_val, preheader] - /// to: - /// phi [preheader_val, preheader], [continue_val, continue_bb]..., [latch_val, latch] - /// - /// # Parameters - /// - `latch_id`: The block that closes the canonical backedge to `header`. - /// - `continue_snapshots`: Per-`continue` block variable snapshots. - /// Each entry represents a predecessor of `header` created by `continue`. - /// - `writes`: Variables modified in loop body (Step 5-1: 選択肢2) - /// Used to distinguish true carriers from loop-invariant variables - /// (Currently unused - PHI optimization uses optimize_same_value() instead) - /// - `header_bypass`: Phase 27.4C: Header φ バイパスフラグ - /// true の場合、Header φ 生成がスキップされているため、φ lookup も行わない - pub fn seal_phis( - &mut self, - ops: &mut O, - latch_id: BasicBlockId, - continue_snapshots: &[(BasicBlockId, BTreeMap)], - _writes: &std::collections::HashSet, // Step 5-1/5-2: Reserved for future optimization - header_bypass: bool, // Phase 27.4C: Header φ バイパスフラグ - ) -> Result<(), String> { - let debug = std::env::var("NYASH_LOOPFORM_DEBUG").is_ok(); - - if debug { - crate::runtime::get_global_ring0().log.debug(&format!( - "[loopform/seal_phis] header={:?} preheader={:?} latch={:?} continue_snapshots={}", - self.header_id, - self.preheader_id, - latch_id, - continue_snapshots.len() - )); - } - - // Phase 27.4C Refactor: Delegate to specialized methods - self.seal_pinned_phis(ops, latch_id, continue_snapshots, header_bypass)?; - self.seal_carrier_phis(ops, latch_id, continue_snapshots, header_bypass)?; - - Ok(()) - } - - /// Phase 27.4C Refactor: Seal Pinned 変数 PHIs - /// - /// Pinned 変数(ループ不変パラメータ)の PHI ノード入力を finalize する。 - /// Header φ バイパス時は φ lookup をスキップし、preheader_copy をそのまま使用。 - fn seal_pinned_phis( - &self, - ops: &mut O, - latch_id: BasicBlockId, - continue_snapshots: &[(BasicBlockId, BTreeMap)], - header_bypass: bool, - ) -> Result<(), String> { - let debug = is_loopform_debug_enabled(); - - for pinned in &self.pinned { - if header_bypass { - // Phase 27.4C: JoinIR 実験経路では Pinned 変数の φ lookup をスキップ - if debug { - eprintln!( - "[loopform/seal_phis/27.4C] SKIP pinned '{}' phi update (header bypass active)", - pinned.name - ); - } - continue; - } - - // ======================================== - // Phase 59: PhiInputCollector インライン化 - // ======================================== - // Step 1: 入力収集 - let mut raw_inputs: Vec<(BasicBlockId, ValueId)> = Vec::new(); - raw_inputs.push((self.preheader_id, pinned.preheader_copy)); - - for (cid, snapshot) in continue_snapshots { - if let Some(&value) = snapshot.get(&pinned.name) { - raw_inputs.push((*cid, value)); - } - } - - let latch_value = ops - .get_variable_at_block(&pinned.name, latch_id) - .unwrap_or(pinned.header_phi); - raw_inputs.push((latch_id, latch_value)); - - // Step 2: sanitize (BTreeMapで重複削除&ソート) - let mut sanitized: std::collections::BTreeMap = - std::collections::BTreeMap::new(); - for (bb, val) in &raw_inputs { - sanitized.insert(*bb, *val); - } - let inputs: Vec<(BasicBlockId, ValueId)> = sanitized.into_iter().collect(); - - // Step 3: optimize_same_value - let same_value = if inputs.is_empty() { - None - } else if inputs.len() == 1 { - Some(inputs[0].1) - } else { - let first_val = inputs[0].1; - if inputs.iter().all(|(_, val)| *val == first_val) { - Some(first_val) - } else { - None - } - }; - - if let Some(same_val) = same_value { - if debug { - eprintln!( - "[loopform/seal_phis] OPTIMIZED pinned '{}': phi={:?} → same_value={:?} (loop-invariant)", - pinned.name, pinned.header_phi, same_val - ); - } - continue; - } - - if debug { - eprintln!( - "[loopform/seal_phis] pinned '{}' phi={:?} inputs={:?}", - pinned.name, pinned.header_phi, inputs - ); - } - - ops.update_phi_inputs(self.header_id, pinned.header_phi, inputs)?; - } - - Ok(()) - } - - /// Phase 27.4C Refactor: Seal Carrier 変数 PHIs - /// - /// Carrier 変数(ループ内変数)の PHI ノード入力を finalize する。 - /// Header φ バイパス時は φ lookup をスキップし、preheader_copy をそのまま使用。 - fn seal_carrier_phis( - &mut self, - ops: &mut O, - latch_id: BasicBlockId, - continue_snapshots: &[(BasicBlockId, BTreeMap)], - header_bypass: bool, - ) -> Result<(), String> { - let debug = is_loopform_debug_enabled(); - - for carrier in &mut self.carriers { - if header_bypass { - // Phase 27.4C: JoinIR 実験経路では Carrier 変数の φ lookup をスキップ - if debug { - eprintln!( - "[loopform/seal_phis/27.4C] SKIP carrier '{}' phi update (header bypass active)", - carrier.name - ); - } - continue; - } - - carrier.latch_value = ops - .get_variable_at_block(&carrier.name, latch_id) - .ok_or_else(|| { - format!( - "carrier '{}' not found at latch {:?}", - carrier.name, latch_id - ) - })?; - - // ======================================== - // Phase 59: PhiInputCollector インライン化 - // ======================================== - // Step 1: 入力収集 - let mut raw_inputs: Vec<(BasicBlockId, ValueId)> = Vec::new(); - raw_inputs.push((self.preheader_id, carrier.preheader_copy)); - - for (cid, snapshot) in continue_snapshots { - if let Some(&value) = snapshot.get(&carrier.name) { - raw_inputs.push((*cid, value)); - } - } - - raw_inputs.push((latch_id, carrier.latch_value)); - - // Step 2: sanitize (BTreeMapで重複削除&ソート) - let mut sanitized: std::collections::BTreeMap = - std::collections::BTreeMap::new(); - for (bb, val) in &raw_inputs { - sanitized.insert(*bb, *val); - } - let inputs: Vec<(BasicBlockId, ValueId)> = sanitized.into_iter().collect(); - - // Step 3: optimize_same_value - let same_value = if inputs.is_empty() { - None - } else if inputs.len() == 1 { - Some(inputs[0].1) - } else { - let first_val = inputs[0].1; - if inputs.iter().all(|(_, val)| *val == first_val) { - Some(first_val) - } else { - None - } - }; - - if let Some(same_val) = same_value { - if debug { - eprintln!( - "[loopform/seal_phis] OPTIMIZED carrier '{}': phi={:?} → same_value={:?} (misclassified as carrier, actually loop-invariant)", - carrier.name, carrier.header_phi, same_val - ); - } - continue; - } - - if debug { - eprintln!( - "[loopform/seal_phis] carrier '{}' phi={:?} inputs={:?}", - carrier.name, carrier.header_phi, inputs - ); - } - - ops.update_phi_inputs(self.header_id, carrier.header_phi, inputs)?; - } - - Ok(()) - } - - /// Build exit PHIs for break/continue merge points - /// - /// Similar to header PHIs, but merges: - /// - Header fallthrough (normal loop exit) - /// - Break snapshots (early exit from loop body) - /// - /// # Parameters - /// - `branch_source_block`: The ACTUAL block that emitted the branch to exit - /// (might differ from header_id if condition evaluation created new blocks) - /// Option C: Build exit PHIs with variable classification - /// - /// ## 引数 - /// - /// - `inspector`: LocalScopeInspectorBox(変数定義位置追跡) - /// - 各ブロックでどの変数が定義されているか追跡 - /// - BodyLocalInternal 変数(全exit predsで定義されていない)を検出 - /// - これらの変数は exit PHI を生成しない(PHI pred mismatch防止) - pub fn build_exit_phis( - &self, - ops: &mut O, - exit_id: BasicBlockId, - branch_source_block: BasicBlockId, - exit_snapshots: &[(BasicBlockId, BTreeMap)], - ) -> Result<(), String> { - ops.set_current_block(exit_id)?; - - let debug = std::env::var("NYASH_LOOPFORM_DEBUG").is_ok(); - if debug { - eprintln!("[DEBUG/exit_phi] ====== Exit PHI Generation ======"); - eprintln!( - "[DEBUG/exit_phi] exit_id = {:?}, header_id = {:?}, branch_source = {:?}", - exit_id, self.header_id, branch_source_block - ); - eprintln!( - "[DEBUG/exit_phi] exit_snapshots.len() = {}", - exit_snapshots.len() - ); - for (i, (bb, snap)) in exit_snapshots.iter().enumerate() { - eprintln!( - "[DEBUG/exit_phi] snapshot[{}]: block = {:?}, num_vars = {}", - i, - bb, - snap.len() - ); - } - eprintln!( - "[DEBUG/exit_phi] pinned.len() = {}, carriers.len() = {}", - self.pinned.len(), - self.carriers.len() - ); - } - - // Phase 25.2: LoopSnapshotMergeBox を使って exit PHI 統合 - - // 1. header_vals を準備(pinned + carriers) - let mut header_vals = BTreeMap::new(); - for pinned in &self.pinned { - header_vals.insert(pinned.name.clone(), pinned.header_phi); - } - for carrier in &self.carriers { - header_vals.insert(carrier.name.clone(), carrier.header_phi); - } - - // 2. body_local_vars を収集(決定的順序のためBTreeSet使用) - let mut body_local_names = Vec::new(); - let mut body_local_set: std::collections::BTreeSet = - std::collections::BTreeSet::new(); - for (_block_id, snapshot) in exit_snapshots { - // 決定的順序のため、keysをソートしてからイテレート - let mut sorted_keys: Vec<_> = snapshot.keys().collect(); - sorted_keys.sort(); - for var_name in sorted_keys { - // Step 5-5-D: Skip __pin$ temporary variables in exit PHI generation - // These are BodyLocalInternal and should NOT get exit PHIs - if var_name.starts_with("__pin$") && var_name.contains("$@") { - if debug { - eprintln!("[DEBUG/exit_phi] SKIP __pin$ variable: {}", var_name); - } - continue; - } - - let is_pinned = self.pinned.iter().any(|p| &p.name == var_name); - let is_carrier = self.carriers.iter().any(|c| &c.name == var_name); - if !is_pinned && !is_carrier && !body_local_set.contains(var_name) { - body_local_names.push(var_name.clone()); - body_local_set.insert(var_name.clone()); - } - } - } - - if debug && !body_local_names.is_empty() { - eprintln!( - "[DEBUG/exit_phi] Found {} body-local variables", - body_local_names.len() - ); - } - - // Option C: Body-local variables should NOT be added to header_vals! - // They are defined inside the loop body, not in the header. - // The inspector will correctly track which exit preds have them. - - // 📦 Hotfix 6: Filter exit_snapshots to only include valid CFG predecessors - let exit_preds = ops.get_block_predecessors(exit_id); - if debug { - eprintln!("[DEBUG/exit_phi] Exit block predecessors: {:?}", exit_preds); - } - - let mut filtered_snapshots = Vec::new(); - for (block_id, snapshot) in exit_snapshots { - if !ops.block_exists(*block_id) { - if debug { - eprintln!( - "[DEBUG/exit_phi] ⚠️ Skipping non-existent block {:?}", - block_id - ); - } - continue; - } - if !exit_preds.contains(block_id) { - if debug { - eprintln!( - "[DEBUG/exit_phi] ⚠️ Skipping block {:?} (not in CFG predecessors)", - block_id - ); - } - continue; - } - filtered_snapshots.push((*block_id, snapshot.clone())); - } - - // 3. Option C: Record snapshots in inspector for availability checking - // Phase 69-2: LocalScopeInspectorBox 削除完了 - // variable_definitions は LoopScopeShape に移行済み(Phase 48-4) - // inspector.record_*() 呼び出しは不要(LoopScopeShape 内部で処理) - - // 4. Option C: merge_exit_with_classification() でPHI pred mismatch防止 - // pinned/carrier名リストを準備 - let pinned_names: Vec = self.pinned.iter().map(|p| p.name.clone()).collect(); - let carrier_names: Vec = self.carriers.iter().map(|c| c.name.clone()).collect(); - - // exit_preds を Vec に変換 - let exit_preds_vec: Vec = exit_preds.iter().copied().collect(); - - // Phase 69-2: inspector 引数を削除(LoopScopeShape に移行済み) - let all_vars = LoopSnapshotMergeBox::merge_exit_with_classification( - branch_source_block, - &header_vals, - &filtered_snapshots, - &exit_preds_vec, // ← 実際のCFG predecessorsを渡す - &pinned_names, - &carrier_names, - )?; - - // ======================================== - // Phase 59: PHI生成(PhiInputCollectorインライン化) - // ======================================== - // 旧: PhiInputCollector::new() + add_snapshot + sanitize + optimize_same_value - // 新: BTreeMapで直接処理(同等のロジックをインライン展開) - for (var_name, inputs) in all_vars { - // Step 1: sanitize - BTreeMapで重複削除&ソート - let mut sanitized: std::collections::BTreeMap = - std::collections::BTreeMap::new(); - for (bb, val) in &inputs { - sanitized.insert(*bb, *val); - } - let final_inputs: Vec<(BasicBlockId, ValueId)> = sanitized.into_iter().collect(); - - if debug { - eprintln!( - "[DEBUG/exit_phi] Variable '{}': {} inputs", - var_name, - final_inputs.len() - ); - } - - // Step 2: optimize_same_value - 全て同じ値ならPHI不要 - 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 直接バインド - if let Some(same_val) = same_value { - // 全て同じ値 or 単一入力 → PHI 不要 - if debug { - eprintln!( - "[DEBUG/exit_phi] Variable '{}': single/same value, direct binding to {:?}", - var_name, same_val - ); - } - ops.update_var(var_name, same_val); - } else { - // 異なる値を持つ場合は PHI ノードを生成 - let phi_id = ops.new_value(); - if debug { - eprintln!( - "[DEBUG/exit_phi] Creating PHI {:?} for var '{}' with {} inputs", - phi_id, - var_name, - final_inputs.len() - ); - for (bb, val) in &final_inputs { - eprintln!("[DEBUG/exit_phi] PHI input: pred={:?} val={:?}", bb, val); - } - } - ops.emit_phi(phi_id, final_inputs)?; - ops.update_var(var_name, phi_id); - } - } - - Ok(()) - } -} - -/// Operations required by LoopFormBuilder -/// -/// This trait abstracts the underlying MIR builder operations, -/// allowing LoopFormBuilder to work with both Rust MIR builder -/// and selfhost compiler's JSON-based approach. -/// Phase 26-E-3: LoopFormOps と PhiBuilderOps の関係(委譲設計) -/// -/// **Design Rationale (ChatGPT + Claude consensus, 2025-11-22):** -/// - PhiBuilderOps = 低レベル「PHI命令発行」道具箱 -/// - LoopFormOps = 高レベル「ループ構造構築」作業場 -/// - 関係: **has-a(委譲)** ではなく is-a(継承) -/// -/// **実装方針:** -/// - LoopFormOps trait: そのまま(継承なし) -/// - PhiBuilderOps 実装: 必要な型に個別実装 -/// - `impl<'a> PhiBuilderOps for LoopBuilder<'a>` で委譲 -/// - HashSet → Vec 変換 + ソートで決定性保証 -/// -/// **段階的統合:** -/// 1. If側: PhiBuilderBox 経由(既に完了) -/// 2. Loop側: LoopFormOps + ExitPhiBuilder/HeaderPhiBuilder(現状維持) -/// 3. 将来: 必要に応じて LoopBuilder に PhiBuilderOps 実装追加 -pub trait LoopFormOps { - /// Allocate a new ValueId - fn new_value(&mut self) -> ValueId; - - /// Ensure MirFunction counter is after the given ValueId to prevent collisions - /// CRITICAL: Must be called before allocating new ValueIds in LoopForm - fn ensure_counter_after(&mut self, max_id: u32) -> Result<(), String>; - - /// 📦 Check if a block exists in the CFG (Hotfix 2: Exit PHI predecessor validation) - /// Used to skip non-existent blocks when building exit PHIs. - fn block_exists(&self, block: BasicBlockId) -> bool; - - /// 📦 Get actual CFG predecessors for a block (Hotfix 6: PHI input validation) - /// Returns the set of blocks that actually branch to this block in the CFG. - /// Used to validate exit PHI inputs against actual control flow. - /// Phase 69-3: Changed to BTreeSet for determinism - fn get_block_predecessors( - &self, - block: BasicBlockId, - ) -> std::collections::BTreeSet; - - /// Phase 26-A-4: ValueIdベースのパラメータ判定(型安全化) - /// - /// 旧実装(名前ベース)から新実装(ValueIdベース)に変更。 - /// MirValueKindによる型安全判定で、GUARDバグを根絶。 - fn is_parameter(&self, value_id: ValueId) -> bool; - - /// Set current block for instruction emission - fn set_current_block(&mut self, block: BasicBlockId) -> Result<(), String>; - - /// Emit a copy instruction: dst = src - fn emit_copy(&mut self, dst: ValueId, src: ValueId) -> Result<(), String>; - - /// Emit a jump instruction to target block - fn emit_jump(&mut self, target: BasicBlockId) -> Result<(), String>; - - /// Emit a PHI node with given inputs - fn emit_phi( - &mut self, - dst: ValueId, - inputs: Vec<(BasicBlockId, ValueId)>, - ) -> Result<(), String>; - - /// Update PHI node inputs (for sealing incomplete PHIs) - fn update_phi_inputs( - &mut self, - block: BasicBlockId, - phi_id: ValueId, - inputs: Vec<(BasicBlockId, ValueId)>, - ) -> Result<(), String>; - - /// Update variable binding in current scope - fn update_var(&mut self, name: String, value: ValueId); - - /// Get variable value at specific block - fn get_variable_at_block(&self, name: &str, block: BasicBlockId) -> Option; - - /// Access underlying MirFunction (for liveness/MirQuery) - fn mir_function(&self) -> &crate::mir::MirFunction; -} - -/// Phase 26-B-3: sanitize_phi_inputs() removed - replaced by PhiInputCollector -/// See: src/mir/phi_core/phi_input_collector.rs - -/// Phase 25.1g: ControlForm-based wrapper for LoopForm Exit PHI generation. -/// This provides a thin adapter layer that accepts ControlForm and delegates -/// to the existing LoopFormBuilder::build_exit_phis implementation. -/// -/// **Important**: This does NOT change any PHI generation logic - it merely -/// provides a ControlForm-based entry point while preserving existing behavior. -// Phase 191: build_exit_phis_for_control moved to loopform_exit_phi.rs -// Re-exported here for compatibility -pub fn build_exit_phis_for_control( - loopform: &LoopFormBuilder, - ops: &mut O, - form: &crate::mir::control_form::ControlForm, - exit_snapshots: &[( - crate::mir::BasicBlockId, - std::collections::BTreeMap, - )], - branch_source_block: crate::mir::BasicBlockId, -) -> Result<(), String> { - use super::loopform_exit_phi::ExitPhiBuilder; - ExitPhiBuilder::build_exit_phis_for_control( - loopform, - ops, - form, - exit_snapshots, - branch_source_block, - ) -} - -#[cfg(test)] -mod tests { - use super::*; - use std::collections::BTreeMap; - - // Phase 26-B-3: test_sanitize_phi_inputs() removed - // Replaced by PhiInputCollector::test_sanitize_removes_duplicates() - // See: src/mir/phi_core/phi_input_collector.rs - - #[test] - fn test_loopform_builder_separation() { - let preheader = BasicBlockId::new(0); - let header = BasicBlockId::new(1); - let mut builder = LoopFormBuilder::new(preheader, header); - - // Mock ops - struct MockOps { - next_value: u32, - params: Vec, - func: crate::mir::MirFunction, - } - - impl MockOps { - fn new() -> Self { - 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::new(0)); - Self { - next_value: 100, - params: vec!["me".to_string(), "limit".to_string()], - func, - } - } - } - - impl LoopFormOps for MockOps { - fn new_value(&mut self) -> ValueId { - let id = ValueId::new(self.next_value); - self.next_value += 1; - id - } - - fn ensure_counter_after(&mut self, max_id: u32) -> Result<(), String> { - // 📦 Hotfix 1: Consider both parameter count and existing ValueIds - let param_count = self.params.len() as u32; - let min_counter = param_count.max(max_id + 1); - - if self.next_value < min_counter { - self.next_value = min_counter; - } - Ok(()) - } - - fn block_exists(&self, _block: BasicBlockId) -> bool { - // MockOps: always return true (all blocks exist in test) - true - } - - fn get_block_predecessors( - &self, - _block: BasicBlockId, - ) -> std::collections::BTreeSet { - // MockOps: return empty set (no CFG in test) - // Phase 69-3: Changed to BTreeSet for determinism - std::collections::BTreeSet::new() - } - - /// Phase 26-A-4: ValueIdベースのパラメータ判定(Mock版) - /// - /// テストモックでは、最初のN個のValueId(N=params.len())をパラメータとする。 - /// これは標準的な規約(%0, %1, ... がパラメータ)に従う。 - fn is_parameter(&self, value_id: ValueId) -> bool { - value_id.0 < self.params.len() as u32 - } - - fn set_current_block(&mut self, _block: BasicBlockId) -> Result<(), String> { - Ok(()) - } - - fn emit_copy(&mut self, _dst: ValueId, _src: ValueId) -> Result<(), String> { - Ok(()) - } - - fn emit_jump(&mut self, _target: BasicBlockId) -> Result<(), String> { - Ok(()) - } - - fn emit_phi( - &mut self, - _dst: ValueId, - _inputs: Vec<(BasicBlockId, ValueId)>, - ) -> Result<(), String> { - Ok(()) - } - - fn update_phi_inputs( - &mut self, - _block: BasicBlockId, - _phi_id: ValueId, - _inputs: Vec<(BasicBlockId, ValueId)>, - ) -> Result<(), String> { - Ok(()) - } - - fn update_var(&mut self, _name: String, _value: ValueId) {} - - fn get_variable_at_block(&self, _name: &str, _block: BasicBlockId) -> Option { - None - } - - fn mir_function(&self) -> &crate::mir::MirFunction { - &self.func - } - } - - let mut ops = MockOps::new(); - - // Setup variables: me, limit (params), i, a, b (locals) - let mut vars = BTreeMap::new(); - vars.insert("me".to_string(), ValueId::new(0)); - vars.insert("limit".to_string(), ValueId::new(1)); - vars.insert("i".to_string(), ValueId::new(2)); - vars.insert("a".to_string(), ValueId::new(3)); - vars.insert("b".to_string(), ValueId::new(4)); - - // Prepare structure - builder.prepare_structure(&mut ops, &vars).unwrap(); - - // Verify separation - assert_eq!(builder.pinned.len(), 2); // me, limit - assert_eq!(builder.carriers.len(), 3); // i, a, b - - // Verify all ValueIds allocated - for pinned in &builder.pinned { - assert_ne!(pinned.preheader_copy, ValueId::INVALID); - assert_ne!(pinned.header_phi, ValueId::INVALID); - } - for carrier in &builder.carriers { - assert_ne!(carrier.preheader_copy, ValueId::INVALID); - assert_ne!(carrier.header_phi, ValueId::INVALID); - } - - // Note: - // ValueId の具体的な数値は実装詳細に依存するため、ここでは - // 「INVALID ではない」「pinned/carrier でペアになっている」ことのみを検証する。 - // - // これにより BTreeMap の反復順序や将来の allocator 実装変更に依存しないテストにする。 - let mut seen_ids = std::collections::HashSet::new(); - - for pinned in &builder.pinned { - assert_ne!(pinned.preheader_copy, ValueId::INVALID); - assert_ne!(pinned.header_phi, ValueId::INVALID); - // preheader_copy と header_phi は異なる ValueId であるべき - assert_ne!(pinned.preheader_copy, pinned.header_phi); - seen_ids.insert(pinned.preheader_copy); - seen_ids.insert(pinned.header_phi); - } - for carrier in &builder.carriers { - assert_ne!(carrier.preheader_copy, ValueId::INVALID); - assert_ne!(carrier.header_phi, ValueId::INVALID); - assert_ne!(carrier.preheader_copy, carrier.header_phi); - // pinned で使われた ID と衝突していないことを軽く確認 - assert!(!seen_ids.contains(&carrier.preheader_copy)); - assert!(!seen_ids.contains(&carrier.header_phi)); - } - } - - #[test] - fn test_seal_phis_includes_continue_snapshots() { - let preheader = BasicBlockId::new(0); - let header = BasicBlockId::new(1); - let latch = BasicBlockId::new(2); - - // Prepare LoopFormBuilder with one pinned and one carrier variable - let mut builder = LoopFormBuilder::new(preheader, header); - builder.pinned.push(PinnedVariable { - name: "p".to_string(), - param_value: ValueId::new(1), - preheader_copy: ValueId::new(10), - header_phi: ValueId::new(20), - }); - builder.carriers.push(CarrierVariable { - name: "i".to_string(), - init_value: ValueId::new(2), - preheader_copy: ValueId::new(11), - header_phi: ValueId::new(21), - latch_value: ValueId::INVALID, - }); - - // Mock LoopFormOps that records PHI updates - struct MockSealOps { - vars_at_block: BTreeMap<(BasicBlockId, String), ValueId>, - phi_updates: Vec<(BasicBlockId, ValueId, Vec<(BasicBlockId, ValueId)>)>, - func: crate::mir::MirFunction, - } - - impl MockSealOps { - fn new() -> Self { - 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::new(0)); - Self { - vars_at_block: BTreeMap::new(), - phi_updates: Vec::new(), - func, - } - } - } - - impl LoopFormOps for MockSealOps { - fn new_value(&mut self) -> ValueId { - // Not used by seal_phis in this test - ValueId::new(999) - } - - fn ensure_counter_after(&mut self, _max_id: u32) -> Result<(), String> { - Ok(()) - } - - fn block_exists(&self, _block: BasicBlockId) -> bool { - true - } - - fn get_block_predecessors( - &self, - _block: BasicBlockId, - ) -> std::collections::BTreeSet { - // Phase 69-3: Changed to BTreeSet for determinism - std::collections::BTreeSet::new() - } - - /// Phase 26-A-4: ValueIdベースのパラメータ判定(Mock版・パラメータなし) - fn is_parameter(&self, _value_id: ValueId) -> bool { - false // このテストにはパラメータなし - } - - fn set_current_block(&mut self, _block: BasicBlockId) -> Result<(), String> { - Ok(()) - } - - fn emit_copy(&mut self, _dst: ValueId, _src: ValueId) -> Result<(), String> { - Ok(()) - } - - fn emit_jump(&mut self, _target: BasicBlockId) -> Result<(), String> { - Ok(()) - } - - fn emit_phi( - &mut self, - _dst: ValueId, - _inputs: Vec<(BasicBlockId, ValueId)>, - ) -> Result<(), String> { - Ok(()) - } - - fn update_phi_inputs( - &mut self, - block: BasicBlockId, - phi_id: ValueId, - inputs: Vec<(BasicBlockId, ValueId)>, - ) -> Result<(), String> { - self.phi_updates.push((block, phi_id, inputs)); - Ok(()) - } - - fn update_var(&mut self, _name: String, _value: ValueId) {} - - fn get_variable_at_block(&self, name: &str, block: BasicBlockId) -> Option { - self.vars_at_block.get(&(block, name.to_string())).copied() - } - - fn mir_function(&self) -> &crate::mir::MirFunction { - &self.func - } - } - - let mut ops = MockSealOps::new(); - // Latch values for p and i - ops.vars_at_block - .insert((latch, "p".to_string()), ValueId::new(30)); - ops.vars_at_block - .insert((latch, "i".to_string()), ValueId::new(31)); - - // Continue snapshot from block 5: p and i have distinct values there - let cont_bb = BasicBlockId::new(5); - let mut cont_snapshot: BTreeMap = BTreeMap::new(); - cont_snapshot.insert("p".to_string(), ValueId::new(40)); - cont_snapshot.insert("i".to_string(), ValueId::new(41)); - let continue_snapshots = vec![(cont_bb, cont_snapshot)]; - - // Act: seal PHIs - use std::collections::HashSet; - let writes = HashSet::new(); // Empty writes for test - builder - .seal_phis(&mut ops, latch, &continue_snapshots, &writes, false) // Phase 27.4C: normal mode for unit test - .expect("seal_phis should succeed"); - - // We expect PHI updates for both pinned (p) and carrier (i) - assert_eq!(ops.phi_updates.len(), 2); - - // Helper to find inputs for a given phi id - let find_inputs = - |phi_id: ValueId, updates: &[(BasicBlockId, ValueId, Vec<(BasicBlockId, ValueId)>)]| { - updates - .iter() - .find(|(_, id, _)| *id == phi_id) - .map(|(_, _, inputs)| inputs.clone()) - .expect("phi id not found in updates") - }; - - let pinned_inputs = find_inputs(ValueId::new(20), &ops.phi_updates); - assert!(pinned_inputs.contains(&(preheader, ValueId::new(10)))); - assert!(pinned_inputs.contains(&(cont_bb, ValueId::new(40)))); - assert!(pinned_inputs.contains(&(latch, ValueId::new(30)))); - - let carrier_inputs = find_inputs(ValueId::new(21), &ops.phi_updates); - assert!(carrier_inputs.contains(&(preheader, ValueId::new(11)))); - assert!(carrier_inputs.contains(&(cont_bb, ValueId::new(41)))); - assert!(carrier_inputs.contains(&(latch, ValueId::new(31)))); - } -} diff --git a/src/mir/phi_core/loopform_passes.rs b/src/mir/phi_core/loopform_passes.rs deleted file mode 100644 index 08d8f1ff..00000000 --- a/src/mir/phi_core/loopform_passes.rs +++ /dev/null @@ -1,133 +0,0 @@ -//! Phase 191: LoopForm 4-Pass PHI Generation Engine -//! -//! Responsibility: LoopForm → PHI construction 4-stage pipeline -//! 1. prepare_structure(): ValueId pre-allocation and carrier/pinned classification -//! 2. emit_preheader(): Preheader copy instruction generation -//! 3. emit_header_phis(): Header PHI generation (incomplete) -//! 4. seal_phis(): PHI completion (pinned/carrier separation) -//! -//! Note: This module documents the 4-pass architecture. The actual implementation -//! remains in loopform_builder.rs for stability. Future refactoring will move -//! the implementation here. - -use crate::mir::{BasicBlockId, ValueId}; -use std::collections::BTreeMap; - -/// 4-Pass PHI generation engine -/// -/// This struct documents the 4-pass pipeline architecture used by LoopFormBuilder. -/// Each pass has a specific responsibility: -/// -/// ## Pass 1: prepare_structure() -/// - Classify variables as carriers (modified in loop) or pinned (loop-invariant) -/// - Allocate ValueIds upfront for preheader_copy and header_phi -/// - Ensure counter is after all existing ValueIds -/// -/// ## Pass 2: emit_preheader() -/// - Generate copy instructions in preheader block -/// - Order: pinned variables first, then carrier variables (deterministic) -/// -/// ## Pass 3: emit_header_phis() -/// - Generate incomplete PHI nodes in header block -/// - First input: preheader_copy (known) -/// - Second input: latch value (unknown at this point) -/// -/// ## Pass 4: seal_phis() -/// - Complete PHI nodes by finding latch values -/// - Separate handling for pinned (seal_pinned_phis) and carrier (seal_carrier_phis) -/// - Update PHI inputs with actual latch values -/// -/// # Example Usage -/// ```ignore -/// let mut builder = LoopFormBuilder::new(preheader, header); -/// builder.prepare_structure(&mut ops, ¤t_vars)?; -/// builder.emit_preheader(&mut ops)?; -/// builder.emit_header_phis(&mut ops)?; -/// // ... loop body construction ... -/// builder.seal_phis(&mut ops, &latch_vars)?; -/// ``` -pub struct LoopFormPassEngine { - // Future: Will contain state for pass management -} - -impl LoopFormPassEngine { - /// Create a new pass engine - pub fn new() -> Self { - Self {} - } - - /// Run all 4 passes (placeholder) - /// - /// This is a placeholder for future implementation. - /// Currently, the passes are called directly on LoopFormBuilder. - pub fn run_all_passes(&mut self) -> Result<(), String> { - // Future implementation - Ok(()) - } -} - -/// Pass metadata for documentation and future optimization -#[derive(Debug, Clone)] -pub struct PassMetadata { - /// Pass number (1-4) - pub pass_number: u8, - /// Pass name - pub name: &'static str, - /// Pass description - pub description: &'static str, -} - -/// Get metadata for all 4 passes -pub fn get_pass_metadata() -> [PassMetadata; 4] { - [ - PassMetadata { - pass_number: 1, - name: "prepare_structure", - description: "ValueId allocation and variable classification", - }, - PassMetadata { - pass_number: 2, - name: "emit_preheader", - description: "Preheader copy instruction generation", - }, - PassMetadata { - pass_number: 3, - name: "emit_header_phis", - description: "Incomplete header PHI generation", - }, - PassMetadata { - pass_number: 4, - name: "seal_phis", - description: "PHI completion with latch values", - }, - ] -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_pass_engine_creation() { - let engine = LoopFormPassEngine::new(); - // Just verify it compiles - let _ = engine; - } - - #[test] - fn test_pass_metadata() { - let metadata = get_pass_metadata(); - assert_eq!(metadata.len(), 4); - assert_eq!(metadata[0].pass_number, 1); - assert_eq!(metadata[0].name, "prepare_structure"); - assert_eq!(metadata[1].name, "emit_preheader"); - assert_eq!(metadata[2].name, "emit_header_phis"); - assert_eq!(metadata[3].name, "seal_phis"); - } - - #[test] - fn test_pass_engine_run_all_passes() { - let mut engine = LoopFormPassEngine::new(); - assert!(engine.run_all_passes().is_ok()); - } -} diff --git a/src/mir/phi_core/mod.rs b/src/mir/phi_core/mod.rs index a3b5d629..71c634fb 100644 --- a/src/mir/phi_core/mod.rs +++ b/src/mir/phi_core/mod.rs @@ -13,13 +13,8 @@ pub mod conservative; // Phase 30 F-2.1: loop_phi 削除(LoopFormBuilder が SSOT) pub mod loop_snapshot_merge; -// Phase 191: LoopForm modularization -pub mod loopform_context; -pub mod loopform_variable_models; -pub mod loopform_utils; -pub mod loopform_passes; -pub mod loopform_exit_phi; -pub mod loopform_builder; +// Phase 191-193: LoopForm modularization - complete directory structure +pub mod loopform; // Trio legacy boxes removed in Phase 70: LoopScopeShape now owns classification/liveness. // Phase 26-B: Box-First Refactoring diff --git a/src/runner/json_v0_bridge/lowering/loop_.rs b/src/runner/json_v0_bridge/lowering/loop_.rs index 90147d66..23eb9b5e 100644 --- a/src/runner/json_v0_bridge/lowering/loop_.rs +++ b/src/runner/json_v0_bridge/lowering/loop_.rs @@ -24,7 +24,7 @@ use super::super::ast::ExprV0; use super::super::ast::StmtV0; use super::{lower_stmt_list_with_vars, new_block, BridgeEnv, LoopContext}; use crate::ast::Span; -use crate::mir::phi_core::loopform_builder::{LoopFormBuilder, LoopFormOps}; +use crate::mir::phi_core::loopform::{LoopFormBuilder, LoopFormOps}; use crate::mir::{BasicBlockId, MirFunction, MirInstruction, ValueId}; use std::collections::BTreeMap;