From 3a7378a7475476685ea2093bbd0e676d5e851131 Mon Sep 17 00:00:00 2001 From: tomoaki Date: Mon, 29 Dec 2025 13:22:55 +0900 Subject: [PATCH] phase29aj(p4): planner-first pattern4 continue subset --- CURRENT_TASK.md | 3 + docs/development/current/main/10-Now.md | 7 +- docs/development/current/main/30-Backlog.md | 4 +- ...RN4-CONTINUE-PLANNER-FIRST-INSTRUCTIONS.md | 93 +++++++ .../current/main/phases/phase-29aj/README.md | 7 + .../control_flow/plan/facts/loop_facts.rs | 7 + .../builder/control_flow/plan/facts/mod.rs | 1 + .../plan/facts/pattern4_continue_facts.rs | 253 ++++++++++++++++++ .../control_flow/plan/planner/build.rs | 82 +++++- .../control_flow/plan/single_planner/rules.rs | 9 +- 10 files changed, 461 insertions(+), 5 deletions(-) create mode 100644 docs/development/current/main/phases/phase-29aj/P4-PATTERN4-CONTINUE-PLANNER-FIRST-INSTRUCTIONS.md create mode 100644 src/mir/builder/control_flow/plan/facts/pattern4_continue_facts.rs diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 7b634cf6..85cacc45 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -27,6 +27,9 @@ chosen_rule を撤去し、Pattern1 SimpleWhile の Facts→Planner-first を導 **2025-12-29: Phase 29aj P3 COMPLETE (Pattern3 planner-first)** Pattern3 If-Phi の Facts→Planner-first を導入し、single_planner の extractor 依存を縮小。 +**2025-12-29: Phase 29aj P4 COMPLETE (Pattern4 planner-first)** +Pattern4 Continue の Facts→Planner-first を導入し、single_planner の extractor 依存を縮小。 + **2025-12-27: Phase 188.3 / Phase 287 P2 COMPLETE (Pattern6 nested loop: merge/latch fixes)** Pattern6(1-level nested loop)の JoinIR→bridge→merge 経路で発生していた `undefined ValueId` と `vm step budget exceeded`(無限ループ)を解消。`apps/tests/phase1883_nested_minimal.hako` が RC=9 を返し、quick 154 PASS を維持。 diff --git a/docs/development/current/main/10-Now.md b/docs/development/current/main/10-Now.md index 7752ef72..a7b3eddb 100644 --- a/docs/development/current/main/10-Now.md +++ b/docs/development/current/main/10-Now.md @@ -2,7 +2,12 @@ ## Current Focus: Phase 29aj(PlannerOutcome SSOT) -Next: Phase 29aj P4(TBD: promotion hint を JoinIR 側の orchestrator へ配線、挙動不変) +Next: Phase 29aj P5(TBD) + +**2025-12-29: Phase 29aj P4 完了** ✅ +- 目的: Pattern4(Continue)を Facts→Planner-first に移行(仕様不変) +- 実装: `src/mir/builder/control_flow/plan/facts/pattern4_continue_facts.rs` / `src/mir/builder/control_flow/plan/planner/build.rs` / `src/mir/builder/control_flow/plan/single_planner/rules.rs` +- 検証: `cargo build --release` / `./tools/smokes/v2/run.sh --profile quick` / `./tools/smokes/v2/profiles/integration/joinir/phase29ae_regression_pack_vm.sh` / `./tools/smokes/v2/run.sh --profile integration --filter "phase286_pattern4_frag_poc"` PASS **2025-12-29: Phase 29aj P3 完了** ✅ - 目的: Pattern3(If-Phi)を Facts→Planner-first に移行(仕様不変) diff --git a/docs/development/current/main/30-Backlog.md b/docs/development/current/main/30-Backlog.md index 965e690f..2dd2bfc6 100644 --- a/docs/development/current/main/30-Backlog.md +++ b/docs/development/current/main/30-Backlog.md @@ -19,8 +19,8 @@ Related: - **Phase 29aj(candidate): PlannerOutcome observability SSOT** - 入口: `docs/development/current/main/phases/phase-29aj/README.md` - - 状況: P0/P1/P2/P3 ✅ 完了 - - Next: Phase 29aj P4(TBD: promotion hint を JoinIR 側の orchestrator へ配線、挙動不変) + - 状況: P0/P1/P2/P3/P4 ✅ 完了 + - Next: Phase 29aj P5(TBD) - **Phase 29ai(candidate): Plan/Frag single-planner(Facts SSOT)** - 入口: `docs/development/current/main/phases/phase-29ai/README.md` diff --git a/docs/development/current/main/phases/phase-29aj/P4-PATTERN4-CONTINUE-PLANNER-FIRST-INSTRUCTIONS.md b/docs/development/current/main/phases/phase-29aj/P4-PATTERN4-CONTINUE-PLANNER-FIRST-INSTRUCTIONS.md new file mode 100644 index 00000000..775264e5 --- /dev/null +++ b/docs/development/current/main/phases/phase-29aj/P4-PATTERN4-CONTINUE-PLANNER-FIRST-INSTRUCTIONS.md @@ -0,0 +1,93 @@ +# Phase 29aj P4: Pattern4 Continue planner-first via Facts (subset) + +Date: 2025-12-29 +Status: Ready for execution +Scope: Pattern4 facts → planner candidate → single_planner planner-first(仕様不変) +Goal: Pattern4 を Facts→Planner に乗せ、extractor 依存を 1 本減らす + +## Objective + +- Pattern4(Loop with Continue)を Facts→Planner 経路に追加 +- single_planner は Pattern4 の型一致時のみ planner-first 採用 +- 既定挙動・観測・エラー文字列は不変 + +## Non-goals + +- Pattern4 サブセット拡張 +- ルール順序 SSOT の CandidateSet 移管 +- 新 env var / 新ログ追加 + +## Implementation Steps + +### Step 1: Facts SSOT 追加(Pattern4) + +Files: +- `src/mir/builder/control_flow/plan/facts/pattern4_continue_facts.rs` (new) + +Facts: +- `Pattern4ContinueFacts { loop_var, condition, continue_condition, carrier_updates, loop_increment }` + +Extraction rules (Ok(None) fallback only): +- condition は ` < ` のみ +- continue が 1 つ以上(recursive) +- break がない +- continue 条件は `if (COND) { continue }` を 1 つ見つける(else 付きは Ok(None)) +- carrier_updates は `var = var + X` のみ(loop_var は除外) +- loop_increment は `extract_loop_increment_plan` で取れるときのみ + +Unit tests: +- success / break / continue 無し + +### Step 2: LoopFacts に接続 + +Files: +- `src/mir/builder/control_flow/plan/facts/mod.rs` +- `src/mir/builder/control_flow/plan/facts/loop_facts.rs` + +Changes: +- LoopFacts に `pattern4_continue` を追加 +- `try_build_loop_facts()` に抽出を追加 +- all-none 判定に `pattern4_continue` を含める + +### Step 3: Planner candidate 追加 + +File: +- `src/mir/builder/control_flow/plan/planner/build.rs` + +Changes: +- facts が Some のとき `DomainPlan::Pattern4Continue` を候補に追加 +- rule 名は `loop/pattern4_continue` +- unit test 追加 + +### Step 4: single_planner を Pattern4 planner-first に + +File: +- `src/mir/builder/control_flow/plan/single_planner/rules.rs` + +Changes: +- RuleKind::Pattern4 を追加 +- planner_opt が `Pattern4Continue` のとき採用 +- それ以外は extractor へフォールバック + +### Step 5: docs / CURRENT_TASK 更新 + +Files: +- `docs/development/current/main/phases/phase-29aj/README.md` +- `docs/development/current/main/10-Now.md` +- `docs/development/current/main/30-Backlog.md` +- `CURRENT_TASK.md` + +## Acceptance Criteria + +- `cargo build --release` +- `./tools/smokes/v2/run.sh --profile quick` +- `./tools/smokes/v2/profiles/integration/joinir/phase29ae_regression_pack_vm.sh` +- `./tools/smokes/v2/run.sh --profile integration --filter "phase286_pattern4_frag_poc"` + +## Commit + +- `git add -A && git commit -m "phase29aj(p4): planner-first pattern4 continue subset"` + +## Next (P5 candidate) + +- TBD diff --git a/docs/development/current/main/phases/phase-29aj/README.md b/docs/development/current/main/phases/phase-29aj/README.md index 045ec705..c47d484a 100644 --- a/docs/development/current/main/phases/phase-29aj/README.md +++ b/docs/development/current/main/phases/phase-29aj/README.md @@ -30,6 +30,13 @@ Goal: planner の facts/plan を 1 本の outcome に集約し、観測の SSOT - 完了: Pattern3 facts/planner/single_planner を接続 - 検証: `cargo build --release` / `./tools/smokes/v2/run.sh --profile quick` / `./tools/smokes/v2/profiles/integration/joinir/phase29ae_regression_pack_vm.sh` +## P4: Pattern4 (Continue) planner-first(subset) + +- 指示書: `docs/development/current/main/phases/phase-29aj/P4-PATTERN4-CONTINUE-PLANNER-FIRST-INSTRUCTIONS.md` +- ねらい: Pattern4 を Facts→Planner-first に接続し、extractor 依存を削減 +- 完了: Pattern4 facts/planner/single_planner を接続 +- 検証: `cargo build --release` / `./tools/smokes/v2/run.sh --profile quick` / `./tools/smokes/v2/profiles/integration/joinir/phase29ae_regression_pack_vm.sh` / `./tools/smokes/v2/run.sh --profile integration --filter "phase286_pattern4_frag_poc"` + ## Verification (SSOT) - `cargo build --release` diff --git a/src/mir/builder/control_flow/plan/facts/loop_facts.rs b/src/mir/builder/control_flow/plan/facts/loop_facts.rs index 0a40c483..a5a2098c 100644 --- a/src/mir/builder/control_flow/plan/facts/loop_facts.rs +++ b/src/mir/builder/control_flow/plan/facts/loop_facts.rs @@ -19,6 +19,9 @@ use super::pattern1_simplewhile_facts::{ use super::pattern3_ifphi_facts::{ Pattern3IfPhiFacts, try_extract_pattern3_ifphi_facts, }; +use super::pattern4_continue_facts::{ + Pattern4ContinueFacts, try_extract_pattern4_continue_facts, +}; use super::pattern2_break_facts::{Pattern2BreakFacts, try_extract_pattern2_break_facts}; use super::pattern2_loopbodylocal_facts::{ Pattern2LoopBodyLocalFacts, try_extract_pattern2_loopbodylocal_facts, @@ -32,6 +35,7 @@ pub(in crate::mir::builder) struct LoopFacts { pub split_scan: Option, pub pattern1_simplewhile: Option, pub pattern3_ifphi: Option, + pub pattern4_continue: Option, pub pattern2_break: Option, pub pattern2_loopbodylocal: Option, } @@ -67,6 +71,7 @@ pub(in crate::mir::builder) fn try_build_loop_facts( let split_scan = try_extract_split_scan_facts(condition, body)?; let pattern1_simplewhile = try_extract_pattern1_simplewhile_facts(condition, body)?; let pattern3_ifphi = try_extract_pattern3_ifphi_facts(condition, body)?; + let pattern4_continue = try_extract_pattern4_continue_facts(condition, body)?; let pattern2_break = try_extract_pattern2_break_facts(condition, body)?; let pattern2_loopbodylocal = try_extract_pattern2_loopbodylocal_facts(condition, body)?; @@ -74,6 +79,7 @@ pub(in crate::mir::builder) fn try_build_loop_facts( && split_scan.is_none() && pattern1_simplewhile.is_none() && pattern3_ifphi.is_none() + && pattern4_continue.is_none() && pattern2_break.is_none() && pattern2_loopbodylocal.is_none() { @@ -87,6 +93,7 @@ pub(in crate::mir::builder) fn try_build_loop_facts( split_scan, pattern1_simplewhile, pattern3_ifphi, + pattern4_continue, pattern2_break, pattern2_loopbodylocal, })) diff --git a/src/mir/builder/control_flow/plan/facts/mod.rs b/src/mir/builder/control_flow/plan/facts/mod.rs index affc1b20..ccb3ec25 100644 --- a/src/mir/builder/control_flow/plan/facts/mod.rs +++ b/src/mir/builder/control_flow/plan/facts/mod.rs @@ -9,6 +9,7 @@ pub(in crate::mir::builder) mod loop_facts; pub(in crate::mir::builder) mod pattern1_simplewhile_facts; pub(in crate::mir::builder) mod pattern3_ifphi_facts; +pub(in crate::mir::builder) mod pattern4_continue_facts; pub(in crate::mir::builder) mod pattern2_break_facts; pub(in crate::mir::builder) mod pattern2_loopbodylocal_facts; pub(in crate::mir::builder) mod scan_shapes; diff --git a/src/mir/builder/control_flow/plan/facts/pattern4_continue_facts.rs b/src/mir/builder/control_flow/plan/facts/pattern4_continue_facts.rs new file mode 100644 index 00000000..4f41901e --- /dev/null +++ b/src/mir/builder/control_flow/plan/facts/pattern4_continue_facts.rs @@ -0,0 +1,253 @@ +//! Phase 29aj P4: Pattern4ContinueFacts (Facts SSOT) + +use crate::ast::{ASTNode, BinaryOperator, LiteralValue}; +use crate::mir::builder::control_flow::plan::extractors::common_helpers::{ + count_control_flow, extract_loop_increment_plan, has_break_statement, ControlFlowDetector, +}; +use crate::mir::builder::control_flow::plan::planner::Freeze; +use std::collections::BTreeMap; + +#[derive(Debug, Clone)] +pub(in crate::mir::builder) struct Pattern4ContinueFacts { + pub loop_var: String, + pub condition: ASTNode, + pub continue_condition: ASTNode, + pub carrier_updates: BTreeMap, + pub loop_increment: ASTNode, +} + +pub(in crate::mir::builder) fn try_extract_pattern4_continue_facts( + condition: &ASTNode, + body: &[ASTNode], +) -> Result, Freeze> { + let Some(loop_var) = extract_loop_var_for_subset(condition) else { + return Ok(None); + }; + + let continue_count = count_control_flow(body, ControlFlowDetector::default()).continue_count; + if continue_count == 0 { + return Ok(None); + } + + if has_break_statement(body) { + return Ok(None); + } + + let continue_condition = match find_continue_condition(body) { + Some(cond) => cond, + None => return Ok(None), + }; + + let carrier_updates = extract_carrier_updates(body, &loop_var); + if carrier_updates.is_empty() { + return Ok(None); + } + + let loop_increment = match extract_loop_increment_plan(body, &loop_var) { + Ok(Some(inc)) => inc, + _ => return Ok(None), + }; + + Ok(Some(Pattern4ContinueFacts { + loop_var, + condition: condition.clone(), + continue_condition, + carrier_updates, + loop_increment, + })) +} + +fn extract_loop_var_for_subset(condition: &ASTNode) -> Option { + let ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left, + right, + .. + } = condition + else { + return None; + }; + + let ASTNode::Variable { name, .. } = left.as_ref() else { + return None; + }; + + if !matches!( + right.as_ref(), + ASTNode::Literal { + value: LiteralValue::Integer(_), + .. + } + ) { + return None; + } + + Some(name.clone()) +} + +fn find_continue_condition(body: &[ASTNode]) -> Option { + for stmt in body { + let ASTNode::If { + condition, + then_body, + else_body, + .. + } = stmt + else { + continue; + }; + + let then_has_continue = then_body.iter().any(|n| matches!(n, ASTNode::Continue { .. })); + let else_has_continue = else_body + .as_ref() + .map_or(false, |b| b.iter().any(|n| matches!(n, ASTNode::Continue { .. }))); + + if else_body.is_some() && (then_has_continue || else_has_continue) { + return None; + } + + if then_has_continue { + return Some(condition.as_ref().clone()); + } + } + + None +} + +fn extract_carrier_updates( + body: &[ASTNode], + loop_var: &str, +) -> BTreeMap { + let mut updates = BTreeMap::new(); + + for stmt in body { + if let ASTNode::Assignment { target, value, .. } = stmt { + let ASTNode::Variable { name, .. } = target.as_ref() else { + continue; + }; + if name == loop_var { + continue; + } + let ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left, + .. + } = value.as_ref() + else { + continue; + }; + if matches!(left.as_ref(), ASTNode::Variable { name: lhs, .. } if lhs == name) { + updates.insert(name.clone(), value.as_ref().clone()); + } + } + } + + updates +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ast::{Span}; + + fn v(name: &str) -> ASTNode { + ASTNode::Variable { + name: name.to_string(), + span: Span::unknown(), + } + } + + fn lit_int(value: i64) -> ASTNode { + ASTNode::Literal { + value: LiteralValue::Integer(value), + span: Span::unknown(), + } + } + + fn condition_lt(var: &str, bound: i64) -> ASTNode { + ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left: Box::new(v(var)), + right: Box::new(lit_int(bound)), + span: Span::unknown(), + } + } + + fn increment(var: &str) -> ASTNode { + ASTNode::Assignment { + target: Box::new(v(var)), + value: Box::new(ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(v(var)), + right: Box::new(lit_int(1)), + span: Span::unknown(), + }), + span: Span::unknown(), + } + } + + fn carrier_update(var: &str, rhs: ASTNode) -> ASTNode { + ASTNode::Assignment { + target: Box::new(v(var)), + value: Box::new(ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(v(var)), + right: Box::new(rhs), + span: Span::unknown(), + }), + span: Span::unknown(), + } + } + + fn if_continue(cond: ASTNode) -> ASTNode { + ASTNode::If { + condition: Box::new(cond), + then_body: vec![ASTNode::Continue { span: Span::unknown() }], + else_body: None, + span: Span::unknown(), + } + } + + #[test] + fn facts_extracts_pattern4_continue_success() { + let condition = condition_lt("i", 6); + let body = vec![ + if_continue(v("skip")), + carrier_update("sum", v("i")), + increment("i"), + ]; + + let facts = try_extract_pattern4_continue_facts(&condition, &body).expect("Ok"); + let facts = facts.expect("Some"); + assert_eq!(facts.loop_var, "i"); + assert_eq!(facts.carrier_updates.len(), 1); + } + + #[test] + fn facts_rejects_break() { + let condition = condition_lt("i", 6); + let body = vec![ + ASTNode::If { + condition: Box::new(v("done")), + then_body: vec![ASTNode::Break { span: Span::unknown() }], + else_body: None, + span: Span::unknown(), + }, + if_continue(v("skip")), + carrier_update("sum", v("i")), + increment("i"), + ]; + + let facts = try_extract_pattern4_continue_facts(&condition, &body).expect("Ok"); + assert!(facts.is_none()); + } + + #[test] + fn facts_rejects_missing_continue() { + let condition = condition_lt("i", 6); + let body = vec![carrier_update("sum", v("i")), increment("i")]; + + let facts = try_extract_pattern4_continue_facts(&condition, &body).expect("Ok"); + assert!(facts.is_none()); + } +} diff --git a/src/mir/builder/control_flow/plan/planner/build.rs b/src/mir/builder/control_flow/plan/planner/build.rs index ce7f1d4f..a00b9e95 100644 --- a/src/mir/builder/control_flow/plan/planner/build.rs +++ b/src/mir/builder/control_flow/plan/planner/build.rs @@ -11,7 +11,7 @@ use super::outcome::build_plan_with_facts; use super::Freeze; use crate::mir::builder::control_flow::plan::{ DomainPlan, Pattern1SimpleWhilePlan, Pattern2BreakPlan, Pattern2PromotionHint, Pattern3IfPhiPlan, - ScanDirection, ScanWithInitPlan, SplitScanPlan, + Pattern4ContinuePlan, ScanDirection, ScanWithInitPlan, SplitScanPlan, }; /// Phase 29ai P0: External-ish SSOT entrypoint (skeleton) @@ -103,6 +103,20 @@ pub(in crate::mir::builder) fn build_plan_from_facts( }); } + if let Some(pattern4) = &facts.facts.pattern4_continue { + candidates.push(PlanCandidate { + plan: DomainPlan::Pattern4Continue(Pattern4ContinuePlan { + loop_var: pattern4.loop_var.clone(), + carrier_vars: pattern4.carrier_updates.keys().cloned().collect(), + condition: pattern4.condition.clone(), + continue_condition: pattern4.continue_condition.clone(), + carrier_updates: pattern4.carrier_updates.clone(), + loop_increment: pattern4.loop_increment.clone(), + }), + rule: "loop/pattern4_continue", + }); + } + if let Some(pattern1) = &facts.facts.pattern1_simplewhile { candidates.push(PlanCandidate { plan: DomainPlan::Pattern1SimpleWhile(Pattern1SimpleWhilePlan { @@ -132,6 +146,9 @@ mod tests { use crate::mir::builder::control_flow::plan::facts::pattern3_ifphi_facts::{ Pattern3IfPhiFacts, }; + use crate::mir::builder::control_flow::plan::facts::pattern4_continue_facts::{ + Pattern4ContinueFacts, + }; use crate::mir::builder::control_flow::plan::facts::pattern2_break_facts::Pattern2BreakFacts; use crate::mir::builder::control_flow::plan::facts::pattern2_loopbodylocal_facts::{ LoopBodyLocalShape, Pattern2LoopBodyLocalFacts, @@ -139,6 +156,7 @@ mod tests { use crate::mir::builder::control_flow::plan::normalize::canonicalize_loop_facts; use crate::mir::builder::control_flow::plan::Pattern2PromotionHint; use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span}; + use std::collections::BTreeMap; fn v(name: &str) -> ASTNode { ASTNode::Variable { @@ -169,6 +187,7 @@ mod tests { }), pattern1_simplewhile: None, pattern3_ifphi: None, + pattern4_continue: None, pattern2_break: None, pattern2_loopbodylocal: None, }; @@ -196,6 +215,7 @@ mod tests { split_scan: None, pattern1_simplewhile: None, pattern3_ifphi: None, + pattern4_continue: None, pattern2_break: None, pattern2_loopbodylocal: None, }; @@ -218,6 +238,7 @@ mod tests { split_scan: None, pattern1_simplewhile: None, pattern3_ifphi: None, + pattern4_continue: None, pattern2_break: None, pattern2_loopbodylocal: None, }; @@ -257,6 +278,7 @@ mod tests { loop_increment: loop_increment.clone(), }), pattern3_ifphi: None, + pattern4_continue: None, pattern2_break: None, pattern2_loopbodylocal: None, }; @@ -319,6 +341,7 @@ mod tests { else_update: else_update.clone(), loop_increment: loop_increment.clone(), }), + pattern4_continue: None, pattern2_break: None, pattern2_loopbodylocal: None, }; @@ -334,6 +357,62 @@ mod tests { } } + #[test] + fn planner_builds_pattern4_continue_plan_from_facts() { + let loop_condition = ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left: Box::new(v("i")), + right: Box::new(lit_int(6)), + span: Span::unknown(), + }; + let continue_condition = v("skip"); + let loop_increment = ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(v("i")), + right: Box::new(lit_int(1)), + span: Span::unknown(), + }; + + let mut carrier_updates = BTreeMap::new(); + carrier_updates.insert( + "sum".to_string(), + ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(v("sum")), + right: Box::new(v("i")), + span: Span::unknown(), + }, + ); + + let facts = LoopFacts { + condition_shape: ConditionShape::Unknown, + step_shape: StepShape::Unknown, + scan_with_init: None, + split_scan: None, + pattern1_simplewhile: None, + pattern3_ifphi: None, + pattern4_continue: Some(Pattern4ContinueFacts { + loop_var: "i".to_string(), + condition: loop_condition.clone(), + continue_condition: continue_condition.clone(), + carrier_updates: carrier_updates.clone(), + loop_increment: loop_increment.clone(), + }), + pattern2_break: None, + pattern2_loopbodylocal: None, + }; + let canonical = canonicalize_loop_facts(facts); + let plan = build_plan_from_facts(canonical).expect("Ok"); + + match plan { + Some(DomainPlan::Pattern4Continue(plan)) => { + assert_eq!(plan.loop_var, "i"); + assert_eq!(plan.carrier_vars, vec!["sum".to_string()]); + } + other => panic!("expected pattern4 continue plan, got {:?}", other), + } + } + #[test] fn planner_sets_promotion_hint_for_pattern2_loopbodylocal() { let loop_condition = ASTNode::BinaryOp { @@ -371,6 +450,7 @@ mod tests { split_scan: None, pattern1_simplewhile: None, pattern3_ifphi: None, + pattern4_continue: None, pattern2_break: Some(Pattern2BreakFacts { loop_var: "i".to_string(), carrier_var: "sum".to_string(), diff --git a/src/mir/builder/control_flow/plan/single_planner/rules.rs b/src/mir/builder/control_flow/plan/single_planner/rules.rs index 1ebf50c9..30399791 100644 --- a/src/mir/builder/control_flow/plan/single_planner/rules.rs +++ b/src/mir/builder/control_flow/plan/single_planner/rules.rs @@ -48,7 +48,7 @@ pub(super) fn try_build_domain_plan(ctx: &LoopPatternContext) -> Result Result (extractors::pattern3::extract_pattern3_plan(ctx.condition, ctx.body)?, true), } } + RuleKind::Pattern4 => { + match planner_opt.as_ref() { + Some(DomainPlan::Pattern4Continue(_)) => (planner_opt.clone(), false), + _ => (extractors::pattern4::extract_pattern4_plan(ctx.condition, ctx.body)?, true), + } + } RuleKind::Pattern1 => { match planner_opt.as_ref() { Some(DomainPlan::Pattern1SimpleWhile(_)) => (planner_opt.clone(), false), @@ -182,5 +188,6 @@ enum RuleKind { Pattern7, Pattern2, Pattern3, + Pattern4, Pattern1, }