feat(phi): Phase 26-C-1 - LoopSnapshotManager実装完了
- Snapshot一元管理Box実装(402行) - Preheader/Exit/Continue snapshot統一管理 - 変数変更検出(is_modified) - 13個の包括的単体テスト全PASS ✅ Box-First理論: Snapshot管理の責任を明確に分離 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
439
src/mir/phi_core/loop_snapshot_manager.rs
Normal file
439
src/mir/phi_core/loop_snapshot_manager.rs
Normal file
@ -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<String, ValueId>,
|
||||
|
||||
/// Exit predecessorごとのsnapshot
|
||||
exit_snapshots: Vec<(BasicBlockId, HashMap<String, ValueId>)>,
|
||||
|
||||
/// Continue predecessorごとのsnapshot
|
||||
continue_snapshots: Vec<(BasicBlockId, HashMap<String, ValueId>)>,
|
||||
}
|
||||
|
||||
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<String, ValueId>) {
|
||||
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<String, ValueId>) {
|
||||
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<String, ValueId>,
|
||||
) {
|
||||
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<String, ValueId> {
|
||||
&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<String, ValueId>)] {
|
||||
&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<String, ValueId>)] {
|
||||
&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<Item = &String> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user