phase29an(p2): stage planner via skeleton/feature inference (no behavior change)
This commit is contained in:
@ -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<SkeletonKind> {
|
||||
facts.facts.skeleton.as_ref().map(|s| s.kind)
|
||||
}
|
||||
|
||||
fn infer_exit_usage(facts: &CanonicalLoopFacts) -> Option<ExitUsageFacts> {
|
||||
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 {
|
||||
|
||||
Reference in New Issue
Block a user