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:
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
Reference in New Issue
Block a user