Files
hakorune/src/mir/join_ir_runner.rs
nyash-codex 7b0db59100 feat(joinir): Phase 53 - SELFHOST-NORM-DEV-EXPAND implementation
Expanded selfhost dev Normalized target with 2 practical P2/P3 loop variations,
strengthened structural signature axis, and implemented two-stage detection.

Key Changes:

1. Documentation (phase49-selfhost-joinir-depth2-design.md +128 lines):
   - Added Phase 53 section with candidate selection rationale
   - Documented two-stage detector strategy (structural primary + dev-only name guard)
   - Defined structural axis strengthening (carrier count/type, branch patterns)

2. Fixtures (+210 lines):
   - selfhost_args_parse_p2.program.json (60 lines): P2 with String carrier + conditional branching
   - selfhost_stmt_count_p3.program.json (150 lines): P3 with 5 carriers + multi-branch if-else

3. Structured Builders (fixtures.rs +48 lines):
   - build_selfhost_args_parse_p2_structured_for_normalized_dev()
   - build_selfhost_stmt_count_p3_structured_for_normalized_dev()

4. ShapeGuard Two-Stage Detection (shape_guard.rs +80 lines):
   - Added SelfhostArgsParseP2/SelfhostStmtCountP3 to NormalizedDevShape enum
   - Implemented is_selfhost_args_parse_p2(): P2 core family + name guard
   - Implemented is_selfhost_stmt_count_p3(): 2-10 carrier check + name guard
   - Updated capability_for_shape() mappings

5. Bridge Integration (bridge.rs +8 lines, normalized.rs +10 lines):
   - Added shape handlers delegating to existing normalizers
   - Added roundtrip reconstruction handlers

6. Entry Point Registration (ast_lowerer/mod.rs +2 lines):
   - Registered selfhost_args_parse_p2/selfhost_stmt_count_p3 as LoopFrontend routes

7. Dev VM Comparison Tests (normalized_joinir_min.rs +40 lines):
   - normalized_selfhost_args_parse_p2_vm_bridge_direct_matches_structured()
   - normalized_selfhost_stmt_count_p3_vm_bridge_direct_matches_structured()

8. Test Context Fix (dev_env.rs):
   - Added thread-local test context depth counter
   - Fixed deadlock in nested test_ctx() calls via reentrant with_dev_env_if_unset()

Structural Axis Growth:

P2 family:
- Carrier count: 1-3 (unchanged)
- NEW: Type diversity (Integer/String mixed)
- NEW: Conditional branching patterns (Eq-heavy comparisons)

P3 family:
- NEW: Carrier count upper bound: 2-10 (was 2-4)
- NEW: Multi-branch if-else (5+ branches with nested structure)
- NEW: Complex conditional patterns

Test Results:
- normalized_dev: 40/40 PASS (including 2 new tests)
- lib regression: 939 PASS, 56 ignored
- Existing behavior unchanged (normalized_dev feature-gated)

Phase 53 Achievements:
 P2/P3 each gained 1 practical variation (2 total)
 Two-stage detection: structural primary + dev-only name guard
 Structural axis expanded: 4 axes (carrier count/type/Compare/branch patterns)
 All tests PASS, no regressions
 Test context deadlock fixed (0.04s for 29 tests)

Files Modified: 14 files
Lines Added: ~516 lines (net)
Implementation: Pure additive (feature-gated)

Next Phase (54+):
- Accumulate 6+ loops per P2/P3 family
- Achieve 5+ stable structural axes
- Target < 5% false positive rate
- Then shrink/remove name guard scope
2025-12-12 16:40:20 +09:00

