feat(joinir): Phase 33-14 JoinFragmentMeta for expr/carrier separation

Introduces JoinFragmentMeta to distinguish between loop expression results
and carrier variable updates, fixing SSA correctness issues.

## Changes

### New: JoinFragmentMeta struct (carrier_info.rs)
- `expr_result: Option<ValueId>` - Loop as expression (return loop(...))
- `exit_meta: ExitMeta` - Carrier updates for variable_map
- Helper methods: with_expr_result(), carrier_only(), empty()

### Pattern 2 Lowerer Updates
- loop_with_break_minimal.rs: Returns (JoinModule, JoinFragmentMeta)
- pattern2_with_break.rs: Sets boundary.expr_result from fragment_meta

### instruction_rewriter.rs
- Phase 33-14: Only add to exit_phi_inputs when boundary.expr_result is Some
- Phase 33-13: MergeResult struct with carrier_inputs map

### JoinInlineBoundary (inline_boundary.rs)
- New field: expr_result: Option<ValueId>
- All constructors updated with expr_result: None default

## Design Philosophy

Previously, exit_phi_inputs mixed expr results with carrier updates, causing:
- PHI inputs referencing undefined remapped values
- SSA-undef errors in VM execution

With JoinFragmentMeta:
- expr_result → exit_phi_inputs (generates PHI for expr value)
- exit_meta → carrier_inputs (updates variable_map via carrier PHIs)

## Test Results
- Pattern 1 (carrier-only): Works correctly (no exit_phi_inputs)
- Pattern 2 (expr result): Design complete, SSA-undef fix deferred to Phase 33-15

🤖 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 05:07:28 +09:00
parent 35f5a48eb0
commit a09ce0cbff
11 changed files with 339 additions and 70 deletions

View File

@ -106,7 +106,7 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
remap_values(builder, &used_values, &mut remapper, debug)?;
// Phase 4: Merge blocks and rewrite instructions
let (exit_block_id, exit_phi_inputs) = instruction_rewriter::merge_and_rewrite(
let merge_result = instruction_rewriter::merge_and_rewrite(
builder,
mir_module,
&mut remapper,
@ -116,21 +116,26 @@ pub(in crate::mir::builder) fn merge_joinir_mir_blocks(
debug,
)?;
// Phase 5: Build exit PHI
let exit_phi_result_id = exit_phi_builder::build_exit_phi(
// Phase 5: Build exit PHI (expr result and carrier PHIs)
// Phase 33-13: Now also builds carrier PHIs and returns their mapping
let (exit_phi_result_id, carrier_phis) = exit_phi_builder::build_exit_phi(
builder,
exit_block_id,
&exit_phi_inputs,
merge_result.exit_block_id,
&merge_result.exit_phi_inputs,
&merge_result.carrier_inputs,
debug,
)?;
// Phase 6: Reconnect boundary (if specified)
// Phase 197-B: Pass remapper to enable per-carrier exit value lookup
// Phase 33-10-Refactor-P3: Delegate to ExitLineOrchestrator
// Phase 33-13: Pass carrier_phis for proper variable_map update
if let Some(boundary) = boundary {
exit_line::ExitLineOrchestrator::execute(builder, boundary, &remapper, debug)?;
exit_line::ExitLineOrchestrator::execute(builder, boundary, &carrier_phis, debug)?;
}
let exit_block_id = merge_result.exit_block_id;
// Jump from current block to entry function's entry block
let (entry_func_name, entry_func) = mir_module
.functions