phase29ak(p5): ctx-aware planner candidates; fix phase1883 routing
This commit is contained in:
@ -88,12 +88,22 @@ pub(crate) fn count_control_flow(
|
||||
ASTNode::Return { .. } if detector.count_returns => {
|
||||
counts.return_count += 1;
|
||||
}
|
||||
ASTNode::Loop { .. } if depth > 0 => {
|
||||
ASTNode::ScopeBox { body, .. } => {
|
||||
for stmt in body {
|
||||
scan_node(stmt, counts, detector, depth);
|
||||
}
|
||||
}
|
||||
ASTNode::Loop { body, .. }
|
||||
| ASTNode::While { body, .. }
|
||||
| ASTNode::ForRange { body, .. } => {
|
||||
counts.has_nested_loop = true;
|
||||
// Skip nested loop bodies if configured
|
||||
if detector.skip_nested_control_flow {
|
||||
return;
|
||||
}
|
||||
for stmt in body {
|
||||
scan_node(stmt, counts, detector, depth + 1);
|
||||
}
|
||||
}
|
||||
ASTNode::If {
|
||||
then_body,
|
||||
@ -358,6 +368,17 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn make_nested_loop() -> ASTNode {
|
||||
ASTNode::Loop {
|
||||
condition: Box::new(ASTNode::Variable {
|
||||
name: "cond".to_string(),
|
||||
span: Span::unknown(),
|
||||
}),
|
||||
body: vec![make_break()],
|
||||
span: Span::unknown(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_count_control_flow_break() {
|
||||
let body = vec![make_break()];
|
||||
@ -417,6 +438,22 @@ mod tests {
|
||||
assert!(!has_control_flow_statement(&body));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_count_control_flow_detects_nested_loop_at_top_level() {
|
||||
let body = vec![make_nested_loop()];
|
||||
let counts = count_control_flow(&body, ControlFlowDetector::default());
|
||||
assert!(counts.has_nested_loop);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_has_control_flow_statement_break_in_scopebox() {
|
||||
let body = vec![ASTNode::ScopeBox {
|
||||
body: vec![make_break()],
|
||||
span: Span::unknown(),
|
||||
}];
|
||||
assert!(has_control_flow_statement(&body));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_loop_variable_success() {
|
||||
let condition = ASTNode::BinaryOp {
|
||||
|
||||
@ -17,7 +17,7 @@ pub(crate) struct Pattern1Parts {
|
||||
/// # Detection Criteria (誤マッチ防止強化版)
|
||||
///
|
||||
/// 1. **Condition**: 比較演算(<, <=, >, >=, ==, !=)で左辺が変数
|
||||
/// 2. **Body**: No break/continue/if-else-phi (return is allowed - it's not loop control flow)
|
||||
/// 2. **Body**: No break/continue/nested-loop/if-else-phi (return is allowed - it's not loop control flow)
|
||||
/// 3. **Step**: 単純な増減パターン (i = i + 1, i = i - 1 など)
|
||||
///
|
||||
/// # Four-Phase Validation
|
||||
@ -48,6 +48,18 @@ pub(crate) fn extract_simple_while_parts(
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// Phase 29ak: Reject nested loops.
|
||||
//
|
||||
// Nested loops are Pattern6NestedLoopMinimal territory; letting Pattern1 match them
|
||||
// causes routing to short-circuit before JoinIR can select the nested-loop lowerer.
|
||||
let counts = super::common_helpers::count_control_flow(
|
||||
body,
|
||||
super::common_helpers::ControlFlowDetector::default(),
|
||||
);
|
||||
if counts.has_nested_loop {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// Phase 286 P2.6: Reject if-else statements (Pattern3 territory)
|
||||
// Pattern1 allows simple if without else, but not if-else (which is Pattern3)
|
||||
if super::common_helpers::has_if_else_statement(body) {
|
||||
|
||||
@ -7,6 +7,7 @@ use crate::ast::ASTNode;
|
||||
use crate::mir::builder::control_flow::plan::normalize::CanonicalLoopFacts;
|
||||
|
||||
use super::candidates::{CandidateSet, PlanCandidate};
|
||||
use super::context::PlannerContext;
|
||||
use super::outcome::build_plan_with_facts;
|
||||
use super::Freeze;
|
||||
use crate::mir::builder::control_flow::plan::{
|
||||
@ -14,6 +15,7 @@ use crate::mir::builder::control_flow::plan::{
|
||||
Pattern4ContinuePlan, Pattern5InfiniteEarlyExitPlan, Pattern8BoolPredicateScanPlan,
|
||||
Pattern9AccumConstLoopPlan, ScanDirection, ScanWithInitPlan, SplitScanPlan,
|
||||
};
|
||||
use crate::mir::loop_pattern_detection::LoopPatternKind;
|
||||
|
||||
/// Phase 29ai P0: External-ish SSOT entrypoint (skeleton)
|
||||
///
|
||||
@ -27,6 +29,13 @@ pub(in crate::mir::builder) fn build_plan(
|
||||
|
||||
pub(in crate::mir::builder) fn build_plan_from_facts(
|
||||
facts: CanonicalLoopFacts,
|
||||
) -> Result<Option<DomainPlan>, Freeze> {
|
||||
build_plan_from_facts_ctx(&PlannerContext::default_for_legacy(), facts)
|
||||
}
|
||||
|
||||
pub(in crate::mir::builder) fn build_plan_from_facts_ctx(
|
||||
ctx: &PlannerContext,
|
||||
facts: CanonicalLoopFacts,
|
||||
) -> Result<Option<DomainPlan>, Freeze> {
|
||||
// Phase 29ai P3: CandidateSet-based boundary (SSOT)
|
||||
//
|
||||
@ -34,6 +43,12 @@ pub(in crate::mir::builder) fn build_plan_from_facts(
|
||||
// unreachable in normal execution today. We still implement the SSOT
|
||||
// boundary here so that future Facts work cannot drift.
|
||||
|
||||
let allow_pattern1 = match ctx.pattern_kind {
|
||||
Some(LoopPatternKind::Pattern1SimpleWhile) | None => true,
|
||||
Some(_) => false,
|
||||
};
|
||||
let allow_pattern8 = !ctx.in_static_box;
|
||||
|
||||
let mut candidates = CandidateSet::new();
|
||||
|
||||
if let Some(scan) = &facts.facts.scan_with_init {
|
||||
@ -133,18 +148,20 @@ pub(in crate::mir::builder) fn build_plan_from_facts(
|
||||
});
|
||||
}
|
||||
|
||||
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 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 {
|
||||
@ -160,15 +177,17 @@ pub(in crate::mir::builder) fn build_plan_from_facts(
|
||||
});
|
||||
}
|
||||
|
||||
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",
|
||||
});
|
||||
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",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
candidates.finalize()
|
||||
|
||||
@ -6,3 +6,13 @@ pub(in crate::mir::builder) struct PlannerContext {
|
||||
pub in_static_box: bool,
|
||||
pub debug: bool,
|
||||
}
|
||||
|
||||
impl PlannerContext {
|
||||
pub(in crate::mir::builder) fn default_for_legacy() -> Self {
|
||||
Self {
|
||||
pattern_kind: None,
|
||||
in_static_box: false,
|
||||
debug: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@ pub(in crate::mir::builder) mod context;
|
||||
pub(in crate::mir::builder) mod freeze;
|
||||
pub(in crate::mir::builder) mod outcome;
|
||||
|
||||
pub(in crate::mir::builder) use build::build_plan;
|
||||
pub(in crate::mir::builder) use build::{build_plan, build_plan_from_facts_ctx};
|
||||
pub(in crate::mir::builder) use context::PlannerContext;
|
||||
pub(in crate::mir::builder) use freeze::Freeze;
|
||||
pub(in crate::mir::builder) use outcome::{
|
||||
|
||||
@ -9,7 +9,7 @@ use crate::mir::builder::control_flow::plan::normalize::{
|
||||
};
|
||||
use crate::mir::builder::control_flow::plan::DomainPlan;
|
||||
|
||||
use super::build::build_plan_from_facts;
|
||||
use super::build::build_plan_from_facts_ctx;
|
||||
use super::context::PlannerContext;
|
||||
use super::Freeze;
|
||||
|
||||
@ -24,7 +24,8 @@ pub(in crate::mir::builder) fn build_plan_with_facts(
|
||||
body: &[ASTNode],
|
||||
) -> Result<PlanBuildOutcome, Freeze> {
|
||||
let facts = try_build_loop_facts(condition, body)?;
|
||||
build_plan_from_facts_opt(facts)
|
||||
let legacy_ctx = PlannerContext::default_for_legacy();
|
||||
build_plan_from_facts_opt_with(&legacy_ctx, facts)
|
||||
}
|
||||
|
||||
pub(in crate::mir::builder) fn build_plan_with_facts_ctx(
|
||||
@ -33,10 +34,11 @@ pub(in crate::mir::builder) fn build_plan_with_facts_ctx(
|
||||
body: &[ASTNode],
|
||||
) -> Result<PlanBuildOutcome, Freeze> {
|
||||
let facts = try_build_loop_facts_with_ctx(ctx, condition, body)?;
|
||||
build_plan_from_facts_opt(facts)
|
||||
build_plan_from_facts_opt_with(ctx, facts)
|
||||
}
|
||||
|
||||
fn build_plan_from_facts_opt(
|
||||
fn build_plan_from_facts_opt_with(
|
||||
ctx: &PlannerContext,
|
||||
facts: Option<LoopFacts>,
|
||||
) -> Result<PlanBuildOutcome, Freeze> {
|
||||
let Some(facts) = facts else {
|
||||
@ -46,7 +48,7 @@ fn build_plan_from_facts_opt(
|
||||
});
|
||||
};
|
||||
let canonical = canonicalize_loop_facts(facts);
|
||||
let plan = build_plan_from_facts(canonical.clone())?;
|
||||
let plan = build_plan_from_facts_ctx(ctx, canonical.clone())?;
|
||||
|
||||
Ok(PlanBuildOutcome {
|
||||
facts: Some(canonical),
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
//! (observability/behavior must not change).
|
||||
|
||||
use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext;
|
||||
use crate::mir::loop_pattern_detection::LoopPatternKind;
|
||||
|
||||
use crate::mir::builder::control_flow::plan::extractors;
|
||||
use crate::mir::builder::control_flow::plan::facts::pattern2_loopbodylocal_facts::LoopBodyLocalShape;
|
||||
@ -33,22 +32,11 @@ pub(super) fn try_build_domain_plan(ctx: &LoopPatternContext) -> Result<Option<D
|
||||
let rule_id = *rule_id;
|
||||
let name = rule_name(rule_id);
|
||||
let planner_hit = try_take_planner(&planner_opt, rule_id);
|
||||
// Pattern1 gating is handled by planner/facts; fallback mirrors the same contract.
|
||||
let allow_pattern1 = match ctx.pattern_kind {
|
||||
LoopPatternKind::Pattern1SimpleWhile => true,
|
||||
_ => false,
|
||||
};
|
||||
let allow_pattern8 = !ctx.in_static_box;
|
||||
let (plan_opt, log_none) = if planner_hit.is_some() {
|
||||
(planner_hit, false)
|
||||
} else {
|
||||
let plan_opt = fallback_extract(ctx, rule_id, allow_pattern1, allow_pattern8)?;
|
||||
let log_none = if matches!(rule_id, PlanRuleId::Pattern1) {
|
||||
allow_pattern1
|
||||
} else {
|
||||
true
|
||||
};
|
||||
(plan_opt, log_none)
|
||||
(fallback_extract(ctx, rule_id, allow_pattern8)?, true)
|
||||
};
|
||||
|
||||
let promotion_tag = if matches!(rule_id, PlanRuleId::Pattern2)
|
||||
@ -103,16 +91,10 @@ fn try_take_planner(planner_opt: &Option<DomainPlan>, kind: PlanRuleId) -> Optio
|
||||
fn fallback_extract(
|
||||
ctx: &LoopPatternContext,
|
||||
kind: PlanRuleId,
|
||||
allow_pattern1: bool,
|
||||
allow_pattern8: bool,
|
||||
) -> Result<Option<DomainPlan>, String> {
|
||||
match kind {
|
||||
PlanRuleId::Pattern1 => {
|
||||
if !allow_pattern1 {
|
||||
return Ok(None);
|
||||
}
|
||||
extractors::pattern1::extract_pattern1_plan(ctx.condition, ctx.body)
|
||||
}
|
||||
PlanRuleId::Pattern1 => extractors::pattern1::extract_pattern1_plan(ctx.condition, ctx.body),
|
||||
PlanRuleId::Pattern2 => {
|
||||
extractors::pattern2_break::extract_pattern2_plan(ctx.condition, ctx.body)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user