phase29ak(p0): ssot rule order + planner context plumbing

This commit is contained in:
2025-12-29 14:34:34 +09:00
parent a91e457302
commit 2626deeb72
11 changed files with 202 additions and 96 deletions

View File

@ -15,12 +15,18 @@ Scope: Repo root の旧リンク互換。現行の入口は `docs/development/cu
**JoinIR 回帰 SSOT**
`./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 以降で段階的に利用する。
**2025-12-29: Phase 29aj P6 COMPLETE (JoinIR regression gate SSOT)**
JoinIR 回帰の integration gate を phase29ae pack に固定し、phase143_* を legacy pack で隔離。
**2025-12-29: Phase 29aj P7 COMPLETE (Pattern8 planner-first)**
Pattern8 BoolPredicateScan の Facts→Planner-first を導入し、single_planner の extractor 依存を縮小。
**2025-12-29: Phase 29ak P0 COMPLETE (PlanRuleOrder + PlannerContext plumbing)**
PlanRuleOrder を SSOT 化し、PlannerContext を配線未使用。single_planner の手書きテーブルを撤去。
**2025-12-29: Phase 29aj P10 COMPLETE (single_planner unified shape)**
single_planner を全パターンで planner-first → extractor フォールバックの共通形に統一(挙動不変)。

View File

@ -1,11 +1,16 @@
# Self Current Task — Now (main)
## Current Focus: Phase 29ajPlannerOutcome SSOT
## Current Focus: Phase 29akPlanRuleOrder + PlannerContext
Next: Phase 29aj P11TBD
Next: Phase 29ak P1TBD
運用ルール: integration filter で phase143_* は回さないJoinIR 回帰は phase29ae pack のみ)
運用ルール: phase286_pattern9_* は legacy pack (SKIP) を使う
**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`
- 検証: `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 P10 完了**
- 目的: single_planner を全パターン planner-first 形に統一(挙動不変)
- 実装: `src/mir/builder/control_flow/plan/single_planner/rules.rs`

View File

@ -24,6 +24,11 @@ Related:
- 運用: integration filter で phase143_* は回さないJoinIR 回帰は phase29ae pack のみ)
- 運用: phase286_pattern9_* は legacy pack (SKIP) を使う
- **Phase 29akcandidate: PlanRuleOrder SSOT + PlannerContext plumbing**
- 入口: `docs/development/current/main/phases/phase-29ak/README.md`
- 状況: P0 ✅ 完了
- Next: Phase 29ak P1TBD
- **Phase 29aicandidate: Plan/Frag single-plannerFacts SSOT**
- 入口: `docs/development/current/main/phases/phase-29ai/README.md`
- Next: Phase 29ai P16TBD: promotion hint を JoinIR 側の orchestrator へ配線、挙動不変)

View File

@ -0,0 +1,71 @@
# Phase 29ak P0: PlanRuleOrder SSOT + PlannerContext plumbing
Date: 2025-12-29
Status: Ready for execution
Scope: 構造整備(仕様不変)+ docs/Now/Backlog/CURRENT_TASK 更新
Goal: single_planner の順序/名前/ガード SSOT を 1 箇所へ寄せ、planner 側へ ctx を通す土台を作る
## Objective
- ルール順序/表示名の SSOT を新設し、single_planner の手書きテーブルを撤去
- PlannerContext を導入して planner へ ctx を渡せるようにするP0 では未使用)
- 既定挙動・ログ・エラー文字列は不変
## Non-goals
- CandidateSet へ順序=優先を移す
- Pattern1 guard / Pattern8 static box filter を planner 側へ移す
- extractor fallback の削除
## Implementation Steps
### Step 1: ルール順序 SSOT を新設
New:
- `src/mir/builder/control_flow/plan/single_planner/rule_order.rs`
Contents:
- `PlanRuleId``PLAN_RULE_ORDER`
- `rule_name()` は従来の名前を完全一致で返す
### Step 2: single_planner を SSOT 参照に差し替え
Update:
- `src/mir/builder/control_flow/plan/single_planner/rules.rs`
Notes:
- Pattern1 guard / Pattern8 static box filter / Pattern2 promotion tag を維持
- ループの順序/ログ文言は変えない
### Step 3: PlannerContext の足場(未使用)
New:
- `src/mir/builder/control_flow/plan/planner/context.rs`
Update:
- `src/mir/builder/control_flow/plan/planner/mod.rs`
- `src/mir/builder/control_flow/plan/planner/outcome.rs`
- `src/mir/builder/control_flow/plan/single_planner/rules.rs`
Rule:
- P0 では ctx を意思決定に使わない(挙動不変)
### Step 4: docs / CURRENT_TASK 更新
Add:
- `docs/development/current/main/phases/phase-29ak/README.md`
Update:
- `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(p0): ssot rule order + planner context plumbing"`

