feat(joinir): Phase 215-2 ExprResult exit contract for Pattern 3

Implements Phase 215 Task 215-2: Wire expr_result exit line through
ExitMeta → JoinInlineBoundary → ExitLine → MIR return for Pattern 3.

## Changes

### Fix 1: JoinIR Lowerer (loop_with_if_phi_if_sum.rs:312)
- Changed `carrier_only()` → `with_expr_result(sum_final, exit_meta)`
- Marks sum final value as expr_result for propagation

### Fix 2: Boundary Builder (pattern3_with_if_phi.rs:176-191)
- Added `.with_expr_result(Some(expr_id))` to JoinInlineBoundaryBuilder
- Passes expr_result to boundary for ExitLineReconnector

### Fix 3: Final Return (pattern3_with_if_phi.rs:195-219)
- Changed from always returning Void to conditional return:
  - Expr-position loops: Return merge_result ValueId
  - Statement-position loops: Return Void
- Matches Pattern 2 behavior

## Test Results

Primary target:
- phase212_if_sum_min.hako: RC=2 achieved 

Regression tests:
- loop_if_phi.hako: RC=2 (existing behavior maintained) 
- Pattern 1/2/3 tests: All PASS 

## Architecture

Pattern 3 now follows the same ExprResult Exit Contract as Pattern 2:
1. JoinIR lowerer creates expr_result with `with_expr_result()`
2. Boundary builder passes expr_result with `.with_expr_result()`
3. Conversion pipeline returns merge result containing exit PHI ValueId
4. Pattern dispatcher conditionally returns expr_result or Void

This enables:
- Expr-position loops: Loop result propagates to caller
- Statement-position loops: Loop updates variable_map only (returns Void)
- Unified contract: Patterns 2 and 3 follow same expr_result flow

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-12-10 01:40:18 +09:00
parent 272d99f3de
commit e2508f9f08
5 changed files with 458 additions and 887 deletions

View File

@ -173,15 +173,26 @@ impl MirBuilder {
)
);
let boundary = JoinInlineBoundaryBuilder::new()
// Phase 215-2: Pass expr_result to boundary
let mut boundary_builder = JoinInlineBoundaryBuilder::new()
.with_inputs(join_inputs, host_inputs)
.with_exit_bindings(exit_bindings)
.with_loop_var_name(Some(ctx.loop_var_name.clone()))
.build();
.with_loop_var_name(Some(ctx.loop_var_name.clone()));
// Add expr_result if present
if let Some(expr_id) = fragment_meta.expr_result {
trace::trace().debug(
"pattern3/if-sum",
&format!("Passing expr_result={:?} to boundary", expr_id)
);
boundary_builder = boundary_builder.with_expr_result(Some(expr_id));
}
let boundary = boundary_builder.build();
// Execute JoinIR conversion pipeline
use super::conversion_pipeline::JoinIRConversionPipeline;
let _ = JoinIRConversionPipeline::execute(
let merge_result = JoinIRConversionPipeline::execute(
self,
join_module,
Some(&boundary),
@ -189,11 +200,23 @@ impl MirBuilder {
debug,
)?;
// Return Void (loop doesn't produce values)
use crate::mir::builder::emission::constant;
let void_val = constant::emit_void(self);
trace::trace().debug("pattern3/if-sum", &format!("Loop complete, returning Void {:?}", void_val));
Ok(Some(void_val))
// Phase 215-2: Return expr_result if present (expr-position loop)
if let Some(expr_val) = merge_result {
trace::trace().debug(
"pattern3/if-sum",
&format!("Loop complete, returning expr_result {:?}", expr_val)
);
Ok(Some(expr_val))
} else {
// Statement-position loop (carrier-only)
use crate::mir::builder::emission::constant;
let void_val = constant::emit_void(self);
trace::trace().debug(
"pattern3/if-sum",
&format!("Loop complete, returning Void {:?}", void_val)
);
Ok(Some(void_val))
}
}
/// Phase 188-195: Legacy PoC lowerer (hardcoded conditions)