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

@ -387,6 +387,8 @@ mod tests {
join_outputs: vec![],
exit_bindings: vec![], // Phase 171: Add missing field
condition_inputs: vec![], // Phase 171: Add missing field
condition_bindings: vec![], // Phase 171-fix: Add missing field
expr_result: None, // Phase 33-14: Add missing field
};
builder.apply_to_boundary(&mut boundary)

View File

@ -142,8 +142,8 @@ impl MirBuilder {
};
// Phase 169 / Phase 171-fix / Phase 172-3: Call Pattern 2 lowerer with ConditionEnv
// Now returns (JoinModule, ExitMeta) for proper exit_bindings construction
let (join_module, exit_meta) = match lower_loop_with_break_minimal(scope, condition, &env, &loop_var_name) {
// Phase 33-14: Now returns (JoinModule, JoinFragmentMeta) for expr_result + carrier separation
let (join_module, fragment_meta) = match lower_loop_with_break_minimal(scope, condition, &env, &loop_var_name) {
Ok((module, meta)) => (module, meta),
Err(e) => {
// Phase 195: Use unified trace
@ -152,6 +152,9 @@ impl MirBuilder {
}
};
// Phase 33-14: Extract exit_meta from fragment_meta for backward compatibility
let exit_meta = &fragment_meta.exit_meta;
// Phase 195: Use unified trace
trace::trace().joinir_stats(
"pattern2",
@ -186,12 +189,14 @@ impl MirBuilder {
let exit_bindings = ExitMetaCollector::collect(self, &exit_meta, debug);
// Phase 172-3: Build boundary with both condition_bindings and exit_bindings
// Phase 33-14: Set expr_result from fragment_meta
let mut boundary = crate::mir::join_ir::lowering::inline_boundary::JoinInlineBoundary::new_inputs_only(
vec![ValueId(0)], // JoinIR's main() parameter (loop variable init)
vec![loop_var_id], // Host's loop variable
);
boundary.condition_bindings = condition_bindings;
boundary.exit_bindings = exit_bindings.clone();
boundary.expr_result = fragment_meta.expr_result; // Phase 33-14: Pass expr_result to merger
// Phase 189: Capture exit PHI result (now used for reconnect)
let _ = self.merge_joinir_mir_blocks(&mir_module, Some(&boundary), debug)?;