refactor(joinir): Phase 33-17-A TailCallClassifier and MergeResult extraction

## Changes
- Extract TailCallClassifier Box (107 lines) with 4 unit tests
- Extract MergeResult Box (44 lines) for merge result data structure
- instruction_rewriter.rs reduced: 649 → 580 lines (-10.6%)

## Box Theory Compliance
- TailCallClassifier: Single responsibility for tail call classification
- MergeResult: Clean data structure separation
- Both modules follow naming conventions (Classifier, Result)

## Quality
- 4 unit tests added for TailCallClassifier
- Build verified 

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-12-07 12:23:37 +09:00
parent 287ceca18d
commit 8baca953ca
4 changed files with 166 additions and 69 deletions

View File

@ -4,82 +4,16 @@
//! into the host MIR builder.
//!
//! Phase 4 Extraction: Separated from merge_joinir_mir_blocks (lines 260-546)
//! Phase 33-17: Further modularization - extracted TailCallClassifier and MergeResult
use crate::mir::{BasicBlock, BasicBlockId, MirInstruction, MirModule, ValueId};
use crate::mir::builder::joinir_id_remapper::JoinIrIdRemapper;
use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary;
use super::loop_header_phi_builder::LoopHeaderPhiInfo;
use super::tail_call_classifier::{TailCallKind, classify_tail_call};
use super::merge_result::MergeResult;
use std::collections::HashMap;
/// Phase 33-16: Tail Call Classification
///
/// Classifies tail calls in JoinIR loops into three semantic categories:
///
/// 1. **LoopEntry**: First entry into the loop (main → loop_step)
/// - Occurs from the entry function's entry block
/// - Should jump directly to target (not redirect to header)
/// - Reason: The entry block IS the header block; redirecting creates self-loop
///
/// 2. **BackEdge**: Loop continuation (loop_step → loop_step)
/// - Occurs from loop body blocks (not entry function's entry block)
/// - MUST redirect to header block where PHI nodes are located
/// - Reason: PHI nodes need to merge values from all back edges
///
/// 3. **ExitJump**: Loop termination (→ k_exit)
/// - Occurs when jumping to continuation functions
/// - Handled separately via Return conversion
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum TailCallKind {
/// First entry into loop - no redirection needed
LoopEntry,
/// Back edge in loop - redirect to header PHI
BackEdge,
/// Exit from loop - becomes Return conversion
ExitJump,
}
/// Classifies a tail call based on context
///
/// # Arguments
/// * `is_entry_func_entry_block` - True if this is the first function's first block (loop entry point)
/// * `has_loop_header_phis` - True if loop header PHI nodes exist
/// * `has_boundary` - True if JoinInlineBoundary exists (indicates loop context)
///
/// # Returns
/// The classification of this tail call
fn classify_tail_call(
is_entry_func_entry_block: bool,
has_loop_header_phis: bool,
has_boundary: bool,
) -> TailCallKind {
// Entry function's entry block is the loop entry point
// It already IS at the header, so no redirection needed
if is_entry_func_entry_block {
return TailCallKind::LoopEntry;
}
// If we have boundary and header PHIs, this is a back edge
// Must redirect to header for PHI merging
if has_boundary && has_loop_header_phis {
return TailCallKind::BackEdge;
}
// Otherwise, treat as exit (will be handled by Return conversion)
TailCallKind::ExitJump
}
/// Phase 33-13: Return type for merge_and_rewrite
///
/// Contains all information needed for exit PHI construction.
pub struct MergeResult {
/// The ID of the exit block where all Returns jump to
pub exit_block_id: BasicBlockId,
/// Vec of (from_block, return_value) for expr result PHI generation
pub exit_phi_inputs: Vec<(BasicBlockId, ValueId)>,
/// Map of carrier_name → Vec of (from_block, exit_value) for carrier PHI generation
pub carrier_inputs: std::collections::BTreeMap<String, Vec<(BasicBlockId, ValueId)>>,
}
/// Phase 4: Merge ALL functions and rewrite instructions
///
/// Returns:
@ -90,6 +24,12 @@ pub struct MergeResult {
/// The `loop_header_phi_info` parameter is used to:
/// 1. Set latch_incoming when processing tail calls
/// 2. Provide PHI dsts for exit value collection (instead of undefined parameters)
///
/// # Phase 33-17
///
/// Uses extracted modules:
/// - tail_call_classifier: TailCallKind classification
/// - merge_result: MergeResult data structure
pub(super) fn merge_and_rewrite(
builder: &mut crate::mir::builder::MirBuilder,
mir_module: &MirModule,

View File

@ -0,0 +1,44 @@
//! Phase 33-17: Merge Result Data Structure
//!
//! Contains all information needed for exit PHI construction.
//!
//! Extracted from instruction_rewriter.rs (lines 71-81) for single responsibility.
use crate::mir::{BasicBlockId, ValueId};
use std::collections::BTreeMap;
/// Phase 33-13: Return type for merge_and_rewrite
///
/// Contains all information needed for exit PHI construction.
pub struct MergeResult {
/// The ID of the exit block where all Returns jump to
pub exit_block_id: BasicBlockId,
/// Vec of (from_block, return_value) for expr result PHI generation
pub exit_phi_inputs: Vec<(BasicBlockId, ValueId)>,
/// Map of carrier_name → Vec of (from_block, exit_value) for carrier PHI generation
pub carrier_inputs: BTreeMap<String, Vec<(BasicBlockId, ValueId)>>,
}
impl MergeResult {
/// Create a new MergeResult with empty inputs
pub fn new(exit_block_id: BasicBlockId) -> Self {
Self {
exit_block_id,
exit_phi_inputs: Vec::new(),
carrier_inputs: BTreeMap::new(),
}
}
/// Add an exit PHI input
pub fn add_exit_phi_input(&mut self, from_block: BasicBlockId, value: ValueId) {
self.exit_phi_inputs.push((from_block, value));
}
/// Add a carrier input
pub fn add_carrier_input(&mut self, carrier_name: String, from_block: BasicBlockId, value: ValueId) {
self.carrier_inputs
.entry(carrier_name)
.or_insert_with(Vec::new)
.push((from_block, value));
}
}

View File

@ -18,6 +18,12 @@ mod instruction_rewriter;
mod exit_phi_builder;
pub mod exit_line;
pub mod loop_header_phi_builder;
mod tail_call_classifier;
mod merge_result;
// Phase 33-17: Re-export for use by other modules
pub use merge_result::MergeResult;
pub use tail_call_classifier::{TailCallKind, classify_tail_call};
use crate::mir::{BasicBlockId, MirModule, ValueId};
use crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary;

View File

@ -0,0 +1,107 @@
//! Phase 33-17: Tail Call Classification
//!
//! Classifies tail calls in JoinIR loops into three semantic categories.
//!
//! Extracted from instruction_rewriter.rs (lines 14-69) for single responsibility.
/// Phase 33-16: Tail Call Classification
///
/// Classifies tail calls in JoinIR loops into three semantic categories:
///
/// 1. **LoopEntry**: First entry into the loop (main → loop_step)
/// - Occurs from the entry function's entry block
/// - Should jump directly to target (not redirect to header)
/// - Reason: The entry block IS the header block; redirecting creates self-loop
///
/// 2. **BackEdge**: Loop continuation (loop_step → loop_step)
/// - Occurs from loop body blocks (not entry function's entry block)
/// - MUST redirect to header block where PHI nodes are located
/// - Reason: PHI nodes need to merge values from all back edges
///
/// 3. **ExitJump**: Loop termination (→ k_exit)
/// - Occurs when jumping to continuation functions
/// - Handled separately via Return conversion
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TailCallKind {
/// First entry into loop - no redirection needed
LoopEntry,
/// Back edge in loop - redirect to header PHI
BackEdge,
/// Exit from loop - becomes Return conversion
ExitJump,
}
/// Classifies a tail call based on context
///
/// # Arguments
/// * `is_entry_func_entry_block` - True if this is the first function's first block (loop entry point)
/// * `has_loop_header_phis` - True if loop header PHI nodes exist
/// * `has_boundary` - True if JoinInlineBoundary exists (indicates loop context)
///
/// # Returns
/// The classification of this tail call
pub fn classify_tail_call(
is_entry_func_entry_block: bool,
has_loop_header_phis: bool,
has_boundary: bool,
) -> TailCallKind {
// Entry function's entry block is the loop entry point
// It already IS at the header, so no redirection needed
if is_entry_func_entry_block {
return TailCallKind::LoopEntry;
}
// If we have boundary and header PHIs, this is a back edge
// Must redirect to header for PHI merging
if has_boundary && has_loop_header_phis {
return TailCallKind::BackEdge;
}
// Otherwise, treat as exit (will be handled by Return conversion)
TailCallKind::ExitJump
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_classify_loop_entry() {
let result = classify_tail_call(
true, // is_entry_func_entry_block
true, // has_loop_header_phis
true, // has_boundary
);
assert_eq!(result, TailCallKind::LoopEntry);
}
#[test]
fn test_classify_back_edge() {
let result = classify_tail_call(
false, // is_entry_func_entry_block (not entry block)
true, // has_loop_header_phis
true, // has_boundary
);
assert_eq!(result, TailCallKind::BackEdge);
}
#[test]
fn test_classify_exit_jump() {
let result = classify_tail_call(
false, // is_entry_func_entry_block
false, // has_loop_header_phis (no header PHIs)
true, // has_boundary
);
assert_eq!(result, TailCallKind::ExitJump);
}
#[test]
fn test_classify_no_boundary() {
let result = classify_tail_call(
false, // is_entry_func_entry_block
true, // has_loop_header_phis
false, // has_boundary (no boundary → exit)
);
assert_eq!(result, TailCallKind::ExitJump);
}
}