diff --git a/src/mir/phi_core/loop_snapshot_manager.rs b/src/mir/phi_core/loop_snapshot_manager.rs new file mode 100644 index 00000000..cf067a6e --- /dev/null +++ b/src/mir/phi_core/loop_snapshot_manager.rs @@ -0,0 +1,439 @@ +//! Loop Snapshot Manager - ループSnapshotの一元管理Box +//! +//! Phase 26-C-1: LoopSnapshotManager実装 +//! - Preheader/Exit/Continue snapshotの一元管理 +//! - 変数変更検出 +//! - builder.rs/loopform_builder.rsで散在しているsnapshot管理を統一 +//! +//! Box-First理論: Snapshot管理の責任を明確に分離し、テスト可能な箱として提供 + +use crate::mir::{BasicBlockId, ValueId}; +use std::collections::HashMap; + +/// ループSnapshotの一元管理Box +/// +/// # Purpose +/// - Preheader/Exit/Continue時のvariable mapを保存 +/// - PHI生成時に必要なsnapshotを提供 +/// - 変数変更検出(preheader比較) +/// +/// # Responsibility Separation +/// - このBox: Snapshot保存・取得・比較 +/// - PhiInputCollector: PHI入力収集 +/// - BodyLocalPhiBuilder: BodyLocal変数判定 +/// +/// # Usage +/// ```ignore +/// let mut manager = LoopSnapshotManager::new(); +/// +/// // Preheader時点の変数を保存 +/// manager.save_preheader(preheader_vars); +/// +/// // Exit predecessorのsnapshotを追加 +/// manager.add_exit_snapshot(exit_block_id, exit_vars); +/// +/// // Continue predecessorのsnapshotを追加 +/// manager.add_continue_snapshot(continue_block_id, continue_vars); +/// +/// // 変数がloop内で変更されたかチェック +/// if manager.is_modified("x", current_value) { +/// // xはloop内で変更された +/// } +/// ``` +#[derive(Debug, Clone, Default)] +pub struct LoopSnapshotManager { + /// Preheader時点の変数状態 + preheader_snapshot: HashMap, + + /// Exit predecessorごとのsnapshot + exit_snapshots: Vec<(BasicBlockId, HashMap)>, + + /// Continue predecessorごとのsnapshot + continue_snapshots: Vec<(BasicBlockId, HashMap)>, +} + +impl LoopSnapshotManager { + /// Create a new LoopSnapshotManager + /// + /// # Returns + /// New manager with empty snapshots + /// + /// # Example + /// ```ignore + /// let manager = LoopSnapshotManager::new(); + /// ``` + pub fn new() -> Self { + Self::default() + } + + /// Save preheader snapshot + /// + /// # Arguments + /// * `vars` - Variable map at preheader block + /// + /// # Example + /// ```ignore + /// let mut vars = HashMap::new(); + /// vars.insert("x".to_string(), ValueId(1)); + /// vars.insert("y".to_string(), ValueId(2)); + /// manager.save_preheader(vars); + /// ``` + pub fn save_preheader(&mut self, vars: HashMap) { + self.preheader_snapshot = vars; + } + + /// Add exit snapshot + /// + /// # Arguments + /// * `block` - Exit predecessor block ID + /// * `vars` - Variable map at this exit predecessor + /// + /// # Example + /// ```ignore + /// manager.add_exit_snapshot(BasicBlockId(5), exit_vars); + /// ``` + pub fn add_exit_snapshot(&mut self, block: BasicBlockId, vars: HashMap) { + self.exit_snapshots.push((block, vars)); + } + + /// Add continue snapshot + /// + /// # Arguments + /// * `block` - Continue predecessor block ID + /// * `vars` - Variable map at this continue predecessor + /// + /// # Example + /// ```ignore + /// manager.add_continue_snapshot(BasicBlockId(7), continue_vars); + /// ``` + pub fn add_continue_snapshot( + &mut self, + block: BasicBlockId, + vars: HashMap, + ) { + self.continue_snapshots.push((block, vars)); + } + + /// Get preheader snapshot + /// + /// # Returns + /// Reference to preheader variable map + /// + /// # Example + /// ```ignore + /// let preheader = manager.preheader(); + /// if let Some(&value_id) = preheader.get("x") { + /// println!("x at preheader: {:?}", value_id); + /// } + /// ``` + pub fn preheader(&self) -> &HashMap { + &self.preheader_snapshot + } + + /// Get exit snapshots + /// + /// # Returns + /// Slice of (block_id, variable_map) pairs for all exit predecessors + /// + /// # Example + /// ```ignore + /// for (block_id, vars) in manager.exit_snapshots() { + /// println!("Exit from block {:?}: {} variables", block_id, vars.len()); + /// } + /// ``` + pub fn exit_snapshots(&self) -> &[(BasicBlockId, HashMap)] { + &self.exit_snapshots + } + + /// Get continue snapshots + /// + /// # Returns + /// Slice of (block_id, variable_map) pairs for all continue predecessors + /// + /// # Example + /// ```ignore + /// for (block_id, vars) in manager.continue_snapshots() { + /// println!("Continue from block {:?}: {} variables", block_id, vars.len()); + /// } + /// ``` + pub fn continue_snapshots(&self) -> &[(BasicBlockId, HashMap)] { + &self.continue_snapshots + } + + /// Check if a variable was modified in the loop + /// + /// # Arguments + /// * `var_name` - Variable name to check + /// * `current_value` - Current ValueId of the variable + /// + /// # Returns + /// - `true` if the variable was modified (different from preheader) + /// - `true` if the variable is new (not in preheader) + /// - `false` if the variable is unchanged + /// + /// # Example + /// ```ignore + /// if manager.is_modified("x", ValueId(10)) { + /// println!("x was modified in the loop"); + /// } else { + /// println!("x is loop-invariant"); + /// } + /// ``` + pub fn is_modified(&self, var_name: &str, current_value: ValueId) -> bool { + self.preheader_snapshot + .get(var_name) + .map(|&preheader_val| preheader_val != current_value) + .unwrap_or(true) // 新規変数は変更扱い + } + + /// Get all variables in preheader snapshot + /// + /// # Returns + /// Iterator over variable names in preheader + /// + /// # Example + /// ```ignore + /// for var_name in manager.preheader_vars() { + /// println!("Preheader var: {}", var_name); + /// } + /// ``` + pub fn preheader_vars(&self) -> impl Iterator { + self.preheader_snapshot.keys() + } + + /// Get number of exit snapshots + /// + /// # Returns + /// Number of exit predecessors recorded + pub fn exit_snapshot_count(&self) -> usize { + self.exit_snapshots.len() + } + + /// Get number of continue snapshots + /// + /// # Returns + /// Number of continue predecessors recorded + pub fn continue_snapshot_count(&self) -> usize { + self.continue_snapshots.len() + } + + /// Clear all snapshots + /// + /// # Purpose + /// Reset manager to initial state (useful for testing or reuse) + /// + /// # Example + /// ```ignore + /// manager.clear(); + /// assert_eq!(manager.exit_snapshot_count(), 0); + /// ``` + pub fn clear(&mut self) { + self.preheader_snapshot.clear(); + self.exit_snapshots.clear(); + self.continue_snapshots.clear(); + } +} + +// ============================================================================ +// Unit Tests +// ============================================================================ + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_new() { + let manager = LoopSnapshotManager::new(); + assert_eq!(manager.exit_snapshot_count(), 0); + assert_eq!(manager.continue_snapshot_count(), 0); + assert_eq!(manager.preheader().len(), 0); + } + + #[test] + fn test_save_preheader() { + let mut manager = LoopSnapshotManager::new(); + let mut vars = HashMap::new(); + vars.insert("x".to_string(), ValueId(1)); + vars.insert("y".to_string(), ValueId(2)); + + manager.save_preheader(vars.clone()); + + assert_eq!(manager.preheader().len(), 2); + assert_eq!(manager.preheader().get("x"), Some(&ValueId(1))); + assert_eq!(manager.preheader().get("y"), Some(&ValueId(2))); + } + + #[test] + fn test_add_exit_snapshot() { + let mut manager = LoopSnapshotManager::new(); + let mut vars1 = HashMap::new(); + vars1.insert("x".to_string(), ValueId(10)); + + let mut vars2 = HashMap::new(); + vars2.insert("x".to_string(), ValueId(20)); + + manager.add_exit_snapshot(BasicBlockId(1), vars1); + manager.add_exit_snapshot(BasicBlockId(2), vars2); + + assert_eq!(manager.exit_snapshot_count(), 2); + assert_eq!(manager.exit_snapshots()[0].0, BasicBlockId(1)); + assert_eq!(manager.exit_snapshots()[1].0, BasicBlockId(2)); + } + + #[test] + fn test_add_continue_snapshot() { + let mut manager = LoopSnapshotManager::new(); + let mut vars = HashMap::new(); + vars.insert("i".to_string(), ValueId(5)); + + manager.add_continue_snapshot(BasicBlockId(3), vars); + + assert_eq!(manager.continue_snapshot_count(), 1); + assert_eq!(manager.continue_snapshots()[0].0, BasicBlockId(3)); + } + + #[test] + fn test_is_modified_changed() { + let mut manager = LoopSnapshotManager::new(); + let mut vars = HashMap::new(); + vars.insert("x".to_string(), ValueId(1)); + manager.save_preheader(vars); + + // Different ValueId → modified + assert!(manager.is_modified("x", ValueId(10))); + } + + #[test] + fn test_is_modified_unchanged() { + let mut manager = LoopSnapshotManager::new(); + let mut vars = HashMap::new(); + vars.insert("x".to_string(), ValueId(1)); + manager.save_preheader(vars); + + // Same ValueId → not modified + assert!(!manager.is_modified("x", ValueId(1))); + } + + #[test] + fn test_is_modified_new_variable() { + let mut manager = LoopSnapshotManager::new(); + let mut vars = HashMap::new(); + vars.insert("x".to_string(), ValueId(1)); + manager.save_preheader(vars); + + // Variable not in preheader → treated as modified + assert!(manager.is_modified("y", ValueId(5))); + } + + #[test] + fn test_preheader_vars() { + let mut manager = LoopSnapshotManager::new(); + let mut vars = HashMap::new(); + vars.insert("x".to_string(), ValueId(1)); + vars.insert("y".to_string(), ValueId(2)); + vars.insert("z".to_string(), ValueId(3)); + manager.save_preheader(vars); + + let var_names: Vec<&String> = manager.preheader_vars().collect(); + assert_eq!(var_names.len(), 3); + assert!(var_names.contains(&&"x".to_string())); + assert!(var_names.contains(&&"y".to_string())); + assert!(var_names.contains(&&"z".to_string())); + } + + #[test] + fn test_clear() { + let mut manager = LoopSnapshotManager::new(); + + // Setup + let mut vars = HashMap::new(); + vars.insert("x".to_string(), ValueId(1)); + manager.save_preheader(vars.clone()); + manager.add_exit_snapshot(BasicBlockId(1), vars.clone()); + manager.add_continue_snapshot(BasicBlockId(2), vars); + + assert_eq!(manager.preheader().len(), 1); + assert_eq!(manager.exit_snapshot_count(), 1); + assert_eq!(manager.continue_snapshot_count(), 1); + + // Clear + manager.clear(); + + assert_eq!(manager.preheader().len(), 0); + assert_eq!(manager.exit_snapshot_count(), 0); + assert_eq!(manager.continue_snapshot_count(), 0); + } + + #[test] + fn test_multiple_exit_snapshots() { + let mut manager = LoopSnapshotManager::new(); + + for i in 1..=5 { + let mut vars = HashMap::new(); + vars.insert("x".to_string(), ValueId(i)); + manager.add_exit_snapshot(BasicBlockId(i), vars); + } + + assert_eq!(manager.exit_snapshot_count(), 5); + + for (i, (block_id, vars)) in manager.exit_snapshots().iter().enumerate() { + assert_eq!(*block_id, BasicBlockId((i + 1) as u32)); + assert_eq!(vars.get("x"), Some(&ValueId((i + 1) as u32))); + } + } + + #[test] + fn test_empty_preheader_all_modified() { + let manager = LoopSnapshotManager::new(); + + // Empty preheader → all variables treated as modified + assert!(manager.is_modified("x", ValueId(1))); + assert!(manager.is_modified("y", ValueId(2))); + } + + #[test] + fn test_default() { + let manager = LoopSnapshotManager::default(); + assert_eq!(manager.exit_snapshot_count(), 0); + assert_eq!(manager.continue_snapshot_count(), 0); + } + + #[test] + fn test_realistic_loop_scenario() { + // skip_whitespaceシナリオ + let mut manager = LoopSnapshotManager::new(); + + // Preheader: s, idx (パラメータ) + let mut preheader = HashMap::new(); + preheader.insert("s".to_string(), ValueId(1)); + preheader.insert("idx".to_string(), ValueId(2)); + manager.save_preheader(preheader); + + // Exit pred 1: early break (idx unchanged, ch undefined) + let mut exit1 = HashMap::new(); + exit1.insert("s".to_string(), ValueId(1)); + exit1.insert("idx".to_string(), ValueId(2)); // unchanged + manager.add_exit_snapshot(BasicBlockId(5), exit1); + + // Exit pred 2: after loop body (idx modified, ch defined) + let mut exit2 = HashMap::new(); + exit2.insert("s".to_string(), ValueId(1)); + exit2.insert("idx".to_string(), ValueId(10)); // modified + exit2.insert("ch".to_string(), ValueId(15)); // body-local + manager.add_exit_snapshot(BasicBlockId(7), exit2); + + // Continue: idx modified + let mut continue_vars = HashMap::new(); + continue_vars.insert("s".to_string(), ValueId(1)); + continue_vars.insert("idx".to_string(), ValueId(12)); + manager.add_continue_snapshot(BasicBlockId(6), continue_vars); + + // Verification + assert!(!manager.is_modified("s", ValueId(1))); // s unchanged + assert!(manager.is_modified("idx", ValueId(10))); // idx modified + assert!(manager.is_modified("ch", ValueId(15))); // ch is new + assert_eq!(manager.exit_snapshot_count(), 2); + assert_eq!(manager.continue_snapshot_count(), 1); + } +} diff --git a/src/mir/phi_core/mod.rs b/src/mir/phi_core/mod.rs index b3138564..987d444f 100644 --- a/src/mir/phi_core/mod.rs +++ b/src/mir/phi_core/mod.rs @@ -22,6 +22,9 @@ pub mod loop_var_classifier; pub mod phi_input_collector; pub mod body_local_phi_builder; +// Phase 26-C: Loop Snapshot & Header PHI Management +pub mod loop_snapshot_manager; + // Public surface for callers that want a stable path: // Phase 1: No re-exports to avoid touching private builder internals. // Callers should continue using existing paths. Future phases may expose