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

@ -6,6 +6,7 @@
//! - Fail-fast with tagged reasons when the shape is close but unsupported
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span};
use crate::mir::builder::control_flow::joinir::patterns::common::var; // Phase 255 P2: Use shared var() helper
use crate::mir::join_ir::lowering::common::balanced_depth_scan_emitter::BalancedDepthScanRecipe;
use crate::mir::join_ir::lowering::error_tags;
use crate::mir::join_ir::lowering::loop_update_analyzer::{UpdateExpr, UpdateRhs};
@ -449,12 +450,7 @@ fn is_var_plus_int(node: &ASTNode, var_name: &str, n: i64) -> bool {
)
}
fn var(name: &str) -> ASTNode {
ASTNode::Variable {
name: name.to_string(),
span: Span::unknown(),
}
}
// Phase 255 P2: var() function removed - now using crate::mir::builder::control_flow::joinir::patterns::common::var
fn eq_str(left: ASTNode, s: &str) -> ASTNode {
ASTNode::BinaryOp {

View File

@ -1,15 +1,67 @@
//! Post-loop early return plan (policy-level SSOT)
//! Phase 255 P2: Post-loop early return plan (policy-level SSOT)
//!
//! # Responsibility
//!
//! Responsibility:
//! - Describe a post-loop guard that emulates an in-loop `return` without making
//! Pattern2 lowering itself return-in-loop aware.
//! - Keep the plan policy-agnostic so multiple Pattern2 families can reuse it.
//!
//! # Architecture
//!
//! Ensures exit PHI values are used (prevents DCE elimination). The post-loop
//! guard creates a conditional that references the exit PHI value, forcing it
//! to be live and preventing dead code elimination.
//!
//! # Usage Patterns
//!
//! ## Pattern 2: Less Than (balanced_depth_scan)
//!
//! Used in `json_cur.find_balanced_*` family functions.
//!
//! ```ignore
//! PostLoopEarlyReturnPlan {
//! cond: BinaryOp { Less, var("i"), var("n") },
//! ret_expr: var("i"),
//! }
//! ```
//!
//! Generated post-loop guard:
//! ```nyash
//! if i < n {
//! return i
//! }
//! ```
//!
//! ## Pattern 6: Not Equal (index_of)
//!
//! Used in `StringUtils.index_of` and similar search functions.
//!
//! ```ignore
//! PostLoopEarlyReturnPlan {
//! cond: BinaryOp { NotEqual, var("i"), Literal(-1) },
//! ret_expr: var("i"),
//! }
//! ```
//!
//! Generated post-loop guard:
//! ```nyash
//! if i != -1 {
//! return i
//! }
//! ```
//!
//! # Builder Decision
//!
//! Deferred to Phase 256+. Currently 2 patterns use this (Pattern 2 and Pattern 6).
//! Direct construction is acceptable. Will create builder when 4+ patterns use it.
use crate::ast::ASTNode;
#[derive(Debug, Clone)]
pub(crate) struct PostLoopEarlyReturnPlan {
/// Condition for the post-loop guard (e.g., `i < n` or `i != -1`)
pub cond: ASTNode,
/// Expression to return if condition is true (e.g., `var("i")`)
pub ret_expr: ASTNode,
}