Files
hakorune/src/mir/join_ir_runner.rs
nyash-codex c2fb9dab6b feat(joinir): Phase 35-5 PHI box deletion (430 lines HIGH safety)
Phase 35-5: First wave deletion
- Deleted if_body_local_merge.rs (339 lines, 0 external calls)
  - Inlined logic into PhiBuilderBox::compute_modified_names_if()
  - Absorbed into PhiBuilderBox (isolated box with no external dependencies)
- Deleted phi_invariants.rs (91 lines, moved to JoinIR Verifier)
  - Removed ensure_if_values_exist() call from phi_builder_box.rs
  - Validation responsibility transferred to JoinIR Verifier
- Updated PHI_BOX_INVENTORY.md with deletion records

Phase 35-3/4: Documentation and route unification
- Added frontend_covered column to PHI_BOX_INVENTORY.md
- Documented JoinIR Runner routes (Route A: SSOT, Route B: structure validation)
- Updated join_ir_runner.rs module docstring with clear route guidelines

Build and test status:
 cargo build --release: Clean
 Phase 34 tests: All PASS (no regression)
 JoinIR tests: joinir_frontend_if_select, test_if_merge_simple_pattern PASS

Total reduction: 430 lines (HIGH safety achieved)
Phase 36+ potential: ~3,275 lines (MEDIUM/LOW safety)

Related docs:
- docs/private/roadmap2/phases/phase-35-phi-reduction/README.md
- docs/private/roadmap2/phases/phase-35-phi-reduction-investigation-report.md
- docs/private/roadmap2/phases/phase-30-final-joinir-world/PHI_BOX_INVENTORY.md

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-28 01:18:57 +09:00

725 lines
25 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! 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<JoinValue, JoinRuntimeError> {
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<JoinValue>,
) -> Result<JoinValue, JoinRuntimeError> {
'exec: loop {
let func = module.functions.get(&current_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<VarId, JoinValue> = 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)"
));
}
}
}
// fallthrough without explicit return
return Ok(JoinValue::Unit);
}
}
fn eval_compute(
vm: &mut crate::backend::mir_interpreter::MirInterpreter,
inst: &MirLikeInst,
locals: &mut HashMap<VarId, JoinValue>,
) -> 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<crate::backend::VMValue> = args[1..]
.iter()
.map(|&var_id| read_var(locals, var_id).map(|jv| jv.to_vm_value()))
.collect::<Result<Vec<_>, _>>()?;
// 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<VarId, JoinValue>, var: VarId) -> Result<JoinValue, JoinRuntimeError> {
locals
.get(&var)
.cloned()
.ok_or_else(|| JoinRuntimeError::new(format!("Variable {:?} not bound", var)))
}
fn materialize_args(
args: &[VarId],
locals: &HashMap<VarId, JoinValue>,
) -> Result<Vec<JoinValue>, JoinRuntimeError> {
args.iter().map(|v| read_var(locals, *v)).collect()
}
fn as_bool(value: &JoinValue) -> Result<bool, JoinRuntimeError> {
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::mir::join_ir::{ConstValue, JoinFunction, JoinModule};
use crate::mir::ValueId;
use crate::backend::mir_interpreter::MirInterpreter;
#[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
}
}