phase29ai(p14): add pattern2 promotion hint

This commit is contained in:
2025-12-29 11:00:55 +09:00
parent 2347bed84b
commit 61bb386b85
6 changed files with 120 additions and 4 deletions

View File

@ -2,7 +2,12 @@
## Current Focus: Phase 29aiPlan/Frag single-planner
Next: `docs/development/current/main/phases/phase-29ai/P14-PLANNER-PATTERN2-LOOPBODYLOCAL-PROMOTION-SUBSET-INSTRUCTIONS.md`
Next: Phase 29ai P15TBD: 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 して二重スキャンを解消(仕様不変)

View File

@ -19,7 +19,7 @@ Related:
- **Phase 29aicandidate: Plan/Frag single-plannerFacts 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 P15TBD: promotion hint を JoinIR 側の orchestrator へ配線、挙動不変)
- **Phase 29ae P1✅ COMPLETE: JoinIR Regression Pack (SSOT固定)**
- 入口: `docs/development/current/main/phases/phase-29ae/README.md`

View File

@ -87,7 +87,9 @@ Goal: pattern 名による分岐を外部APIから消し、Facts事実
## P14: Planner supportPattern2 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)

View File

@ -264,6 +264,7 @@ pub(crate) fn extract_pattern2_plan(
carrier_update_in_break,
carrier_update_in_body,
loop_increment,
promotion: None,
})))
}

View File

@ -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<Pattern2PromotionHint>,
}
/// 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

View File

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