diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index fa5fddb6..f689d334 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -21,6 +21,9 @@ planner outcome(facts+plan)を SSOT 化し、single_planner の観測が pla **2025-12-29: Phase 29aj P1 COMPLETE (Remove legacy_rules)** single_planner の legacy_rules を撤去し、Pattern1/3/4/5/8/9 の抽出を plan/extractors に統一。 +**2025-12-29: Phase 29aj P2 COMPLETE (Pattern1 planner-first)** +chosen_rule を撤去し、Pattern1 SimpleWhile の Facts→Planner-first を導入(仕様不変)。 + **2025-12-27: Phase 188.3 / Phase 287 P2 COMPLETE (Pattern6 nested loop: merge/latch fixes)** Pattern6(1-level nested loop)の JoinIR→bridge→merge 経路で発生していた `undefined ValueId` と `vm step budget exceeded`(無限ループ)を解消。`apps/tests/phase1883_nested_minimal.hako` が RC=9 を返し、quick 154 PASS を維持。 diff --git a/docs/development/current/main/10-Now.md b/docs/development/current/main/10-Now.md index 84bd425f..c4a3d6dc 100644 --- a/docs/development/current/main/10-Now.md +++ b/docs/development/current/main/10-Now.md @@ -2,7 +2,12 @@ ## Current Focus: Phase 29aj(PlannerOutcome SSOT) -Next: Phase 29aj P2(TBD: promotion hint を JoinIR 側の orchestrator へ配線、挙動不変) +Next: Phase 29aj P3(TBD: promotion hint を JoinIR 側の orchestrator へ配線、挙動不変) + +**2025-12-29: Phase 29aj P2 完了** ✅ +- 目的: chosen_rule を撤去し、Pattern1 を Facts→Planner-first に移行(仕様不変) +- 実装: `src/mir/builder/control_flow/plan/planner/outcome.rs` / `src/mir/builder/control_flow/plan/facts/pattern1_simplewhile_facts.rs` / `src/mir/builder/control_flow/plan/planner/build.rs` / `src/mir/builder/control_flow/plan/single_planner/rules.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 29aj P1 完了** ✅ - 目的: single_planner の legacy_rules を撤去し、plan extractor を SSOT に集約(仕様不変) diff --git a/docs/development/current/main/30-Backlog.md b/docs/development/current/main/30-Backlog.md index aad77c9e..1ae04db2 100644 --- a/docs/development/current/main/30-Backlog.md +++ b/docs/development/current/main/30-Backlog.md @@ -19,8 +19,8 @@ Related: - **Phase 29aj(candidate): PlannerOutcome observability SSOT** - 入口: `docs/development/current/main/phases/phase-29aj/README.md` - - 状況: P0/P1 ✅ 完了 - - Next: Phase 29aj P2(TBD: promotion hint を JoinIR 側の orchestrator へ配線、挙動不変) + - 状況: P0/P1/P2 ✅ 完了 + - Next: Phase 29aj P3(TBD: promotion hint を JoinIR 側の orchestrator へ配線、挙動不変) - **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-29aj/P0-PLANNER-OUTCOME-SSOT-INSTRUCTIONS.md b/docs/development/current/main/phases/phase-29aj/P0-PLANNER-OUTCOME-SSOT-INSTRUCTIONS.md index 257482f8..5c666e64 100644 --- a/docs/development/current/main/phases/phase-29aj/P0-PLANNER-OUTCOME-SSOT-INSTRUCTIONS.md +++ b/docs/development/current/main/phases/phase-29aj/P0-PLANNER-OUTCOME-SSOT-INSTRUCTIONS.md @@ -32,7 +32,6 @@ Goal: facts の直抽出を撤去し、strict/dev 観測が planner outcome の 構造(例): - `facts: Option` - `plan: Option` -- `chosen_rule: Option<&'static str>`(任意。未使用なら None) ## Implementation Steps diff --git a/docs/development/current/main/phases/phase-29aj/P2-CHOSEN_RULE-REMOVE-PATTERN1-PLANNER-FIRST-INSTRUCTIONS.md b/docs/development/current/main/phases/phase-29aj/P2-CHOSEN_RULE-REMOVE-PATTERN1-PLANNER-FIRST-INSTRUCTIONS.md new file mode 100644 index 00000000..d0c416f2 --- /dev/null +++ b/docs/development/current/main/phases/phase-29aj/P2-CHOSEN_RULE-REMOVE-PATTERN1-PLANNER-FIRST-INSTRUCTIONS.md @@ -0,0 +1,82 @@ +# Phase 29aj P2: chosen_rule 撤去 + Pattern1 planner-first(subset) + +Date: 2025-12-29 +Status: Ready for execution +Scope: planner outcome の整理 + Pattern1 Facts→Planner-first(仕様不変) +Goal: unused outcome field を削除し、Pattern1 の extractor 依存を 1 つ減らす + +## Objective + +- `PlanBuildOutcome::chosen_rule` を撤去し、outcome SSOT を引き締める +- Pattern1(SimpleWhile)を Facts→Planner-first に移す(超保守 subset) +- 既定挙動・観測・エラー文字列は不変(P15 タグ維持) + +## Non-goals + +- Pattern3/4/5/8/9 の planner-first 化 +- ルール順序 SSOT の CandidateSet への移管 +- 新しい env var 追加 +- 追加ログ + +## Implementation Steps + +### Step 1: chosen_rule を削除 + +Files: +- `src/mir/builder/control_flow/plan/planner/outcome.rs` + +やること: +- `PlanBuildOutcome` から `chosen_rule` を削除 +- `build_plan_with_facts()` の返却からも削除 + +### Step 2: Pattern1 Facts を追加(subset) + +Files: +- `src/mir/builder/control_flow/plan/facts/pattern1_simplewhile_facts.rs`(新規) +- `src/mir/builder/control_flow/plan/facts/mod.rs` +- `src/mir/builder/control_flow/plan/facts/loop_facts.rs` + +Subset 条件: +- condition: ` < ` のみ +- body: break/continue/return なし +- if-else 禁止(if は許可) +- loop_increment: `var = var + `(PoC は ` == 1`) + +### Step 3: Planner candidate に Pattern1 を追加 + +Files: +- `src/mir/builder/control_flow/plan/planner/build.rs` + +やること: +- `pattern1_simplewhile` が Some のとき `DomainPlan::Pattern1SimpleWhile` を候補に追加 +- unit test を追加 + +### Step 4: single_planner を Pattern1 planner-first に + +Files: +- `src/mir/builder/control_flow/plan/single_planner/rules.rs` + +やること: +- RuleKind に Pattern1 を追加 +- planner_opt が `Pattern1SimpleWhile` のとき採用 +- それ以外は `extract_pattern1_plan()` にフォールバック +- 既存の Pattern1 guard は維持 + +### Step 5: docs / CURRENT_TASK 更新 + +Files: +- `docs/development/current/main/phases/phase-29aj/README.md` +- `docs/development/current/main/10-Now.md` +- `docs/development/current/main/30-Backlog.md` +- `CURRENT_TASK.md` + +## Acceptance Criteria + +- `cargo build --release` +- `./tools/smokes/v2/run.sh --profile quick` +- `./tools/smokes/v2/profiles/integration/joinir/phase29ae_regression_pack_vm.sh` +- `rg -n "chosen_rule" src/mir/builder/control_flow/plan/planner/outcome.rs` がヒットしない + +## Commit + +- `git add -A && git commit -m "phase29aj(p2): planner-first pattern1 simplewhile subset"` diff --git a/docs/development/current/main/phases/phase-29aj/README.md b/docs/development/current/main/phases/phase-29aj/README.md index 575570ed..5c01d6a0 100644 --- a/docs/development/current/main/phases/phase-29aj/README.md +++ b/docs/development/current/main/phases/phase-29aj/README.md @@ -16,6 +16,13 @@ Goal: planner の facts/plan を 1 本の outcome に集約し、観測の SSOT - 完了: Pattern1/3/4/5/8/9 を plan/extractors へ移設、legacy_rules を削除 - 検証: `cargo build --release` / `./tools/smokes/v2/run.sh --profile quick` / `./tools/smokes/v2/profiles/integration/joinir/phase29ae_regression_pack_vm.sh` +## P2: chosen_rule 撤去 + Pattern1 planner-first(subset) + +- 指示書: `docs/development/current/main/phases/phase-29aj/P2-CHOSEN_RULE-REMOVE-PATTERN1-PLANNER-FIRST-INSTRUCTIONS.md` +- ねらい: outcome の未使用フィールド撤去と Pattern1 の planner-first 化(仕様不変) +- 完了: chosen_rule を削除し、Pattern1 facts→planner を single_planner に接続 +- 検証: `cargo build --release` / `./tools/smokes/v2/run.sh --profile quick` / `./tools/smokes/v2/profiles/integration/joinir/phase29ae_regression_pack_vm.sh` + ## Verification (SSOT) - `cargo build --release` 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 b4e5afd0..56075721 100644 --- a/src/mir/builder/control_flow/plan/facts/loop_facts.rs +++ b/src/mir/builder/control_flow/plan/facts/loop_facts.rs @@ -13,6 +13,9 @@ use super::scan_shapes::{ConditionShape, StepShape}; use crate::mir::builder::control_flow::plan::planner::Freeze; use crate::ast::{BinaryOperator, LiteralValue}; use super::scan_shapes::LengthMethod; +use super::pattern1_simplewhile_facts::{ + Pattern1SimpleWhileFacts, try_extract_pattern1_simplewhile_facts, +}; use super::pattern2_break_facts::{Pattern2BreakFacts, try_extract_pattern2_break_facts}; use super::pattern2_loopbodylocal_facts::{ Pattern2LoopBodyLocalFacts, try_extract_pattern2_loopbodylocal_facts, @@ -24,6 +27,7 @@ pub(in crate::mir::builder) struct LoopFacts { pub step_shape: StepShape, pub scan_with_init: Option, pub split_scan: Option, + pub pattern1_simplewhile: Option, pub pattern2_break: Option, pub pattern2_loopbodylocal: Option, } @@ -57,11 +61,13 @@ 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 pattern2_break = try_extract_pattern2_break_facts(condition, body)?; let pattern2_loopbodylocal = try_extract_pattern2_loopbodylocal_facts(condition, body)?; if scan_with_init.is_none() && split_scan.is_none() + && pattern1_simplewhile.is_none() && pattern2_break.is_none() && pattern2_loopbodylocal.is_none() { @@ -73,6 +79,7 @@ pub(in crate::mir::builder) fn try_build_loop_facts( step_shape, scan_with_init, split_scan, + pattern1_simplewhile, pattern2_break, pattern2_loopbodylocal, })) diff --git a/src/mir/builder/control_flow/plan/facts/mod.rs b/src/mir/builder/control_flow/plan/facts/mod.rs index 1f3456cf..0f2dbd59 100644 --- a/src/mir/builder/control_flow/plan/facts/mod.rs +++ b/src/mir/builder/control_flow/plan/facts/mod.rs @@ -7,6 +7,7 @@ #![allow(dead_code)] pub(in crate::mir::builder) mod loop_facts; +pub(in crate::mir::builder) mod pattern1_simplewhile_facts; 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; diff --git a/src/mir/builder/control_flow/plan/facts/pattern1_simplewhile_facts.rs b/src/mir/builder/control_flow/plan/facts/pattern1_simplewhile_facts.rs new file mode 100644 index 00000000..9d46df35 --- /dev/null +++ b/src/mir/builder/control_flow/plan/facts/pattern1_simplewhile_facts.rs @@ -0,0 +1,99 @@ +//! Phase 29aj P2: Pattern1SimpleWhileFacts (Facts SSOT) + +use crate::ast::{ASTNode, BinaryOperator, LiteralValue}; +use crate::mir::builder::control_flow::plan::planner::Freeze; +use crate::mir::builder::control_flow::plan::extractors::common_helpers::{ + extract_loop_increment_plan, has_break_statement, has_continue_statement, has_if_else_statement, + has_return_statement, +}; + +#[derive(Debug, Clone)] +pub(in crate::mir::builder) struct Pattern1SimpleWhileFacts { + pub loop_var: String, + pub condition: ASTNode, + pub loop_increment: ASTNode, +} + +pub(in crate::mir::builder) fn try_extract_pattern1_simplewhile_facts( + condition: &ASTNode, + body: &[ASTNode], +) -> Result, Freeze> { + let Some(loop_var) = extract_loop_var_for_subset(condition) else { + return Ok(None); + }; + + if has_break_statement(body) || has_continue_statement(body) || has_return_statement(body) { + return Ok(None); + } + + if has_if_else_statement(body) { + return Ok(None); + } + + let loop_increment = match extract_loop_increment_plan(body, &loop_var) { + Ok(Some(inc)) => inc, + _ => return Ok(None), + }; + + if !is_increment_step_one(&loop_increment, &loop_var) { + return Ok(None); + } + + Ok(Some(Pattern1SimpleWhileFacts { + loop_var, + condition: condition.clone(), + loop_increment, + })) +} + +fn extract_loop_var_for_subset(condition: &ASTNode) -> Option { + let ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left, + right, + .. + } = condition + else { + return None; + }; + + let ASTNode::Variable { name, .. } = left.as_ref() else { + return None; + }; + + if !matches!( + right.as_ref(), + ASTNode::Literal { + value: LiteralValue::Integer(_), + .. + } + ) { + return None; + } + + Some(name.clone()) +} + +fn is_increment_step_one(loop_increment: &ASTNode, loop_var: &str) -> bool { + let ASTNode::BinaryOp { + operator: BinaryOperator::Add, + left, + right, + .. + } = loop_increment + else { + return false; + }; + + if !matches!(left.as_ref(), ASTNode::Variable { name, .. } if name == loop_var) { + return false; + } + + matches!( + right.as_ref(), + ASTNode::Literal { + value: LiteralValue::Integer(1), + .. + } + ) +} diff --git a/src/mir/builder/control_flow/plan/planner/build.rs b/src/mir/builder/control_flow/plan/planner/build.rs index c2d5dcfd..973656df 100644 --- a/src/mir/builder/control_flow/plan/planner/build.rs +++ b/src/mir/builder/control_flow/plan/planner/build.rs @@ -10,8 +10,8 @@ use super::candidates::{CandidateSet, PlanCandidate}; use super::outcome::build_plan_with_facts; use super::Freeze; use crate::mir::builder::control_flow::plan::{ - DomainPlan, Pattern2BreakPlan, Pattern2PromotionHint, ScanDirection, ScanWithInitPlan, - SplitScanPlan, + DomainPlan, Pattern1SimpleWhilePlan, Pattern2BreakPlan, Pattern2PromotionHint, ScanDirection, + ScanWithInitPlan, SplitScanPlan, }; /// Phase 29ai P0: External-ish SSOT entrypoint (skeleton) @@ -88,6 +88,17 @@ pub(in crate::mir::builder) fn build_plan_from_facts( }); } + if let Some(pattern1) = &facts.facts.pattern1_simplewhile { + candidates.push(PlanCandidate { + plan: DomainPlan::Pattern1SimpleWhile(Pattern1SimpleWhilePlan { + loop_var: pattern1.loop_var.clone(), + condition: pattern1.condition.clone(), + loop_increment: pattern1.loop_increment.clone(), + }), + rule: "loop/pattern1_simplewhile", + }); + } + candidates.finalize() } @@ -100,6 +111,9 @@ mod tests { use crate::mir::builder::control_flow::plan::facts::loop_facts::{ LoopFacts, ScanWithInitFacts, SplitScanFacts, }; + use crate::mir::builder::control_flow::plan::facts::pattern1_simplewhile_facts::{ + Pattern1SimpleWhileFacts, + }; 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, @@ -135,6 +149,7 @@ mod tests { i_var: "i".to_string(), start_var: "start".to_string(), }), + pattern1_simplewhile: None, pattern2_break: None, pattern2_loopbodylocal: None, }; @@ -160,6 +175,7 @@ mod tests { step_shape: StepShape::Unknown, scan_with_init: None, split_scan: None, + pattern1_simplewhile: None, pattern2_break: None, pattern2_loopbodylocal: None, }; @@ -180,6 +196,7 @@ mod tests { step_lit: 1, }), split_scan: None, + pattern1_simplewhile: None, pattern2_break: None, pattern2_loopbodylocal: None, }; @@ -193,6 +210,45 @@ mod tests { } } + #[test] + fn planner_builds_pattern1_simplewhile_plan_from_facts() { + let loop_condition = ASTNode::BinaryOp { + operator: BinaryOperator::Less, + left: Box::new(v("i")), + right: Box::new(lit_int(3)), + 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, + pattern1_simplewhile: Some(Pattern1SimpleWhileFacts { + loop_var: "i".to_string(), + condition: loop_condition.clone(), + loop_increment: loop_increment.clone(), + }), + pattern2_break: None, + pattern2_loopbodylocal: None, + }; + let canonical = canonicalize_loop_facts(facts); + let plan = build_plan_from_facts(canonical).expect("Ok"); + + match plan { + Some(DomainPlan::Pattern1SimpleWhile(plan)) => { + assert_eq!(plan.loop_var, "i"); + } + other => panic!("expected pattern1 simplewhile plan, got {:?}", other), + } + } + #[test] fn planner_sets_promotion_hint_for_pattern2_loopbodylocal() { let loop_condition = ASTNode::BinaryOp { @@ -228,6 +284,7 @@ mod tests { step_shape: StepShape::Unknown, scan_with_init: None, split_scan: None, + pattern1_simplewhile: None, pattern2_break: Some(Pattern2BreakFacts { loop_var: "i".to_string(), carrier_var: "sum".to_string(), diff --git a/src/mir/builder/control_flow/plan/planner/outcome.rs b/src/mir/builder/control_flow/plan/planner/outcome.rs index ae164fa0..7432ce56 100644 --- a/src/mir/builder/control_flow/plan/planner/outcome.rs +++ b/src/mir/builder/control_flow/plan/planner/outcome.rs @@ -14,8 +14,6 @@ use super::Freeze; pub(in crate::mir::builder) struct PlanBuildOutcome { pub facts: Option, pub plan: Option, - #[allow(dead_code)] - pub chosen_rule: Option<&'static str>, } pub(in crate::mir::builder) fn build_plan_with_facts( @@ -26,7 +24,6 @@ pub(in crate::mir::builder) fn build_plan_with_facts( return Ok(PlanBuildOutcome { facts: None, plan: None, - chosen_rule: None, }); }; let canonical = canonicalize_loop_facts(facts); @@ -35,6 +32,5 @@ pub(in crate::mir::builder) fn build_plan_with_facts( Ok(PlanBuildOutcome { facts: Some(canonical), plan, - chosen_rule: None, }) } 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 7e70b4b0..69d8f7db 100644 --- a/src/mir/builder/control_flow/plan/single_planner/rules.rs +++ b/src/mir/builder/control_flow/plan/single_planner/rules.rs @@ -60,7 +60,7 @@ pub(super) fn try_build_domain_plan(ctx: &LoopPatternContext) -> Result Result { + match planner_opt.as_ref() { + Some(DomainPlan::Pattern1SimpleWhile(_)) => (planner_opt.clone(), false), + _ => (extractors::pattern1::extract_pattern1_plan(ctx.condition, ctx.body)?, true), + } + } }; let promotion_tag = if matches!(entry.kind, RuleKind::Pattern2) @@ -169,4 +175,5 @@ enum RuleKind { Pattern6, Pattern7, Pattern2, + Pattern1, }