View File

@ -0,0 +1,10 @@
# Phase 29ak: PlanRuleOrder SSOT + PlannerContext plumbing
Goal: single_planner の「順序・名前・ガード」の SSOT を 1 箇所へ寄せ、planner 側へ ctx を通す土台を作る(仕様不変)。
## P0: PlanRuleOrder SSOT + PlannerContext plumbing
- 指示書: `docs/development/current/main/phases/phase-29ak/P0-RULE-ORDER-SSOT-PLANNER-CONTEXT-PLUMBING-INSTRUCTIONS.md`
- ねらい: 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`

View File

@ -0,0 +1,8 @@
use crate::mir::loop_pattern_detection::LoopPatternKind;
#[derive(Debug, Clone, Copy)]
pub(in crate::mir::builder) struct PlannerContext {
pub pattern_kind: Option<LoopPatternKind>,
pub in_static_box: bool,
pub debug: bool,
}

View File

@ -7,9 +7,13 @@
pub(in crate::mir::builder) mod build;
pub(in crate::mir::builder) mod candidates;
pub(in crate::mir::builder) mod context;
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 context::PlannerContext;
pub(in crate::mir::builder) use freeze::Freeze;
pub(in crate::mir::builder) use outcome::{build_plan_with_facts, PlanBuildOutcome};
pub(in crate::mir::builder) use outcome::{
build_plan_with_facts, build_plan_with_facts_ctx, PlanBuildOutcome,
};

View File

@ -8,6 +8,7 @@ use crate::mir::builder::control_flow::plan::normalize::{
use crate::mir::builder::control_flow::plan::DomainPlan;
use super::build::build_plan_from_facts;
use super::context::PlannerContext;
use super::Freeze;
#[derive(Debug, Clone)]
@ -34,3 +35,11 @@ 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<PlanBuildOutcome, Freeze> {
build_plan_with_facts(condition, body)
}

View File

@ -8,6 +8,7 @@ use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternCont
use super::DomainPlan;
mod rules;
mod rule_order;
pub(in crate::mir::builder) fn try_build_domain_plan(
ctx: &LoopPatternContext,

View File

@ -0,0 +1,38 @@
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum PlanRuleId {
Pattern6,
Pattern7,
Pattern5,
Pattern8,
Pattern3,
Pattern4,
Pattern9,
Pattern2,
Pattern1,
}
pub(super) const PLAN_RULE_ORDER: &[PlanRuleId] = &[
PlanRuleId::Pattern6,
PlanRuleId::Pattern7,
PlanRuleId::Pattern5,
PlanRuleId::Pattern8,
PlanRuleId::Pattern3,
PlanRuleId::Pattern4,
PlanRuleId::Pattern9,
PlanRuleId::Pattern2,
PlanRuleId::Pattern1,
];
pub(super) fn rule_name(id: PlanRuleId) -> &'static str {
match id {
PlanRuleId::Pattern6 => "Pattern6_ScanWithInit (Phase 273)",
PlanRuleId::Pattern7 => "Pattern7_SplitScan (Phase 273)",
PlanRuleId::Pattern5 => "Pattern5_InfiniteEarlyExit (Phase 286 P3.2)",
PlanRuleId::Pattern8 => "Pattern8_BoolPredicateScan (Phase 286 P2.4)",
PlanRuleId::Pattern3 => "Pattern3_IfPhi (Phase 286 P2.6)",
PlanRuleId::Pattern4 => "Pattern4_Continue (Phase 286 P2)",
PlanRuleId::Pattern9 => "Pattern9_AccumConstLoop (Phase 286 P2.3)",
PlanRuleId::Pattern2 => "Pattern2_Break (Phase 286 P3.1)",
PlanRuleId::Pattern1 => "Pattern1_SimpleWhile (Phase 286 P2.1)",
}
}

View File

@ -8,13 +8,20 @@ use crate::mir::loop_pattern_detection::LoopPatternKind;
use crate::mir::builder::control_flow::plan::extractors;
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::planner::{self, PlannerContext};
use crate::mir::builder::control_flow::plan::DomainPlan;
use super::rule_order::{rule_name, PlanRuleId, PLAN_RULE_ORDER};
pub(super) fn try_build_domain_plan(ctx: &LoopPatternContext) -> Result<Option<DomainPlan>, String> {
use crate::mir::builder::control_flow::joinir::trace;
let outcome = planner::build_plan_with_facts(ctx.condition, ctx.body)
let planner_ctx = PlannerContext {
pattern_kind: Some(ctx.pattern_kind),
in_static_box: ctx.in_static_box,
debug: ctx.debug,
};
let outcome = planner::build_plan_with_facts_ctx(&planner_ctx, ctx.condition, ctx.body)
.map_err(|freeze| freeze.to_string())?;
let planner_opt = outcome.plan.clone();
let promotion_facts = outcome
@ -22,65 +29,26 @@ pub(super) fn try_build_domain_plan(ctx: &LoopPatternContext) -> Result<Option<D
.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.
let rules: &[RuleEntry] = &[
RuleEntry {
name: "Pattern6_ScanWithInit (Phase 273)",
kind: RuleKind::Pattern6,
},
RuleEntry {
name: "Pattern7_SplitScan (Phase 273)",
kind: RuleKind::Pattern7,
},
RuleEntry {
name: "Pattern5_InfiniteEarlyExit (Phase 286 P3.2)",
kind: RuleKind::Pattern5,
},
RuleEntry {
name: "Pattern8_BoolPredicateScan (Phase 286 P2.4)",
kind: RuleKind::Pattern8,
},
RuleEntry {
name: "Pattern3_IfPhi (Phase 286 P2.6)",
kind: RuleKind::Pattern3,
},
RuleEntry {
name: "Pattern4_Continue (Phase 286 P2)",
kind: RuleKind::Pattern4,
},
RuleEntry {
name: "Pattern9_AccumConstLoop (Phase 286 P2.3)",
kind: RuleKind::Pattern9,
},
RuleEntry {
name: "Pattern2_Break (Phase 286 P3.1)",
kind: RuleKind::Pattern2,
},
RuleEntry {
name: "Pattern1_SimpleWhile (Phase 286 P2.1)",
kind: RuleKind::Pattern1,
},
];
for entry in rules {
for rule_id in PLAN_RULE_ORDER {
let rule_id = *rule_id;
let name = rule_name(rule_id);
// Phase 286 P2.6: Pattern1 Plan guard (structural Fail-Fast)
// Pattern1 should only match Pattern1SimpleWhile pattern_kind
// This prevents Pattern1 from incorrectly matching Pattern3 fixtures
if matches!(entry.kind, RuleKind::Pattern1)
if matches!(rule_id, PlanRuleId::Pattern1)
&& ctx.pattern_kind != LoopPatternKind::Pattern1SimpleWhile
{
continue;
}
let planner_hit = try_take_planner(&planner_opt, entry.kind);
let planner_hit = try_take_planner(&planner_opt, rule_id);
let (plan_opt, log_none) = if planner_hit.is_some() {
(planner_hit, false)
} else {
(fallback_extract(ctx, entry.kind)?, true)
(fallback_extract(ctx, rule_id)?, true)
};
let promotion_tag = if matches!(entry.kind, RuleKind::Pattern2)
let promotion_tag = if matches!(rule_id, PlanRuleId::Pattern2)
&& crate::config::env::joinir_dev::strict_enabled()
{
promotion_facts.map(|facts| match facts.shape {
@ -96,28 +64,26 @@ pub(super) fn try_build_domain_plan(ctx: &LoopPatternContext) -> Result<Option<D
}
if let Some(domain_plan) = plan_opt {
// Phase 286 P3: Pattern8 static box filtering
// Plan extractors are pure (no builder access), so we filter here.
if matches!(entry.kind, RuleKind::Pattern8) && ctx.in_static_box {
if matches!(rule_id, PlanRuleId::Pattern8) && ctx.in_static_box {
if ctx.debug {
trace::trace().debug(
"route/plan",
&format!(
"{} extracted but rejected: static box context (fallback to legacy)",
entry.name
name
),
);
}
continue;
}
let log_msg = format!("route=plan strategy=extract pattern={}", entry.name);
let log_msg = format!("route=plan strategy=extract pattern={}", name);
trace::trace().pattern("route", &log_msg, true);
return Ok(Some(domain_plan));
} else if log_none && ctx.debug {
let debug_msg =
format!("{} extraction returned None, trying next pattern", entry.name);
let debug_msg = format!("{} extraction returned None, trying next pattern", name);
trace::trace().debug("route", &debug_msg);
}
}
@ -125,69 +91,52 @@ pub(super) fn try_build_domain_plan(ctx: &LoopPatternContext) -> Result<Option<D
Ok(None)
}
#[derive(Debug, Clone, Copy)]
struct RuleEntry {
name: &'static str,
kind: RuleKind,
}
#[derive(Debug, Clone, Copy)]
enum RuleKind {
Pattern1,
Pattern2,
Pattern3,
Pattern4,
Pattern5,
Pattern6,
Pattern7,
Pattern8,
Pattern9,
}
fn try_take_planner(planner_opt: &Option<DomainPlan>, kind: RuleKind) -> Option<DomainPlan> {
fn try_take_planner(planner_opt: &Option<DomainPlan>, kind: PlanRuleId) -> Option<DomainPlan> {
match (kind, planner_opt.as_ref()) {
(RuleKind::Pattern1, Some(DomainPlan::Pattern1SimpleWhile(_))) => planner_opt.clone(),
(RuleKind::Pattern2, Some(DomainPlan::Pattern2Break(_))) => planner_opt.clone(),
(RuleKind::Pattern3, Some(DomainPlan::Pattern3IfPhi(_))) => planner_opt.clone(),
(RuleKind::Pattern4, Some(DomainPlan::Pattern4Continue(_))) => planner_opt.clone(),
(RuleKind::Pattern5, Some(DomainPlan::Pattern5InfiniteEarlyExit(_))) => {
(PlanRuleId::Pattern1, Some(DomainPlan::Pattern1SimpleWhile(_))) => planner_opt.clone(),
(PlanRuleId::Pattern2, Some(DomainPlan::Pattern2Break(_))) => planner_opt.clone(),
(PlanRuleId::Pattern3, Some(DomainPlan::Pattern3IfPhi(_))) => planner_opt.clone(),
(PlanRuleId::Pattern4, Some(DomainPlan::Pattern4Continue(_))) => planner_opt.clone(),
(PlanRuleId::Pattern5, Some(DomainPlan::Pattern5InfiniteEarlyExit(_))) => {
planner_opt.clone()
}
(RuleKind::Pattern6, Some(DomainPlan::ScanWithInit(_))) => planner_opt.clone(),
(RuleKind::Pattern7, Some(DomainPlan::SplitScan(_))) => planner_opt.clone(),
(RuleKind::Pattern8, Some(DomainPlan::Pattern8BoolPredicateScan(_))) => {
(PlanRuleId::Pattern6, Some(DomainPlan::ScanWithInit(_))) => planner_opt.clone(),
(PlanRuleId::Pattern7, Some(DomainPlan::SplitScan(_))) => planner_opt.clone(),
(PlanRuleId::Pattern8, Some(DomainPlan::Pattern8BoolPredicateScan(_))) => {
planner_opt.clone()
}
(PlanRuleId::Pattern9, Some(DomainPlan::Pattern9AccumConstLoop(_))) => {
planner_opt.clone()
}
(RuleKind::Pattern9, Some(DomainPlan::Pattern9AccumConstLoop(_))) => planner_opt.clone(),
_ => None,
}
}
fn fallback_extract(
ctx: &LoopPatternContext,
kind: RuleKind,
kind: PlanRuleId,
) -> Result<Option<DomainPlan>, String> {
match kind {
RuleKind::Pattern1 => extractors::pattern1::extract_pattern1_plan(ctx.condition, ctx.body),
RuleKind::Pattern2 => {
PlanRuleId::Pattern1 => extractors::pattern1::extract_pattern1_plan(ctx.condition, ctx.body),
PlanRuleId::Pattern2 => {
extractors::pattern2_break::extract_pattern2_plan(ctx.condition, ctx.body)
}
RuleKind::Pattern3 => extractors::pattern3::extract_pattern3_plan(ctx.condition, ctx.body),
RuleKind::Pattern4 => extractors::pattern4::extract_pattern4_plan(ctx.condition, ctx.body),
RuleKind::Pattern5 => extractors::pattern5::extract_pattern5_plan(ctx.condition, ctx.body),
RuleKind::Pattern6 => {
PlanRuleId::Pattern3 => extractors::pattern3::extract_pattern3_plan(ctx.condition, ctx.body),
PlanRuleId::Pattern4 => extractors::pattern4::extract_pattern4_plan(ctx.condition, ctx.body),
PlanRuleId::Pattern5 => extractors::pattern5::extract_pattern5_plan(ctx.condition, ctx.body),
PlanRuleId::Pattern6 => {
extractors::pattern6_scan_with_init::extract_scan_with_init_plan(
ctx.condition,
ctx.body,
ctx.fn_body,
)
}
RuleKind::Pattern7 => extractors::pattern7_split_scan::extract_split_scan_plan(
PlanRuleId::Pattern7 => extractors::pattern7_split_scan::extract_split_scan_plan(
ctx.condition,
ctx.body,
&[],
),
RuleKind::Pattern8 => extractors::pattern8::extract_pattern8_plan(ctx.condition, ctx.body),
RuleKind::Pattern9 => extractors::pattern9::extract_pattern9_plan(ctx.condition, ctx.body),
PlanRuleId::Pattern8 => extractors::pattern8::extract_pattern8_plan(ctx.condition, ctx.body),
PlanRuleId::Pattern9 => extractors::pattern9::extract_pattern9_plan(ctx.condition, ctx.body),
}
}