From 02a312a522c58a62efecb8d9747d09929588ed05 Mon Sep 17 00:00:00 2001 From: tomoaki Date: Wed, 31 Dec 2025 06:49:41 +0900 Subject: [PATCH] phase29ap(p8): remove pattern4 legacy routing --- .../phase29ap_pattern4_continue_min.hako | 17 + docs/development/current/main/10-Now.md | 2 +- docs/development/current/main/30-Backlog.md | 2 +- .../design/coreplan-migration-roadmap-ssot.md | 2 +- .../current/main/phases/phase-29ae/README.md | 1 + .../current/main/phases/phase-29ap/README.md | 15 +- .../joinir/patterns/extractors/mod.rs | 2 +- .../joinir/patterns/extractors/pattern4.rs | 3 - .../control_flow/joinir/patterns/mod.rs | 7 +- .../patterns/pattern4_carrier_analyzer.rs | 366 ------------ .../joinir/patterns/pattern4_with_continue.rs | 534 ------------------ .../control_flow/joinir/patterns/router.rs | 29 +- .../joinir/phase29ae_regression_pack_vm.sh | 1 + .../phase29ap_pattern4_continue_min_vm.sh | 16 + 14 files changed, 61 insertions(+), 936 deletions(-) create mode 100644 apps/tests/phase29ap_pattern4_continue_min.hako delete mode 100644 src/mir/builder/control_flow/joinir/patterns/extractors/pattern4.rs delete mode 100644 src/mir/builder/control_flow/joinir/patterns/pattern4_carrier_analyzer.rs delete mode 100644 src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs create mode 100644 tools/smokes/v2/profiles/integration/joinir/phase29ap_pattern4_continue_min_vm.sh diff --git a/apps/tests/phase29ap_pattern4_continue_min.hako b/apps/tests/phase29ap_pattern4_continue_min.hako new file mode 100644 index 00000000..0ea16c92 --- /dev/null +++ b/apps/tests/phase29ap_pattern4_continue_min.hako @@ -0,0 +1,17 @@ +// Phase 29ap P8: Pattern4 continue minimal fixture (plan routing) +// Expected output: 6 + +static box Main { + main() { + local i, sum + i = 0 + sum = 0 + loop(i < 4) { + if (i == 0) { i = i + 1; continue } + sum = sum + i + i = i + 1 + } + print(sum) + return 0 + } +} diff --git a/docs/development/current/main/10-Now.md b/docs/development/current/main/10-Now.md index a8f3eed1..62f34b55 100644 --- a/docs/development/current/main/10-Now.md +++ b/docs/development/current/main/10-Now.md @@ -3,7 +3,7 @@ ## Current Focus - Phase: `docs/development/current/main/phases/phase-29ap/README.md` -- Next: Phase 29ap P8 (planned; see `docs/development/current/main/phases/phase-29ap/README.md`) +- Next: Phase 29ap P9 (planned; see `docs/development/current/main/phases/phase-29ap/README.md`) ## Gate (SSOT) diff --git a/docs/development/current/main/30-Backlog.md b/docs/development/current/main/30-Backlog.md index d3cf6c96..02b2cd35 100644 --- a/docs/development/current/main/30-Backlog.md +++ b/docs/development/current/main/30-Backlog.md @@ -5,7 +5,7 @@ Scope: 「次にやる候補」を短く列挙するメモ。入口は `docs/dev ## Active -- Phase 29ap: `docs/development/current/main/phases/phase-29ap/README.md` (Next: P8 planned) +- Phase 29ap: `docs/development/current/main/phases/phase-29ap/README.md` (Next: P9 planned) - JoinIR regression gate SSOT: `docs/development/current/main/phases/phase-29ae/README.md` - CorePlan hardening (docs-first): `docs/development/current/main/phases/phase-29al/README.md` diff --git a/docs/development/current/main/design/coreplan-migration-roadmap-ssot.md b/docs/development/current/main/design/coreplan-migration-roadmap-ssot.md index 80fef151..781212ce 100644 --- a/docs/development/current/main/design/coreplan-migration-roadmap-ssot.md +++ b/docs/development/current/main/design/coreplan-migration-roadmap-ssot.md @@ -34,7 +34,7 @@ Related: ## 1.1 Current (active) - Active phase: `docs/development/current/main/phases/phase-29ap/README.md` -- Next step: Phase 29ap P8 (planned) +- Next step: Phase 29ap P9 (planned) ## 2. すでに固めた SSOT(再発防止の土台) diff --git a/docs/development/current/main/phases/phase-29ae/README.md b/docs/development/current/main/phases/phase-29ae/README.md index 7771d9fb..28df800e 100644 --- a/docs/development/current/main/phases/phase-29ae/README.md +++ b/docs/development/current/main/phases/phase-29ae/README.md @@ -10,6 +10,7 @@ Goal: JoinIR の最小回帰セットを SSOT として固定する。 - Pattern2 (release adopt, VM): `phase29ao_pattern2_release_adopt_vm` - Pattern3 (If‑Phi, VM): `phase118_pattern3_if_sum_vm` - Pattern3 (release adopt, VM): `phase29ao_pattern3_release_adopt_vm` +- Pattern4 (continue min, VM): `phase29ap_pattern4_continue_min_vm` - Pattern1 (strict shadow, VM): `phase29ao_pattern1_strict_shadow_vm` - Pattern1 (subset reject, VM): `phase29ao_pattern1_subset_reject_extra_stmt_vm` - Pattern1 (stdlib to_lower, VM): `phase29ap_stringutils_tolower_vm` diff --git a/docs/development/current/main/phases/phase-29ap/README.md b/docs/development/current/main/phases/phase-29ap/README.md index 14e8ab83..03bacbc1 100644 --- a/docs/development/current/main/phases/phase-29ap/README.md +++ b/docs/development/current/main/phases/phase-29ap/README.md @@ -57,7 +57,7 @@ Gate (SSOT): - Scope: - Remove Pattern8 from `LOOP_PATTERNS` so plan/composer stays SSOT for normal loops. - - Keep legacy table as last resort for Pattern6_NestedLoopMinimal / Pattern4 / Pattern9 only. + - Keep legacy table as a last resort for the remaining legacy entries. - Guardrails: - No change to logs or error strings. - Legacy routing remains a last-resort fallback. @@ -81,8 +81,6 @@ Gate (SSOT): - No new logs or error strings. - Subset only: if shape deviates, return `Ok(None)`. -## Next (planned) - ## P7: Pattern9 legacy table removal ✅ - Scope: @@ -91,6 +89,15 @@ Gate (SSOT): - Guardrails: - No change to logs or error strings. +## P8: Remove Pattern4 legacy routing ✅ + +- Scope: + - Remove Pattern4_WithContinue from `LOOP_PATTERNS`. + - Add a minimal continue fixture + regression smoke under JoinIR gate. +- Guardrails: + - No change to logs or error strings. + - Plan/composer remains the only routing path for Pattern4. + ## Next (planned) -- P8: Follow-up legacy table shrink or leave as-is with justification +- P9: Pattern6_NestedLoopMinimal migration/design (keep phase1883 gate green) diff --git a/src/mir/builder/control_flow/joinir/patterns/extractors/mod.rs b/src/mir/builder/control_flow/joinir/patterns/extractors/mod.rs index 12900574..56d58383 100644 --- a/src/mir/builder/control_flow/joinir/patterns/extractors/mod.rs +++ b/src/mir/builder/control_flow/joinir/patterns/extractors/mod.rs @@ -23,5 +23,5 @@ pub(crate) mod pattern1; pub(crate) mod pattern2; // Phase 282 P4: Pattern2 extraction pub(crate) mod pattern3; // Phase 282 P5: Pattern3 extraction -pub(crate) mod pattern4; // Phase 282 P6: Pattern4 extraction +// Pattern4 extraction migrated to plan routing (Phase 29ap P8). // Pattern5/8/9 extractors moved to plan layer; joinir uses plan::single_planner routing. diff --git a/src/mir/builder/control_flow/joinir/patterns/extractors/pattern4.rs b/src/mir/builder/control_flow/joinir/patterns/extractors/pattern4.rs deleted file mode 100644 index 1cf2aa70..00000000 --- a/src/mir/builder/control_flow/joinir/patterns/extractors/pattern4.rs +++ /dev/null @@ -1,3 +0,0 @@ -//! Phase 29aj P1: Plan-layer extractor wrapper (SSOT) - -pub(crate) use crate::mir::builder::control_flow::plan::extractors::pattern4::*; diff --git a/src/mir/builder/control_flow/joinir/patterns/mod.rs b/src/mir/builder/control_flow/joinir/patterns/mod.rs index f0adde50..ad2e6fe4 100644 --- a/src/mir/builder/control_flow/joinir/patterns/mod.rs +++ b/src/mir/builder/control_flow/joinir/patterns/mod.rs @@ -3,7 +3,7 @@ //! Phase 2: Extracted from control_flow.rs //! - Pattern 1: Simple While Loop (pattern1_minimal.rs) //! - Pattern 3: Loop with If-Else PHI (pattern3_with_if_phi.rs) -//! - Pattern 4: Loop with Continue (pattern4_with_continue.rs) [Phase 194+] +//! - Pattern 4: Loop with Continue (migrated to plan routing in Phase 29ap P8) //! //! Phase 194: Table-driven router for pattern dispatch //! - Router module provides table-driven pattern matching @@ -31,14 +31,13 @@ //! - condition_env_builder.rs: Unified ConditionEnv construction (Issue 5) //! //! Phase 33-23: Pattern-Specific Analyzers (Stage 2) -//! - pattern4_carrier_analyzer.rs: Pattern 4 carrier analysis and normalization (Issue 2) //! //! Stage 3 + Issue 1: Trim Pattern Extraction //! - trim_pattern_validator.rs: Trim pattern validation and whitespace check generation //! - trim_pattern_lowerer.rs: Trim-specific JoinIR lowering //! //! Phase 179-B: Generic Pattern Framework -//! - pattern_pipeline.rs: Unified preprocessing pipeline for Patterns 1-4 +//! - pattern_pipeline.rs: Unified preprocessing pipeline for Patterns 1-3 //! //! Phase 91 P5b: Escape Pattern Recognizer //! - escape_pattern_recognizer.rs: P5b (escape sequence handling) pattern detection @@ -78,8 +77,6 @@ pub(in crate::mir::builder) mod exit_binding_validator; // Phase 222.5-C pub(in crate::mir::builder) mod loop_scope_shape_builder; pub(in crate::mir::builder) mod pattern1_minimal; pub(in crate::mir::builder) mod pattern3_with_if_phi; -pub(in crate::mir::builder) mod pattern4_carrier_analyzer; -pub(in crate::mir::builder) mod pattern4_with_continue; pub(in crate::mir::builder) mod pattern6_nested_minimal; // Phase 188.3: 1-level nested loop (Pattern1 outer + Pattern1 inner) pub(in crate::mir::builder) mod pattern8_scan_bool_predicate; // Phase 259 P0: boolean predicate scan (is_integer/is_valid) pub(in crate::mir::builder) mod pattern_pipeline; diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern4_carrier_analyzer.rs b/src/mir/builder/control_flow/joinir/patterns/pattern4_carrier_analyzer.rs deleted file mode 100644 index fcdcb5ee..00000000 --- a/src/mir/builder/control_flow/joinir/patterns/pattern4_carrier_analyzer.rs +++ /dev/null @@ -1,366 +0,0 @@ -//! Phase 33-23: Pattern 4 Carrier Analysis -//! -//! Extracts carrier analysis logic from pattern4_with_continue.rs. -//! Responsible for: -//! - Identifying which carriers are updated by continue branches -//! - Normalizing else-continue clauses -//! - Filtering out invalid carriers -//! -//! # Design Philosophy -//! -//! - **Single responsibility**: Focus only on carrier analysis for Pattern 4 -//! - **Reusability**: Can be used by future continue-pattern variants -//! - **Testability**: Pure functions that are easy to unit test -//! - **Independence**: Does not depend on MirBuilder context - -use crate::ast::ASTNode; -use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, CarrierVar}; -use crate::mir::join_ir::lowering::continue_branch_normalizer::ContinueBranchNormalizer; -use crate::mir::join_ir::lowering::loop_update_analyzer::{LoopUpdateAnalyzer, UpdateExpr}; -use std::collections::BTreeMap; // Phase 222.5-D: HashMap → BTreeMap for determinism - -pub(crate) struct Pattern4CarrierAnalyzer; - -impl Pattern4CarrierAnalyzer { - /// Analyze and filter carriers for continue pattern - /// - /// Identifies which carriers are actually updated by continue branches - /// and filters out carriers that don't participate in the loop body. - /// - /// # Arguments - /// - /// * `loop_body` - The loop body AST nodes to analyze - /// * `all_carriers` - All potential carrier variables from initial analysis - /// - /// # Returns - /// - /// CarrierInfo containing only the carriers that are actually updated - /// - /// # Example - /// - /// ```nyash - /// local i = 0 - /// local sum = 0 - /// local M = 10 // Constant, should be filtered out - /// loop(i < M) { - /// i = i + 1 - /// if i % 2 == 0 { continue } - /// sum = sum + i - /// } - /// // Result: carriers = [i, sum], NOT [i, sum, M] - /// ``` - pub fn analyze_carriers( - loop_body: &[ASTNode], - all_carriers: &CarrierInfo, - ) -> Result { - // Identify which carriers are updated in loop body - let carrier_updates = - LoopUpdateAnalyzer::analyze_carrier_updates(loop_body, &all_carriers.carriers); - - // Filter carriers: only keep those that have update expressions - let updated_carriers: Vec = all_carriers - .carriers - .iter() - .filter(|carrier| carrier_updates.contains_key(&carrier.name)) - .cloned() - .collect(); - - Ok(CarrierInfo { - loop_var_name: all_carriers.loop_var_name.clone(), - loop_var_id: all_carriers.loop_var_id, - carriers: updated_carriers, - trim_helper: all_carriers.trim_helper.clone(), - promoted_loopbodylocals: all_carriers.promoted_loopbodylocals.clone(), // Phase 224 - #[cfg(feature = "normalized_dev")] - promoted_bindings: all_carriers.promoted_bindings.clone(), // Phase 76 - }) - } - - /// Analyze carrier update expressions - /// - /// Delegates to LoopUpdateAnalyzer but returns the result in a more - /// convenient form for Pattern 4 processing. - /// - /// # Returns - /// - /// Map from carrier name to UpdateExpr - pub fn analyze_carrier_updates( - loop_body: &[ASTNode], - carriers: &[CarrierVar], - ) -> BTreeMap { - // Phase 222.5-D: HashMap → BTreeMap for determinism - LoopUpdateAnalyzer::analyze_carrier_updates(loop_body, carriers) - } - - /// Normalize continue branches to standard form - /// - /// Transforms else-continue patterns to a canonical form - /// for easier JoinIR lowering. - /// - /// # Pattern - /// - /// Transforms: `if (cond) { body } else { continue }` - /// Into: `if (!cond) { continue } else { body }` - /// - /// # Arguments - /// - /// * `body` - Loop body statements to normalize - /// - /// # Returns - /// - /// Normalized loop body with all continue statements in then branches - pub fn normalize_continue_branches(body: &[ASTNode]) -> Vec { - ContinueBranchNormalizer::normalize_loop_body(body) - } - - /// Verify continue pattern structure - /// - /// Ensures the loop has proper continue structure for Pattern 4. - /// - /// # Arguments - /// - /// * `body` - Loop body statements to validate - /// - /// # Returns - /// - /// Ok(()) if continue structure is valid, Err(message) otherwise - #[allow(dead_code)] - pub fn validate_continue_structure(body: &[ASTNode]) -> Result<(), String> { - // Check for at least one continue statement - for stmt in body { - if Self::has_continue(stmt) { - return Ok(()); - } - } - Err("No continue statement found in loop body".to_string()) - } - - /// Helper: Check if node or its children contain continue - /// - /// Recursively searches the AST for continue statements. - /// - /// # Arguments - /// - /// * `node` - AST node to check - /// - /// # Returns - /// - /// true if the node or any of its children is a Continue statement - #[allow(dead_code)] - fn has_continue(node: &ASTNode) -> bool { - match node { - ASTNode::Continue { .. } => true, - ASTNode::If { - then_body, - else_body, - .. - } => { - then_body.iter().any(|n| Self::has_continue(n)) - || else_body - .as_ref() - .map_or(false, |body| body.iter().any(|n| Self::has_continue(n))) - } - ASTNode::Loop { body, .. } => body.iter().any(|n| Self::has_continue(n)), - _ => false, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::ast::{BinaryOperator, LiteralValue, Span}; - use crate::mir::ValueId; - - #[test] - fn test_validate_continue_structure_present() { - // Test with continue statement - let body = vec![ASTNode::Continue { - span: Span::unknown(), - }]; - assert!(Pattern4CarrierAnalyzer::validate_continue_structure(&body).is_ok()); - } - - #[test] - fn test_validate_continue_structure_absent() { - // Test without continue statement - let body = vec![ASTNode::Break { - span: Span::unknown(), - }]; - assert!(Pattern4CarrierAnalyzer::validate_continue_structure(&body).is_err()); - } - - #[test] - fn test_has_continue_detection() { - let continue_node = ASTNode::Continue { - span: Span::unknown(), - }; - assert!(Pattern4CarrierAnalyzer::has_continue(&continue_node)); - - let break_node = ASTNode::Break { - span: Span::unknown(), - }; - assert!(!Pattern4CarrierAnalyzer::has_continue(&break_node)); - } - - #[test] - fn test_has_continue_nested_in_if() { - // if (x) { continue } else { ... } - let if_node = ASTNode::If { - condition: Box::new(ASTNode::Variable { - name: "x".to_string(), - span: Span::unknown(), - }), - then_body: vec![ASTNode::Continue { - span: Span::unknown(), - }], - else_body: None, - span: Span::unknown(), - }; - assert!(Pattern4CarrierAnalyzer::has_continue(&if_node)); - } - - #[test] - fn test_analyze_carriers_filtering() { - // Test that analyze_carriers filters out non-updated carriers - let span = Span::unknown(); - - // Create loop body: i = i + 1, sum = sum + i - let loop_body = vec![ - ASTNode::Assignment { - target: Box::new(ASTNode::Variable { - name: "i".to_string(), - span: span.clone(), - }), - value: Box::new(ASTNode::BinaryOp { - operator: BinaryOperator::Add, - left: Box::new(ASTNode::Variable { - name: "i".to_string(), - span: span.clone(), - }), - right: Box::new(ASTNode::Literal { - value: LiteralValue::Integer(1), - span: span.clone(), - }), - span: span.clone(), - }), - span: span.clone(), - }, - ASTNode::Assignment { - target: Box::new(ASTNode::Variable { - name: "sum".to_string(), - span: span.clone(), - }), - value: Box::new(ASTNode::BinaryOp { - operator: BinaryOperator::Add, - left: Box::new(ASTNode::Variable { - name: "sum".to_string(), - span: span.clone(), - }), - right: Box::new(ASTNode::Variable { - name: "i".to_string(), - span: span.clone(), - }), - span: span.clone(), - }), - span: span.clone(), - }, - ]; - - // Create CarrierInfo with i, sum, and M (constant) - let all_carriers = CarrierInfo { - loop_var_name: "i".to_string(), - loop_var_id: ValueId(0), - carriers: vec![ - CarrierVar { - name: "i".to_string(), - host_id: ValueId(1), - join_id: None, - role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, - init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 - #[cfg(feature = "normalized_dev")] - binding_id: None, - }, - CarrierVar { - name: "sum".to_string(), - host_id: ValueId(2), - join_id: None, - role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, - init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 - #[cfg(feature = "normalized_dev")] - binding_id: None, - }, - CarrierVar { - name: "M".to_string(), - host_id: ValueId(3), - join_id: None, - role: crate::mir::join_ir::lowering::carrier_info::CarrierRole::LoopState, - init: crate::mir::join_ir::lowering::carrier_info::CarrierInit::FromHost, // Phase 228 - #[cfg(feature = "normalized_dev")] - binding_id: None, - }, - ], - trim_helper: None, - promoted_loopbodylocals: Vec::new(), // Phase 224 - #[cfg(feature = "normalized_dev")] - promoted_bindings: std::collections::BTreeMap::new(), // Phase 76 - }; - - // Analyze carriers - let result = Pattern4CarrierAnalyzer::analyze_carriers(&loop_body, &all_carriers); - assert!(result.is_ok()); - - let filtered = result.unwrap(); - // Should only have i and sum, not M - assert_eq!(filtered.carriers.len(), 2); - assert!(filtered.carriers.iter().any(|c| c.name == "i")); - assert!(filtered.carriers.iter().any(|c| c.name == "sum")); - assert!(!filtered.carriers.iter().any(|c| c.name == "M")); - } - - #[test] - fn test_normalize_continue_branches() { - // Test normalization delegation - let span = Span::unknown(); - - // Create if-else-continue pattern - let body = vec![ASTNode::If { - condition: Box::new(ASTNode::Variable { - name: "x".to_string(), - span: span.clone(), - }), - then_body: vec![ASTNode::Assignment { - target: Box::new(ASTNode::Variable { - name: "y".to_string(), - span: span.clone(), - }), - value: Box::new(ASTNode::Literal { - value: LiteralValue::Integer(1), - span: span.clone(), - }), - span: span.clone(), - }], - else_body: Some(vec![ASTNode::Continue { span: span.clone() }]), - span: span.clone(), - }]; - - let normalized = Pattern4CarrierAnalyzer::normalize_continue_branches(&body); - - // Should be transformed to if (!x) { continue } else { y = 1 } - assert_eq!(normalized.len(), 1); - if let ASTNode::If { - condition, - then_body, - .. - } = &normalized[0] - { - // Condition should be negated - assert!(matches!(**condition, ASTNode::UnaryOp { .. })); - // Then body should be continue - assert_eq!(then_body.len(), 1); - assert!(matches!(then_body[0], ASTNode::Continue { .. })); - } else { - panic!("Expected If node"); - } - } -} diff --git a/src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs b/src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs deleted file mode 100644 index 156cf32c..00000000 --- a/src/mir/builder/control_flow/joinir/patterns/pattern4_with_continue.rs +++ /dev/null @@ -1,534 +0,0 @@ -//! Phase 33-21: Pattern 4 (Loop with Continue) Implementation -//! -//! **Current Status**: Fully implemented using Select-based continue semantics. -//! -//! ## Implementation Approach -//! -//! Continue is handled via Select instruction for carrier updates: -//! - When continue condition is true: carrier keeps its value (no update) -//! - When continue condition is false: carrier gets updated value -//! -//! This avoids the complexity of explicit latch blocks by using PHI-based -//! value selection at each iteration. -//! -//! ## Example -//! -//! ```nyash -//! local i = 0 -//! local sum = 0 -//! loop(i < 10) { -//! i = i + 1 -//! if i % 2 == 0 { continue } // Skip even numbers -//! sum = sum + i -//! } -//! // sum = 25 (1+3+5+7+9) -//! ``` -//! -//! ## Key Implementation Details -//! -//! - **Select instruction**: Used to conditionally skip carrier updates -//! - **Header PHI**: Tracks carrier values across iterations -//! - **ExitMeta**: Maps final carrier values to host variable slots -//! - **Phase 33-21 fix**: Correct remapping of function parameters to header PHI dsts - -use super::super::trace; -use crate::ast::ASTNode; -use crate::mir::builder::MirBuilder; -use crate::mir::join_ir::lowering::loop_update_analyzer::UpdateExpr; -use crate::mir::loop_pattern_detection::error_messages; -use crate::mir::ValueId; -use std::collections::BTreeMap; - -// Phase 284 P1: has_return_in_body/has_return_node removed -// Return detection now handled by return_collector SSOT in conversion_pipeline.rs - -/// Phase 282 P6: Detection function for Pattern 4 (ExtractionBased) -/// -/// Migrated from StructureBased to ExtractionBased detection model. -/// Uses pure extractor (extract_loop_with_continue_parts) for SSOT. -/// -/// Pattern 4 matches loops with continue statements. -/// -/// # ExtractionBased Detection (Phase 282 P6) -/// -/// Two-stage guard: -/// 1. **Safety valve**: ctx.pattern_kind == Pattern4Continue (O(1) perf guard) -/// 2. **Extractor**: extract_loop_with_continue_parts (SSOT) -/// -/// # Detection Rules (from extractor) -/// -/// 1. **Condition**: Comparison operator with left=variable (Pattern1 reuse) -/// 2. **Must have continue**: At least 1 continue statement (recursive detection) -/// 3. **No break**: break statements → Pattern2 territory -/// 4. **No return**: return statements → Err (close-but-unsupported, Phase 142 P2 pending) -/// 5. **Carrier validation**: Delegated to CommonPatternInitializer (existing logic) -/// -/// If all conditions are met, Pattern 4 is detected. -pub(crate) fn can_lower(builder: &MirBuilder, ctx: &super::router::LoopPatternContext) -> bool { - use super::common_init::CommonPatternInitializer; - use crate::mir::loop_pattern_detection::LoopPatternKind; - - // Phase 282 P6 Step 1: Pattern kind safety valve (O(1) guard) - if ctx.pattern_kind != LoopPatternKind::Pattern4Continue { - return false; - } - - // Phase 282 P6 Step 2: ExtractionBased detection (SSOT) - use super::extractors::pattern4::extract_loop_with_continue_parts; - - match extract_loop_with_continue_parts(ctx.condition, ctx.body) { - Ok(Some(parts)) => { - trace::trace().debug( - "pattern4/can_lower", - &format!( - "✅ Pattern4 detected: loop_var='{}', continue_count={}", - parts.loop_var, parts.continue_count - ), - ); - - // Phase 282 P6 Step 3: Carrier validation (existing logic preserved) - CommonPatternInitializer::check_carrier_updates_allowed( - ctx.body, - &parts.loop_var, - &builder.variable_ctx.variable_map, - ) - } - Ok(None) => { - trace::trace().debug( - "pattern4/can_lower", - "Not Pattern4 (extraction returned None - structural mismatch)", - ); - false - } - Err(e) => { - // USER CORRECTION: Log "unsupported" for Err cases (e.g., return found) - trace::trace().debug( - "pattern4/can_lower", - &format!("Pattern4 unsupported: {}", e), - ); - false - } - } -} - -/// Phase 282 P6: Lowering function for Pattern 4 (ExtractionBased) -/// -/// Re-extracts pattern parts to enforce SSOT (extraction is single source of truth). -/// Wrapper around cf_loop_pattern4_with_continue to match router signature. -/// -/// # Implementation (Phase 195-197 + Phase 282 P6) -/// -/// 1. Re-extract pattern parts (SSOT enforcement - Phase 282 P6) -/// 2. Extract loop variables from condition -/// 3. Generate JoinIR with continue support (lower_loop_with_continue_minimal) -/// 4. Convert JoinModule → MirModule -/// 5. Create JoinInlineBoundary for input/output mapping -/// 6. Merge MIR blocks into current_function -/// 7. Return loop result (first carrier value) -pub(crate) fn lower( - builder: &mut MirBuilder, - ctx: &super::router::LoopPatternContext, -) -> Result, String> { - // Phase 282 P6 Step 4: Re-extract to enforce SSOT (extraction must succeed in lower()) - use super::extractors::pattern4::extract_loop_with_continue_parts; - - let parts = match extract_loop_with_continue_parts(ctx.condition, ctx.body) { - Ok(Some(p)) => p, - Ok(None) => { - return Err( - "[pattern4/lower] Extraction returned None (should not happen - can_lower() passed)" - .to_string(), - ); - } - Err(e) => { - // USER CORRECTION: Return Err directly for fail-fast (e.g., return found) - return Err(format!("[pattern4/lower] Extraction failed: {}", e)); - } - }; - - trace::trace().debug( - "pattern4/lower", - &format!( - "Pattern4 lowering: loop_var='{}', continue_count={}", - parts.loop_var, parts.continue_count - ), - ); - - // Phase 284 P1: Return check removed - now handled by return_collector SSOT - // in conversion_pipeline.rs (JoinIR line common entry point) - - // Phase 33-19: Connect to actual implementation (zero behavior change) - builder.cf_loop_pattern4_with_continue(ctx.condition, ctx.body, ctx.func_name, ctx.debug) -} - -impl MirBuilder { - /// Phase 179-B: Pattern 4 (Loop with Continue) lowerer - /// - /// **Refactored**: Now uses PatternPipelineContext for unified preprocessing - /// - /// # Pipeline (Phase 179-B) - /// 1. Build preprocessing context → PatternPipelineContext - /// 2. Pattern 4-specific processing (continue normalization, carrier analysis) - /// 3. Call JoinIR lowerer → JoinModule - /// 4. Create boundary from context → JoinInlineBoundary - /// 5. Merge MIR blocks → JoinIRConversionPipeline - /// - /// # Example - /// - /// ```nyash - /// local i = 0 - /// local sum = 0 - /// loop(i < 10) { - /// i = i + 1 - /// if (i % 2 == 0) { - /// continue // Skip even numbers - /// } - /// sum = sum + i - /// } - /// // sum = 25 (1+3+5+7+9) - /// ``` - /// - /// Note: Pattern 4 has complex carrier analysis logic that remains inline - /// for now, using Pattern4CarrierAnalyzer for Select-based continue semantics. - pub(in crate::mir::builder) fn cf_loop_pattern4_with_continue( - &mut self, - condition: &ASTNode, - _body: &[ASTNode], - _func_name: &str, - debug: bool, - ) -> Result, String> { - // Phase 195: Use unified trace - trace::trace().debug("pattern4", "Calling Pattern 4 minimal lowerer"); - - let prepared = prepare_pattern4_context(self, condition, _body)?; - lower_pattern4_joinir(self, condition, &prepared, debug) - } -} - -/// Preprocessed data for Pattern 4 lowering. -struct Pattern4Prepared<'a> { - loop_var_name: String, - loop_scope: crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape, - carrier_info: crate::mir::join_ir::lowering::carrier_info::CarrierInfo, - carrier_updates: BTreeMap, - /// Phase 284 P1: Reference to loop body for return detection - body: &'a [ASTNode], -} - -/// Normalize, build context, analyze carriers, and promote loop-body locals. -fn prepare_pattern4_context<'a>( - builder: &mut MirBuilder, - condition: &ASTNode, - body: &'a [ASTNode], -) -> Result, String> { - use super::pattern4_carrier_analyzer::Pattern4CarrierAnalyzer; - use super::pattern_pipeline::{build_pattern_context, PatternVariant}; - use crate::mir::loop_pattern_detection::loop_body_cond_promoter::{ - ConditionPromotionRequest, ConditionPromotionResult, LoopBodyCondPromoter, - }; - use crate::mir::loop_pattern_detection::loop_condition_scope::LoopConditionScopeBox; - - // Normalize continue branches for analysis/lowering - let normalized_body = Pattern4CarrierAnalyzer::normalize_continue_branches(body); - - // Build preprocessing context - let ctx = build_pattern_context( - builder, - condition, - &normalized_body, - PatternVariant::Pattern4, - )?; - - let loop_var_name = ctx.loop_var_name.clone(); - let loop_scope = ctx.loop_scope.clone(); - let carrier_info_prelim = ctx.carrier_info.clone(); - - // Analyze carrier updates and filter carriers - let carrier_updates = Pattern4CarrierAnalyzer::analyze_carrier_updates( - &normalized_body, - &carrier_info_prelim.carriers, - ); - let mut carrier_info = - Pattern4CarrierAnalyzer::analyze_carriers(&normalized_body, &carrier_info_prelim)?; - - trace::trace().debug( - "pattern4", - &format!( - "CarrierInfo: loop_var={}, carriers={:?}", - carrier_info.loop_var_name, - carrier_info - .carriers - .iter() - .map(|c| &c.name) - .collect::>() - ), - ); - - trace::trace().debug( - "pattern4", - &format!( - "Analyzed {} carrier update expressions", - carrier_updates.len() - ), - ); - for (carrier_name, update_expr) in &carrier_updates { - trace::trace().debug( - "pattern4", - &format!(" {} → {:?}", carrier_name, update_expr), - ); - } - - // LoopBodyLocal promotion for conditions (skip_whitespace etc.) - let continue_cond = LoopBodyCondPromoter::extract_continue_condition(&normalized_body); - let conditions_to_analyze: Vec<&ASTNode> = if let Some(cont_cond) = continue_cond { - vec![condition, cont_cond] - } else { - vec![condition] - }; - - let cond_scope = - LoopConditionScopeBox::analyze(&loop_var_name, &conditions_to_analyze, Some(&loop_scope)); - - if cond_scope.has_loop_body_local() { - let promotion_req = ConditionPromotionRequest { - loop_param_name: &loop_var_name, - cond_scope: &cond_scope, - scope_shape: Some(&loop_scope), - break_cond: None, // Pattern 4 has no break - continue_cond, - loop_body: &normalized_body, - #[cfg(feature = "normalized_dev")] - // Phase 136 Step 4/7: Use binding_ctx for binding_map reference - binding_map: Some(builder.binding_ctx.binding_map()), - }; - - match LoopBodyCondPromoter::try_promote_for_condition(promotion_req) { - ConditionPromotionResult::Promoted { - carrier_info: promoted_carrier, - promoted_var, - carrier_name, - } => { - trace::trace().debug( - "pattern4/cond_promoter", - &format!( - "LoopBodyLocal '{}' promoted to carrier '{}'", - promoted_var, carrier_name - ), - ); - - #[cfg(feature = "normalized_dev")] - { - use crate::mir::join_ir::lowering::carrier_binding_assigner::CarrierBindingAssigner; - let mut promoted_carrier = promoted_carrier; - CarrierBindingAssigner::assign_promoted_binding( - builder, - &mut promoted_carrier, - &promoted_var, - &carrier_name, - ) - .map_err(|e| format!("[phase78/binding_assign] {:?}", e))?; - carrier_info.merge_from(&promoted_carrier); - } - #[cfg(not(feature = "normalized_dev"))] - { - carrier_info.merge_from(&promoted_carrier); - } - - trace::trace().debug( - "pattern4/cond_promoter", - &format!( - "Merged carrier '{}' into CarrierInfo (total carriers: {})", - carrier_name, - carrier_info.carrier_count() - ), - ); - - if let Some(helper) = carrier_info.trim_helper() { - if helper.is_safe_trim() { - trace::trace().debug( - "pattern4/cond_promoter", - "Safe Trim/skip_whitespace pattern detected", - ); - trace::trace().debug( - "pattern4/cond_promoter", - &format!( - "Carrier: '{}', original var: '{}', whitespace chars: {:?}", - helper.carrier_name, helper.original_var, helper.whitespace_chars - ), - ); - } else { - return Err(error_messages::format_error_pattern4_trim_not_safe( - &helper.carrier_name, - helper.whitespace_count(), - )); - } - } - } - ConditionPromotionResult::CannotPromote { reason, vars } => { - return Err(error_messages::format_error_pattern4_promotion_failed( - &vars, &reason, - )); - } - } - } - - Ok(Pattern4Prepared { - loop_var_name, - loop_scope, - carrier_info, - carrier_updates, - body, // Phase 284 P1: Include body for return detection - }) -} - -/// JoinIR lowering stage for Pattern 4 using prepared context. -fn lower_pattern4_joinir( - builder: &mut MirBuilder, - condition: &ASTNode, - prepared: &Pattern4Prepared<'_>, - debug: bool, -) -> Result, String> { - use super::super::merge::exit_line::meta_collector::ExitMetaCollector; - use super::conversion_pipeline::JoinIRConversionPipeline; - use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace; - use crate::mir::join_ir::lowering::loop_with_continue_minimal::lower_loop_with_continue_minimal; - use crate::mir::join_ir::lowering::return_collector::{collect_return_from_body, ReturnInfo}; - use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder; - - trace::trace().varmap("pattern4_start", &builder.variable_ctx.variable_map); - - // Phase 284 P1: Detect return statements in body - let return_info: Option = match collect_return_from_body(prepared.body) { - Ok(info) => info, - Err(e) => { - return Err(format!("[pattern4] Return detection failed: {}", e)); - } - }; - - if let Some(ref ret_info) = return_info { - trace::trace().debug( - "pattern4", - &format!( - "Phase 284 P1: Return detected - value={}, has_condition={}, in_else={}", - ret_info.value, - ret_info.condition.is_some(), - ret_info.in_else - ), - ); - } - - let mut join_value_space = JoinValueSpace::new(); - - #[cfg(feature = "normalized_dev")] - // Phase 136 Step 4/7: Use binding_ctx for binding_map clone - let binding_map_clone = builder.binding_ctx.binding_map().clone(); - - let (join_module, exit_meta) = match lower_loop_with_continue_minimal( - prepared.loop_scope.clone(), - condition, - builder, - &prepared.carrier_info, - &prepared.carrier_updates, - &mut join_value_space, - return_info.as_ref(), // Phase 284 P1 - #[cfg(feature = "normalized_dev")] - Some(&binding_map_clone), - ) { - Ok(result) => result, - Err(e) => { - trace::trace().debug("pattern4", &format!("Pattern 4 lowerer failed: {}", e)); - return Err(error_messages::format_error_pattern4_lowering_failed(&e)); - } - }; - - trace::trace().debug( - "pattern4", - &format!("ExitMeta: {} exit bindings", exit_meta.exit_values.len()), - ); - for (carrier_name, join_value) in &exit_meta.exit_values { - trace::trace().debug( - "pattern4", - &format!(" {} → ValueId({})", carrier_name, join_value.0), - ); - } - - let exit_bindings = ExitMetaCollector::collect( - builder, - &exit_meta, - Some(&prepared.carrier_info), // Phase 228-8: Include ConditionOnly carriers - debug, - ); - - for carrier in &prepared.carrier_info.carriers { - if !exit_bindings.iter().any(|b| b.carrier_name == carrier.name) { - return Err(error_messages::format_error_pattern4_carrier_not_found( - &carrier.name, - )); - } - } - - // Phase 256.8.5: Use JoinModule.entry.params as SSOT (no hardcoded ValueIds) - use super::common::get_entry_function; - let main_func = get_entry_function(&join_module, "pattern4")?; - - // SSOT: Use actual params allocated by JoinIR lowerer - let join_inputs = main_func.params.clone(); - - // Build host_inputs in same order (loop_var + carriers) - let mut host_inputs = vec![prepared.carrier_info.loop_var_id]; - for carrier in &prepared.carrier_info.carriers { - host_inputs.push(carrier.host_id); - } - - // Verify count consistency (fail-fast) - if join_inputs.len() != host_inputs.len() { - return Err(format!( - "[pattern4] Params count mismatch: join_inputs={}, host_inputs={}", - join_inputs.len(), host_inputs.len() - )); - } - - trace::trace().debug( - "pattern4", - &format!( - "join_inputs (SSOT): {:?}, host_inputs: {:?}", - join_inputs.iter().map(|v| v.0).collect::>(), - host_inputs.iter().map(|v| v.0).collect::>() - ), - ); - - // Phase 284 P1: Build continuation set - include k_return if return_info exists - let continuation_funcs = { - use crate::mir::join_ir::lowering::canonical_names as cn; - let mut funcs = std::collections::BTreeSet::new(); - funcs.insert(cn::K_EXIT.to_string()); - if return_info.is_some() { - funcs.insert(cn::K_RETURN.to_string()); - } - funcs - }; - - let boundary = JoinInlineBoundaryBuilder::new() - .with_inputs(join_inputs, host_inputs) // Dynamic carrier count - .with_exit_bindings(exit_bindings) - .with_loop_var_name(Some(prepared.loop_var_name.clone())) - .with_carrier_info(prepared.carrier_info.clone()) - .with_continuation_funcs(continuation_funcs) // Phase 284 P1 - .build(); - - let _result_val = JoinIRConversionPipeline::execute( - builder, - join_module, - Some(&boundary), - "pattern4", - debug, - )?; - - let void_val = crate::mir::builder::emission::constant::emit_void(builder); - trace::trace().debug( - "pattern4", - &format!("Loop complete, returning Void {:?}", void_val), - ); - - Ok(Some(void_val)) -} diff --git a/src/mir/builder/control_flow/joinir/patterns/router.rs b/src/mir/builder/control_flow/joinir/patterns/router.rs index 4b39cbbc..90d55f7a 100644 --- a/src/mir/builder/control_flow/joinir/patterns/router.rs +++ b/src/mir/builder/control_flow/joinir/patterns/router.rs @@ -151,7 +151,7 @@ impl<'a> LoopPatternContext<'a> { /// Phase 286 P2.2: Common helper for Plan line lowering /// -/// Extracts the common 3-line pattern used by Pattern6/7/4/1: +/// Extracts the common 3-line pattern used by Plan-based routing: /// 1. Normalize DomainPlan → CorePlan /// 2. Verify CorePlan invariants (fail-fast) /// 3. Lower CorePlan → MIR @@ -180,7 +180,7 @@ fn lower_via_plan( /// - Cons: Extraction can be expensive (but amortized over lowering) /// /// ## StructureBased (Feature Classification) -/// - Used by: Pattern4 (legacy) +/// - Used by: Pattern6_NestedLoopMinimal (legacy) /// - Strategy: Check pattern_kind (from LoopPatternContext), plus optional structural checks /// - Pros: Fast classification, reuses centralized feature detection /// - Cons: Two sources of truth (classify + structural checks) @@ -188,7 +188,7 @@ fn lower_via_plan( /// ## Rationale for Dual Strategy: /// - Pattern6/7: Complex extraction logic (variable step, carrier tracking) /// → ExtractionBased avoids duplication between detect and extract -/// - Other patterns: Simple structural features (continue) +/// - Other patterns: Legacy structural features /// → StructureBased leverages centralized LoopFeatures classification /// /// This documentation prevents bugs like Phase 272 P0.2's Pattern7 issue @@ -201,7 +201,7 @@ pub(crate) enum CanLowerStrategy { ExtractionBased, /// Structure-based detection: Check pattern_kind from LoopPatternContext - /// Used by Pattern4 + /// Used by legacy patterns StructureBased, } @@ -222,18 +222,13 @@ pub(crate) struct LoopPatternEntry { /// /// **IMPORTANT**: Patterns are tried in array order (SSOT). /// Array order defines priority - earlier entries are tried first. -/// Pattern6_NestedLoopMinimal → Pattern4 +/// Pattern6_NestedLoopMinimal (legacy) /// /// # Current Patterns (Structure-based detection, established Phase 131-11+) /// /// Pattern detection strategies (updated Phase 286): /// - Pattern6/7: ExtractionBased (Plan line, Phase 273+) /// - Pattern8/9: ExtractionBased (extraction functions, Plan line Phase 286+) -/// - Pattern4: StructureBased (LoopFeatures classification, legacy) -/// -/// - Pattern 4: Loop with Continue (loop_continue_pattern4.hako) -/// - Detection: pattern_kind == Pattern4Continue -/// - Structure: has_continue && !has_break /// /// - Pattern3: moved to plan-based routing (planner+composer SSOT) /// @@ -248,11 +243,6 @@ pub(crate) static LOOP_PATTERNS: &[LoopPatternEntry] = &[ detect: super::pattern6_nested_minimal::can_lower, lower: super::pattern6_nested_minimal::lower, }, - LoopPatternEntry { - name: "Pattern4_WithContinue", - detect: super::pattern4_with_continue::can_lower, - lower: super::pattern4_with_continue::lower, - }, // Phase 273 P0.1: Pattern6 entry removed (migrated to Plan-based routing) // Pattern6_ScanWithInit now handled via extract_scan_with_init_plan() + PlanLowerer // Phase 273 P2: Pattern7 entry removed (migrated to Plan-based routing) @@ -270,18 +260,18 @@ pub(crate) static LOOP_PATTERNS: &[LoopPatternEntry] = &[ /// This router uses multiple detection strategies: /// - Plan-based (Pattern6/7): extract_*_plan() → DomainPlan (Phase 273+ SSOT) /// - Extraction-based (Pattern8/9): extract_*() functions (already implemented) -/// - Structure-based (Pattern4): ctx.pattern_kind classification (legacy) +/// - Structure-based (Pattern6_NestedLoopMinimal): ctx.pattern_kind classification (legacy) /// /// # Plan Line SSOT for Pattern6/7 (Phase 273+) /// /// This function implements the following routing strategy: /// 1. Try Plan-based Pattern6 (extract_scan_with_init_plan) → DomainPlan /// 2. Try Plan-based Pattern7 (extract_split_scan_plan) → DomainPlan -/// 3. Fall through to legacy Pattern4 table for other patterns +/// 3. Fall through to legacy Pattern6_NestedLoopMinimal table for other patterns /// /// The Plan line (Extractor → Normalizer → Verifier → Lowerer) is the -/// current operational SSOT for Pattern6/7. Legacy patterns (1/2/4/8/9) use -/// the traditional LoopPatternContext-based routing. +/// current operational SSOT for Pattern6/7. Legacy patterns +/// (Pattern6_NestedLoopMinimal) use the traditional LoopPatternContext-based routing. /// /// Plan-based architecture (Phase 273 P1-P3): /// - extract_*_plan() → DomainPlan (pure extraction, no builder) @@ -292,7 +282,6 @@ pub(crate) static LOOP_PATTERNS: &[LoopPatternEntry] = &[ /// SSOT Entry Points: /// - Pattern6: src/mir/builder/control_flow/plan/normalizer.rs (ScanWithInit normalization) /// - Pattern7: src/mir/builder/control_flow/plan/normalizer.rs (SplitScan normalization) -/// - Pattern4: src/mir/builder/control_flow/joinir/patterns/pattern*.rs (direct lowering) /// - Pattern6_NestedLoopMinimal: src/mir/builder/control_flow/joinir/patterns/pattern6_nested_minimal.rs pub(crate) fn route_loop_pattern( builder: &mut MirBuilder, diff --git a/tools/smokes/v2/profiles/integration/joinir/phase29ae_regression_pack_vm.sh b/tools/smokes/v2/profiles/integration/joinir/phase29ae_regression_pack_vm.sh index 774bf2d4..434b13c9 100644 --- a/tools/smokes/v2/profiles/integration/joinir/phase29ae_regression_pack_vm.sh +++ b/tools/smokes/v2/profiles/integration/joinir/phase29ae_regression_pack_vm.sh @@ -23,6 +23,7 @@ run_filter "pattern2_subset" "phase29ai_pattern2_break_plan_subset_ok_min_vm" || run_filter "pattern2_release_adopt_vm" "phase29ao_pattern2_release_adopt_vm" || exit 1 run_filter "pattern3_ifphi_vm" "phase118_pattern3_if_sum_vm" || exit 1 run_filter "pattern3_release_adopt_vm" "phase29ao_pattern3_release_adopt_vm" || exit 1 +run_filter "pattern4_continue_vm" "phase29ap_pattern4_continue_min_vm" || exit 1 run_filter "pattern1_strict_shadow_vm" "phase29ao_pattern1_strict_shadow_vm" || exit 1 run_filter "pattern1_subset_reject_extra_stmt_vm" "phase29ao_pattern1_subset_reject_extra_stmt_vm" || exit 1 run_filter "pattern1_stringutils_tolower_vm" "phase29ap_stringutils_tolower_vm" || exit 1 diff --git a/tools/smokes/v2/profiles/integration/joinir/phase29ap_pattern4_continue_min_vm.sh b/tools/smokes/v2/profiles/integration/joinir/phase29ap_pattern4_continue_min_vm.sh new file mode 100644 index 00000000..dfa4e627 --- /dev/null +++ b/tools/smokes/v2/profiles/integration/joinir/phase29ap_pattern4_continue_min_vm.sh @@ -0,0 +1,16 @@ +#!/bin/bash +# phase29ap_pattern4_continue_min_vm.sh - Pattern4 continue via plan routing (VM) + +source "$(dirname "$0")/../../../lib/test_runner.sh" +require_env || exit 2 + +FIXTURE="$NYASH_ROOT/apps/tests/phase29ap_pattern4_continue_min.hako" + +output=$(run_nyash_vm "$FIXTURE") + +expected=$(cat << 'TXT' +6 +TXT +) + +compare_outputs "$expected" "$output" "phase29ap_pattern4_continue_min_vm" || exit 1