phase29ao(p49): compose pattern5 early-exit via v1 value-join

This commit is contained in:
2025-12-30 20:45:19 +09:00
parent e5045c40ec
commit 6e733cf25c
8 changed files with 192 additions and 11 deletions

View File

@ -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)

View File

@ -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

View File

@ -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再発防止の土台

View File

@ -266,6 +266,11 @@ GateSSOT:
- 指示書: `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 一本化を維持(仕様不変)
## Nextplanned
- Next: TBD

View File

@ -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)
}

View File

@ -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<Option<CorePlan>, 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());
}
}

View File

@ -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]",

View File

@ -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,