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:
nyash-codex
2025-11-20 21:02:40 +09:00
parent 857af3bbab
commit ff9bd3c238
2 changed files with 561 additions and 0 deletions

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

View File

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