diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index cbd70893..4fc43490 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -16,7 +16,7 @@ Scope: Repo root の旧リンク互換。現行の入口は `docs/development/cu `./tools/smokes/v2/profiles/integration/joinir/phase29ae_regression_pack_vm.sh` が唯一の integration gate。phase143_* は対象外(legacy pack で隔離)。phase286_pattern9_* は legacy pack (SKIP) で運用。 **PlanRuleOrder SSOT** -single_planner の順序/名前 SSOT は `src/mir/builder/control_flow/plan/single_planner/rule_order.rs` に固定。PlannerContext は P0 では未使用で、P1 以降で段階的に利用する。 +single_planner の順序/名前 SSOT は `src/mir/builder/control_flow/plan/single_planner/rule_order.rs` に固定。PlannerContext で Pattern1 facts の抑制を開始し、残りの guard/filter は段階移行。 **2025-12-29: Phase 29aj P6 COMPLETE (JoinIR regression gate SSOT)** JoinIR 回帰の integration gate を phase29ae pack に固定し、phase143_* を legacy pack で隔離。 @@ -27,6 +27,9 @@ Pattern8 BoolPredicateScan の Facts→Planner-first を導入し、single_plann **2025-12-29: Phase 29ak P0 COMPLETE (PlanRuleOrder + PlannerContext plumbing)** PlanRuleOrder を SSOT 化し、PlannerContext を配線(未使用)。single_planner の手書きテーブルを撤去。 +**2025-12-29: Phase 29ak P1 COMPLETE (Pattern1 facts guard via planner)** +Pattern1 以外のループで pattern1_simplewhile facts 抽出を抑制。single_planner 側の guard は安全策として維持。 + **2025-12-29: Phase 29aj P10 COMPLETE (single_planner unified shape)** single_planner を全パターンで planner-first → extractor フォールバックの共通形に統一(挙動不変)。 diff --git a/docs/development/current/main/10-Now.md b/docs/development/current/main/10-Now.md index 4670e21a..8d311ebd 100644 --- a/docs/development/current/main/10-Now.md +++ b/docs/development/current/main/10-Now.md @@ -2,10 +2,15 @@ ## Current Focus: Phase 29ak(PlanRuleOrder + PlannerContext) -Next: Phase 29ak P1(TBD) +Next: Phase 29ak P2(TBD) 運用ルール: integration filter で phase143_* は回さない(JoinIR 回帰は phase29ae pack のみ) 運用ルール: phase286_pattern9_* は legacy pack (SKIP) を使う +**2025-12-29: Phase 29ak P1 完了** ✅ +- 目的: Pattern1 guard を planner 側へ移して facts 抽出を抑制(仕様不変) +- 実装: `src/mir/builder/control_flow/plan/facts/loop_facts.rs` / `src/mir/builder/control_flow/plan/planner/outcome.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 29ak P0 完了** ✅ - 目的: PlanRuleOrder SSOT を新設し、PlannerContext の配線だけ先に導入(仕様不変) - 実装: `src/mir/builder/control_flow/plan/single_planner/rule_order.rs` / `src/mir/builder/control_flow/plan/planner/context.rs` / `src/mir/builder/control_flow/plan/single_planner/rules.rs` diff --git a/docs/development/current/main/30-Backlog.md b/docs/development/current/main/30-Backlog.md index 523d4646..48f7cdca 100644 --- a/docs/development/current/main/30-Backlog.md +++ b/docs/development/current/main/30-Backlog.md @@ -26,8 +26,8 @@ Related: - **Phase 29ak(candidate): PlanRuleOrder SSOT + PlannerContext plumbing** - 入口: `docs/development/current/main/phases/phase-29ak/README.md` - - 状況: P0 ✅ 完了 - - Next: Phase 29ak P1(TBD) + - 状況: P0/P1 ✅ 完了 + - Next: Phase 29ak P2(TBD) - **Phase 29ai(candidate): Plan/Frag single-planner(Facts SSOT)** - 入口: `docs/development/current/main/phases/phase-29ai/README.md` diff --git a/docs/development/current/main/phases/phase-29ak/P1-PLANNER-PATTERN1-GUARD-INSTRUCTIONS.md b/docs/development/current/main/phases/phase-29ak/P1-PLANNER-PATTERN1-GUARD-INSTRUCTIONS.md new file mode 100644 index 00000000..05ba1617 --- /dev/null +++ b/docs/development/current/main/phases/phase-29ak/P1-PLANNER-PATTERN1-GUARD-INSTRUCTIONS.md @@ -0,0 +1,69 @@ +# Phase 29ak P1: Guard Pattern1 facts via PlannerContext + +Date: 2025-12-29 +Status: Ready for execution +Scope: planner の Facts 入口で pattern_kind を参照して早期 Ok(None)(保守的) +Goal: single_planner の guard 責務を減らし、planner 側へ寄せる第一歩にする(仕様不変) + +## Objective + +- PlannerContext.pattern_kind を使い、Pattern1 以外のループで Pattern1 facts 抽出を行わない +- 挙動・ログは不変(single_planner 側の guard は維持) + +## Non-goals + +- Pattern8 static box filter の移動 +- CandidateSet の順序SSOT化 +- extractor fallback の撤去 + +## Implementation Steps + +### Step 1: loop_facts に guard を追加 + +Update: +- `src/mir/builder/control_flow/plan/facts/loop_facts.rs` + +Add: +- `try_build_loop_facts_with_ctx(ctx, condition, body)` を新設 +- ctx.pattern_kind が Pattern1 以外のとき pattern1_simplewhile 抽出を抑制 + +### Step 2: planner outcome から ctx 版を使う + +Update: +- `src/mir/builder/control_flow/plan/planner/outcome.rs` + +Notes: +- `build_plan_with_facts_ctx` は `try_build_loop_facts_with_ctx` を使う +- `build_plan_with_facts` は既存挙動のまま + +### Step 3: single_planner guard は残す + +Update: +- `src/mir/builder/control_flow/plan/single_planner/rules.rs` + +Notes: +- planner 側に移した旨を docs に明記(guard は冗長だが安全策として維持) + +### Step 4: テストを追加 + +Add: +- pattern_kind != Pattern1 のとき pattern1 facts が None になること +- pattern_kind == Pattern1 のとき従来通り抽出できること + +### Step 5: docs / CURRENT_TASK 更新 + +Update: +- `docs/development/current/main/phases/phase-29ak/README.md` +- `docs/development/current/main/10-Now.md` +- `docs/development/current/main/30-Backlog.md` +- `CURRENT_TASK.md` + +## Verification + +- `cargo build --release` +- `./tools/smokes/v2/run.sh --profile quick` +- `./tools/smokes/v2/profiles/integration/joinir/phase29ae_regression_pack_vm.sh` + +## Commit + +- `git add -A && git commit -m "phase29ak(p1): guard pattern1 facts via planner context"` diff --git a/docs/development/current/main/phases/phase-29ak/README.md b/docs/development/current/main/phases/phase-29ak/README.md index 7106d433..f31b7684 100644 --- a/docs/development/current/main/phases/phase-29ak/README.md +++ b/docs/development/current/main/phases/phase-29ak/README.md @@ -8,3 +8,10 @@ Goal: single_planner の「順序・名前・ガード」の SSOT を 1 箇所 - ねらい: rule_order.rs を順序/名前 SSOT に固定し、PlannerContext を配線(未使用) - 完了: PlanRuleOrder を追加し、single_planner の手書きテーブルを撤去 - 検証: `cargo build --release` / `./tools/smokes/v2/run.sh --profile quick` / `./tools/smokes/v2/profiles/integration/joinir/phase29ae_regression_pack_vm.sh` + +## P1: Pattern1 guard を planner 側へ移動(facts 抽出抑制) + +- 指示書: `docs/development/current/main/phases/phase-29ak/P1-PLANNER-PATTERN1-GUARD-INSTRUCTIONS.md` +- ねらい: pattern_kind が Pattern1 以外のとき pattern1 facts 抽出を行わない +- 完了: PlannerContext を参照して loop_facts 入口で Pattern1 を抑制(single_planner 側の guard は維持) +- 検証: `cargo build --release` / `./tools/smokes/v2/run.sh --profile quick` / `./tools/smokes/v2/profiles/integration/joinir/phase29ae_regression_pack_vm.sh` diff --git a/src/mir/builder/control_flow/plan/facts/loop_facts.rs b/src/mir/builder/control_flow/plan/facts/loop_facts.rs index 34005d60..3b78dd9a 100644 --- a/src/mir/builder/control_flow/plan/facts/loop_facts.rs +++ b/src/mir/builder/control_flow/plan/facts/loop_facts.rs @@ -10,7 +10,8 @@ use crate::ast::ASTNode; use super::scan_shapes::{ConditionShape, StepShape}; -use crate::mir::builder::control_flow::plan::planner::Freeze; +use crate::mir::builder::control_flow::plan::planner::{Freeze, PlannerContext}; +use crate::mir::loop_pattern_detection::LoopPatternKind; use crate::ast::{BinaryOperator, LiteralValue}; use super::scan_shapes::LengthMethod; use super::pattern1_simplewhile_facts::{ @@ -72,6 +73,27 @@ pub(in crate::mir::builder) struct SplitScanFacts { pub(in crate::mir::builder) fn try_build_loop_facts( condition: &ASTNode, body: &[ASTNode], +) -> Result, Freeze> { + try_build_loop_facts_inner(condition, body, true) +} + +pub(in crate::mir::builder) fn try_build_loop_facts_with_ctx( + ctx: &PlannerContext, + condition: &ASTNode, + body: &[ASTNode], +) -> Result, Freeze> { + let allow_pattern1 = match ctx.pattern_kind { + Some(LoopPatternKind::Pattern1SimpleWhile) => true, + Some(_) => false, + None => true, + }; + try_build_loop_facts_inner(condition, body, allow_pattern1) +} + +fn try_build_loop_facts_inner( + condition: &ASTNode, + body: &[ASTNode], + allow_pattern1: bool, ) -> Result, Freeze> { // Phase 29ai P4/P7: keep Facts conservative; only return Some when we can // build a concrete pattern fact set (no guesses / no hardcoded names). @@ -81,7 +103,11 @@ pub(in crate::mir::builder) fn try_build_loop_facts( let step_shape = try_extract_step_shape(body)?.unwrap_or(StepShape::Unknown); let scan_with_init = try_extract_scan_with_init_facts(body, &condition_shape, &step_shape)?; let split_scan = try_extract_split_scan_facts(condition, body)?; - let pattern1_simplewhile = try_extract_pattern1_simplewhile_facts(condition, body)?; + let pattern1_simplewhile = if allow_pattern1 { + try_extract_pattern1_simplewhile_facts(condition, body)? + } else { + None + }; let pattern3_ifphi = try_extract_pattern3_ifphi_facts(condition, body)?; let pattern4_continue = try_extract_pattern4_continue_facts(condition, body)?; let pattern5_infinite_early_exit = @@ -642,6 +668,8 @@ fn is_i_increment_by_one(stmt: &ASTNode, i_var: &str) -> bool { mod tests { use super::*; use crate::ast::Span; + use crate::mir::builder::control_flow::plan::planner::PlannerContext; + use crate::mir::loop_pattern_detection::LoopPatternKind; fn v(name: &str) -> ASTNode { ASTNode::Variable { @@ -711,6 +739,75 @@ mod tests { assert!(facts.is_some()); } + #[test] + fn loopfacts_ctx_skips_pattern1_when_kind_mismatch() { + let condition = ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left: Box::new(v("i")), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(3), + span: Span::unknown(), + }), + span: Span::unknown(), + }; + let step = ASTNode::Assignment { + target: Box::new(v("i")), + value: Box::new(ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(v("i")), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(1), + span: Span::unknown(), + }), + span: Span::unknown(), + }), + span: Span::unknown(), + }; + let ctx = PlannerContext { + pattern_kind: Some(LoopPatternKind::Pattern2Break), + in_static_box: false, + debug: false, + }; + + let facts = try_build_loop_facts_with_ctx(&ctx, &condition, &[step]).expect("Ok"); + assert!(facts.is_none()); + } + + #[test] + fn loopfacts_ctx_allows_pattern1_when_kind_matches() { + let condition = ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left: Box::new(v("i")), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(3), + span: Span::unknown(), + }), + span: Span::unknown(), + }; + let step = ASTNode::Assignment { + target: Box::new(v("i")), + value: Box::new(ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left: Box::new(v("i")), + right: Box::new(ASTNode::Literal { + value: LiteralValue::Integer(1), + span: Span::unknown(), + }), + span: Span::unknown(), + }), + span: Span::unknown(), + }; + let ctx = PlannerContext { + pattern_kind: Some(LoopPatternKind::Pattern1SimpleWhile), + in_static_box: false, + debug: false, + }; + + let facts = try_build_loop_facts_with_ctx(&ctx, &condition, &[step]).expect("Ok"); + let facts = facts.expect("Some"); + assert!(facts.pattern1_simplewhile.is_some()); + } + #[test] fn loopfacts_ok_none_when_condition_not_supported() { let condition = v("i"); // not `i < n` diff --git a/src/mir/builder/control_flow/plan/facts/mod.rs b/src/mir/builder/control_flow/plan/facts/mod.rs index 5e745c92..f64b5e9d 100644 --- a/src/mir/builder/control_flow/plan/facts/mod.rs +++ b/src/mir/builder/control_flow/plan/facts/mod.rs @@ -17,4 +17,6 @@ pub(in crate::mir::builder) mod pattern2_break_facts; pub(in crate::mir::builder) mod pattern2_loopbodylocal_facts; pub(in crate::mir::builder) mod scan_shapes; -pub(in crate::mir::builder) use loop_facts::{try_build_loop_facts, LoopFacts}; +pub(in crate::mir::builder) use loop_facts::{ + try_build_loop_facts, try_build_loop_facts_with_ctx, LoopFacts, +}; diff --git a/src/mir/builder/control_flow/plan/planner/outcome.rs b/src/mir/builder/control_flow/plan/planner/outcome.rs index d492606c..e22b3e7e 100644 --- a/src/mir/builder/control_flow/plan/planner/outcome.rs +++ b/src/mir/builder/control_flow/plan/planner/outcome.rs @@ -1,7 +1,9 @@ //! Phase 29aj P0: Planner outcome (facts + plan) SSOT use crate::ast::ASTNode; -use crate::mir::builder::control_flow::plan::facts::try_build_loop_facts; +use crate::mir::builder::control_flow::plan::facts::{ + try_build_loop_facts, try_build_loop_facts_with_ctx, LoopFacts, +}; use crate::mir::builder::control_flow::plan::normalize::{ canonicalize_loop_facts, CanonicalLoopFacts, }; @@ -21,7 +23,23 @@ pub(in crate::mir::builder) fn build_plan_with_facts( condition: &ASTNode, body: &[ASTNode], ) -> Result { - let Some(facts) = try_build_loop_facts(condition, body)? else { + let facts = try_build_loop_facts(condition, body)?; + build_plan_from_facts_opt(facts) +} + +pub(in crate::mir::builder) fn build_plan_with_facts_ctx( + ctx: &PlannerContext, + condition: &ASTNode, + body: &[ASTNode], +) -> Result { + let facts = try_build_loop_facts_with_ctx(ctx, condition, body)?; + build_plan_from_facts_opt(facts) +} + +fn build_plan_from_facts_opt( + facts: Option, +) -> Result { + let Some(facts) = facts else { return Ok(PlanBuildOutcome { facts: None, plan: None, @@ -35,11 +53,3 @@ pub(in crate::mir::builder) fn build_plan_with_facts( plan, }) } - -pub(in crate::mir::builder) fn build_plan_with_facts_ctx( - _ctx: &PlannerContext, - condition: &ASTNode, - body: &[ASTNode], -) -> Result { - build_plan_with_facts(condition, body) -} diff --git a/src/mir/builder/control_flow/plan/single_planner/rules.rs b/src/mir/builder/control_flow/plan/single_planner/rules.rs index 4db0b652..04090eea 100644 --- a/src/mir/builder/control_flow/plan/single_planner/rules.rs +++ b/src/mir/builder/control_flow/plan/single_planner/rules.rs @@ -33,6 +33,7 @@ pub(super) fn try_build_domain_plan(ctx: &LoopPatternContext) -> Result