From d4a9969fa305e5a9bce0664f286829bbe4396a3e Mon Sep 17 00:00:00 2001 From: tomoaki Date: Mon, 29 Dec 2025 13:39:30 +0900 Subject: [PATCH] phase29aj(p5): planner-first pattern5 infinite early-exit subset --- CURRENT_TASK.md | 3 + docs/development/current/main/10-Now.md | 7 +- docs/development/current/main/30-Backlog.md | 4 +- ...E-EARLY-EXIT-PLANNER-FIRST-INSTRUCTIONS.md | 93 +++++ .../current/main/phases/phase-29aj/README.md | 7 + .../control_flow/plan/facts/loop_facts.rs | 8 + .../builder/control_flow/plan/facts/mod.rs | 1 + .../pattern5_infinite_early_exit_facts.rs | 319 ++++++++++++++++++ .../control_flow/plan/planner/build.rs | 77 ++++- .../control_flow/plan/single_planner/rules.rs | 9 +- 10 files changed, 522 insertions(+), 6 deletions(-) create mode 100644 docs/development/current/main/phases/phase-29aj/P5-PATTERN5-INFINITE-EARLY-EXIT-PLANNER-FIRST-INSTRUCTIONS.md create mode 100644 src/mir/builder/control_flow/plan/facts/pattern5_infinite_early_exit_facts.rs diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 85cacc45..eae17a1b 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -30,6 +30,9 @@ Pattern3 If-Phi の Facts→Planner-first を導入し、single_planner の extr **2025-12-29: Phase 29aj P4 COMPLETE (Pattern4 planner-first)** Pattern4 Continue の Facts→Planner-first を導入し、single_planner の extractor 依存を縮小。 +**2025-12-29: Phase 29aj P5 COMPLETE (Pattern5 planner-first)** +Pattern5 Infinite Early Exit の 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 a7b3eddb..b8048daa 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 P5(TBD) +Next: Phase 29aj P6(TBD) + +**2025-12-29: Phase 29aj P5 完了** ✅ +- 目的: Pattern5(Infinite Early Exit)を Facts→Planner-first に移行(仕様不変) +- 実装: `src/mir/builder/control_flow/plan/facts/pattern5_infinite_early_exit_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 "phase143_"` PASS **2025-12-29: Phase 29aj P4 完了** ✅ - 目的: Pattern4(Continue)を Facts→Planner-first に移行(仕様不変) diff --git a/docs/development/current/main/30-Backlog.md b/docs/development/current/main/30-Backlog.md index 2dd2bfc6..225e4a79 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/P4 ✅ 完了 - - Next: Phase 29aj P5(TBD) + - 状況: P0/P1/P2/P3/P4/P5 ✅ 完了 + - Next: Phase 29aj P6(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/P5-PATTERN5-INFINITE-EARLY-EXIT-PLANNER-FIRST-INSTRUCTIONS.md b/docs/development/current/main/phases/phase-29aj/P5-PATTERN5-INFINITE-EARLY-EXIT-PLANNER-FIRST-INSTRUCTIONS.md new file mode 100644 index 00000000..422d7f8b --- /dev/null +++ b/docs/development/current/main/phases/phase-29aj/P5-PATTERN5-INFINITE-EARLY-EXIT-PLANNER-FIRST-INSTRUCTIONS.md @@ -0,0 +1,93 @@ +# Phase 29aj P5: Pattern5 infinite early-exit planner-first via Facts (subset) + +Date: 2025-12-29 +Status: Ready for execution +Scope: Pattern5 facts → planner candidate → single_planner planner-first(仕様不変) +Goal: Pattern5 を Facts→Planner に乗せ、extractor 依存を 1 本減らす + +## Objective + +- Pattern5(loop(true) + early exit)を Facts→Planner 経路に追加 +- single_planner は Pattern5 の型一致時のみ planner-first 採用 +- 既定挙動・観測・エラー文字列は不変 + +## Non-goals + +- Pattern5 サブセット拡張(複数exit/複雑条件/複数carrier) +- ルール順序 SSOT の CandidateSet 移管 +- 新 env var / 新ログ追加 + +## Implementation Steps + +### Step 1: Facts SSOT 追加(Pattern5) + +Files: +- `src/mir/builder/control_flow/plan/facts/pattern5_infinite_early_exit_facts.rs` (new) + +Facts: +- `Pattern5InfiniteEarlyExitFacts { loop_var, exit_kind, exit_condition, exit_value, carrier_var, carrier_update, loop_increment }` + +Extraction rules (Ok(None) fallback only): +- condition は `loop(true)` のみ +- body 先頭が `if (cond) { return }` か `if (cond) { break }`(else 無し) +- then_body は単一要素のみ(Return / Break のみ) +- Break 版は carrier 1 個だけ許可し、`var = var + ...` 形のみ +- loop_increment は `extract_loop_increment_plan(body, loop_var)` が取れる場合のみ + +Unit tests: +- return 版 / break 版の success +- else 付き / increment 無し → Ok(None) + +### 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 に `pattern5_infinite_early_exit` を追加 +- `try_build_loop_facts()` に抽出を追加 +- all-none 判定に `pattern5_infinite_early_exit` を含める + +### Step 3: Planner candidate 追加 + +File: +- `src/mir/builder/control_flow/plan/planner/build.rs` + +Changes: +- facts が Some のとき `DomainPlan::Pattern5InfiniteEarlyExit` を候補に追加 +- rule 名は `loop/pattern5_infinite_early_exit` +- unit test 追加 + +### Step 4: single_planner を Pattern5 planner-first に + +File: +- `src/mir/builder/control_flow/plan/single_planner/rules.rs` + +Changes: +- RuleKind::Pattern5 を追加 +- planner_opt が `Pattern5InfiniteEarlyExit` のとき採用 +- それ以外は 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 "phase143_"` + +## Commit + +- `git add -A && git commit -m "phase29aj(p5): planner-first pattern5 infinite early-exit subset"` + +## Next (P6 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 c47d484a..a986de83 100644 --- a/docs/development/current/main/phases/phase-29aj/README.md +++ b/docs/development/current/main/phases/phase-29aj/README.md @@ -37,6 +37,13 @@ Goal: planner の facts/plan を 1 本の outcome に集約し、観測の SSOT - 完了: 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"` +## P5: Pattern5 (Infinite Early Exit) planner-first(subset) + +- 指示書: `docs/development/current/main/phases/phase-29aj/P5-PATTERN5-INFINITE-EARLY-EXIT-PLANNER-FIRST-INSTRUCTIONS.md` +- ねらい: Pattern5 を Facts→Planner-first に接続し、extractor 依存を削減 +- 完了: Pattern5 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 "phase143_"` + ## 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 a5a2098c..94733828 100644 --- a/src/mir/builder/control_flow/plan/facts/loop_facts.rs +++ b/src/mir/builder/control_flow/plan/facts/loop_facts.rs @@ -22,6 +22,9 @@ use super::pattern3_ifphi_facts::{ use super::pattern4_continue_facts::{ Pattern4ContinueFacts, try_extract_pattern4_continue_facts, }; +use super::pattern5_infinite_early_exit_facts::{ + Pattern5InfiniteEarlyExitFacts, try_extract_pattern5_infinite_early_exit_facts, +}; use super::pattern2_break_facts::{Pattern2BreakFacts, try_extract_pattern2_break_facts}; use super::pattern2_loopbodylocal_facts::{ Pattern2LoopBodyLocalFacts, try_extract_pattern2_loopbodylocal_facts, @@ -36,6 +39,7 @@ pub(in crate::mir::builder) struct LoopFacts { pub pattern1_simplewhile: Option, pub pattern3_ifphi: Option, pub pattern4_continue: Option, + pub pattern5_infinite_early_exit: Option, pub pattern2_break: Option, pub pattern2_loopbodylocal: Option, } @@ -72,6 +76,8 @@ pub(in crate::mir::builder) fn try_build_loop_facts( 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 pattern5_infinite_early_exit = + try_extract_pattern5_infinite_early_exit_facts(condition, body)?; let pattern2_break = try_extract_pattern2_break_facts(condition, body)?; let pattern2_loopbodylocal = try_extract_pattern2_loopbodylocal_facts(condition, body)?; @@ -80,6 +86,7 @@ pub(in crate::mir::builder) fn try_build_loop_facts( && pattern1_simplewhile.is_none() && pattern3_ifphi.is_none() && pattern4_continue.is_none() + && pattern5_infinite_early_exit.is_none() && pattern2_break.is_none() && pattern2_loopbodylocal.is_none() { @@ -94,6 +101,7 @@ pub(in crate::mir::builder) fn try_build_loop_facts( pattern1_simplewhile, pattern3_ifphi, pattern4_continue, + pattern5_infinite_early_exit, 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 ccb3ec25..affab25e 100644 --- a/src/mir/builder/control_flow/plan/facts/mod.rs +++ b/src/mir/builder/control_flow/plan/facts/mod.rs @@ -10,6 +10,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 pattern5_infinite_early_exit_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/pattern5_infinite_early_exit_facts.rs b/src/mir/builder/control_flow/plan/facts/pattern5_infinite_early_exit_facts.rs new file mode 100644 index 00000000..e7f7b50e --- /dev/null +++ b/src/mir/builder/control_flow/plan/facts/pattern5_infinite_early_exit_facts.rs @@ -0,0 +1,319 @@ +//! Phase 29aj P5: Pattern5InfiniteEarlyExitFacts (Facts SSOT) + +use crate::ast::{ASTNode, BinaryOperator}; +use crate::mir::builder::control_flow::plan::extractors::common_helpers::{ + count_control_flow, extract_loop_increment_plan, is_true_literal, ControlFlowDetector, +}; +use crate::mir::builder::control_flow::plan::planner::Freeze; +use crate::mir::builder::control_flow::plan::Pattern5ExitKind; + +#[derive(Debug, Clone)] +pub(in crate::mir::builder) struct Pattern5InfiniteEarlyExitFacts { + pub loop_var: String, + pub exit_kind: Pattern5ExitKind, + pub exit_condition: ASTNode, + pub exit_value: Option, + pub carrier_var: Option, + pub carrier_update: Option, + pub loop_increment: ASTNode, +} + +pub(in crate::mir::builder) fn try_extract_pattern5_infinite_early_exit_facts( + condition: &ASTNode, + body: &[ASTNode], +) -> Result, Freeze> { + if !is_true_literal(condition) { + return Ok(None); + } + + let Some((exit_kind, exit_condition, exit_value)) = extract_exit_if(body) else { + return Ok(None); + }; + + let mut detector = ControlFlowDetector::default(); + detector.count_returns = true; + let counts = count_control_flow(body, detector); + if counts.has_nested_loop || counts.continue_count > 0 { + return Ok(None); + } + + match exit_kind { + Pattern5ExitKind::Return => { + if counts.return_count != 1 || counts.break_count != 0 { + return Ok(None); + } + } + Pattern5ExitKind::Break => { + if counts.break_count != 1 || counts.return_count != 0 { + return Ok(None); + } + } + } + + let remaining = &body[1..]; + + match exit_kind { + Pattern5ExitKind::Return => { + if remaining.len() != 1 { + return Ok(None); + } + + let loop_var = match extract_assignment_target(&remaining[0]) { + Some(var) => var, + None => return Ok(None), + }; + + let loop_increment = match extract_loop_increment_plan(body, &loop_var) { + Ok(Some(inc)) => inc, + _ => return Ok(None), + }; + + Ok(Some(Pattern5InfiniteEarlyExitFacts { + loop_var, + exit_kind, + exit_condition, + exit_value, + carrier_var: None, + carrier_update: None, + loop_increment, + })) + } + Pattern5ExitKind::Break => { + if remaining.len() != 2 { + return Ok(None); + } + + let (carrier_var, carrier_update) = match extract_carrier_update(&remaining[0]) { + Some(values) => values, + None => return Ok(None), + }; + + let loop_var = match extract_assignment_target(&remaining[1]) { + Some(var) => var, + None => return Ok(None), + }; + + if carrier_var == loop_var { + return Ok(None); + } + + let loop_increment = match extract_loop_increment_plan(body, &loop_var) { + Ok(Some(inc)) => inc, + _ => return Ok(None), + }; + + Ok(Some(Pattern5InfiniteEarlyExitFacts { + loop_var, + exit_kind, + exit_condition, + exit_value, + carrier_var: Some(carrier_var), + carrier_update: Some(carrier_update), + loop_increment, + })) + } + } +} + +fn extract_exit_if(body: &[ASTNode]) -> Option<(Pattern5ExitKind, ASTNode, Option)> { + let first = body.first()?; + let ASTNode::If { + condition, + then_body, + else_body, + .. + } = first + else { + return None; + }; + + if else_body.is_some() || then_body.len() != 1 { + return None; + } + + match &then_body[0] { + ASTNode::Return { value, .. } => { + let exit_value = value.as_ref().map(|boxed| boxed.as_ref().clone()); + Some(( + Pattern5ExitKind::Return, + condition.as_ref().clone(), + exit_value, + )) + } + ASTNode::Break { .. } => Some(( + Pattern5ExitKind::Break, + condition.as_ref().clone(), + None, + )), + _ => None, + } +} + +fn extract_assignment_target(stmt: &ASTNode) -> Option { + let ASTNode::Assignment { target, .. } = stmt else { + return None; + }; + let ASTNode::Variable { name, .. } = target.as_ref() else { + return None; + }; + Some(name.clone()) +} + +fn extract_carrier_update(stmt: &ASTNode) -> Option<(String, ASTNode)> { + let ASTNode::Assignment { target, value, .. } = stmt else { + return None; + }; + let ASTNode::Variable { name, .. } = target.as_ref() else { + return None; + }; + let ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left, + .. + } = value.as_ref() + else { + return None; + }; + if !matches!(left.as_ref(), ASTNode::Variable { name: lhs, .. } if lhs == name) { + return None; + } + Some((name.clone(), value.as_ref().clone())) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ast::{LiteralValue, 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 lit_true() -> ASTNode { + ASTNode::Literal { + value: LiteralValue::Bool(true), + 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_return(cond: ASTNode, value: Option) -> ASTNode { + ASTNode::If { + condition: Box::new(cond), + then_body: vec![ASTNode::Return { + value: value.map(Box::new), + span: Span::unknown(), + }], + else_body: None, + span: Span::unknown(), + } + } + + fn if_break(cond: ASTNode) -> ASTNode { + ASTNode::If { + condition: Box::new(cond), + then_body: vec![ASTNode::Break { span: Span::unknown() }], + else_body: None, + span: Span::unknown(), + } + } + + fn if_break_else(cond: ASTNode) -> ASTNode { + ASTNode::If { + condition: Box::new(cond), + then_body: vec![ASTNode::Break { span: Span::unknown() }], + else_body: Some(vec![ASTNode::Continue { span: Span::unknown() }]), + span: Span::unknown(), + } + } + + #[test] + fn facts_extracts_pattern5_return_success() { + let condition = lit_true(); + let body = vec![ + if_return(v("done"), Some(v("value"))), + increment("i"), + ]; + + let facts = try_extract_pattern5_infinite_early_exit_facts(&condition, &body) + .expect("Ok"); + let facts = facts.expect("Some"); + + assert_eq!(facts.loop_var, "i"); + assert_eq!(facts.exit_kind, Pattern5ExitKind::Return); + assert!(facts.carrier_var.is_none()); + } + + #[test] + fn facts_extracts_pattern5_break_success() { + let condition = lit_true(); + let body = vec![ + if_break(v("done")), + carrier_update("sum", v("i")), + increment("i"), + ]; + + let facts = try_extract_pattern5_infinite_early_exit_facts(&condition, &body) + .expect("Ok"); + let facts = facts.expect("Some"); + + assert_eq!(facts.loop_var, "i"); + assert_eq!(facts.exit_kind, Pattern5ExitKind::Break); + assert_eq!(facts.carrier_var.as_deref(), Some("sum")); + } + + #[test] + fn facts_rejects_else_branch() { + let condition = lit_true(); + let body = vec![if_break_else(v("done")), increment("i")]; + + let facts = try_extract_pattern5_infinite_early_exit_facts(&condition, &body) + .expect("Ok"); + assert!(facts.is_none()); + } + + #[test] + fn facts_rejects_missing_increment() { + let condition = lit_true(); + let body = vec![if_return(v("done"), None)]; + + let facts = try_extract_pattern5_infinite_early_exit_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 a00b9e95..45a9f46f 100644 --- a/src/mir/builder/control_flow/plan/planner/build.rs +++ b/src/mir/builder/control_flow/plan/planner/build.rs @@ -11,7 +11,8 @@ use super::outcome::build_plan_with_facts; use super::Freeze; use crate::mir::builder::control_flow::plan::{ DomainPlan, Pattern1SimpleWhilePlan, Pattern2BreakPlan, Pattern2PromotionHint, Pattern3IfPhiPlan, - Pattern4ContinuePlan, ScanDirection, ScanWithInitPlan, SplitScanPlan, + Pattern4ContinuePlan, Pattern5InfiniteEarlyExitPlan, ScanDirection, ScanWithInitPlan, + SplitScanPlan, }; /// Phase 29ai P0: External-ish SSOT entrypoint (skeleton) @@ -117,6 +118,21 @@ pub(in crate::mir::builder) fn build_plan_from_facts( }); } + if let Some(pattern5) = &facts.facts.pattern5_infinite_early_exit { + candidates.push(PlanCandidate { + plan: DomainPlan::Pattern5InfiniteEarlyExit(Pattern5InfiniteEarlyExitPlan { + loop_var: pattern5.loop_var.clone(), + exit_kind: pattern5.exit_kind, + exit_condition: pattern5.exit_condition.clone(), + exit_value: pattern5.exit_value.clone(), + carrier_var: pattern5.carrier_var.clone(), + carrier_update: pattern5.carrier_update.clone(), + loop_increment: pattern5.loop_increment.clone(), + }), + rule: "loop/pattern5_infinite_early_exit", + }); + } + if let Some(pattern1) = &facts.facts.pattern1_simplewhile { candidates.push(PlanCandidate { plan: DomainPlan::Pattern1SimpleWhile(Pattern1SimpleWhilePlan { @@ -149,12 +165,15 @@ mod tests { use crate::mir::builder::control_flow::plan::facts::pattern4_continue_facts::{ Pattern4ContinueFacts, }; + use crate::mir::builder::control_flow::plan::facts::pattern5_infinite_early_exit_facts::{ + Pattern5InfiniteEarlyExitFacts, + }; 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, }; use crate::mir::builder::control_flow::plan::normalize::canonicalize_loop_facts; - use crate::mir::builder::control_flow::plan::Pattern2PromotionHint; + use crate::mir::builder::control_flow::plan::{Pattern2PromotionHint, Pattern5ExitKind}; use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span}; use std::collections::BTreeMap; @@ -188,6 +207,7 @@ mod tests { pattern1_simplewhile: None, pattern3_ifphi: None, pattern4_continue: None, + pattern5_infinite_early_exit: None, pattern2_break: None, pattern2_loopbodylocal: None, }; @@ -216,6 +236,7 @@ mod tests { pattern1_simplewhile: None, pattern3_ifphi: None, pattern4_continue: None, + pattern5_infinite_early_exit: None, pattern2_break: None, pattern2_loopbodylocal: None, }; @@ -239,6 +260,7 @@ mod tests { pattern1_simplewhile: None, pattern3_ifphi: None, pattern4_continue: None, + pattern5_infinite_early_exit: None, pattern2_break: None, pattern2_loopbodylocal: None, }; @@ -279,6 +301,7 @@ mod tests { }), pattern3_ifphi: None, pattern4_continue: None, + pattern5_infinite_early_exit: None, pattern2_break: None, pattern2_loopbodylocal: None, }; @@ -342,6 +365,7 @@ mod tests { loop_increment: loop_increment.clone(), }), pattern4_continue: None, + pattern5_infinite_early_exit: None, pattern2_break: None, pattern2_loopbodylocal: None, }; @@ -398,6 +422,7 @@ mod tests { carrier_updates: carrier_updates.clone(), loop_increment: loop_increment.clone(), }), + pattern5_infinite_early_exit: None, pattern2_break: None, pattern2_loopbodylocal: None, }; @@ -413,6 +438,53 @@ mod tests { } } + #[test] + fn planner_builds_pattern5_infinite_early_exit_plan_from_facts() { + let exit_condition = ASTNode::BinaryOp { + operator: BinaryOperator::Equal, + left: Box::new(v("i")), + right: Box::new(lit_int(3)), + span: Span::unknown(), + }; + let loop_increment = ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(v("i")), + right: Box::new(lit_int(1)), + 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: None, + pattern5_infinite_early_exit: Some(Pattern5InfiniteEarlyExitFacts { + loop_var: "i".to_string(), + exit_kind: Pattern5ExitKind::Return, + exit_condition: exit_condition.clone(), + exit_value: Some(v("sum")), + carrier_var: None, + carrier_update: None, + 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::Pattern5InfiniteEarlyExit(plan)) => { + assert_eq!(plan.loop_var, "i"); + assert_eq!(plan.exit_kind, Pattern5ExitKind::Return); + } + other => panic!("expected pattern5 infinite early exit plan, got {:?}", other), + } + } + #[test] fn planner_sets_promotion_hint_for_pattern2_loopbodylocal() { let loop_condition = ASTNode::BinaryOp { @@ -451,6 +523,7 @@ mod tests { pattern1_simplewhile: None, pattern3_ifphi: None, pattern4_continue: None, + pattern5_infinite_early_exit: 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 30399791..fa9e677b 100644 --- a/src/mir/builder/control_flow/plan/single_planner/rules.rs +++ b/src/mir/builder/control_flow/plan/single_planner/rules.rs @@ -36,7 +36,7 @@ pub(super) fn try_build_domain_plan(ctx: &LoopPatternContext) -> Result Result { + match planner_opt.as_ref() { + Some(DomainPlan::Pattern5InfiniteEarlyExit(_)) => (planner_opt.clone(), false), + _ => (extractors::pattern5::extract_pattern5_plan(ctx.condition, ctx.body)?, true), + } + } RuleKind::Pattern2 => { match planner_opt.as_ref() { Some(DomainPlan::Pattern2Break(_)) => (planner_opt.clone(), false), @@ -186,6 +192,7 @@ enum RuleKind { Simple(fn(&ASTNode, &[ASTNode]) -> Result, String>), Pattern6, Pattern7, + Pattern5, Pattern2, Pattern3, Pattern4,