diff --git a/docs/development/current/main/10-Now.md b/docs/development/current/main/10-Now.md index 11068df3..e8ecb84b 100644 --- a/docs/development/current/main/10-Now.md +++ b/docs/development/current/main/10-Now.md @@ -3,7 +3,7 @@ ## Current Focus - Phase: `docs/development/current/main/phases/phase-29ao/README.md` -- Next: TBD (see `docs/development/current/main/phases/phase-29ao/README.md`) +- Next: TBD (P45 done; see `docs/development/current/main/phases/phase-29ao/README.md`) ## Gate (SSOT) diff --git a/docs/development/current/main/30-Backlog.md b/docs/development/current/main/30-Backlog.md index 014cff7a..51831a33 100644 --- a/docs/development/current/main/30-Backlog.md +++ b/docs/development/current/main/30-Backlog.md @@ -5,7 +5,7 @@ Scope: 「次にやる候補」を短く列挙するメモ。入口は `docs/dev ## Active -- CorePlan migration: `docs/development/current/main/phases/phase-29ao/README.md`(Next: TBD) +- CorePlan migration: `docs/development/current/main/phases/phase-29ao/README.md`(Next: TBD, P45 done) ## Near-Term Candidates diff --git a/docs/development/current/main/design/coreplan-migration-roadmap-ssot.md b/docs/development/current/main/design/coreplan-migration-roadmap-ssot.md index 20c4c6cb..de94d39f 100644 --- a/docs/development/current/main/design/coreplan-migration-roadmap-ssot.md +++ b/docs/development/current/main/design/coreplan-migration-roadmap-ssot.md @@ -34,7 +34,7 @@ Related: ## 1.1 Current (active) - Active phase: `docs/development/current/main/phases/phase-29ao/README.md` -- Next step: TBD +- Next step: TBD (P45 done) ## 2. すでに固めた SSOT(再発防止の土台) diff --git a/docs/development/current/main/phases/phase-29ao/README.md b/docs/development/current/main/phases/phase-29ao/README.md index c6fdf963..5e4c5472 100644 --- a/docs/development/current/main/phases/phase-29ao/README.md +++ b/docs/development/current/main/phases/phase-29ao/README.md @@ -246,6 +246,11 @@ Gate(SSOT): - 指示書: `docs/development/current/main/phases/phase-29ao/P44-CORELOOPCOMPOSER-V0-PATTERN1-MINIMAL-COMPOSITION-INSTRUCTIONS.md` - ねらい: Pattern1 skeleton の最小合成を v0 で開始し、Facts→CorePlan の責務を composer 側へ寄せる(仕様不変) +## P45: CoreLoopComposer v0 — Pattern6 (ScanWithInit) minimal composition ✅ + +- 指示書: `docs/development/current/main/phases/phase-29ao/P45-CORELOOPCOMPOSER-V0-PATTERN6-SCANWITHINIT-INSTRUCTIONS.md` +- ねらい: Pattern6 planner subset の最小合成を v0 で開始し、composer に合成のSSOTを寄せる(仕様不変) + ## Next(planned) - Next: TBD diff --git a/src/mir/builder/control_flow/plan/composer/coreloop_v0.rs b/src/mir/builder/control_flow/plan/composer/coreloop_v0.rs index 361761d2..e0e53c57 100644 --- a/src/mir/builder/control_flow/plan/composer/coreloop_v0.rs +++ b/src/mir/builder/control_flow/plan/composer/coreloop_v0.rs @@ -1,14 +1,21 @@ //! Phase 29ao P43: CoreLoopComposer v0 scaffold (unconnected). +use crate::ast::{ASTNode, Span}; use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; +use crate::mir::builder::control_flow::plan::facts::feature_facts::ExitKindFacts; +use crate::mir::builder::control_flow::plan::facts::scan_shapes::{ + ConditionShape, StepShape, +}; use crate::mir::builder::control_flow::plan::facts::skeleton_facts::SkeletonKind; use crate::mir::builder::control_flow::plan::normalize::CanonicalLoopFacts; -use crate::mir::builder::control_flow::plan::normalizer::build_pattern1_coreloop; -use crate::mir::builder::control_flow::plan::CorePlan; +use crate::mir::builder::control_flow::plan::normalizer::{ + build_pattern1_coreloop, PlanNormalizer, +}; +use crate::mir::builder::control_flow::plan::{CorePlan, ScanDirection, ScanWithInitPlan}; use crate::mir::builder::MirBuilder; #[allow(dead_code)] -pub(in crate::mir::builder) fn try_compose_core_loop_v0( +pub(in crate::mir::builder) fn try_compose_core_loop_v0_scan_with_init( builder: &mut MirBuilder, facts: &CanonicalLoopFacts, ctx: &LoopPatternContext, @@ -19,10 +26,80 @@ pub(in crate::mir::builder) fn try_compose_core_loop_v0( if facts.value_join_needed { return Ok(None); } + if !facts.cleanup_kinds_present.is_empty() { + return Ok(None); + } - let Some(pattern1) = facts.facts.pattern1_simplewhile.as_ref() else { + let Some(scan) = facts.facts.scan_with_init.as_ref() else { return Ok(None); }; + + if !facts.exit_kinds_present.is_empty() + && !(facts.exit_kinds_present.len() == 1 + && facts.exit_kinds_present.contains(&ExitKindFacts::Return)) + { + return Ok(None); + } + + let scan_direction = match scan.step_lit { + 1 => ScanDirection::Forward, + -1 => ScanDirection::Reverse, + _ => return Ok(None), + }; + + let shapes_match = match ( + &facts.facts.condition_shape, + &facts.facts.step_shape, + ) { + ( + ConditionShape::VarLessLength { + idx_var, + haystack_var, + .. + }, + StepShape::AssignAddConst { var, k }, + ) => idx_var == &scan.loop_var + && haystack_var == &scan.haystack + && var == &scan.loop_var + && *k == scan.step_lit, + _ => false, + }; + if !shapes_match { + return Ok(None); + } + + let plan = 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: Span::unknown(), + }, + not_found_return_lit: -1, + scan_direction, + dynamic_needle: false, + }; + let core = PlanNormalizer::normalize_scan_with_init(builder, plan, ctx)?; + Ok(Some(core)) +} + +#[allow(dead_code)] +pub(in crate::mir::builder) fn try_compose_core_loop_v0( + builder: &mut MirBuilder, + facts: &CanonicalLoopFacts, + ctx: &LoopPatternContext, +) -> Result, String> { + if let Some(core) = try_compose_core_loop_v0_scan_with_init(builder, facts, ctx)? { + return Ok(Some(core)); + } + if !matches!(facts.skeleton_kind, SkeletonKind::Loop) { + return Ok(None); + } + if facts.value_join_needed { + return Ok(None); + } if !facts.exit_kinds_present.is_empty() { return Ok(None); } @@ -30,6 +107,10 @@ pub(in crate::mir::builder) fn try_compose_core_loop_v0( return Ok(None); } + let Some(pattern1) = facts.facts.pattern1_simplewhile.as_ref() else { + return Ok(None); + }; + let loop_plan = build_pattern1_coreloop( builder, &pattern1.loop_var, @@ -48,9 +129,10 @@ mod tests { ExitKindFacts, ExitMapFacts, ExitUsageFacts, LoopFeatureFacts, ValueJoinFacts, }; use crate::mir::builder::control_flow::plan::facts::loop_facts::LoopFacts; + use crate::mir::builder::control_flow::plan::facts::loop_facts::ScanWithInitFacts; use crate::mir::builder::control_flow::plan::facts::pattern1_simplewhile_facts::Pattern1SimpleWhileFacts; use crate::mir::builder::control_flow::plan::facts::scan_shapes::{ - ConditionShape, StepShape, + ConditionShape, LengthMethod, StepShape, }; use crate::mir::builder::control_flow::plan::facts::skeleton_facts::{ SkeletonFacts, SkeletonKind, @@ -246,4 +328,133 @@ mod tests { try_compose_core_loop_v0(&mut builder, &canonical, &ctx).expect("Ok"); assert!(composed.is_none()); } + + #[test] + fn coreloop_v0_composes_scan_with_init_subset() { + let condition = ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left: Box::new(v("i")), + right: Box::new(ASTNode::MethodCall { + object: Box::new(v("s")), + method: "length".to_string(), + arguments: vec![], + span: Span::unknown(), + }), + span: Span::unknown(), + }; + let mut kinds_present = BTreeSet::new(); + kinds_present.insert(ExitKindFacts::Return); + let features = LoopFeatureFacts { + exit_usage: ExitUsageFacts { + has_break: false, + has_continue: false, + has_return: true, + }, + exit_map: Some(ExitMapFacts { kinds_present }), + value_join: None, + cleanup: None, + }; + let facts = LoopFacts { + condition_shape: ConditionShape::VarLessLength { + idx_var: "i".to_string(), + haystack_var: "s".to_string(), + method: LengthMethod::Length, + }, + step_shape: StepShape::AssignAddConst { + var: "i".to_string(), + k: 1, + }, + skeleton: SkeletonFacts { + kind: SkeletonKind::Loop, + }, + features, + 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 mut builder = MirBuilder::new(); + builder.enter_function_for_test("coreloop_v0_scan_with_init".to_string()); + let i_init = builder.alloc_typed(MirType::Integer); + let s_val = builder.alloc_typed(MirType::String); + let ch_val = builder.alloc_typed(MirType::String); + builder + .variable_ctx + .variable_map + .insert("i".to_string(), i_init); + builder + .variable_ctx + .variable_map + .insert("s".to_string(), s_val); + builder + .variable_ctx + .variable_map + .insert("ch".to_string(), ch_val); + let ctx = LoopPatternContext::new(&condition, &[], "coreloop_v0_scan", false, false); + let composed = + try_compose_core_loop_v0(&mut builder, &canonical, &ctx).expect("Ok"); + assert!(matches!(composed, Some(CorePlan::Loop(_)))); + } + + #[test] + fn coreloop_v0_rejects_scan_with_init_when_shapes_mismatch() { + let condition = ASTNode::Literal { + value: LiteralValue::Bool(true), + span: Span::unknown(), + }; + let mut kinds_present = BTreeSet::new(); + kinds_present.insert(ExitKindFacts::Return); + let features = LoopFeatureFacts { + exit_usage: ExitUsageFacts { + has_break: false, + has_continue: false, + has_return: true, + }, + exit_map: Some(ExitMapFacts { kinds_present }), + value_join: None, + cleanup: None, + }; + let facts = LoopFacts { + condition_shape: ConditionShape::Unknown, + step_shape: StepShape::Unknown, + skeleton: SkeletonFacts { + kind: SkeletonKind::Loop, + }, + features, + 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 mut builder = MirBuilder::new(); + builder.enter_function_for_test("coreloop_v0_scan_mismatch".to_string()); + let ctx = LoopPatternContext::new(&condition, &[], "coreloop_v0_scan", false, false); + let composed = + try_compose_core_loop_v0(&mut builder, &canonical, &ctx).expect("Ok"); + assert!(composed.is_none()); + } } diff --git a/src/mir/builder/control_flow/plan/composer/shadow_adopt.rs b/src/mir/builder/control_flow/plan/composer/shadow_adopt.rs index e1d6b3ba..94c1c453 100644 --- a/src/mir/builder/control_flow/plan/composer/shadow_adopt.rs +++ b/src/mir/builder/control_flow/plan/composer/shadow_adopt.rs @@ -1,14 +1,14 @@ //! Phase 29ao P30: shadow adopt composer entrypoints (Facts -> CorePlan). +use super::coreloop_v0::try_compose_core_loop_v0_scan_with_init; use super::PlanNormalizer; -use crate::ast::{ASTNode, Span}; use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; use crate::mir::builder::control_flow::plan::normalize::CanonicalLoopFacts; use crate::mir::builder::control_flow::plan::planner::PlanBuildOutcome; use crate::mir::builder::control_flow::plan::policies::pattern1_subset_policy::is_pattern1_step_only_body; use crate::mir::builder::control_flow::plan::{ CorePlan, DomainPlan, Pattern2BreakPlan, Pattern2PromotionHint, - Pattern3IfPhiPlan, Pattern5InfiniteEarlyExitPlan, ScanDirection, ScanWithInitPlan, + Pattern3IfPhiPlan, Pattern5InfiniteEarlyExitPlan, SplitScanPlan, }; use crate::mir::builder::MirBuilder; @@ -259,24 +259,7 @@ pub(in crate::mir::builder) fn compose_coreplan_for_pattern6_scan_with_init( facts: &CanonicalLoopFacts, ctx: &LoopPatternContext, ) -> Result, String> { - let Some(scan) = facts.facts.scan_with_init.as_ref() else { - return Ok(None); - }; - let 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: Span::unknown(), - }, - not_found_return_lit: -1, - scan_direction: ScanDirection::Forward, - dynamic_needle: false, - }); - let core = PlanNormalizer::normalize(builder, plan, ctx)?; - Ok(Some(core)) + try_compose_core_loop_v0_scan_with_init(builder, facts, ctx) } pub(in crate::mir::builder) fn compose_coreplan_for_pattern7_split_scan( diff --git a/src/mir/builder/control_flow/plan/normalizer/pattern_scan_with_init.rs b/src/mir/builder/control_flow/plan/normalizer/pattern_scan_with_init.rs index c33d4dca..6ad9b2ca 100644 --- a/src/mir/builder/control_flow/plan/normalizer/pattern_scan_with_init.rs +++ b/src/mir/builder/control_flow/plan/normalizer/pattern_scan_with_init.rs @@ -15,7 +15,7 @@ impl super::PlanNormalizer { /// - header_effects: one=1, needle_len, len, bound, cond_loop /// - body: i+needle_len, substring, cond_match /// - step_effects: i_next = i + 1 - pub(super) fn normalize_scan_with_init( + pub(in crate::mir::builder) fn normalize_scan_with_init( builder: &mut MirBuilder, parts: ScanWithInitPlan, ctx: &LoopPatternContext,