From 9a686cd5108e438fc5cb1370c1dd508a960e3aa3 Mon Sep 17 00:00:00 2001 From: tomoaki Date: Mon, 29 Dec 2025 14:49:48 +0900 Subject: [PATCH] phase29ak(p2): gate pattern8 facts by static box ctx --- CURRENT_TASK.md | 3 + docs/development/current/main/10-Now.md | 7 +- docs/development/current/main/30-Backlog.md | 4 +- ...PATTERN8-STATIC-BOX-FILTER-INSTRUCTIONS.md | 55 +++++++++ .../current/main/phases/phase-29ak/README.md | 7 ++ .../control_flow/plan/facts/loop_facts.rs | 112 ++++++++++++++++-- 6 files changed, 177 insertions(+), 11 deletions(-) create mode 100644 docs/development/current/main/phases/phase-29ak/P2-PLANNER-PATTERN8-STATIC-BOX-FILTER-INSTRUCTIONS.md diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 4fc43490..1bea4df5 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -30,6 +30,9 @@ PlanRuleOrder を SSOT 化し、PlannerContext を配線(未使用)。single **2025-12-29: Phase 29ak P1 COMPLETE (Pattern1 facts guard via planner)** Pattern1 以外のループで pattern1_simplewhile facts 抽出を抑制。single_planner 側の guard は安全策として維持。 +**2025-12-29: Phase 29ak P2 COMPLETE (Pattern8 static box filter via planner)** +static box では Pattern8 facts 抽出を抑制。single_planner 側の filter は安全策として維持。 + **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 8d311ebd..747f6bbd 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 P2(TBD) +Next: Phase 29ak P3(TBD) 運用ルール: integration filter で phase143_* は回さない(JoinIR 回帰は phase29ae pack のみ) 運用ルール: phase286_pattern9_* は legacy pack (SKIP) を使う +**2025-12-29: Phase 29ak P2 完了** ✅ +- 目的: Pattern8 static box filter を planner 側へ移し、facts 抽出を抑制(仕様不変) +- 実装: `src/mir/builder/control_flow/plan/facts/loop_facts.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 P1 完了** ✅ - 目的: Pattern1 guard を planner 側へ移して facts 抽出を抑制(仕様不変) - 実装: `src/mir/builder/control_flow/plan/facts/loop_facts.rs` / `src/mir/builder/control_flow/plan/planner/outcome.rs` diff --git a/docs/development/current/main/30-Backlog.md b/docs/development/current/main/30-Backlog.md index 48f7cdca..09e93046 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/P1 ✅ 完了 - - Next: Phase 29ak P2(TBD) + - 状況: P0/P1/P2 ✅ 完了 + - Next: Phase 29ak P3(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/P2-PLANNER-PATTERN8-STATIC-BOX-FILTER-INSTRUCTIONS.md b/docs/development/current/main/phases/phase-29ak/P2-PLANNER-PATTERN8-STATIC-BOX-FILTER-INSTRUCTIONS.md new file mode 100644 index 00000000..dc4dd064 --- /dev/null +++ b/docs/development/current/main/phases/phase-29ak/P2-PLANNER-PATTERN8-STATIC-BOX-FILTER-INSTRUCTIONS.md @@ -0,0 +1,55 @@ +# Phase 29ak P2: Gate Pattern8 facts by static box ctx + +Date: 2025-12-29 +Status: Ready for execution +Scope: PlannerContext.in_static_box を使って Pattern8 facts を抑制(挙動・ログ不変) +Goal: single_planner の特例フィルタ責務を planner 側へ移す + +## Objective + +- in_static_box == true のとき Pattern8 facts 抽出を行わない +- single_planner 側の static box reject 分岐は安全策として残す +- ログ差分は出さない(planner 側は無言で Ok(None)) + +## Non-goals + +- Pattern8 static box reject 分岐の削除 +- CandidateSet への順序移管 +- 新 env var 追加 + +## Implementation Steps + +### Step 1: facts 入口で Pattern8 を抑制 + +Update: +- `src/mir/builder/control_flow/plan/facts/loop_facts.rs` + +Notes: +- `try_build_loop_facts_with_ctx` で `ctx.in_static_box` を参照し、Pattern8 抽出関数を呼ばない + +### Step 2: planner outcome 側は ctx 版を継続使用 + +Update: +- `src/mir/builder/control_flow/plan/planner/outcome.rs` + +### Step 3: unit test 追加(facts) + +- in_static_box=true で Pattern8 facts が None になることを固定 + +### Step 4: 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(p2): gate pattern8 facts by static box ctx"` diff --git a/docs/development/current/main/phases/phase-29ak/README.md b/docs/development/current/main/phases/phase-29ak/README.md index f31b7684..98f4bbeb 100644 --- a/docs/development/current/main/phases/phase-29ak/README.md +++ b/docs/development/current/main/phases/phase-29ak/README.md @@ -15,3 +15,10 @@ Goal: single_planner の「順序・名前・ガード」の SSOT を 1 箇所 - ねらい: 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` + +## P2: Pattern8 static box filter を planner 側へ移動 + +- 指示書: `docs/development/current/main/phases/phase-29ak/P2-PLANNER-PATTERN8-STATIC-BOX-FILTER-INSTRUCTIONS.md` +- ねらい: static box では Pattern8 facts 抽出を抑制(single_planner 側の filter は維持) +- 完了: PlannerContext.in_static_box を参照して loop_facts 入口で Pattern8 を抑制 +- 検証: `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 3b78dd9a..b71541ee 100644 --- a/src/mir/builder/control_flow/plan/facts/loop_facts.rs +++ b/src/mir/builder/control_flow/plan/facts/loop_facts.rs @@ -74,7 +74,7 @@ pub(in crate::mir::builder) fn try_build_loop_facts( condition: &ASTNode, body: &[ASTNode], ) -> Result, Freeze> { - try_build_loop_facts_inner(condition, body, true) + try_build_loop_facts_inner(condition, body, true, true) } pub(in crate::mir::builder) fn try_build_loop_facts_with_ctx( @@ -87,13 +87,15 @@ pub(in crate::mir::builder) fn try_build_loop_facts_with_ctx( Some(_) => false, None => true, }; - try_build_loop_facts_inner(condition, body, allow_pattern1) + let allow_pattern8 = !ctx.in_static_box; + try_build_loop_facts_inner(condition, body, allow_pattern1, allow_pattern8) } fn try_build_loop_facts_inner( condition: &ASTNode, body: &[ASTNode], allow_pattern1: bool, + allow_pattern8: 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). @@ -112,12 +114,16 @@ fn try_build_loop_facts_inner( let pattern4_continue = try_extract_pattern4_continue_facts(condition, body)?; let pattern5_infinite_early_exit = try_extract_pattern5_infinite_early_exit_facts(condition, body)?; - let pattern8_bool_predicate_scan = try_extract_pattern8_bool_predicate_scan_facts( - condition, - body, - &condition_shape, - &step_shape, - )?; + let pattern8_bool_predicate_scan = if allow_pattern8 { + try_extract_pattern8_bool_predicate_scan_facts( + condition, + body, + &condition_shape, + &step_shape, + )? + } else { + None + }; let pattern9_accum_const_loop = try_extract_pattern9_accum_const_loop_facts(condition, body)?; let pattern2_break = try_extract_pattern2_break_facts(condition, body)?; let pattern2_loopbodylocal = try_extract_pattern2_loopbodylocal_facts(condition, body)?; @@ -808,6 +814,96 @@ mod tests { assert!(facts.pattern1_simplewhile.is_some()); } + #[test] + fn loopfacts_ctx_blocks_pattern8_in_static_box() { + 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 predicate_if = ASTNode::If { + condition: Box::new(ASTNode::UnaryOp { + operator: crate::ast::UnaryOperator::Not, + operand: Box::new(ASTNode::MethodCall { + object: Box::new(ASTNode::Me { + span: Span::unknown(), + }), + method: "is_digit".to_string(), + arguments: vec![ASTNode::MethodCall { + object: Box::new(v("s")), + method: "substring".to_string(), + arguments: vec![ + v("i"), + 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(), + }], + span: Span::unknown(), + }), + span: Span::unknown(), + }), + then_body: vec![ASTNode::Return { + value: Some(Box::new(ASTNode::Literal { + value: LiteralValue::Bool(false), + span: Span::unknown(), + })), + span: Span::unknown(), + }], + else_body: None, + 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 body = vec![predicate_if, step]; + let allow_ctx = PlannerContext { + pattern_kind: None, + in_static_box: false, + debug: false, + }; + let block_ctx = PlannerContext { + pattern_kind: None, + in_static_box: true, + debug: false, + }; + + let allow = try_build_loop_facts_with_ctx(&allow_ctx, &condition, &body).expect("Ok"); + assert!(allow + .as_ref() + .and_then(|facts| facts.pattern8_bool_predicate_scan.as_ref()) + .is_some()); + + let blocked = try_build_loop_facts_with_ctx(&block_ctx, &condition, &body).expect("Ok"); + assert!(blocked + .as_ref() + .and_then(|facts| facts.pattern8_bool_predicate_scan.as_ref()) + .is_none()); + } + #[test] fn loopfacts_ok_none_when_condition_not_supported() { let condition = v("i"); // not `i < n`