From 61bb386b85053fe8ea20bcba2b32d59c95b7e7cc Mon Sep 17 00:00:00 2001 From: tomoaki Date: Mon, 29 Dec 2025 11:00:55 +0900 Subject: [PATCH] phase29ai(p14): add pattern2 promotion hint --- docs/development/current/main/10-Now.md | 7 +- docs/development/current/main/30-Backlog.md | 2 +- .../current/main/phases/phase-29ai/README.md | 4 +- .../plan/extractors/pattern2_break.rs | 1 + src/mir/builder/control_flow/plan/mod.rs | 11 +++ .../control_flow/plan/planner/build.rs | 99 ++++++++++++++++++- 6 files changed, 120 insertions(+), 4 deletions(-) diff --git a/docs/development/current/main/10-Now.md b/docs/development/current/main/10-Now.md index cfe103fb..6f6ce3eb 100644 --- a/docs/development/current/main/10-Now.md +++ b/docs/development/current/main/10-Now.md @@ -2,7 +2,12 @@ ## Current Focus: Phase 29ai(Plan/Frag single-planner) -Next: `docs/development/current/main/phases/phase-29ai/P14-PLANNER-PATTERN2-LOOPBODYLOCAL-PROMOTION-SUBSET-INSTRUCTIONS.md` +Next: Phase 29ai P15(TBD: promotion hint を JoinIR 側の orchestrator へ配線、挙動不変) + +**2025-12-29: Phase 29ai P14 完了** ✅ +- 目的: Pattern2 LoopBodyLocal promotion の要求を Plan に載せる(仕様不変) +- 実装: `src/mir/builder/control_flow/plan/mod.rs` / `src/mir/builder/control_flow/plan/planner/build.rs` / `src/mir/builder/control_flow/plan/extractors/pattern2_break.rs` +- 検証: `cargo build --release` / `./tools/smokes/v2/run.sh --profile quick` / `./tools/smokes/v2/profiles/integration/joinir/phase29ae_regression_pack_vm.sh` PASS **2025-12-29: Phase 29ai P13 完了** ✅ - 目的: single_planner の planner 呼び出しを 1 回に memoize して二重スキャンを解消(仕様不変) diff --git a/docs/development/current/main/30-Backlog.md b/docs/development/current/main/30-Backlog.md index eb552e00..ba1bf88f 100644 --- a/docs/development/current/main/30-Backlog.md +++ b/docs/development/current/main/30-Backlog.md @@ -19,7 +19,7 @@ Related: - **Phase 29ai(candidate): Plan/Frag single-planner(Facts SSOT)** - 入口: `docs/development/current/main/phases/phase-29ai/README.md` - - Next: `docs/development/current/main/phases/phase-29ai/P14-PLANNER-PATTERN2-LOOPBODYLOCAL-PROMOTION-SUBSET-INSTRUCTIONS.md` + - Next: Phase 29ai P15(TBD: promotion hint を JoinIR 側の orchestrator へ配線、挙動不変) - **Phase 29ae P1(✅ COMPLETE): JoinIR Regression Pack (SSOT固定)** - 入口: `docs/development/current/main/phases/phase-29ae/README.md` diff --git a/docs/development/current/main/phases/phase-29ai/README.md b/docs/development/current/main/phases/phase-29ai/README.md index 0e91c821..a8e975bd 100644 --- a/docs/development/current/main/phases/phase-29ai/README.md +++ b/docs/development/current/main/phases/phase-29ai/README.md @@ -87,7 +87,9 @@ Goal: pattern 名による分岐を外部APIから消し、Facts(事実)→ ## P14: Planner support(Pattern2 LoopBodyLocal promotion subset) - 指示書: `docs/development/current/main/phases/phase-29ai/P14-PLANNER-PATTERN2-LOOPBODYLOCAL-PROMOTION-SUBSET-INSTRUCTIONS.md` -- ねらい: LoopBodyLocal promotion の要求を DomainPlan に載せ、promotion の “解析” を Facts として固定した上で段階的に plan 系へ寄せる(仕様不変) +- ねらい: LoopBodyLocal promotion の “要求” を Pattern2BreakPlan に付加し、挙動不変のまま Plan にメタ情報を載せる +- 完了: promotion hint を plan vocab に追加し、planner が facts から hint を付与(legacy は None) +- 検証: `cargo build --release` / `./tools/smokes/v2/run.sh --profile quick` / `./tools/smokes/v2/profiles/integration/joinir/phase29ae_regression_pack_vm.sh` ## Verification (SSOT) diff --git a/src/mir/builder/control_flow/plan/extractors/pattern2_break.rs b/src/mir/builder/control_flow/plan/extractors/pattern2_break.rs index 6668a26a..d2cc74f4 100644 --- a/src/mir/builder/control_flow/plan/extractors/pattern2_break.rs +++ b/src/mir/builder/control_flow/plan/extractors/pattern2_break.rs @@ -264,6 +264,7 @@ pub(crate) fn extract_pattern2_plan( carrier_update_in_break, carrier_update_in_body, loop_increment, + promotion: None, }))) } diff --git a/src/mir/builder/control_flow/plan/mod.rs b/src/mir/builder/control_flow/plan/mod.rs index 0e6e9c0a..2bf35be1 100644 --- a/src/mir/builder/control_flow/plan/mod.rs +++ b/src/mir/builder/control_flow/plan/mod.rs @@ -24,6 +24,7 @@ //! This prevents "second language processor" from growing inside Lowerer. use crate::ast::ASTNode; +use crate::mir::builder::control_flow::plan::facts::pattern2_loopbodylocal_facts::Pattern2LoopBodyLocalFacts; use crate::mir::{BasicBlockId, BinaryOp, CompareOp, ConstValue, EffectMask, ValueId}; use crate::mir::builder::control_flow::edgecfg::api::Frag; @@ -279,6 +280,16 @@ pub(in crate::mir::builder) struct Pattern2BreakPlan { pub carrier_update_in_body: ASTNode, /// Loop increment expression AST (e.g., `i + 1`) pub loop_increment: ASTNode, + /// Optional promotion hint for LoopBodyLocal handling (planner-only metadata) + #[allow(dead_code)] + pub promotion: Option, +} + +/// Phase 29ai P14: Promotion hint metadata for Pattern2BreakPlan +#[allow(dead_code)] +#[derive(Debug, Clone)] +pub(in crate::mir::builder) enum Pattern2PromotionHint { + LoopBodyLocal(Pattern2LoopBodyLocalFacts), } /// Phase 286 P3.2: Exit kind for Pattern5 infinite loop diff --git a/src/mir/builder/control_flow/plan/planner/build.rs b/src/mir/builder/control_flow/plan/planner/build.rs index 97747c32..0a1453f1 100644 --- a/src/mir/builder/control_flow/plan/planner/build.rs +++ b/src/mir/builder/control_flow/plan/planner/build.rs @@ -10,7 +10,8 @@ use crate::mir::builder::control_flow::plan::normalize::{canonicalize_loop_facts use super::candidates::{CandidateSet, PlanCandidate}; use super::Freeze; use crate::mir::builder::control_flow::plan::{ - DomainPlan, Pattern2BreakPlan, ScanDirection, ScanWithInitPlan, SplitScanPlan, + DomainPlan, Pattern2BreakPlan, Pattern2PromotionHint, ScanDirection, ScanWithInitPlan, + SplitScanPlan, }; /// Phase 29ai P0: External-ish SSOT entrypoint (skeleton) @@ -71,6 +72,11 @@ pub(in crate::mir::builder) fn build_plan_from_facts( } if let Some(pattern2) = &facts.facts.pattern2_break { + let promotion = facts + .facts + .pattern2_loopbodylocal + .as_ref() + .map(|facts| Pattern2PromotionHint::LoopBodyLocal(facts.clone())); candidates.push(PlanCandidate { plan: DomainPlan::Pattern2Break(Pattern2BreakPlan { loop_var: pattern2.loop_var.clone(), @@ -80,6 +86,7 @@ pub(in crate::mir::builder) fn build_plan_from_facts( carrier_update_in_break: pattern2.carrier_update_in_break.clone(), carrier_update_in_body: pattern2.carrier_update_in_body.clone(), loop_increment: pattern2.loop_increment.clone(), + promotion, }), rule: "loop/pattern2_break", }); @@ -97,7 +104,27 @@ mod tests { use crate::mir::builder::control_flow::plan::facts::loop_facts::{ LoopFacts, ScanWithInitFacts, SplitScanFacts, }; + use crate::mir::builder::control_flow::plan::facts::pattern2_break_facts::Pattern2BreakFacts; + use crate::mir::builder::control_flow::plan::facts::pattern2_loopbodylocal_facts::{ + LoopBodyLocalShape, Pattern2LoopBodyLocalFacts, + }; use crate::mir::builder::control_flow::plan::normalize::canonicalize_loop_facts; + use crate::mir::builder::control_flow::plan::Pattern2PromotionHint; + use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span}; + + fn v(name: &str) -> ASTNode { + ASTNode::Variable { + name: name.to_string(), + span: Span::unknown(), + } + } + + fn lit_int(value: i64) -> ASTNode { + ASTNode::Literal { + value: LiteralValue::Integer(value), + span: Span::unknown(), + } + } #[test] fn planner_builds_split_scan_plan_from_facts() { @@ -169,4 +196,74 @@ mod tests { other => panic!("expected scan_with_init plan, got {:?}", other), } } + + #[test] + fn planner_sets_promotion_hint_for_pattern2_loopbodylocal() { + let loop_condition = ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left: Box::new(v("i")), + right: Box::new(lit_int(3)), + span: Span::unknown(), + }; + let break_condition = ASTNode::BinaryOp { + operator: BinaryOperator::Equal, + left: Box::new(v("seg")), + right: Box::new(ASTNode::Literal { + value: LiteralValue::String(" ".to_string()), + span: Span::unknown(), + }), + 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 facts = LoopFacts { + condition_shape: ConditionShape::Unknown, + step_shape: StepShape::Unknown, + scan_with_init: None, + split_scan: None, + pattern2_break: Some(Pattern2BreakFacts { + loop_var: "i".to_string(), + carrier_var: "sum".to_string(), + loop_condition, + break_condition, + carrier_update_in_break: None, + carrier_update_in_body: carrier_update, + loop_increment, + }), + pattern2_loopbodylocal: Some(Pattern2LoopBodyLocalFacts { + loop_var: "i".to_string(), + loopbodylocal_var: "seg".to_string(), + break_uses_loopbodylocal: true, + shape: LoopBodyLocalShape::TrimSeg { + s_var: "s".to_string(), + i_var: "i".to_string(), + }, + }), + }; + let canonical = canonicalize_loop_facts(facts); + let plan = build_plan_from_facts(canonical).expect("Ok"); + + match plan { + Some(DomainPlan::Pattern2Break(plan)) => { + let promotion = plan.promotion.expect("promotion hint"); + match promotion { + Pattern2PromotionHint::LoopBodyLocal(facts) => { + assert_eq!(facts.loopbodylocal_var, "seg"); + } + } + } + other => panic!("expected pattern2 break plan, got {:?}", other), + } + } }