diff --git a/src/mir/join_ir_vm_bridge.rs b/src/mir/join_ir_vm_bridge.rs index fcfba1c3..56163876 100644 --- a/src/mir/join_ir_vm_bridge.rs +++ b/src/mir/join_ir_vm_bridge.rs @@ -151,8 +151,9 @@ fn convert_joinir_to_mir( fn convert_join_function_to_mir( join_func: &crate::mir::join_ir::JoinFunction, ) -> Result { - // Phase 27-shortterm S-4.3 最小実装: - // 1つの basic block に全命令を配置 + // Phase 27-shortterm S-4.4: skip_ws パターン対応版 + // - Call (tail call): MIR Call に変換 + // - Jump (conditional exit): Branch + Return に変換 let entry_block = BasicBlockId(0); // Create minimal FunctionSignature for JoinIR function @@ -170,44 +171,141 @@ fn convert_join_function_to_mir( let mut mir_func = MirFunction::new(signature, entry_block); - let mut instructions = Vec::new(); + // Phase 27-shortterm S-4.4: Multi-block conversion for Jump instructions + // Strategy: + // - Accumulate Compute instructions in current block + // - On Jump: emit Branch + create exit block with Return + // - On Call: emit Call in current block + let mut current_block_id = entry_block; + let mut current_instructions = Vec::new(); + let mut next_block_id = 1u32; // for creating new blocks - // JoinInst → MirInstruction 変換 for join_inst in &join_func.body { match join_inst { JoinInst::Compute(mir_like) => { let mir_inst = convert_mir_like_inst(mir_like)?; - instructions.push(mir_inst); + current_instructions.push(mir_inst); } - JoinInst::Call { func, args, dst, .. } => { - // TODO: S-4.4 で関数呼び出し実装 - eprintln!( - "[joinir_vm_bridge] WARNING: Call instruction not yet implemented (func={:?}, args={:?}, dst={:?})", - func, args, dst - ); + JoinInst::Call { func, args, dst, k_next } => { + // Phase 27-shortterm S-4.4-A: skip_ws pattern only (tail calls) + // Validate: dst=None, k_next=None (tail call) + if dst.is_some() || k_next.is_some() { + return Err(JoinIrVmBridgeError::new(format!( + "Non-tail Call not supported (dst={:?}, k_next={:?}). Only skip_ws tail call pattern is supported.", + dst, k_next + ))); + } + + // Convert JoinFuncId to function name + // For skip_ws: func=0 → "main", func=1 → "join_func_1" + let func_name = if func.0 == 0 { + "main".to_string() + } else { + format!("join_func_{}", func.0) + }; + + eprintln!("[joinir_vm_bridge] Converting Call to function '{}' with {} args", func_name, args.len()); + + // Create a temporary ValueId to hold the function name (string constant) + // This is a workaround for MIR's string-based Call resolution + // In Phase 27-shortterm, we use a high ValueId to avoid conflicts + let func_name_id = ValueId(9999); + + // Add Const instruction for function name + current_instructions.push(MirInstruction::Const { + dst: func_name_id, + value: MirConstValue::String(func_name.clone()), + }); + + // Create Call instruction (legacy string-based) + // Phase 27-shortterm: No Callee yet, use func field with string ValueId + current_instructions.push(MirInstruction::Call { + dst: None, // tail call, no return value captured + func: func_name_id, + callee: None, // Phase 27-shortterm: no Callee resolution + args: args.clone(), + effects: EffectMask::PURE, + }); } JoinInst::Jump { cont, args, cond } => { - // TODO: S-4.4 で継続実装 - eprintln!( - "[joinir_vm_bridge] WARNING: Jump instruction not yet implemented (cont={:?}, args={:?}, cond={:?})", - cont, args, cond - ); + // Phase 27-shortterm S-4.4-A: Jump with condition → Branch + Return + // Jump represents an exit continuation (k_exit) in skip_ws pattern + + eprintln!("[joinir_vm_bridge] Converting Jump to cont={:?}, args={:?}, cond={:?}", cont, args, cond); + + match cond { + Some(cond_var) => { + // Conditional jump: Branch to exit block + let exit_block_id = BasicBlockId(next_block_id); + next_block_id += 1; + let continue_block_id = BasicBlockId(next_block_id); + next_block_id += 1; + + // Emit Branch in current block + current_instructions.push(MirInstruction::Branch { + condition: *cond_var, + then_bb: exit_block_id, + else_bb: continue_block_id, + }); + + // Finalize current block + if let Some(block) = mir_func.blocks.get_mut(¤t_block_id) { + block.instructions = current_instructions; + } + + // Create exit block with Return + let exit_value = args.first().copied(); + let mut exit_block = crate::mir::BasicBlock::new(exit_block_id); + exit_block.instructions.push(MirInstruction::Return { + value: exit_value, + }); + mir_func.blocks.insert(exit_block_id, exit_block); + + // Create continue block (will be populated by subsequent instructions) + let continue_block = crate::mir::BasicBlock::new(continue_block_id); + mir_func.blocks.insert(continue_block_id, continue_block); + + // Continue in the next block + current_block_id = continue_block_id; + current_instructions = Vec::new(); + } + None => { + // Unconditional jump: direct Return + let exit_value = args.first().copied(); + current_instructions.push(MirInstruction::Return { + value: exit_value, + }); + + // Finalize current block + if let Some(block) = mir_func.blocks.get_mut(¤t_block_id) { + block.instructions = current_instructions; + } + + // No continuation after unconditional return + current_instructions = Vec::new(); + } + } } JoinInst::Ret { value } => { - if let Some(val_id) = value { - instructions.push(MirInstruction::Return { - value: Some(*val_id), - }); - } else { - instructions.push(MirInstruction::Return { value: None }); + current_instructions.push(MirInstruction::Return { + value: *value, + }); + + // Finalize current block + if let Some(block) = mir_func.blocks.get_mut(¤t_block_id) { + block.instructions = current_instructions; } + + current_instructions = Vec::new(); } } } - // Entry block に命令を登録 - if let Some(block) = mir_func.blocks.get_mut(&entry_block) { - block.instructions = instructions; + // Finalize any remaining instructions in the last block + if !current_instructions.is_empty() { + if let Some(block) = mir_func.blocks.get_mut(¤t_block_id) { + block.instructions = current_instructions; + } } Ok(mir_func) diff --git a/src/tests/joinir_vm_bridge_skip_ws.rs b/src/tests/joinir_vm_bridge_skip_ws.rs new file mode 100644 index 00000000..8a69b028 --- /dev/null +++ b/src/tests/joinir_vm_bridge_skip_ws.rs @@ -0,0 +1,105 @@ +// Phase 27-shortterm S-4.4: JoinIR → Rust VM Bridge A/B Test +// +// 目的: +// - JoinIR を VM ブリッジ経由で実行し、直接 VM 実行の結果と一致することを確認する +// - Route A (AST→MIR→VM) と Route C (AST→MIR→JoinIR→MIR'→VM) の比較 +// +// Test Pattern: +// - skip_ws(" abc") → 3 (leading spaces count) +// +// Implementation Status: +// - S-4.3: Basic bridge structure ✅ +// - S-4.4-A: Call/Jump instructions for skip_ws pattern ✅ +// - S-4.4-B: A/B test (this file) ✅ + +use crate::ast::ASTNode; +use crate::backend::VM; +use crate::mir::join_ir::{lower_skip_ws_to_joinir, JoinFuncId}; +use crate::mir::join_ir_ops::JoinValue; +use crate::mir::join_ir_vm_bridge::run_joinir_via_vm; +use crate::mir::MirCompiler; +use crate::parser::NyashParser; + +fn require_experiment_toggle() -> bool { + if std::env::var("NYASH_JOINIR_VM_BRIDGE") + .ok() + .as_deref() + != Some("1") + { + eprintln!( + "[joinir/vm_bridge] NYASH_JOINIR_VM_BRIDGE=1 not set, skipping VM bridge test" + ); + return false; + } + true +} + +#[test] +#[ignore] +fn joinir_vm_bridge_skip_ws_matches_direct_vm() { + if !require_experiment_toggle() { + return; + } + + std::env::set_var("NYASH_PARSER_STAGE3", "1"); + std::env::set_var("HAKO_PARSER_STAGE3", "1"); + std::env::set_var("NYASH_DISABLE_PLUGINS", "1"); + std::env::set_var("NYASH_VM_MAX_STEPS", "100000"); + + let src = std::fs::read_to_string("apps/tests/minimal_ssa_skip_ws.hako") + .expect("failed to read minimal_ssa_skip_ws.hako"); + let runner = r#" +static box Runner { + main(args) { + return Main.skip(" abc") + } +} +"#; + let full_src = format!("{src}\n{runner}"); + + let ast: ASTNode = + NyashParser::parse_from_string(&full_src).expect("skip_ws: parse failed"); + let mut mc = MirCompiler::with_options(false); + let compiled = mc.compile(ast).expect("skip_ws: MIR compile failed"); + + // Route A: AST → MIR → VM (direct) + eprintln!("[joinir_vm_bridge_test] Route A: Direct VM execution"); + std::env::set_var("NYASH_ENTRY", "Runner.main"); + let mut vm = VM::new(); + let vm_out = vm + .execute_module(&compiled.module) + .expect("skip_ws: VM execution failed"); + let vm_result = vm_out.to_string_box().value; + std::env::remove_var("NYASH_ENTRY"); + + eprintln!("[joinir_vm_bridge_test] Route A result: {}", vm_result); + + // Route C: AST → MIR → JoinIR → MIR' → VM (via bridge) + eprintln!("[joinir_vm_bridge_test] Route C: JoinIR → VM bridge execution"); + let join_module = + lower_skip_ws_to_joinir(&compiled.module).expect("lower_skip_ws_to_joinir failed"); + + let bridge_result = run_joinir_via_vm( + &join_module, + JoinFuncId::new(0), + &[JoinValue::Str(" abc".to_string())], + ) + .expect("JoinIR VM bridge failed for skip_ws"); + + eprintln!("[joinir_vm_bridge_test] Route C result: {:?}", bridge_result); + + // Assertions: Both routes should produce the same result + assert_eq!(vm_result, "3", "Route A (VM) expected to skip 3 leading spaces"); + match bridge_result { + JoinValue::Int(v) => { + assert_eq!(v, 3, "Route C (JoinIR→VM bridge) skip_ws result mismatch"); + eprintln!("[joinir_vm_bridge_test] ✅ A/B test passed: both routes returned 3"); + } + other => panic!("JoinIR VM bridge returned non-int value: {:?}", other), + } + + std::env::remove_var("NYASH_PARSER_STAGE3"); + std::env::remove_var("HAKO_PARSER_STAGE3"); + std::env::remove_var("NYASH_DISABLE_PLUGINS"); + std::env::remove_var("NYASH_VM_MAX_STEPS"); +} diff --git a/src/tests/mod.rs b/src/tests/mod.rs index fd799c8b..79a69452 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -18,6 +18,7 @@ pub mod mir_joinir_stage1_using_resolver_min; // Phase 27.12: Stage1UsingResolve pub mod mir_joinir_funcscanner_append_defs; // Phase 27.14: FuncScannerBox._append_defs JoinIR変換 pub mod joinir_runner_min; // Phase 27.2: JoinIR 実行器 A/B 比較テスト pub mod joinir_runner_standalone; // Phase 27-shortterm S-3.2: JoinIR Runner 単体テスト +pub mod joinir_vm_bridge_skip_ws; // Phase 27-shortterm S-4.4: JoinIR → Rust VM Bridge A/B Test pub mod mir_locals_ssa; pub mod mir_loopform_conditional_reassign; pub mod mir_loopform_exit_phi;