refactor(joinir): Pattern 3 ExitMeta化 - Hardcoded ValueIds削除

Refactoring 5.1: Pattern 3 を Pattern 4 と同じ ExitMeta ベースアーキテクチャに統一化

Changes:
1. loop_with_if_phi_minimal.rs
   - 署名: Option<JoinModule> → Result<(JoinModule, JoinFragmentMeta), String>
   - ExitMeta 動的生成ロジック追加(sum, count)
   - インポート追加: carrier_info::{ExitMeta, JoinFragmentMeta}

2. pattern3_with_if_phi.rs
   - Hardcoded 定数削除(PATTERN3_K_EXIT_*_ID 2個削除)
   - Manual exit binding 42行 → ExitMetaCollector 4行に置き換え
   - インポート追加: ExitMetaCollector

3. loop_patterns/with_if_phi.rs
   - Result型変更に対応(.ok()? で変換)

Benefits:
- Pattern 3/4 アーキテクチャ統一化 
- 19行純削減(+55 -74行、3ファイル合計) 
- Hardcoded ValueIds 完全撤廃 
- Phase 213 AST-based generalization の基盤強化 

Tests: All tests passing, loop_if_phi.hako outputs "sum=9" correctly
This commit is contained in:
nyash-codex
2025-12-10 00:29:25 +09:00
parent d7805e5974
commit 8394018694
7 changed files with 865 additions and 86 deletions

View File

@ -3,31 +3,11 @@
use crate::ast::ASTNode;
use crate::mir::builder::MirBuilder;
use crate::mir::ValueId;
use super::super::merge::exit_line::meta_collector::ExitMetaCollector;
use super::super::trace;
/// Phase 179-A / Phase 195: Expected ValueIds for k_exit parameters in Pattern 3
/// These correspond to the exit PHI inputs in the JoinIR lowering for loop_with_if_phi_minimal
///
/// # TODO (Phase 179 Task 2): Convert to ExitMeta-based exit binding generation
///
/// **Current State**: Hardcoded ValueIds - fragile and non-reusable
///
/// **Why it's hardcoded**:
/// - Pattern 3's lowerer (`lower_loop_with_if_phi_pattern`) returns `Option<JoinModule>`
/// - Unlike Pattern 4 which returns `(JoinModule, JoinFragmentMeta)`
/// - No ExitMeta available to dynamically look up exit PHI ValueIds
///
/// **Migration Path** (when Pattern 3 lowerer is updated):
/// 1. Change `lower_loop_with_if_phi_pattern` to return `(JoinModule, JoinFragmentMeta)`
/// 2. Remove these constants
/// 3. Use ExitMeta loop (like Pattern 4 lines 350-378) to generate exit_bindings dynamically
/// 4. See: pattern4_with_continue.rs lines 350-378 for reference implementation
///
/// **Impact**: Low priority - Pattern 3 is test-only and works correctly with hardcoded values
///
/// Phase 195: Multi-carrier support - now includes both sum_final and count_final
const PATTERN3_K_EXIT_SUM_FINAL_ID: ValueId = ValueId(24); // Phase 195: Updated from ValueId(18)
const PATTERN3_K_EXIT_COUNT_FINAL_ID: ValueId = ValueId(25); // Phase 195: New count carrier
// Phase 213: Hardcoded ValueIds removed - now using ExitMeta-based exit binding generation
// See: ExitMetaCollector usage below (lines 115-135)
/// Phase 194: Detection function for Pattern 3
///
@ -106,62 +86,53 @@ impl MirBuilder {
let mut join_value_space = JoinValueSpace::new();
// Call Pattern 3 lowerer with preprocessed scope
let join_module = match lower_loop_with_if_phi_pattern(ctx.loop_scope, &mut join_value_space) {
Some(module) => module,
None => {
// Phase 195: Use unified trace
trace::trace().debug("pattern3", "Pattern 3 lowerer returned None");
return Ok(None);
let (join_module, fragment_meta) = match lower_loop_with_if_phi_pattern(ctx.loop_scope, &mut join_value_space) {
Ok(result) => result,
Err(e) => {
trace::trace().debug("pattern3", &format!("Pattern 3 lowerer failed: {}", e));
return Err(format!("[cf_loop/pattern3] Lowering failed: {}", e));
}
};
let exit_meta = &fragment_meta.exit_meta;
trace::trace().debug(
"pattern3",
&format!("ExitMeta: {} exit values", exit_meta.exit_values.len())
);
for (carrier_name, join_value) in &exit_meta.exit_values {
trace::trace().debug(
"pattern3",
&format!(" {} → ValueId({})", carrier_name, join_value.0)
);
}
// Phase 195: Create boundary from context (multi-carrier support with backward compatibility)
// Phase 201: Use JoinInlineBoundaryBuilder for clean construction
// Canonical Builder pattern - see docs/development/current/main/joinir-boundary-builder-pattern.md
self.trace_varmap("pattern3_before_merge");
use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder;
use crate::mir::join_ir::lowering::inline_boundary::LoopExitBinding;
// Phase 195: Build inputs and exit_bindings dynamically based on available carriers
let (join_inputs, host_inputs, exit_bindings) = if has_count {
// Multi-carrier: i, sum, count
let count_var_id = count_carrier_opt.unwrap().host_id;
(
vec![ValueId(0), ValueId(1), ValueId(2)], // JoinIR's main() parameters
vec![ctx.loop_var_id, sum_var_id, count_var_id], // Host's loop variables
vec![
LoopExitBinding {
carrier_name: "sum".to_string(),
join_exit_value: PATTERN3_K_EXIT_SUM_FINAL_ID, // ValueId(24)
host_slot: sum_var_id,
},
LoopExitBinding {
carrier_name: "count".to_string(),
join_exit_value: PATTERN3_K_EXIT_COUNT_FINAL_ID, // ValueId(25)
host_slot: count_var_id,
}
]
)
// Phase 213: Use ExitMetaCollector for dynamic exit binding generation
// Note: ExitMetaCollector internally validates that all exit carriers in ExitMeta
// have corresponding variable_map entries. No additional validation needed here.
let exit_bindings = ExitMetaCollector::collect(
self,
exit_meta,
debug,
);
// Build join_inputs and host_inputs (retain existing logic)
let join_inputs = vec![ValueId(0), ValueId(1), ValueId(2)];
let mut host_inputs = vec![ctx.loop_var_id, sum_var_id];
if has_count {
host_inputs.push(count_carrier_opt.unwrap().host_id);
} else {
// Single-carrier (backward compatibility): i, sum only
// Phase 195: JoinIR lowerer now always generates 3 parameters (i, sum, count)
// For backward compat, we create a dummy count variable that will be discarded
use crate::mir::builder::emission::constant;
let dummy_count_id = constant::emit_void(self); // Use void as dummy value
(
vec![ValueId(0), ValueId(1), ValueId(2)], // JoinIR's main() parameters (i, sum, count)
vec![ctx.loop_var_id, sum_var_id, dummy_count_id], // Host's loop variables (count is dummy)
vec![
LoopExitBinding {
carrier_name: "sum".to_string(),
join_exit_value: PATTERN3_K_EXIT_SUM_FINAL_ID, // ValueId(24)
host_slot: sum_var_id,
}
// Don't bind count in single-carrier mode - it's just discarded
]
)
};
let dummy_count_id = constant::emit_void(self);
host_inputs.push(dummy_count_id);
}
let boundary = JoinInlineBoundaryBuilder::new()
.with_inputs(join_inputs, host_inputs)