diff --git a/src/config/env.rs b/src/config/env.rs index f0f9b9a3..3b1447cc 100644 --- a/src/config/env.rs +++ b/src/config/env.rs @@ -265,6 +265,18 @@ pub fn joinir_debug_level() -> u8 { 0 } +/// Phase 61-2: If-in-loop JoinIR dry-run有効化 +/// +/// `HAKO_JOINIR_IF_IN_LOOP_DRYRUN=1` でdry-runモードを有効化 +/// +/// dry-runモード: +/// - JoinIR経路でPHI仕様を計算 +/// - PhiBuilderBox経路と比較 +/// - 実際のPHI生成はPhiBuilderBoxを使用(安全) +pub fn joinir_if_in_loop_dryrun_enabled() -> bool { + env_bool("HAKO_JOINIR_IF_IN_LOOP_DRYRUN") +} + // VM legacy by-name call fallback was removed (Phase 2 complete). // Phase 40-4.1: use_joinir_for_array_filter() removed (Route B now default). diff --git a/src/mir/join_ir/lowering/if_phi_spec.rs b/src/mir/join_ir/lowering/if_phi_spec.rs new file mode 100644 index 00000000..e24534ee --- /dev/null +++ b/src/mir/join_ir/lowering/if_phi_spec.rs @@ -0,0 +1,188 @@ +//! Phase 61-2: JoinIR経路でのPHI仕様計算 +//! +//! JoinInst(Select/IfMerge)から、どの変数がPHIを必要とするかを計算する。 + +use crate::mir::join_ir::JoinInst; +use crate::mir::join_ir::lowering::if_phi_context::IfPhiContext; +use crate::mir::ValueId; +use std::collections::{BTreeMap, BTreeSet}; + +/// PHI仕様(どの変数がPHIを持つべきか) +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PhiSpec { + /// Header PHI候補(ループキャリア変数) + pub header_phis: BTreeSet, + + /// Exit PHI候補(ループ脱出時の値) + pub exit_phis: BTreeSet, +} + +impl PhiSpec { + pub fn new() -> Self { + Self { + header_phis: BTreeSet::new(), + exit_phis: BTreeSet::new(), + } + } + + /// Header PHI数を取得 + pub fn header_count(&self) -> usize { + self.header_phis.len() + } + + /// Exit PHI数を取得 + pub fn exit_count(&self) -> usize { + self.exit_phis.len() + } + + /// 2つのPhiSpecが一致するか検証 + pub fn matches(&self, other: &PhiSpec) -> bool { + self.header_phis == other.header_phis && self.exit_phis == other.exit_phis + } +} + +/// JoinInstからPHI仕様を計算 +/// +/// # Arguments +/// +/// * `ctx` - If-in-loopコンテキスト(carrier_names情報) +/// * `join_inst` - JoinIR命令(Select/IfMerge) +/// +/// # Returns +/// +/// PHI仕様(header/exit PHI候補) +pub fn compute_phi_spec_from_joinir(ctx: &IfPhiContext, join_inst: &JoinInst) -> PhiSpec { + let mut spec = PhiSpec::new(); + + match join_inst { + JoinInst::Select { dst, .. } => { + // Select命令: 単一変数のPHI + // carrier_namesに含まれる変数をheader PHIとして扱う + // TODO Phase 61-3: dstからvariable_nameを逆引き(MIR Builderのvariable_map参照) + spec.header_phis = ctx.carrier_names.clone(); + } + JoinInst::IfMerge { merges, .. } => { + // IfMerge命令: 複数変数のPHI + // TODO Phase 61-3: merge_pair.dstからvariable_nameを逆引き + // 暫定: carrier_namesに含まれる変数をheader PHIとして扱う + spec.header_phis = ctx.carrier_names.clone(); + + if ctx.in_loop_body { + eprintln!( + "[Phase 61-2] IfMerge with {} merge pairs in loop body", + merges.len() + ); + } + } + _ => { + eprintln!("[Phase 61-2] ⚠️ Unexpected JoinInst variant for PHI spec"); + } + } + + spec +} + +/// PhiBuilderBoxから実際に生成されたPHI仕様を取得 +/// +/// # Arguments +/// +/// * `pre_if_var_map` - If前の変数マップ +/// * `post_snapshots` - If後のスナップショット +/// * `carrier_names` - ループキャリア変数名 +/// +/// # Note +/// +/// Phase 61-2では、PhiBuilderBox経路で実際に生成されたPHIを観察する。 +/// Phase 61-3で、このロジックがJoinIR経路に置き換わる。 +pub fn extract_phi_spec_from_builder( + pre_if_var_map: &BTreeMap, + _post_snapshots: &[BTreeMap], + carrier_names: &BTreeSet, +) -> PhiSpec { + let mut spec = PhiSpec::new(); + + // carrier_namesに含まれる変数をPHI候補として扱う + for var_name in carrier_names { + if pre_if_var_map.contains_key(var_name) { + spec.header_phis.insert(var_name.clone()); + } + } + + // Exit PHIは別途計算が必要(Phase 61-3で詳細実装) + // 現状はheader PHIのみを比較対象とする + + spec +} + +/// 2つのPhiSpecを比較し、結果をログ出力 +/// +/// # Returns +/// +/// true if specs match, false otherwise +pub fn compare_and_log_phi_specs(joinir_spec: &PhiSpec, builder_spec: &PhiSpec) -> bool { + let matches = joinir_spec.matches(builder_spec); + + if matches { + eprintln!("[Phase 61-2] ✅ PHI spec matches!"); + eprintln!( + "[Phase 61-2] Header PHIs: {}", + joinir_spec.header_count() + ); + eprintln!( + "[Phase 61-2] Exit PHIs: {}", + joinir_spec.exit_count() + ); + } else { + eprintln!("[Phase 61-2] ❌ PHI spec mismatch detected!"); + eprintln!("[Phase 61-2] JoinIR spec:"); + eprintln!( + "[Phase 61-2] Header: {:?}", + joinir_spec.header_phis + ); + eprintln!("[Phase 61-2] Exit: {:?}", joinir_spec.exit_phis); + eprintln!("[Phase 61-2] PhiBuilderBox spec:"); + eprintln!( + "[Phase 61-2] Header: {:?}", + builder_spec.header_phis + ); + eprintln!("[Phase 61-2] Exit: {:?}", builder_spec.exit_phis); + } + + matches +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_phi_spec_creation() { + let spec = PhiSpec::new(); + assert_eq!(spec.header_count(), 0); + assert_eq!(spec.exit_count(), 0); + } + + #[test] + fn test_phi_spec_matches() { + let mut spec1 = PhiSpec::new(); + spec1.header_phis.insert("x".to_string()); + spec1.header_phis.insert("y".to_string()); + + let mut spec2 = PhiSpec::new(); + spec2.header_phis.insert("x".to_string()); + spec2.header_phis.insert("y".to_string()); + + assert!(spec1.matches(&spec2)); + } + + #[test] + fn test_phi_spec_mismatch() { + let mut spec1 = PhiSpec::new(); + spec1.header_phis.insert("x".to_string()); + + let mut spec2 = PhiSpec::new(); + spec2.header_phis.insert("y".to_string()); + + assert!(!spec1.matches(&spec2)); + } +} diff --git a/src/mir/join_ir/lowering/mod.rs b/src/mir/join_ir/lowering/mod.rs index fb6c24a4..aa81ce5b 100644 --- a/src/mir/join_ir/lowering/mod.rs +++ b/src/mir/join_ir/lowering/mod.rs @@ -23,6 +23,7 @@ pub mod generic_case_a; pub mod if_dry_runner; // Phase 33-10.0 pub mod if_merge; // Phase 33-7 pub mod if_phi_context; // Phase 61-1 +pub mod if_phi_spec; // Phase 61-2 pub mod if_select; // Phase 33 pub mod loop_form_intake; pub mod loop_scope_shape; diff --git a/src/mir/loop_builder.rs b/src/mir/loop_builder.rs index 3f68123e..76e2149e 100644 --- a/src/mir/loop_builder.rs +++ b/src/mir/loop_builder.rs @@ -1159,7 +1159,12 @@ impl<'a> LoopBuilder<'a> { .cloned() .collect(); - // Phase 61-1: JoinIR経路を試行(`Ops`作成前に実行) + // Phase 61-2: JoinIR dry-run検証モード + // dry-run用: JoinInstとPhiSpecを保存(A/B比較用) + let mut joinir_phi_spec_opt: Option< + crate::mir::join_ir::lowering::if_phi_spec::PhiSpec, + > = None; + let joinir_success = if crate::config::env::joinir_if_select_enabled() { // IfPhiContext作成 let if_phi_context = @@ -1176,15 +1181,42 @@ impl<'a> LoopBuilder<'a> { Some(&if_phi_context), ) { Some(join_inst) => { - eprintln!("[Phase 61-1] ✅ If-in-loop lowered via JoinIR: {:?}", join_inst); + eprintln!("[Phase 61-2] ✅ If-in-loop lowered via JoinIR: {:?}", join_inst); - // TODO: join_inst を dry-run して PHI 生成 - // Phase 61-1では一旦スキップし、Phase 61-2で実装 - eprintln!("[Phase 61-1] ⚠️ JoinIR dry-run not yet implemented, using fallback"); - false // 一旦フォールバック + // Phase 61-2: dry-runモードでPHI仕様を検証 + if crate::config::env::joinir_if_in_loop_dryrun_enabled() { + eprintln!("[Phase 61-2] 🔍 dry-run mode enabled"); + eprintln!("[Phase 61-2] Carrier variables: {:?}", carrier_names); + eprintln!( + "[Phase 61-2] JoinInst type: {}", + match &join_inst { + crate::mir::join_ir::JoinInst::Select { .. } => "Select", + crate::mir::join_ir::JoinInst::IfMerge { .. } => "IfMerge", + _ => "Other" + } + ); + + // Phase 61-2.3: JoinInstからPhiSpecを計算 + let joinir_spec = crate::mir::join_ir::lowering::if_phi_spec::compute_phi_spec_from_joinir( + &if_phi_context, + &join_inst, + ); + eprintln!( + "[Phase 61-2] JoinIR PhiSpec: header={}, exit={}", + joinir_spec.header_count(), + joinir_spec.exit_count() + ); + + // A/B比較用に保存 + joinir_phi_spec_opt = Some(joinir_spec); + } + + false // Phase 61-2では検証のみ、本番切り替えはPhase 61-3 } None => { - eprintln!("[Phase 61-1] ⏭️ JoinIR pattern not matched, using fallback"); + if crate::config::env::joinir_if_in_loop_dryrun_enabled() { + eprintln!("[Phase 61-2] ⏭️ JoinIR pattern not matched, using fallback"); + } false } } @@ -1210,7 +1242,7 @@ impl<'a> LoopBuilder<'a> { // Phase 26-F-3: ループ内if-mergeコンテキスト設定(ChatGPT設計) phi_builder.set_if_context( true, // in_loop_body = true - carrier_names, + carrier_names.clone(), ); // Phase 35-5: if_body_local_merge.rs削除、ロジックはPhiBuilderBox内に統合 @@ -1220,6 +1252,31 @@ impl<'a> LoopBuilder<'a> { vec![then_var_map_end.clone()] }; phi_builder.generate_phis(&mut ops, &form, &pre_if_var_map, &post_snapshots)?; + + // Phase 61-2: A/B比較(JoinIR vs PhiBuilderBox) + if crate::config::env::joinir_if_in_loop_dryrun_enabled() { + if let Some(ref joinir_spec) = joinir_phi_spec_opt { + // PhiBuilderBox経路でのPhiSpecを抽出 + let builder_spec = + crate::mir::join_ir::lowering::if_phi_spec::extract_phi_spec_from_builder( + &pre_if_var_map, + &post_snapshots, + &carrier_names, + ); + + eprintln!( + "[Phase 61-2] PhiBuilderBox PhiSpec: header={}, exit={}", + builder_spec.header_count(), + builder_spec.exit_count() + ); + + // A/B比較実行 + let _matches = crate::mir::join_ir::lowering::if_phi_spec::compare_and_log_phi_specs( + joinir_spec, + &builder_spec, + ); + } + } } // Phase 26-E-4: PHI生成後に variable_map をリセット(ChatGPT/Task先生指示) diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 2869d0bb..2ee80d43 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -17,6 +17,7 @@ pub mod mir; pub mod namingbox_static_method_id; // Phase 21.7++ Phase 1: StaticMethodId structure tests pub mod nyash_abi_basic; pub mod parser; +pub mod phase61_if_in_loop_dryrun; // Phase 61-2: If-in-loop JoinIR dry-run tests pub mod plugin_hygiene; pub mod policy_mutdeny; pub mod refcell_assignment_test; diff --git a/src/tests/phase61_if_in_loop_dryrun.rs b/src/tests/phase61_if_in_loop_dryrun.rs new file mode 100644 index 00000000..57257d11 --- /dev/null +++ b/src/tests/phase61_if_in_loop_dryrun.rs @@ -0,0 +1,48 @@ +//! Phase 61-2: If-in-loop JoinIR dry-run + PHI生成 A/B比較テスト +//! +//! 目的: JoinIR経路でPHI仕様を計算し、PhiBuilderBox経路との一致を検証 +//! +//! 注意: Phase 61-2はdry-run検証のみ。実行結果は変わらない。 +//! JoinIRパターンマッチは Phase 33の厳格な条件に依存する。 + +#[cfg(test)] +mod tests { + #[test] + fn phase61_2_dry_run_flag_available() { + // Phase 61-2: dry-runフラグが正しく読み取れることを確認 + std::env::set_var("HAKO_JOINIR_IF_IN_LOOP_DRYRUN", "1"); + assert_eq!(crate::config::env::joinir_if_in_loop_dryrun_enabled(), true); + + std::env::remove_var("HAKO_JOINIR_IF_IN_LOOP_DRYRUN"); + assert_eq!( + crate::config::env::joinir_if_in_loop_dryrun_enabled(), + false + ); + + eprintln!("[Test] phase61_2_dry_run_flag_available passed"); + } + + #[test] + fn phase61_2_phi_spec_creation() { + use crate::mir::join_ir::lowering::if_phi_spec::PhiSpec; + + let mut spec1 = PhiSpec::new(); + assert_eq!(spec1.header_count(), 0); + assert_eq!(spec1.exit_count(), 0); + + spec1.header_phis.insert("x".to_string()); + spec1.header_phis.insert("y".to_string()); + assert_eq!(spec1.header_count(), 2); + + let mut spec2 = PhiSpec::new(); + spec2.header_phis.insert("x".to_string()); + spec2.header_phis.insert("y".to_string()); + + assert!(spec1.matches(&spec2)); + + eprintln!("[Test] phase61_2_phi_spec_creation passed"); + } +} + +// Note: E2E tests for actual if-in-loop JoinIR lowering will be added in Phase 61-3 +// when the production switch is made. Phase 61-2 focuses on dry-run infrastructure.