//! JoinIR 実験用のミニ実行器(Phase 27.2) //! //! 目的: hand-written / minimal JoinIR を VM と A/B 比較するための軽量ランナー。 //! - 対応値: i64 / bool / String / Unit //! - 対応命令: Const / BinOp / Compare / BoxCall(StringBox: length, substring) / //! Call / Jump / Ret use std::collections::HashMap; use crate::mir::join_ir::{ BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinInst, JoinModule, MirLikeInst, VarId, }; #[derive(Debug, Clone, PartialEq)] pub enum JoinValue { Int(i64), Bool(bool), Str(String), Unit, } #[derive(Debug, Clone)] pub struct JoinRuntimeError { pub message: String, } impl JoinRuntimeError { fn new(msg: impl Into) -> Self { Self { message: msg.into(), } } } impl std::fmt::Display for JoinRuntimeError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.message) } } impl std::error::Error for JoinRuntimeError {} pub fn run_joinir_function( module: &JoinModule, entry: JoinFuncId, args: &[JoinValue], ) -> Result { execute_function(module, entry, args.to_vec()) } fn execute_function( 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(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(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); } } } // fallthrough without explicit return return Ok(JoinValue::Unit); } } fn eval_compute(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 } => { let l = read_var(locals, *lhs)?; let r = read_var(locals, *rhs)?; let v = match op { BinOpKind::Add => match (l, r) { (JoinValue::Int(a), JoinValue::Int(b)) => JoinValue::Int(a + b), (JoinValue::Str(a), JoinValue::Str(b)) => JoinValue::Str(format!("{a}{b}")), _ => { return Err(JoinRuntimeError::new( "Add supported only for (int,int) or (str,str)", )) } }, BinOpKind::Sub => match (l, r) { (JoinValue::Int(a), JoinValue::Int(b)) => JoinValue::Int(a - b), _ => return Err(JoinRuntimeError::new("Sub supported only for integers")), }, BinOpKind::Mul => match (l, r) { (JoinValue::Int(a), JoinValue::Int(b)) => JoinValue::Int(a * b), _ => return Err(JoinRuntimeError::new("Mul supported only for integers")), }, BinOpKind::Div => match (l, r) { (JoinValue::Int(_), JoinValue::Int(0)) => { return Err(JoinRuntimeError::new("Division by zero")) } (JoinValue::Int(a), JoinValue::Int(b)) => JoinValue::Int(a / b), _ => return Err(JoinRuntimeError::new("Div supported only for integers")), }, BinOpKind::Or => match (l, r) { (JoinValue::Bool(a), JoinValue::Bool(b)) => JoinValue::Bool(a || b), _ => return Err(JoinRuntimeError::new("Or supported only for bools")), }, BinOpKind::And => match (l, r) { (JoinValue::Bool(a), JoinValue::Bool(b)) => JoinValue::Bool(a && b), _ => return Err(JoinRuntimeError::new("And supported only for bools")), }, }; locals.insert(*dst, v); } MirLikeInst::Compare { dst, op, lhs, rhs } => { let l = read_var(locals, *lhs)?; let r = read_var(locals, *rhs)?; let v = match (l, r) { (JoinValue::Int(a), JoinValue::Int(b)) => match op { CompareOp::Lt => a < b, CompareOp::Le => a <= b, CompareOp::Gt => a > b, CompareOp::Ge => a >= b, CompareOp::Eq => a == b, CompareOp::Ne => a != b, }, (JoinValue::Bool(a), JoinValue::Bool(b)) => match op { CompareOp::Eq => a == b, CompareOp::Ne => a != b, _ => { return Err(JoinRuntimeError::new( "Bool comparison only supports Eq/Ne in the JoinIR runner", )) } }, (JoinValue::Str(a), JoinValue::Str(b)) => match op { CompareOp::Eq => a == b, CompareOp::Ne => a != b, _ => { return Err(JoinRuntimeError::new( "String comparison only supports Eq/Ne in the JoinIR runner", )) } }, _ => { return Err(JoinRuntimeError::new( "Type mismatch in Compare (expected homogeneous operands)", )) } }; locals.insert(*dst, JoinValue::Bool(v)); } MirLikeInst::BoxCall { dst, box_name, method, args, } => { if box_name != "StringBox" { return Err(JoinRuntimeError::new(format!( "Unsupported box call target: {}", box_name ))); } match method.as_str() { "length" => { let arg = expect_str(&read_var(locals, args[0])?)?; locals.insert(*dst.as_ref().ok_or_else(|| { JoinRuntimeError::new("length call requires destination") })?, JoinValue::Int(arg.len() as i64)); } "substring" => { if args.len() != 3 { return Err(JoinRuntimeError::new( "substring expects 3 arguments (s, start, end)", )); } let s = expect_str(&read_var(locals, args[0])?)?; let start = expect_int(&read_var(locals, args[1])?)?; let end = expect_int(&read_var(locals, args[2])?)?; let slice = safe_substring(&s, start, end)?; let dst_var = dst.ok_or_else(|| { JoinRuntimeError::new("substring call requires destination") })?; locals.insert(dst_var, JoinValue::Str(slice)); } _ => { return Err(JoinRuntimeError::new(format!( "Unsupported StringBox method: {}", method ))) } } } } Ok(()) } fn safe_substring(s: &str, start: i64, end: i64) -> Result { if start < 0 || end < 0 { return Err(JoinRuntimeError::new("substring indices must be non-negative")); } let (start_usize, end_usize) = (start as usize, end as usize); if start_usize > end_usize { return Err(JoinRuntimeError::new("substring start > end")); } if start_usize > s.len() || end_usize > s.len() { return Err(JoinRuntimeError::new("substring indices out of bounds")); } Ok(s[start_usize..end_usize].to_string()) } 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 ))), } } fn expect_int(value: &JoinValue) -> Result { match value { JoinValue::Int(i) => Ok(*i), other => Err(JoinRuntimeError::new(format!( "Expected int, got {:?}", other ))), } } fn expect_str(value: &JoinValue) -> Result { match value { JoinValue::Str(s) => Ok(s.clone()), other => Err(JoinRuntimeError::new(format!( "Expected string, got {:?}", other ))), } }