refactor(phi_core): F-2.1 - 早期グループPHI箱削除(約2,500行削減)

## 削除したファイル
- header_phi_builder.rs (~628行) - バイパス関数を loopform_builder.rs に移動
- exit_phi_builder.rs (~1000行) - バイパス関数を loopform_builder.rs に移動
- body_local_phi_builder.rs (~550行) - 依存なし
- loop_phi.rs (~288行) - LoopPhiOps実装も削除

## 移動した関数
loopform_builder.rs に以下を移動:
- get_loop_bypass_flags() / LoopBypassFlags struct
- is_joinir_header_bypass_target()
- joinir_exit_bypass_enabled()
- is_joinir_exit_bypass_target()

## 修正したファイル
- loop_builder.rs: バイパス関数の参照先変更 + LoopPhiOps impl削除
- 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-11-25 23:42:35 +09:00
parent a898ff3f83
commit 2b47f47061
8 changed files with 87 additions and 2549 deletions

View File

@ -66,8 +66,6 @@ pub struct LoopBuilder<'a> {
}
impl<'a> LoopBuilder<'a> {
// Implement phi_core LoopPhiOps on LoopBuilder for in-place delegation
/// Find the source value of a Copy instruction in a given block
/// If `dst` is defined by a Copy instruction `dst = copy src`, return Some(src)
/// Otherwise return None
@ -303,7 +301,7 @@ impl<'a> LoopBuilder<'a> {
.unwrap_or_default();
let bypass_flags =
crate::mir::phi_core::header_phi_builder::get_loop_bypass_flags(&fn_name);
crate::mir::phi_core::loopform_builder::get_loop_bypass_flags(&fn_name);
if bypass_flags.header {
// Phase 27.4-C: JoinIR 実験経路では Header φ を生成しない。
@ -608,7 +606,7 @@ impl<'a> LoopBuilder<'a> {
// Phase 27.4C Refactor: Header φ バイパスフラグを統一取得seal_phis に渡す)
// Note: fn_name は既に line 299-304 で取得済み、String として保持されている
let bypass_flags_for_seal =
crate::mir::phi_core::header_phi_builder::get_loop_bypass_flags(&fn_name);
crate::mir::phi_core::loopform_builder::get_loop_bypass_flags(&fn_name);
// Step 5-1/5-2: Pass writes 集合 for PHI縮約
// Phase 27.4C: header_bypass フラグも渡す
@ -674,8 +672,8 @@ impl<'a> LoopBuilder<'a> {
.map(|f| f.signature.name.as_str())
.unwrap_or("");
let exit_bypass = crate::mir::phi_core::exit_phi_builder::joinir_exit_bypass_enabled()
&& crate::mir::phi_core::exit_phi_builder::is_joinir_exit_bypass_target(fn_name);
let exit_bypass = crate::mir::phi_core::loopform_builder::joinir_exit_bypass_enabled()
&& crate::mir::phi_core::loopform_builder::is_joinir_exit_bypass_target(fn_name);
if exit_bypass {
// Phase 27.6-2: JoinIR 実験経路では Exit φ を生成しない。
@ -971,34 +969,7 @@ impl<'a> LoopBuilder<'a> {
) -> Result<ValueId, String> {
// Reserve a deterministic join id for debug region labeling (nested inside loop)
let join_id = self.parent_builder.debug_next_join_id();
// Pre-pin comparison operands to slots so repeated uses across blocks are safe
if crate::config::env::mir_pre_pin_compare_operands() {
if let ASTNode::BinaryOp {
operator,
left,
right,
..
} = &condition
{
use crate::ast::BinaryOperator as BO;
match operator {
BO::Equal
| BO::NotEqual
| BO::Less
| BO::LessEqual
| BO::Greater
| BO::GreaterEqual => {
if let Ok(lhs_v) = self.parent_builder.build_expression((**left).clone()) {
let _ = self.parent_builder.pin_to_slot(lhs_v, "@loop_if_lhs");
}
if let Ok(rhs_v) = self.parent_builder.build_expression((**right).clone()) {
let _ = self.parent_builder.pin_to_slot(rhs_v, "@loop_if_rhs");
}
}
_ => {}
}
}
}
// Pre-pin heuristic was deprecated; leave operands untouched for clarity.
// Evaluate condition and create blocks
let cond_val = self.parent_builder.build_expression(condition)?;
let then_bb = self.new_block();
@ -1263,84 +1234,8 @@ impl<'a> LoopBuilder<'a> {
}
}
// Implement phi_core LoopPhiOps on LoopBuilder for in-place delegation
impl crate::mir::phi_core::loop_phi::LoopPhiOps for LoopBuilder<'_> {
fn new_value(&mut self) -> ValueId {
self.new_value()
}
fn emit_phi_at_block_start(
&mut self,
block: BasicBlockId,
dst: ValueId,
inputs: Vec<(BasicBlockId, ValueId)>,
) -> Result<(), String> {
self.emit_phi_at_block_start(block, dst, inputs)
}
fn update_var(&mut self, name: String, value: ValueId) {
self.update_variable(name, value)
}
fn get_variable_at_block(&mut self, name: &str, block: BasicBlockId) -> Option<ValueId> {
// Call the inherent method (immutable borrow) to avoid recursion
LoopBuilder::get_variable_at_block(self, name, block)
}
fn debug_verify_phi_inputs(
&mut self,
merge_bb: BasicBlockId,
inputs: &[(BasicBlockId, ValueId)],
) {
if let Some(ref func) = self.parent_builder.current_function {
crate::mir::phi_core::common::debug_verify_phi_inputs(func, merge_bb, inputs);
}
}
fn emit_copy_at_preheader(
&mut self,
preheader_id: BasicBlockId,
dst: ValueId,
src: ValueId,
) -> Result<(), String> {
let dbg = std::env::var("NYASH_BUILDER_DEBUG").ok().as_deref() == Some("1");
if dbg {
eprintln!(
"[DEBUG] emit_copy_at_preheader: preheader={}, dst=%{}, src=%{}",
preheader_id, dst.0, src.0
);
}
if let Some(ref mut function) = self.parent_builder.current_function {
if let Some(block) = function.get_block_mut(preheader_id) {
if dbg {
eprintln!(
"[DEBUG] Adding Copy instruction to block {}",
preheader_id
);
}
block.add_instruction_with_span(
MirInstruction::Copy { dst, src },
self.parent_builder.current_span,
);
Ok(())
} else {
if dbg {
eprintln!("[DEBUG] ❌ Preheader block {} not found!", preheader_id);
}
Err(format!("Preheader block {} not found", preheader_id))
}
} else {
if dbg {
eprintln!("[DEBUG] ❌ No current function!");
}
Err("No current function".to_string())
}
}
fn add_predecessor_edge(
&mut self,
block: BasicBlockId,
pred: BasicBlockId,
) -> Result<(), String> {
self.add_predecessor(block, pred)
}
}
// Phase 30 F-2.1: LoopPhiOps 実装削除loop_phi.rs 削除に伴う)
// LoopFormOps が SSOT として機能しているため、レガシー互換層は不要
// Implement LoopFormOps trait for LoopBuilder to support LoopFormBuilder integration
impl<'a> LoopFormOps for LoopBuilder<'a> {

View File

@ -1,557 +0,0 @@
//! Body Local PHI Builder - BodyLocal変数PHI生成専門Box
//!
//! Phase 26-B-2: BodyLocalPhiBuilder実装
//! - BodyLocal変数のPHI生成判定
//! - BodyLocalInternal変数のスキップ
//! - Exit PHI候補のフィルタリング
//!
//! Phase 26-F-4: Exit Liveness統合環境変数制御
//! - live_at_exit パラメータ追加(デフォルト: 空集合)
//! - `NYASH_EXIT_LIVE_ENABLE=1` で将来のMIRスキャン実装を有効化
//!
//! Box-First理論: BodyLocal変数処理の責任を明確に分離し、テスト可能な箱として提供
use super::local_scope_inspector::LocalScopeInspectorBox;
use super::loop_var_classifier::{LoopVarClass, LoopVarClassBox};
use crate::mir::BasicBlockId;
/// BodyLocal変数PHI生成専門Box
///
/// # Purpose
/// - BodyLocalExit/BodyLocalInternal変数を判定
/// - Exit PHI生成の要否を決定
/// - Exit PHI候補のフィルタリング
///
/// # Responsibility Separation
/// - Classifier: LoopVarClassBox変数分類
/// - Inspector: LocalScopeInspectorBox定義位置追跡
/// - Builder: このBoxPHI生成判定
///
/// # Usage
/// ```ignore
/// let inspector = LocalScopeInspectorBox::new();
/// let classifier = LoopVarClassBox::new();
/// let mut builder = BodyLocalPhiBuilder::new(classifier, inspector);
///
/// // Record definitions
/// builder.inspector_mut().record_definition("ch", block_id);
///
/// // Check if variable needs exit PHI
/// if builder.should_generate_exit_phi("ch", &pinned, &carrier, &exit_preds) {
/// // Generate exit PHI
/// }
/// ```
#[derive(Clone)]
pub struct BodyLocalPhiBuilder {
/// Variable classifier
classifier: LoopVarClassBox,
/// Definition location tracker
inspector: LocalScopeInspectorBox,
}
impl BodyLocalPhiBuilder {
/// Create a new BodyLocalPhiBuilder
///
/// # Arguments
/// * `classifier` - LoopVarClassBox for variable classification
/// * `inspector` - LocalScopeInspectorBox for definition tracking
///
/// # Returns
/// New builder ready to classify variables
pub fn new(classifier: LoopVarClassBox, inspector: LocalScopeInspectorBox) -> Self {
Self {
classifier,
inspector,
}
}
/// Check if a variable needs exit PHI
///
/// # Arguments
/// * `var_name` - Variable name to check
/// * `pinned_vars` - Known loop-crossing parameters
/// * `carrier_vars` - Known loop-modified variables
/// * `exit_preds` - All exit predecessor blocks
///
/// # Returns
/// - `true` if variable needs exit PHI (Pinned/Carrier/BodyLocalExit)
/// - `false` if variable should skip exit PHI (BodyLocalInternal)
///
/// # Classification Logic
/// - Pinned: Always needs exit PHI
/// - Carrier: Always needs exit PHI
/// - BodyLocalExit: Defined in ALL exit preds → needs exit PHI
/// - BodyLocalInternal: Defined in SOME exit preds → NO exit PHI
///
/// # Example
/// ```ignore
/// // skip_whitespace scenario:
/// // ch is defined only in block 5, but exit preds are [block 2, block 5]
/// let needs_phi = builder.should_generate_exit_phi(
/// "ch",
/// &[], // not pinned
/// &[], // not carrier
/// &[BasicBlockId(2), BasicBlockId(5)],
/// );
/// assert!(!needs_phi); // BodyLocalInternal → skip exit PHI!
/// ```
pub fn should_generate_exit_phi(
&self,
var_name: &str,
pinned_vars: &[String],
carrier_vars: &[String],
exit_preds: &[BasicBlockId],
) -> bool {
let class = self.classifier.classify(
var_name,
pinned_vars,
carrier_vars,
&self.inspector,
exit_preds,
);
// BodyLocalInternal → Skip exit PHI
class.needs_exit_phi()
}
/// Filter variables to get only those needing exit PHI
///
/// # Arguments
/// * `all_vars` - All variable names to consider
/// * `pinned_vars` - Known loop-crossing parameters
/// * `carrier_vars` - Known loop-modified variables
/// * `exit_preds` - All exit predecessor blocks
/// * `live_at_exit` - Phase 26-F-4: Variables that are actually used after exit
///
/// # Returns
/// Filtered list of variable names that need exit PHI
///
/// # Phase 26-F-4: OR判定による統合
/// - class.needs_exit_phi() == true → 既存ロジックPinned/Carrier/BodyLocalExit
/// - OR live_at_exit に含まれる → 新規ロジックBodyLocalInternal でも救う)
///
/// # Example
/// ```ignore
/// // Phase 26-F-4: skip_whitespace scenario保守的live全pred定義が条件
/// let all_vars = vec!["s", "idx", "ch", "n"];
/// let pinned = vec!["s", "idx"];
/// let carrier = vec![];
/// let live_at_exit = BTreeSet::from(["s", "idx", "ch", "n"]); // 保守的近似
///
/// let phi_vars = builder.filter_exit_phi_candidates(
/// &all_vars,
/// &pinned,
/// &carrier,
/// &exit_preds,
/// &live_at_exit, // Phase 26-F-4: 追加
/// );
/// // Result (現行ロジック): ["s", "idx", "n"]
/// // - "ch" は BodyLocalInternal かつ exit_preds 全てで定義されていないため除外される
/// ```
pub fn filter_exit_phi_candidates(
&self,
all_vars: &[String],
pinned_vars: &[String],
carrier_vars: &[String],
exit_preds: &[BasicBlockId],
live_at_exit: &std::collections::BTreeSet<String>, // Phase 26-F-4: 追加
) -> Vec<String> {
// 環境変数ガード既定は従来挙動BodyLocalInternal を救済しない)
let enable_live_rescue =
std::env::var("NYASH_EXIT_LIVE_ENABLE").ok().as_deref() == Some("1");
all_vars
.iter()
.filter(|var_name| {
let class = self.classifier.classify(
var_name,
pinned_vars,
carrier_vars,
&self.inspector,
exit_preds,
);
// Phase 26-F-4: OR判定ただし BodyLocalInternal は「全predで定義される」場合に限定
// - Pinned/Carrier/BodyLocalExit → 既存ロジック
// - BodyLocalInternal でも live_at_exit に含まれ、かつ全predで定義されるなら exit PHI 候補
if class.needs_exit_phi() {
return true;
}
if enable_live_rescue {
if matches!(
class,
super::loop_var_classifier::LoopVarClass::BodyLocalInternal
) && live_at_exit.contains(*var_name)
&& self.inspector.is_available_in_all(var_name, exit_preds)
{
return true;
}
}
false
})
.cloned()
.collect()
}
// Phase 26-F-2: この filter_if_merge_candidates() は削除
// 理由: LoopVarClassBoxループ全体スコープ分析と if-merge専用処理が混在
// 代替: if_body_local_merge.rs の IfBodyLocalMergeBox を使用
/// Get mutable reference to inspector
///
/// # Purpose
/// Allow caller to record variable definitions
///
/// # Returns
/// Mutable reference to LocalScopeInspectorBox
///
/// # Example
/// ```ignore
/// builder.inspector_mut().record_definition("ch", BasicBlockId(5));
/// ```
pub fn inspector_mut(&mut self) -> &mut LocalScopeInspectorBox {
&mut self.inspector
}
/// Get immutable reference to inspector
///
/// # Purpose
/// Allow caller to query variable definitions
///
/// # Returns
/// Immutable reference to LocalScopeInspectorBox
pub fn inspector(&self) -> &LocalScopeInspectorBox {
&self.inspector
}
/// Get classification for a variable
///
/// # Arguments
/// * `var_name` - Variable name to classify
/// * `pinned_vars` - Known loop-crossing parameters
/// * `carrier_vars` - Known loop-modified variables
/// * `exit_preds` - All exit predecessor blocks
///
/// # Returns
/// LoopVarClass classification
///
/// # Example
/// ```ignore
/// let class = builder.classify_variable("ch", &pinned, &carrier, &exit_preds);
/// assert_eq!(class, LoopVarClass::BodyLocalInternal);
/// ```
pub fn classify_variable(
&self,
var_name: &str,
pinned_vars: &[String],
carrier_vars: &[String],
exit_preds: &[BasicBlockId],
) -> LoopVarClass {
self.classifier.classify(
var_name,
pinned_vars,
carrier_vars,
&self.inspector,
exit_preds,
)
}
}
// ============================================================================
// Unit Tests
// ============================================================================
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pinned_variable_needs_exit_phi() {
let classifier = LoopVarClassBox::new();
let inspector = LocalScopeInspectorBox::new();
let builder = BodyLocalPhiBuilder::new(classifier, inspector);
// Pinned variable always needs exit PHI
let needs_phi = builder.should_generate_exit_phi(
"s",
&["s".to_string()], // pinned
&[],
&[BasicBlockId(1), BasicBlockId(2)],
);
assert!(needs_phi);
}
#[test]
fn test_carrier_variable_needs_exit_phi() {
let classifier = LoopVarClassBox::new();
let inspector = LocalScopeInspectorBox::new();
let builder = BodyLocalPhiBuilder::new(classifier, inspector);
// Carrier variable always needs exit PHI
let needs_phi = builder.should_generate_exit_phi(
"i",
&[],
&["i".to_string()], // carrier
&[BasicBlockId(1), BasicBlockId(2)],
);
assert!(needs_phi);
}
#[test]
fn test_body_local_exit_needs_phi() {
let classifier = LoopVarClassBox::new();
let mut inspector = LocalScopeInspectorBox::new();
// Variable defined in ALL exit predecessors
inspector.record_definition("x", BasicBlockId(1));
inspector.record_definition("x", BasicBlockId(2));
let builder = BodyLocalPhiBuilder::new(classifier, inspector);
// BodyLocalExit: defined in all exit preds → needs exit PHI
let needs_phi =
builder.should_generate_exit_phi("x", &[], &[], &[BasicBlockId(1), BasicBlockId(2)]);
assert!(needs_phi);
}
#[test]
fn test_body_local_internal_skip_phi() {
let classifier = LoopVarClassBox::new();
let mut inspector = LocalScopeInspectorBox::new();
// Variable defined in SOME exit predecessors (not all)
inspector.record_definition("ch", BasicBlockId(5));
let builder = BodyLocalPhiBuilder::new(classifier, inspector);
// BodyLocalInternal: defined only in block 5, but exit preds are [2, 5]
// → NO exit PHI (Option C fix!)
let needs_phi =
builder.should_generate_exit_phi("ch", &[], &[], &[BasicBlockId(2), BasicBlockId(5)]);
assert!(!needs_phi); // ← Skip exit PHI!
}
#[test]
fn test_filter_exit_phi_candidates() {
let classifier = LoopVarClassBox::new();
let mut inspector = LocalScopeInspectorBox::new();
// s, idx: pinned (defined before loop)
// ch: body-local, only in block 5
// n: body-local, in all exit preds (block 2, 5)
inspector.record_definition("n", BasicBlockId(2));
inspector.record_definition("n", BasicBlockId(5));
inspector.record_definition("ch", BasicBlockId(5));
let builder = BodyLocalPhiBuilder::new(classifier, inspector);
let all_vars = vec![
"s".to_string(),
"idx".to_string(),
"ch".to_string(),
"n".to_string(),
];
let pinned = vec!["s".to_string(), "idx".to_string()];
let carrier: Vec<String> = vec![];
// Phase 26-F-4: empty live_at_exitlive情報なし
let live_at_exit = std::collections::BTreeSet::new();
let phi_vars = builder.filter_exit_phi_candidates(
&all_vars,
&pinned,
&carrier,
&[BasicBlockId(2), BasicBlockId(5)],
&live_at_exit, // Phase 26-F-4
);
// Expected: s, idx, n (ch is BodyLocalInternal → filtered out)
assert_eq!(phi_vars.len(), 3);
assert!(phi_vars.contains(&"s".to_string()));
assert!(phi_vars.contains(&"idx".to_string()));
assert!(phi_vars.contains(&"n".to_string()));
assert!(!phi_vars.contains(&"ch".to_string())); // ← Filtered out!
}
#[test]
fn test_skip_whitespace_scenario() {
// Real-world scenario from skip_whitespace function
// Parameters: s, idx (pinned)
// Loop carrier: idx
// Body-local: ch (only defined in some exit paths)
let classifier = LoopVarClassBox::new();
let mut inspector = LocalScopeInspectorBox::new();
// ch is defined only in block 5 (after condition check)
inspector.record_definition("ch", BasicBlockId(5));
let builder = BodyLocalPhiBuilder::new(classifier, inspector);
let all_vars = vec!["s".to_string(), "idx".to_string(), "ch".to_string()];
let pinned = vec!["s".to_string(), "idx".to_string()];
let carrier: Vec<String> = vec![];
// Exit preds: [block 2 (early break), block 5 (after ch definition)]
let exit_preds = vec![BasicBlockId(2), BasicBlockId(5)];
// Phase 26-F-4: empty live_at_exitlive情報なし
let live_at_exit = std::collections::BTreeSet::new();
let phi_vars = builder.filter_exit_phi_candidates(
&all_vars,
&pinned,
&carrier,
&exit_preds,
&live_at_exit,
);
// Expected: s, idx (ch filtered out!)
assert_eq!(phi_vars.len(), 2);
assert!(phi_vars.contains(&"s".to_string()));
assert!(phi_vars.contains(&"idx".to_string()));
assert!(!phi_vars.contains(&"ch".to_string())); // ← Option C fix!
}
#[test]
fn test_classify_variable() {
let classifier = LoopVarClassBox::new();
let mut inspector = LocalScopeInspectorBox::new();
inspector.record_definition("ch", BasicBlockId(5));
let builder = BodyLocalPhiBuilder::new(classifier, inspector);
let class = builder.classify_variable("ch", &[], &[], &[BasicBlockId(2), BasicBlockId(5)]);
assert_eq!(class, LoopVarClass::BodyLocalInternal);
assert!(!class.needs_exit_phi());
}
#[test]
fn test_inspector_mut_access() {
let classifier = LoopVarClassBox::new();
let inspector = LocalScopeInspectorBox::new();
let mut builder = BodyLocalPhiBuilder::new(classifier, inspector);
// Test mutable access
builder
.inspector_mut()
.record_definition("test", BasicBlockId(10));
// Verify recording worked
let class = builder.classify_variable("test", &[], &[], &[BasicBlockId(10)]);
assert_eq!(class, LoopVarClass::BodyLocalExit);
}
#[test]
fn test_pin_temporary_variables_filtered() {
// __pin$ temporary variables should always be BodyLocalInternal
// and thus filtered out from exit PHI candidates
let classifier = LoopVarClassBox::new();
let mut inspector = LocalScopeInspectorBox::new();
// Record __pin$ temporary
inspector.record_definition("__pin$42$@binop_lhs", BasicBlockId(5));
let builder = BodyLocalPhiBuilder::new(classifier, inspector);
// Should be classified as BodyLocalInternal
let class = builder.classify_variable("__pin$42$@binop_lhs", &[], &[], &[BasicBlockId(5)]);
assert_eq!(class, LoopVarClass::BodyLocalInternal);
assert!(!class.needs_exit_phi());
// Should be filtered out from exit PHI candidates
let all_vars = vec!["__pin$42$@binop_lhs".to_string(), "s".to_string()];
let pinned = vec!["s".to_string()];
// Phase 26-F-4: empty live_at_exitlive情報なし
let live_at_exit = std::collections::BTreeSet::new();
let phi_vars = builder.filter_exit_phi_candidates(
&all_vars,
&pinned,
&[],
&[BasicBlockId(5)],
&live_at_exit,
);
// Only s should remain
assert_eq!(phi_vars.len(), 1);
assert!(phi_vars.contains(&"s".to_string()));
assert!(!phi_vars.contains(&"__pin$42$@binop_lhs".to_string()));
}
#[test]
fn test_empty_variables() {
let classifier = LoopVarClassBox::new();
let inspector = LocalScopeInspectorBox::new();
let builder = BodyLocalPhiBuilder::new(classifier, inspector);
// Phase 26-F-4: empty live_at_exitlive情報なし
let live_at_exit = std::collections::BTreeSet::new();
let phi_vars = builder.filter_exit_phi_candidates(&[], &[], &[], &[], &live_at_exit);
assert_eq!(phi_vars.len(), 0);
}
#[test]
fn test_all_pinned_variables() {
let classifier = LoopVarClassBox::new();
let inspector = LocalScopeInspectorBox::new();
let builder = BodyLocalPhiBuilder::new(classifier, inspector);
let all_vars = vec!["a".to_string(), "b".to_string(), "c".to_string()];
let pinned = vec!["a".to_string(), "b".to_string(), "c".to_string()];
// Phase 26-F-4: empty live_at_exitlive情報なし
let live_at_exit = std::collections::BTreeSet::new();
let phi_vars = builder.filter_exit_phi_candidates(
&all_vars,
&pinned,
&[],
&[BasicBlockId(1), BasicBlockId(2)],
&live_at_exit,
);
// All should need exit PHI
assert_eq!(phi_vars.len(), 3);
}
#[test]
fn test_all_carrier_variables() {
let classifier = LoopVarClassBox::new();
let inspector = LocalScopeInspectorBox::new();
let builder = BodyLocalPhiBuilder::new(classifier, inspector);
let all_vars = vec!["i".to_string(), "j".to_string()];
let carrier = vec!["i".to_string(), "j".to_string()];
// Phase 26-F-4: empty live_at_exitlive情報なし
let live_at_exit = std::collections::BTreeSet::new();
let phi_vars = builder.filter_exit_phi_candidates(
&all_vars,
&[],
&carrier,
&[BasicBlockId(1), BasicBlockId(2)],
&live_at_exit,
);
// All should need exit PHI
assert_eq!(phi_vars.len(), 2);
}
}

View File

@ -1,960 +0,0 @@
//! Exit PHI Builder - Exit時のPHI生成専門Box
//!
//! Phase 26-D: ExitPhiBuilder実装
//! - Exit PHI生成の完全分離
//! - Phantom block除外ロジック
//! - BodyLocal変数フィルタリングBodyLocalPhiBuilder活用
//!
//! Phase 26-F-4: LoopExitLivenessBox統合
//! - live_at_exit 変数の計算LoopExitLivenessBox
//! - BodyLocalInternal変数の救済OR判定
//! - 4箱構成による責務分離完成
//!
//! Box-First理論: Exit PHI生成という最も複雑な責任を明確に分離し、テスト可能な箱として提供
use crate::mir::{BasicBlockId, ValueId};
use std::collections::{BTreeMap, BTreeSet};
use super::body_local_phi_builder::BodyLocalPhiBuilder;
use super::loop_exit_liveness::{ExitLivenessProvider, LoopExitLivenessBox}; // Phase 26-F-4
use super::loop_snapshot_merge::LoopSnapshotMergeBox;
use super::phi_input_collector::PhiInputCollector;
use super::phi_invariants::PhiInvariantsBox;
/// Exit PHI生成専門Box
///
/// # Purpose
/// - Exit時のPHI node生成
/// - Exit predecessors検証
/// - Phantom block除外
/// - Body-local変数の適切な処理
///
/// # Responsibility Separation
/// - このBox: Exit PHI生成・Phantom除外
/// - LoopSnapshotMergeBox: PHI入力生成static function
/// - BodyLocalPhiBuilder: Body-local変数判定
/// - PhiInputCollector: PHI input最適化
///
/// # Usage
/// ```ignore
/// let body_local_builder = BodyLocalPhiBuilder::new(classifier, inspector);
/// let mut exit_builder = ExitPhiBuilder::new(body_local_builder);
///
/// exit_builder.build_exit_phis(
/// ops,
/// exit_id,
/// header_id,
/// branch_source_block,
/// header_vals,
/// exit_snapshots,
/// pinned_vars,
/// carrier_vars,
/// )?;
/// ```
pub struct ExitPhiBuilder {
/// Body-local variable builder
body_local_builder: BodyLocalPhiBuilder,
/// Exit liveness provider (legacy by default, swappable for MIR scan)
liveness_provider: Box<dyn ExitLivenessProvider>,
}
impl ExitPhiBuilder {
/// Create a new ExitPhiBuilder
///
/// # Arguments
/// * `body_local_builder` - BodyLocalPhiBuilder for body-local detection
///
/// # Returns
/// New ExitPhiBuilder instance
///
/// # Example
/// ```ignore
/// let classifier = LoopVarClassBox::new();
/// let inspector = LocalScopeInspectorBox::new();
/// let body_builder = BodyLocalPhiBuilder::new(classifier, inspector);
/// let exit_builder = ExitPhiBuilder::new(body_builder);
/// ```
pub fn new(body_local_builder: BodyLocalPhiBuilder) -> Self {
// 環境変数で簡易 MirScan 版を opt-in できるようにする
let use_scan = std::env::var("NYASH_EXIT_LIVE_ENABLE").ok().as_deref() == Some("1");
if use_scan {
Self::with_liveness(
body_local_builder,
Box::new(crate::mir::phi_core::loop_exit_liveness::MirScanExitLiveness),
)
} else {
Self::with_liveness(body_local_builder, Box::new(LoopExitLivenessBox::new()))
}
}
/// Create ExitPhiBuilder with a custom liveness provider (for tests / future MIR scan)
pub fn with_liveness(
body_local_builder: BodyLocalPhiBuilder,
liveness_provider: Box<dyn ExitLivenessProvider>,
) -> Self {
Self {
body_local_builder,
liveness_provider,
}
}
/// [LoopForm] Build Exit PHIs
///
/// # Arguments
/// * `ops` - LoopFormOps trait implementation
/// * `exit_id` - Exit block ID
/// * `header_id` - Loop header block ID
/// * `branch_source_block` - Branch source block ID (early exit path, Case A)
/// * `header_vals` - Header variable values (parameter values)
/// * `exit_snapshots` - Exit predecessor snapshots (from break statements)
/// * `pinned_vars` - Pinned variable names (loop-invariant parameters)
/// * `carrier_vars` - Carrier variable names (loop-modified variables)
/// * `mir_func` - Underlying MIR function (for MirQuery)
///
/// # Returns
/// Result: Ok(()) on success, Err(msg) on failure
///
/// # [LoopForm] Process
/// 1. Get exit predecessors (CFG validation) - determines Case A/B
/// 2. Filter phantom blocks (Step 5-5-H)
/// 3. Record definitions in inspector
/// 4. [LoopForm] Generate PHI inputs using LoopSnapshotMergeBox::merge_exit_with_classification
/// - Case A: header+break paths included
/// - Case B: break paths only (header not a predecessor)
/// 5. For each variable, use PhiInputCollector to optimize and generate PHI nodes
///
/// # Example
/// ```ignore
/// exit_builder.build_exit_phis(
/// ops,
/// BasicBlockId(10),
/// BasicBlockId(5),
/// BasicBlockId(7),
/// &header_vals,
/// &exit_snapshots,
/// &["s", "idx"],
/// &["ch"],
/// )?;
/// ```
pub fn build_exit_phis<O: LoopFormOps>(
&mut self,
ops: &mut O,
exit_id: BasicBlockId,
header_id: BasicBlockId,
branch_source_block: BasicBlockId,
header_vals: &BTreeMap<String, ValueId>,
exit_snapshots: &[(BasicBlockId, BTreeMap<String, ValueId>)],
pinned_vars: &[String],
carrier_vars: &[String],
) -> Result<(), String> {
ops.set_current_block(exit_id)?;
// [LoopForm] 1. Exit predecessorsを取得CFG検証- Case A/B判定のキー
// BTreeSet で決定性を確保
let exit_preds_set = ops.get_block_predecessors(exit_id);
let mut exit_preds: Vec<BasicBlockId> = exit_preds_set.iter().copied().collect();
exit_preds.sort_by_key(|bb| bb.0);
// [LoopForm] 2. Phantom blockをフィルタリングStep 5-5-H
let filtered_snapshots = self.filter_phantom_blocks(exit_snapshots, &exit_preds_set, ops);
// [LoopForm] 3. Inspectorに定義を記録変数分類の基盤
let inspector = self.body_local_builder.inspector_mut();
for pinned_name in pinned_vars {
inspector.record_definition(pinned_name, header_id);
}
for carrier_name in carrier_vars {
inspector.record_definition(carrier_name, header_id);
}
for (block_id, snapshot) in &filtered_snapshots {
inspector.record_snapshot(*block_id, snapshot);
}
if exit_preds_set.contains(&branch_source_block) {
inspector.record_snapshot(branch_source_block, header_vals);
}
// [LoopForm] 4. exit φ 対象変数を決定BodyLocalInternal を除外)
let mut required_vars: BTreeSet<String> = BTreeSet::new();
required_vars.extend(header_vals.keys().cloned());
for (_, snap) in &filtered_snapshots {
required_vars.extend(snap.keys().cloned());
}
required_vars.extend(pinned_vars.iter().cloned());
required_vars.extend(carrier_vars.iter().cloned());
// Phase 26-F/G: ExitLivenessProvider で live_at_exit を計算MirQuery 経由)
let query = crate::mir::MirQueryBox::new(ops.mir_function());
let live_at_exit = self.liveness_provider.compute_live_at_exit(
&query,
exit_id,
header_vals,
exit_snapshots,
);
let phi_vars = self.body_local_builder.filter_exit_phi_candidates(
&required_vars.iter().cloned().collect::<Vec<_>>(),
pinned_vars,
carrier_vars,
&exit_preds,
&live_at_exit, // Phase 26-F-4: live_at_exit 追加
);
// Fail-Fast invariant共通箱経由:
// - exit φ 対象に選ばれた変数は、すべての exit predecessor で「定義済み」でなければならない。
// Pinned/Carrier/BodyLocalExit のみ / BodyLocalInternal は候補から除外済み)
PhiInvariantsBox::ensure_exit_phi_availability(
&phi_vars,
&exit_preds,
exit_id,
header_id,
self.body_local_builder.inspector(),
)?;
// Dev trace: which vars became exit-phi candidates
if std::env::var("NYASH_IF_HOLE_TRACE").ok().as_deref() == Some("1") {
eprintln!(
"[exit-phi] exit={:?} header={:?} phi_vars={:?} live_at_exit={:?}",
exit_id, header_id, phi_vars, live_at_exit
);
}
let include_header_input = exit_preds_set.contains(&header_id) || exit_preds.is_empty();
// 5. PHI生成PhiInputCollectorで最適化適用
for var_name in phi_vars {
let mut inputs_map: BTreeMap<BasicBlockId, ValueId> = BTreeMap::new();
if include_header_input {
if let Some(&val) = header_vals.get(&var_name) {
inputs_map.insert(header_id, val);
}
}
for (bb, snap) in &filtered_snapshots {
if let Some(&val) = snap.get(&var_name) {
inputs_map.insert(*bb, val);
}
}
// Dev trace: exit φ inputs
if std::env::var("NYASH_IF_HOLE_TRACE").ok().as_deref() == Some("1") {
eprintln!(
"[exit-phi] var='{}' preds={:?} header_included={} inputs={:?}",
var_name, exit_preds, include_header_input, inputs_map
);
}
let mut inputs: Vec<(BasicBlockId, ValueId)> = inputs_map.into_iter().collect();
LoopSnapshotMergeBox::sanitize_inputs(&mut inputs);
// incoming 0 → header で直接バインド(最低限定義を保証)
if inputs.is_empty() {
if let Some(&val) = header_vals.get(&var_name) {
ops.update_var(var_name, val);
}
continue;
}
let mut collector = PhiInputCollector::new();
collector.add_snapshot(&inputs);
// Sanitize + Optimize
collector.sanitize();
if let Some(same_val) = collector.optimize_same_value() {
// 同値PHI → 直接バインド
ops.update_var(var_name, same_val);
} else {
// 異なる値 → PHI生成
let final_inputs = collector.finalize();
let phi_id = ops.new_value();
ops.emit_phi(phi_id, final_inputs)?;
ops.update_var(var_name, phi_id);
}
}
Ok(())
}
/// Filter phantom blocks
///
/// # Arguments
/// * `exit_snapshots` - Raw exit predecessor snapshots
/// * `exit_preds` - Actual CFG exit predecessors
/// * `ops` - LoopFormOps for block existence check
///
/// # Returns
/// Filtered snapshots (phantom blocks removed)
///
/// # Phantom Block Definition
/// - Non-existent block (removed during optimization)
/// - Not a CFG predecessor (no edge to exit block)
///
/// # Example
/// ```ignore
/// let filtered = exit_builder.filter_phantom_blocks(
/// &snapshots,
/// &exit_preds_set,
/// ops,
/// );
/// // Removed phantom blocks
/// ```
fn filter_phantom_blocks<O: LoopFormOps>(
&self,
exit_snapshots: &[(BasicBlockId, BTreeMap<String, ValueId>)],
exit_preds: &BTreeSet<BasicBlockId>,
ops: &O,
) -> Vec<(BasicBlockId, BTreeMap<String, ValueId>)> {
let mut filtered = Vec::new();
for (block_id, snapshot) in exit_snapshots {
if !ops.block_exists(*block_id) {
continue; // Non-existent block
}
if !exit_preds.contains(block_id) {
continue; // Not a CFG predecessor
}
filtered.push((*block_id, snapshot.clone()));
}
filtered
}
/// Get mutable reference to body local builder (for testing)
#[cfg(test)]
pub fn body_local_builder_mut(&mut self) -> &mut BodyLocalPhiBuilder {
&mut self.body_local_builder
}
}
/// LoopFormOps trait - Operations needed by ExitPhiBuilder
///
/// # Purpose
/// - Abstract MIR builder operations for testability
/// - Enable mock implementation for unit tests
pub trait LoopFormOps {
/// Set current block
fn set_current_block(&mut self, block_id: BasicBlockId) -> Result<(), String>;
/// Get block predecessors
fn get_block_predecessors(&self, block_id: BasicBlockId) -> BTreeSet<BasicBlockId>;
/// Check if block exists
fn block_exists(&self, block_id: BasicBlockId) -> bool;
/// Create new value ID
fn new_value(&mut self) -> ValueId;
/// Emit PHI instruction
fn emit_phi(
&mut self,
phi_id: ValueId,
inputs: Vec<(BasicBlockId, ValueId)>,
) -> Result<(), String>;
/// Update variable binding
fn update_var(&mut self, var_name: String, value_id: ValueId);
/// Access underlying MirFunction (for MirQuery)
fn mir_function(&self) -> &crate::mir::MirFunction;
}
// Bridge: allow any LoopFormOps (loopform_builder版) to be used here
impl<T: crate::mir::phi_core::loopform_builder::LoopFormOps> LoopFormOps for T {
fn set_current_block(&mut self, block_id: BasicBlockId) -> Result<(), String> {
crate::mir::phi_core::loopform_builder::LoopFormOps::set_current_block(self, block_id)
}
fn get_block_predecessors(&self, block_id: BasicBlockId) -> BTreeSet<BasicBlockId> {
crate::mir::phi_core::loopform_builder::LoopFormOps::get_block_predecessors(self, block_id)
.into_iter()
.collect()
}
fn block_exists(&self, block_id: BasicBlockId) -> bool {
crate::mir::phi_core::loopform_builder::LoopFormOps::block_exists(self, block_id)
}
fn new_value(&mut self) -> ValueId {
crate::mir::phi_core::loopform_builder::LoopFormOps::new_value(self)
}
fn emit_phi(
&mut self,
phi_id: ValueId,
inputs: Vec<(BasicBlockId, ValueId)>,
) -> Result<(), String> {
crate::mir::phi_core::loopform_builder::LoopFormOps::emit_phi(self, phi_id, inputs)
}
fn update_var(&mut self, var_name: String, value_id: ValueId) {
crate::mir::phi_core::loopform_builder::LoopFormOps::update_var(self, var_name, value_id)
}
fn mir_function(&self) -> &crate::mir::MirFunction {
crate::mir::phi_core::loopform_builder::LoopFormOps::mir_function(self)
}
}
// ============================================================================
// Phase 27.6-2: JoinIR Exit φ バイパス用ヘルパー関数
// ============================================================================
/// JoinIR Exit φ 縮退実験トグル
///
/// - NYASH_JOINIR_EXPERIMENT=1
/// - NYASH_JOINIR_EXIT_EXP=1
///
/// の両方が立っているときだけ true。
pub(crate) fn joinir_exit_bypass_enabled() -> bool {
crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_EXPERIMENT")
&& crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_EXIT_EXP")
}
/// Exit φ バイパス対象の関数かどうか
///
/// 当面は minimal/trim の 2 本だけ:
/// - Main.skip/1
/// - FuncScannerBox.trim/1
pub(crate) fn is_joinir_exit_bypass_target(func_name: &str) -> bool {
matches!(func_name, "Main.skip/1" | "FuncScannerBox.trim/1")
}
// ============================================================================
// Unit Tests
// ============================================================================
#[cfg(test)]
mod tests {
use super::*;
use crate::mir::phi_core::local_scope_inspector::LocalScopeInspectorBox;
use crate::mir::phi_core::loop_var_classifier::LoopVarClassBox;
/// Mock LoopFormOps for testing
struct MockOps {
current_block: Option<BasicBlockId>,
blocks: BTreeSet<BasicBlockId>,
predecessors: BTreeMap<BasicBlockId, BTreeSet<BasicBlockId>>,
next_value_id: u32,
emitted_phis: Vec<(ValueId, Vec<(BasicBlockId, ValueId)>)>,
var_bindings: BTreeMap<String, ValueId>,
func: crate::mir::MirFunction,
}
impl MockOps {
fn new() -> Self {
// Minimal function for testing (1 block)
let sig = crate::mir::function::FunctionSignature {
name: "mock".to_string(),
params: vec![],
return_type: crate::mir::MirType::Void,
effects: crate::mir::effect::EffectMask::PURE,
};
let func = crate::mir::MirFunction::new(sig, BasicBlockId(0));
Self {
current_block: None,
blocks: BTreeSet::new(),
predecessors: BTreeMap::new(),
next_value_id: 100,
emitted_phis: Vec::new(),
var_bindings: BTreeMap::new(),
func,
}
}
fn add_block(&mut self, block_id: BasicBlockId) {
self.blocks.insert(block_id);
}
fn add_predecessor(&mut self, block_id: BasicBlockId, pred: BasicBlockId) {
self.predecessors
.entry(block_id)
.or_insert_with(BTreeSet::new)
.insert(pred);
}
}
impl LoopFormOps for MockOps {
fn set_current_block(&mut self, block_id: BasicBlockId) -> Result<(), String> {
self.current_block = Some(block_id);
Ok(())
}
fn get_block_predecessors(&self, block_id: BasicBlockId) -> BTreeSet<BasicBlockId> {
self.predecessors
.get(&block_id)
.cloned()
.unwrap_or_default()
}
fn block_exists(&self, block_id: BasicBlockId) -> bool {
self.blocks.contains(&block_id)
}
fn new_value(&mut self) -> ValueId {
let id = ValueId(self.next_value_id);
self.next_value_id += 1;
id
}
fn emit_phi(
&mut self,
phi_id: ValueId,
inputs: Vec<(BasicBlockId, ValueId)>,
) -> Result<(), String> {
self.emitted_phis.push((phi_id, inputs));
Ok(())
}
fn update_var(&mut self, var_name: String, value_id: ValueId) {
self.var_bindings.insert(var_name, value_id);
}
fn mir_function(&self) -> &crate::mir::MirFunction {
&self.func
}
}
#[test]
fn test_new() {
let classifier = LoopVarClassBox::new();
let inspector = LocalScopeInspectorBox::new();
let body_builder = BodyLocalPhiBuilder::new(classifier, inspector);
let _exit_builder = ExitPhiBuilder::new(body_builder);
assert!(true, "ExitPhiBuilder created successfully");
}
#[test]
fn test_filter_phantom_blocks_all_valid() {
let classifier = LoopVarClassBox::new();
let inspector = LocalScopeInspectorBox::new();
let body_builder = BodyLocalPhiBuilder::new(classifier, inspector);
let exit_builder = ExitPhiBuilder::new(body_builder);
let mut ops = MockOps::new();
ops.add_block(BasicBlockId(1));
ops.add_block(BasicBlockId(2));
let mut exit_preds = BTreeSet::new();
exit_preds.insert(BasicBlockId(1));
exit_preds.insert(BasicBlockId(2));
let mut snapshot1 = BTreeMap::new();
snapshot1.insert("x".to_string(), ValueId(10));
let mut snapshot2 = BTreeMap::new();
snapshot2.insert("x".to_string(), ValueId(20));
let snapshots = vec![
(BasicBlockId(1), snapshot1.clone()),
(BasicBlockId(2), snapshot2.clone()),
];
let filtered = exit_builder.filter_phantom_blocks(&snapshots, &exit_preds, &ops);
assert_eq!(filtered.len(), 2);
assert_eq!(filtered[0].0, BasicBlockId(1));
assert_eq!(filtered[1].0, BasicBlockId(2));
}
#[test]
fn test_filter_phantom_blocks_non_existent() {
let classifier = LoopVarClassBox::new();
let inspector = LocalScopeInspectorBox::new();
let body_builder = BodyLocalPhiBuilder::new(classifier, inspector);
let exit_builder = ExitPhiBuilder::new(body_builder);
let mut ops = MockOps::new();
ops.add_block(BasicBlockId(1));
// Block 2 does not exist
let mut exit_preds = BTreeSet::new();
exit_preds.insert(BasicBlockId(1));
exit_preds.insert(BasicBlockId(2));
let mut snapshot1 = BTreeMap::new();
snapshot1.insert("x".to_string(), ValueId(10));
let mut snapshot2 = BTreeMap::new();
snapshot2.insert("x".to_string(), ValueId(20));
let snapshots = vec![
(BasicBlockId(1), snapshot1.clone()),
(BasicBlockId(2), snapshot2.clone()), // Phantom block
];
let filtered = exit_builder.filter_phantom_blocks(&snapshots, &exit_preds, &ops);
// Only block 1 remains
assert_eq!(filtered.len(), 1);
assert_eq!(filtered[0].0, BasicBlockId(1));
}
#[test]
fn test_filter_phantom_blocks_not_predecessor() {
let classifier = LoopVarClassBox::new();
let inspector = LocalScopeInspectorBox::new();
let body_builder = BodyLocalPhiBuilder::new(classifier, inspector);
let exit_builder = ExitPhiBuilder::new(body_builder);
let mut ops = MockOps::new();
ops.add_block(BasicBlockId(1));
ops.add_block(BasicBlockId(2));
let mut exit_preds = BTreeSet::new();
exit_preds.insert(BasicBlockId(1));
// Block 2 is not a predecessor
let mut snapshot1 = BTreeMap::new();
snapshot1.insert("x".to_string(), ValueId(10));
let mut snapshot2 = BTreeMap::new();
snapshot2.insert("x".to_string(), ValueId(20));
let snapshots = vec![
(BasicBlockId(1), snapshot1.clone()),
(BasicBlockId(2), snapshot2.clone()), // Not a predecessor
];
let filtered = exit_builder.filter_phantom_blocks(&snapshots, &exit_preds, &ops);
// Only block 1 remains
assert_eq!(filtered.len(), 1);
assert_eq!(filtered[0].0, BasicBlockId(1));
}
#[test]
fn test_filter_phantom_blocks_empty() {
let classifier = LoopVarClassBox::new();
let inspector = LocalScopeInspectorBox::new();
let body_builder = BodyLocalPhiBuilder::new(classifier, inspector);
let exit_builder = ExitPhiBuilder::new(body_builder);
let ops = MockOps::new();
let exit_preds = BTreeSet::new();
let snapshots = vec![];
let filtered = exit_builder.filter_phantom_blocks(&snapshots, &exit_preds, &ops);
assert_eq!(filtered.len(), 0);
}
#[test]
fn test_build_exit_phis_simple_pinned() {
let classifier = LoopVarClassBox::new();
let inspector = LocalScopeInspectorBox::new();
let body_builder = BodyLocalPhiBuilder::new(classifier, inspector);
let mut exit_builder = ExitPhiBuilder::new(body_builder);
let mut ops = MockOps::new();
// Setup blocks
let header_id = BasicBlockId(5);
let exit_id = BasicBlockId(10);
let branch_source = BasicBlockId(7);
ops.add_block(header_id);
ops.add_block(exit_id);
ops.add_block(branch_source);
ops.add_predecessor(exit_id, branch_source);
// Header vals (pinned variable)
let mut header_vals = BTreeMap::new();
header_vals.insert("s".to_string(), ValueId(1));
let exit_snapshots = vec![];
let pinned_vars = vec!["s".to_string()];
let carrier_vars = vec![];
let _func = ops.func.clone();
let result = exit_builder.build_exit_phis(
&mut ops,
exit_id,
header_id,
branch_source,
&header_vals,
&exit_snapshots,
&pinned_vars,
&carrier_vars,
);
assert!(result.is_ok());
// Check current block is set
assert_eq!(ops.current_block, Some(exit_id));
// Check variable binding
assert!(ops.var_bindings.contains_key("s"));
}
#[test]
fn test_build_exit_phis_with_carrier() {
let classifier = LoopVarClassBox::new();
let inspector = LocalScopeInspectorBox::new();
let body_builder = BodyLocalPhiBuilder::new(classifier, inspector);
let mut exit_builder = ExitPhiBuilder::new(body_builder);
let mut ops = MockOps::new();
// Setup blocks
let header_id = BasicBlockId(5);
let exit_id = BasicBlockId(10);
let branch_source = BasicBlockId(7);
let exit_pred1 = BasicBlockId(8);
ops.add_block(header_id);
ops.add_block(exit_id);
ops.add_block(branch_source);
ops.add_block(exit_pred1);
ops.add_predecessor(exit_id, branch_source);
ops.add_predecessor(exit_id, exit_pred1);
// Header vals
let mut header_vals = BTreeMap::new();
header_vals.insert("s".to_string(), ValueId(1));
header_vals.insert("idx".to_string(), ValueId(2));
// Exit snapshot (idx modified)
let mut snapshot1 = BTreeMap::new();
snapshot1.insert("s".to_string(), ValueId(1));
snapshot1.insert("idx".to_string(), ValueId(10)); // Modified
let exit_snapshots = vec![(exit_pred1, snapshot1)];
let pinned_vars = vec!["s".to_string()];
let carrier_vars = vec!["idx".to_string()];
let _func = ops.func.clone();
let result = exit_builder.build_exit_phis(
&mut ops,
exit_id,
header_id,
branch_source,
&header_vals,
&exit_snapshots,
&pinned_vars,
&carrier_vars,
);
assert!(result.is_ok());
// Check variables are bound
assert!(ops.var_bindings.contains_key("s"));
assert!(ops.var_bindings.contains_key("idx"));
}
#[test]
fn test_build_exit_phis_skip_whitespace_scenario() {
// Realistic skip_whitespace scenario
let classifier = LoopVarClassBox::new();
let inspector = LocalScopeInspectorBox::new();
let body_builder = BodyLocalPhiBuilder::new(classifier, inspector);
let mut exit_builder = ExitPhiBuilder::new(body_builder);
let mut ops = MockOps::new();
// Setup blocks
let header_id = BasicBlockId(5);
let exit_id = BasicBlockId(10);
let branch_source = BasicBlockId(7); // Early break
let exit_pred1 = BasicBlockId(8); // After loop body
ops.add_block(header_id);
ops.add_block(exit_id);
ops.add_block(branch_source);
ops.add_block(exit_pred1);
ops.add_predecessor(exit_id, branch_source);
ops.add_predecessor(exit_id, exit_pred1);
// Header vals (parameters)
let mut header_vals = BTreeMap::new();
header_vals.insert("s".to_string(), ValueId(1));
header_vals.insert("idx".to_string(), ValueId(2));
// Exit pred 1: idx modified, ch defined
let mut snapshot1 = BTreeMap::new();
snapshot1.insert("s".to_string(), ValueId(1));
snapshot1.insert("idx".to_string(), ValueId(10)); // Modified
snapshot1.insert("ch".to_string(), ValueId(15)); // Body-local
let exit_snapshots = vec![(exit_pred1, snapshot1)];
let pinned_vars = vec!["s".to_string()];
let carrier_vars = vec!["idx".to_string()];
let _func = ops.func.clone();
let result = exit_builder.build_exit_phis(
&mut ops,
exit_id,
header_id,
branch_source,
&header_vals,
&exit_snapshots,
&pinned_vars,
&carrier_vars,
);
assert!(result.is_ok());
// Check variables are bound
assert!(ops.var_bindings.contains_key("s"));
assert!(ops.var_bindings.contains_key("idx"));
}
#[test]
fn test_build_exit_phis_phantom_block_filtered() {
let classifier = LoopVarClassBox::new();
let inspector = LocalScopeInspectorBox::new();
let body_builder = BodyLocalPhiBuilder::new(classifier, inspector);
let mut exit_builder = ExitPhiBuilder::new(body_builder);
let mut ops = MockOps::new();
// Setup blocks
let header_id = BasicBlockId(5);
let exit_id = BasicBlockId(10);
let branch_source = BasicBlockId(7);
let phantom_block = BasicBlockId(99); // Does not exist
ops.add_block(header_id);
ops.add_block(exit_id);
ops.add_block(branch_source);
// phantom_block NOT added
ops.add_predecessor(exit_id, branch_source);
// Header vals
let mut header_vals = BTreeMap::new();
header_vals.insert("x".to_string(), ValueId(1));
// Phantom snapshot (should be filtered)
let mut phantom_snapshot = BTreeMap::new();
phantom_snapshot.insert("x".to_string(), ValueId(999));
let exit_snapshots = vec![(phantom_block, phantom_snapshot)];
let pinned_vars = vec!["x".to_string()];
let carrier_vars = vec![];
let _func = ops.func.clone();
let result = exit_builder.build_exit_phis(
&mut ops,
exit_id,
header_id,
branch_source,
&header_vals,
&exit_snapshots,
&pinned_vars,
&carrier_vars,
);
assert!(result.is_ok());
// Phantom block should be filtered, so x should be bound
assert!(ops.var_bindings.contains_key("x"));
}
#[test]
fn test_build_exit_phis_empty_snapshots() {
let classifier = LoopVarClassBox::new();
let inspector = LocalScopeInspectorBox::new();
let body_builder = BodyLocalPhiBuilder::new(classifier, inspector);
let mut exit_builder = ExitPhiBuilder::new(body_builder);
let mut ops = MockOps::new();
// Setup blocks
let header_id = BasicBlockId(5);
let exit_id = BasicBlockId(10);
let branch_source = BasicBlockId(7);
ops.add_block(header_id);
ops.add_block(exit_id);
ops.add_block(branch_source);
ops.add_predecessor(exit_id, branch_source);
// Header vals
let mut header_vals = BTreeMap::new();
header_vals.insert("x".to_string(), ValueId(1));
let exit_snapshots = vec![];
let pinned_vars = vec!["x".to_string()];
let carrier_vars = vec![];
let _func = ops.func.clone();
let result = exit_builder.build_exit_phis(
&mut ops,
exit_id,
header_id,
branch_source,
&header_vals,
&exit_snapshots,
&pinned_vars,
&carrier_vars,
);
assert!(result.is_ok());
// No snapshots, x should be bound directly
assert!(ops.var_bindings.contains_key("x"));
}
#[test]
fn test_build_exit_phis_no_predecessors() {
let classifier = LoopVarClassBox::new();
let inspector = LocalScopeInspectorBox::new();
let body_builder = BodyLocalPhiBuilder::new(classifier, inspector);
let mut exit_builder = ExitPhiBuilder::new(body_builder);
let mut ops = MockOps::new();
// Setup blocks
let header_id = BasicBlockId(5);
let exit_id = BasicBlockId(10);
let branch_source = BasicBlockId(7);
ops.add_block(header_id);
ops.add_block(exit_id);
ops.add_block(branch_source);
// No predecessors added
// Header vals
let mut header_vals = BTreeMap::new();
header_vals.insert("x".to_string(), ValueId(1));
let exit_snapshots = vec![];
let pinned_vars = vec!["x".to_string()];
let carrier_vars = vec![];
let _func = ops.func.clone();
let result = exit_builder.build_exit_phis(
&mut ops,
exit_id,
header_id,
branch_source,
&header_vals,
&exit_snapshots,
&pinned_vars,
&carrier_vars,
);
assert!(result.is_ok());
// No predecessors, x should still be bound
assert!(ops.var_bindings.contains_key("x"));
}
#[test]
fn test_accessors() {
let classifier = LoopVarClassBox::new();
let inspector = LocalScopeInspectorBox::new();
let body_builder = BodyLocalPhiBuilder::new(classifier, inspector);
let mut exit_builder = ExitPhiBuilder::new(body_builder);
// Test accessors
let _builder_ref = exit_builder.body_local_builder_mut();
assert!(true, "Accessors work correctly");
}
}

View File

@ -1,627 +0,0 @@
//! Header PHI Builder - Header PHI生成専門Box
//!
//! Phase 26-C-2: HeaderPhiBuilder実装
//! - Loop header PHI node生成の専門化
//! - Preheader入力設定
//! - Latch値更新seal時
//!
//! Box-First理論: Header PHI生成の責任を明確に分離し、テスト可能な箱として提供
//!
//! # Phase 27.4 移行計画
//!
//! **Header φ の責務は JoinIR の loop_step 引数に順次移していく。**
//! Rust 側の φ は当面互換のため残すが、JoinIR 経路では以下の方針で縮退していく:
//!
//! 1. **Phase 27.4-A**: JoinIR 側で Pinned/Carrier を loop_step 引数として表現(完了)
//! 2. **Phase 27.4-B**: HeaderPhiBuilder に JoinIR 実験フラグを追加(現在)
//! 3. **Phase 27.4-C**: JoinIR 経路で Header φ をスキップ可能にする(将来)
//! 4. **Phase 27.5+**: HeaderPhiBuilder を JoinIR 前段に完全統合(長期目標)
//!
//! **原則**: 本線 MIR/LoopForm → VM の挙動は一切変えない。JoinIR はトグル付き実験経路。
use crate::mir::{BasicBlockId, ValueId};
use std::collections::HashMap;
/// Phase 27.4C Cleanup: この関数は削除され、get_loop_bypass_flags() に統合されました。
/// 直接 get_loop_bypass_flags() を使用してください。
///
/// 理由: 単一の呼び出し元しかなく、API サーフェスを縮小するため。
/// Phase 27.4C Cleanup: JoinIR Header φ バイパス対象関数リスト
///
/// Phase 27.4-C のスコープは以下の 2 関数のみ:
/// - `Main.skip/1` (minimal_ssa_skip_ws.hako)
/// - `FuncScannerBox.trim/1` (funcscanner_trim_min.hako)
///
/// **重要**: 他の関数では Header φ を絶対にスキップしないこと。
const JOINIR_HEADER_BYPASS_TARGETS: &[&str] = &["Main.skip/1", "FuncScannerBox.trim/1"];
/// Phase 27.4-C: JoinIR Header φ バイパス対象関数かチェック
///
/// `JOINIR_HEADER_BYPASS_TARGETS` に含まれる関数のみ true を返す。
pub(crate) fn is_joinir_header_bypass_target(fn_name: &str) -> bool {
JOINIR_HEADER_BYPASS_TARGETS.contains(&fn_name)
}
/// Phase 27.4-C Refactor: JoinIR Loop φ バイパスフラグ統合
///
/// Header φ と Exit φ のバイパスフラグを一箇所で管理。
/// 将来的に Exit φ バイパスPhase 27.6-2とも統合可能。
#[derive(Debug, Clone, Copy, Default)]
pub(crate) struct LoopBypassFlags {
/// Header φ バイパスが有効か
pub header: bool,
// Phase 30: exit フィールド削除(完全未使用、将来 JoinIR で代替予定)
}
/// Phase 27.4-C Refactor: JoinIR Loop φ バイパスフラグを取得
///
/// **用途**: Header/Exit φ バイパスの判定を一元化。
/// 複数箇所での重複したトグル判定を避ける。
///
/// # Arguments
/// - `fn_name` - 関数名(例: "Main.skip/1"
///
/// # Returns
/// - `LoopBypassFlags` - Header/Exit バイパスの有効状態
///
/// # Example
/// ```rust
/// let flags = get_loop_bypass_flags("Main.skip/1");
/// if flags.header {
/// // Header φ 生成をスキップ
/// }
/// ```
pub(crate) fn get_loop_bypass_flags(fn_name: &str) -> LoopBypassFlags {
// Phase 27.4C Cleanup: joinir_header_experiment_enabled() をインライン化
let joinir_exp = crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_EXPERIMENT");
let header_exp = crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_HEADER_EXP");
LoopBypassFlags {
header: joinir_exp && header_exp && is_joinir_header_bypass_target(fn_name),
// Phase 30: exit フィールド削除済み
}
}
/// 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 {
// Phase 27.4-B/27.4C Cleanup: JoinIR 実験フラグのチェック(ログ出力のみ、挙動変更なし)
if crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_HEADER_EXP") {
eprintln!(
"[HeaderPhiBuilder] JoinIR experiment flag is ON (NYASH_JOINIR_HEADER_EXP=1)"
);
}
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

@ -1,287 +0,0 @@
/*!
* phi_core::loop_phi loop-specific PHI management (legacy scaffold)
*
* - 25.1e / 25.1q / 25.2 で LoopForm v2 + LoopSnapshotMergeBox に切り替え済み。
* - 現在の Run 時間経路AST / JSON frontは `loopform_builder.rs` を SSOT としており、
* 本モジュールは互換レイヤ(歴史的な隊列や分析用)としてのみ残している。
* - Phase 31.x 以降で段階的に削除予定。新しい PHI 実装をここに追加してはいけない。
*/
use crate::ast::ASTNode;
use crate::mir::{BasicBlockId, ValueId};
/// Loop-local placeholder of an incomplete PHI (header-time declaration).
/// Moved from loop_builder to centralize PHI-related types.
#[derive(Debug, Clone)]
pub struct IncompletePhi {
pub phi_id: ValueId,
pub var_name: String,
pub known_inputs: Vec<(BasicBlockId, ValueId)>,
}
/// Common snapshot type used for continue/exit points
pub type VarSnapshot = std::collections::HashMap<String, ValueId>;
pub type SnapshotAt = (BasicBlockId, VarSnapshot);
#[derive(Default)]
pub struct LoopPhiManager;
impl LoopPhiManager {
pub fn new() -> Self {
Self::default()
}
}
/// Operations required from a loop builder to finalize PHIs.
pub trait LoopPhiOps {
fn new_value(&mut self) -> ValueId;
fn emit_phi_at_block_start(
&mut self,
block: BasicBlockId,
dst: ValueId,
inputs: Vec<(BasicBlockId, ValueId)>,
) -> Result<(), String>;
fn update_var(&mut self, name: String, value: ValueId);
fn get_variable_at_block(&mut self, name: &str, block: BasicBlockId) -> Option<ValueId>;
fn debug_verify_phi_inputs(
&mut self,
_merge_bb: BasicBlockId,
_inputs: &[(BasicBlockId, ValueId)],
) {
}
/// PHI UseBeforeDef修正: preheaderブロックでCopy命令を先行生成
fn emit_copy_at_preheader(
&mut self,
preheader_id: BasicBlockId,
dst: ValueId,
src: ValueId,
) -> Result<(), String>;
/// Optionally declare a predecessor edge pred -> block in CFG.
/// Default no-op for backends that maintain CFG elsewhere.
fn add_predecessor_edge(
&mut self,
_block: BasicBlockId,
_pred: BasicBlockId,
) -> Result<(), String> {
Ok(())
}
}
/// Finalize PHIs at loop exit (merge of break points and header fall-through).
/// Behavior mirrors loop_builder's create_exit_phis using the provided ops.
///
/// Note:
/// - 25.1e 以降の新規実装では LoopFormBuilder + LoopFormOps 側をSSOTとし、
/// 本APIは legacy 経路からの利用に限定する(構造設計上の deprecated 扱い)。
pub fn build_exit_phis_with<O: LoopPhiOps>(
ops: &mut O,
header_id: BasicBlockId,
exit_id: BasicBlockId,
header_vars: &std::collections::HashMap<String, ValueId>,
exit_snapshots: &[(BasicBlockId, VarSnapshot)],
) -> Result<(), String> {
// 1) Collect all variable names possibly participating in exit PHIs決定的順序のためBTreeSet使用
let mut all_vars = std::collections::BTreeSet::new();
for var_name in header_vars.keys() {
all_vars.insert(var_name.clone());
}
for (_bid, snapshot) in exit_snapshots.iter() {
for var_name in snapshot.keys() {
all_vars.insert(var_name.clone());
}
}
// 2) For each variable, gather incoming valuesアルファベット順で決定的
for var_name in all_vars {
let mut phi_inputs: Vec<(BasicBlockId, ValueId)> = Vec::new();
if let Some(&hv) = header_vars.get(&var_name) {
phi_inputs.push((header_id, hv));
}
for (block_id, snapshot) in exit_snapshots.iter() {
if let Some(&v) = snapshot.get(&var_name) {
phi_inputs.push((*block_id, v));
}
}
// Sanitize inputs: deduplicate by predecessor, stable sort by bb id
sanitize_phi_inputs(&mut phi_inputs);
// Ensure CFG has edges pred -> exit for all incoming preds (idempotent)
for (pred_bb, _v) in &phi_inputs {
let _ = ops.add_predecessor_edge(exit_id, *pred_bb);
}
match phi_inputs.len() {
0 => {} // nothing to do
1 => {
// single predecessor: direct binding
ops.update_var(var_name, phi_inputs[0].1);
}
_ => {
let dst = ops.new_value();
ops.debug_verify_phi_inputs(exit_id, &phi_inputs);
ops.emit_phi_at_block_start(exit_id, dst, phi_inputs)?;
ops.update_var(var_name, dst);
}
}
}
Ok(())
}
/// Seal a header block by completing its incomplete PHIs with values from
/// continue snapshots and the latch block.
///
/// Note:
/// - LoopForm PHI v2 では LoopFormBuilder 側で header/latch の PHI 完成を行うため、
/// 本関数は legacy LoopBuilder のみから呼ばれる経路として扱う。
pub fn seal_incomplete_phis_with<O: LoopPhiOps>(
ops: &mut O,
block_id: BasicBlockId,
latch_id: BasicBlockId,
mut incomplete_phis: Vec<IncompletePhi>,
continue_snapshots: &[(BasicBlockId, VarSnapshot)],
) -> Result<(), String> {
for mut phi in incomplete_phis.drain(..) {
// from continue points
for (cid, snapshot) in continue_snapshots.iter() {
if let Some(&v) = snapshot.get(&phi.var_name) {
phi.known_inputs.push((*cid, v));
let _ = ops.add_predecessor_edge(block_id, *cid);
}
}
// from latch
let value_after = ops
.get_variable_at_block(&phi.var_name, latch_id)
.ok_or_else(|| format!("Variable {} not found at latch block", phi.var_name))?;
// 🔧 ループ不変変数の自己参照PHI問題を修正
// value_afterがPHI自身の場合ループ内で変更されていない変数は、
// preheaderの値を使用する
let latch_value = if value_after == phi.phi_id {
// ループ不変変数preheaderの値を使用
phi.known_inputs[0].1 // preheaderからの初期値
} else {
value_after
};
phi.known_inputs.push((latch_id, latch_value));
let _ = ops.add_predecessor_edge(block_id, latch_id);
sanitize_phi_inputs(&mut phi.known_inputs);
ops.debug_verify_phi_inputs(block_id, &phi.known_inputs);
ops.emit_phi_at_block_start(block_id, phi.phi_id, phi.known_inputs)?;
ops.update_var(phi.var_name.clone(), phi.phi_id);
}
Ok(())
}
/// Prepare loop header PHIs by declaring one IncompletePhi per variable found
/// in `current_vars` (preheader snapshot), seeding each with (preheader_id, val)
/// and rebinding the variable to the newly allocated Phi result in the builder.
///
/// Note:
/// - 25.1e 以降、新しいLoopForm実装では LoopFormBuilder::prepare_structure を正とし、
/// 本APIは legacy 互換のための読み取り専用設計として扱う(新規利用は避ける)。
pub fn prepare_loop_variables_with<O: LoopPhiOps>(
ops: &mut O,
header_id: BasicBlockId,
preheader_id: BasicBlockId,
current_vars: &std::collections::HashMap<String, ValueId>,
) -> Result<Vec<IncompletePhi>, String> {
// 🎯 修正: current_varsをpreheader時点の値のみに限定header blockで定義された値を除外
// これにより、UseBeforeDefPHI inputsにheader内で定義された値が含まれるを防ぐ
let mut incomplete_phis: Vec<IncompletePhi> = Vec::new();
for (var_name, &value_before) in current_vars.iter() {
// Phase 25.1b fix: Include pinned variables in loop header PHIs
// Previously pinned variables were skipped, causing "use of undefined value"
// errors when receivers were used after loops. Pinned variables need PHIs
// at both header and exit points to properly merge values across control flow.
// Materialize the incoming value at preheader to satisfy UseBeforeDef constraints
// even when `value_before` was defined in a different block (e.g., previous loop header).
let pre_copy = ops.new_value();
ops.emit_copy_at_preheader(preheader_id, pre_copy, value_before)?;
let phi_id = ops.new_value();
let inc = IncompletePhi {
phi_id,
var_name: var_name.clone(),
known_inputs: vec![(preheader_id, pre_copy)], // ensure def at preheader
};
// Insert an initial PHI at header with only the preheader input so that
// the header condition reads the PHI value (first iteration = preheader).
// Later sealing will update the PHI inputs to include latch/continue preds.
ops.emit_phi_at_block_start(header_id, phi_id, inc.known_inputs.clone())?;
// Rebind variable to PHI now so that any header-time use (e.g., loop condition)
// refers to the PHI value.
ops.update_var(var_name.clone(), phi_id);
incomplete_phis.push(inc);
}
// Ensure CFG has preheader -> header edge recorded (idempotent)
let _ = ops.add_predecessor_edge(header_id, preheader_id);
Ok(incomplete_phis)
}
/// Collect variables assigned within a loop body and detect control-flow
/// statements (break/continue). Used for lightweight carrier hinting.
pub fn collect_carrier_assigns(node: &ASTNode, vars: &mut Vec<String>, has_ctrl: &mut bool) {
match node {
ASTNode::Assignment { target, .. } => {
if let ASTNode::Variable { name, .. } = target.as_ref() {
if !vars.iter().any(|v| v == name) {
vars.push(name.clone());
}
}
}
ASTNode::Break { .. } | ASTNode::Continue { .. } => {
*has_ctrl = true;
}
ASTNode::If {
then_body,
else_body,
..
} => {
let tp = ASTNode::Program {
statements: then_body.clone(),
span: crate::ast::Span::unknown(),
};
collect_carrier_assigns(&tp, vars, has_ctrl);
if let Some(eb) = else_body {
let ep = ASTNode::Program {
statements: eb.clone(),
span: crate::ast::Span::unknown(),
};
collect_carrier_assigns(&ep, vars, has_ctrl);
}
}
ASTNode::Program { statements, .. } => {
for s in statements {
collect_carrier_assigns(s, vars, has_ctrl);
}
}
_ => {}
}
}
/// Save a block-local variable snapshot into the provided store.
pub fn save_block_snapshot(
store: &mut std::collections::HashMap<BasicBlockId, VarSnapshot>,
block: BasicBlockId,
snapshot: &VarSnapshot,
) {
store.insert(block, snapshot.clone());
}
/// Deduplicate PHI inputs by predecessor and sort by block id for stability
fn sanitize_phi_inputs(inputs: &mut Vec<(BasicBlockId, ValueId)>) {
use std::collections::HashMap;
let mut map: HashMap<BasicBlockId, ValueId> = HashMap::new();
for (bb, v) in inputs.iter().cloned() {
// Later entries override earlier ones (latch should override preheader when duplicated)
map.insert(bb, v);
}
let mut vec: Vec<(BasicBlockId, ValueId)> = map.into_iter().collect();
vec.sort_by_key(|(bb, _)| bb.as_u32());
*inputs = vec;
}

View File

@ -22,6 +22,80 @@ pub(crate) fn is_loopform_debug_enabled() -> bool {
std::env::var("NYASH_LOOPFORM_DEBUG").is_ok()
}
// ============================================================================
// Phase 30 F-2.1: JoinIR バイパスフラグheader_phi_builder から移動)
// ============================================================================
/// Phase 27.4C Cleanup: JoinIR Header φ バイパス対象関数リスト
///
/// Phase 27.4-C のスコープは以下の 2 関数のみ:
/// - `Main.skip/1` (minimal_ssa_skip_ws.hako)
/// - `FuncScannerBox.trim/1` (funcscanner_trim_min.hako)
///
/// **重要**: 他の関数では Header φ を絶対にスキップしないこと。
const JOINIR_HEADER_BYPASS_TARGETS: &[&str] = &["Main.skip/1", "FuncScannerBox.trim/1"];
/// Phase 27.4-C: JoinIR Header φ バイパス対象関数かチェック
///
/// `JOINIR_HEADER_BYPASS_TARGETS` に含まれる関数のみ true を返す。
pub(crate) fn is_joinir_header_bypass_target(fn_name: &str) -> bool {
JOINIR_HEADER_BYPASS_TARGETS.contains(&fn_name)
}
/// Phase 27.4-C Refactor: JoinIR Loop φ バイパスフラグ統合
///
/// Header φ と Exit φ のバイパスフラグを一箇所で管理。
/// 将来的に Exit φ バイパスPhase 27.6-2とも統合可能。
#[derive(Debug, Clone, Copy, Default)]
pub(crate) struct LoopBypassFlags {
/// Header φ バイパスが有効か
pub header: bool,
// Phase 30: exit フィールド削除(完全未使用、将来 JoinIR で代替予定)
}
/// Phase 27.4-C Refactor: JoinIR Loop φ バイパスフラグを取得
///
/// **用途**: Header/Exit φ バイパスの判定を一元化。
/// 複数箇所での重複したトグル判定を避ける。
///
/// # Arguments
/// - `fn_name` - 関数名(例: "Main.skip/1"
///
/// # Returns
/// - `LoopBypassFlags` - Header/Exit バイパスの有効状態
pub(crate) fn get_loop_bypass_flags(fn_name: &str) -> LoopBypassFlags {
let joinir_exp = crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_EXPERIMENT");
let header_exp = crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_HEADER_EXP");
LoopBypassFlags {
header: joinir_exp && header_exp && is_joinir_header_bypass_target(fn_name),
}
}
// ============================================================================
// Phase 30 F-2.1: Exit バイパスフラグexit_phi_builder から移動)
// ============================================================================
/// JoinIR Exit φ バイパスが有効かどうか
///
/// - NYASH_JOINIR_EXPERIMENT=1
/// - NYASH_JOINIR_EXIT_EXP=1
///
/// の両方が立っているときだけ true。
pub(crate) fn joinir_exit_bypass_enabled() -> bool {
crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_EXPERIMENT")
&& crate::mir::join_ir::env_flag_is_1("NYASH_JOINIR_EXIT_EXP")
}
/// Exit φ バイパス対象の関数かどうか
///
/// 当面は minimal/trim の 2 本だけ:
/// - Main.skip/1
/// - FuncScannerBox.trim/1
pub(crate) fn is_joinir_exit_bypass_target(func_name: &str) -> bool {
matches!(func_name, "Main.skip/1" | "FuncScannerBox.trim/1")
}
/// 📦 LoopForm Context - Box-First理論に基づくパラメータ予約明示化
///
/// ValueId割り当ての境界を明確にし、パラメータ予約を明示的に管理。

View File

@ -10,7 +10,7 @@
pub mod common;
pub mod conservative;
pub mod if_phi;
pub mod loop_phi;
// Phase 30 F-2.1: loop_phi 削除LoopFormBuilder が SSOT
pub mod loop_snapshot_merge;
pub mod loopform_builder;
@ -19,15 +19,15 @@ pub mod local_scope_inspector;
pub mod loop_var_classifier;
// Phase 26-B: Box-First Refactoring
pub mod body_local_phi_builder;
// Phase 30 F-2.1: body_local_phi_builder 削除LoopScopeShape で代替)
pub mod phi_input_collector;
// Phase 26-C: Loop Snapshot & Header PHI Management
pub mod header_phi_builder;
// Phase 26-C: Loop Snapshot Management
// Phase 30 F-2.1: header_phi_builder 削除JoinIR loop_step で代替)
pub mod loop_snapshot_manager;
// Phase 26-D: Exit PHI Management
pub mod exit_phi_builder;
// Phase 30 F-2.1: exit_phi_builder 削除JoinIR k_exit で代替、バイパス関数は loopform_builder に移動)
// Phase 26-E: PHI SSOT Unification - PhiBuilderBox
pub mod phi_builder_box;