phase29ao(p45): coreloop v0 composes pattern6 subset

This commit is contained in:
2025-12-30 19:42:13 +09:00
parent 225a205332
commit b126346e7f
7 changed files with 228 additions and 29 deletions

View File

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

View File

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

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
- Next step: TBD (P45 done)
## 2. すでに固めた SSOT再発防止の土台

View File

@ -246,6 +246,11 @@ GateSSOT:
- 指示書: `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を寄せる仕様不変
## Nextplanned
- Next: TBD

View File

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

View File

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

View File

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