From 5736c825f8041cb9259f6c1e53e0e6d71c14f31c Mon Sep 17 00:00:00 2001 From: tomoaki Date: Tue, 30 Dec 2025 20:20:40 +0900 Subject: [PATCH] phase29ao(p47): coreloop v1 split-scan value-join --- docs/development/current/main/10-Now.md | 2 +- docs/development/current/main/30-Backlog.md | 2 +- .../design/coreplan-migration-roadmap-ssot.md | 2 +- .../current/main/phases/phase-29ao/README.md | 5 + .../plan/composer/coreloop_gates.rs | 18 ++ .../control_flow/plan/composer/coreloop_v0.rs | 36 ++-- .../control_flow/plan/composer/coreloop_v1.rs | 188 ++++++++++++++++++ .../builder/control_flow/plan/composer/mod.rs | 2 + .../plan/composer/shadow_adopt.rs | 14 +- 9 files changed, 243 insertions(+), 26 deletions(-) create mode 100644 src/mir/builder/control_flow/plan/composer/coreloop_gates.rs create mode 100644 src/mir/builder/control_flow/plan/composer/coreloop_v1.rs diff --git a/docs/development/current/main/10-Now.md b/docs/development/current/main/10-Now.md index ddd45bfc..178986e0 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 (P46 done; see `docs/development/current/main/phases/phase-29ao/README.md`) +- Next: TBD (P47 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 87ee7bc0..c5b39a8b 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, P46 done) +- CorePlan migration: `docs/development/current/main/phases/phase-29ao/README.md`(Next: TBD, P47 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 a4abfa33..c0f2598c 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 (P46 done) +- Next step: TBD (P47 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 33ec96d5..19ee8bb9 100644 --- a/docs/development/current/main/phases/phase-29ao/README.md +++ b/docs/development/current/main/phases/phase-29ao/README.md @@ -256,6 +256,11 @@ Gate(SSOT): - 指示書: `docs/development/current/main/phases/phase-29ao/P46-CORELOOPCOMPOSER-V0-PATTERN7-SPLITSCAN-INSTRUCTIONS.md` - ねらい: Pattern7 planner subset の最小合成を v0 で開始し、composer に合成のSSOTを寄せる(仕様不変) +## P47: CoreLoopComposer v1 — Pattern7 value-join minimal composition ✅ + +- 指示書: `docs/development/current/main/phases/phase-29ao/P47-CORELOOPCOMPOSER-V1-SPLITSCAN-VALUEJOIN-INSTRUCTIONS.md` +- ねらい: Pattern7 value-join を v1 で受理し、block_params/EdgeArgs 経由の PHI 一本化を維持(仕様不変) + ## Next(planned) - Next: TBD diff --git a/src/mir/builder/control_flow/plan/composer/coreloop_gates.rs b/src/mir/builder/control_flow/plan/composer/coreloop_gates.rs new file mode 100644 index 00000000..726d958b --- /dev/null +++ b/src/mir/builder/control_flow/plan/composer/coreloop_gates.rs @@ -0,0 +1,18 @@ +use crate::mir::builder::control_flow::plan::facts::feature_facts::ExitKindFacts; +use crate::mir::builder::control_flow::plan::facts::skeleton_facts::SkeletonKind; +use crate::mir::builder::control_flow::plan::normalize::CanonicalLoopFacts; + +pub(super) fn coreloop_base_gate(facts: &CanonicalLoopFacts) -> bool { + matches!(facts.skeleton_kind, SkeletonKind::Loop) + && facts.cleanup_kinds_present.is_empty() +} + +pub(super) fn exit_kinds_allow_return_only(facts: &CanonicalLoopFacts) -> bool { + facts.exit_kinds_present.is_empty() + || (facts.exit_kinds_present.len() == 1 + && facts.exit_kinds_present.contains(&ExitKindFacts::Return)) +} + +pub(super) fn exit_kinds_empty(facts: &CanonicalLoopFacts) -> bool { + facts.exit_kinds_present.is_empty() +} 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 8b0369f7..14a225c4 100644 --- a/src/mir/builder/control_flow/plan/composer/coreloop_v0.rs +++ b/src/mir/builder/control_flow/plan/composer/coreloop_v0.rs @@ -2,11 +2,12 @@ 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, SplitScanShape, StepShape, }; -use crate::mir::builder::control_flow::plan::facts::skeleton_facts::SkeletonKind; +use crate::mir::builder::control_flow::plan::composer::coreloop_gates::{ + coreloop_base_gate, exit_kinds_allow_return_only, exit_kinds_empty, +}; use crate::mir::builder::control_flow::plan::normalize::CanonicalLoopFacts; use crate::mir::builder::control_flow::plan::normalizer::{ build_pattern1_coreloop, PlanNormalizer, @@ -16,29 +17,16 @@ use crate::mir::builder::control_flow::plan::{ }; use crate::mir::builder::MirBuilder; -fn coreloop_v0_base_gate(facts: &CanonicalLoopFacts) -> bool { - matches!(facts.skeleton_kind, SkeletonKind::Loop) - && !facts.value_join_needed - && facts.cleanup_kinds_present.is_empty() -} - -fn exit_kinds_allow_return_only(facts: &CanonicalLoopFacts) -> bool { - facts.exit_kinds_present.is_empty() - || (facts.exit_kinds_present.len() == 1 - && facts.exit_kinds_present.contains(&ExitKindFacts::Return)) -} - -fn exit_kinds_empty(facts: &CanonicalLoopFacts) -> bool { - facts.exit_kinds_present.is_empty() -} - #[allow(dead_code)] pub(in crate::mir::builder) fn try_compose_core_loop_v0_scan_with_init( builder: &mut MirBuilder, facts: &CanonicalLoopFacts, ctx: &LoopPatternContext, ) -> Result, String> { - if !coreloop_v0_base_gate(facts) { + if !coreloop_base_gate(facts) { + return Ok(None); + } + if facts.value_join_needed { return Ok(None); } @@ -100,7 +88,10 @@ pub(in crate::mir::builder) fn try_compose_core_loop_v0_split_scan( facts: &CanonicalLoopFacts, ctx: &LoopPatternContext, ) -> Result, String> { - if !coreloop_v0_base_gate(facts) { + if !coreloop_base_gate(facts) { + return Ok(None); + } + if facts.value_join_needed { return Ok(None); } @@ -139,7 +130,10 @@ pub(in crate::mir::builder) fn try_compose_core_loop_v0( if let Some(core) = try_compose_core_loop_v0_split_scan(builder, facts, ctx)? { return Ok(Some(core)); } - if !coreloop_v0_base_gate(facts) { + if !coreloop_base_gate(facts) { + return Ok(None); + } + if facts.value_join_needed { return Ok(None); } if !exit_kinds_empty(facts) { diff --git a/src/mir/builder/control_flow/plan/composer/coreloop_v1.rs b/src/mir/builder/control_flow/plan/composer/coreloop_v1.rs new file mode 100644 index 00000000..154b1380 --- /dev/null +++ b/src/mir/builder/control_flow/plan/composer/coreloop_v1.rs @@ -0,0 +1,188 @@ +use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; +use crate::mir::builder::control_flow::plan::composer::coreloop_gates::{ + coreloop_base_gate, exit_kinds_allow_return_only, +}; +use crate::mir::builder::control_flow::plan::facts::scan_shapes::SplitScanShape; +use crate::mir::builder::control_flow::plan::normalize::CanonicalLoopFacts; +use crate::mir::builder::control_flow::plan::normalizer::PlanNormalizer; +use crate::mir::builder::control_flow::plan::{CorePlan, SplitScanPlan}; +use crate::mir::builder::MirBuilder; + +#[allow(dead_code)] +pub(in crate::mir::builder) fn try_compose_core_loop_v1( + builder: &mut MirBuilder, + facts: &CanonicalLoopFacts, + ctx: &LoopPatternContext, +) -> Result, String> { + if !coreloop_base_gate(facts) { + return Ok(None); + } + if !facts.value_join_needed { + return Ok(None); + } + + let Some(split_scan) = facts.facts.split_scan.as_ref() else { + return Ok(None); + }; + + if !exit_kinds_allow_return_only(facts) { + return Ok(None); + } + if !matches!(split_scan.shape, SplitScanShape::Minimal) { + return Ok(None); + } + + let plan = 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(), + }; + let core = PlanNormalizer::normalize_split_scan(builder, plan, ctx)?; + Ok(Some(core)) +} + +#[cfg(test)] +mod tests { + use super::try_compose_core_loop_v1; + use crate::ast::{ASTNode, LiteralValue, Span}; + use crate::mir::builder::control_flow::plan::facts::feature_facts::{ + ExitKindFacts, ExitMapFacts, LoopFeatureFacts, ValueJoinFacts, + }; + use crate::mir::builder::control_flow::plan::facts::loop_facts::LoopFacts; + use crate::mir::builder::control_flow::plan::facts::scan_shapes::{ + ConditionShape, SplitScanShape, StepShape, + }; + 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::joinir::patterns::router::LoopPatternContext; + use crate::mir::builder::MirBuilder; + use crate::mir::MirType; + use std::collections::BTreeSet; + + #[test] + fn coreloop_v1_composes_split_scan_with_value_join() { + let condition = ASTNode::Literal { + value: LiteralValue::Bool(true), + span: Span::unknown(), + }; + let features = LoopFeatureFacts { + value_join: Some(ValueJoinFacts { needed: true }), + ..LoopFeatureFacts::default() + }; + let facts = LoopFacts { + condition_shape: ConditionShape::Unknown, + step_shape: StepShape::Unknown, + skeleton: SkeletonFacts { + kind: SkeletonKind::Loop, + }, + features, + scan_with_init: None, + split_scan: Some(crate::mir::builder::control_flow::plan::facts::loop_facts::SplitScanFacts { + s_var: "s".to_string(), + sep_var: "sep".to_string(), + result_var: "result".to_string(), + i_var: "i".to_string(), + start_var: "start".to_string(), + shape: SplitScanShape::Minimal, + }), + 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_v1_split_scan".to_string()); + let s_val = builder.alloc_typed(MirType::String); + let sep_val = builder.alloc_typed(MirType::String); + let result_val = builder.alloc_typed(MirType::Array(Box::new(MirType::String))); + let i_val = builder.alloc_typed(MirType::Integer); + let start_val = builder.alloc_typed(MirType::Integer); + builder + .variable_ctx + .variable_map + .insert("s".to_string(), s_val); + builder + .variable_ctx + .variable_map + .insert("sep".to_string(), sep_val); + builder + .variable_ctx + .variable_map + .insert("result".to_string(), result_val); + builder + .variable_ctx + .variable_map + .insert("i".to_string(), i_val); + builder + .variable_ctx + .variable_map + .insert("start".to_string(), start_val); + let ctx = + LoopPatternContext::new(&condition, &[], "coreloop_v1_split_scan", false, false); + let composed = + try_compose_core_loop_v1(&mut builder, &canonical, &ctx).expect("Ok"); + assert!(matches!(composed, Some(crate::mir::builder::control_flow::plan::CorePlan::Loop(_)))); + } + + #[test] + fn coreloop_v1_rejects_split_scan_with_disallowed_exitmap() { + let condition = ASTNode::Literal { + value: LiteralValue::Bool(true), + span: Span::unknown(), + }; + let mut kinds_present = BTreeSet::new(); + kinds_present.insert(ExitKindFacts::Break); + let features = LoopFeatureFacts { + exit_map: Some(ExitMapFacts { kinds_present }), + value_join: Some(ValueJoinFacts { needed: true }), + ..LoopFeatureFacts::default() + }; + let facts = LoopFacts { + condition_shape: ConditionShape::Unknown, + step_shape: StepShape::Unknown, + skeleton: SkeletonFacts { + kind: SkeletonKind::Loop, + }, + features, + scan_with_init: None, + split_scan: Some(crate::mir::builder::control_flow::plan::facts::loop_facts::SplitScanFacts { + s_var: "s".to_string(), + sep_var: "sep".to_string(), + result_var: "result".to_string(), + i_var: "i".to_string(), + start_var: "start".to_string(), + shape: SplitScanShape::Minimal, + }), + 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_v1_split_scan_exit".to_string()); + let ctx = LoopPatternContext::new( + &condition, + &[], + "coreloop_v1_split_scan_exit", + false, + false, + ); + let composed = + try_compose_core_loop_v1(&mut builder, &canonical, &ctx).expect("Ok"); + assert!(composed.is_none()); + } +} diff --git a/src/mir/builder/control_flow/plan/composer/mod.rs b/src/mir/builder/control_flow/plan/composer/mod.rs index 69a23872..e097ac32 100644 --- a/src/mir/builder/control_flow/plan/composer/mod.rs +++ b/src/mir/builder/control_flow/plan/composer/mod.rs @@ -1,6 +1,8 @@ //! Phase 29ao P0: CorePlan composer scaffold (CanonicalLoopFacts -> CorePlan) +pub(super) mod coreloop_gates; pub(super) mod coreloop_v0; +pub(super) mod coreloop_v1; mod shadow_adopt; use super::{normalizer::PlanNormalizer, CorePlan, DomainPlan, Pattern1SimpleWhilePlan}; 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 2055a1b7..211be4c3 100644 --- a/src/mir/builder/control_flow/plan/composer/shadow_adopt.rs +++ b/src/mir/builder/control_flow/plan/composer/shadow_adopt.rs @@ -3,6 +3,7 @@ use super::coreloop_v0::{ try_compose_core_loop_v0_scan_with_init, try_compose_core_loop_v0_split_scan, }; +use super::coreloop_v1::try_compose_core_loop_v1; use super::PlanNormalizer; use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; use crate::mir::builder::control_flow::plan::normalize::CanonicalLoopFacts; @@ -99,7 +100,12 @@ pub(in crate::mir::builder) fn try_release_adopt_core_plan_for_pattern7_split_sc return Ok(None); } - match try_compose_core_loop_v0_split_scan(builder, facts, ctx) { + let composed = if facts.value_join_needed { + try_compose_core_loop_v1(builder, facts, ctx) + } else { + try_compose_core_loop_v0_split_scan(builder, facts, ctx) + }; + match composed { Ok(Some(core)) => Ok(Some(core)), Ok(None) | Err(_) => Ok(None), } @@ -364,7 +370,11 @@ pub(in crate::mir::builder) fn try_shadow_adopt_core_plan( if facts.facts.split_scan.is_none() { return Err("pattern7 strict/dev adopt failed: facts mismatch".to_string()); } - let core_plan = try_compose_core_loop_v0_split_scan(builder, facts, ctx)? + let core_plan = if facts.value_join_needed { + try_compose_core_loop_v1(builder, facts, ctx)? + } else { + try_compose_core_loop_v0_split_scan(builder, facts, ctx)? + } .ok_or_else(|| "pattern7 strict/dev adopt failed: compose rejected".to_string())?; Ok(Some(ShadowAdoptOutcome { core_plan,