refactor(joinir): Phase 260 P0.3 Phase 2 - extract 5 complex handlers
Extracts all remaining complex handlers from joinir_block_converter.rs: **handlers/call.rs** (308 lines): - handle_call() - Call + tail call processing - Phase 131 P2: Stable function name ValueId (module-global SSOT) - Phase 131 P2: Legacy jump-args metadata for tail calls - Phase 256 P1.8: Function name resolution - 5 comprehensive unit tests **handlers/jump.rs** (566 lines): - handle_jump() - Jump to continuation (conditional/unconditional) - get_continuation_name() helper (Phase 256 P1.9) - Stable ValueId allocation (JUMP_FUNC_NAME_ID_BASE = 91000) - Phase 246-EX: Legacy jump-args metadata - 5 comprehensive unit tests **handlers/conditional_method_call.rs** (413 lines): - handle_conditional_method_call() - If/Phi expansion (single variable) - 3-block pattern: cond → then (BoxCall) / else (Copy) → merge (PHI) - Uses block_allocator + terminator_builder - 4 comprehensive unit tests **handlers/if_merge.rs** (454 lines): - handle_if_merge() - If/Phi with multiple variables - Handles parallel merges (e.g., sum + count in fold operations) - Uses merge_variable_handler for copy emission - 4 comprehensive unit tests **handlers/nested_if_merge.rs** (557 lines): - handle_nested_if_merge() - Multi-level nested branches (MOST COMPLEX) - Dynamic N+3 block allocation (N levels + then + final_else + merge) - Cascading AND logic for N conditions - 4 comprehensive unit tests All handlers: - Use extracted utilities (block_allocator, call_generator, merge_variable_handler, terminator_builder, block_finalizer) - Accept finalize_fn as parameter (enables testing) - Fully tested (22 unit tests total, all passing) - Ready for integration into converter Pattern reduction: ~581 lines of complex control flow → 5 focused modules Phase 2 complete - all handlers extracted and tested. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
460
src/mir/join_ir_vm_bridge/handlers/call.rs
Normal file
460
src/mir/join_ir_vm_bridge/handlers/call.rs
Normal file
@ -0,0 +1,460 @@
|
||||
//! Call Handler - JoinIR Call instruction to MIR conversion
|
||||
//!
|
||||
//! Phase 260 P0.3: Extracted from joinir_block_converter.rs (lines 367-454)
|
||||
//!
|
||||
//! ## Responsibility
|
||||
//!
|
||||
//! Converts JoinIR Call instructions to MIR Call + Return sequences.
|
||||
//! Handles both tail calls and non-tail calls with proper block finalization.
|
||||
//!
|
||||
//! ## Key Design Points
|
||||
//!
|
||||
//! ### 1. Tail Call vs Non-Tail Call
|
||||
//!
|
||||
//! - **Non-tail call** (dst = Some): Emit Call instruction only
|
||||
//! - **Tail call** (dst = None): Emit Call + Return sequence
|
||||
//!
|
||||
//! ### 2. Phase 131 P2: Stable Function Name ValueId (Module-Global SSOT)
|
||||
//!
|
||||
//! The merge pipeline relies on `Const(String("join_func_N"))` to detect tail calls.
|
||||
//! The ValueId used for that const MUST be stable across *all* functions in the module.
|
||||
//!
|
||||
//! ```text
|
||||
//! FUNC_NAME_ID_BASE = 90000
|
||||
//! func_name_id = ValueId(90000 + func.0)
|
||||
//! ```
|
||||
//!
|
||||
//! **Collision Avoidance**: Must not conflict with `call_result_id = ValueId(99991)`
|
||||
//!
|
||||
//! ### 3. Phase 131 P2: Legacy Jump-Args Metadata for Tail Calls
|
||||
//!
|
||||
//! The merge pipeline recovers carrier/env values from the legacy jump-args path
|
||||
//! (via BasicBlock APIs) when converting Return → Jump to the exit block.
|
||||
//! Without this, tail-call blocks look like "no args", forcing fallbacks that can
|
||||
//! produce undefined ValueIds in DirectValue mode.
|
||||
//!
|
||||
//! ```rust
|
||||
//! block.set_legacy_jump_args(args.to_vec(), Some(JumpArgsLayout::CarriersOnly));
|
||||
//! ```
|
||||
//!
|
||||
//! ### 4. Phase 256 P1.8: Function Name Resolution
|
||||
//!
|
||||
//! When `func_name_map` is provided, use actual function names instead of "join_func_N".
|
||||
//! This ensures proper function resolution in the merge pipeline.
|
||||
//!
|
||||
//! ## Example Conversions
|
||||
//!
|
||||
//! ### Non-Tail Call
|
||||
//!
|
||||
//! ```text
|
||||
//! JoinIR:
|
||||
//! Call(func=JoinFuncId(5), args=[v1, v2], dst=Some(v10))
|
||||
//!
|
||||
//! MIR:
|
||||
//! %90005 = Const { value: "join_func_5" }
|
||||
//! %10 = Call { func: %90005, args: [v1, v2] }
|
||||
//! ```
|
||||
//!
|
||||
//! ### Tail Call
|
||||
//!
|
||||
//! ```text
|
||||
//! JoinIR:
|
||||
//! Call(func=JoinFuncId(5), args=[v1, v2], dst=None)
|
||||
//!
|
||||
//! MIR:
|
||||
//! %90005 = Const { value: "join_func_5" }
|
||||
//! %99991 = Call { func: %90005, args: [v1, v2] }
|
||||
//! Return %99991
|
||||
//!
|
||||
//! Block Metadata:
|
||||
//! legacy_jump_args = [v1, v2]
|
||||
//! jump_args_layout = CarriersOnly
|
||||
//! ```
|
||||
|
||||
use crate::mir::join_ir::{JoinFuncId, JoinContId};
|
||||
use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout;
|
||||
use crate::mir::{BasicBlockId, MirFunction, MirInstruction, ValueId};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use super::super::{join_func_name, JoinIrVmBridgeError};
|
||||
use super::super::call_generator;
|
||||
|
||||
/// Handle JoinIR Call instruction conversion
|
||||
///
|
||||
/// Converts a JoinIR Call to MIR Call instruction(s), handling both tail calls
|
||||
/// and non-tail calls. For tail calls, emits Call + Return and sets legacy jump-args metadata.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `mir_func` - Target MIR function to modify
|
||||
/// * `current_block_id` - ID of the block being built
|
||||
/// * `current_instructions` - Instruction vector to append to (consumed for tail calls)
|
||||
/// * `func_name_map` - Optional map from JoinFuncId to actual function names
|
||||
/// * `func` - JoinIR function ID to call
|
||||
/// * `args` - Call arguments
|
||||
/// * `dst` - Optional destination for result (None = tail call)
|
||||
/// * `k_next` - Continuation (must be None, error otherwise)
|
||||
/// * `finalize_fn` - Function to finalize blocks (called for tail calls)
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// - Returns error if k_next is Some (not yet supported)
|
||||
/// - Returns error if func_name_id collides with call_result_id (99991)
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// let mut instructions = vec![];
|
||||
/// handle_call(
|
||||
/// &mut mir_func,
|
||||
/// BasicBlockId(0),
|
||||
/// instructions,
|
||||
/// &func_name_map,
|
||||
/// &JoinFuncId(5),
|
||||
/// &[ValueId(1), ValueId(2)],
|
||||
/// &Some(ValueId(10)), // non-tail
|
||||
/// &None, // no k_next
|
||||
/// finalize_block,
|
||||
/// )?;
|
||||
/// ```
|
||||
pub fn handle_call<F>(
|
||||
mir_func: &mut MirFunction,
|
||||
current_block_id: BasicBlockId,
|
||||
mut current_instructions: Vec<MirInstruction>,
|
||||
func_name_map: &Option<BTreeMap<JoinFuncId, String>>,
|
||||
func: &JoinFuncId,
|
||||
args: &[ValueId],
|
||||
dst: &Option<ValueId>,
|
||||
k_next: &Option<JoinContId>,
|
||||
finalize_fn: F,
|
||||
) -> Result<Vec<MirInstruction>, JoinIrVmBridgeError>
|
||||
where
|
||||
F: FnOnce(&mut MirFunction, BasicBlockId, Vec<MirInstruction>, MirInstruction),
|
||||
{
|
||||
// Phase 30.x: Call conversion
|
||||
if k_next.is_some() {
|
||||
return Err(JoinIrVmBridgeError::new(
|
||||
"Call with k_next is not yet supported".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
// Phase 256 P1.8: Use actual function name if available
|
||||
let func_name = if let Some(ref map) = func_name_map {
|
||||
map.get(func).cloned().unwrap_or_else(|| join_func_name(*func))
|
||||
} else {
|
||||
join_func_name(*func)
|
||||
};
|
||||
|
||||
// Phase 131 P2: Stable function name ValueId (module-global SSOT)
|
||||
//
|
||||
// The merge pipeline relies on `Const(String("join_func_N"))` to detect tail calls.
|
||||
// The ValueId used for that const MUST be stable across *all* functions in the module.
|
||||
//
|
||||
// IMPORTANT: avoid collisions with `call_result_id = ValueId(99991)`.
|
||||
const FUNC_NAME_ID_BASE: u32 = 90000;
|
||||
let func_name_id = ValueId(FUNC_NAME_ID_BASE + func.0);
|
||||
if func_name_id == ValueId(99991) {
|
||||
return Err(JoinIrVmBridgeError::new(
|
||||
"[joinir_block] func_name_id collided with call_result_id (99991)".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
match dst {
|
||||
Some(result_dst) => {
|
||||
// Non-tail call: use call_generator to emit Const + Call
|
||||
call_generator::emit_call_pair(
|
||||
&mut current_instructions,
|
||||
func_name_id,
|
||||
*result_dst,
|
||||
&func_name,
|
||||
args,
|
||||
);
|
||||
Ok(current_instructions)
|
||||
}
|
||||
None => {
|
||||
// Tail call: emit Call + Return
|
||||
let call_result_id = ValueId(99991);
|
||||
call_generator::emit_call_pair(
|
||||
&mut current_instructions,
|
||||
func_name_id,
|
||||
call_result_id,
|
||||
&func_name,
|
||||
args,
|
||||
);
|
||||
|
||||
// Phase 131 P2: Preserve tail-call args as legacy jump-args metadata (for exit wiring)
|
||||
//
|
||||
// The merge pipeline recovers carrier/env values from the legacy jump-args path
|
||||
// (via BasicBlock APIs) when converting Return → Jump to the exit block.
|
||||
// Without this, tail-call blocks look like "no args", forcing fallbacks that can
|
||||
// produce undefined ValueIds in DirectValue mode.
|
||||
if let Some(block) = mir_func.blocks.get_mut(¤t_block_id) {
|
||||
if !block.has_legacy_jump_args() {
|
||||
block.set_legacy_jump_args(args.to_vec(), Some(JumpArgsLayout::CarriersOnly));
|
||||
}
|
||||
}
|
||||
|
||||
let terminator = MirInstruction::Return {
|
||||
value: Some(call_result_id),
|
||||
};
|
||||
finalize_fn(
|
||||
mir_func,
|
||||
current_block_id,
|
||||
current_instructions,
|
||||
terminator,
|
||||
);
|
||||
Ok(vec![]) // Instructions consumed by finalize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mir::{BasicBlock, ConstValue, EffectMask, MirType, FunctionSignature};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// Helper: Create empty MirFunction for testing
|
||||
fn create_test_mir_func() -> MirFunction {
|
||||
let signature = FunctionSignature {
|
||||
name: "test_func".to_string(),
|
||||
params: vec![],
|
||||
return_type: MirType::Void,
|
||||
effects: EffectMask::PURE,
|
||||
};
|
||||
let func = MirFunction::new(signature, BasicBlockId(0));
|
||||
// Entry block is already created by MirFunction::new
|
||||
func
|
||||
}
|
||||
|
||||
/// Mock finalize function that captures the block finalization
|
||||
fn mock_finalize(
|
||||
mir_func: &mut MirFunction,
|
||||
block_id: BasicBlockId,
|
||||
instructions: Vec<MirInstruction>,
|
||||
terminator: MirInstruction,
|
||||
) {
|
||||
if let Some(block) = mir_func.blocks.get_mut(&block_id) {
|
||||
block.instructions = instructions;
|
||||
block.instruction_spans = vec![crate::ast::Span::unknown(); block.instructions.len()];
|
||||
block.terminator = Some(terminator);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handle_call_non_tail() {
|
||||
let mut mir_func = create_test_mir_func();
|
||||
let current_block_id = BasicBlockId(0);
|
||||
let current_instructions = vec![];
|
||||
let func_name_map = None;
|
||||
|
||||
let result = handle_call(
|
||||
&mut mir_func,
|
||||
current_block_id,
|
||||
current_instructions,
|
||||
&func_name_map,
|
||||
&JoinFuncId(5),
|
||||
&[ValueId(1), ValueId(2)],
|
||||
&Some(ValueId(10)), // non-tail call
|
||||
&None,
|
||||
mock_finalize,
|
||||
);
|
||||
|
||||
assert!(result.is_ok());
|
||||
let instructions = result.unwrap();
|
||||
assert_eq!(instructions.len(), 2);
|
||||
|
||||
// Check Const instruction
|
||||
match &instructions[0] {
|
||||
MirInstruction::Const { dst, value } => {
|
||||
assert_eq!(*dst, ValueId(90005)); // FUNC_NAME_ID_BASE + 5
|
||||
if let ConstValue::String(s) = value {
|
||||
assert_eq!(s, "join_func_5");
|
||||
} else {
|
||||
panic!("Expected ConstValue::String");
|
||||
}
|
||||
}
|
||||
_ => panic!("Expected Const instruction"),
|
||||
}
|
||||
|
||||
// Check Call instruction
|
||||
match &instructions[1] {
|
||||
MirInstruction::Call {
|
||||
dst,
|
||||
func,
|
||||
callee,
|
||||
args,
|
||||
effects,
|
||||
} => {
|
||||
assert_eq!(*dst, Some(ValueId(10)));
|
||||
assert_eq!(*func, ValueId(90005));
|
||||
assert_eq!(*callee, None);
|
||||
assert_eq!(args, &[ValueId(1), ValueId(2)]);
|
||||
assert_eq!(*effects, EffectMask::PURE);
|
||||
}
|
||||
_ => panic!("Expected Call instruction"),
|
||||
}
|
||||
|
||||
// Block should not have terminator (non-tail call)
|
||||
let block = mir_func.blocks.get(¤t_block_id).unwrap();
|
||||
assert!(block.terminator.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handle_call_tail() {
|
||||
let mut mir_func = create_test_mir_func();
|
||||
let current_block_id = BasicBlockId(0);
|
||||
let current_instructions = vec![];
|
||||
let func_name_map = None;
|
||||
|
||||
let result = handle_call(
|
||||
&mut mir_func,
|
||||
current_block_id,
|
||||
current_instructions,
|
||||
&func_name_map,
|
||||
&JoinFuncId(3),
|
||||
&[ValueId(10), ValueId(20)],
|
||||
&None, // tail call
|
||||
&None,
|
||||
mock_finalize,
|
||||
);
|
||||
|
||||
assert!(result.is_ok());
|
||||
let instructions = result.unwrap();
|
||||
assert_eq!(instructions.len(), 0); // Instructions consumed by finalize
|
||||
|
||||
// Check block has been finalized
|
||||
let block = mir_func.blocks.get(¤t_block_id).unwrap();
|
||||
assert_eq!(block.instructions.len(), 2);
|
||||
|
||||
// Check Const instruction in block
|
||||
match &block.instructions[0] {
|
||||
MirInstruction::Const { dst, value } => {
|
||||
assert_eq!(*dst, ValueId(90003)); // FUNC_NAME_ID_BASE + 3
|
||||
if let ConstValue::String(s) = value {
|
||||
assert_eq!(s, "join_func_3");
|
||||
} else {
|
||||
panic!("Expected ConstValue::String");
|
||||
}
|
||||
}
|
||||
_ => panic!("Expected Const instruction"),
|
||||
}
|
||||
|
||||
// Check Call instruction in block
|
||||
match &block.instructions[1] {
|
||||
MirInstruction::Call {
|
||||
dst,
|
||||
func,
|
||||
args,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(*dst, Some(ValueId(99991)));
|
||||
assert_eq!(*func, ValueId(90003));
|
||||
assert_eq!(args, &[ValueId(10), ValueId(20)]);
|
||||
}
|
||||
_ => panic!("Expected Call instruction"),
|
||||
}
|
||||
|
||||
// Check Return terminator
|
||||
match &block.terminator {
|
||||
Some(MirInstruction::Return { value }) => {
|
||||
assert_eq!(*value, Some(ValueId(99991)));
|
||||
}
|
||||
_ => panic!("Expected Return terminator"),
|
||||
}
|
||||
|
||||
// Check legacy jump args metadata
|
||||
assert!(block.has_legacy_jump_args());
|
||||
let jump_args = block.legacy_jump_args_values().unwrap();
|
||||
let layout = block.legacy_jump_args_layout();
|
||||
assert_eq!(jump_args, &[ValueId(10), ValueId(20)]);
|
||||
assert_eq!(layout, Some(JumpArgsLayout::CarriersOnly));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handle_call_rejects_k_next() {
|
||||
let mut mir_func = create_test_mir_func();
|
||||
let current_block_id = BasicBlockId(0);
|
||||
let current_instructions = vec![];
|
||||
let func_name_map = None;
|
||||
|
||||
let result = handle_call(
|
||||
&mut mir_func,
|
||||
current_block_id,
|
||||
current_instructions,
|
||||
&func_name_map,
|
||||
&JoinFuncId(1),
|
||||
&[],
|
||||
&Some(ValueId(5)),
|
||||
&Some(JoinContId(1)), // k_next provided
|
||||
mock_finalize,
|
||||
);
|
||||
|
||||
assert!(result.is_err());
|
||||
let err = result.unwrap_err();
|
||||
assert!(err.message.contains("k_next is not yet supported"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handle_call_with_func_name_map() {
|
||||
let mut mir_func = create_test_mir_func();
|
||||
let current_block_id = BasicBlockId(0);
|
||||
let current_instructions = vec![];
|
||||
|
||||
// Create func_name_map
|
||||
let mut func_name_map = BTreeMap::new();
|
||||
func_name_map.insert(JoinFuncId(7), "my_custom_func".to_string());
|
||||
|
||||
let result = handle_call(
|
||||
&mut mir_func,
|
||||
current_block_id,
|
||||
current_instructions,
|
||||
&Some(func_name_map),
|
||||
&JoinFuncId(7),
|
||||
&[ValueId(1)],
|
||||
&Some(ValueId(100)),
|
||||
&None,
|
||||
mock_finalize,
|
||||
);
|
||||
|
||||
assert!(result.is_ok());
|
||||
let instructions = result.unwrap();
|
||||
|
||||
// Check that custom function name is used
|
||||
match &instructions[0] {
|
||||
MirInstruction::Const { value, .. } => {
|
||||
if let ConstValue::String(s) = value {
|
||||
assert_eq!(s, "my_custom_func");
|
||||
} else {
|
||||
panic!("Expected ConstValue::String");
|
||||
}
|
||||
}
|
||||
_ => panic!("Expected Const instruction"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handle_call_collision_detection() {
|
||||
let mut mir_func = create_test_mir_func();
|
||||
let current_block_id = BasicBlockId(0);
|
||||
let current_instructions = vec![];
|
||||
let func_name_map = None;
|
||||
|
||||
// JoinFuncId that would collide: 90000 + x = 99991 => x = 9991
|
||||
let result = handle_call(
|
||||
&mut mir_func,
|
||||
current_block_id,
|
||||
current_instructions,
|
||||
&func_name_map,
|
||||
&JoinFuncId(9991),
|
||||
&[],
|
||||
&Some(ValueId(50)),
|
||||
&None,
|
||||
mock_finalize,
|
||||
);
|
||||
|
||||
assert!(result.is_err());
|
||||
let err = result.unwrap_err();
|
||||
assert!(err.message.contains("collided with call_result_id"));
|
||||
}
|
||||
}
|
||||
430
src/mir/join_ir_vm_bridge/handlers/conditional_method_call.rs
Normal file
430
src/mir/join_ir_vm_bridge/handlers/conditional_method_call.rs
Normal file
@ -0,0 +1,430 @@
|
||||
//! Conditional Method Call Handler
|
||||
//!
|
||||
//! Phase 260 P0.3: Extracted from joinir_block_converter.rs (lines 243-327)
|
||||
//! Handles JoinIR ConditionalMethodCall instruction conversion.
|
||||
//!
|
||||
//! ## Pattern: if/phi expansion (3 blocks)
|
||||
//!
|
||||
//! ```text
|
||||
//! ConditionalMethodCall:
|
||||
//! if cond:
|
||||
//! then_value = receiver.method(args)
|
||||
//! else:
|
||||
//! else_value = receiver
|
||||
//! dst = phi(then_value, else_value)
|
||||
//! ```
|
||||
//!
|
||||
//! ## Control Flow Graph
|
||||
//!
|
||||
//! ```text
|
||||
//! [current_block]
|
||||
//! |
|
||||
//! | Branch(cond)
|
||||
//! +-------------+
|
||||
//! | |
|
||||
//! v v
|
||||
//! [then_block] [else_block]
|
||||
//! BoxCall Copy receiver
|
||||
//! | |
|
||||
//! +------+------+
|
||||
//! |
|
||||
//! v
|
||||
//! [merge_block]
|
||||
//! Phi(then, else)
|
||||
//! ```
|
||||
|
||||
use crate::ast::Span;
|
||||
use crate::mir::join_ir_vm_bridge::block_allocator::BlockAllocator;
|
||||
use crate::mir::join_ir_vm_bridge::terminator_builder::{
|
||||
create_branch_terminator, create_jump_terminator,
|
||||
};
|
||||
use crate::mir::join_ir_vm_bridge::JoinIrVmBridgeError;
|
||||
use crate::mir::{BasicBlock, BasicBlockId, EffectMask, MirFunction, MirInstruction, MirType, ValueId};
|
||||
|
||||
/// Handle JoinIR ConditionalMethodCall instruction
|
||||
///
|
||||
/// Phase 56: ConditionalMethodCall → if/phi expansion
|
||||
///
|
||||
/// Creates a 3-block control flow pattern:
|
||||
/// 1. Current block: Branch on condition
|
||||
/// 2. Then block: Execute method call
|
||||
/// 3. Else block: Copy receiver (no-op path)
|
||||
/// 4. Merge block: PHI to select result
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `mir_func` - Target MIR function to modify
|
||||
/// * `allocator` - Block ID allocator
|
||||
/// * `current_block_id` - Current block ID (becomes cond block)
|
||||
/// * `current_instructions` - Instructions accumulated in current block
|
||||
/// * `cond` - Condition ValueId (boolean)
|
||||
/// * `dst` - Destination ValueId for result (receives PHI output)
|
||||
/// * `receiver` - Receiver ValueId for method call
|
||||
/// * `method` - Method name string
|
||||
/// * `args` - Method arguments
|
||||
/// * `type_hint` - Optional type hint for PHI instruction
|
||||
/// * `finalize_fn` - Block finalization function (preserves PHI)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(BasicBlockId)` - New current block ID (merge_block)
|
||||
/// * `Err(...)` - Conversion error
|
||||
///
|
||||
/// # Block Creation
|
||||
///
|
||||
/// - **then_block**: BoxCall instruction + Jump to merge
|
||||
/// - **else_block**: Copy receiver + Jump to merge
|
||||
/// - **merge_block**: PHI(then_value, else_value) → dst (no terminator yet)
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// // JoinIR:
|
||||
/// ConditionalMethodCall { cond: %1, dst: %2, receiver: %3, method: "foo", args: [%4] }
|
||||
///
|
||||
/// // MIR:
|
||||
/// bb0: // current_block
|
||||
/// Branch(%1, then=bb1, else=bb2)
|
||||
///
|
||||
/// bb1: // then_block
|
||||
/// %5 = BoxCall(%3, "foo", [%4])
|
||||
/// Jump(bb3)
|
||||
///
|
||||
/// bb2: // else_block
|
||||
/// %6 = Copy(%3)
|
||||
/// Jump(bb3)
|
||||
///
|
||||
/// bb3: // merge_block
|
||||
/// %2 = Phi((bb1, %5), (bb2, %6))
|
||||
/// // ... continues ...
|
||||
/// ```
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn handle_conditional_method_call<F>(
|
||||
mir_func: &mut MirFunction,
|
||||
allocator: &mut BlockAllocator,
|
||||
current_block_id: BasicBlockId,
|
||||
current_instructions: Vec<MirInstruction>,
|
||||
cond: &ValueId,
|
||||
dst: &ValueId,
|
||||
receiver: &ValueId,
|
||||
method: &str,
|
||||
args: &[ValueId],
|
||||
type_hint: &Option<MirType>,
|
||||
finalize_fn: F,
|
||||
) -> Result<BasicBlockId, JoinIrVmBridgeError>
|
||||
where
|
||||
F: FnOnce(&mut MirFunction, BasicBlockId, Vec<MirInstruction>, MirInstruction),
|
||||
{
|
||||
// Phase 56: ConditionalMethodCall を if/phi に変換
|
||||
debug_log!(
|
||||
"[joinir_block] Converting ConditionalMethodCall: dst={:?}, cond={:?}",
|
||||
dst,
|
||||
cond
|
||||
);
|
||||
|
||||
// Allocate 3 blocks: then, else, merge
|
||||
let (then_block, else_block, merge_block) = allocator.allocate_three();
|
||||
|
||||
// Finalize current block with Branch terminator
|
||||
let branch_terminator = create_branch_terminator(*cond, then_block, else_block);
|
||||
finalize_fn(mir_func, current_block_id, current_instructions, branch_terminator);
|
||||
|
||||
// Allocate new ValueIds for then/else results
|
||||
let then_value = mir_func.next_value_id();
|
||||
let else_value = mir_func.next_value_id();
|
||||
|
||||
// Then block: method call
|
||||
let mut then_block_obj = BasicBlock::new(then_block);
|
||||
then_block_obj.instructions.push(MirInstruction::BoxCall {
|
||||
dst: Some(then_value),
|
||||
box_val: *receiver,
|
||||
method: method.to_string(),
|
||||
method_id: None,
|
||||
args: args.to_vec(),
|
||||
effects: EffectMask::WRITE,
|
||||
});
|
||||
then_block_obj.instruction_spans.push(Span::unknown());
|
||||
then_block_obj.set_terminator(create_jump_terminator(merge_block));
|
||||
mir_func.blocks.insert(then_block, then_block_obj);
|
||||
|
||||
// Else block: copy receiver
|
||||
let mut else_block_obj = BasicBlock::new(else_block);
|
||||
else_block_obj.instructions.push(MirInstruction::Copy {
|
||||
dst: else_value,
|
||||
src: *receiver,
|
||||
});
|
||||
else_block_obj.instruction_spans.push(Span::unknown());
|
||||
else_block_obj.set_terminator(create_jump_terminator(merge_block));
|
||||
mir_func.blocks.insert(else_block, else_block_obj);
|
||||
|
||||
// Merge block: phi for dst
|
||||
let mut merge_block_obj = BasicBlock::new(merge_block);
|
||||
merge_block_obj.instructions.push(MirInstruction::Phi {
|
||||
dst: *dst,
|
||||
inputs: vec![(then_block, then_value), (else_block, else_value)],
|
||||
type_hint: type_hint.clone(),
|
||||
});
|
||||
merge_block_obj.instruction_spans.push(Span::unknown());
|
||||
mir_func.blocks.insert(merge_block, merge_block_obj);
|
||||
|
||||
Ok(merge_block)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mir::{EffectMask, FunctionSignature, MirType};
|
||||
|
||||
fn create_test_function() -> MirFunction {
|
||||
let signature = FunctionSignature {
|
||||
name: "test".to_string(),
|
||||
params: vec![],
|
||||
return_type: MirType::Void,
|
||||
effects: EffectMask::PURE,
|
||||
};
|
||||
MirFunction::new(signature, BasicBlockId::new(0))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handle_conditional_method_call_basic() {
|
||||
let mut mir_func = create_test_function();
|
||||
let mut allocator = BlockAllocator::new(1); // start at 1 (0 is entry)
|
||||
|
||||
let current_block_id = BasicBlockId::new(0);
|
||||
let current_instructions = vec![];
|
||||
let cond = ValueId(100);
|
||||
let dst = ValueId(200);
|
||||
let receiver = ValueId(300);
|
||||
let method = "test_method";
|
||||
let args = vec![];
|
||||
|
||||
let result = handle_conditional_method_call(
|
||||
&mut mir_func,
|
||||
&mut allocator,
|
||||
current_block_id,
|
||||
current_instructions,
|
||||
&cond,
|
||||
&dst,
|
||||
&receiver,
|
||||
method,
|
||||
&args,
|
||||
&None,
|
||||
|func, block_id, insts, terminator| {
|
||||
// Finalize function mock
|
||||
let mut block = BasicBlock::new(block_id);
|
||||
block.instructions = insts;
|
||||
block.set_terminator(terminator);
|
||||
func.blocks.insert(block_id, block);
|
||||
},
|
||||
);
|
||||
|
||||
assert!(result.is_ok());
|
||||
let merge_block = result.unwrap();
|
||||
|
||||
// Verify block allocation
|
||||
assert_eq!(merge_block, BasicBlockId::new(3)); // then=1, else=2, merge=3
|
||||
|
||||
// Verify current block has Branch terminator
|
||||
let current_block = mir_func.blocks.get(¤t_block_id).unwrap();
|
||||
if let Some(MirInstruction::Branch {
|
||||
condition,
|
||||
then_bb,
|
||||
else_bb,
|
||||
..
|
||||
}) = ¤t_block.terminator
|
||||
{
|
||||
assert_eq!(*condition, ValueId(100));
|
||||
assert_eq!(*then_bb, BasicBlockId::new(1));
|
||||
assert_eq!(*else_bb, BasicBlockId::new(2));
|
||||
} else {
|
||||
panic!("Expected Branch terminator");
|
||||
}
|
||||
|
||||
// Verify then block has BoxCall
|
||||
let then_block = mir_func.blocks.get(&BasicBlockId::new(1)).unwrap();
|
||||
assert_eq!(then_block.instructions.len(), 1);
|
||||
if let MirInstruction::BoxCall {
|
||||
dst: Some(then_dst),
|
||||
box_val,
|
||||
method: method_name,
|
||||
args: method_args,
|
||||
..
|
||||
} = &then_block.instructions[0]
|
||||
{
|
||||
assert_eq!(*box_val, ValueId(300));
|
||||
assert_eq!(method_name, "test_method");
|
||||
assert_eq!(*method_args, vec![]);
|
||||
// then_dst is allocated dynamically
|
||||
assert!(then_dst.0 > 0);
|
||||
} else {
|
||||
panic!("Expected BoxCall instruction");
|
||||
}
|
||||
|
||||
// Verify else block has Copy
|
||||
let else_block = mir_func.blocks.get(&BasicBlockId::new(2)).unwrap();
|
||||
assert_eq!(else_block.instructions.len(), 1);
|
||||
if let MirInstruction::Copy { src, .. } = &else_block.instructions[0] {
|
||||
assert_eq!(*src, ValueId(300));
|
||||
} else {
|
||||
panic!("Expected Copy instruction");
|
||||
}
|
||||
|
||||
// Verify merge block has Phi
|
||||
let merge_block_obj = mir_func.blocks.get(&merge_block).unwrap();
|
||||
assert_eq!(merge_block_obj.instructions.len(), 1);
|
||||
if let MirInstruction::Phi { dst: phi_dst, inputs, .. } = &merge_block_obj.instructions[0] {
|
||||
assert_eq!(*phi_dst, ValueId(200));
|
||||
assert_eq!(inputs.len(), 2);
|
||||
assert_eq!(inputs[0].0, BasicBlockId::new(1)); // then_block
|
||||
assert_eq!(inputs[1].0, BasicBlockId::new(2)); // else_block
|
||||
} else {
|
||||
panic!("Expected Phi instruction");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handle_conditional_method_call_with_args() {
|
||||
let mut mir_func = create_test_function();
|
||||
let mut allocator = BlockAllocator::new(1);
|
||||
|
||||
let current_block_id = BasicBlockId::new(0);
|
||||
let current_instructions = vec![];
|
||||
let cond = ValueId(100);
|
||||
let dst = ValueId(200);
|
||||
let receiver = ValueId(300);
|
||||
let method = "foo";
|
||||
let args = vec![ValueId(400), ValueId(500)];
|
||||
|
||||
let result = handle_conditional_method_call(
|
||||
&mut mir_func,
|
||||
&mut allocator,
|
||||
current_block_id,
|
||||
current_instructions,
|
||||
&cond,
|
||||
&dst,
|
||||
&receiver,
|
||||
method,
|
||||
&args,
|
||||
&None,
|
||||
|func, block_id, insts, terminator| {
|
||||
let mut block = BasicBlock::new(block_id);
|
||||
block.instructions = insts;
|
||||
block.set_terminator(terminator);
|
||||
func.blocks.insert(block_id, block);
|
||||
},
|
||||
);
|
||||
|
||||
assert!(result.is_ok());
|
||||
|
||||
// Verify then block has BoxCall with args
|
||||
let then_block = mir_func.blocks.get(&BasicBlockId::new(1)).unwrap();
|
||||
if let MirInstruction::BoxCall {
|
||||
args: method_args, ..
|
||||
} = &then_block.instructions[0]
|
||||
{
|
||||
assert_eq!(*method_args, vec![ValueId(400), ValueId(500)]);
|
||||
} else {
|
||||
panic!("Expected BoxCall instruction");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handle_conditional_method_call_type_hint() {
|
||||
let mut mir_func = create_test_function();
|
||||
let mut allocator = BlockAllocator::new(1);
|
||||
|
||||
let current_block_id = BasicBlockId::new(0);
|
||||
let current_instructions = vec![];
|
||||
let cond = ValueId(100);
|
||||
let dst = ValueId(200);
|
||||
let receiver = ValueId(300);
|
||||
let method = "bar";
|
||||
let args = vec![];
|
||||
let type_hint = Some(MirType::Integer);
|
||||
|
||||
let result = handle_conditional_method_call(
|
||||
&mut mir_func,
|
||||
&mut allocator,
|
||||
current_block_id,
|
||||
current_instructions,
|
||||
&cond,
|
||||
&dst,
|
||||
&receiver,
|
||||
method,
|
||||
&args,
|
||||
&type_hint,
|
||||
|func, block_id, insts, terminator| {
|
||||
let mut block = BasicBlock::new(block_id);
|
||||
block.instructions = insts;
|
||||
block.set_terminator(terminator);
|
||||
func.blocks.insert(block_id, block);
|
||||
},
|
||||
);
|
||||
|
||||
assert!(result.is_ok());
|
||||
let merge_block = result.unwrap();
|
||||
|
||||
// Verify merge block Phi has type_hint
|
||||
let merge_block_obj = mir_func.blocks.get(&merge_block).unwrap();
|
||||
if let MirInstruction::Phi { type_hint: hint, .. } = &merge_block_obj.instructions[0] {
|
||||
assert_eq!(*hint, Some(MirType::Integer));
|
||||
} else {
|
||||
panic!("Expected Phi instruction");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handle_conditional_method_call_current_instructions_preserved() {
|
||||
let mut mir_func = create_test_function();
|
||||
let mut allocator = BlockAllocator::new(1);
|
||||
|
||||
let current_block_id = BasicBlockId::new(0);
|
||||
// Current block has some instructions before ConditionalMethodCall
|
||||
let current_instructions = vec![
|
||||
MirInstruction::Copy {
|
||||
dst: ValueId(10),
|
||||
src: ValueId(20),
|
||||
},
|
||||
MirInstruction::Copy {
|
||||
dst: ValueId(30),
|
||||
src: ValueId(40),
|
||||
},
|
||||
];
|
||||
let cond = ValueId(100);
|
||||
let dst = ValueId(200);
|
||||
let receiver = ValueId(300);
|
||||
let method = "baz";
|
||||
let args = vec![];
|
||||
|
||||
let result = handle_conditional_method_call(
|
||||
&mut mir_func,
|
||||
&mut allocator,
|
||||
current_block_id,
|
||||
current_instructions,
|
||||
&cond,
|
||||
&dst,
|
||||
&receiver,
|
||||
method,
|
||||
&args,
|
||||
&None,
|
||||
|func, block_id, insts, terminator| {
|
||||
let mut block = BasicBlock::new(block_id);
|
||||
block.instructions = insts;
|
||||
block.set_terminator(terminator);
|
||||
func.blocks.insert(block_id, block);
|
||||
},
|
||||
);
|
||||
|
||||
assert!(result.is_ok());
|
||||
|
||||
// Verify current block preserves instructions
|
||||
let current_block = mir_func.blocks.get(¤t_block_id).unwrap();
|
||||
assert_eq!(current_block.instructions.len(), 2);
|
||||
if let MirInstruction::Copy { dst, src } = ¤t_block.instructions[0] {
|
||||
assert_eq!(*dst, ValueId(10));
|
||||
assert_eq!(*src, ValueId(20));
|
||||
} else {
|
||||
panic!("Expected Copy instruction");
|
||||
}
|
||||
}
|
||||
}
|
||||
461
src/mir/join_ir_vm_bridge/handlers/if_merge.rs
Normal file
461
src/mir/join_ir_vm_bridge/handlers/if_merge.rs
Normal file
@ -0,0 +1,461 @@
|
||||
//! IfMerge Handler - JoinIR IfMerge instruction to MIR conversion
|
||||
//!
|
||||
//! Phase 260 P0.3: Extracted from joinir_block_converter.rs (lines 610-688)
|
||||
//!
|
||||
//! ## Responsibility
|
||||
//!
|
||||
//! Converts JoinIR IfMerge instructions to MIR control flow with if/phi pattern.
|
||||
//! Handles multiple variable merges simultaneously (e.g., sum and count in fold operations).
|
||||
//!
|
||||
//! ## Key Design Points
|
||||
//!
|
||||
//! ### 1. IfMerge → If/Phi Transformation (Phase 33-6)
|
||||
//!
|
||||
//! JoinIR IfMerge instructions expand to conditional control flow with merge copies:
|
||||
//!
|
||||
//! ```text
|
||||
//! JoinIR:
|
||||
//! IfMerge {
|
||||
//! cond: v100,
|
||||
//! merges: [
|
||||
//! MergePair { dst: v10, then_val: v20, else_val: v30 },
|
||||
//! MergePair { dst: v11, then_val: v21, else_val: v31 },
|
||||
//! ],
|
||||
//! k_next: None
|
||||
//! }
|
||||
//!
|
||||
//! MIR:
|
||||
//! Cond Block:
|
||||
//! Branch { condition: v100, then_bb: then_block, else_bb: else_block }
|
||||
//!
|
||||
//! Then Block:
|
||||
//! v10 = Copy v20
|
||||
//! v11 = Copy v21
|
||||
//! Jump merge_block
|
||||
//!
|
||||
//! Else Block:
|
||||
//! v10 = Copy v30
|
||||
//! v11 = Copy v31
|
||||
//! Jump merge_block
|
||||
//!
|
||||
//! Merge Block:
|
||||
//! (ready for next instruction)
|
||||
//! ```
|
||||
//!
|
||||
//! ### 2. Multiple Variable Support
|
||||
//!
|
||||
//! IfMerge can handle multiple merge pairs simultaneously, enabling efficient
|
||||
//! parallel updates (e.g., accumulator and counter in fold operations).
|
||||
//!
|
||||
//! ### 3. Block Structure
|
||||
//!
|
||||
//! ```text
|
||||
//! cond_block (current)
|
||||
//! ↓ (Branch)
|
||||
//! ├─→ then_block → merge_block
|
||||
//! └─→ else_block → merge_block
|
||||
//!
|
||||
//! Continuation: merge_block becomes new current_block
|
||||
//! ```
|
||||
//!
|
||||
//! ### 4. k_next Limitation (Phase 33-6)
|
||||
//!
|
||||
//! Currently, k_next (continuation after merge) is not supported.
|
||||
//! All IfMerge instructions must have k_next = None.
|
||||
//!
|
||||
//! ## Example Conversions
|
||||
//!
|
||||
//! ### Single Variable Merge
|
||||
//!
|
||||
//! ```text
|
||||
//! JoinIR:
|
||||
//! IfMerge { cond: v1, merges: [{ dst: v10, then_val: v20, else_val: v30 }] }
|
||||
//!
|
||||
//! MIR:
|
||||
//! Cond Block (bb0):
|
||||
//! Branch { condition: v1, then_bb: bb1, else_bb: bb2 }
|
||||
//!
|
||||
//! Then Block (bb1):
|
||||
//! v10 = Copy v20
|
||||
//! Jump bb3
|
||||
//!
|
||||
//! Else Block (bb2):
|
||||
//! v10 = Copy v30
|
||||
//! Jump bb3
|
||||
//!
|
||||
//! Merge Block (bb3):
|
||||
//! (current_block_id = bb3)
|
||||
//! ```
|
||||
//!
|
||||
//! ### Multiple Variable Merge
|
||||
//!
|
||||
//! ```text
|
||||
//! JoinIR:
|
||||
//! IfMerge {
|
||||
//! cond: v50,
|
||||
//! merges: [
|
||||
//! { dst: v100, then_val: v101, else_val: v102 },
|
||||
//! { dst: v200, then_val: v201, else_val: v202 },
|
||||
//! ]
|
||||
//! }
|
||||
//!
|
||||
//! MIR:
|
||||
//! Cond Block (bb0):
|
||||
//! Branch { condition: v50, then_bb: bb1, else_bb: bb2 }
|
||||
//!
|
||||
//! Then Block (bb1):
|
||||
//! v100 = Copy v101
|
||||
//! v200 = Copy v201
|
||||
//! Jump bb3
|
||||
//!
|
||||
//! Else Block (bb2):
|
||||
//! v100 = Copy v102
|
||||
//! v200 = Copy v202
|
||||
//! Jump bb3
|
||||
//!
|
||||
//! Merge Block (bb3):
|
||||
//! (current_block_id = bb3)
|
||||
//! ```
|
||||
|
||||
use crate::ast::Span;
|
||||
use crate::mir::join_ir::{JoinContId, MergePair};
|
||||
use crate::mir::{BasicBlock, BasicBlockId, MirFunction, MirInstruction, ValueId};
|
||||
|
||||
use super::super::JoinIrVmBridgeError;
|
||||
|
||||
/// Handle JoinIR IfMerge instruction
|
||||
///
|
||||
/// Converts IfMerge to MIR control flow with conditional branches and merge copies.
|
||||
/// Phase 33-6: Supports multiple variable merges (e.g., sum and count in fold).
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `mir_func` - Target MIR function to modify
|
||||
/// * `current_block_id` - Current block ID (will become cond block)
|
||||
/// * `current_instructions` - Instructions to finalize in cond block
|
||||
/// * `cond` - Condition ValueId for branch
|
||||
/// * `merges` - Array of MergePair (dst, then_val, else_val) for each variable
|
||||
/// * `k_next` - Optional continuation (currently not supported, must be None)
|
||||
/// * `next_block_id` - Reference to next available block ID (will be incremented by 3)
|
||||
/// * `finalize_fn` - Function to finalize blocks (signature matches finalize_block)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns the new current_block_id (merge_block) on success.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns error if k_next is Some (not yet supported).
|
||||
pub fn handle_if_merge<F>(
|
||||
mir_func: &mut MirFunction,
|
||||
current_block_id: BasicBlockId,
|
||||
current_instructions: Vec<MirInstruction>,
|
||||
cond: &ValueId,
|
||||
merges: &[MergePair],
|
||||
k_next: &Option<JoinContId>,
|
||||
next_block_id: &mut u32,
|
||||
finalize_fn: F,
|
||||
) -> Result<BasicBlockId, JoinIrVmBridgeError>
|
||||
where
|
||||
F: FnOnce(&mut MirFunction, BasicBlockId, Vec<MirInstruction>, MirInstruction),
|
||||
{
|
||||
// Phase 33-6: IfMerge → if/phi (multiple variables)
|
||||
if k_next.is_some() {
|
||||
return Err(JoinIrVmBridgeError::new(
|
||||
"IfMerge: k_next not yet supported".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if crate::config::env::joinir_test_debug_enabled() {
|
||||
eprintln!(
|
||||
"[joinir_block] Converting IfMerge: merges.len()={}",
|
||||
merges.len()
|
||||
);
|
||||
}
|
||||
|
||||
// Allocate 3 blocks: then, else, merge
|
||||
let cond_block = current_block_id;
|
||||
let then_block = BasicBlockId(*next_block_id);
|
||||
*next_block_id += 1;
|
||||
let else_block = BasicBlockId(*next_block_id);
|
||||
*next_block_id += 1;
|
||||
let merge_block = BasicBlockId(*next_block_id);
|
||||
*next_block_id += 1;
|
||||
|
||||
// Cond block: branch terminator
|
||||
let branch_terminator = MirInstruction::Branch {
|
||||
condition: *cond,
|
||||
then_bb: then_block,
|
||||
else_bb: else_block,
|
||||
then_edge_args: None,
|
||||
else_edge_args: None,
|
||||
};
|
||||
finalize_fn(mir_func, cond_block, current_instructions, branch_terminator);
|
||||
|
||||
// Then block: copy then_val for each merge
|
||||
let mut then_block_obj = BasicBlock::new(then_block);
|
||||
for merge in merges {
|
||||
then_block_obj.instructions.push(MirInstruction::Copy {
|
||||
dst: merge.dst,
|
||||
src: merge.then_val,
|
||||
});
|
||||
then_block_obj.instruction_spans.push(Span::unknown());
|
||||
}
|
||||
then_block_obj.set_terminator(MirInstruction::Jump {
|
||||
target: merge_block,
|
||||
edge_args: None,
|
||||
});
|
||||
mir_func.blocks.insert(then_block, then_block_obj);
|
||||
|
||||
// Else block: copy else_val for each merge
|
||||
let mut else_block_obj = BasicBlock::new(else_block);
|
||||
for merge in merges {
|
||||
else_block_obj.instructions.push(MirInstruction::Copy {
|
||||
dst: merge.dst,
|
||||
src: merge.else_val,
|
||||
});
|
||||
else_block_obj.instruction_spans.push(Span::unknown());
|
||||
}
|
||||
else_block_obj.set_terminator(MirInstruction::Jump {
|
||||
target: merge_block,
|
||||
edge_args: None,
|
||||
});
|
||||
mir_func.blocks.insert(else_block, else_block_obj);
|
||||
|
||||
// Merge block (empty, ready for next instruction)
|
||||
let merge_block_obj = BasicBlock::new(merge_block);
|
||||
mir_func.blocks.insert(merge_block, merge_block_obj);
|
||||
|
||||
Ok(merge_block)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mir::function::{FunctionMetadata, FunctionSignature, MirFunction};
|
||||
use crate::mir::{BasicBlock, BasicBlockId, MirInstruction, ValueId};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Mock finalize function for testing
|
||||
fn mock_finalize(
|
||||
mir_func: &mut MirFunction,
|
||||
block_id: BasicBlockId,
|
||||
instructions: Vec<MirInstruction>,
|
||||
terminator: MirInstruction,
|
||||
) {
|
||||
if let Some(block) = mir_func.blocks.get_mut(&block_id) {
|
||||
block.instructions.extend(instructions);
|
||||
block.set_terminator(terminator);
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper to create a minimal MirFunction for testing
|
||||
fn create_test_function() -> MirFunction {
|
||||
let signature = FunctionSignature {
|
||||
name: "test_func".to_string(),
|
||||
params: vec![],
|
||||
return_type: crate::mir::MirType::Void,
|
||||
effects: crate::mir::EffectMask::PURE,
|
||||
};
|
||||
let entry_block = BasicBlockId(0);
|
||||
MirFunction::new(signature, entry_block)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handle_if_merge_single_variable() {
|
||||
let mut mir_func = create_test_function();
|
||||
|
||||
let current_block = BasicBlockId(0);
|
||||
|
||||
let cond = ValueId(100);
|
||||
let dst = ValueId(10);
|
||||
let then_val = ValueId(20);
|
||||
let else_val = ValueId(30);
|
||||
|
||||
let merges = vec![MergePair {
|
||||
dst,
|
||||
then_val,
|
||||
else_val,
|
||||
type_hint: None,
|
||||
}];
|
||||
|
||||
let mut next_block_id = 1;
|
||||
let result = handle_if_merge(
|
||||
&mut mir_func,
|
||||
current_block,
|
||||
vec![],
|
||||
&cond,
|
||||
&merges,
|
||||
&None,
|
||||
&mut next_block_id,
|
||||
mock_finalize,
|
||||
);
|
||||
|
||||
assert!(result.is_ok());
|
||||
let merge_block = result.unwrap();
|
||||
assert_eq!(merge_block, BasicBlockId(3));
|
||||
assert_eq!(next_block_id, 4); // 3 blocks allocated
|
||||
|
||||
// Verify cond block has Branch terminator
|
||||
let cond_block_obj = mir_func.blocks.get(¤t_block).unwrap();
|
||||
assert!(matches!(
|
||||
cond_block_obj.terminator,
|
||||
Some(MirInstruction::Branch { .. })
|
||||
));
|
||||
|
||||
// Verify then block has Copy and Jump
|
||||
let then_block_obj = mir_func.blocks.get(&BasicBlockId(1)).unwrap();
|
||||
assert_eq!(then_block_obj.instructions.len(), 1);
|
||||
assert!(matches!(
|
||||
then_block_obj.instructions[0],
|
||||
MirInstruction::Copy { dst: d, src: s } if d == dst && s == then_val
|
||||
));
|
||||
assert!(matches!(
|
||||
then_block_obj.terminator,
|
||||
Some(MirInstruction::Jump { target: t, .. }) if t == merge_block
|
||||
));
|
||||
|
||||
// Verify else block has Copy and Jump
|
||||
let else_block_obj = mir_func.blocks.get(&BasicBlockId(2)).unwrap();
|
||||
assert_eq!(else_block_obj.instructions.len(), 1);
|
||||
assert!(matches!(
|
||||
else_block_obj.instructions[0],
|
||||
MirInstruction::Copy { dst: d, src: s } if d == dst && s == else_val
|
||||
));
|
||||
assert!(matches!(
|
||||
else_block_obj.terminator,
|
||||
Some(MirInstruction::Jump { target: t, .. }) if t == merge_block
|
||||
));
|
||||
|
||||
// Verify merge block exists and is empty
|
||||
let merge_block_obj = mir_func.blocks.get(&merge_block).unwrap();
|
||||
assert_eq!(merge_block_obj.instructions.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handle_if_merge_multiple_variables() {
|
||||
let mut mir_func = create_test_function();
|
||||
|
||||
let current_block = BasicBlockId(0);
|
||||
|
||||
let cond = ValueId(50);
|
||||
let merges = vec![
|
||||
MergePair {
|
||||
dst: ValueId(100),
|
||||
then_val: ValueId(101),
|
||||
else_val: ValueId(102),
|
||||
type_hint: None,
|
||||
},
|
||||
MergePair {
|
||||
dst: ValueId(200),
|
||||
then_val: ValueId(201),
|
||||
else_val: ValueId(202),
|
||||
type_hint: None,
|
||||
},
|
||||
];
|
||||
|
||||
let mut next_block_id = 1;
|
||||
let result = handle_if_merge(
|
||||
&mut mir_func,
|
||||
current_block,
|
||||
vec![],
|
||||
&cond,
|
||||
&merges,
|
||||
&None,
|
||||
&mut next_block_id,
|
||||
mock_finalize,
|
||||
);
|
||||
|
||||
assert!(result.is_ok());
|
||||
let _merge_block = result.unwrap();
|
||||
|
||||
// Verify then block has 2 Copy instructions
|
||||
let then_block_obj = mir_func.blocks.get(&BasicBlockId(1)).unwrap();
|
||||
assert_eq!(then_block_obj.instructions.len(), 2);
|
||||
assert!(matches!(
|
||||
then_block_obj.instructions[0],
|
||||
MirInstruction::Copy { dst: ValueId(100), src: ValueId(101) }
|
||||
));
|
||||
assert!(matches!(
|
||||
then_block_obj.instructions[1],
|
||||
MirInstruction::Copy { dst: ValueId(200), src: ValueId(201) }
|
||||
));
|
||||
|
||||
// Verify else block has 2 Copy instructions
|
||||
let else_block_obj = mir_func.blocks.get(&BasicBlockId(2)).unwrap();
|
||||
assert_eq!(else_block_obj.instructions.len(), 2);
|
||||
assert!(matches!(
|
||||
else_block_obj.instructions[0],
|
||||
MirInstruction::Copy { dst: ValueId(100), src: ValueId(102) }
|
||||
));
|
||||
assert!(matches!(
|
||||
else_block_obj.instructions[1],
|
||||
MirInstruction::Copy { dst: ValueId(200), src: ValueId(202) }
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handle_if_merge_rejects_k_next() {
|
||||
let mut mir_func = create_test_function();
|
||||
|
||||
let current_block = BasicBlockId(0);
|
||||
|
||||
let cond = ValueId(100);
|
||||
let merges = vec![MergePair {
|
||||
dst: ValueId(10),
|
||||
then_val: ValueId(20),
|
||||
else_val: ValueId(30),
|
||||
type_hint: None,
|
||||
}];
|
||||
|
||||
let k_next = Some(JoinContId(999));
|
||||
let mut next_block_id = 1;
|
||||
|
||||
let result = handle_if_merge(
|
||||
&mut mir_func,
|
||||
current_block,
|
||||
vec![],
|
||||
&cond,
|
||||
&merges,
|
||||
&k_next,
|
||||
&mut next_block_id,
|
||||
mock_finalize,
|
||||
);
|
||||
|
||||
assert!(result.is_err());
|
||||
let err = result.unwrap_err();
|
||||
assert!(err.message.contains("k_next not yet supported"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handle_if_merge_empty_merges() {
|
||||
// Edge case: no merge pairs (should still create blocks)
|
||||
let mut mir_func = create_test_function();
|
||||
|
||||
let current_block = BasicBlockId(0);
|
||||
|
||||
let cond = ValueId(100);
|
||||
let merges: Vec<MergePair> = vec![];
|
||||
|
||||
let mut next_block_id = 1;
|
||||
let result = handle_if_merge(
|
||||
&mut mir_func,
|
||||
current_block,
|
||||
vec![],
|
||||
&cond,
|
||||
&merges,
|
||||
&None,
|
||||
&mut next_block_id,
|
||||
mock_finalize,
|
||||
);
|
||||
|
||||
assert!(result.is_ok());
|
||||
|
||||
// Verify blocks exist even with empty merges
|
||||
let then_block_obj = mir_func.blocks.get(&BasicBlockId(1)).unwrap();
|
||||
assert_eq!(then_block_obj.instructions.len(), 0);
|
||||
|
||||
let else_block_obj = mir_func.blocks.get(&BasicBlockId(2)).unwrap();
|
||||
assert_eq!(else_block_obj.instructions.len(), 0);
|
||||
}
|
||||
}
|
||||
520
src/mir/join_ir_vm_bridge/handlers/jump.rs
Normal file
520
src/mir/join_ir_vm_bridge/handlers/jump.rs
Normal file
@ -0,0 +1,520 @@
|
||||
//! Jump Handler - JoinIR Jump instruction to MIR conversion
|
||||
//!
|
||||
//! Phase 260 P0.3: Extracted from joinir_block_converter.rs (lines 456-579)
|
||||
//!
|
||||
//! ## Responsibility
|
||||
//!
|
||||
//! Converts JoinIR Jump instructions to MIR tail call sequences.
|
||||
//! Handles both conditional and unconditional jumps to continuation functions.
|
||||
//!
|
||||
//! ## Key Design Points
|
||||
//!
|
||||
//! ### 1. Jump → Tail Call Transformation (Phase 256 P1.9)
|
||||
//!
|
||||
//! JoinIR Jump instructions are converted to tail calls to continuation functions:
|
||||
//!
|
||||
//! ```text
|
||||
//! JoinIR:
|
||||
//! Jump(cont=JoinContId(5), args=[v1, v2], cond=None)
|
||||
//!
|
||||
//! MIR:
|
||||
//! %91005 = Const { value: "join_func_5" }
|
||||
//! %99992 = Call { func: %91005, args: [v1, v2] }
|
||||
//! Return %99992
|
||||
//! ```
|
||||
//!
|
||||
//! ### 2. Conditional vs Unconditional Jumps
|
||||
//!
|
||||
//! - **Unconditional** (cond = None): Direct tail call in current block
|
||||
//! - **Conditional** (cond = Some): Branch to exit block (tail call) vs continue block
|
||||
//!
|
||||
//! ### 3. Stable ValueId Allocation
|
||||
//!
|
||||
//! ```text
|
||||
//! JUMP_FUNC_NAME_ID_BASE = 91000 (distinct from handle_call's 90000)
|
||||
//! func_name_id = ValueId(91000 + cont.0)
|
||||
//! call_result_id = ValueId(99992) (distinct from handle_call's 99991)
|
||||
//! ```
|
||||
//!
|
||||
//! This ensures no collision between Call and Jump instructions.
|
||||
//!
|
||||
//! ### 4. Legacy Jump-Args Metadata (Phase 246-EX)
|
||||
//!
|
||||
//! Jump args are preserved in block metadata for exit PHI construction:
|
||||
//!
|
||||
//! ```rust
|
||||
//! block.set_legacy_jump_args(args.to_vec(), Some(JumpArgsLayout::CarriersOnly));
|
||||
//! ```
|
||||
//!
|
||||
//! This metadata is used by the merge pipeline to wire up exit PHI nodes correctly.
|
||||
//!
|
||||
//! ## Example Conversions
|
||||
//!
|
||||
//! ### Unconditional Jump
|
||||
//!
|
||||
//! ```text
|
||||
//! JoinIR:
|
||||
//! Jump(cont=JoinContId(3), args=[v10, v20], cond=None)
|
||||
//!
|
||||
//! MIR (appended to current block):
|
||||
//! %91003 = Const { value: "join_func_3" }
|
||||
//! %99992 = Call { func: %91003, args: [v10, v20] }
|
||||
//! Return %99992
|
||||
//!
|
||||
//! Block Metadata:
|
||||
//! legacy_jump_args = [v10, v20]
|
||||
//! jump_args_layout = CarriersOnly
|
||||
//! ```
|
||||
//!
|
||||
//! ### Conditional Jump
|
||||
//!
|
||||
//! ```text
|
||||
//! JoinIR:
|
||||
//! Jump(cont=JoinContId(7), args=[v1, v2], cond=Some(v100))
|
||||
//!
|
||||
//! MIR:
|
||||
//! Current Block:
|
||||
//! Branch { condition: v100, then_bb: exit_block, else_bb: continue_block }
|
||||
//!
|
||||
//! Exit Block (tail call):
|
||||
//! %91007 = Const { value: "join_func_7" }
|
||||
//! %99992 = Call { func: %91007, args: [v1, v2] }
|
||||
//! Return %99992
|
||||
//!
|
||||
//! Continue Block:
|
||||
//! (empty, ready for next instruction)
|
||||
//!
|
||||
//! Exit Block Metadata:
|
||||
//! legacy_jump_args = [v1, v2]
|
||||
//! jump_args_layout = CarriersOnly
|
||||
//! ```
|
||||
|
||||
use crate::ast::Span;
|
||||
use crate::mir::join_ir::{JoinContId, JoinFuncId};
|
||||
use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout;
|
||||
use crate::mir::{BasicBlock, BasicBlockId, EffectMask, MirFunction, MirInstruction, ValueId};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use super::super::{join_func_name, JoinIrVmBridgeError};
|
||||
use super::super::call_generator;
|
||||
|
||||
/// Handle JoinIR Jump instruction conversion
|
||||
///
|
||||
/// Converts a JoinIR Jump to MIR tail call to continuation function.
|
||||
/// Handles both conditional jumps (Branch + tail call) and unconditional jumps (direct tail call).
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `mir_func` - Target MIR function to modify
|
||||
/// * `allocator` - Block allocator for creating new blocks (conditional case)
|
||||
/// * `current_block_id` - ID of the block being built
|
||||
/// * `current_instructions` - Instruction vector to append to (consumed for unconditional jumps)
|
||||
/// * `func_name_map` - Optional map from JoinFuncId to actual function names
|
||||
/// * `cont` - Continuation ID (target of jump)
|
||||
/// * `args` - Jump arguments (passed to continuation)
|
||||
/// * `cond` - Optional condition variable (Some = conditional jump, None = unconditional)
|
||||
/// * `finalize_fn` - Function to finalize blocks (called for unconditional jumps)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// New current block ID. For conditional jumps, returns the continue block.
|
||||
/// For unconditional jumps, returns the same block ID (since block is finalized).
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Currently does not return errors, but uses Result for consistency with other handlers.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// // Unconditional jump
|
||||
/// let new_block_id = handle_jump(
|
||||
/// &mut mir_func,
|
||||
/// &mut allocator,
|
||||
/// BasicBlockId(0),
|
||||
/// instructions,
|
||||
/// &func_name_map,
|
||||
/// &JoinContId(5),
|
||||
/// &[ValueId(1), ValueId(2)],
|
||||
/// &None, // unconditional
|
||||
/// finalize_block,
|
||||
/// )?;
|
||||
///
|
||||
/// // Conditional jump
|
||||
/// let new_block_id = handle_jump(
|
||||
/// &mut mir_func,
|
||||
/// &mut allocator,
|
||||
/// BasicBlockId(0),
|
||||
/// instructions,
|
||||
/// &func_name_map,
|
||||
/// &JoinContId(7),
|
||||
/// &[ValueId(10)],
|
||||
/// &Some(ValueId(100)), // conditional on v100
|
||||
/// finalize_block,
|
||||
/// )?;
|
||||
/// ```
|
||||
pub fn handle_jump<F>(
|
||||
mir_func: &mut MirFunction,
|
||||
allocator: &mut super::super::block_allocator::BlockAllocator,
|
||||
current_block_id: BasicBlockId,
|
||||
mut current_instructions: Vec<MirInstruction>,
|
||||
func_name_map: &Option<BTreeMap<JoinFuncId, String>>,
|
||||
cont: &JoinContId,
|
||||
args: &[ValueId],
|
||||
cond: &Option<ValueId>,
|
||||
finalize_fn: F,
|
||||
) -> Result<BasicBlockId, JoinIrVmBridgeError>
|
||||
where
|
||||
F: FnOnce(&mut MirFunction, BasicBlockId, Vec<MirInstruction>, MirInstruction),
|
||||
{
|
||||
// Phase 256 P1.9: Jump → tail call to continuation function
|
||||
// Previously was just `ret args[0]`, now generates `call cont(args...); ret result`
|
||||
#[cfg(debug_assertions)]
|
||||
if crate::config::env::joinir_test_debug_enabled() {
|
||||
eprintln!(
|
||||
"[joinir_block] Converting Jump to tail call: cont={:?}, args={:?}, cond={:?}",
|
||||
cont, args, cond
|
||||
);
|
||||
}
|
||||
|
||||
// Get continuation function name
|
||||
let cont_name = get_continuation_name(func_name_map, cont);
|
||||
|
||||
// Phase 256 P1.9: Use distinct ValueIds for Jump tail call
|
||||
// FUNC_NAME_ID_BASE for call targets, 99992 for Jump result (distinct from 99991 in handle_call)
|
||||
const JUMP_FUNC_NAME_ID_BASE: u32 = 91000; // Different from handle_call's 90000
|
||||
let func_name_id = ValueId(JUMP_FUNC_NAME_ID_BASE + cont.0);
|
||||
let call_result_id = ValueId(99992); // Distinct from handle_call's 99991
|
||||
|
||||
match cond {
|
||||
Some(cond_var) => {
|
||||
// Conditional jump → Branch + tail call to continuation
|
||||
let (exit_block_id, continue_block_id) = allocator.allocate_two();
|
||||
|
||||
let branch_terminator = MirInstruction::Branch {
|
||||
condition: *cond_var,
|
||||
then_bb: exit_block_id,
|
||||
else_bb: continue_block_id,
|
||||
then_edge_args: None,
|
||||
else_edge_args: None,
|
||||
};
|
||||
|
||||
finalize_fn(
|
||||
mir_func,
|
||||
current_block_id,
|
||||
current_instructions,
|
||||
branch_terminator,
|
||||
);
|
||||
|
||||
// Exit block: tail call to continuation function
|
||||
let mut exit_block = BasicBlock::new(exit_block_id);
|
||||
|
||||
// Phase 246-EX: Store Jump args in metadata for exit PHI construction
|
||||
exit_block.set_legacy_jump_args(args.to_vec(), Some(JumpArgsLayout::CarriersOnly));
|
||||
|
||||
// Phase 256 P1.9: Generate tail call to continuation
|
||||
call_generator::emit_call_pair_with_spans(
|
||||
&mut exit_block.instructions,
|
||||
&mut exit_block.instruction_spans,
|
||||
func_name_id,
|
||||
call_result_id,
|
||||
&cont_name,
|
||||
args,
|
||||
);
|
||||
exit_block.set_terminator(MirInstruction::Return {
|
||||
value: Some(call_result_id),
|
||||
});
|
||||
mir_func.blocks.insert(exit_block_id, exit_block);
|
||||
|
||||
// Continue block
|
||||
let continue_block = BasicBlock::new(continue_block_id);
|
||||
mir_func.blocks.insert(continue_block_id, continue_block);
|
||||
|
||||
Ok(continue_block_id)
|
||||
}
|
||||
None => {
|
||||
// Unconditional jump → tail call to continuation
|
||||
// Finalize current block with tail call
|
||||
call_generator::emit_call_pair(
|
||||
&mut current_instructions,
|
||||
func_name_id,
|
||||
call_result_id,
|
||||
&cont_name,
|
||||
args,
|
||||
);
|
||||
|
||||
// Preserve jump args as metadata (SSOT for ExitLine/jump_args wiring).
|
||||
if let Some(block) = mir_func.blocks.get_mut(¤t_block_id) {
|
||||
if !block.has_legacy_jump_args() {
|
||||
block.set_legacy_jump_args(
|
||||
args.to_vec(),
|
||||
Some(JumpArgsLayout::CarriersOnly),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let return_terminator = MirInstruction::Return {
|
||||
value: Some(call_result_id),
|
||||
};
|
||||
|
||||
finalize_fn(mir_func, current_block_id, current_instructions, return_terminator);
|
||||
|
||||
Ok(current_block_id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Phase 256 P1.9: Get continuation function name from func_name_map
|
||||
///
|
||||
/// Resolves a continuation ID to its function name, using the provided
|
||||
/// func_name_map if available, otherwise falling back to join_func_name.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `func_name_map` - Optional map from JoinFuncId to actual function names
|
||||
/// * `cont` - Continuation ID to resolve
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Function name as a String
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// JoinContId.0 == JoinFuncId.0 (same underlying ID via as_cont())
|
||||
fn get_continuation_name(
|
||||
func_name_map: &Option<BTreeMap<JoinFuncId, String>>,
|
||||
cont: &JoinContId,
|
||||
) -> String {
|
||||
// JoinContId.0 == JoinFuncId.0 (same underlying ID via as_cont())
|
||||
if let Some(ref map) = func_name_map {
|
||||
if let Some(name) = map.get(&JoinFuncId(cont.0)) {
|
||||
return name.clone();
|
||||
}
|
||||
}
|
||||
// Fallback: use join_func_name()
|
||||
join_func_name(JoinFuncId(cont.0))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mir::{FunctionSignature, MirType, ConstValue};
|
||||
use crate::mir::join_ir_vm_bridge::block_allocator::BlockAllocator;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
/// Helper: Create empty MirFunction for testing
|
||||
fn create_test_mir_func() -> MirFunction {
|
||||
let signature = FunctionSignature {
|
||||
name: "test_func".to_string(),
|
||||
params: vec![],
|
||||
return_type: MirType::Void,
|
||||
effects: EffectMask::PURE,
|
||||
};
|
||||
let func = MirFunction::new(signature, BasicBlockId(0));
|
||||
func
|
||||
}
|
||||
|
||||
/// Mock finalize function that captures the block finalization
|
||||
fn mock_finalize(
|
||||
mir_func: &mut MirFunction,
|
||||
block_id: BasicBlockId,
|
||||
instructions: Vec<MirInstruction>,
|
||||
terminator: MirInstruction,
|
||||
) {
|
||||
if let Some(block) = mir_func.blocks.get_mut(&block_id) {
|
||||
block.instructions = instructions;
|
||||
block.instruction_spans = vec![Span::unknown(); block.instructions.len()];
|
||||
block.terminator = Some(terminator);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handle_jump_unconditional() {
|
||||
let mut mir_func = create_test_mir_func();
|
||||
let mut allocator = BlockAllocator::new(1);
|
||||
let current_block_id = BasicBlockId(0);
|
||||
let current_instructions = vec![];
|
||||
let func_name_map = None;
|
||||
|
||||
let result = handle_jump(
|
||||
&mut mir_func,
|
||||
&mut allocator,
|
||||
current_block_id,
|
||||
current_instructions,
|
||||
&func_name_map,
|
||||
&JoinContId(3),
|
||||
&[ValueId(10), ValueId(20)],
|
||||
&None, // unconditional
|
||||
mock_finalize,
|
||||
);
|
||||
|
||||
assert!(result.is_ok());
|
||||
let new_block_id = result.unwrap();
|
||||
assert_eq!(new_block_id, current_block_id); // Same block for unconditional
|
||||
|
||||
// Check block has been finalized
|
||||
let block = mir_func.blocks.get(¤t_block_id).unwrap();
|
||||
assert_eq!(block.instructions.len(), 2);
|
||||
|
||||
// Check Const instruction
|
||||
match &block.instructions[0] {
|
||||
MirInstruction::Const { dst, value } => {
|
||||
assert_eq!(*dst, ValueId(91003)); // JUMP_FUNC_NAME_ID_BASE + 3
|
||||
if let ConstValue::String(s) = value {
|
||||
assert_eq!(s, "join_func_3");
|
||||
} else {
|
||||
panic!("Expected ConstValue::String");
|
||||
}
|
||||
}
|
||||
_ => panic!("Expected Const instruction"),
|
||||
}
|
||||
|
||||
// Check Call instruction
|
||||
match &block.instructions[1] {
|
||||
MirInstruction::Call {
|
||||
dst,
|
||||
func,
|
||||
args,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(*dst, Some(ValueId(99992)));
|
||||
assert_eq!(*func, ValueId(91003));
|
||||
assert_eq!(args, &[ValueId(10), ValueId(20)]);
|
||||
}
|
||||
_ => panic!("Expected Call instruction"),
|
||||
}
|
||||
|
||||
// Check Return terminator
|
||||
match &block.terminator {
|
||||
Some(MirInstruction::Return { value }) => {
|
||||
assert_eq!(*value, Some(ValueId(99992)));
|
||||
}
|
||||
_ => panic!("Expected Return terminator"),
|
||||
}
|
||||
|
||||
// Check legacy jump args metadata
|
||||
assert!(block.has_legacy_jump_args());
|
||||
let jump_args = block.legacy_jump_args_values().unwrap();
|
||||
let layout = block.legacy_jump_args_layout();
|
||||
assert_eq!(jump_args, &[ValueId(10), ValueId(20)]);
|
||||
assert_eq!(layout, Some(JumpArgsLayout::CarriersOnly));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handle_jump_conditional() {
|
||||
let mut mir_func = create_test_mir_func();
|
||||
let mut allocator = BlockAllocator::new(1);
|
||||
let current_block_id = BasicBlockId(0);
|
||||
let current_instructions = vec![];
|
||||
let func_name_map = None;
|
||||
|
||||
let result = handle_jump(
|
||||
&mut mir_func,
|
||||
&mut allocator,
|
||||
current_block_id,
|
||||
current_instructions,
|
||||
&func_name_map,
|
||||
&JoinContId(7),
|
||||
&[ValueId(1), ValueId(2)],
|
||||
&Some(ValueId(100)), // conditional on v100
|
||||
mock_finalize,
|
||||
);
|
||||
|
||||
assert!(result.is_ok());
|
||||
let new_block_id = result.unwrap();
|
||||
assert_eq!(new_block_id, BasicBlockId(2)); // Continue block
|
||||
|
||||
// Check current block has Branch terminator
|
||||
let current_block = mir_func.blocks.get(¤t_block_id).unwrap();
|
||||
match ¤t_block.terminator {
|
||||
Some(MirInstruction::Branch {
|
||||
condition,
|
||||
then_bb,
|
||||
else_bb,
|
||||
..
|
||||
}) => {
|
||||
assert_eq!(*condition, ValueId(100));
|
||||
assert_eq!(*then_bb, BasicBlockId(1)); // exit block
|
||||
assert_eq!(*else_bb, BasicBlockId(2)); // continue block
|
||||
}
|
||||
_ => panic!("Expected Branch terminator"),
|
||||
}
|
||||
|
||||
// Check exit block (should have tail call)
|
||||
let exit_block = mir_func.blocks.get(&BasicBlockId(1)).unwrap();
|
||||
assert_eq!(exit_block.instructions.len(), 2);
|
||||
|
||||
// Check Const instruction in exit block
|
||||
match &exit_block.instructions[0] {
|
||||
MirInstruction::Const { dst, value } => {
|
||||
assert_eq!(*dst, ValueId(91007)); // JUMP_FUNC_NAME_ID_BASE + 7
|
||||
if let ConstValue::String(s) = value {
|
||||
assert_eq!(s, "join_func_7");
|
||||
} else {
|
||||
panic!("Expected ConstValue::String");
|
||||
}
|
||||
}
|
||||
_ => panic!("Expected Const instruction"),
|
||||
}
|
||||
|
||||
// Check Call instruction in exit block
|
||||
match &exit_block.instructions[1] {
|
||||
MirInstruction::Call {
|
||||
dst,
|
||||
func,
|
||||
args,
|
||||
..
|
||||
} => {
|
||||
assert_eq!(*dst, Some(ValueId(99992)));
|
||||
assert_eq!(*func, ValueId(91007));
|
||||
assert_eq!(args, &[ValueId(1), ValueId(2)]);
|
||||
}
|
||||
_ => panic!("Expected Call instruction"),
|
||||
}
|
||||
|
||||
// Check Return terminator in exit block
|
||||
match &exit_block.terminator {
|
||||
Some(MirInstruction::Return { value }) => {
|
||||
assert_eq!(*value, Some(ValueId(99992)));
|
||||
}
|
||||
_ => panic!("Expected Return terminator"),
|
||||
}
|
||||
|
||||
// Check legacy jump args metadata in exit block
|
||||
assert!(exit_block.has_legacy_jump_args());
|
||||
let jump_args = exit_block.legacy_jump_args_values().unwrap();
|
||||
let layout = exit_block.legacy_jump_args_layout();
|
||||
assert_eq!(jump_args, &[ValueId(1), ValueId(2)]);
|
||||
assert_eq!(layout, Some(JumpArgsLayout::CarriersOnly));
|
||||
|
||||
// Check continue block exists and is empty
|
||||
let continue_block = mir_func.blocks.get(&BasicBlockId(2)).unwrap();
|
||||
assert_eq!(continue_block.instructions.len(), 0);
|
||||
assert!(continue_block.terminator.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_continuation_name_with_map() {
|
||||
let mut func_name_map = BTreeMap::new();
|
||||
func_name_map.insert(JoinFuncId(5), "my_continuation".to_string());
|
||||
|
||||
let name = get_continuation_name(&Some(func_name_map), &JoinContId(5));
|
||||
assert_eq!(name, "my_continuation");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_continuation_name_without_map() {
|
||||
let name = get_continuation_name(&None, &JoinContId(3));
|
||||
assert_eq!(name, "join_func_3");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_continuation_name_fallback() {
|
||||
let mut func_name_map = BTreeMap::new();
|
||||
func_name_map.insert(JoinFuncId(1), "other_func".to_string());
|
||||
|
||||
// Request cont 5, but only cont 1 is in map
|
||||
let name = get_continuation_name(&Some(func_name_map), &JoinContId(5));
|
||||
assert_eq!(name, "join_func_5"); // Should fallback
|
||||
}
|
||||
}
|
||||
@ -5,8 +5,13 @@
|
||||
//! Each handler corresponds to a specific JoinIR instruction type and is
|
||||
//! responsible for converting it to MIR instructions.
|
||||
|
||||
pub(super) mod call;
|
||||
pub(super) mod conditional_method_call;
|
||||
pub(super) mod field_access;
|
||||
pub(super) mod if_merge;
|
||||
pub(super) mod jump;
|
||||
pub(super) mod method_call;
|
||||
pub(super) mod nested_if_merge;
|
||||
pub(super) mod new_box;
|
||||
pub(super) mod ret;
|
||||
pub(super) mod select;
|
||||
|
||||
611
src/mir/join_ir_vm_bridge/handlers/nested_if_merge.rs
Normal file
611
src/mir/join_ir_vm_bridge/handlers/nested_if_merge.rs
Normal file
@ -0,0 +1,611 @@
|
||||
//! NestedIfMerge Handler - JoinIR NestedIfMerge instruction to MIR conversion
|
||||
//!
|
||||
//! Phase 260 P0.3: Extracted from joinir_block_converter.rs (lines 705-819)
|
||||
//!
|
||||
//! ## Responsibility
|
||||
//!
|
||||
//! Converts JoinIR NestedIfMerge instructions to MIR multi-level control flow.
|
||||
//! This is the **most complex handler** in the system, handling N-level nested
|
||||
//! conditional branching with merge operations.
|
||||
//!
|
||||
//! ## Key Design Points
|
||||
//!
|
||||
//! ### 1. NestedIfMerge → Multi-Level Branch Transformation (Phase 41-4)
|
||||
//!
|
||||
//! JoinIR NestedIfMerge instructions expand to multi-level conditional control flow
|
||||
//! where each condition creates a nested branch level:
|
||||
//!
|
||||
//! ```text
|
||||
//! JoinIR:
|
||||
//! NestedIfMerge {
|
||||
//! conds: [v100, v101], // 2 levels of conditions
|
||||
//! merges: [
|
||||
//! MergePair { dst: v10, then_val: v20, else_val: v30 },
|
||||
//! ],
|
||||
//! k_next: None
|
||||
//! }
|
||||
//!
|
||||
//! MIR (2-level nesting):
|
||||
//! Level 0 Block (bb0):
|
||||
//! Branch { condition: v100, then_bb: bb1 (level 1), else_bb: bb3 (final_else) }
|
||||
//!
|
||||
//! Level 1 Block (bb1):
|
||||
//! Branch { condition: v101, then_bb: bb2 (then), else_bb: bb3 (final_else) }
|
||||
//!
|
||||
//! Then Block (bb2):
|
||||
//! v10 = Copy v20
|
||||
//! Jump bb4 (merge)
|
||||
//!
|
||||
//! Final Else Block (bb3):
|
||||
//! v10 = Copy v30
|
||||
//! Jump bb4 (merge)
|
||||
//!
|
||||
//! Merge Block (bb4):
|
||||
//! (ready for next instruction)
|
||||
//! ```
|
||||
//!
|
||||
//! ### 2. Dynamic Block Allocation (N+3 blocks)
|
||||
//!
|
||||
//! The handler allocates exactly N+3 blocks for N conditions:
|
||||
//! - N level blocks (including current block for level 0)
|
||||
//! - 1 then block (reached when all conditions are true)
|
||||
//! - 1 final_else block (reached when any condition is false)
|
||||
//! - 1 merge block (continuation point)
|
||||
//!
|
||||
//! ### 3. Cascading Branch Logic
|
||||
//!
|
||||
//! Each level block branches to:
|
||||
//! - **Then target**: Next level block (or then_block if last level)
|
||||
//! - **Else target**: Always final_else_block (short-circuit on first false)
|
||||
//!
|
||||
//! This creates a cascading AND pattern: all conditions must be true to reach then_block.
|
||||
//!
|
||||
//! ### 4. Block Structure (3-level example)
|
||||
//!
|
||||
//! ```text
|
||||
//! level_0_block (current)
|
||||
//! ↓ (Branch cond[0])
|
||||
//! ├─→ level_1_block
|
||||
//! │ ↓ (Branch cond[1])
|
||||
//! │ ├─→ level_2_block
|
||||
//! │ │ ↓ (Branch cond[2])
|
||||
//! │ │ ├─→ then_block → merge_block
|
||||
//! │ │ └─→ final_else_block → merge_block
|
||||
//! │ └─→ final_else_block → merge_block
|
||||
//! └─→ final_else_block → merge_block
|
||||
//!
|
||||
//! Continuation: merge_block becomes new current_block
|
||||
//! ```
|
||||
//!
|
||||
//! ### 5. k_next Limitation (Phase 41-4)
|
||||
//!
|
||||
//! Currently, k_next (continuation after merge) is not supported.
|
||||
//! All NestedIfMerge instructions must have k_next = None.
|
||||
//!
|
||||
//! ## Example Conversions
|
||||
//!
|
||||
//! ### Two-Level Nesting
|
||||
//!
|
||||
//! ```text
|
||||
//! JoinIR:
|
||||
//! NestedIfMerge {
|
||||
//! conds: [v1, v2],
|
||||
//! merges: [{ dst: v10, then_val: v20, else_val: v30 }]
|
||||
//! }
|
||||
//!
|
||||
//! MIR:
|
||||
//! Level 0 Block (bb0):
|
||||
//! Branch { condition: v1, then_bb: bb1, else_bb: bb3 }
|
||||
//!
|
||||
//! Level 1 Block (bb1):
|
||||
//! Branch { condition: v2, then_bb: bb2, else_bb: bb3 }
|
||||
//!
|
||||
//! Then Block (bb2):
|
||||
//! v10 = Copy v20
|
||||
//! Jump bb4
|
||||
//!
|
||||
//! Final Else Block (bb3):
|
||||
//! v10 = Copy v30
|
||||
//! Jump bb4
|
||||
//!
|
||||
//! Merge Block (bb4):
|
||||
//! (current_block_id = bb4)
|
||||
//! ```
|
||||
//!
|
||||
//! ### Three-Level Nesting with Multiple Variables
|
||||
//!
|
||||
//! ```text
|
||||
//! JoinIR:
|
||||
//! NestedIfMerge {
|
||||
//! conds: [v50, v51, v52],
|
||||
//! merges: [
|
||||
//! { dst: v100, then_val: v101, else_val: v102 },
|
||||
//! { dst: v200, then_val: v201, else_val: v202 },
|
||||
//! ]
|
||||
//! }
|
||||
//!
|
||||
//! MIR:
|
||||
//! Level 0 Block (bb0):
|
||||
//! Branch { condition: v50, then_bb: bb1, else_bb: bb4 }
|
||||
//!
|
||||
//! Level 1 Block (bb1):
|
||||
//! Branch { condition: v51, then_bb: bb2, else_bb: bb4 }
|
||||
//!
|
||||
//! Level 2 Block (bb2):
|
||||
//! Branch { condition: v52, then_bb: bb3, else_bb: bb4 }
|
||||
//!
|
||||
//! Then Block (bb3):
|
||||
//! v100 = Copy v101
|
||||
//! v200 = Copy v201
|
||||
//! Jump bb5
|
||||
//!
|
||||
//! Final Else Block (bb4):
|
||||
//! v100 = Copy v102
|
||||
//! v200 = Copy v202
|
||||
//! Jump bb5
|
||||
//!
|
||||
//! Merge Block (bb5):
|
||||
//! (current_block_id = bb5)
|
||||
//! ```
|
||||
|
||||
use crate::ast::Span;
|
||||
use crate::mir::join_ir::{JoinContId, MergePair};
|
||||
use crate::mir::{BasicBlock, BasicBlockId, MirFunction, MirInstruction, ValueId};
|
||||
|
||||
use super::super::JoinIrVmBridgeError;
|
||||
|
||||
/// Handle JoinIR NestedIfMerge instruction
|
||||
///
|
||||
/// Converts NestedIfMerge to MIR multi-level control flow with cascading branches.
|
||||
/// Phase 41-4: Supports N-level nesting with multiple variable merges.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `mir_func` - Target MIR function to modify
|
||||
/// * `current_block_id` - Current block ID (will become level 0 block)
|
||||
/// * `current_instructions` - Instructions to finalize in level 0 block
|
||||
/// * `conds` - Array of condition ValueIds (one per nesting level, must not be empty)
|
||||
/// * `merges` - Array of MergePair (dst, then_val, else_val) for each variable
|
||||
/// * `k_next` - Optional continuation (currently not supported, must be None)
|
||||
/// * `next_block_id` - Reference to next available block ID (will be incremented by N+2)
|
||||
/// * `finalize_fn` - Function to finalize blocks (signature matches finalize_block)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// Returns the new current_block_id (merge_block) on success.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns error if:
|
||||
/// - k_next is Some (not yet supported)
|
||||
/// - conds is empty (invalid configuration)
|
||||
pub fn handle_nested_if_merge<F>(
|
||||
mir_func: &mut MirFunction,
|
||||
current_block_id: BasicBlockId,
|
||||
current_instructions: Vec<MirInstruction>,
|
||||
conds: &[ValueId],
|
||||
merges: &[MergePair],
|
||||
k_next: &Option<JoinContId>,
|
||||
next_block_id: &mut u32,
|
||||
finalize_fn: F,
|
||||
) -> Result<BasicBlockId, JoinIrVmBridgeError>
|
||||
where
|
||||
F: FnOnce(&mut MirFunction, BasicBlockId, Vec<MirInstruction>, MirInstruction),
|
||||
{
|
||||
// Phase 41-4: NestedIfMerge → multi-level Branch + PHI
|
||||
if k_next.is_some() {
|
||||
return Err(JoinIrVmBridgeError::new(
|
||||
"NestedIfMerge: k_next not yet supported".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if conds.is_empty() {
|
||||
return Err(JoinIrVmBridgeError::new(
|
||||
"NestedIfMerge: conds must not be empty".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
if crate::config::env::joinir_test_debug_enabled() {
|
||||
eprintln!(
|
||||
"[joinir_block] Converting NestedIfMerge: conds.len()={}",
|
||||
conds.len()
|
||||
);
|
||||
}
|
||||
|
||||
let num_conds = conds.len();
|
||||
|
||||
// Allocate N+3 blocks: N level blocks (including current), then, final_else, merge
|
||||
let mut level_blocks: Vec<BasicBlockId> = Vec::with_capacity(num_conds);
|
||||
level_blocks.push(current_block_id); // Level 0 is current block
|
||||
|
||||
// Allocate level 1..N blocks
|
||||
for _ in 1..num_conds {
|
||||
level_blocks.push(BasicBlockId(*next_block_id));
|
||||
*next_block_id += 1;
|
||||
}
|
||||
|
||||
// Allocate then, final_else, and merge blocks
|
||||
let then_block = BasicBlockId(*next_block_id);
|
||||
*next_block_id += 1;
|
||||
let final_else_block = BasicBlockId(*next_block_id);
|
||||
*next_block_id += 1;
|
||||
let merge_block = BasicBlockId(*next_block_id);
|
||||
*next_block_id += 1;
|
||||
|
||||
// Pre-create level 1+ blocks
|
||||
for level in 1..num_conds {
|
||||
mir_func.blocks.insert(
|
||||
level_blocks[level],
|
||||
BasicBlock::new(level_blocks[level]),
|
||||
);
|
||||
}
|
||||
|
||||
// Finalize level 0 (current block) with finalize_fn (FnOnce constraint)
|
||||
// This must be done first and separately due to FnOnce
|
||||
let level_0_cond = conds[0];
|
||||
let level_0_next = if num_conds > 1 {
|
||||
level_blocks[1]
|
||||
} else {
|
||||
then_block
|
||||
};
|
||||
let level_0_branch = MirInstruction::Branch {
|
||||
condition: level_0_cond,
|
||||
then_bb: level_0_next,
|
||||
else_bb: final_else_block,
|
||||
then_edge_args: None,
|
||||
else_edge_args: None,
|
||||
};
|
||||
finalize_fn(mir_func, current_block_id, current_instructions, level_0_branch);
|
||||
|
||||
// Now set terminators for level 1+ blocks
|
||||
for level in 1..num_conds {
|
||||
let this_block = level_blocks[level];
|
||||
let cond_var = conds[level];
|
||||
|
||||
let next_true_block = if level + 1 < num_conds {
|
||||
level_blocks[level + 1]
|
||||
} else {
|
||||
then_block
|
||||
};
|
||||
|
||||
let branch_terminator = MirInstruction::Branch {
|
||||
condition: cond_var,
|
||||
then_bb: next_true_block,
|
||||
else_bb: final_else_block,
|
||||
then_edge_args: None,
|
||||
else_edge_args: None,
|
||||
};
|
||||
|
||||
if let Some(block) = mir_func.blocks.get_mut(&this_block) {
|
||||
block.set_terminator(branch_terminator);
|
||||
}
|
||||
}
|
||||
|
||||
// Then block: copy then_val for each merge
|
||||
let mut then_block_obj = BasicBlock::new(then_block);
|
||||
for merge in merges {
|
||||
then_block_obj.instructions.push(MirInstruction::Copy {
|
||||
dst: merge.dst,
|
||||
src: merge.then_val,
|
||||
});
|
||||
then_block_obj.instruction_spans.push(Span::unknown());
|
||||
}
|
||||
then_block_obj.set_terminator(MirInstruction::Jump {
|
||||
target: merge_block,
|
||||
edge_args: None,
|
||||
});
|
||||
mir_func.blocks.insert(then_block, then_block_obj);
|
||||
|
||||
// Final else block: copy else_val for each merge
|
||||
let mut else_block_obj = BasicBlock::new(final_else_block);
|
||||
for merge in merges {
|
||||
else_block_obj.instructions.push(MirInstruction::Copy {
|
||||
dst: merge.dst,
|
||||
src: merge.else_val,
|
||||
});
|
||||
else_block_obj.instruction_spans.push(Span::unknown());
|
||||
}
|
||||
else_block_obj.set_terminator(MirInstruction::Jump {
|
||||
target: merge_block,
|
||||
edge_args: None,
|
||||
});
|
||||
mir_func.blocks.insert(final_else_block, else_block_obj);
|
||||
|
||||
// Merge block (empty, ready for next instruction)
|
||||
let merge_block_obj = BasicBlock::new(merge_block);
|
||||
mir_func.blocks.insert(merge_block, merge_block_obj);
|
||||
|
||||
Ok(merge_block)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::mir::function::{FunctionMetadata, FunctionSignature, MirFunction};
|
||||
use crate::mir::{BasicBlock, BasicBlockId, MirInstruction, ValueId};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Mock finalize function for testing
|
||||
fn mock_finalize(
|
||||
mir_func: &mut MirFunction,
|
||||
block_id: BasicBlockId,
|
||||
instructions: Vec<MirInstruction>,
|
||||
terminator: MirInstruction,
|
||||
) {
|
||||
if let Some(block) = mir_func.blocks.get_mut(&block_id) {
|
||||
block.instructions.extend(instructions);
|
||||
block.set_terminator(terminator);
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper to create a minimal MirFunction for testing
|
||||
fn create_test_function() -> MirFunction {
|
||||
let signature = FunctionSignature {
|
||||
name: "test_func".to_string(),
|
||||
params: vec![],
|
||||
return_type: crate::mir::MirType::Void,
|
||||
effects: crate::mir::EffectMask::PURE,
|
||||
};
|
||||
let entry_block = BasicBlockId(0);
|
||||
MirFunction::new(signature, entry_block)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handle_nested_if_merge_two_levels() {
|
||||
let mut mir_func = create_test_function();
|
||||
|
||||
let current_block = BasicBlockId(0);
|
||||
|
||||
let conds = vec![ValueId(100), ValueId(101)];
|
||||
let dst = ValueId(10);
|
||||
let then_val = ValueId(20);
|
||||
let else_val = ValueId(30);
|
||||
|
||||
let merges = vec![MergePair {
|
||||
dst,
|
||||
then_val,
|
||||
else_val,
|
||||
type_hint: None,
|
||||
}];
|
||||
|
||||
let mut next_block_id = 1;
|
||||
let result = handle_nested_if_merge(
|
||||
&mut mir_func,
|
||||
current_block,
|
||||
vec![],
|
||||
&conds,
|
||||
&merges,
|
||||
&None,
|
||||
&mut next_block_id,
|
||||
mock_finalize,
|
||||
);
|
||||
|
||||
assert!(result.is_ok());
|
||||
let merge_block = result.unwrap();
|
||||
|
||||
// Verify block allocation: 2 levels + then + else + merge = 5 blocks total
|
||||
// Level 0: bb0 (current), Level 1: bb1, Then: bb2, Else: bb3, Merge: bb4
|
||||
assert_eq!(merge_block, BasicBlockId(4));
|
||||
assert_eq!(next_block_id, 5); // 4 blocks allocated (level 1 + then + else + merge)
|
||||
|
||||
// Verify level 0 block branches to level 1 or final_else
|
||||
let level_0_block = mir_func.blocks.get(&BasicBlockId(0)).unwrap();
|
||||
assert!(matches!(
|
||||
level_0_block.terminator,
|
||||
Some(MirInstruction::Branch {
|
||||
condition: ValueId(100),
|
||||
then_bb: BasicBlockId(1), // level 1
|
||||
else_bb: BasicBlockId(3), // final_else
|
||||
..
|
||||
})
|
||||
));
|
||||
|
||||
// Verify level 1 block branches to then or final_else
|
||||
let level_1_block = mir_func.blocks.get(&BasicBlockId(1)).unwrap();
|
||||
assert!(matches!(
|
||||
level_1_block.terminator,
|
||||
Some(MirInstruction::Branch {
|
||||
condition: ValueId(101),
|
||||
then_bb: BasicBlockId(2), // then
|
||||
else_bb: BasicBlockId(3), // final_else
|
||||
..
|
||||
})
|
||||
));
|
||||
|
||||
// Verify then block has Copy and Jump to merge
|
||||
let then_block_obj = mir_func.blocks.get(&BasicBlockId(2)).unwrap();
|
||||
assert_eq!(then_block_obj.instructions.len(), 1);
|
||||
assert!(matches!(
|
||||
then_block_obj.instructions[0],
|
||||
MirInstruction::Copy { dst: d, src: s } if d == dst && s == then_val
|
||||
));
|
||||
assert!(matches!(
|
||||
then_block_obj.terminator,
|
||||
Some(MirInstruction::Jump { target: t, .. }) if t == merge_block
|
||||
));
|
||||
|
||||
// Verify final_else block has Copy and Jump to merge
|
||||
let else_block_obj = mir_func.blocks.get(&BasicBlockId(3)).unwrap();
|
||||
assert_eq!(else_block_obj.instructions.len(), 1);
|
||||
assert!(matches!(
|
||||
else_block_obj.instructions[0],
|
||||
MirInstruction::Copy { dst: d, src: s } if d == dst && s == else_val
|
||||
));
|
||||
assert!(matches!(
|
||||
else_block_obj.terminator,
|
||||
Some(MirInstruction::Jump { target: t, .. }) if t == merge_block
|
||||
));
|
||||
|
||||
// Verify merge block exists and is empty
|
||||
let merge_block_obj = mir_func.blocks.get(&merge_block).unwrap();
|
||||
assert_eq!(merge_block_obj.instructions.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handle_nested_if_merge_three_levels() {
|
||||
let mut mir_func = create_test_function();
|
||||
|
||||
let current_block = BasicBlockId(0);
|
||||
|
||||
let conds = vec![ValueId(100), ValueId(101), ValueId(102)];
|
||||
let merges = vec![
|
||||
MergePair {
|
||||
dst: ValueId(10),
|
||||
then_val: ValueId(20),
|
||||
else_val: ValueId(30),
|
||||
type_hint: None,
|
||||
},
|
||||
MergePair {
|
||||
dst: ValueId(11),
|
||||
then_val: ValueId(21),
|
||||
else_val: ValueId(31),
|
||||
type_hint: None,
|
||||
},
|
||||
];
|
||||
|
||||
let mut next_block_id = 1;
|
||||
let result = handle_nested_if_merge(
|
||||
&mut mir_func,
|
||||
current_block,
|
||||
vec![],
|
||||
&conds,
|
||||
&merges,
|
||||
&None,
|
||||
&mut next_block_id,
|
||||
mock_finalize,
|
||||
);
|
||||
|
||||
assert!(result.is_ok());
|
||||
let merge_block = result.unwrap();
|
||||
|
||||
// 3 levels + then + else + merge = 6 blocks total
|
||||
// Level 0: bb0, Level 1: bb1, Level 2: bb2, Then: bb3, Else: bb4, Merge: bb5
|
||||
assert_eq!(merge_block, BasicBlockId(5));
|
||||
assert_eq!(next_block_id, 6);
|
||||
|
||||
// Verify level 0 branches
|
||||
let level_0 = mir_func.blocks.get(&BasicBlockId(0)).unwrap();
|
||||
assert!(matches!(
|
||||
level_0.terminator,
|
||||
Some(MirInstruction::Branch {
|
||||
condition: ValueId(100),
|
||||
then_bb: BasicBlockId(1),
|
||||
else_bb: BasicBlockId(4), // final_else
|
||||
..
|
||||
})
|
||||
));
|
||||
|
||||
// Verify level 1 branches
|
||||
let level_1 = mir_func.blocks.get(&BasicBlockId(1)).unwrap();
|
||||
assert!(matches!(
|
||||
level_1.terminator,
|
||||
Some(MirInstruction::Branch {
|
||||
condition: ValueId(101),
|
||||
then_bb: BasicBlockId(2),
|
||||
else_bb: BasicBlockId(4), // final_else
|
||||
..
|
||||
})
|
||||
));
|
||||
|
||||
// Verify level 2 branches to then block
|
||||
let level_2 = mir_func.blocks.get(&BasicBlockId(2)).unwrap();
|
||||
assert!(matches!(
|
||||
level_2.terminator,
|
||||
Some(MirInstruction::Branch {
|
||||
condition: ValueId(102),
|
||||
then_bb: BasicBlockId(3), // then
|
||||
else_bb: BasicBlockId(4), // final_else
|
||||
..
|
||||
})
|
||||
));
|
||||
|
||||
// Verify then block has 2 Copy instructions
|
||||
let then_block_obj = mir_func.blocks.get(&BasicBlockId(3)).unwrap();
|
||||
assert_eq!(then_block_obj.instructions.len(), 2);
|
||||
assert!(matches!(
|
||||
then_block_obj.instructions[0],
|
||||
MirInstruction::Copy { dst: ValueId(10), src: ValueId(20) }
|
||||
));
|
||||
assert!(matches!(
|
||||
then_block_obj.instructions[1],
|
||||
MirInstruction::Copy { dst: ValueId(11), src: ValueId(21) }
|
||||
));
|
||||
|
||||
// Verify final_else block has 2 Copy instructions
|
||||
let else_block_obj = mir_func.blocks.get(&BasicBlockId(4)).unwrap();
|
||||
assert_eq!(else_block_obj.instructions.len(), 2);
|
||||
assert!(matches!(
|
||||
else_block_obj.instructions[0],
|
||||
MirInstruction::Copy { dst: ValueId(10), src: ValueId(30) }
|
||||
));
|
||||
assert!(matches!(
|
||||
else_block_obj.instructions[1],
|
||||
MirInstruction::Copy { dst: ValueId(11), src: ValueId(31) }
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handle_nested_if_merge_rejects_k_next() {
|
||||
let mut mir_func = create_test_function();
|
||||
|
||||
let current_block = BasicBlockId(0);
|
||||
|
||||
let conds = vec![ValueId(100), ValueId(101)];
|
||||
let merges = vec![MergePair {
|
||||
dst: ValueId(10),
|
||||
then_val: ValueId(20),
|
||||
else_val: ValueId(30),
|
||||
type_hint: None,
|
||||
}];
|
||||
|
||||
let k_next = Some(JoinContId(999));
|
||||
let mut next_block_id = 1;
|
||||
|
||||
let result = handle_nested_if_merge(
|
||||
&mut mir_func,
|
||||
current_block,
|
||||
vec![],
|
||||
&conds,
|
||||
&merges,
|
||||
&k_next,
|
||||
&mut next_block_id,
|
||||
mock_finalize,
|
||||
);
|
||||
|
||||
assert!(result.is_err());
|
||||
let err = result.unwrap_err();
|
||||
assert!(err.message.contains("k_next not yet supported"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_handle_nested_if_merge_rejects_empty_conds() {
|
||||
let mut mir_func = create_test_function();
|
||||
|
||||
let current_block = BasicBlockId(0);
|
||||
|
||||
let conds: Vec<ValueId> = vec![];
|
||||
let merges = vec![MergePair {
|
||||
dst: ValueId(10),
|
||||
then_val: ValueId(20),
|
||||
else_val: ValueId(30),
|
||||
type_hint: None,
|
||||
}];
|
||||
|
||||
let mut next_block_id = 1;
|
||||
|
||||
let result = handle_nested_if_merge(
|
||||
&mut mir_func,
|
||||
current_block,
|
||||
vec![],
|
||||
&conds,
|
||||
&merges,
|
||||
&None,
|
||||
&mut next_block_id,
|
||||
mock_finalize,
|
||||
);
|
||||
|
||||
assert!(result.is_err());
|
||||
let err = result.unwrap_err();
|
||||
assert!(err.message.contains("conds must not be empty"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user