feat(phi): Phase 26-C-2 - HeaderPhiBuilder実装完了
- Header PHI生成専門Box実装(563行) - Pinned/Carrier変数PHI管理 - PhiInputCollector統合でseal処理サポート - 13個の包括的単体テスト全PASS ✅ Box-First理論: Header PHI生成の責任を明確に分離 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
560
src/mir/phi_core/header_phi_builder.rs
Normal file
560
src/mir/phi_core/header_phi_builder.rs
Normal file
@ -0,0 +1,560 @@
|
||||
//! Header PHI Builder - Header PHI生成専門Box
|
||||
//!
|
||||
//! Phase 26-C-2: HeaderPhiBuilder実装
|
||||
//! - Loop header PHI node生成の専門化
|
||||
//! - Preheader入力設定
|
||||
//! - Latch値更新(seal時)
|
||||
//!
|
||||
//! Box-First理論: Header PHI生成の責任を明確に分離し、テスト可能な箱として提供
|
||||
|
||||
use crate::mir::{BasicBlockId, ValueId};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Header PHI生成専門Box
|
||||
///
|
||||
/// # Purpose
|
||||
/// - Loop headerでのPHI node生成
|
||||
/// - Pinned/Carrier変数の区別管理
|
||||
/// - Seal時のlatch/continue入力追加
|
||||
///
|
||||
/// # Responsibility Separation
|
||||
/// - このBox: Header PHI生成・seal処理
|
||||
/// - PhiInputCollector: PHI入力収集・最適化
|
||||
/// - LoopSnapshotManager: Snapshot管理
|
||||
///
|
||||
/// # Usage
|
||||
/// ```ignore
|
||||
/// let mut builder = HeaderPhiBuilder::new();
|
||||
///
|
||||
/// // Pinned変数のPHI準備
|
||||
/// builder.prepare_pinned_phi(
|
||||
/// "x".to_string(),
|
||||
/// ValueId(10), // phi_id
|
||||
/// ValueId(1), // param_value
|
||||
/// ValueId(2), // preheader_copy
|
||||
/// );
|
||||
///
|
||||
/// // Carrier変数のPHI準備
|
||||
/// builder.prepare_carrier_phi(
|
||||
/// "i".to_string(),
|
||||
/// ValueId(20), // phi_id
|
||||
/// ValueId(0), // init_value
|
||||
/// ValueId(3), // preheader_copy
|
||||
/// );
|
||||
///
|
||||
/// // PHI情報を取得してemit
|
||||
/// for phi_info in builder.pinned_phis() {
|
||||
/// // emit_phi(phi_info.phi_id, ...)
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct HeaderPhiBuilder {
|
||||
/// Pinned変数のPHI情報
|
||||
pinned_phis: Vec<PinnedPhiInfo>,
|
||||
/// Carrier変数のPHI情報
|
||||
carrier_phis: Vec<CarrierPhiInfo>,
|
||||
}
|
||||
|
||||
/// Pinned変数PHI情報
|
||||
///
|
||||
/// # Fields
|
||||
/// - `var_name`: 変数名
|
||||
/// - `phi_id`: Header PHIのValueId
|
||||
/// - `param_value`: 元のパラメータValueId
|
||||
/// - `preheader_copy`: PreheaderでのCopy ValueId
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct PinnedPhiInfo {
|
||||
/// Variable name
|
||||
pub var_name: String,
|
||||
/// PHI node ValueId
|
||||
pub phi_id: ValueId,
|
||||
/// Original parameter ValueId
|
||||
pub param_value: ValueId,
|
||||
/// Preheader copy ValueId
|
||||
pub preheader_copy: ValueId,
|
||||
}
|
||||
|
||||
/// Carrier変数PHI情報
|
||||
///
|
||||
/// # Fields
|
||||
/// - `var_name`: 変数名
|
||||
/// - `phi_id`: Header PHIのValueId
|
||||
/// - `init_value`: 初期ValueId
|
||||
/// - `preheader_copy`: PreheaderでのCopy ValueId
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct CarrierPhiInfo {
|
||||
/// Variable name
|
||||
pub var_name: String,
|
||||
/// PHI node ValueId
|
||||
pub phi_id: ValueId,
|
||||
/// Initial ValueId
|
||||
pub init_value: ValueId,
|
||||
/// Preheader copy ValueId
|
||||
pub preheader_copy: ValueId,
|
||||
}
|
||||
|
||||
impl HeaderPhiBuilder {
|
||||
/// Create a new HeaderPhiBuilder
|
||||
///
|
||||
/// # Returns
|
||||
/// New builder with empty PHI lists
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// let builder = HeaderPhiBuilder::new();
|
||||
/// ```
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
/// Prepare pinned variable PHI
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `var_name` - Variable name
|
||||
/// * `phi_id` - PHI node ValueId
|
||||
/// * `param_value` - Original parameter ValueId
|
||||
/// * `preheader_copy` - Preheader copy ValueId
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// builder.prepare_pinned_phi(
|
||||
/// "x".to_string(),
|
||||
/// ValueId(10),
|
||||
/// ValueId(1),
|
||||
/// ValueId(2),
|
||||
/// );
|
||||
/// ```
|
||||
pub fn prepare_pinned_phi(
|
||||
&mut self,
|
||||
var_name: String,
|
||||
phi_id: ValueId,
|
||||
param_value: ValueId,
|
||||
preheader_copy: ValueId,
|
||||
) {
|
||||
self.pinned_phis.push(PinnedPhiInfo {
|
||||
var_name,
|
||||
phi_id,
|
||||
param_value,
|
||||
preheader_copy,
|
||||
});
|
||||
}
|
||||
|
||||
/// Prepare carrier variable PHI
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `var_name` - Variable name
|
||||
/// * `phi_id` - PHI node ValueId
|
||||
/// * `init_value` - Initial ValueId
|
||||
/// * `preheader_copy` - Preheader copy ValueId
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// builder.prepare_carrier_phi(
|
||||
/// "i".to_string(),
|
||||
/// ValueId(20),
|
||||
/// ValueId(0),
|
||||
/// ValueId(3),
|
||||
/// );
|
||||
/// ```
|
||||
pub fn prepare_carrier_phi(
|
||||
&mut self,
|
||||
var_name: String,
|
||||
phi_id: ValueId,
|
||||
init_value: ValueId,
|
||||
preheader_copy: ValueId,
|
||||
) {
|
||||
self.carrier_phis.push(CarrierPhiInfo {
|
||||
var_name,
|
||||
phi_id,
|
||||
init_value,
|
||||
preheader_copy,
|
||||
});
|
||||
}
|
||||
|
||||
/// Get pinned PHI information
|
||||
///
|
||||
/// # Returns
|
||||
/// Slice of pinned PHI info
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// for phi_info in builder.pinned_phis() {
|
||||
/// println!("Pinned PHI: {} → {:?}", phi_info.var_name, phi_info.phi_id);
|
||||
/// }
|
||||
/// ```
|
||||
pub fn pinned_phis(&self) -> &[PinnedPhiInfo] {
|
||||
&self.pinned_phis
|
||||
}
|
||||
|
||||
/// Get carrier PHI information
|
||||
///
|
||||
/// # Returns
|
||||
/// Slice of carrier PHI info
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// for phi_info in builder.carrier_phis() {
|
||||
/// println!("Carrier PHI: {} → {:?}", phi_info.var_name, phi_info.phi_id);
|
||||
/// }
|
||||
/// ```
|
||||
pub fn carrier_phis(&self) -> &[CarrierPhiInfo] {
|
||||
&self.carrier_phis
|
||||
}
|
||||
|
||||
/// Get pinned PHI count
|
||||
///
|
||||
/// # Returns
|
||||
/// Number of pinned PHIs
|
||||
pub fn pinned_phi_count(&self) -> usize {
|
||||
self.pinned_phis.len()
|
||||
}
|
||||
|
||||
/// Get carrier PHI count
|
||||
///
|
||||
/// # Returns
|
||||
/// Number of carrier PHIs
|
||||
pub fn carrier_phi_count(&self) -> usize {
|
||||
self.carrier_phis.len()
|
||||
}
|
||||
|
||||
/// Get total PHI count
|
||||
///
|
||||
/// # Returns
|
||||
/// Total number of PHIs (pinned + carrier)
|
||||
pub fn total_phi_count(&self) -> usize {
|
||||
self.pinned_phi_count() + self.carrier_phi_count()
|
||||
}
|
||||
|
||||
/// Find pinned PHI by variable name
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `var_name` - Variable name to search
|
||||
///
|
||||
/// # Returns
|
||||
/// Option containing pinned PHI info if found
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// if let Some(phi_info) = builder.find_pinned_phi("x") {
|
||||
/// println!("Found pinned PHI: {:?}", phi_info.phi_id);
|
||||
/// }
|
||||
/// ```
|
||||
pub fn find_pinned_phi(&self, var_name: &str) -> Option<&PinnedPhiInfo> {
|
||||
self.pinned_phis
|
||||
.iter()
|
||||
.find(|phi| phi.var_name == var_name)
|
||||
}
|
||||
|
||||
/// Find carrier PHI by variable name
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `var_name` - Variable name to search
|
||||
///
|
||||
/// # Returns
|
||||
/// Option containing carrier PHI info if found
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// if let Some(phi_info) = builder.find_carrier_phi("i") {
|
||||
/// println!("Found carrier PHI: {:?}", phi_info.phi_id);
|
||||
/// }
|
||||
/// ```
|
||||
pub fn find_carrier_phi(&self, var_name: &str) -> Option<&CarrierPhiInfo> {
|
||||
self.carrier_phis
|
||||
.iter()
|
||||
.find(|phi| phi.var_name == var_name)
|
||||
}
|
||||
|
||||
/// Build PHI inputs for sealing (helper for actual seal implementation)
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `var_name` - Variable name
|
||||
/// * `preheader_id` - Preheader block ID
|
||||
/// * `preheader_copy` - Preheader copy ValueId
|
||||
/// * `latch_id` - Latch block ID
|
||||
/// * `latch_value` - Latch ValueId
|
||||
/// * `continue_snapshots` - Continue snapshot list
|
||||
///
|
||||
/// # Returns
|
||||
/// PHI inputs as (BasicBlockId, ValueId) pairs
|
||||
///
|
||||
/// # Note
|
||||
/// This is a helper method. Actual PHI emission should be done by the caller.
|
||||
pub fn build_phi_inputs_for_seal(
|
||||
&self,
|
||||
var_name: &str,
|
||||
preheader_id: BasicBlockId,
|
||||
preheader_copy: ValueId,
|
||||
latch_id: BasicBlockId,
|
||||
latch_value: ValueId,
|
||||
continue_snapshots: &[(BasicBlockId, HashMap<String, ValueId>)],
|
||||
) -> Vec<(BasicBlockId, ValueId)> {
|
||||
use crate::mir::phi_core::phi_input_collector::PhiInputCollector;
|
||||
|
||||
let mut collector = PhiInputCollector::new();
|
||||
|
||||
// Add preheader input
|
||||
collector.add_preheader(preheader_id, preheader_copy);
|
||||
|
||||
// Add continue snapshot inputs
|
||||
for (cid, snapshot) in continue_snapshots {
|
||||
if let Some(&value) = snapshot.get(var_name) {
|
||||
collector.add_snapshot(&[(*cid, value)]);
|
||||
}
|
||||
}
|
||||
|
||||
// Add latch input
|
||||
collector.add_latch(latch_id, latch_value);
|
||||
|
||||
// Sanitize and optimize
|
||||
collector.sanitize();
|
||||
|
||||
// Check for same-value optimization
|
||||
if let Some(_same_value) = collector.optimize_same_value() {
|
||||
// All inputs are the same → PHI can be eliminated
|
||||
// Return empty vec to signal PHI elimination
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
collector.finalize()
|
||||
}
|
||||
|
||||
/// Clear all PHI information
|
||||
///
|
||||
/// # Purpose
|
||||
/// Reset builder to initial state (useful for testing or reuse)
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// builder.clear();
|
||||
/// assert_eq!(builder.total_phi_count(), 0);
|
||||
/// ```
|
||||
pub fn clear(&mut self) {
|
||||
self.pinned_phis.clear();
|
||||
self.carrier_phis.clear();
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Unit Tests
|
||||
// ============================================================================
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_new() {
|
||||
let builder = HeaderPhiBuilder::new();
|
||||
assert_eq!(builder.pinned_phi_count(), 0);
|
||||
assert_eq!(builder.carrier_phi_count(), 0);
|
||||
assert_eq!(builder.total_phi_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prepare_pinned_phi() {
|
||||
let mut builder = HeaderPhiBuilder::new();
|
||||
builder.prepare_pinned_phi(
|
||||
"x".to_string(),
|
||||
ValueId(10),
|
||||
ValueId(1),
|
||||
ValueId(2),
|
||||
);
|
||||
|
||||
assert_eq!(builder.pinned_phi_count(), 1);
|
||||
let phi = &builder.pinned_phis()[0];
|
||||
assert_eq!(phi.var_name, "x");
|
||||
assert_eq!(phi.phi_id, ValueId(10));
|
||||
assert_eq!(phi.param_value, ValueId(1));
|
||||
assert_eq!(phi.preheader_copy, ValueId(2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prepare_carrier_phi() {
|
||||
let mut builder = HeaderPhiBuilder::new();
|
||||
builder.prepare_carrier_phi(
|
||||
"i".to_string(),
|
||||
ValueId(20),
|
||||
ValueId(0),
|
||||
ValueId(3),
|
||||
);
|
||||
|
||||
assert_eq!(builder.carrier_phi_count(), 1);
|
||||
let phi = &builder.carrier_phis()[0];
|
||||
assert_eq!(phi.var_name, "i");
|
||||
assert_eq!(phi.phi_id, ValueId(20));
|
||||
assert_eq!(phi.init_value, ValueId(0));
|
||||
assert_eq!(phi.preheader_copy, ValueId(3));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_total_phi_count() {
|
||||
let mut builder = HeaderPhiBuilder::new();
|
||||
builder.prepare_pinned_phi("x".to_string(), ValueId(10), ValueId(1), ValueId(2));
|
||||
builder.prepare_pinned_phi("y".to_string(), ValueId(11), ValueId(3), ValueId(4));
|
||||
builder.prepare_carrier_phi("i".to_string(), ValueId(20), ValueId(0), ValueId(5));
|
||||
|
||||
assert_eq!(builder.pinned_phi_count(), 2);
|
||||
assert_eq!(builder.carrier_phi_count(), 1);
|
||||
assert_eq!(builder.total_phi_count(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_pinned_phi() {
|
||||
let mut builder = HeaderPhiBuilder::new();
|
||||
builder.prepare_pinned_phi("x".to_string(), ValueId(10), ValueId(1), ValueId(2));
|
||||
builder.prepare_pinned_phi("y".to_string(), ValueId(11), ValueId(3), ValueId(4));
|
||||
|
||||
let phi = builder.find_pinned_phi("x");
|
||||
assert!(phi.is_some());
|
||||
assert_eq!(phi.unwrap().phi_id, ValueId(10));
|
||||
|
||||
let not_found = builder.find_pinned_phi("z");
|
||||
assert!(not_found.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_find_carrier_phi() {
|
||||
let mut builder = HeaderPhiBuilder::new();
|
||||
builder.prepare_carrier_phi("i".to_string(), ValueId(20), ValueId(0), ValueId(3));
|
||||
builder.prepare_carrier_phi("j".to_string(), ValueId(21), ValueId(0), ValueId(4));
|
||||
|
||||
let phi = builder.find_carrier_phi("i");
|
||||
assert!(phi.is_some());
|
||||
assert_eq!(phi.unwrap().phi_id, ValueId(20));
|
||||
|
||||
let not_found = builder.find_carrier_phi("k");
|
||||
assert!(not_found.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clear() {
|
||||
let mut builder = HeaderPhiBuilder::new();
|
||||
builder.prepare_pinned_phi("x".to_string(), ValueId(10), ValueId(1), ValueId(2));
|
||||
builder.prepare_carrier_phi("i".to_string(), ValueId(20), ValueId(0), ValueId(3));
|
||||
|
||||
assert_eq!(builder.total_phi_count(), 2);
|
||||
|
||||
builder.clear();
|
||||
|
||||
assert_eq!(builder.pinned_phi_count(), 0);
|
||||
assert_eq!(builder.carrier_phi_count(), 0);
|
||||
assert_eq!(builder.total_phi_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default() {
|
||||
let builder = HeaderPhiBuilder::default();
|
||||
assert_eq!(builder.total_phi_count(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_phi_inputs_for_seal_simple() {
|
||||
let builder = HeaderPhiBuilder::new();
|
||||
|
||||
// Simple case: preheader + latch only
|
||||
let inputs = builder.build_phi_inputs_for_seal(
|
||||
"x",
|
||||
BasicBlockId(1), // preheader_id
|
||||
ValueId(10), // preheader_copy
|
||||
BasicBlockId(2), // latch_id
|
||||
ValueId(20), // latch_value
|
||||
&[], // no continue snapshots
|
||||
);
|
||||
|
||||
// Should have 2 inputs: preheader and latch
|
||||
assert_eq!(inputs.len(), 2);
|
||||
assert!(inputs.contains(&(BasicBlockId(1), ValueId(10))));
|
||||
assert!(inputs.contains(&(BasicBlockId(2), ValueId(20))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_phi_inputs_for_seal_with_continue() {
|
||||
let builder = HeaderPhiBuilder::new();
|
||||
|
||||
// Continue snapshot
|
||||
let mut continue_vars = HashMap::new();
|
||||
continue_vars.insert("x".to_string(), ValueId(15));
|
||||
|
||||
let inputs = builder.build_phi_inputs_for_seal(
|
||||
"x",
|
||||
BasicBlockId(1), // preheader_id
|
||||
ValueId(10), // preheader_copy
|
||||
BasicBlockId(3), // latch_id
|
||||
ValueId(20), // latch_value
|
||||
&[(BasicBlockId(2), continue_vars)],
|
||||
);
|
||||
|
||||
// Should have 3 inputs: preheader, continue, latch
|
||||
assert_eq!(inputs.len(), 3);
|
||||
assert!(inputs.contains(&(BasicBlockId(1), ValueId(10))));
|
||||
assert!(inputs.contains(&(BasicBlockId(2), ValueId(15))));
|
||||
assert!(inputs.contains(&(BasicBlockId(3), ValueId(20))));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_build_phi_inputs_for_seal_same_value_optimization() {
|
||||
let builder = HeaderPhiBuilder::new();
|
||||
|
||||
// All inputs are the same → PHI elimination
|
||||
let inputs = builder.build_phi_inputs_for_seal(
|
||||
"x",
|
||||
BasicBlockId(1), // preheader_id
|
||||
ValueId(10), // preheader_copy (same as latch)
|
||||
BasicBlockId(2), // latch_id
|
||||
ValueId(10), // latch_value (same!)
|
||||
&[],
|
||||
);
|
||||
|
||||
// Should return empty vec (PHI eliminated)
|
||||
assert_eq!(inputs.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_skip_whitespace_scenario() {
|
||||
// skip_whitespaceシナリオ: s, idx (pinned)
|
||||
let mut builder = HeaderPhiBuilder::new();
|
||||
|
||||
builder.prepare_pinned_phi(
|
||||
"s".to_string(),
|
||||
ValueId(100), // phi_id
|
||||
ValueId(1), // param_value
|
||||
ValueId(2), // preheader_copy
|
||||
);
|
||||
|
||||
builder.prepare_pinned_phi(
|
||||
"idx".to_string(),
|
||||
ValueId(101), // phi_id
|
||||
ValueId(3), // param_value
|
||||
ValueId(4), // preheader_copy
|
||||
);
|
||||
|
||||
assert_eq!(builder.pinned_phi_count(), 2);
|
||||
assert_eq!(builder.carrier_phi_count(), 0);
|
||||
|
||||
let s_phi = builder.find_pinned_phi("s");
|
||||
assert!(s_phi.is_some());
|
||||
assert_eq!(s_phi.unwrap().phi_id, ValueId(100));
|
||||
|
||||
let idx_phi = builder.find_pinned_phi("idx");
|
||||
assert!(idx_phi.is_some());
|
||||
assert_eq!(idx_phi.unwrap().phi_id, ValueId(101));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mixed_pinned_and_carrier() {
|
||||
let mut builder = HeaderPhiBuilder::new();
|
||||
|
||||
// Pinned: function parameters
|
||||
builder.prepare_pinned_phi("s".to_string(), ValueId(100), ValueId(1), ValueId(2));
|
||||
builder.prepare_pinned_phi("idx".to_string(), ValueId(101), ValueId(3), ValueId(4));
|
||||
|
||||
// Carrier: loop-modified locals
|
||||
builder.prepare_carrier_phi("i".to_string(), ValueId(200), ValueId(0), ValueId(5));
|
||||
builder.prepare_carrier_phi("sum".to_string(), ValueId(201), ValueId(0), ValueId(6));
|
||||
|
||||
assert_eq!(builder.pinned_phi_count(), 2);
|
||||
assert_eq!(builder.carrier_phi_count(), 2);
|
||||
assert_eq!(builder.total_phi_count(), 4);
|
||||
}
|
||||
}
|
||||
@ -24,6 +24,7 @@ pub mod body_local_phi_builder;
|
||||
|
||||
// Phase 26-C: Loop Snapshot & Header PHI Management
|
||||
pub mod loop_snapshot_manager;
|
||||
pub mod header_phi_builder;
|
||||
|
||||
// Public surface for callers that want a stable path:
|
||||
// Phase 1: No re-exports to avoid touching private builder internals.
|
||||
|
||||
Reference in New Issue
Block a user