refactor(joinir): Phase 33-17-B LoopHeaderPhiInfo extraction

## Changes
- Extract LoopHeaderPhiInfo Box (116 lines) from loop_header_phi_builder
- loop_header_phi_builder.rs reduced: 318 → 217 lines (-32%)
- Proper separation of data structures from builder logic

## Module Structure
- loop_header_phi_info.rs: LoopHeaderPhiInfo, CarrierPhiEntry structs + tests
- loop_header_phi_builder.rs: LoopHeaderPhiBuilder implementation

## Box Theory Compliance
- Data structures separated from builder logic
- Each file has clear single responsibility
- Clean re-exports through mod.rs

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-12-07 12:28:11 +09:00
parent 8baca953ca
commit a6a97b3781
4 changed files with 128 additions and 110 deletions

View File

@ -9,7 +9,7 @@
use crate::mir::{BasicBlock, BasicBlockId, MirInstruction, MirModule, ValueId};
use crate::mir::builder::joinir_id_remapper::JoinIrIdRemapper;
use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary;
use super::loop_header_phi_builder::LoopHeaderPhiInfo;
use super::loop_header_phi_info::LoopHeaderPhiInfo;
use super::tail_call_classifier::{TailCallKind, classify_tail_call};
use super::merge_result::MergeResult;
use std::collections::HashMap;

View File

@ -1,6 +1,7 @@
//! Loop Header PHI Builder
//!
//! Phase 33-16: Generates PHI nodes at loop header blocks to track carrier values.
//! Phase 33-17-B: Refactored to separate data structures from builder logic.
//!
//! ## Problem
//!
@ -26,74 +27,8 @@
//! Called from merge pipeline between Phase 3 (remap_values) and Phase 4
//! (instruction_rewriter).
use crate::mir::{BasicBlock, BasicBlockId, MirInstruction, ValueId};
use std::collections::BTreeMap;
/// Information about loop header PHIs
///
/// Generated by LoopHeaderPhiBuilder and used by:
/// - exit_phi_builder: to reference the current loop value
/// - ExitLineReconnector: to update variable_map with final values
#[derive(Debug, Clone)]
pub struct LoopHeaderPhiInfo {
/// The block where header PHIs are placed
pub header_block: BasicBlockId,
/// Carrier variable PHI mappings: carrier_name → PHI dst
///
/// The PHI dst is the ValueId that represents the "current value"
/// of this carrier during loop iteration.
pub carrier_phis: BTreeMap<String, CarrierPhiEntry>,
/// Expression result PHI dst (if loop is used as expression)
///
/// For Pattern 2 (joinir_min_loop), this is the same as the loop
/// variable's PHI dst. For carrier-only patterns (trim), this is None.
pub expr_result_phi: Option<ValueId>,
}
/// Entry for a single carrier's header PHI
#[derive(Debug, Clone)]
pub struct CarrierPhiEntry {
/// PHI destination ValueId (the "current value" during iteration)
pub phi_dst: ValueId,
/// Entry edge: (from_block, init_value)
pub entry_incoming: (BasicBlockId, ValueId),
/// Latch edge: (from_block, updated_value) - set after instruction rewrite
pub latch_incoming: Option<(BasicBlockId, ValueId)>,
}
impl LoopHeaderPhiInfo {
/// Create empty LoopHeaderPhiInfo
pub fn empty(header_block: BasicBlockId) -> Self {
Self {
header_block,
carrier_phis: BTreeMap::new(),
expr_result_phi: None,
}
}
/// Get the PHI dst for a carrier variable
pub fn get_carrier_phi(&self, name: &str) -> Option<ValueId> {
self.carrier_phis.get(name).map(|e| e.phi_dst)
}
/// Set latch incoming for a carrier
///
/// Called from instruction_rewriter after processing tail call Copy instructions.
pub fn set_latch_incoming(&mut self, name: &str, from_block: BasicBlockId, value: ValueId) {
if let Some(entry) = self.carrier_phis.get_mut(name) {
entry.latch_incoming = Some((from_block, value));
}
}
/// Check if all carriers have latch incoming set
pub fn all_latch_set(&self) -> bool {
self.carrier_phis.values().all(|e| e.latch_incoming.is_some())
}
}
use crate::mir::{MirInstruction, ValueId, BasicBlockId};
use super::loop_header_phi_info::{LoopHeaderPhiInfo, CarrierPhiEntry};
/// Builder for loop header PHIs
///
@ -280,39 +215,3 @@ impl LoopHeaderPhiBuilder {
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_loop_header_phi_info_creation() {
let header = BasicBlockId(10);
let info = LoopHeaderPhiInfo::empty(header);
assert_eq!(info.header_block, header);
assert!(info.carrier_phis.is_empty());
assert!(info.expr_result_phi.is_none());
}
#[test]
fn test_carrier_phi_entry() {
let mut info = LoopHeaderPhiInfo::empty(BasicBlockId(10));
info.carrier_phis.insert(
"i".to_string(),
CarrierPhiEntry {
phi_dst: ValueId(100),
entry_incoming: (BasicBlockId(1), ValueId(5)),
latch_incoming: None,
},
);
assert_eq!(info.get_carrier_phi("i"), Some(ValueId(100)));
assert_eq!(info.get_carrier_phi("x"), None);
assert!(!info.all_latch_set());
info.set_latch_incoming("i", BasicBlockId(20), ValueId(50));
assert!(info.all_latch_set());
}
}

View File

