phase29aj(p2): planner-first pattern1 simplewhile subset

This commit is contained in:
2025-12-29 12:54:32 +09:00
parent ff3af90b4c
commit 97d5d3ef77
12 changed files with 274 additions and 11 deletions

View File

@ -21,6 +21,9 @@ planner outcomefacts+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)**
Pattern61-level nested loopの JoinIR→bridge→merge 経路で発生していた `undefined ValueId``vm step budget exceeded`(無限ループ)を解消。`apps/tests/phase1883_nested_minimal.hako` が RC=9 を返し、quick 154 PASS を維持。

View File

@ -2,7 +2,12 @@
## Current Focus: Phase 29ajPlannerOutcome SSOT
Next: Phase 29aj P2TBD: promotion hint を JoinIR 側の orchestrator へ配線、挙動不変)
Next: Phase 29aj P3TBD: 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 に集約(仕様不変)

View File

@ -19,8 +19,8 @@ Related:
- **Phase 29ajcandidate: PlannerOutcome observability SSOT**
- 入口: `docs/development/current/main/phases/phase-29aj/README.md`
- 状況: P0/P1 ✅ 完了
- Next: Phase 29aj P2TBD: promotion hint を JoinIR 側の orchestrator へ配線、挙動不変)
- 状況: P0/P1/P2 ✅ 完了
- Next: Phase 29aj P3TBD: promotion hint を JoinIR 側の orchestrator へ配線、挙動不変)
- **Phase 29aicandidate: Plan/Frag single-plannerFacts SSOT**
- 入口: `docs/development/current/main/phases/phase-29ai/README.md`

View File

@ -32,7 +32,6 @@ Goal: facts の直抽出を撤去し、strict/dev 観測が planner outcome の
構造(例):
- `facts: Option<CanonicalLoopFacts>`
- `plan: Option<DomainPlan>`
- `chosen_rule: Option<&'static str>`(任意。未使用なら None
## Implementation Steps

View File

@ -0,0 +1,82 @@
# Phase 29aj P2: chosen_rule 撤去 + Pattern1 planner-firstsubset
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 を引き締める
- Pattern1SimpleWhileを 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: `<var> < <int_lit>` のみ
- body: break/continue/return なし
- if-else 禁止if は許可)
- loop_increment: `var = var + <int_lit>`PoC は `<int_lit> == 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"`

View File

@ -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-firstsubset
- 指示書: `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`

View File

@ -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<ScanWithInitFacts>,
pub split_scan: Option<SplitScanFacts>,
pub pattern1_simplewhile: Option<Pattern1SimpleWhileFacts>,
pub pattern2_break: Option<Pattern2BreakFacts>,
pub pattern2_loopbodylocal: Option<Pattern2LoopBodyLocalFacts>,
}
@ -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,
}))

View File

@ -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;

View File

@ -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<Option<Pattern1SimpleWhileFacts>, 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<String> {
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),
..
}
)
}

View File

@ -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(),

View File

@ -14,8 +14,6 @@ use super::Freeze;
pub(in crate::mir::builder) struct PlanBuildOutcome {
pub facts: Option<CanonicalLoopFacts>,
pub plan: Option<DomainPlan>,
#[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,
})
}

View File

@ -60,7 +60,7 @@ pub(super) fn try_build_domain_plan(ctx: &LoopPatternContext) -> Result<Option<D
},
RuleEntry {
name: "Pattern1_SimpleWhile (Phase 286 P2.1)",
kind: RuleKind::Simple(extractors::pattern1::extract_pattern1_plan),
kind: RuleKind::Pattern1,
},
];
@ -110,6 +110,12 @@ pub(super) fn try_build_domain_plan(ctx: &LoopPatternContext) -> Result<Option<D
),
}
}
RuleKind::Pattern1 => {
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,
}