From 5988374ecf5ccb200288fed4e296855acb8b48a2 Mon Sep 17 00:00:00 2001 From: tomoaki Date: Mon, 29 Dec 2025 12:17:57 +0900 Subject: [PATCH] phase29aj(p0): expose planner outcome facts for strict observability --- CURRENT_TASK.md | 3 + docs/development/current/main/10-Now.md | 9 +- docs/development/current/main/30-Backlog.md | 4 + ...VE-PATTERN2-PROMOTION_HINT-INSTRUCTIONS.md | 10 +- .../current/main/phases/phase-29ai/README.md | 2 +- .../P0-PLANNER-OUTCOME-SSOT-INSTRUCTIONS.md | 93 +++++++++++++++++++ .../current/main/phases/phase-29aj/README.md | 16 ++++ .../control_flow/plan/planner/build.rs | 10 +- .../builder/control_flow/plan/planner/mod.rs | 2 + .../control_flow/plan/planner/outcome.rs | 40 ++++++++ .../control_flow/plan/single_planner/rules.rs | 46 +++------ 11 files changed, 187 insertions(+), 48 deletions(-) create mode 100644 docs/development/current/main/phases/phase-29aj/P0-PLANNER-OUTCOME-SSOT-INSTRUCTIONS.md create mode 100644 docs/development/current/main/phases/phase-29aj/README.md create mode 100644 src/mir/builder/control_flow/plan/planner/outcome.rs diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index 34c0bb06..39a28f43 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -15,6 +15,9 @@ Scope: Repo root の旧リンク互換。現行の入口は `docs/development/cu **2025-12-29: Phase 29ai P15 COMPLETE (Pattern2 promotion hint observe)** strict/dev 時のみ `[plan/pattern2/promotion_hint:{TrimSeg|DigitPos}]` を観測できるようにし、次は promotion hint の Plan/Frag 吸収残作業(P16候補)。 +**2025-12-29: Phase 29aj P0 COMPLETE (PlannerOutcome observability SSOT)** +planner outcome(facts+plan)を SSOT 化し、single_planner の観測が planner facts のみに依存するように統一。 + **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 37510f66..9009d569 100644 --- a/docs/development/current/main/10-Now.md +++ b/docs/development/current/main/10-Now.md @@ -1,8 +1,13 @@ # Self Current Task — Now (main) -## Current Focus: Phase 29ai(Plan/Frag single-planner) +## Current Focus: Phase 29aj(PlannerOutcome SSOT) -Next: Phase 29ai P16(TBD: promotion hint を JoinIR 側の orchestrator へ配線、挙動不変) +Next: Phase 29aj P1(TBD: promotion hint を JoinIR 側の orchestrator へ配線、挙動不変) + +**2025-12-29: Phase 29aj P0 完了** ✅ +- 目的: planner outcome(facts+plan)を SSOT 化して strict 観測の再スキャンを撤去(仕様不変) +- 実装: `src/mir/builder/control_flow/plan/planner/outcome.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 29ai P15 完了** ✅ - 目的: strict/dev のときだけ LoopBodyLocal facts を安定タグで観測できるようにする(仕様不変) diff --git a/docs/development/current/main/30-Backlog.md b/docs/development/current/main/30-Backlog.md index f142aa13..3b0a255d 100644 --- a/docs/development/current/main/30-Backlog.md +++ b/docs/development/current/main/30-Backlog.md @@ -17,6 +17,10 @@ Related: - **Phase 29ah(✅ COMPLETE): JoinIR regression pack expansion(real-world coverage)** - 入口: `docs/development/current/main/phases/phase-29ah/README.md` +- **Phase 29aj(candidate): PlannerOutcome observability SSOT** + - 入口: `docs/development/current/main/phases/phase-29aj/README.md` + - Next: Phase 29aj P1(TBD: promotion hint を JoinIR 側の orchestrator へ配線、挙動不変) + - **Phase 29ai(candidate): Plan/Frag single-planner(Facts SSOT)** - 入口: `docs/development/current/main/phases/phase-29ai/README.md` - Next: Phase 29ai P16(TBD: promotion hint を JoinIR 側の orchestrator へ配線、挙動不変) diff --git a/docs/development/current/main/phases/phase-29ai/P15-OBSERVE-PATTERN2-PROMOTION_HINT-INSTRUCTIONS.md b/docs/development/current/main/phases/phase-29ai/P15-OBSERVE-PATTERN2-PROMOTION_HINT-INSTRUCTIONS.md index 155ff92c..ceafbc71 100644 --- a/docs/development/current/main/phases/phase-29ai/P15-OBSERVE-PATTERN2-PROMOTION_HINT-INSTRUCTIONS.md +++ b/docs/development/current/main/phases/phase-29ai/P15-OBSERVE-PATTERN2-PROMOTION_HINT-INSTRUCTIONS.md @@ -17,7 +17,7 @@ Goal: promotion hint が付与されている事実を stable tag で固定し ## Implementation Steps -### Step 1: タグ出力を追加(planner採用時のみ) +### Step 1: タグ出力を追加(planner outcome 参照) ファイル: - `src/mir/builder/control_flow/plan/single_planner/rules.rs` @@ -25,10 +25,13 @@ Goal: promotion hint が付与されている事実を stable tag で固定し 実装位置: - `try_build_domain_plan()` の `if let Some(domain_plan) = plan_opt { ... }` 直前付近 +補足: +- planner outcome は `build_plan_with_facts()` の結果を使い、facts 直抽出はしない + ガード条件(全部満たすときだけ出す): - `entry.kind` が `RuleKind::Pattern2` -- `crate::config::env::joinir_strict_enabled()` が true -- `Pattern2LoopBodyLocalFacts` が取れる(planner 由来の promotion か、facts 直抽出) +- `crate::config::env::joinir_dev::strict_enabled()` が true +- `build_plan_with_facts()` の outcome から `Pattern2LoopBodyLocalFacts` が取れる 出力するタグ(stderr 推奨、1行固定): - TrimSeg: `[plan/pattern2/promotion_hint:TrimSeg]` @@ -36,6 +39,7 @@ Goal: promotion hint が付与されている事実を stable tag で固定し 注意: - `trace::trace()` は `filter_noise()` で落ちるので `eprintln!` を使う +- facts 直抽出は禁止(planner outcome だけを参照する) ### Step 2: integration smoke をタグ検証に昇格 diff --git a/docs/development/current/main/phases/phase-29ai/README.md b/docs/development/current/main/phases/phase-29ai/README.md index eacc65b0..b5b71da7 100644 --- a/docs/development/current/main/phases/phase-29ai/README.md +++ b/docs/development/current/main/phases/phase-29ai/README.md @@ -95,7 +95,7 @@ Goal: pattern 名による分岐を外部APIから消し、Facts(事実)→ - 指示書: `docs/development/current/main/phases/phase-29ai/P15-OBSERVE-PATTERN2-PROMOTION_HINT-INSTRUCTIONS.md` - ねらい: strict/dev のときだけ promotion hint を安定タグで観測できるようにする(挙動不変) -- 完了: LoopBodyLocal facts が取れたときに `[plan/pattern2/promotion_hint:{TrimSeg|DigitPos}]` を出力し、2 本をタグ検証に昇格 +- 完了: planner outcome から LoopBodyLocal facts を参照して `[plan/pattern2/promotion_hint:{TrimSeg|DigitPos}]` を出力し、2 本をタグ検証に昇格 - 検証: `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/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 new file mode 100644 index 00000000..257482f8 --- /dev/null +++ b/docs/development/current/main/phases/phase-29aj/P0-PLANNER-OUTCOME-SSOT-INSTRUCTIONS.md @@ -0,0 +1,93 @@ +# Phase 29aj P0: PlannerOutcome(Facts+Plan)SSOT + +Date: 2025-12-29 +Status: Ready for execution +Scope: planner API 拡張(互換維持)+ single_planner の観測を outcome SSOT に統一 +Goal: facts の直抽出を撤去し、strict/dev 観測が planner outcome の facts に依存する状態へ + +## Objective + +- single_planner が観測タグのために facts を再スキャンする状態をやめる +- planner が plan が None でも facts を返せるようにし、観測 SSOT を planner に固定する +- 既定挙動・エラー文字列は不変(strict/dev の観測タグのみ対象) + +## Non-goals + +- Pattern2 LoopBodyLocal を planner 経路で実際に lowering する(P1 以降) +- 新しい env var 追加 +- タグ文字列の変更(`[plan/pattern2/promotion_hint:{TrimSeg|DigitPos}]` 維持) + +## Target Architecture + +- planner API 追加(互換維持) + - 既存: `build_plan(condition, body) -> Result, Freeze>` + - 追加: `build_plan_with_facts(condition, body) -> Result` +- single_planner は `build_plan_with_facts()` を 1 回だけ呼び、観測は outcome.facts 参照のみ + +## PlanBuildOutcome(新設) + +推奨ファイル: +- `src/mir/builder/control_flow/plan/planner/outcome.rs` + +構造(例): +- `facts: Option` +- `plan: Option` +- `chosen_rule: Option<&'static str>`(任意。未使用なら None) + +## Implementation Steps + +### Step 1: planner outcome の追加(互換維持) + +Files: +- `src/mir/builder/control_flow/plan/planner/mod.rs` +- `src/mir/builder/control_flow/plan/planner/outcome.rs` + +やること: +- PlanBuildOutcome を追加 +- `build_plan_with_facts()` 実装: + 1. `try_build_loop_facts(condition, body)?` + 2. `canonicalize_loop_facts(facts)` → outcome.facts に格納 + 3. `build_plan_from_facts(canonical)` → outcome.plan に格納 +- 既存 `build_plan()` は互換用に `build_plan_with_facts().map(|o| o.plan)` へ委譲 + +注意: +- plan が None でも facts は Some になり得る(観測のために重要) + +### Step 2: single_planner を outcome SSOT に統一 + +Files: +- `src/mir/builder/control_flow/plan/single_planner/rules.rs` + +やること: +- planner 呼び出しを `build_plan_with_facts()` に置換し memoize 維持 +- P15 で入った facts 直抽出を撤去 +- タグ判定は outcome.facts のみ参照 + +### Step 3: smoke は現状維持 + +Files: +- `tools/smokes/v2/profiles/integration/apps/phase29ab_pattern2_loopbodylocal_seg_min_vm.sh` +- `tools/smokes/v2/profiles/integration/apps/phase29ab_pattern2_loopbodylocal_min_vm.sh` + +やること: +- 変更なし(タグ必須のまま PASS すること) + +### Step 4: docs / CURRENT_TASK 更新 + +Files: +- `docs/development/current/main/phases/phase-29ai/README.md`(P15 の観測が planner outcome 参照になったこと) +- `docs/development/current/main/phases/phase-29ai/P15-...INSTRUCTIONS.md`(facts直抽出禁止の追記) +- `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`(154/154 PASS) +- `./tools/smokes/v2/profiles/integration/joinir/phase29ae_regression_pack_vm.sh` PASS +- single_planner に facts 直抽出が残っていない + +## Commit + +- `git add -A && git commit -m "phase29aj(p0): expose planner outcome facts for strict observability"` diff --git a/docs/development/current/main/phases/phase-29aj/README.md b/docs/development/current/main/phases/phase-29aj/README.md new file mode 100644 index 00000000..c380c95f --- /dev/null +++ b/docs/development/current/main/phases/phase-29aj/README.md @@ -0,0 +1,16 @@ +# Phase 29aj: PlannerOutcome observability SSOT + +Goal: planner の facts/plan を 1 本の outcome に集約し、観測の SSOT を planner 側に固定する(仕様不変)。 + +## P0: PlannerOutcome(Facts+Plan)SSOT + +- 指示書: `docs/development/current/main/phases/phase-29aj/P0-PLANNER-OUTCOME-SSOT-INSTRUCTIONS.md` +- ねらい: single_planner の観測が planner outcome の facts だけに依存する状態へ統一 +- 完了: build_plan_with_facts を追加し、single_planner のタグ出力は outcome.facts 参照に収束 +- 検証: `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` +- `./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/planner/build.rs b/src/mir/builder/control_flow/plan/planner/build.rs index 0a1453f1..c2d5dcfd 100644 --- a/src/mir/builder/control_flow/plan/planner/build.rs +++ b/src/mir/builder/control_flow/plan/planner/build.rs @@ -4,10 +4,10 @@ use crate::ast::ASTNode; -use crate::mir::builder::control_flow::plan::facts::try_build_loop_facts; -use crate::mir::builder::control_flow::plan::normalize::{canonicalize_loop_facts, CanonicalLoopFacts}; +use crate::mir::builder::control_flow::plan::normalize::CanonicalLoopFacts; 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, @@ -21,11 +21,7 @@ pub(in crate::mir::builder) fn build_plan( condition: &ASTNode, body: &[ASTNode], ) -> Result, Freeze> { - let Some(facts) = try_build_loop_facts(condition, body)? else { - return Ok(None); - }; - let canonical = canonicalize_loop_facts(facts); - build_plan_from_facts(canonical) + Ok(build_plan_with_facts(condition, body)?.plan) } pub(in crate::mir::builder) fn build_plan_from_facts( diff --git a/src/mir/builder/control_flow/plan/planner/mod.rs b/src/mir/builder/control_flow/plan/planner/mod.rs index 05c9e645..b4ed9993 100644 --- a/src/mir/builder/control_flow/plan/planner/mod.rs +++ b/src/mir/builder/control_flow/plan/planner/mod.rs @@ -8,6 +8,8 @@ pub(in crate::mir::builder) mod build; pub(in crate::mir::builder) mod candidates; pub(in crate::mir::builder) mod freeze; +pub(in crate::mir::builder) mod outcome; pub(in crate::mir::builder) use build::build_plan; pub(in crate::mir::builder) use freeze::Freeze; +pub(in crate::mir::builder) use outcome::{build_plan_with_facts, PlanBuildOutcome}; diff --git a/src/mir/builder/control_flow/plan/planner/outcome.rs b/src/mir/builder/control_flow/plan/planner/outcome.rs new file mode 100644 index 00000000..ae164fa0 --- /dev/null +++ b/src/mir/builder/control_flow/plan/planner/outcome.rs @@ -0,0 +1,40 @@ +//! 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::normalize::{ + canonicalize_loop_facts, CanonicalLoopFacts, +}; +use crate::mir::builder::control_flow::plan::DomainPlan; + +use super::build::build_plan_from_facts; +use super::Freeze; + +#[derive(Debug, Clone)] +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( + condition: &ASTNode, + body: &[ASTNode], +) -> Result { + let Some(facts) = try_build_loop_facts(condition, body)? else { + return Ok(PlanBuildOutcome { + facts: None, + plan: None, + chosen_rule: None, + }); + }; + let canonical = canonicalize_loop_facts(facts); + let plan = build_plan_from_facts(canonical.clone())?; + + 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 bbd88939..b6c2c451 100644 --- a/src/mir/builder/control_flow/plan/single_planner/rules.rs +++ b/src/mir/builder/control_flow/plan/single_planner/rules.rs @@ -7,17 +7,20 @@ use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternCont use crate::mir::loop_pattern_detection::LoopPatternKind; use super::legacy_rules; -use crate::mir::builder::control_flow::plan::facts::pattern2_loopbodylocal_facts::{ - try_extract_pattern2_loopbodylocal_facts, LoopBodyLocalShape, -}; +use crate::mir::builder::control_flow::plan::facts::pattern2_loopbodylocal_facts::LoopBodyLocalShape; use crate::mir::builder::control_flow::plan::planner; -use crate::mir::builder::control_flow::plan::{DomainPlan, Pattern2PromotionHint}; +use crate::mir::builder::control_flow::plan::DomainPlan; pub(super) fn try_build_domain_plan(ctx: &LoopPatternContext) -> Result, String> { use crate::mir::builder::control_flow::joinir::trace; - let planner_opt = planner::build_plan(ctx.condition, ctx.body) + let outcome = planner::build_plan_with_facts(ctx.condition, ctx.body) .map_err(|freeze| freeze.to_string())?; + let planner_opt = outcome.plan.clone(); + let promotion_facts = outcome + .facts + .as_ref() + .and_then(|facts| facts.facts.pattern2_loopbodylocal.as_ref()); // NOTE: Names must match the previous PLAN_EXTRACTORS entries exactly. // Order must match exactly. @@ -94,36 +97,9 @@ pub(super) fn try_build_domain_plan(ctx: &LoopPatternContext) -> Result { - if let Some(Pattern2PromotionHint::LoopBodyLocal(facts)) = &plan.promotion { - Some(match facts.shape { - LoopBodyLocalShape::TrimSeg { .. } => { - "[plan/pattern2/promotion_hint:TrimSeg]" - } - LoopBodyLocalShape::DigitPos { .. } => { - "[plan/pattern2/promotion_hint:DigitPos]" - } - }) - } else { - None - } - } - _ => None, - }; - from_plan.or_else(|| { - try_extract_pattern2_loopbodylocal_facts(ctx.condition, ctx.body) - .ok() - .and_then(|facts| { - facts.map(|facts| match facts.shape { - LoopBodyLocalShape::TrimSeg { .. } => { - "[plan/pattern2/promotion_hint:TrimSeg]" - } - LoopBodyLocalShape::DigitPos { .. } => { - "[plan/pattern2/promotion_hint:DigitPos]" - } - }) - }) + promotion_facts.map(|facts| match facts.shape { + LoopBodyLocalShape::TrimSeg { .. } => "[plan/pattern2/promotion_hint:TrimSeg]", + LoopBodyLocalShape::DigitPos { .. } => "[plan/pattern2/promotion_hint:DigitPos]", }) } else { None