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:
@ -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: このBox(PHI生成判定)
|
||||
///
|
||||
/// # 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_exit(live情報なし)
|
||||
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_exit(live情報なし)
|
||||
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_exit(live情報なし)
|
||||
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_exit(live情報なし)
|
||||
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_exit(live情報なし)
|
||||
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_exit(live情報なし)
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -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");
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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で定義された値を除外)
|
||||
// これにより、UseBeforeDef(PHI 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;
|
||||
}
|
||||
@ -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割り当ての境界を明確にし、パラメータ予約を明示的に管理。
|
||||
|
||||
@ -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;
|
||||
|
||||
Reference in New Issue
Block a user