refactor(phase62): Delete phi_input_collector.rs (-384 lines)
Phase 62前倒し: PhiInputCollectorの物理削除 Changes: - Delete src/mir/phi_core/phi_input_collector.rs (-384 lines) - Inline PhiInputCollector logic in json_v0_bridge/lowering/loop_.rs - sanitize: BTreeMap dedup + sort - optimize_same_value: check all inputs for same value - Remove mod declaration in phi_core/mod.rs Rationale: - PhiInputCollector already inlined in Phase 59 (loopform_builder.rs) - Last remaining usage was in JSON v0 Bridge - Simple inline preserves exact behavior Impact: - -384 lines (Box + tests) - No behavior change (inline equivalent) - Build & test pass ✅ 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -20,7 +20,7 @@ pub mod loop_var_classifier;
|
||||
|
||||
// Phase 26-B: Box-First Refactoring
|
||||
// Phase 30 F-2.1: body_local_phi_builder 削除(LoopScopeShape で代替)
|
||||
pub mod phi_input_collector;
|
||||
// Phase 62: phi_input_collector 削除(インライン化完了)
|
||||
|
||||
// Phase 26-C: Loop Snapshot Management
|
||||
// Phase 30 F-2.1: header_phi_builder 削除(JoinIR loop_step で代替)
|
||||
|
||||
@ -1,383 +0,0 @@
|
||||
//! PHI Input Collector - PHI入力収集専門Box
|
||||
//!
|
||||
//! Phase 26-B-1: PhiInputCollector実装
|
||||
//! - PHI入力の収集
|
||||
//! - 重複predecessor削除(sanitize)
|
||||
//! - 同値縮約最適化(optimize)
|
||||
//!
|
||||
//! Box-First理論: PHI入力収集の責任を明確に分離し、テスト可能な箱として提供
|
||||
|
||||
use crate::mir::BasicBlockId;
|
||||
use crate::mir::ValueId;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// PHI入力収集専門Box
|
||||
///
|
||||
/// # Purpose
|
||||
/// - 複数のpredecessorからPHI入力を収集
|
||||
/// - 重複入力の削除(同じpredecessorからの複数入力)
|
||||
/// - 同値最適化(全て同じ値ならPHI不要)
|
||||
///
|
||||
/// # Usage
|
||||
/// ```ignore
|
||||
/// let mut collector = PhiInputCollector::new();
|
||||
/// collector.add_preheader(preheader_id, init_value);
|
||||
/// collector.add_latch(latch_id, updated_value);
|
||||
/// collector.sanitize();
|
||||
///
|
||||
/// if let Some(single_val) = collector.optimize_same_value() {
|
||||
/// // All inputs have the same value - no PHI needed
|
||||
/// return single_val;
|
||||
/// }
|
||||
///
|
||||
/// let inputs = collector.finalize();
|
||||
/// builder.insert_phi(inputs)?;
|
||||
/// ```
|
||||
pub struct PhiInputCollector {
|
||||
/// 収集された入力: (predecessor, value)
|
||||
inputs: Vec<(BasicBlockId, ValueId)>,
|
||||
}
|
||||
|
||||
impl PhiInputCollector {
|
||||
/// Create a new PhiInputCollector
|
||||
///
|
||||
/// # Returns
|
||||
/// Empty collector ready to collect PHI inputs
|
||||
pub fn new() -> Self {
|
||||
Self { inputs: Vec::new() }
|
||||
}
|
||||
|
||||
/// Add preheader input
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `block` - Preheader block ID
|
||||
/// * `value` - Initial value from preheader
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// collector.add_preheader(preheader_id, ValueId(0));
|
||||
/// ```
|
||||
pub fn add_preheader(&mut self, block: BasicBlockId, value: ValueId) {
|
||||
self.inputs.push((block, value));
|
||||
}
|
||||
|
||||
/// Add continue/break snapshot inputs
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `snapshot` - Slice of (block, value) pairs from continue/break points
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// let snapshot = vec![(continue_block, val1), (break_block, val2)];
|
||||
/// collector.add_snapshot(&snapshot);
|
||||
/// ```
|
||||
pub fn add_snapshot(&mut self, snapshot: &[(BasicBlockId, ValueId)]) {
|
||||
self.inputs.extend_from_slice(snapshot);
|
||||
}
|
||||
|
||||
/// Add latch input
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `block` - Latch block ID
|
||||
/// * `value` - Updated value from latch
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// collector.add_latch(latch_id, updated_val);
|
||||
/// ```
|
||||
pub fn add_latch(&mut self, block: BasicBlockId, value: ValueId) {
|
||||
self.inputs.push((block, value));
|
||||
}
|
||||
|
||||
/// Sanitize inputs - remove duplicates and sort
|
||||
///
|
||||
/// # Purpose
|
||||
/// - Remove duplicate entries from the same predecessor
|
||||
/// - Sort by BasicBlockId for determinism
|
||||
///
|
||||
/// # Algorithm
|
||||
/// 1. Collect all inputs into BTreeMap (eliminates duplicates)
|
||||
/// 2. Convert back to Vec
|
||||
/// 3. Sort by BasicBlockId
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// collector.add_preheader(bb1, val1);
|
||||
/// collector.add_preheader(bb1, val2); // Duplicate predecessor
|
||||
/// collector.sanitize();
|
||||
/// // Result: [(bb1, val2)] - only last value kept
|
||||
/// ```
|
||||
pub fn sanitize(&mut self) {
|
||||
// Use BTreeMap for deterministic iteration order
|
||||
let mut seen: BTreeMap<BasicBlockId, ValueId> = BTreeMap::new();
|
||||
for (bb, val) in self.inputs.iter() {
|
||||
seen.insert(*bb, *val);
|
||||
}
|
||||
self.inputs = seen.into_iter().collect();
|
||||
// BTreeMap already provides sorted order, but explicit sort for clarity
|
||||
self.inputs.sort_by_key(|(bb, _)| bb.0);
|
||||
}
|
||||
|
||||
/// Optimize same value - check if all inputs have the same value
|
||||
///
|
||||
/// # Returns
|
||||
/// - `Some(value)` if all inputs have the same value (PHI not needed)
|
||||
/// - `None` if inputs have different values (PHI required)
|
||||
///
|
||||
/// # Cases
|
||||
/// - Empty inputs: return None
|
||||
/// - Single input: return Some(value) - no PHI needed
|
||||
/// - Multiple inputs with same value: return Some(value) - no PHI needed
|
||||
/// - Multiple inputs with different values: return None - PHI required
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// collector.add_preheader(bb1, ValueId(5));
|
||||
/// collector.add_latch(bb2, ValueId(5));
|
||||
/// assert_eq!(collector.optimize_same_value(), Some(ValueId(5)));
|
||||
/// ```
|
||||
pub fn optimize_same_value(&self) -> Option<ValueId> {
|
||||
if self.inputs.is_empty() {
|
||||
return None;
|
||||
}
|
||||
if self.inputs.len() == 1 {
|
||||
return Some(self.inputs[0].1);
|
||||
}
|
||||
let first_val = self.inputs[0].1;
|
||||
if self.inputs.iter().all(|(_, val)| *val == first_val) {
|
||||
Some(first_val)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Finalize and retrieve collected inputs
|
||||
///
|
||||
/// # Returns
|
||||
/// Final list of (predecessor, value) pairs ready for PHI insertion
|
||||
///
|
||||
/// # Note
|
||||
/// This consumes the collector. Call after sanitize() and optimize_same_value().
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// let inputs = collector.finalize();
|
||||
/// builder.insert_phi(inputs)?;
|
||||
/// ```
|
||||
pub fn finalize(self) -> Vec<(BasicBlockId, ValueId)> {
|
||||
self.inputs
|
||||
}
|
||||
|
||||
/// Get current number of inputs
|
||||
///
|
||||
/// # Returns
|
||||
/// Number of collected inputs (before or after sanitization)
|
||||
pub fn len(&self) -> usize {
|
||||
self.inputs.len()
|
||||
}
|
||||
|
||||
/// Check if collector is empty
|
||||
///
|
||||
/// # Returns
|
||||
/// true if no inputs have been collected
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.inputs.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for PhiInputCollector {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Unit Tests
|
||||
// ============================================================================
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_empty_collector() {
|
||||
let collector = PhiInputCollector::new();
|
||||
assert!(collector.is_empty());
|
||||
assert_eq!(collector.len(), 0);
|
||||
assert_eq!(collector.optimize_same_value(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_single_input() {
|
||||
let mut collector = PhiInputCollector::new();
|
||||
collector.add_preheader(BasicBlockId(1), ValueId(10));
|
||||
|
||||
assert!(!collector.is_empty());
|
||||
assert_eq!(collector.len(), 1);
|
||||
|
||||
// Single input should optimize to that value
|
||||
assert_eq!(collector.optimize_same_value(), Some(ValueId(10)));
|
||||
|
||||
let inputs = collector.finalize();
|
||||
assert_eq!(inputs, vec![(BasicBlockId(1), ValueId(10))]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_inputs_same_value() {
|
||||
let mut collector = PhiInputCollector::new();
|
||||
collector.add_preheader(BasicBlockId(1), ValueId(5));
|
||||
collector.add_latch(BasicBlockId(2), ValueId(5));
|
||||
|
||||
assert_eq!(collector.len(), 2);
|
||||
|
||||
// Same value - should optimize
|
||||
assert_eq!(collector.optimize_same_value(), Some(ValueId(5)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multiple_inputs_different_values() {
|
||||
let mut collector = PhiInputCollector::new();
|
||||
collector.add_preheader(BasicBlockId(1), ValueId(5));
|
||||
collector.add_latch(BasicBlockId(2), ValueId(10));
|
||||
|
||||
assert_eq!(collector.len(), 2);
|
||||
|
||||
// Different values - cannot optimize
|
||||
assert_eq!(collector.optimize_same_value(), None);
|
||||
|
||||
let inputs = collector.finalize();
|
||||
assert_eq!(inputs.len(), 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sanitize_removes_duplicates() {
|
||||
let mut collector = PhiInputCollector::new();
|
||||
|
||||
// Add same predecessor twice with different values
|
||||
collector.add_preheader(BasicBlockId(1), ValueId(5));
|
||||
collector.add_preheader(BasicBlockId(1), ValueId(10));
|
||||
|
||||
assert_eq!(collector.len(), 2);
|
||||
|
||||
collector.sanitize();
|
||||
|
||||
// Should keep only one entry (last value)
|
||||
assert_eq!(collector.len(), 1);
|
||||
let inputs = collector.finalize();
|
||||
assert_eq!(inputs, vec![(BasicBlockId(1), ValueId(10))]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sanitize_sorts_by_block_id() {
|
||||
let mut collector = PhiInputCollector::new();
|
||||
|
||||
// Add in unsorted order
|
||||
collector.add_latch(BasicBlockId(5), ValueId(50));
|
||||
collector.add_preheader(BasicBlockId(2), ValueId(20));
|
||||
collector.add_snapshot(&[(BasicBlockId(3), ValueId(30))]);
|
||||
|
||||
collector.sanitize();
|
||||
|
||||
let inputs = collector.finalize();
|
||||
// Should be sorted by BasicBlockId
|
||||
assert_eq!(
|
||||
inputs,
|
||||
vec![
|
||||
(BasicBlockId(2), ValueId(20)),
|
||||
(BasicBlockId(3), ValueId(30)),
|
||||
(BasicBlockId(5), ValueId(50)),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_snapshot() {
|
||||
let mut collector = PhiInputCollector::new();
|
||||
|
||||
let snapshot = vec![
|
||||
(BasicBlockId(10), ValueId(100)),
|
||||
(BasicBlockId(20), ValueId(200)),
|
||||
];
|
||||
|
||||
collector.add_snapshot(&snapshot);
|
||||
|
||||
assert_eq!(collector.len(), 2);
|
||||
let inputs = collector.finalize();
|
||||
assert_eq!(inputs, snapshot);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_complex_workflow() {
|
||||
let mut collector = PhiInputCollector::new();
|
||||
|
||||
// Typical loop PHI scenario
|
||||
collector.add_preheader(BasicBlockId(100), ValueId(0)); // init value
|
||||
|
||||
// Continue snapshots
|
||||
collector.add_snapshot(&[
|
||||
(BasicBlockId(110), ValueId(1)),
|
||||
(BasicBlockId(120), ValueId(2)),
|
||||
]);
|
||||
|
||||
// Latch
|
||||
collector.add_latch(BasicBlockId(130), ValueId(3));
|
||||
|
||||
// Add duplicate to test sanitization
|
||||
collector.add_preheader(BasicBlockId(100), ValueId(99)); // Duplicate
|
||||
|
||||
assert_eq!(collector.len(), 5);
|
||||
|
||||
collector.sanitize();
|
||||
|
||||
// After sanitize: 4 unique blocks (100 deduplicated)
|
||||
assert_eq!(collector.len(), 4);
|
||||
|
||||
// Different values - cannot optimize
|
||||
assert_eq!(collector.optimize_same_value(), None);
|
||||
|
||||
let inputs = collector.finalize();
|
||||
|
||||
// Should be sorted
|
||||
assert_eq!(
|
||||
inputs,
|
||||
vec![
|
||||
(BasicBlockId(100), ValueId(99)), // Last value for bb 100
|
||||
(BasicBlockId(110), ValueId(1)),
|
||||
(BasicBlockId(120), ValueId(2)),
|
||||
(BasicBlockId(130), ValueId(3)),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_skip_whitespace_scenario() {
|
||||
// Real-world scenario from skip_whitespace function
|
||||
// Parameter s=ValueId(0), idx=ValueId(1)
|
||||
// Loop carrier: idx
|
||||
|
||||
let mut collector = PhiInputCollector::new();
|
||||
|
||||
// Preheader: idx from parameter
|
||||
collector.add_preheader(BasicBlockId(200), ValueId(1));
|
||||
|
||||
// Continue snapshots (idx updated in loop body)
|
||||
collector.add_snapshot(&[
|
||||
(BasicBlockId(210), ValueId(50)), // idx after first iteration
|
||||
(BasicBlockId(220), ValueId(51)), // idx after second iteration
|
||||
]);
|
||||
|
||||
collector.sanitize();
|
||||
|
||||
// Different values - PHI required
|
||||
assert_eq!(collector.optimize_same_value(), None);
|
||||
|
||||
let inputs = collector.finalize();
|
||||
assert_eq!(inputs.len(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default() {
|
||||
let collector = PhiInputCollector::default();
|
||||
assert!(collector.is_empty());
|
||||
}
|
||||
}
|
||||
@ -25,7 +25,6 @@ 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::phi_input_collector::PhiInputCollector;
|
||||
use crate::mir::{BasicBlockId, MirFunction, MirInstruction, ValueId};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
@ -355,28 +354,42 @@ pub(super) fn lower_loop_stmt(
|
||||
}
|
||||
|
||||
// 6-2) continue_merge_bb に必要な PHI を生成しつつ、merged_snapshot を作る
|
||||
// Phase 26-B-3: Use PhiInputCollector
|
||||
// Phase 62: PhiInputCollector インライン化
|
||||
let mut merged_snapshot: BTreeMap<String, ValueId> = BTreeMap::new();
|
||||
for (name, inputs) in all_inputs {
|
||||
let mut collector = PhiInputCollector::new();
|
||||
collector.add_snapshot(&inputs);
|
||||
collector.sanitize();
|
||||
// Inline PhiInputCollector logic
|
||||
// 1. Sanitize: remove duplicates and sort
|
||||
let mut seen: BTreeMap<BasicBlockId, ValueId> = BTreeMap::new();
|
||||
for (bb, val) in inputs.iter() {
|
||||
seen.insert(*bb, *val);
|
||||
}
|
||||
let mut sanitized_inputs: Vec<(BasicBlockId, ValueId)> = seen.into_iter().collect();
|
||||
sanitized_inputs.sort_by_key(|(bb, _)| bb.0);
|
||||
|
||||
let value = if let Some(same_val) = collector.optimize_same_value() {
|
||||
// 全て同じ値 or 単一入力 → PHI 不要
|
||||
same_val
|
||||
// 2. Optimize: check if all inputs have the same value
|
||||
let value = if sanitized_inputs.is_empty() {
|
||||
// Should not happen, but handle gracefully
|
||||
continue;
|
||||
} else if sanitized_inputs.len() == 1 {
|
||||
// Single input - no PHI needed
|
||||
sanitized_inputs[0].1
|
||||
} else {
|
||||
// 異なる値を持つ場合は PHI ノードを continue_merge_bb に生成
|
||||
let final_inputs = collector.finalize();
|
||||
let phi_id = ops.f.next_value_id();
|
||||
crate::mir::ssot::cf_common::insert_phi_at_head_spanned(
|
||||
ops.f,
|
||||
continue_merge_bb,
|
||||
phi_id,
|
||||
final_inputs,
|
||||
Span::unknown(),
|
||||
);
|
||||
phi_id
|
||||
let first_val = sanitized_inputs[0].1;
|
||||
if sanitized_inputs.iter().all(|(_, val)| *val == first_val) {
|
||||
// All same value - no PHI needed
|
||||
first_val
|
||||
} else {
|
||||
// Different values - PHI required
|
||||
let phi_id = ops.f.next_value_id();
|
||||
crate::mir::ssot::cf_common::insert_phi_at_head_spanned(
|
||||
ops.f,
|
||||
continue_merge_bb,
|
||||
phi_id,
|
||||
sanitized_inputs,
|
||||
Span::unknown(),
|
||||
);
|
||||
phi_id
|
||||
}
|
||||
};
|
||||
merged_snapshot.insert(name, value);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user