diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index f689d334..7b634cf6 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -24,6 +24,9 @@ single_planner の legacy_rules を撤去し、Pattern1/3/4/5/8/9 の抽出を p **2025-12-29: Phase 29aj P2 COMPLETE (Pattern1 planner-first)** 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-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 c4a3d6dc..7752ef72 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 P3(TBD: promotion hint を JoinIR 側の orchestrator へ配線、挙動不変) +Next: Phase 29aj P4(TBD: promotion hint を JoinIR 側の orchestrator へ配線、挙動不変) + +**2025-12-29: Phase 29aj P3 完了** ✅ +- 目的: Pattern3(If-Phi)を Facts→Planner-first に移行(仕様不変) +- 実装: `src/mir/builder/control_flow/plan/facts/pattern3_ifphi_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` PASS **2025-12-29: Phase 29aj P2 完了** ✅ - 目的: chosen_rule を撤去し、Pattern1 を Facts→Planner-first に移行(仕様不変) diff --git a/docs/development/current/main/30-Backlog.md b/docs/development/current/main/30-Backlog.md index 1ae04db2..965e690f 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 ✅ 完了 - - Next: Phase 29aj P3(TBD: promotion hint を JoinIR 側の orchestrator へ配線、挙動不変) + - 状況: P0/P1/P2/P3 ✅ 完了 + - Next: Phase 29aj P4(TBD: promotion hint を JoinIR 側の orchestrator へ配線、挙動不変) - **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/P3-PATTERN3-IFPHI-PLANNER-FIRST-INSTRUCTIONS.md b/docs/development/current/main/phases/phase-29aj/P3-PATTERN3-IFPHI-PLANNER-FIRST-INSTRUCTIONS.md new file mode 100644 index 00000000..63ae6fdb --- /dev/null +++ b/docs/development/current/main/phases/phase-29aj/P3-PATTERN3-IFPHI-PLANNER-FIRST-INSTRUCTIONS.md @@ -0,0 +1,87 @@ +# Phase 29aj P3: Pattern3 (If-Phi) planner-first via Facts (subset) + +Date: 2025-12-29 +Status: Ready for execution +Scope: Pattern3 facts → planner candidate → single_planner planner-first(仕様不変) +Goal: Pattern3 を Facts→Planner に乗せ、extractor 依存を 1 本減らす + +## Objective + +- Pattern3(Loop with If-Else PHI)を Facts→Planner 経路に追加 +- single_planner は Pattern3 の型一致時のみ planner-first 採用 +- 既定挙動・観測・エラー文字列は不変 + +## Non-goals + +- Pattern4/5/8/9 の planner-first 化 +- ルール順序 SSOT の CandidateSet 移管 +- Freeze/Fail-Fast の新規導入 + +## Implementation Steps + +### Step 1: Facts SSOT 追加(Pattern3) + +Files: +- `src/mir/builder/control_flow/plan/facts/pattern3_ifphi_facts.rs` (new) + +Facts: +- `Pattern3IfPhiFacts { loop_var, carrier_var, condition, if_condition, then_update, else_update, loop_increment }` + +Extraction rules (Ok(None) fallback only): +- condition は比較演算(left が Variable) +- if-else が存在し、then/else 両方で同一変数に代入 +- return / break / continue / nested-if は Ok(None) +- loop_increment は `extract_loop_increment_plan` で取れるときのみ + +### 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 に `pattern3_ifphi` を追加 +- `try_build_loop_facts()` に抽出を追加 +- all-none 判定に `pattern3_ifphi` を含める + +### Step 3: Planner candidate 追加 + +File: +- `src/mir/builder/control_flow/plan/planner/build.rs` + +Changes: +- facts が Some のとき `DomainPlan::Pattern3IfPhi` を候補に追加 +- rule 名は `loop/pattern3_ifphi` +- unit test 追加 + +### Step 4: single_planner を Pattern3 planner-first に + +File: +- `src/mir/builder/control_flow/plan/single_planner/rules.rs` + +Changes: +- RuleKind::Pattern3 を追加 +- planner_opt が `Pattern3IfPhi` のとき採用 +- それ以外は 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` + +## Commit + +- `git add -A && git commit -m "phase29aj(p3): planner-first pattern3 if-phi facts subset"` + +## Next (P4 candidate) + +- Pattern4(Continue)を Facts→Planner-first に寄せる(subset) diff --git a/docs/development/current/main/phases/phase-29aj/README.md b/docs/development/current/main/phases/phase-29aj/README.md index 5c01d6a0..045ec705 100644 --- a/docs/development/current/main/phases/phase-29aj/README.md +++ b/docs/development/current/main/phases/phase-29aj/README.md @@ -23,6 +23,13 @@ Goal: planner の facts/plan を 1 本の outcome に集約し、観測の SSOT - 完了: chosen_rule を削除し、Pattern1 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` +## P3: Pattern3 (If-Phi) planner-first(subset) + +- 指示書: `docs/development/current/main/phases/phase-29aj/P3-PATTERN3-IFPHI-PLANNER-FIRST-INSTRUCTIONS.md` +- ねらい: Pattern3 を Facts→Planner-first に接続し、extractor 依存を削減 +- 完了: 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` + ## 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 56075721..0a40c483 100644 --- a/src/mir/builder/control_flow/plan/facts/loop_facts.rs +++ b/src/mir/builder/control_flow/plan/facts/loop_facts.rs @@ -16,6 +16,9 @@ use super::scan_shapes::LengthMethod; use super::pattern1_simplewhile_facts::{ Pattern1SimpleWhileFacts, try_extract_pattern1_simplewhile_facts, }; +use super::pattern3_ifphi_facts::{ + Pattern3IfPhiFacts, try_extract_pattern3_ifphi_facts, +}; use super::pattern2_break_facts::{Pattern2BreakFacts, try_extract_pattern2_break_facts}; use super::pattern2_loopbodylocal_facts::{ Pattern2LoopBodyLocalFacts, try_extract_pattern2_loopbodylocal_facts, @@ -28,6 +31,7 @@ pub(in crate::mir::builder) struct LoopFacts { pub scan_with_init: Option, pub split_scan: Option, pub pattern1_simplewhile: Option, + pub pattern3_ifphi: Option, pub pattern2_break: Option, pub pattern2_loopbodylocal: Option, } @@ -62,12 +66,14 @@ pub(in crate::mir::builder) fn try_build_loop_facts( let scan_with_init = try_extract_scan_with_init_facts(body, &condition_shape, &step_shape)?; 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 pattern2_break = try_extract_pattern2_break_facts(condition, body)?; let pattern2_loopbodylocal = try_extract_pattern2_loopbodylocal_facts(condition, body)?; if scan_with_init.is_none() && split_scan.is_none() && pattern1_simplewhile.is_none() + && pattern3_ifphi.is_none() && pattern2_break.is_none() && pattern2_loopbodylocal.is_none() { @@ -80,6 +86,7 @@ pub(in crate::mir::builder) fn try_build_loop_facts( scan_with_init, split_scan, pattern1_simplewhile, + pattern3_ifphi, 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 0f2dbd59..affc1b20 100644 --- a/src/mir/builder/control_flow/plan/facts/mod.rs +++ b/src/mir/builder/control_flow/plan/facts/mod.rs @@ -8,6 +8,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 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/pattern3_ifphi_facts.rs b/src/mir/builder/control_flow/plan/facts/pattern3_ifphi_facts.rs new file mode 100644 index 00000000..eea1b534 --- /dev/null +++ b/src/mir/builder/control_flow/plan/facts/pattern3_ifphi_facts.rs @@ -0,0 +1,321 @@ +//! Phase 29aj P3: Pattern3IfPhiFacts (Facts SSOT) + +use crate::ast::ASTNode; +use crate::mir::builder::control_flow::plan::extractors::common_helpers::{ + extract_loop_increment_plan, find_if_else_statement, +}; +use crate::mir::builder::control_flow::plan::extractors::pattern3::extract_loop_with_if_phi_parts; +use crate::mir::builder::control_flow::plan::planner::Freeze; + +#[derive(Debug, Clone)] +pub(in crate::mir::builder) struct Pattern3IfPhiFacts { + pub loop_var: String, + pub carrier_var: String, + pub condition: ASTNode, + pub if_condition: ASTNode, + pub then_update: ASTNode, + pub else_update: ASTNode, + pub loop_increment: ASTNode, +} + +pub(in crate::mir::builder) fn try_extract_pattern3_ifphi_facts( + condition: &ASTNode, + body: &[ASTNode], +) -> Result, Freeze> { + let parts = match extract_loop_with_if_phi_parts(condition, body) { + Ok(Some(parts)) => parts, + Ok(None) => return Ok(None), + Err(_) => return Ok(None), + }; + + let if_stmt = match find_if_else_statement(body) { + Some(stmt) => stmt, + None => return Ok(None), + }; + + let (if_condition, then_update, else_update) = match if_stmt { + ASTNode::If { + condition: if_cond, + then_body, + else_body: Some(else_body), + .. + } => { + let then_update = match extract_single_update(then_body, &parts.merged_var) { + Some(update) => update, + None => return Ok(None), + }; + let else_update = match extract_single_update(else_body, &parts.merged_var) { + Some(update) => update, + None => return Ok(None), + }; + (if_cond.as_ref().clone(), then_update, else_update) + } + _ => return Ok(None), + }; + + let loop_increment = match extract_loop_increment_plan(body, &parts.loop_var) { + Ok(Some(inc)) => inc, + _ => return Ok(None), + }; + + Ok(Some(Pattern3IfPhiFacts { + loop_var: parts.loop_var, + carrier_var: parts.merged_var, + condition: condition.clone(), + if_condition, + then_update, + else_update, + loop_increment, + })) +} + +fn extract_single_update(body: &[ASTNode], carrier_var: &str) -> Option { + for stmt in body { + if let ASTNode::Assignment { target, value, .. } = stmt { + if let ASTNode::Variable { name, .. } = target.as_ref() { + if name == carrier_var { + return Some(value.as_ref().clone()); + } + } + } + } + None +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ast::{BinaryOperator, 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 assign(name: &str, value: ASTNode) -> ASTNode { + ASTNode::Assignment { + target: Box::new(v(name)), + value: Box::new(value), + span: Span::unknown(), + } + } + + fn if_else(cond: ASTNode, then_body: Vec, else_body: Vec) -> ASTNode { + ASTNode::If { + condition: Box::new(cond), + then_body, + else_body: Some(else_body), + span: Span::unknown(), + } + } + + fn loop_condition() -> ASTNode { + ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left: Box::new(v("i")), + right: Box::new(lit_int(3)), + span: Span::unknown(), + } + } + + fn loop_increment() -> ASTNode { + ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(v("i")), + right: Box::new(lit_int(1)), + span: Span::unknown(), + } + } + + #[test] + fn facts_extracts_pattern3_ifphi_success() { + let if_stmt = if_else( + ASTNode::BinaryOp { + operator: BinaryOperator::Greater, + left: Box::new(v("i")), + right: Box::new(lit_int(0)), + span: Span::unknown(), + }, + vec![assign( + "sum", + ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(v("sum")), + right: Box::new(lit_int(1)), + span: Span::unknown(), + }, + )], + vec![assign( + "sum", + ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(v("sum")), + right: Box::new(lit_int(0)), + span: Span::unknown(), + }, + )], + ); + + let body = vec![if_stmt, assign("i", loop_increment())]; + let facts = try_extract_pattern3_ifphi_facts(&loop_condition(), &body).expect("Ok"); + let facts = facts.expect("Some"); + assert_eq!(facts.loop_var, "i"); + assert_eq!(facts.carrier_var, "sum"); + } + + #[test] + fn facts_rejects_if_without_else() { + let if_stmt = ASTNode::If { + condition: Box::new(ASTNode::BinaryOp { + operator: BinaryOperator::Greater, + left: Box::new(v("i")), + right: Box::new(lit_int(0)), + span: Span::unknown(), + }), + then_body: vec![assign( + "sum", + ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(v("sum")), + right: Box::new(lit_int(1)), + span: Span::unknown(), + }, + )], + else_body: None, + span: Span::unknown(), + }; + + let body = vec![if_stmt, assign("i", loop_increment())]; + let facts = try_extract_pattern3_ifphi_facts(&loop_condition(), &body).expect("Ok"); + assert!(facts.is_none()); + } + + #[test] + fn facts_rejects_mismatched_carrier_vars() { + let if_stmt = if_else( + ASTNode::BinaryOp { + operator: BinaryOperator::Greater, + left: Box::new(v("i")), + right: Box::new(lit_int(0)), + span: Span::unknown(), + }, + vec![assign( + "sum", + ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(v("sum")), + right: Box::new(lit_int(1)), + span: Span::unknown(), + }, + )], + vec![assign( + "acc", + ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(v("acc")), + right: Box::new(lit_int(1)), + span: Span::unknown(), + }, + )], + ); + + let body = vec![if_stmt, assign("i", loop_increment())]; + let facts = try_extract_pattern3_ifphi_facts(&loop_condition(), &body).expect("Ok"); + assert!(facts.is_none()); + } + + #[test] + fn facts_rejects_break_continue_or_return() { + let if_stmt = if_else( + ASTNode::BinaryOp { + operator: BinaryOperator::Greater, + left: Box::new(v("i")), + right: Box::new(lit_int(0)), + span: Span::unknown(), + }, + vec![ASTNode::Break { span: Span::unknown() }], + vec![assign( + "sum", + ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(v("sum")), + right: Box::new(lit_int(1)), + span: Span::unknown(), + }, + )], + ); + + let body = vec![ + if_stmt, + ASTNode::Return { + value: Some(Box::new(lit_int(0))), + span: Span::unknown(), + }, + assign("i", loop_increment()), + ]; + let facts = try_extract_pattern3_ifphi_facts(&loop_condition(), &body).expect("Ok"); + assert!(facts.is_none()); + } + + #[test] + fn facts_rejects_nested_if() { + let nested_if = if_else( + ASTNode::BinaryOp { + operator: BinaryOperator::Greater, + left: Box::new(v("i")), + right: Box::new(lit_int(0)), + span: Span::unknown(), + }, + vec![assign( + "sum", + ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(v("sum")), + right: Box::new(lit_int(1)), + span: Span::unknown(), + }, + )], + vec![assign( + "sum", + ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(v("sum")), + right: Box::new(lit_int(0)), + span: Span::unknown(), + }, + )], + ); + + let if_stmt = if_else( + ASTNode::BinaryOp { + operator: BinaryOperator::Greater, + left: Box::new(v("i")), + right: Box::new(lit_int(0)), + span: Span::unknown(), + }, + vec![nested_if], + vec![assign( + "sum", + ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(v("sum")), + right: Box::new(lit_int(1)), + span: Span::unknown(), + }, + )], + ); + + let body = vec![if_stmt, assign("i", loop_increment())]; + let facts = try_extract_pattern3_ifphi_facts(&loop_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 973656df..ce7f1d4f 100644 --- a/src/mir/builder/control_flow/plan/planner/build.rs +++ b/src/mir/builder/control_flow/plan/planner/build.rs @@ -10,8 +10,8 @@ use super::candidates::{CandidateSet, PlanCandidate}; use super::outcome::build_plan_with_facts; use super::Freeze; use crate::mir::builder::control_flow::plan::{ - DomainPlan, Pattern1SimpleWhilePlan, Pattern2BreakPlan, Pattern2PromotionHint, ScanDirection, - ScanWithInitPlan, SplitScanPlan, + DomainPlan, Pattern1SimpleWhilePlan, Pattern2BreakPlan, Pattern2PromotionHint, Pattern3IfPhiPlan, + ScanDirection, ScanWithInitPlan, SplitScanPlan, }; /// Phase 29ai P0: External-ish SSOT entrypoint (skeleton) @@ -88,6 +88,21 @@ pub(in crate::mir::builder) fn build_plan_from_facts( }); } + if let Some(pattern3) = &facts.facts.pattern3_ifphi { + candidates.push(PlanCandidate { + plan: DomainPlan::Pattern3IfPhi(Pattern3IfPhiPlan { + loop_var: pattern3.loop_var.clone(), + carrier_var: pattern3.carrier_var.clone(), + condition: pattern3.condition.clone(), + if_condition: pattern3.if_condition.clone(), + then_update: pattern3.then_update.clone(), + else_update: pattern3.else_update.clone(), + loop_increment: pattern3.loop_increment.clone(), + }), + rule: "loop/pattern3_ifphi", + }); + } + if let Some(pattern1) = &facts.facts.pattern1_simplewhile { candidates.push(PlanCandidate { plan: DomainPlan::Pattern1SimpleWhile(Pattern1SimpleWhilePlan { @@ -114,6 +129,9 @@ mod tests { use crate::mir::builder::control_flow::plan::facts::pattern1_simplewhile_facts::{ Pattern1SimpleWhileFacts, }; + use crate::mir::builder::control_flow::plan::facts::pattern3_ifphi_facts::{ + Pattern3IfPhiFacts, + }; 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, @@ -150,6 +168,7 @@ mod tests { start_var: "start".to_string(), }), pattern1_simplewhile: None, + pattern3_ifphi: None, pattern2_break: None, pattern2_loopbodylocal: None, }; @@ -176,6 +195,7 @@ mod tests { scan_with_init: None, split_scan: None, pattern1_simplewhile: None, + pattern3_ifphi: None, pattern2_break: None, pattern2_loopbodylocal: None, }; @@ -197,6 +217,7 @@ mod tests { }), split_scan: None, pattern1_simplewhile: None, + pattern3_ifphi: None, pattern2_break: None, pattern2_loopbodylocal: None, }; @@ -235,6 +256,7 @@ mod tests { condition: loop_condition.clone(), loop_increment: loop_increment.clone(), }), + pattern3_ifphi: None, pattern2_break: None, pattern2_loopbodylocal: None, }; @@ -249,6 +271,69 @@ mod tests { } } + #[test] + fn planner_builds_pattern3_ifphi_plan_from_facts() { + let loop_condition = ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left: Box::new(v("i")), + right: Box::new(lit_int(3)), + span: Span::unknown(), + }; + let if_condition = ASTNode::BinaryOp { + operator: BinaryOperator::Greater, + left: Box::new(v("i")), + right: Box::new(lit_int(0)), + span: Span::unknown(), + }; + let then_update = ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(v("sum")), + right: Box::new(lit_int(1)), + span: Span::unknown(), + }; + let else_update = ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(v("sum")), + right: Box::new(lit_int(0)), + 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: Some(Pattern3IfPhiFacts { + loop_var: "i".to_string(), + carrier_var: "sum".to_string(), + condition: loop_condition.clone(), + if_condition: if_condition.clone(), + then_update: then_update.clone(), + else_update: else_update.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::Pattern3IfPhi(plan)) => { + assert_eq!(plan.loop_var, "i"); + assert_eq!(plan.carrier_var, "sum"); + } + other => panic!("expected pattern3 if-phi plan, got {:?}", other), + } + } + #[test] fn planner_sets_promotion_hint_for_pattern2_loopbodylocal() { let loop_condition = ASTNode::BinaryOp { @@ -285,6 +370,7 @@ mod tests { scan_with_init: None, split_scan: None, pattern1_simplewhile: None, + pattern3_ifphi: 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 69d8f7db..1ebf50c9 100644 --- a/src/mir/builder/control_flow/plan/single_planner/rules.rs +++ b/src/mir/builder/control_flow/plan/single_planner/rules.rs @@ -44,7 +44,7 @@ pub(super) fn try_build_domain_plan(ctx: &LoopPatternContext) -> Result Result { + match planner_opt.as_ref() { + Some(DomainPlan::Pattern3IfPhi(_)) => (planner_opt.clone(), false), + _ => (extractors::pattern3::extract_pattern3_plan(ctx.condition, ctx.body)?, true), + } + } RuleKind::Pattern1 => { match planner_opt.as_ref() { Some(DomainPlan::Pattern1SimpleWhile(_)) => (planner_opt.clone(), false), @@ -175,5 +181,6 @@ enum RuleKind { Pattern6, Pattern7, Pattern2, + Pattern3, Pattern1, }