diff --git a/src/mir/builder/control_flow/plan/planner/build.rs b/src/mir/builder/control_flow/plan/planner/build.rs index 0ddefc3b..d95fd457 100644 --- a/src/mir/builder/control_flow/plan/planner/build.rs +++ b/src/mir/builder/control_flow/plan/planner/build.rs @@ -4,6 +4,8 @@ use crate::ast::ASTNode; +use crate::mir::builder::control_flow::plan::facts::feature_facts::ExitUsageFacts; +use crate::mir::builder::control_flow::plan::facts::skeleton_facts::SkeletonKind; use crate::mir::builder::control_flow::plan::normalize::CanonicalLoopFacts; use super::candidates::{CandidateSet, PlanCandidate}; @@ -49,156 +51,221 @@ pub(in crate::mir::builder) fn build_plan_from_facts_ctx( }; let allow_pattern8 = !ctx.in_static_box; + let _skeleton_kind = infer_skeleton_kind(&facts); + let _exit_usage = infer_exit_usage(&facts); + let mut candidates = CandidateSet::new(); - if let Some(scan) = &facts.facts.scan_with_init { - candidates.push(PlanCandidate { - plan: DomainPlan::ScanWithInit(ScanWithInitPlan { - loop_var: scan.loop_var.clone(), - haystack: scan.haystack.clone(), - needle: scan.needle.clone(), - step_lit: scan.step_lit, - early_return_expr: ASTNode::Variable { - name: scan.loop_var.clone(), - span: crate::ast::Span::unknown(), - }, - not_found_return_lit: -1, - scan_direction: ScanDirection::Forward, - dynamic_needle: false, - }), - rule: "loop/scan_with_init", - }); - } - - if let Some(split_scan) = &facts.facts.split_scan { - candidates.push(PlanCandidate { - plan: DomainPlan::SplitScan(SplitScanPlan { - s_var: split_scan.s_var.clone(), - sep_var: split_scan.sep_var.clone(), - result_var: split_scan.result_var.clone(), - i_var: split_scan.i_var.clone(), - start_var: split_scan.start_var.clone(), - }), - rule: "loop/split_scan", - }); - } - - if let Some(pattern2) = &facts.facts.pattern2_break { - let promotion = facts - .facts - .pattern2_loopbodylocal - .as_ref() - .map(|facts| Pattern2PromotionHint::LoopBodyLocal(facts.clone())); - candidates.push(PlanCandidate { - plan: DomainPlan::Pattern2Break(Pattern2BreakPlan { - loop_var: pattern2.loop_var.clone(), - carrier_var: pattern2.carrier_var.clone(), - loop_condition: pattern2.loop_condition.clone(), - break_condition: pattern2.break_condition.clone(), - carrier_update_in_break: pattern2.carrier_update_in_break.clone(), - carrier_update_in_body: pattern2.carrier_update_in_body.clone(), - loop_increment: pattern2.loop_increment.clone(), - promotion, - }), - rule: "loop/pattern2_break", - }); - } - - 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(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(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 allow_pattern8 { - if let Some(pattern8) = &facts.facts.pattern8_bool_predicate_scan { - candidates.push(PlanCandidate { - plan: DomainPlan::Pattern8BoolPredicateScan(Pattern8BoolPredicateScanPlan { - loop_var: pattern8.loop_var.clone(), - haystack: pattern8.haystack.clone(), - predicate_receiver: pattern8.predicate_receiver.clone(), - predicate_method: pattern8.predicate_method.clone(), - condition: pattern8.condition.clone(), - step_lit: pattern8.step_lit, - }), - rule: "loop/pattern8_bool_predicate_scan", - }); - } - } - - if let Some(pattern9) = &facts.facts.pattern9_accum_const_loop { - candidates.push(PlanCandidate { - plan: DomainPlan::Pattern9AccumConstLoop(Pattern9AccumConstLoopPlan { - loop_var: pattern9.loop_var.clone(), - acc_var: pattern9.acc_var.clone(), - condition: pattern9.condition.clone(), - acc_update: pattern9.acc_update.clone(), - loop_increment: pattern9.loop_increment.clone(), - }), - rule: "loop/pattern9_accum_const_loop", - }); - } - - if allow_pattern1 { - if let Some(pattern1) = &facts.facts.pattern1_simplewhile { - candidates.push(PlanCandidate { - plan: DomainPlan::Pattern1SimpleWhile(Pattern1SimpleWhilePlan { - loop_var: pattern1.loop_var.clone(), - condition: pattern1.condition.clone(), - loop_increment: pattern1.loop_increment.clone(), - }), - rule: "loop/pattern1_simplewhile", - }); - } - } + push_scan_with_init(&mut candidates, &facts); + push_split_scan(&mut candidates, &facts); + push_pattern2_break(&mut candidates, &facts); + push_pattern3_ifphi(&mut candidates, &facts); + push_pattern4_continue(&mut candidates, &facts); + push_pattern5_infinite_early_exit(&mut candidates, &facts); + push_pattern8_bool_predicate_scan(&mut candidates, &facts, allow_pattern8); + push_pattern9_accum_const_loop(&mut candidates, &facts); + push_pattern1_simplewhile(&mut candidates, &facts, allow_pattern1); candidates.finalize() } +fn infer_skeleton_kind(facts: &CanonicalLoopFacts) -> Option { + facts.facts.skeleton.as_ref().map(|s| s.kind) +} + +fn infer_exit_usage(facts: &CanonicalLoopFacts) -> Option { + facts + .facts + .features + .as_ref() + .map(|features| features.exit_usage.clone()) +} + +fn push_scan_with_init(candidates: &mut CandidateSet, facts: &CanonicalLoopFacts) { + let Some(scan) = &facts.facts.scan_with_init else { + return; + }; + candidates.push(PlanCandidate { + plan: DomainPlan::ScanWithInit(ScanWithInitPlan { + loop_var: scan.loop_var.clone(), + haystack: scan.haystack.clone(), + needle: scan.needle.clone(), + step_lit: scan.step_lit, + early_return_expr: ASTNode::Variable { + name: scan.loop_var.clone(), + span: crate::ast::Span::unknown(), + }, + not_found_return_lit: -1, + scan_direction: ScanDirection::Forward, + dynamic_needle: false, + }), + rule: "loop/scan_with_init", + }); +} + +fn push_split_scan(candidates: &mut CandidateSet, facts: &CanonicalLoopFacts) { + let Some(split_scan) = &facts.facts.split_scan else { + return; + }; + candidates.push(PlanCandidate { + plan: DomainPlan::SplitScan(SplitScanPlan { + s_var: split_scan.s_var.clone(), + sep_var: split_scan.sep_var.clone(), + result_var: split_scan.result_var.clone(), + i_var: split_scan.i_var.clone(), + start_var: split_scan.start_var.clone(), + }), + rule: "loop/split_scan", + }); +} + +fn push_pattern2_break(candidates: &mut CandidateSet, facts: &CanonicalLoopFacts) { + let Some(pattern2) = &facts.facts.pattern2_break else { + return; + }; + let promotion = facts + .facts + .pattern2_loopbodylocal + .as_ref() + .map(|facts| Pattern2PromotionHint::LoopBodyLocal(facts.clone())); + candidates.push(PlanCandidate { + plan: DomainPlan::Pattern2Break(Pattern2BreakPlan { + loop_var: pattern2.loop_var.clone(), + carrier_var: pattern2.carrier_var.clone(), + loop_condition: pattern2.loop_condition.clone(), + break_condition: pattern2.break_condition.clone(), + carrier_update_in_break: pattern2.carrier_update_in_break.clone(), + carrier_update_in_body: pattern2.carrier_update_in_body.clone(), + loop_increment: pattern2.loop_increment.clone(), + promotion, + }), + rule: "loop/pattern2_break", + }); +} + +fn push_pattern3_ifphi(candidates: &mut CandidateSet, facts: &CanonicalLoopFacts) { + let Some(pattern3) = &facts.facts.pattern3_ifphi else { + return; + }; + 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", + }); +} + +fn push_pattern4_continue(candidates: &mut CandidateSet, facts: &CanonicalLoopFacts) { + let Some(pattern4) = &facts.facts.pattern4_continue else { + return; + }; + 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", + }); +} + +fn push_pattern5_infinite_early_exit(candidates: &mut CandidateSet, facts: &CanonicalLoopFacts) { + let Some(pattern5) = &facts.facts.pattern5_infinite_early_exit else { + return; + }; + 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", + }); +} + +fn push_pattern8_bool_predicate_scan( + candidates: &mut CandidateSet, + facts: &CanonicalLoopFacts, + allow_pattern8: bool, +) { + if !allow_pattern8 { + return; + } + let Some(pattern8) = &facts.facts.pattern8_bool_predicate_scan else { + return; + }; + candidates.push(PlanCandidate { + plan: DomainPlan::Pattern8BoolPredicateScan(Pattern8BoolPredicateScanPlan { + loop_var: pattern8.loop_var.clone(), + haystack: pattern8.haystack.clone(), + predicate_receiver: pattern8.predicate_receiver.clone(), + predicate_method: pattern8.predicate_method.clone(), + condition: pattern8.condition.clone(), + step_lit: pattern8.step_lit, + }), + rule: "loop/pattern8_bool_predicate_scan", + }); +} + +fn push_pattern9_accum_const_loop(candidates: &mut CandidateSet, facts: &CanonicalLoopFacts) { + let Some(pattern9) = &facts.facts.pattern9_accum_const_loop else { + return; + }; + candidates.push(PlanCandidate { + plan: DomainPlan::Pattern9AccumConstLoop(Pattern9AccumConstLoopPlan { + loop_var: pattern9.loop_var.clone(), + acc_var: pattern9.acc_var.clone(), + condition: pattern9.condition.clone(), + acc_update: pattern9.acc_update.clone(), + loop_increment: pattern9.loop_increment.clone(), + }), + rule: "loop/pattern9_accum_const_loop", + }); +} + +fn push_pattern1_simplewhile( + candidates: &mut CandidateSet, + facts: &CanonicalLoopFacts, + allow_pattern1: bool, +) { + if !allow_pattern1 { + return; + } + let Some(pattern1) = &facts.facts.pattern1_simplewhile else { + return; + }; + candidates.push(PlanCandidate { + plan: DomainPlan::Pattern1SimpleWhile(Pattern1SimpleWhilePlan { + loop_var: pattern1.loop_var.clone(), + condition: pattern1.condition.clone(), + loop_increment: pattern1.loop_increment.clone(), + }), + rule: "loop/pattern1_simplewhile", + }); +} + #[cfg(test)] mod tests { use super::*; use crate::mir::builder::control_flow::plan::facts::scan_shapes::{ ConditionShape, StepShape, }; + use crate::mir::builder::control_flow::plan::facts::feature_facts::{ + ExitUsageFacts, LoopFeatureFacts, + }; use crate::mir::builder::control_flow::plan::facts::loop_facts::{ LoopFacts, ScanWithInitFacts, SplitScanFacts, }; @@ -224,6 +291,9 @@ mod tests { use crate::mir::builder::control_flow::plan::facts::pattern2_loopbodylocal_facts::{ LoopBodyLocalShape, Pattern2LoopBodyLocalFacts, }; + use crate::mir::builder::control_flow::plan::facts::skeleton_facts::{ + SkeletonFacts, SkeletonKind, + }; use crate::mir::builder::control_flow::plan::normalize::canonicalize_loop_facts; use crate::mir::builder::control_flow::plan::{Pattern2PromotionHint, Pattern5ExitKind}; use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span}; @@ -338,6 +408,47 @@ mod tests { } } + #[test] + fn planner_ignores_skeleton_and_feature_staging() { + let facts = LoopFacts { + condition_shape: ConditionShape::Unknown, + step_shape: StepShape::Unknown, + skeleton: Some(SkeletonFacts { + kind: SkeletonKind::Loop, + }), + features: Some(LoopFeatureFacts { + exit_usage: ExitUsageFacts { + has_break: true, + has_continue: false, + has_return: false, + }, + value_join: None, + cleanup: None, + }), + scan_with_init: Some(ScanWithInitFacts { + loop_var: "i".to_string(), + haystack: "s".to_string(), + needle: "ch".to_string(), + step_lit: 1, + }), + split_scan: None, + pattern1_simplewhile: None, + pattern3_ifphi: None, + pattern4_continue: None, + pattern5_infinite_early_exit: None, + pattern8_bool_predicate_scan: None, + pattern9_accum_const_loop: None, + 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::ScanWithInit(_)) => {} + other => panic!("expected scan_with_init plan, got {:?}", other), + } + } + #[test] fn planner_builds_pattern1_simplewhile_plan_from_facts() { let loop_condition = ASTNode::BinaryOp {