refactor(joinir): Phase 255 P2 - loop_invariants + var() unification + Phase 256 prep

## Task A: Loop Invariants Architecture (Option A - Boundary Extension)

Introduced explicit loop_invariants concept for variables that are:
- Used in loop body but never modified (e.g., substring needle in index_of)
- Need header PHI (all iterations use same value)
- Do NOT need exit PHI (not a LoopState)

Implementation:
- Added `loop_invariants: Vec<(String, ValueId)>` field to JoinInlineBoundary
- Added `with_loop_invariants()` method to JoinInlineBoundaryBuilder
- Modified Pattern 6 to use loop_invariants instead of ConditionOnly misuse
  * s (haystack) and ch (needle) now properly classified
  * exit_bindings simplified to only LoopState carriers
- Extended LoopHeaderPhiBuilder to generate invariant PHIs
  * Entry PHI: from host
  * Latch PHI: self-reference (same value every iteration)
- Updated instruction_rewriter to handle invariant latch incoming

Files Modified:
- src/mir/join_ir/lowering/inline_boundary.rs (structure + builder)
- src/mir/join_ir/lowering/inline_boundary_builder.rs (builder method)
- src/mir/builder/control_flow/joinir/patterns/pattern6_scan_with_init.rs
- src/mir/builder/control_flow/joinir/merge/loop_header_phi_builder.rs
- src/mir/builder/control_flow/joinir/merge/mod.rs
- src/mir/builder/control_flow/joinir/merge/instruction_rewriter.rs

## Task B: Code Quality - var() Helper Unification

Created common module to eliminate var() duplication:
- New module: src/mir/builder/control_flow/joinir/patterns/common/
- Centralized var() helper in ast_helpers.rs
- Updated Pattern 6 and Pattern 2 to use common var()
- Test code (5 occurrences) deferred to Phase 256+ per 80/20 rule

Result: Eliminated 2 duplicate var() functions, 5 test occurrences remain

Files Created:
- src/mir/builder/control_flow/joinir/patterns/common/ast_helpers.rs
- src/mir/builder/control_flow/joinir/patterns/common/mod.rs

Files Modified:
- src/mir/builder/control_flow/joinir/patterns/mod.rs
- src/mir/builder/control_flow/joinir/patterns/pattern6_scan_with_init.rs
- src/mir/builder/control_flow/joinir/patterns/policies/balanced_depth_scan_policy.rs

## Task C: Documentation - PostLoopEarlyReturnPlan Usage Examples

Enhanced post_loop_early_return_plan.rs with:
- Architecture explanation (exit PHI usage prevents DCE)
- Pattern 2 example (Less: balanced_depth_scan)
- Pattern 6 example (NotEqual: index_of)
- Builder defer decision: when 4+ patterns emerge, add builder

Files Modified:
- src/mir/builder/control_flow/joinir/patterns/policies/post_loop_early_return_plan.rs

## Task D: Phase 256 Preparation

Analyzed next failure from smoke tests:
- `StringUtils.split/2` with variable-step loop
- Not constant step (i += len or similar)
- Three implementation options identified

Created Phase 256 README with:
- Minimal reproduction code
- Root cause analysis
- Implementation options with trade-offs
- Clear next steps

Files Created:
- docs/development/current/main/phases/phase-256/README.md

## Verification Results

 All tests passing:
- pattern254_p0_index_of_vm.sh: PASS
- No regression in Pattern 1-5
- Smoke test progresses past json_lint_vm to next failure

 Architecture achievements:
- Semantic clarity: loop_invariants vs exit_bindings properly separated
- ConditionOnly misuse eliminated
- PHI generation correct for all carrier types
- Code quality: var() duplication reduced
- Next phase clearly defined

## Summary

Phase 255 P2 achieves root-cause fix for multi-param loop support:
- Boundary concept expanded (loop_invariants field)
- Pattern 6 architecture corrected (no ConditionOnly misuse)
- Code quality improved (var() centralized)
- Next pattern (variable-step) is now the challenge

 Box-first principles maintained: clean separation, explicit roles, minimal coupling

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

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-19 23:48:49 +09:00
parent 2d9c6ea3c6
commit 575a5d750f
12 changed files with 408 additions and 60 deletions

View File