@ -0,0 +1,116 @@
//! Loop Header PHI Information Structures
//!
//! Phase 33-17-B: Data structures for loop header PHI tracking.
//!
//! ## Overview
//!
//! These structures hold metadata about PHI nodes generated at loop headers
//! to track carrier variables. Used by:
//! - LoopHeaderPhiBuilder: to build PHIs
//! - exit_phi_builder: to reference current loop values
//! - ExitLineReconnector: to update variable_map with final values
use crate::mir::{BasicBlockId, ValueId};
use std::collections::BTreeMap;
/// Information about loop header PHIs
///
/// Generated by LoopHeaderPhiBuilder and used by:
/// - exit_phi_builder: to reference the current loop value
/// - ExitLineReconnector: to update variable_map with final values
#[derive(Debug, Clone)]
pub struct LoopHeaderPhiInfo {
/// The block where header PHIs are placed
pub header_block: BasicBlockId,
/// Carrier variable PHI mappings: carrier_name → PHI dst
///
/// The PHI dst is the ValueId that represents the "current value"
/// of this carrier during loop iteration.
pub carrier_phis: BTreeMap<String, CarrierPhiEntry>,
/// Expression result PHI dst (if loop is used as expression)
///
/// For Pattern 2 (joinir_min_loop), this is the same as the loop
/// variable's PHI dst. For carrier-only patterns (trim), this is None.
pub expr_result_phi: Option<ValueId>,
}
/// Entry for a single carrier's header PHI
#[derive(Debug, Clone)]
pub struct CarrierPhiEntry {
/// PHI destination ValueId (the "current value" during iteration)
pub phi_dst: ValueId,
/// Entry edge: (from_block, init_value)
pub entry_incoming: (BasicBlockId, ValueId),
/// Latch edge: (from_block, updated_value) - set after instruction rewrite
pub latch_incoming: Option<(BasicBlockId, ValueId)>,
}
impl LoopHeaderPhiInfo {
/// Create empty LoopHeaderPhiInfo
pub fn empty(header_block: BasicBlockId) -> Self {
Self {
header_block,
carrier_phis: BTreeMap::new(),
expr_result_phi: None,
}
}
/// Get the PHI dst for a carrier variable
pub fn get_carrier_phi(&self, name: &str) -> Option<ValueId> {
self.carrier_phis.get(name).map(|e| e.phi_dst)
}
/// Set latch incoming for a carrier
///
/// Called from instruction_rewriter after processing tail call Copy instructions.
pub fn set_latch_incoming(&mut self, name: &str, from_block: BasicBlockId, value: ValueId) {
if let Some(entry) = self.carrier_phis.get_mut(name) {
entry.latch_incoming = Some((from_block, value));
}
}
/// Check if all carriers have latch incoming set
pub fn all_latch_set(&self) -> bool {
self.carrier_phis.values().all(|e| e.latch_incoming.is_some())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_loop_header_phi_info_creation() {
let header = BasicBlockId(10);
let info = LoopHeaderPhiInfo::empty(header);
assert_eq!(info.header_block, header);
assert!(info.carrier_phis.is_empty());
assert!(info.expr_result_phi.is_none());
}
#[test]
fn test_carrier_phi_entry() {
let mut info = LoopHeaderPhiInfo::empty(BasicBlockId(10));
info.carrier_phis.insert(
"i".to_string(),
CarrierPhiEntry {
phi_dst: ValueId(100),
entry_incoming: (BasicBlockId(1), ValueId(5)),
latch_incoming: None,
},
);
assert_eq!(info.get_carrier_phi("i"), Some(ValueId(100)));
assert_eq!(info.get_carrier_phi("x"), None);
assert!(!info.all_latch_set());
info.set_latch_incoming("i", BasicBlockId(20), ValueId(50));
assert!(info.all_latch_set());
}
}

View File

@ -17,13 +17,16 @@ mod value_collector;
mod instruction_rewriter;
mod exit_phi_builder;
pub mod exit_line;
pub mod loop_header_phi_builder;
mod loop_header_phi_info;
mod loop_header_phi_builder;
mod tail_call_classifier;
mod merge_result;
// Phase 33-17: Re-export for use by other modules
pub use merge_result::MergeResult;
pub use tail_call_classifier::{TailCallKind, classify_tail_call};
pub use loop_header_phi_info::{LoopHeaderPhiInfo, CarrierPhiEntry};
pub use loop_header_phi_builder::LoopHeaderPhiBuilder;
use crate::mir::{BasicBlockId, MirModule, ValueId};
use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary;
@ -154,7 +157,7 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
);
}
let phi_info = loop_header_phi_builder::LoopHeaderPhiBuilder::build(
let phi_info = LoopHeaderPhiBuilder::build(
builder,
entry_block_remapped, // header_block (JoinIR's entry block = loop header)
host_entry_block, // entry_block (host's block that jumps to loop header)
@ -182,10 +185,10 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
phi_info
} else {
loop_header_phi_builder::LoopHeaderPhiInfo::empty(entry_block_remapped)
LoopHeaderPhiInfo::empty(entry_block_remapped)
}
} else {
loop_header_phi_builder::LoopHeaderPhiInfo::empty(entry_block_remapped)
LoopHeaderPhiInfo::empty(entry_block_remapped)
};
// Phase 4: Merge blocks and rewrite instructions
@ -212,7 +215,7 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
loop_header_phi_info.carrier_phis.len()
);
}
loop_header_phi_builder::LoopHeaderPhiBuilder::finalize(
LoopHeaderPhiBuilder::finalize(
builder,
&loop_header_phi_info,
debug,