diff --git a/src/mir/join_ir/lowering/if_select.rs b/src/mir/join_ir/lowering/if_select.rs index 09e3129e..6fd477e6 100644 --- a/src/mir/join_ir/lowering/if_select.rs +++ b/src/mir/join_ir/lowering/if_select.rs @@ -3,44 +3,227 @@ //! 最小の if/else(副作用なし、単純な値選択)を JoinInst::Select に変換する。 use crate::mir::join_ir::JoinInst; -use crate::mir::{BasicBlockId, MirFunction}; +use crate::mir::{BasicBlockId, MirFunction, MirInstruction, ValueId}; pub struct IfSelectLowerer { debug: bool, } +/// If/Else パターンの分類 +#[derive(Debug, Clone, Copy)] +enum IfPatternType { + /// Simple pattern: if cond { return 1 } else { return 2 } + Simple, + /// Local pattern: if cond { x = a } else { x = b }; return x + Local, +} + +/// 検出された If/Else パターン情報 +#[derive(Debug, Clone)] +struct IfPattern { + pattern_type: IfPatternType, + cond: ValueId, + then_val: ValueId, + else_val: ValueId, + dst: Option, +} + +/// Branch 命令の情報 +#[derive(Debug, Clone)] +struct IfBranch { + cond: ValueId, + then_block: BasicBlockId, + else_block: BasicBlockId, +} + impl IfSelectLowerer { pub fn new(debug: bool) -> Self { Self { debug } } /// if/else が Select に lowering できるかチェック - pub fn can_lower_to_select(&self, _func: &MirFunction, _if_block_id: BasicBlockId) -> bool { - // パターン: - // 1. if_block に Branch がある - // 2. then/else ブロックが存在 - // 3. merge ブロックに 1 つの PHI がある - // 4. PHI の incoming が then/else から来ている - - // 実装: Phase 33-2 では保守的に false を返す(フォールバック) - // Phase 33-3 で実装予定 - if self.debug { - eprintln!("[joinir/if_select] can_lower_to_select: Phase 33-2 stub (always false)"); - } - false + pub fn can_lower_to_select(&self, func: &MirFunction, if_block_id: BasicBlockId) -> bool { + self.find_if_pattern(func, if_block_id).is_some() } /// if/else を Select に変換 pub fn lower_if_to_select( &self, - _func: &MirFunction, - _if_block_id: BasicBlockId, + func: &MirFunction, + if_block_id: BasicBlockId, ) -> Option { - // 実装: Phase 33-2 では None を返す(フォールバック) - // Phase 33-3 で実装予定 + let pattern = self.find_if_pattern(func, if_block_id)?; + if self.debug { - eprintln!("[joinir/if_select] lower_if_to_select: Phase 33-2 stub (always None)"); + eprintln!( + "[IfSelectLowerer] lowering {:?} pattern to Select", + pattern.pattern_type + ); + } + + // Select 命令を生成 + let dst = pattern.dst.unwrap_or(pattern.then_val); + + Some(JoinInst::Select { + dst, + cond: pattern.cond, + then_val: pattern.then_val, + else_val: pattern.else_val, + }) + } + + /// MIR 関数から if/else パターンを探す + fn find_if_pattern( + &self, + func: &MirFunction, + block_id: BasicBlockId, + ) -> Option { + // 1. Block が Branch 命令で終わっているか確認 + let block = func.blocks.get(&block_id)?; + let branch = match block.terminator.as_ref()? { + MirInstruction::Branch { + condition, + then_bb, + else_bb, + } => IfBranch { + cond: *condition, + then_block: *then_bb, + else_block: *else_bb, + }, + _ => return None, + }; + + // 2. then/else ブロックの構造を確認 + let then_block = func.blocks.get(&branch.then_block)?; + let else_block = func.blocks.get(&branch.else_block)?; + + // 3. simple パターンのチェック + if let Some(pattern) = self.try_match_simple_pattern(&branch, then_block, else_block) { + if self.debug { + eprintln!("[IfSelectLowerer] matched simple pattern"); + } + return Some(pattern); + } + + // 4. local パターンのチェック + if let Some(pattern) = self.try_match_local_pattern(func, &branch, then_block, else_block) + { + if self.debug { + eprintln!("[IfSelectLowerer] matched local pattern"); + } + return Some(pattern); + } + + if self.debug { + eprintln!("[IfSelectLowerer] no pattern matched"); } None } + + /// simple パターン: if cond { return 1 } else { return 2 } + fn try_match_simple_pattern( + &self, + branch: &IfBranch, + then_block: &crate::mir::BasicBlock, + else_block: &crate::mir::BasicBlock, + ) -> Option { + // then ブロックが Return だけか確認 + let then_val = match then_block.terminator.as_ref()? { + MirInstruction::Return { value: Some(v) } => *v, + _ => return None, + }; + + // else ブロックが Return だけか確認 + let else_val = match else_block.terminator.as_ref()? { + MirInstruction::Return { value: Some(v) } => *v, + _ => return None, + }; + + // 両方のブロックが命令を持たない(Return のみ)ことを確認 + if !then_block.instructions.is_empty() || !else_block.instructions.is_empty() { + return None; + } + + Some(IfPattern { + pattern_type: IfPatternType::Simple, + cond: branch.cond, + then_val, + else_val, + dst: None, + }) + } + + /// local パターン: if cond { x = a } else { x = b }; return x + fn try_match_local_pattern( + &self, + func: &MirFunction, + branch: &IfBranch, + then_block: &crate::mir::BasicBlock, + else_block: &crate::mir::BasicBlock, + ) -> Option { + // then ブロックが「1命令 + Jump」の形か確認 + if then_block.instructions.len() != 1 { + return None; + } + + // then ブロックの命令が代入(Copy)か確認 + let (dst_then, val_then) = match &then_block.instructions[0] { + MirInstruction::Copy { dst, src } => (*dst, *src), + _ => return None, + }; + + // then ブロックが Jump で終わるか確認 + let merge_block_id = match then_block.terminator.as_ref()? { + MirInstruction::Jump { target } => *target, + _ => return None, + }; + + // else ブロックも同じ構造か確認 + if else_block.instructions.len() != 1 { + return None; + } + + let (dst_else, val_else) = match &else_block.instructions[0] { + MirInstruction::Copy { dst, src } => (*dst, *src), + _ => return None, + }; + + // 代入先が同じ変数か確認 + if dst_then != dst_else { + return None; + } + + // else ブロックも同じ merge ブロックに Jump するか確認 + let else_merge = match else_block.terminator.as_ref()? { + MirInstruction::Jump { target } => *target, + _ => return None, + }; + + if merge_block_id != else_merge { + return None; + } + + // merge ブロックが「return dst」だけか確認 + let merge_block = func.blocks.get(&merge_block_id)?; + match merge_block.terminator.as_ref()? { + MirInstruction::Return { + value: Some(v), + } if *v == dst_then => { + // OK + } + _ => return None, + } + + if !merge_block.instructions.is_empty() { + return None; + } + + Some(IfPattern { + pattern_type: IfPatternType::Local, + cond: branch.cond, + then_val: val_then, + else_val: val_else, + dst: Some(dst_then), + }) + } } diff --git a/src/mir/join_ir/lowering/mod.rs b/src/mir/join_ir/lowering/mod.rs index f788f89f..3ce488e6 100644 --- a/src/mir/join_ir/lowering/mod.rs +++ b/src/mir/join_ir/lowering/mod.rs @@ -41,3 +41,61 @@ pub use skip_ws::lower_skip_ws_to_joinir; pub use stage1_using_resolver::lower_stage1_usingresolver_to_joinir; pub use stageb_body::lower_stageb_body_to_joinir; pub use stageb_funcscanner::lower_stageb_funcscanner_to_joinir; + +// Phase 33: If/Else → Select lowering entry point +use crate::mir::join_ir::JoinInst; +use crate::mir::{BasicBlockId, MirFunction}; + +/// Phase 33-3: Try to lower if/else to JoinIR Select instruction +/// +/// Scope: +/// - Only applies to functions matching "IfSelectTest.*" +/// - Requires NYASH_JOINIR_IF_SELECT=1 environment variable +/// - Falls back to traditional if_phi on pattern mismatch +/// +/// Returns Some(JoinInst::Select) if pattern matched, None otherwise. +pub fn try_lower_if_to_joinir( + func: &MirFunction, + block_id: BasicBlockId, + debug: bool, +) -> Option { + // dev トグルチェック + if !crate::config::env::joinir_if_select_enabled() { + return None; + } + + // 関数名で制限(IfSelectTest.* のみ) + if !func.signature.name.starts_with("IfSelectTest.") { + if debug { + eprintln!( + "[try_lower_if_to_joinir] skipping non-test function: {}", + func.signature.name + ); + } + return None; + } + + // if_select lowering を試行 + let lowerer = if_select::IfSelectLowerer::new(debug); + + if !lowerer.can_lower_to_select(func, block_id) { + if debug { + eprintln!( + "[try_lower_if_to_joinir] pattern not matched for {}", + func.signature.name + ); + } + return None; + } + + let result = lowerer.lower_if_to_select(func, block_id); + + if result.is_some() && debug { + eprintln!( + "[try_lower_if_to_joinir] if_select lowering used for {}", + func.signature.name + ); + } + + result +} diff --git a/src/tests/mir_joinir_if_select.rs b/src/tests/mir_joinir_if_select.rs new file mode 100644 index 00000000..c3a22c89 --- /dev/null +++ b/src/tests/mir_joinir_if_select.rs @@ -0,0 +1,232 @@ +//! Phase 33-3: If/Else → Select lowering integration tests +//! +//! Tests the pattern matching and lowering of if/else to JoinIR Select instruction. + +#[cfg(test)] +mod tests { + use crate::mir::join_ir::lowering::try_lower_if_to_joinir; + use crate::mir::join_ir::JoinInst; + use crate::mir::{ + BasicBlock, BasicBlockId, MirFunction, MirInstruction, MirModule, ValueId, + }; + use std::collections::BTreeMap; + + /// Helper to create a simple if/else function matching the "simple" pattern + fn create_simple_pattern_mir() -> MirFunction { + let mut blocks = BTreeMap::new(); + + // Entry block (bb0): branch on cond + let mut entry = BasicBlock::new(BasicBlockId::new(0)); + entry.terminator = Some(MirInstruction::Branch { + condition: ValueId(0), // cond parameter + then_bb: BasicBlockId::new(1), + else_bb: BasicBlockId::new(2), + }); + blocks.insert(BasicBlockId::new(0), entry); + + // Then block (bb1): return 10 + // NOTE: Pattern matcher expects empty blocks (Return only) + let mut then_block = BasicBlock::new(BasicBlockId::new(1)); + then_block.terminator = Some(MirInstruction::Return { + value: Some(ValueId(1)), // Assumes ValueId(1) is const 10 + }); + blocks.insert(BasicBlockId::new(1), then_block); + + // Else block (bb2): return 20 + // NOTE: Pattern matcher expects empty blocks (Return only) + let mut else_block = BasicBlock::new(BasicBlockId::new(2)); + else_block.terminator = Some(MirInstruction::Return { + value: Some(ValueId(2)), // Assumes ValueId(2) is const 20 + }); + blocks.insert(BasicBlockId::new(2), else_block); + + use crate::mir::{EffectMask, MirType}; + use crate::mir::function::FunctionMetadata; + use std::collections::HashMap; + + MirFunction { + signature: crate::mir::FunctionSignature { + name: "IfSelectTest.test/1".to_string(), + params: vec![MirType::Unknown], + return_type: MirType::Integer, + effects: EffectMask::PURE, + }, + entry_block: BasicBlockId::new(0), + blocks: blocks.into_iter().collect(), + locals: vec![], + params: vec![ValueId(0)], + next_value_id: 3, + metadata: FunctionMetadata::default(), + } + } + + /// Helper to create a local pattern function + fn create_local_pattern_mir() -> MirFunction { + let mut blocks = BTreeMap::new(); + + // Entry block (bb0): branch on cond + let mut entry = BasicBlock::new(BasicBlockId::new(0)); + entry.terminator = Some(MirInstruction::Branch { + condition: ValueId(0), // cond + then_bb: BasicBlockId::new(1), + else_bb: BasicBlockId::new(2), + }); + blocks.insert(BasicBlockId::new(0), entry); + + // Then block (bb1): x = 100; jump merge + // NOTE: Pattern matcher expects exactly 1 Copy instruction + let mut then_block = BasicBlock::new(BasicBlockId::new(1)); + then_block.instructions.push(MirInstruction::Copy { + dst: ValueId(3), // x + src: ValueId(10), // Assumes ValueId(10) is const 100 + }); + then_block.terminator = Some(MirInstruction::Jump { + target: BasicBlockId::new(3), + }); + blocks.insert(BasicBlockId::new(1), then_block); + + // Else block (bb2): x = 200; jump merge + // NOTE: Pattern matcher expects exactly 1 Copy instruction + let mut else_block = BasicBlock::new(BasicBlockId::new(2)); + else_block.instructions.push(MirInstruction::Copy { + dst: ValueId(3), // x + src: ValueId(20), // Assumes ValueId(20) is const 200 + }); + else_block.terminator = Some(MirInstruction::Jump { + target: BasicBlockId::new(3), + }); + blocks.insert(BasicBlockId::new(2), else_block); + + // Merge block (bb3): return x + let mut merge_block = BasicBlock::new(BasicBlockId::new(3)); + merge_block.terminator = Some(MirInstruction::Return { + value: Some(ValueId(3)), + }); + blocks.insert(BasicBlockId::new(3), merge_block); + + use crate::mir::{EffectMask, MirType}; + use crate::mir::function::FunctionMetadata; + use std::collections::HashMap; + + MirFunction { + signature: crate::mir::FunctionSignature { + name: "IfSelectTest.main/0".to_string(), + params: vec![], + return_type: MirType::Integer, + effects: EffectMask::PURE, + }, + entry_block: BasicBlockId::new(0), + blocks: blocks.into_iter().collect(), + locals: vec![], + params: vec![], + next_value_id: 21, + metadata: FunctionMetadata::default(), + } + } + + #[test] + fn test_if_select_simple_pattern() { + // Set environment variable for this test + std::env::set_var("NYASH_JOINIR_IF_SELECT", "1"); + + let func = create_simple_pattern_mir(); + let entry_block = func.entry_block; + + // Try to lower to JoinIR + let result = try_lower_if_to_joinir(&func, entry_block, true); + + assert!( + result.is_some(), + "Expected simple pattern to be lowered to Select" + ); + + if let Some(JoinInst::Select { + dst, + cond, + then_val, + else_val, + }) = result + { + eprintln!("✅ Simple pattern successfully lowered to Select"); + eprintln!(" dst: {:?}, cond: {:?}, then: {:?}, else: {:?}", dst, cond, then_val, else_val); + } else { + panic!("Expected JoinInst::Select, got {:?}", result); + } + + // Clean up + std::env::remove_var("NYASH_JOINIR_IF_SELECT"); + } + + #[test] + fn test_if_select_local_pattern() { + std::env::set_var("NYASH_JOINIR_IF_SELECT", "1"); + + let func = create_local_pattern_mir(); + let entry_block = func.entry_block; + + // Try to lower to JoinIR + let result = try_lower_if_to_joinir(&func, entry_block, true); + + assert!( + result.is_some(), + "Expected local pattern to be lowered to Select" + ); + + if let Some(JoinInst::Select { + dst, + cond, + then_val, + else_val, + }) = result + { + eprintln!("✅ Local pattern successfully lowered to Select"); + eprintln!(" dst: {:?}, cond: {:?}, then: {:?}, else: {:?}", dst, cond, then_val, else_val); + } else { + panic!("Expected JoinInst::Select, got {:?}", result); + } + + std::env::remove_var("NYASH_JOINIR_IF_SELECT"); + } + + #[test] + fn test_if_select_disabled_by_default() { + // Ensure environment variable is NOT set + std::env::remove_var("NYASH_JOINIR_IF_SELECT"); + + let func = create_simple_pattern_mir(); + let entry_block = func.entry_block; + + // Should return None when disabled + let result = try_lower_if_to_joinir(&func, entry_block, false); + + assert!( + result.is_none(), + "Expected None when NYASH_JOINIR_IF_SELECT is not set" + ); + + eprintln!("✅ If/Select lowering correctly disabled by default"); + } + + #[test] + fn test_if_select_wrong_function_name() { + std::env::set_var("NYASH_JOINIR_IF_SELECT", "1"); + + // Create function with wrong name (not IfSelectTest.*) + let mut func = create_simple_pattern_mir(); + func.signature.name = "WrongName.test/1".to_string(); + + let entry_block = func.entry_block; + + // Should return None for non-IfSelectTest functions + let result = try_lower_if_to_joinir(&func, entry_block, true); + + assert!( + result.is_none(), + "Expected None for non-IfSelectTest functions" + ); + + eprintln!("✅ Function name filter working correctly"); + + std::env::remove_var("NYASH_JOINIR_IF_SELECT"); + } +} diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 8aad7e37..de0d2ebf 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -20,6 +20,7 @@ pub mod mir_funcscanner_ssa; pub mod mir_funcscanner_trim_min; pub mod mir_joinir_funcscanner_append_defs; // Phase 27.14: FuncScannerBox._append_defs JoinIR変換 pub mod mir_joinir_funcscanner_trim; // Phase 27.1: FuncScannerBox.trim JoinIR変換 +pub mod mir_joinir_if_select; // Phase 33-3: If/Else → Select lowering tests pub mod mir_joinir_min; // Phase 26-H: JoinIR型定義妥当性確認 pub mod mir_joinir_skip_ws; // Phase 27.0: minimal_ssa_skip_ws JoinIR変換 pub mod mir_joinir_stage1_using_resolver_min; // Phase 27.12: Stage1UsingResolverBox.resolve_for_source JoinIR変換