953 lines
34 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;
#[cfg(feature = "normalized_dev")]
use crate::config::env::joinir_dev::{current_joinir_mode, JoinIrMode};
use crate::mir::join_ir::{ConstValue, JoinFuncId, JoinInst, JoinModule, MirLikeInst, VarId};
#[cfg(feature = "normalized_dev")]
use crate::mir::join_ir::normalized::{
dev_env, normalized_dev_roundtrip_structured, shape_guard,
};
// 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> {
#[cfg(feature = "normalized_dev")]
{
// Canonical shapes always go through Normalized roundtrip regardless of mode/env.
let canonical_shapes = shape_guard::canonical_shapes(module);
if !canonical_shapes.is_empty() {
let args_vec = args.to_vec();
return dev_env::with_dev_env_if_unset(|| {
let structured = normalized_dev_roundtrip_structured(module).map_err(|msg| {
JoinRuntimeError::new(format!(
"[joinir/normalized-dev/runner] canonical roundtrip failed: {}",
msg
))
})?;
if dev_env::normalized_dev_logs_enabled() {
eprintln!(
"[joinir/normalized-dev/runner] canonical normalized roundtrip (shapes={:?}, functions={})",
canonical_shapes,
structured.functions.len()
);
}
execute_function(vm, &structured, entry, args_vec)
});
}
}
#[cfg(feature = "normalized_dev")]
match current_joinir_mode() {
JoinIrMode::NormalizedDev => {
return run_joinir_function_normalized_dev(vm, module, entry, args);
}
_ => {
// Structured-only path (default)
}
}
execute_function(vm, module, entry, args.to_vec())
}
#[cfg(feature = "normalized_dev")]
fn run_joinir_function_normalized_dev(
vm: &mut crate::backend::mir_interpreter::MirInterpreter,
module: &JoinModule,
entry: JoinFuncId,
args: &[JoinValue],
) -> Result<JoinValue, JoinRuntimeError> {
// JoinIrMode::NormalizedDev path: Structured→Normalized→Structured roundtrip
// Keep dev path opt-in and fail-fast: only Structured P1/P2 minis are supported.
dev_env::with_dev_env_if_unset(|| {
let debug = dev_env::normalized_dev_logs_enabled();
let args_vec = args.to_vec();
let shapes = shape_guard::supported_shapes(module);
if shapes.is_empty() {
if debug {
eprintln!("[joinir/normalized-dev/runner] shape unsupported; staying on Structured path");
}
return execute_function(vm, module, entry, args_vec);
}
let structured_roundtrip = normalized_dev_roundtrip_structured(module).map_err(|msg| {
JoinRuntimeError::new(format!("[joinir/normalized-dev/runner] {}", msg))
})?;
if debug {
eprintln!(
"[joinir/normalized-dev/runner] normalized roundtrip succeeded (shapes={:?}, functions={})",
shapes,
structured_roundtrip.functions.len()
);
}
execute_function(vm, &structured_roundtrip, entry, args_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> {
let verbose = crate::config::env::joinir_dev_enabled();
'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,
type_hint: _, // Phase 63-3: 実行時は未使用
} => {
// 1. Evaluate cond (Bool or Int)
let cond_value = read_var(&locals, *cond)?;
if verbose {
eprintln!(
"[joinir/runner/select] 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)?;
if verbose {
eprintln!(
"[joinir/runner/select] 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)?;
if verbose {
eprintln!(
"[joinir/runner/select] 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 56: ConditionalMethodCall instruction execution
JoinInst::ConditionalMethodCall { .. } => {
// Phase 56: ConditionalMethodCall は JoinIR Runner では未対応
// JoinIR → MIR 変換経由で VM が実行する
return Err(JoinRuntimeError::new(
"ConditionalMethodCall 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)"
));
}
// Phase 51: FieldAccess instruction execution
JoinInst::FieldAccess { .. } => {
// Phase 51: FieldAccess は JoinIR Runner では未対応
// JoinIR → MIR 変換経由で VM が実行する
return Err(JoinRuntimeError::new(
"FieldAccess is not supported in JoinIR Runner (use JoinIR→MIR→VM bridge instead)"
));
}
// Phase 51: NewBox instruction execution
JoinInst::NewBox { .. } => {
// Phase 51: NewBox は JoinIR Runner では未対応
// JoinIR → MIR 変換経由で VM が実行する
return Err(JoinRuntimeError::new(
"NewBox 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);
}
}
// Phase 56: UnaryOp
MirLikeInst::UnaryOp { dst, op, operand } => {
let operand_val = read_var(locals, *operand)?;
let result = match op {
crate::mir::join_ir::UnaryOp::Not => match operand_val {
JoinValue::Bool(b) => JoinValue::Bool(!b),
JoinValue::Int(i) => JoinValue::Bool(i == 0),
_ => {
return Err(JoinRuntimeError::new(format!(
"Cannot apply 'not' to {:?}",
operand_val
)))
}
},
crate::mir::join_ir::UnaryOp::Neg => match operand_val {
JoinValue::Int(i) => JoinValue::Int(-i),
_ => {
return Err(JoinRuntimeError::new(format!(
"Cannot apply '-' to {:?}",
operand_val
)))
}
},
};
locals.insert(*dst, result);
}
// Phase 188: Print
MirLikeInst::Print { value } => {
let val = read_var(locals, *value)?;
// Print to stdout (convert to string representation)
let output = match val {
JoinValue::Int(i) => i.to_string(),
JoinValue::Bool(b) => b.to_string(),
JoinValue::Str(s) => s,
JoinValue::Unit => "null".to_string(),
JoinValue::BoxRef(_) => "[BoxRef]".to_string(),
};
println!("{}", output);
}
// Phase 188-Impl-3: Select
MirLikeInst::Select {
dst,
cond,
then_val,
else_val,
} => {
let cond_value = read_var(locals, *cond)?;
let is_true = match cond_value {
JoinValue::Bool(b) => b,
JoinValue::Int(i) => i != 0,
_ => {
return Err(JoinRuntimeError::new(format!(
"Select condition must be Bool or Int, got {:?}",
cond_value
)))
}
};
let result = if is_true {
read_var(locals, *then_val)?
} else {
read_var(locals, *else_val)?
};
locals.insert(*dst, result);
}
}
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::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,
type_hint: None, // Phase 63-3
});
// 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,
type_hint: None, // Phase 63-3
});
// 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,
type_hint: None, // Phase 63-3
});
// 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,
type_hint: None, // Phase 63-3
},
crate::mir::join_ir::MergePair {
dst: v_result_y,
then_val: v_then_y,
else_val: v_else_y,
type_hint: None, // Phase 63-3
},
],
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,
type_hint: None, // Phase 63-3
},
crate::mir::join_ir::MergePair {
dst: v_result_y,
then_val: v_then_y,
else_val: v_else_y,
type_hint: None, // Phase 63-3
},
],
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,
type_hint: None, // Phase 63-3
},
crate::mir::join_ir::MergePair {
dst: v_result_y,
then_val: v_then_y,
else_val: v_else_y,
type_hint: None, // Phase 63-3
},
crate::mir::join_ir::MergePair {
dst: v_result_z,
then_val: v_then_z,
else_val: v_else_z,
type_hint: None, // Phase 63-3
},
],
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
}
}