From 6e733cf25c788d5bf58b851bdb1ac47a068866ae Mon Sep 17 00:00:00 2001 From: tomoaki Date: Tue, 30 Dec 2025 20:45:19 +0900 Subject: [PATCH] phase29ao(p49): compose pattern5 early-exit via v1 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 | 4 + .../control_flow/plan/composer/coreloop_v1.rs | 166 +++++++++++++++++- .../plan/composer/shadow_adopt.rs | 20 ++- .../pattern5_infinite_early_exit.rs | 2 +- 8 files changed, 192 insertions(+), 11 deletions(-) diff --git a/docs/development/current/main/10-Now.md b/docs/development/current/main/10-Now.md index 05998f20..75f058b9 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 (P48 done; see `docs/development/current/main/phases/phase-29ao/README.md`) +- Next: TBD (P49 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 7cd5c086..38fd8afa 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, P48 done) +- CorePlan migration: `docs/development/current/main/phases/phase-29ao/README.md`(Next: TBD, P49 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 8fd57ee8..9ed85369 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 (P48 done) +- Next step: TBD (P49 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 49e0ea14..a05df1ae 100644 --- a/docs/development/current/main/phases/phase-29ao/README.md +++ b/docs/development/current/main/phases/phase-29ao/README.md @@ -266,6 +266,11 @@ Gate(SSOT): - 指示書: `docs/development/current/main/phases/phase-29ao/P48-CORELOOPCOMPOSER-V1-PATTERN2-BREAK-VALUEJOIN-INSTRUCTIONS.md` - ねらい: Pattern2 after-join を v1 で受理し、block_params/EdgeArgs 経由の PHI 一本化を維持(仕様不変) +## P49: CoreLoopComposer v1 — Pattern5 (Infinite Early-Exit) value-join minimal composition ✅ + +- 指示書: `docs/development/current/main/phases/phase-29ao/P49-CORELOOPCOMPOSER-V1-PATTERN5-INFINITE-EARLY-EXIT-VALUEJOIN-INSTRUCTIONS.md` +- ねらい: Pattern5 after-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 index fe9c69dd..77d139ed 100644 --- a/src/mir/builder/control_flow/plan/composer/coreloop_gates.rs +++ b/src/mir/builder/control_flow/plan/composer/coreloop_gates.rs @@ -24,3 +24,7 @@ pub(super) fn exit_kinds_empty(facts: &CanonicalLoopFacts) -> bool { pub(super) fn pattern2_value_join_gate(facts: &CanonicalLoopFacts) -> bool { coreloop_value_join_gate(facts) && exit_kinds_allow_return_only(facts) } + +pub(super) fn pattern5_value_join_gate(facts: &CanonicalLoopFacts) -> bool { + coreloop_value_join_gate(facts) && exit_kinds_allow_return_only(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 index e7fee178..3801a60f 100644 --- a/src/mir/builder/control_flow/plan/composer/coreloop_v1.rs +++ b/src/mir/builder/control_flow/plan/composer/coreloop_v1.rs @@ -1,12 +1,14 @@ use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; use crate::mir::builder::control_flow::plan::composer::coreloop_gates::{ coreloop_value_join_gate, exit_kinds_allow_return_only, pattern2_value_join_gate, + pattern5_value_join_gate, }; 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, Pattern2BreakPlan, Pattern2PromotionHint, SplitScanPlan, + CorePlan, Pattern2BreakPlan, Pattern2PromotionHint, Pattern5InfiniteEarlyExitPlan, + SplitScanPlan, }; use crate::mir::builder::MirBuilder; @@ -18,6 +20,11 @@ pub(in crate::mir::builder) fn try_compose_core_loop_v1( if let Some(core) = try_compose_core_loop_v1_pattern2_break(builder, facts, ctx)? { return Ok(Some(core)); } + if let Some(core) = try_compose_core_loop_v1_pattern5_infinite_early_exit( + builder, facts, ctx, + )? { + return Ok(Some(core)); + } try_compose_core_loop_v1_split_scan(builder, facts, ctx) } @@ -84,6 +91,32 @@ pub(in crate::mir::builder) fn try_compose_core_loop_v1_pattern2_break( Ok(Some(core)) } +pub(in crate::mir::builder) fn try_compose_core_loop_v1_pattern5_infinite_early_exit( + builder: &mut MirBuilder, + facts: &CanonicalLoopFacts, + ctx: &LoopPatternContext, +) -> Result, String> { + if !pattern5_value_join_gate(facts) { + return Ok(None); + } + + let Some(pattern5) = facts.facts.pattern5_infinite_early_exit.as_ref() else { + return Ok(None); + }; + + let plan = 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(), + }; + let core = PlanNormalizer::normalize_pattern5_infinite_early_exit(builder, plan, ctx)?; + Ok(Some(core)) +} + #[cfg(test)] mod tests { use super::try_compose_core_loop_v1; @@ -93,6 +126,7 @@ mod tests { ValueJoinFacts, }; use crate::mir::builder::control_flow::plan::facts::loop_facts::LoopFacts; + use crate::mir::builder::control_flow::plan::facts::pattern5_infinite_early_exit_facts::Pattern5InfiniteEarlyExitFacts; use crate::mir::builder::control_flow::plan::facts::pattern2_break_facts::Pattern2BreakFacts; use crate::mir::builder::control_flow::plan::facts::scan_shapes::{ ConditionShape, SplitScanShape, StepShape, @@ -100,6 +134,7 @@ mod tests { use crate::mir::builder::control_flow::plan::facts::skeleton_facts::{ SkeletonFacts, SkeletonKind, }; + use crate::mir::builder::control_flow::plan::Pattern5ExitKind; 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; @@ -373,4 +408,133 @@ mod tests { try_compose_core_loop_v1(&mut builder, &canonical, &ctx).expect("Ok"); assert!(composed.is_none()); } + + #[test] + fn coreloop_v1_composes_pattern5_with_value_join() { + let condition = ASTNode::Literal { + value: LiteralValue::Bool(true), + span: Span::unknown(), + }; + let exit_condition = ASTNode::BinaryOp { + operator: BinaryOperator::Equal, + left: Box::new(v("i")), + right: Box::new(lit_int(2)), + span: Span::unknown(), + }; + let carrier_update = ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(v("sum")), + right: Box::new(lit_int(1)), + span: Span::unknown(), + }; + let loop_increment = ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(v("i")), + right: Box::new(lit_int(1)), + 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: None, + pattern1_simplewhile: None, + pattern3_ifphi: None, + pattern4_continue: None, + pattern5_infinite_early_exit: Some(Pattern5InfiniteEarlyExitFacts { + loop_var: "i".to_string(), + exit_kind: Pattern5ExitKind::Break, + exit_condition, + exit_value: None, + carrier_var: Some("sum".to_string()), + carrier_update: Some(carrier_update), + loop_increment, + }), + 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_pattern5".to_string()); + let i_val = builder.alloc_typed(MirType::Integer); + let sum_val = builder.alloc_typed(MirType::Integer); + builder + .variable_ctx + .variable_map + .insert("i".to_string(), i_val); + builder + .variable_ctx + .variable_map + .insert("sum".to_string(), sum_val); + let ctx = + LoopPatternContext::new(&condition, &[], "coreloop_v1_pattern5", 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_pattern5_with_cleanup() { + let condition = ASTNode::Literal { + value: LiteralValue::Bool(true), + span: Span::unknown(), + }; + let mut kinds_present = BTreeSet::new(); + kinds_present.insert(CleanupKindFacts::Return); + let features = LoopFeatureFacts { + cleanup: Some(CleanupFacts { 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: None, + pattern1_simplewhile: None, + pattern3_ifphi: None, + pattern4_continue: None, + pattern5_infinite_early_exit: Some(Pattern5InfiniteEarlyExitFacts { + loop_var: "i".to_string(), + exit_kind: Pattern5ExitKind::Break, + exit_condition: condition.clone(), + exit_value: None, + carrier_var: Some("sum".to_string()), + carrier_update: Some(lit_int(0)), + loop_increment: lit_int(0), + }), + 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_pattern5_cleanup".to_string()); + let ctx = LoopPatternContext::new( + &condition, + &[], + "coreloop_v1_pattern5_cleanup", + 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/shadow_adopt.rs b/src/mir/builder/control_flow/plan/composer/shadow_adopt.rs index 88979243..49d1dc78 100644 --- a/src/mir/builder/control_flow/plan/composer/shadow_adopt.rs +++ b/src/mir/builder/control_flow/plan/composer/shadow_adopt.rs @@ -4,7 +4,9 @@ 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::coreloop_v1::try_compose_core_loop_v1_pattern2_break; +use super::coreloop_v1::{ + try_compose_core_loop_v1_pattern2_break, try_compose_core_loop_v1_pattern5_infinite_early_exit, +}; use super::PlanNormalizer; use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext; use crate::mir::builder::control_flow::plan::normalize::CanonicalLoopFacts; @@ -192,7 +194,12 @@ pub(in crate::mir::builder) fn try_release_adopt_core_plan_for_pattern5_infinite return Ok(None); } - match compose_coreplan_for_pattern5_infinite_early_exit(builder, facts, ctx) { + let composed = if facts.value_join_needed { + try_compose_core_loop_v1_pattern5_infinite_early_exit(builder, facts, ctx) + } else { + compose_coreplan_for_pattern5_infinite_early_exit(builder, facts, ctx) + }; + match composed { Ok(Some(core)) => Ok(Some(core)), Ok(None) | Err(_) => Ok(None), } @@ -395,11 +402,12 @@ pub(in crate::mir::builder) fn try_shadow_adopt_core_plan( if facts.facts.pattern5_infinite_early_exit.is_none() { return Err("pattern5 strict/dev adopt failed: facts mismatch".to_string()); } - let core_plan = + let core_plan = if facts.value_join_needed { + try_compose_core_loop_v1_pattern5_infinite_early_exit(builder, facts, ctx)? + } else { compose_coreplan_for_pattern5_infinite_early_exit(builder, facts, ctx)? - .ok_or_else(|| { - "pattern5 strict/dev adopt failed: compose rejected".to_string() - })?; + } + .ok_or_else(|| "pattern5 strict/dev adopt failed: compose rejected".to_string())?; Ok(Some(ShadowAdoptOutcome { core_plan, tag: "[coreplan/shadow_adopt:pattern5_infinite_early_exit]", diff --git a/src/mir/builder/control_flow/plan/normalizer/pattern5_infinite_early_exit.rs b/src/mir/builder/control_flow/plan/normalizer/pattern5_infinite_early_exit.rs index f8ce4384..d7fde896 100644 --- a/src/mir/builder/control_flow/plan/normalizer/pattern5_infinite_early_exit.rs +++ b/src/mir/builder/control_flow/plan/normalizer/pattern5_infinite_early_exit.rs @@ -35,7 +35,7 @@ impl super::PlanNormalizer { /// ↓ /// then path → after_bb(join: carrier_out) /// ``` - pub(super) fn normalize_pattern5_infinite_early_exit( + pub(in crate::mir::builder) fn normalize_pattern5_infinite_early_exit( builder: &mut MirBuilder, parts: Pattern5InfiniteEarlyExitPlan, ctx: &LoopPatternContext,