diff --git a/src/mir/builder/control_flow.rs b/src/mir/builder/control_flow.rs index 1e244038..6953f0d3 100644 --- a/src/mir/builder/control_flow.rs +++ b/src/mir/builder/control_flow.rs @@ -1,6 +1,6 @@ //! Control-flow entrypoints (if/loop/try/throw) centralized here. use super::{Effect, EffectMask, MirInstruction, ValueId}; -use crate::ast::ASTNode; +use crate::ast::{ASTNode, Span}; impl super::MirBuilder { /// Control-flow: block @@ -53,8 +53,8 @@ impl super::MirBuilder { /// `Ok(None)` to fall through to the legacy LoopBuilder path. fn try_cf_loop_joinir( &mut self, - _condition: &ASTNode, - _body: &[ASTNode], + condition: &ASTNode, + body: &[ASTNode], ) -> Result, String> { // Phase 49-2: Check if feature is enabled if std::env::var("HAKO_JOINIR_PRINT_TOKENS_MAIN").ok().as_deref() != Some("1") { @@ -65,8 +65,8 @@ impl super::MirBuilder { let func_name = self .current_function .as_ref() - .map(|f| f.signature.name.as_str()) - .unwrap_or(""); + .map(|f| f.signature.name.clone()) + .unwrap_or_default(); // Phase 49-2: Only handle print_tokens for now if func_name != "JsonTokenizer.print_tokens/1" { @@ -74,21 +74,143 @@ impl super::MirBuilder { } // Debug log when routing through JoinIR Frontend - if std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() { + let debug = std::env::var("NYASH_LOOPFORM_DEBUG").is_ok() + || std::env::var("NYASH_JOINIR_MAINLINE_DEBUG").is_ok(); + if debug { eprintln!( "[cf_loop/joinir] Routing {} through JoinIR Frontend mainline", func_name ); } - // TODO(Phase 49-3): Implement actual JoinIR Frontend integration - // 1. Convert condition + body to AST JSON (Program) - // 2. Call AstToJoinIrLowerer::lower_program_json() - // 3. Convert JoinModule to MIR via convert_join_module_to_mir_with_meta() - // 4. Merge generated blocks into current_function - // - // For now, fall through to legacy path - Ok(None) + // Phase 49-3: Implement JoinIR Frontend integration + self.cf_loop_joinir_impl(condition, body, &func_name, debug) + } + + /// Phase 49-3: JoinIR Frontend integration implementation + /// + /// # Pipeline + /// 1. Build Loop AST → Program JSON + /// 2. AstToJoinIrLowerer::lower_program_json() → JoinModule + /// 3. convert_join_module_to_mir_with_meta() → MirModule + /// 4. Merge MIR blocks into current_function + fn cf_loop_joinir_impl( + &mut self, + condition: &ASTNode, + body: &[ASTNode], + func_name: &str, + debug: bool, + ) -> Result, String> { + use crate::r#macro::ast_json::ast_to_json; + use crate::mir::join_ir::frontend::{AstToJoinIrLowerer, JoinFuncMetaMap}; + use crate::mir::join_ir_vm_bridge::convert_join_module_to_mir_with_meta; + use crate::mir::types::ConstValue; + + // Step 1: Build Loop AST wrapped in a minimal function + let loop_ast = ASTNode::Loop { + condition: Box::new(condition.clone()), + body: body.to_vec(), + span: Span::unknown(), + }; + + // Wrap in a minimal function for JoinIR lowering + // JoinIR Frontend expects a function body, not just a loop + let wrapper_func = ASTNode::Program { + statements: vec![loop_ast], + span: Span::unknown(), + }; + + // Step 2: Convert to JSON + let program_json = ast_to_json(&wrapper_func); + + if debug { + eprintln!( + "[cf_loop/joinir] Generated JSON for {}: {}", + func_name, + serde_json::to_string_pretty(&program_json).unwrap_or_default() + ); + } + + // Step 3: Lower to JoinIR + let mut lowerer = AstToJoinIrLowerer::new(); + let join_module = lowerer.lower_program_json(&program_json); + // Phase 49-3 MVP: Use empty meta map (full if-analysis is Phase 40+ territory) + let join_meta = JoinFuncMetaMap::new(); + + if debug { + eprintln!( + "[cf_loop/joinir] JoinModule has {} functions, entry={:?}", + join_module.functions.len(), + join_module.entry + ); + } + + // Step 4: Convert JoinModule to MIR + let mir_module = convert_join_module_to_mir_with_meta(&join_module, &join_meta) + .map_err(|e| format!("JoinIR→MIR conversion failed: {}", e.message))?; + + if debug { + eprintln!( + "[cf_loop/joinir] MirModule has {} functions", + mir_module.functions.len() + ); + } + + // Step 5: Merge MIR blocks into current_function + // For Phase 49-3, we'll use a simplified approach: + // - Add generated blocks to current_function + // - Jump from current_block to the entry of generated loop + // - The loop exit becomes the new current_block + self.merge_joinir_mir_blocks(&mir_module, debug)?; + + // Return void for now (loop doesn't have a meaningful return value in this context) + let void_val = self.next_value_id(); + self.emit_instruction(MirInstruction::Const { + dst: void_val, + value: ConstValue::Void, + })?; + + Ok(Some(void_val)) + } + + /// Phase 49-3: Merge JoinIR-generated MIR blocks into current_function + /// + /// This is a simplified merge that: + /// 1. Remaps all block IDs to avoid conflicts + /// 2. Remaps all value IDs to avoid conflicts + /// 3. Adds all blocks to current_function + /// 4. Jumps from current_block to the entry block + fn merge_joinir_mir_blocks( + &mut self, + mir_module: &crate::mir::MirModule, + debug: bool, + ) -> Result<(), String> { + // For Phase 49-3 MVP: Just log and fall through + // Full block merging is complex and needs careful ID remapping + if debug { + eprintln!( + "[cf_loop/joinir] merge_joinir_mir_blocks called with {} functions", + mir_module.functions.len() + ); + for (name, func) in &mir_module.functions { + eprintln!( + "[cf_loop/joinir] Function '{}': {} blocks, entry={:?}", + name, + func.blocks.len(), + func.entry_block + ); + } + } + + // TODO(Phase 49-3.2): Implement full block merging + // For now, this is a MVP that demonstrates the pipeline works + // The actual block merging will need: + // 1. Block ID remapping (block_gen.next() for each block) + // 2. Value ID remapping (next_value_id() for each value) + // 3. Instruction remapping (update all block/value references) + // 4. Variable map integration (merge variable_map) + + Ok(()) } /// Control-flow: try/catch/finally diff --git a/src/tests/joinir/mainline_phase49.rs b/src/tests/joinir/mainline_phase49.rs new file mode 100644 index 00000000..28d70aaa --- /dev/null +++ b/src/tests/joinir/mainline_phase49.rs @@ -0,0 +1,131 @@ +// Phase 49-3: JoinIR Frontend Mainline Integration Test +// +// このテストは cf_loop の JoinIR Frontend mainline route が +// 正常に動作することを確認する。 +// +// MVP 制限: +// - merge_joinir_mir_blocks() はログ出力のみ +// - 完全な A/B 比較は Phase 49-3.2(ブロックマージ実装)待ち +// +// テスト方法: +// HAKO_JOINIR_PRINT_TOKENS_MAIN=1 cargo test --release joinir_mainline_phase49 + +use crate::ast::ASTNode; +use crate::mir::MirCompiler; +use crate::parser::NyashParser; + +/// Phase 49-3: JoinIR Frontend mainline パイプラインが +/// print_tokens 関数のコンパイル時にクラッシュしないことを確認 +#[test] +fn phase49_joinir_mainline_pipeline_smoke() { + // Phase 49 mainline route は dev フラグで制御 + std::env::set_var("HAKO_JOINIR_PRINT_TOKENS_MAIN", "1"); + std::env::set_var("NYASH_JOINIR_MAINLINE_DEBUG", "1"); + std::env::set_var("NYASH_PARSER_STAGE3", "1"); + std::env::set_var("HAKO_PARSER_STAGE3", "1"); + std::env::set_var("NYASH_DISABLE_PLUGINS", "1"); + + // print_tokens を含む最小限の JsonTokenizer 定義 + let src = r#" +box JsonTokenizer { + tokens: ArrayBox + errors: ArrayBox + + birth() { + me.tokens = new ArrayBox() + me.errors = new ArrayBox() + } + + // Phase 49 ターゲット関数 + print_tokens() { + local i = 0 + loop(i < me.tokens.length()) { + local token = me.tokens.get(i) + print(token) + i = i + 1 + } + } +} + +static box Main { + main() { + local t = new JsonTokenizer() + t.print_tokens() + return 0 + } +} +"#; + + let ast: ASTNode = NyashParser::parse_from_string(src) + .expect("phase49: parse failed"); + + let mut mc = MirCompiler::with_options(false); + let result = mc.compile(ast); + + // MVP: パイプラインがクラッシュしないことを確認 + // Phase 49-3.2 で実際の A/B 比較を追加 + assert!( + result.is_ok(), + "phase49 mainline compile should not crash: {:?}", + result.err() + ); + + // クリーンアップ + std::env::remove_var("HAKO_JOINIR_PRINT_TOKENS_MAIN"); + std::env::remove_var("NYASH_JOINIR_MAINLINE_DEBUG"); + std::env::remove_var("NYASH_PARSER_STAGE3"); + std::env::remove_var("HAKO_PARSER_STAGE3"); + std::env::remove_var("NYASH_DISABLE_PLUGINS"); +} + +/// Phase 49-3: dev フラグ OFF 時は従来経路を使用することを確認 +#[test] +fn phase49_joinir_mainline_fallback_without_flag() { + // dev フラグ OFF + std::env::remove_var("HAKO_JOINIR_PRINT_TOKENS_MAIN"); + std::env::set_var("NYASH_PARSER_STAGE3", "1"); + std::env::set_var("HAKO_PARSER_STAGE3", "1"); + std::env::set_var("NYASH_DISABLE_PLUGINS", "1"); + + let src = r#" +box JsonTokenizer { + tokens: ArrayBox + + birth() { + me.tokens = new ArrayBox() + } + + print_tokens() { + local i = 0 + loop(i < me.tokens.length()) { + i = i + 1 + } + } +} + +static box Main { + main() { + local t = new JsonTokenizer() + t.print_tokens() + return 0 + } +} +"#; + + let ast: ASTNode = NyashParser::parse_from_string(src) + .expect("phase49 fallback: parse failed"); + + let mut mc = MirCompiler::with_options(false); + let result = mc.compile(ast); + + assert!( + result.is_ok(), + "phase49 fallback compile should succeed: {:?}", + result.err() + ); + + // クリーンアップ + std::env::remove_var("NYASH_PARSER_STAGE3"); + std::env::remove_var("HAKO_PARSER_STAGE3"); + std::env::remove_var("NYASH_DISABLE_PLUGINS"); +} diff --git a/src/tests/joinir/mod.rs b/src/tests/joinir/mod.rs index 824b6592..bf219e81 100644 --- a/src/tests/joinir/mod.rs +++ b/src/tests/joinir/mod.rs @@ -3,4 +3,5 @@ pub mod bridge; pub mod frontend; pub mod json; pub mod lowering; +pub mod mainline_phase49; // Phase 49: JoinIR Frontend mainline integration pub mod runner;