phase29ai(p5): route JoinIR plan extraction via single_planner

This commit is contained in:
2025-12-29 08:04:00 +09:00
parent ff5c122fdc
commit 5ba68da9a0
16 changed files with 243 additions and 160 deletions

View File

@ -407,7 +407,7 @@ mod tests {
},
];
let ctx = LoopPatternContext::new(&condition, &body, "parse_number_like", true);
let ctx = LoopPatternContext::new(&condition, &body, "parse_number_like", true, false);
let builder = MirBuilder::new();
assert_eq!(ctx.pattern_kind, LoopPatternKind::Pattern2Break);
@ -452,7 +452,7 @@ mod tests {
},
];
let ctx = LoopPatternContext::new(&condition, &body, "_atoi", true);
let ctx = LoopPatternContext::new(&condition, &body, "_atoi", true, false);
let builder = MirBuilder::new();
// Verify pattern classification

View File

@ -28,6 +28,7 @@ use crate::mir::loop_pattern_detection::{LoopFeatures, LoopPatternKind};
use crate::mir::builder::control_flow::plan::lowerer::PlanLowerer;
use crate::mir::builder::control_flow::plan::normalizer::PlanNormalizer;
use crate::mir::builder::control_flow::plan::verifier::PlanVerifier;
use crate::mir::builder::control_flow::plan::single_planner;
/// AST Feature Extractor (declared in mod.rs as pub module, import from parent)
use super::ast_feature_extractor as ast_features;
@ -49,6 +50,9 @@ pub(crate) struct LoopPatternContext<'a> {
/// Debug logging enabled
pub debug: bool,
/// In static box context? (affects Pattern8 routing)
pub in_static_box: bool,
/// Has continue statement(s) in body? (Phase 194+)
#[allow(dead_code)]
pub has_continue: bool,
@ -93,6 +97,7 @@ impl<'a> LoopPatternContext<'a> {
body: &'a [ASTNode],
func_name: &'a str,
debug: bool,
in_static_box: bool,
) -> Self {
// Use AST Feature Extractor for break/continue detection
let has_continue = ast_features::detect_continue_in_body(body);
@ -110,6 +115,7 @@ impl<'a> LoopPatternContext<'a> {
body,
func_name,
debug,
in_static_box,
has_continue,
has_break,
features,
@ -126,9 +132,10 @@ impl<'a> LoopPatternContext<'a> {
body: &'a [ASTNode],
func_name: &'a str,
debug: bool,
in_static_box: bool,
fn_body: &'a [ASTNode],
) -> Self {
let mut ctx = Self::new(condition, body, func_name, debug);
let mut ctx = Self::new(condition, body, func_name, debug, in_static_box);
ctx.fn_body = Some(fn_body);
ctx
}
@ -159,157 +166,7 @@ fn lower_via_plan(
PlanLowerer::lower(builder, core_plan, ctx)
}
/// Phase 287: Plan extractor function type for routing table
///
/// Extractors have two signatures:
/// - Simple: (condition, body) - Pattern1/4/9
/// - WithFnBody: (condition, body, fn_body) - Pattern6/7
type SimplePlanExtractor = fn(&ASTNode, &[ASTNode]) -> Result<Option<crate::mir::builder::control_flow::plan::DomainPlan>, String>;
/// Phase 287: Plan extractor table entry
struct PlanExtractorEntry {
/// Pattern name for debug logging
name: &'static str,
/// Extractor function
extractor: PlanExtractorVariant,
}
/// Phase 287: Extractor function variant (handles different signatures)
enum PlanExtractorVariant {
/// Simple extractor: (condition, body)
Simple(SimplePlanExtractor),
/// Extractor with fn_body: (condition, body, fn_body) - Pattern6
WithFnBody(fn(&ASTNode, &[ASTNode], Option<&[ASTNode]>) -> Result<Option<crate::mir::builder::control_flow::plan::DomainPlan>, String>),
/// Extractor with post_loop_code: (condition, body, post_loop_code) - Pattern7
///
/// NOTE (Phase 286): Currently always called with &[] for post_loop_code.
/// This variant is kept for future extension (post-loop segment analysis).
/// The _post_loop_code parameter in Pattern7 extractor is intentionally unused.
WithPostLoop(fn(&ASTNode, &[ASTNode], &[ASTNode]) -> Result<Option<crate::mir::builder::control_flow::plan::DomainPlan>, String>),
}
/// Phase 287: Plan extractor routing table (SSOT)
///
/// Order is important: more specific patterns first
/// - Pattern6/7: Need fn_body for capture analysis
/// - Pattern8: Bool predicate scan (early-exit return, more specific)
/// - Pattern3: If-phi merge (carrier with conditional update, more specific than Pattern4)
/// - Pattern4: Continue loop (more specific than Pattern1)
/// - Pattern9: Accum const loop (2 carriers, more specific than Pattern1)
/// - Pattern1: Simple while (fallback)
static PLAN_EXTRACTORS: &[PlanExtractorEntry] = &[
PlanExtractorEntry {
name: "Pattern6_ScanWithInit (Phase 273)",
extractor: PlanExtractorVariant::WithFnBody(super::pattern6_scan_with_init::extract_scan_with_init_plan),
},
PlanExtractorEntry {
name: "Pattern7_SplitScan (Phase 273)",
extractor: PlanExtractorVariant::WithPostLoop(super::pattern7_split_scan::extract_split_scan_plan),
},
PlanExtractorEntry {
name: "Pattern5_InfiniteEarlyExit (Phase 286 P3.2)",
extractor: PlanExtractorVariant::Simple(super::extractors::pattern5::extract_pattern5_plan),
},
PlanExtractorEntry {
name: "Pattern8_BoolPredicateScan (Phase 286 P2.4)",
extractor: PlanExtractorVariant::Simple(super::extractors::pattern8::extract_pattern8_plan),
},
PlanExtractorEntry {
name: "Pattern3_IfPhi (Phase 286 P2.6)",
extractor: PlanExtractorVariant::Simple(super::extractors::pattern3::extract_pattern3_plan),
},
PlanExtractorEntry {
name: "Pattern4_Continue (Phase 286 P2)",
extractor: PlanExtractorVariant::Simple(super::extractors::pattern4::extract_pattern4_plan),
},
PlanExtractorEntry {
name: "Pattern9_AccumConstLoop (Phase 286 P2.3)",
extractor: PlanExtractorVariant::Simple(super::extractors::pattern9::extract_pattern9_plan),
},
PlanExtractorEntry {
name: "Pattern2_Break (Phase 286 P3.1)",
extractor: PlanExtractorVariant::Simple(super::extractors::pattern2::extract_pattern2_plan),
},
PlanExtractorEntry {
name: "Pattern1_SimpleWhile (Phase 286 P2.1)",
extractor: PlanExtractorVariant::Simple(super::extractors::pattern1::extract_pattern1_plan),
},
];
/// Phase 287: Try all plan extractors in priority order
///
/// Returns Ok(Some(value_id)) if any extractor succeeds
/// Returns Ok(None) if no extractor matches
/// Returns Err if extraction or lowering fails
fn try_plan_extractors(
builder: &mut MirBuilder,
ctx: &LoopPatternContext,
) -> Result<Option<ValueId>, String> {
use super::super::trace;
for entry in PLAN_EXTRACTORS {
// Phase 286 P2.6: Pattern1 Plan guard (structural Fail-Fast)
// Pattern1 should only match Pattern1SimpleWhile pattern_kind
// This prevents Pattern1 from incorrectly matching Pattern3 fixtures
if entry.name.contains("Pattern1") {
use crate::mir::loop_pattern_detection::LoopPatternKind;
if ctx.pattern_kind != LoopPatternKind::Pattern1SimpleWhile {
continue;
}
}
// Try extraction based on variant
let plan_opt = match &entry.extractor {
PlanExtractorVariant::Simple(extractor) => {
extractor(ctx.condition, ctx.body)?
}
PlanExtractorVariant::WithFnBody(extractor) => {
// Pattern6: needs fn_body for capture analysis
extractor(ctx.condition, ctx.body, ctx.fn_body)?
}
PlanExtractorVariant::WithPostLoop(extractor) => {
// Pattern7: uses empty slice for post_loop_code (3rd param)
extractor(ctx.condition, ctx.body, &[])?
}
};
// If extraction succeeded, check if we can lower it
if let Some(domain_plan) = plan_opt {
// Phase 286 P3: Pattern8 static box filtering
// Plan extractors are pure (no builder access), so we filter here
// Static box loops with `me.method()` should be handled by ReceiverNormalizeBox, not Pattern8
if entry.name.contains("Pattern8") && builder.comp_ctx.current_static_box.is_some() {
if ctx.debug {
trace::trace().debug(
"route/plan",
&format!("{} extracted but rejected: static box context (fallback to legacy)", entry.name),
);
}
// Skip this pattern, try next extractor
continue;
}
let log_msg = format!("route=plan strategy=extract pattern={}", entry.name);
trace::trace().pattern("route", &log_msg, true);
// Phase 286 P2.6.1: Fail-Fast 統一 - extract 成功 → normalize/lower 失敗は即 Err
// Pattern3 stub fallback 撤去normalizer 実装済み)
return lower_via_plan(builder, domain_plan, ctx);
} else {
// Extraction returned None - try next extractor
if ctx.debug {
let debug_msg = format!("{} extraction returned None, trying next pattern", entry.name);
trace::trace().debug("route", &debug_msg);
}
}
}
// No extractor matched
Ok(None)
}
// Phase 29ai P5: Plan extractor routing moved to `plan::single_planner`.
/// Phase 272 P0.2 Refactoring: can_lower() strategy classification
///
@ -473,10 +330,9 @@ pub(crate) fn route_loop_pattern(
) -> Result<Option<ValueId>, String> {
use super::super::trace;
// Phase 287: Try all Plan extractors in priority order (Pattern6/7/4/9/1)
// This replaces ~100 lines of repetitive extraction blocks
if let Some(value_id) = try_plan_extractors(builder, ctx)? {
return Ok(Some(value_id));
// Phase 29ai P5: Single entrypoint for plan extraction (router has no rule table).
if let Some(domain_plan) = single_planner::try_build_domain_plan(ctx)? {
return lower_via_plan(builder, domain_plan, ctx);
}
// Phase 183: Route based on pre-classified pattern kind