@ -158,6 +158,50 @@ pub struct JoinInlineBoundary {
#[deprecated(since = "Phase 190", note = "Use exit_bindings instead")]
pub host_outputs: Vec<ValueId>,
/// Phase 255 P2: Loop invariant variables
///
/// Variables that are referenced inside the loop body but do not change
/// across iterations. These variables need header PHI nodes (with the same
/// value from all incoming edges) but do NOT need exit PHI nodes.
///
/// # Example: index_of(s, ch)
///
/// ```nyash
/// index_of(s, ch) {
/// local i = 0
/// loop(i < s.length()) {
/// if s.substring(i, i + 1) == ch { return i }
/// i = i + 1
/// }
/// return -1
/// }
/// ```
///
/// Here:
/// - `s` (haystack): loop invariant - used in loop body but never modified
/// - `ch` (needle): loop invariant - used in loop body but never modified
/// - `i` (loop index): loop state - modified each iteration (goes in exit_bindings)
///
/// # Format
///
/// Each entry is `(variable_name, host_value_id)`:
/// ```
/// loop_invariants: vec![
/// ("s".to_string(), ValueId(10)), // HOST ID for "s"
/// ("ch".to_string(), ValueId(11)), // HOST ID for "ch"
/// ]
/// ```
///
/// # Header PHI Generation
///
/// For each loop invariant, LoopHeaderPhiBuilder generates:
/// ```mir
/// %phi_dst = phi [%host_id, entry], [%phi_dst, latch]
/// ```
///
/// The latch incoming is the PHI destination itself (same value preserved).
pub loop_invariants: Vec<(String, ValueId)>,
/// Explicit exit bindings for loop carriers (Phase 190+)
///
/// Each binding explicitly names which variable is being updated and
@ -323,6 +367,7 @@ impl JoinInlineBoundary {
join_outputs: vec![],
#[allow(deprecated)]
host_outputs: vec![],
loop_invariants: vec![], // Phase 255 P2: Default to empty
exit_bindings: vec![],
#[allow(deprecated)]
condition_inputs: vec![], // Phase 171: Default to empty (deprecated)
@ -368,6 +413,7 @@ impl JoinInlineBoundary {
join_outputs: vec![],
#[allow(deprecated)]
host_outputs,
loop_invariants: vec![], // Phase 255 P2: Default to empty
exit_bindings: vec![],
#[allow(deprecated)]
condition_inputs: vec![], // Phase 171: Default to empty (deprecated)
@ -430,6 +476,7 @@ impl JoinInlineBoundary {
join_outputs: vec![],
#[allow(deprecated)]
host_outputs: vec![],
loop_invariants: vec![], // Phase 255 P2: Default to empty
exit_bindings,
#[allow(deprecated)]
condition_inputs: vec![], // Phase 171: Default to empty (deprecated)
@ -478,6 +525,7 @@ impl JoinInlineBoundary {
join_outputs: vec![],
#[allow(deprecated)]
host_outputs: vec![],
loop_invariants: vec![], // Phase 255 P2: Default to empty
exit_bindings: vec![],
#[allow(deprecated)]
condition_inputs,
@ -530,6 +578,7 @@ impl JoinInlineBoundary {
join_outputs: vec![],
#[allow(deprecated)]
host_outputs: vec![],
loop_invariants: vec![], // Phase 255 P2: Default to empty
exit_bindings,
#[allow(deprecated)]
condition_inputs,
@ -589,6 +638,7 @@ impl JoinInlineBoundary {
join_outputs: vec![],
#[allow(deprecated)]
host_outputs: vec![],
loop_invariants: vec![], // Phase 255 P2: Default to empty
exit_bindings: vec![],
#[allow(deprecated)]
condition_inputs: vec![], // Deprecated, use condition_bindings instead

View File

@ -90,6 +90,7 @@ impl JoinInlineBoundaryBuilder {
join_outputs: vec![],
#[allow(deprecated)]
host_outputs: vec![],
loop_invariants: vec![], // Phase 255 P2: Initialize as empty
exit_bindings: vec![],
#[allow(deprecated)]
condition_inputs: vec![],
@ -155,6 +156,25 @@ impl JoinInlineBoundaryBuilder {
self
}
/// Phase 255 P2: Set loop invariants
///
/// Variables that are referenced inside the loop body but do not change
/// across iterations. These need header PHI nodes (with same value from all
/// incoming edges) but do NOT need exit PHI nodes.
///
/// # Example
///
/// ```ignore
/// builder.with_loop_invariants(vec![
/// ("s".to_string(), ValueId(10)), // haystack
/// ("ch".to_string(), ValueId(11)), // needle
/// ])
/// ```
pub fn with_loop_invariants(mut self, invariants: Vec<(String, ValueId)>) -> Self {
self.boundary.loop_invariants = invariants;
self
}
/// Set loop variable name (Phase 33-16)
///
/// Used for LoopHeaderPhiBuilder to track which PHI corresponds to the loop variable.