//! JoinIR Runner - Development Harness (Structure Validation Only) //! //! # Two Routes //! //! ## Route A: JoinIR→MIR→VM (Recommended SSOT) //! - Full semantic validation via MIR lowering pipeline //! - Tests should use `JoinIrFrontendTestRunner` or `run_joinir_via_vm` //! - Examples: Phase 34 tests (IfSelect, Loop, Break, Continue) //! - **Use this route for ALL semantic tests** //! //! ## Route B: Direct JoinIR Runner (Structure Validation) //! - For structure-only validation of JoinIR constructs //! - Use `run_joinir_function` only when Route A is insufficient //! - Examples: Handwritten JoinIR module tests, low-level instruction tests //! - Note: Some operations (e.g., MethodCall) may be unimplemented in Runner //! //! # Phase 35-4 Unification Strategy //! All semantic tests migrated to Route A. Route B kept only for fundamental //! structure validation that cannot be verified through MIR→VM path. //! //! # Original Purpose (Phase 27.2) //! hand-written / minimal JoinIR を VM と A/B 比較するための軽量ランナー。 //! - 対応値: i64 / bool / String / Unit //! - 対応命令: Const / BinOp / Compare / BoxCall(StringBox: length, substring) / //! Call / Jump / Ret //! //! Phase 27.8: ops box 統合 //! - JoinValue / JoinIrOpError は join_ir_ops から再エクスポート //! - eval_binop() / eval_compare() を使用(実装を一箇所に集約) use std::collections::HashMap; use crate::mir::join_ir::{ConstValue, JoinFuncId, JoinInst, JoinModule, MirLikeInst, VarId}; // Phase 27.8: ops box からの再エクスポート pub use crate::mir::join_ir_ops::{JoinIrOpError, JoinValue}; // Phase 27.8: 互換性のため JoinRuntimeError を JoinIrOpError の別名として保持 pub type JoinRuntimeError = JoinIrOpError; pub fn run_joinir_function( vm: &mut crate::backend::mir_interpreter::MirInterpreter, module: &JoinModule, entry: JoinFuncId, args: &[JoinValue], ) -> Result { execute_function(vm, module, entry, args.to_vec()) } fn execute_function( vm: &mut crate::backend::mir_interpreter::MirInterpreter, module: &JoinModule, mut current_func: JoinFuncId, mut current_args: Vec, ) -> Result { 'exec: loop { let func = module.functions.get(¤t_func).ok_or_else(|| { JoinRuntimeError::new(format!("Function {:?} not found", current_func)) })?; if func.params.len() != current_args.len() { return Err(JoinRuntimeError::new(format!( "Arity mismatch for {:?}: expected {}, got {}", func.id, func.params.len(), current_args.len() ))); } let mut locals: HashMap = HashMap::new(); for (param, arg) in func.params.iter().zip(current_args.iter()) { locals.insert(*param, arg.clone()); } let mut ip = 0usize; while ip < func.body.len() { match &func.body[ip] { JoinInst::Compute(inst) => { eval_compute(vm, inst, &mut locals)?; ip += 1; } JoinInst::Call { func: target, args, k_next, dst, } => { if k_next.is_some() { return Err(JoinRuntimeError::new( "Join continuation (k_next) is not supported in the experimental runner", )); } let resolved_args = materialize_args(args, &locals)?; if let Some(dst_var) = dst { let value = execute_function(vm, module, *target, resolved_args)?; locals.insert(*dst_var, value); ip += 1; } else { current_func = *target; current_args = resolved_args; continue 'exec; } } JoinInst::Jump { cont: _, args, cond, } => { let should_jump = match cond { Some(var) => as_bool(&read_var(&locals, *var)?)?, None => true, }; if should_jump { let ret = if let Some(first) = args.first() { read_var(&locals, *first)? } else { JoinValue::Unit }; return Ok(ret); } ip += 1; } JoinInst::Ret { value } => { let ret = match value { Some(var) => read_var(&locals, *var)?, None => JoinValue::Unit, }; return Ok(ret); } // Phase 33: Select instruction execution JoinInst::Select { dst, cond, then_val, else_val, } => { // 1. Evaluate cond (Bool or Int) let cond_value = read_var(&locals, *cond)?; eprintln!( "[SELECT DEBUG] cond={:?}, cond_value={:?}", cond, cond_value ); let cond_bool = match cond_value { JoinValue::Bool(b) => b, JoinValue::Int(i) => i != 0, // Int も許す(0=false, それ以外=true) _ => { return Err(JoinRuntimeError::new(format!( "Select: cond must be Bool or Int, got {:?}", cond_value ))) } }; // 2. Select then_val or else_val let then_value = read_var(&locals, *then_val)?; let else_value = read_var(&locals, *else_val)?; eprintln!( "[SELECT DEBUG] cond_bool={}, then_val={:?}={:?}, else_val={:?}={:?}", cond_bool, then_val, then_value, else_val, else_value ); let selected_id = if cond_bool { *then_val } else { *else_val }; let selected_value = read_var(&locals, selected_id)?; eprintln!( "[SELECT DEBUG] selected_id={:?}, selected_value={:?}", selected_id, selected_value ); // 3. Write to dst locals.insert(*dst, selected_value); ip += 1; } // Phase 33-6: IfMerge instruction execution (複数変数 PHI) JoinInst::IfMerge { cond, merges, k_next, } => { // Phase 33-6 最小実装: k_next は None のみサポート if k_next.is_some() { return Err(JoinRuntimeError::new( "IfMerge: k_next continuation is not yet supported (Phase 33-6 minimal)", )); } // 1. Evaluate cond (Bool or Int) let cond_value = read_var(&locals, *cond)?; let cond_bool = match cond_value { JoinValue::Bool(b) => b, JoinValue::Int(i) => i != 0, _ => { return Err(JoinRuntimeError::new(format!( "IfMerge: cond must be Bool or Int, got {:?}", cond_value ))) } }; // 2. 各 merge ペアについて、cond に応じて値を選択して代入 for merge in merges { let selected_id = if cond_bool { merge.then_val } else { merge.else_val }; let selected_value = read_var(&locals, selected_id)?; locals.insert(merge.dst, selected_value); } ip += 1; } // Phase 34-6: MethodCall instruction execution JoinInst::MethodCall { .. } => { // Phase 34-6: MethodCall は JoinIR Runner では未対応 // JoinIR → MIR 変換経由で VM が実行する return Err(JoinRuntimeError::new( "MethodCall is not supported in JoinIR Runner (use JoinIR→MIR→VM bridge instead)" )); } // Phase 41-4: NestedIfMerge instruction execution JoinInst::NestedIfMerge { .. } => { // Phase 41-4: NestedIfMerge は JoinIR Runner では未対応 // JoinIR → MIR 変換経由で VM が実行する return Err(JoinRuntimeError::new( "NestedIfMerge is not supported in JoinIR Runner (use JoinIR→MIR→VM bridge instead)" )); } } } // fallthrough without explicit return return Ok(JoinValue::Unit); } } fn eval_compute( vm: &mut crate::backend::mir_interpreter::MirInterpreter, inst: &MirLikeInst, locals: &mut HashMap, ) -> Result<(), JoinRuntimeError> { match inst { MirLikeInst::Const { dst, value } => { let v = match value { ConstValue::Integer(i) => JoinValue::Int(*i), ConstValue::Bool(b) => JoinValue::Bool(*b), ConstValue::String(s) => JoinValue::Str(s.clone()), ConstValue::Null => JoinValue::Unit, }; locals.insert(*dst, v); } MirLikeInst::BinOp { dst, op, lhs, rhs } => { // Phase 27.8: ops box の eval_binop() を使用 let l = read_var(locals, *lhs)?; let r = read_var(locals, *rhs)?; let v = crate::mir::join_ir_ops::eval_binop(*op, &l, &r)?; locals.insert(*dst, v); } MirLikeInst::Compare { dst, op, lhs, rhs } => { // Phase 27.8: ops box の eval_compare() を使用 let l = read_var(locals, *lhs)?; let r = read_var(locals, *rhs)?; let v = crate::mir::join_ir_ops::eval_compare(*op, &l, &r)?; locals.insert(*dst, v); } // S-5.2-improved: BoxCall → VM execute_box_call ラッパー経由 // - 制御フロー: JoinIR Runner が担当 // - Box/Plugin 実装: Rust VM に完全委譲(VM 2号機を避ける) // - VM の完全な BoxCall 意味論を使用: // * Void guards (Void.length() → 0) // * PluginBox サポート (FileBox, NetBox) // * InstanceBox policy checks // * object_fields handling // * Method re-routing (toString→str) MirLikeInst::BoxCall { dst, box_name: _, // box_name は VM が内部で判定するため不要 method, args, } => { // First argument is the receiver (box instance) if args.is_empty() { return Err(JoinRuntimeError::new( "BoxCall requires at least a receiver argument", )); } // Convert receiver to VMValue let receiver_jv = read_var(locals, args[0])?; let receiver_vm = receiver_jv.to_vm_value(); // Convert remaining arguments to VMValue let method_args_vm: Vec = args[1..] .iter() .map(|&var_id| read_var(locals, var_id).map(|jv| jv.to_vm_value())) .collect::, _>>()?; // Invoke VM's execute_box_call for complete semantics let result_vm = vm .execute_box_call(receiver_vm, method, method_args_vm) .map_err(|e| JoinRuntimeError::new(format!("BoxCall failed: {}", e)))?; // Convert result back to JoinValue let result_jv = crate::mir::join_ir_ops::JoinValue::from_vm_value(&result_vm)?; // Store result if destination is specified if let Some(dst_var) = dst { locals.insert(*dst_var, result_jv); } } } Ok(()) } fn read_var(locals: &HashMap, var: VarId) -> Result { locals .get(&var) .cloned() .ok_or_else(|| JoinRuntimeError::new(format!("Variable {:?} not bound", var))) } fn materialize_args( args: &[VarId], locals: &HashMap, ) -> Result, JoinRuntimeError> { args.iter().map(|v| read_var(locals, *v)).collect() } fn as_bool(value: &JoinValue) -> Result { match value { JoinValue::Bool(b) => Ok(*b), JoinValue::Int(i) => Ok(*i != 0), JoinValue::Unit => Ok(false), other => Err(JoinRuntimeError::new(format!( "Expected bool-compatible value, got {:?}", other ))), } } #[cfg(test)] mod tests { use super::*; use crate::backend::mir_interpreter::MirInterpreter; use crate::mir::join_ir::{ConstValue, JoinFunction, JoinModule}; use crate::mir::ValueId; #[test] fn test_select_true() { // let result = if true { 1 } else { 2 } // expected: result == 1 let mut module = JoinModule::new(); let mut func = JoinFunction::new(JoinFuncId::new(0), "test_func".to_string(), vec![]); let v_cond = ValueId(1); let v_then = ValueId(2); let v_else = ValueId(3); let v_result = ValueId(4); // const v1 = true func.body.push(JoinInst::Compute(MirLikeInst::Const { dst: v_cond, value: ConstValue::Bool(true), })); // const v2 = 1 func.body.push(JoinInst::Compute(MirLikeInst::Const { dst: v_then, value: ConstValue::Integer(1), })); // const v3 = 2 func.body.push(JoinInst::Compute(MirLikeInst::Const { dst: v_else, value: ConstValue::Integer(2), })); // select v4 = v1 ? v2 : v3 func.body.push(JoinInst::Select { dst: v_result, cond: v_cond, then_val: v_then, else_val: v_else, }); // return v4 func.body.push(JoinInst::Ret { value: Some(v_result), }); module.add_function(func); let mut vm = MirInterpreter::new(); let result = run_joinir_function(&mut vm, &module, JoinFuncId::new(0), &[]).unwrap(); assert_eq!(result, JoinValue::Int(1)); } #[test] fn test_select_false() { // let result = if false { 1 } else { 2 } // expected: result == 2 let mut module = JoinModule::new(); let mut func = JoinFunction::new(JoinFuncId::new(0), "test_func".to_string(), vec![]); let v_cond = ValueId(1); let v_then = ValueId(2); let v_else = ValueId(3); let v_result = ValueId(4); // const v1 = false func.body.push(JoinInst::Compute(MirLikeInst::Const { dst: v_cond, value: ConstValue::Bool(false), })); // const v2 = 1 func.body.push(JoinInst::Compute(MirLikeInst::Const { dst: v_then, value: ConstValue::Integer(1), })); // const v3 = 2 func.body.push(JoinInst::Compute(MirLikeInst::Const { dst: v_else, value: ConstValue::Integer(2), })); // select v4 = v1 ? v2 : v3 func.body.push(JoinInst::Select { dst: v_result, cond: v_cond, then_val: v_then, else_val: v_else, }); // return v4 func.body.push(JoinInst::Ret { value: Some(v_result), }); module.add_function(func); let mut vm = MirInterpreter::new(); let result = run_joinir_function(&mut vm, &module, JoinFuncId::new(0), &[]).unwrap(); assert_eq!(result, JoinValue::Int(2)); } #[test] fn test_select_int_cond() { // cond=Int(0) → false、Int(1) → true let mut module = JoinModule::new(); let mut func = JoinFunction::new(JoinFuncId::new(0), "test_func".to_string(), vec![]); let v_cond = ValueId(1); let v_then = ValueId(2); let v_else = ValueId(3); let v_result = ValueId(4); // const v1 = 0 (treated as false) func.body.push(JoinInst::Compute(MirLikeInst::Const { dst: v_cond, value: ConstValue::Integer(0), })); // const v2 = 100 func.body.push(JoinInst::Compute(MirLikeInst::Const { dst: v_then, value: ConstValue::Integer(100), })); // const v3 = 200 func.body.push(JoinInst::Compute(MirLikeInst::Const { dst: v_else, value: ConstValue::Integer(200), })); // select v4 = v1 ? v2 : v3 func.body.push(JoinInst::Select { dst: v_result, cond: v_cond, then_val: v_then, else_val: v_else, }); // return v4 func.body.push(JoinInst::Ret { value: Some(v_result), }); module.add_function(func); let mut vm = MirInterpreter::new(); let result = run_joinir_function(&mut vm, &module, JoinFuncId::new(0), &[]).unwrap(); assert_eq!(result, JoinValue::Int(200)); // 0 is false, so should select else } // Phase 33-6: IfMerge instruction tests #[test] fn test_if_merge_true() { // if true { x=1; y=2 } else { x=3; y=4 } // expected: x=1, y=2 let mut module = JoinModule::new(); let mut func = JoinFunction::new(JoinFuncId::new(0), "test_func".to_string(), vec![]); let v_cond = ValueId(1); let v_then_x = ValueId(2); let v_then_y = ValueId(3); let v_else_x = ValueId(4); let v_else_y = ValueId(5); let v_result_x = ValueId(6); let v_result_y = ValueId(7); // const v1 = true func.body.push(JoinInst::Compute(MirLikeInst::Const { dst: v_cond, value: ConstValue::Bool(true), })); // const v2 = 1 (then x) func.body.push(JoinInst::Compute(MirLikeInst::Const { dst: v_then_x, value: ConstValue::Integer(1), })); // const v3 = 2 (then y) func.body.push(JoinInst::Compute(MirLikeInst::Const { dst: v_then_y, value: ConstValue::Integer(2), })); // const v4 = 3 (else x) func.body.push(JoinInst::Compute(MirLikeInst::Const { dst: v_else_x, value: ConstValue::Integer(3), })); // const v5 = 4 (else y) func.body.push(JoinInst::Compute(MirLikeInst::Const { dst: v_else_y, value: ConstValue::Integer(4), })); // if_merge v1 { v6=v2; v7=v3 } else { v6=v4; v7=v5 } func.body.push(JoinInst::IfMerge { cond: v_cond, merges: vec![ crate::mir::join_ir::MergePair { dst: v_result_x, then_val: v_then_x, else_val: v_else_x, }, crate::mir::join_ir::MergePair { dst: v_result_y, then_val: v_then_y, else_val: v_else_y, }, ], k_next: None, }); // return v6 + v7 let v_sum = ValueId(8); func.body.push(JoinInst::Compute(MirLikeInst::BinOp { dst: v_sum, op: crate::mir::join_ir::BinOpKind::Add, lhs: v_result_x, rhs: v_result_y, })); func.body.push(JoinInst::Ret { value: Some(v_sum) }); module.add_function(func); let mut vm = MirInterpreter::new(); let result = run_joinir_function(&mut vm, &module, JoinFuncId::new(0), &[]).unwrap(); assert_eq!(result, JoinValue::Int(3)); // 1 + 2 = 3 } #[test] fn test_if_merge_false() { // if false { x=1; y=2 } else { x=3; y=4 } // expected: x=3, y=4 let mut module = JoinModule::new(); let mut func = JoinFunction::new(JoinFuncId::new(0), "test_func".to_string(), vec![]); let v_cond = ValueId(1); let v_then_x = ValueId(2); let v_then_y = ValueId(3); let v_else_x = ValueId(4); let v_else_y = ValueId(5); let v_result_x = ValueId(6); let v_result_y = ValueId(7); // const v1 = false func.body.push(JoinInst::Compute(MirLikeInst::Const { dst: v_cond, value: ConstValue::Bool(false), })); // const v2 = 1 (then x) func.body.push(JoinInst::Compute(MirLikeInst::Const { dst: v_then_x, value: ConstValue::Integer(1), })); // const v3 = 2 (then y) func.body.push(JoinInst::Compute(MirLikeInst::Const { dst: v_then_y, value: ConstValue::Integer(2), })); // const v4 = 3 (else x) func.body.push(JoinInst::Compute(MirLikeInst::Const { dst: v_else_x, value: ConstValue::Integer(3), })); // const v5 = 4 (else y) func.body.push(JoinInst::Compute(MirLikeInst::Const { dst: v_else_y, value: ConstValue::Integer(4), })); // if_merge v1 { v6=v2; v7=v3 } else { v6=v4; v7=v5 } func.body.push(JoinInst::IfMerge { cond: v_cond, merges: vec![ crate::mir::join_ir::MergePair { dst: v_result_x, then_val: v_then_x, else_val: v_else_x, }, crate::mir::join_ir::MergePair { dst: v_result_y, then_val: v_then_y, else_val: v_else_y, }, ], k_next: None, }); // return v6 + v7 let v_sum = ValueId(8); func.body.push(JoinInst::Compute(MirLikeInst::BinOp { dst: v_sum, op: crate::mir::join_ir::BinOpKind::Add, lhs: v_result_x, rhs: v_result_y, })); func.body.push(JoinInst::Ret { value: Some(v_sum) }); module.add_function(func); let mut vm = MirInterpreter::new(); let result = run_joinir_function(&mut vm, &module, JoinFuncId::new(0), &[]).unwrap(); assert_eq!(result, JoinValue::Int(7)); // 3 + 4 = 7 } #[test] fn test_if_merge_multiple() { // if true { x=10; y=20; z=30 } else { x=1; y=2; z=3 } // expected: x=10, y=20, z=30 → sum=60 let mut module = JoinModule::new(); let mut func = JoinFunction::new(JoinFuncId::new(0), "test_func".to_string(), vec![]); let v_cond = ValueId(1); let v_then_x = ValueId(2); let v_then_y = ValueId(3); let v_then_z = ValueId(4); let v_else_x = ValueId(5); let v_else_y = ValueId(6); let v_else_z = ValueId(7); let v_result_x = ValueId(8); let v_result_y = ValueId(9); let v_result_z = ValueId(10); // const v1 = true func.body.push(JoinInst::Compute(MirLikeInst::Const { dst: v_cond, value: ConstValue::Bool(true), })); // then values func.body.push(JoinInst::Compute(MirLikeInst::Const { dst: v_then_x, value: ConstValue::Integer(10), })); func.body.push(JoinInst::Compute(MirLikeInst::Const { dst: v_then_y, value: ConstValue::Integer(20), })); func.body.push(JoinInst::Compute(MirLikeInst::Const { dst: v_then_z, value: ConstValue::Integer(30), })); // else values func.body.push(JoinInst::Compute(MirLikeInst::Const { dst: v_else_x, value: ConstValue::Integer(1), })); func.body.push(JoinInst::Compute(MirLikeInst::Const { dst: v_else_y, value: ConstValue::Integer(2), })); func.body.push(JoinInst::Compute(MirLikeInst::Const { dst: v_else_z, value: ConstValue::Integer(3), })); // if_merge with 3 variables func.body.push(JoinInst::IfMerge { cond: v_cond, merges: vec![ crate::mir::join_ir::MergePair { dst: v_result_x, then_val: v_then_x, else_val: v_else_x, }, crate::mir::join_ir::MergePair { dst: v_result_y, then_val: v_then_y, else_val: v_else_y, }, crate::mir::join_ir::MergePair { dst: v_result_z, then_val: v_then_z, else_val: v_else_z, }, ], k_next: None, }); // return x + y + z let v_sum_xy = ValueId(11); func.body.push(JoinInst::Compute(MirLikeInst::BinOp { dst: v_sum_xy, op: crate::mir::join_ir::BinOpKind::Add, lhs: v_result_x, rhs: v_result_y, })); let v_sum_xyz = ValueId(12); func.body.push(JoinInst::Compute(MirLikeInst::BinOp { dst: v_sum_xyz, op: crate::mir::join_ir::BinOpKind::Add, lhs: v_sum_xy, rhs: v_result_z, })); func.body.push(JoinInst::Ret { value: Some(v_sum_xyz), }); module.add_function(func); let mut vm = MirInterpreter::new(); let result = run_joinir_function(&mut vm, &module, JoinFuncId::new(0), &[]).unwrap(); assert_eq!(result, JoinValue::Int(60)); // 10 + 20 + 30 = 60 } }