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:
nyash-codex
2025-11-20 20:41:50 +09:00
parent d2d76694af
commit 857af3bbab
2 changed files with 442 additions and 0 deletions

View 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);
}
}

View File

@ -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