phase29aj(p3): planner-first pattern3 if-phi facts subset

This commit is contained in:
2025-12-29 13:06:17 +09:00
parent 97d5d3ef77
commit 31e4ac8c99
10 changed files with 530 additions and 6 deletions

View File

@ -24,6 +24,9 @@ single_planner の legacy_rules を撤去し、Pattern1/3/4/5/8/9 の抽出を p
**2025-12-29: Phase 29aj P2 COMPLETE (Pattern1 planner-first)**
chosen_rule を撤去し、Pattern1 SimpleWhile の Facts→Planner-first を導入(仕様不変)。
**2025-12-29: Phase 29aj P3 COMPLETE (Pattern3 planner-first)**
Pattern3 If-Phi の Facts→Planner-first を導入し、single_planner の extractor 依存を縮小。
**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 P3TBD: promotion hint を JoinIR 側の orchestrator へ配線、挙動不変)
Next: Phase 29aj P4TBD: promotion hint を JoinIR 側の orchestrator へ配線、挙動不変)
**2025-12-29: Phase 29aj P3 完了**
- 目的: Pattern3If-Phiを Facts→Planner-first に移行(仕様不変)
- 実装: `src/mir/builder/control_flow/plan/facts/pattern3_ifphi_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 P2 完了**
- 目的: chosen_rule を撤去し、Pattern1 を Facts→Planner-first に移行(仕様不変)

View File

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

View File

@ -0,0 +1,87 @@
# Phase 29aj P3: Pattern3 (If-Phi) planner-first via Facts (subset)
Date: 2025-12-29
Status: Ready for execution
Scope: Pattern3 facts → planner candidate → single_planner planner-first仕様不変
Goal: Pattern3 を Facts→Planner に乗せ、extractor 依存を 1 本減らす
## Objective
- Pattern3Loop with If-Else PHIを Facts→Planner 経路に追加
- single_planner は Pattern3 の型一致時のみ planner-first 採用
- 既定挙動・観測・エラー文字列は不変
## Non-goals
- Pattern4/5/8/9 の planner-first 化
- ルール順序 SSOT の CandidateSet 移管
- Freeze/Fail-Fast の新規導入
## Implementation Steps
### Step 1: Facts SSOT 追加Pattern3
Files:
- `src/mir/builder/control_flow/plan/facts/pattern3_ifphi_facts.rs` (new)
Facts:
- `Pattern3IfPhiFacts { loop_var, carrier_var, condition, if_condition, then_update, else_update, loop_increment }`
Extraction rules (Ok(None) fallback only):
- condition は比較演算left が Variable
- if-else が存在し、then/else 両方で同一変数に代入
- return / break / continue / nested-if は Ok(None)
- loop_increment は `extract_loop_increment_plan` で取れるときのみ
### Step 2: LoopFacts に接続
Files:
- `src/mir/builder/control_flow/plan/facts/mod.rs`
- `src/mir/builder/control_flow/plan/facts/loop_facts.rs`
Changes:
- LoopFacts に `pattern3_ifphi` を追加
- `try_build_loop_facts()` に抽出を追加
- all-none 判定に `pattern3_ifphi` を含める
### Step 3: Planner candidate 追加
File:
- `src/mir/builder/control_flow/plan/planner/build.rs`
Changes:
- facts が Some のとき `DomainPlan::Pattern3IfPhi` を候補に追加
- rule 名は `loop/pattern3_ifphi`
- unit test 追加
### Step 4: single_planner を Pattern3 planner-first に
File:
- `src/mir/builder/control_flow/plan/single_planner/rules.rs`
Changes:
- RuleKind::Pattern3 を追加
- planner_opt が `Pattern3IfPhi` のとき採用
- それ以外は extractor へフォールバック
### 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`
## Commit
- `git add -A && git commit -m "phase29aj(p3): planner-first pattern3 if-phi facts subset"`
## Next (P4 candidate)
- Pattern4Continueを Facts→Planner-first に寄せるsubset

View File

@ -23,6 +23,13 @@ Goal: planner の facts/plan を 1 本の outcome に集約し、観測の SSOT
- 完了: 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`
## P3: Pattern3 (If-Phi) planner-firstsubset
- 指示書: `docs/development/current/main/phases/phase-29aj/P3-PATTERN3-IFPHI-PLANNER-FIRST-INSTRUCTIONS.md`
- ねらい: Pattern3 を Facts→Planner-first に接続し、extractor 依存を削減
- 完了: Pattern3 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

@ -16,6 +16,9 @@ use super::scan_shapes::LengthMethod;
use super::pattern1_simplewhile_facts::{
Pattern1SimpleWhileFacts, try_extract_pattern1_simplewhile_facts,
};
use super::pattern3_ifphi_facts::{
Pattern3IfPhiFacts, try_extract_pattern3_ifphi_facts,
};
use super::pattern2_break_facts::{Pattern2BreakFacts, try_extract_pattern2_break_facts};
use super::pattern2_loopbodylocal_facts::{
Pattern2LoopBodyLocalFacts, try_extract_pattern2_loopbodylocal_facts,
@ -28,6 +31,7 @@ pub(in crate::mir::builder) struct LoopFacts {
pub scan_with_init: Option<ScanWithInitFacts>,
pub split_scan: Option<SplitScanFacts>,
pub pattern1_simplewhile: Option<Pattern1SimpleWhileFacts>,
pub pattern3_ifphi: Option<Pattern3IfPhiFacts>,
pub pattern2_break: Option<Pattern2BreakFacts>,
pub pattern2_loopbodylocal: Option<Pattern2LoopBodyLocalFacts>,
}
@ -62,12 +66,14 @@ pub(in crate::mir::builder) fn try_build_loop_facts(
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 pattern3_ifphi = try_extract_pattern3_ifphi_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()
&& pattern3_ifphi.is_none()
&& pattern2_break.is_none()
&& pattern2_loopbodylocal.is_none()
{
@ -80,6 +86,7 @@ pub(in crate::mir::builder) fn try_build_loop_facts(
scan_with_init,
split_scan,
pattern1_simplewhile,
pattern3_ifphi,
pattern2_break,
pattern2_loopbodylocal,
}))

View File

@ -8,6 +8,7 @@
pub(in crate::mir::builder) mod loop_facts;
pub(in crate::mir::builder) mod pattern1_simplewhile_facts;
pub(in crate::mir::builder) mod pattern3_ifphi_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,321 @@
//! Phase 29aj P3: Pattern3IfPhiFacts (Facts SSOT)
use crate::ast::ASTNode;
use crate::mir::builder::control_flow::plan::extractors::common_helpers::{
extract_loop_increment_plan, find_if_else_statement,
};
use crate::mir::builder::control_flow::plan::extractors::pattern3::extract_loop_with_if_phi_parts;
use crate::mir::builder::control_flow::plan::planner::Freeze;
#[derive(Debug, Clone)]
pub(in crate::mir::builder) struct Pattern3IfPhiFacts {
pub loop_var: String,
pub carrier_var: String,
pub condition: ASTNode,
pub if_condition: ASTNode,
pub then_update: ASTNode,
pub else_update: ASTNode,
pub loop_increment: ASTNode,
}
pub(in crate::mir::builder) fn try_extract_pattern3_ifphi_facts(
condition: &ASTNode,
body: &[ASTNode],
) -> Result<Option<Pattern3IfPhiFacts>, Freeze> {
let parts = match extract_loop_with_if_phi_parts(condition, body) {
Ok(Some(parts)) => parts,
Ok(None) => return Ok(None),
Err(_) => return Ok(None),
};
let if_stmt = match find_if_else_statement(body) {
Some(stmt) => stmt,
None => return Ok(None),
};
let (if_condition, then_update, else_update) = match if_stmt {
ASTNode::If {
condition: if_cond,
then_body,
else_body: Some(else_body),
..
} => {
let then_update = match extract_single_update(then_body, &parts.merged_var) {
Some(update) => update,
None => return Ok(None),
};
let else_update = match extract_single_update(else_body, &parts.merged_var) {
Some(update) => update,
None => return Ok(None),
};
(if_cond.as_ref().clone(), then_update, else_update)
}
_ => return Ok(None),
};
let loop_increment = match extract_loop_increment_plan(body, &parts.loop_var) {
Ok(Some(inc)) => inc,
_ => return Ok(None),
};
Ok(Some(Pattern3IfPhiFacts {
loop_var: parts.loop_var,
carrier_var: parts.merged_var,
condition: condition.clone(),
if_condition,
then_update,
else_update,
loop_increment,
}))
}
fn extract_single_update(body: &[ASTNode], carrier_var: &str) -> Option<ASTNode> {
for stmt in body {
if let ASTNode::Assignment { target, value, .. } = stmt {
if let ASTNode::Variable { name, .. } = target.as_ref() {
if name == carrier_var {
return Some(value.as_ref().clone());
}
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::{BinaryOperator, LiteralValue, Span};
fn v(name: &str) -> ASTNode {
ASTNode::Variable {
name: name.to_string(),
span: Span::unknown(),
}
}
fn lit_int(value: i64) -> ASTNode {
ASTNode::Literal {
value: LiteralValue::Integer(value),
span: Span::unknown(),
}
}
fn assign(name: &str, value: ASTNode) -> ASTNode {
ASTNode::Assignment {
target: Box::new(v(name)),
value: Box::new(value),
span: Span::unknown(),
}
}
fn if_else(cond: ASTNode, then_body: Vec<ASTNode>, else_body: Vec<ASTNode>) -> ASTNode {
ASTNode::If {
condition: Box::new(cond),
then_body,
else_body: Some(else_body),
span: Span::unknown(),
}
}
fn loop_condition() -> ASTNode {
ASTNode::BinaryOp {
operator: BinaryOperator::Less,
left: Box::new(v("i")),
right: Box::new(lit_int(3)),
span: Span::unknown(),
}
}
fn loop_increment() -> ASTNode {
ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left: Box::new(v("i")),
right: Box::new(lit_int(1)),
span: Span::unknown(),
}
}
#[test]
fn facts_extracts_pattern3_ifphi_success() {
let if_stmt = if_else(
ASTNode::BinaryOp {
operator: BinaryOperator::Greater,
left: Box::new(v("i")),
right: Box::new(lit_int(0)),
span: Span::unknown(),
},
vec![assign(
"sum",
ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left: Box::new(v("sum")),
right: Box::new(lit_int(1)),
span: Span::unknown(),
},
)],
vec![assign(
"sum",
ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left: Box::new(v("sum")),
right: Box::new(lit_int(0)),
span: Span::unknown(),
},
)],
);
let body = vec![if_stmt, assign("i", loop_increment())];
let facts = try_extract_pattern3_ifphi_facts(&loop_condition(), &body).expect("Ok");
let facts = facts.expect("Some");
assert_eq!(facts.loop_var, "i");
assert_eq!(facts.carrier_var, "sum");
}
#[test]
fn facts_rejects_if_without_else() {
let if_stmt = ASTNode::If {
condition: Box::new(ASTNode::BinaryOp {
operator: BinaryOperator::Greater,
left: Box::new(v("i")),
right: Box::new(lit_int(0)),
span: Span::unknown(),
}),
then_body: vec![assign(
"sum",
ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left: Box::new(v("sum")),
right: Box::new(lit_int(1)),
span: Span::unknown(),
},
)],
else_body: None,
span: Span::unknown(),
};
let body = vec![if_stmt, assign("i", loop_increment())];
let facts = try_extract_pattern3_ifphi_facts(&loop_condition(), &body).expect("Ok");
assert!(facts.is_none());
}
#[test]
fn facts_rejects_mismatched_carrier_vars() {
let if_stmt = if_else(
ASTNode::BinaryOp {
operator: BinaryOperator::Greater,
left: Box::new(v("i")),
right: Box::new(lit_int(0)),
span: Span::unknown(),
},
vec![assign(
"sum",
ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left: Box::new(v("sum")),
right: Box::new(lit_int(1)),
span: Span::unknown(),
},
)],
vec![assign(
"acc",
ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left: Box::new(v("acc")),
right: Box::new(lit_int(1)),
span: Span::unknown(),
},
)],
);
let body = vec![if_stmt, assign("i", loop_increment())];
let facts = try_extract_pattern3_ifphi_facts(&loop_condition(), &body).expect("Ok");
assert!(facts.is_none());
}
#[test]
fn facts_rejects_break_continue_or_return() {
let if_stmt = if_else(
ASTNode::BinaryOp {
operator: BinaryOperator::Greater,
left: Box::new(v("i")),
right: Box::new(lit_int(0)),
span: Span::unknown(),
},
vec![ASTNode::Break { span: Span::unknown() }],
vec![assign(
"sum",
ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left: Box::new(v("sum")),
right: Box::new(lit_int(1)),
span: Span::unknown(),
},
)],
);
let body = vec![
if_stmt,
ASTNode::Return {
value: Some(Box::new(lit_int(0))),
span: Span::unknown(),
},
assign("i", loop_increment()),
];
let facts = try_extract_pattern3_ifphi_facts(&loop_condition(), &body).expect("Ok");
assert!(facts.is_none());
}
#[test]
fn facts_rejects_nested_if() {
let nested_if = if_else(
ASTNode::BinaryOp {
operator: BinaryOperator::Greater,
left: Box::new(v("i")),
right: Box::new(lit_int(0)),
span: Span::unknown(),
},
vec![assign(
"sum",
ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left: Box::new(v("sum")),
right: Box::new(lit_int(1)),
span: Span::unknown(),
},
)],
vec![assign(
"sum",
ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left: Box::new(v("sum")),
right: Box::new(lit_int(0)),
span: Span::unknown(),
},
)],
);
let if_stmt = if_else(
ASTNode::BinaryOp {
operator: BinaryOperator::Greater,
left: Box::new(v("i")),
right: Box::new(lit_int(0)),
span: Span::unknown(),
},
vec![nested_if],
vec![assign(
"sum",
ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left: Box::new(v("sum")),
right: Box::new(lit_int(1)),
span: Span::unknown(),
},
)],
);
let body = vec![if_stmt, assign("i", loop_increment())];
let facts = try_extract_pattern3_ifphi_facts(&loop_condition(), &body).expect("Ok");
assert!(facts.is_none());
}
}

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, Pattern1SimpleWhilePlan, Pattern2BreakPlan, Pattern2PromotionHint, ScanDirection,
ScanWithInitPlan, SplitScanPlan,
DomainPlan, Pattern1SimpleWhilePlan, Pattern2BreakPlan, Pattern2PromotionHint, Pattern3IfPhiPlan,
ScanDirection, ScanWithInitPlan, SplitScanPlan,
};
/// Phase 29ai P0: External-ish SSOT entrypoint (skeleton)
@ -88,6 +88,21 @@ pub(in crate::mir::builder) fn build_plan_from_facts(
});
}
if let Some(pattern3) = &facts.facts.pattern3_ifphi {
candidates.push(PlanCandidate {
plan: DomainPlan::Pattern3IfPhi(Pattern3IfPhiPlan {
loop_var: pattern3.loop_var.clone(),
carrier_var: pattern3.carrier_var.clone(),
condition: pattern3.condition.clone(),
if_condition: pattern3.if_condition.clone(),
then_update: pattern3.then_update.clone(),
else_update: pattern3.else_update.clone(),
loop_increment: pattern3.loop_increment.clone(),
}),
rule: "loop/pattern3_ifphi",
});
}
if let Some(pattern1) = &facts.facts.pattern1_simplewhile {
candidates.push(PlanCandidate {
plan: DomainPlan::Pattern1SimpleWhile(Pattern1SimpleWhilePlan {
@ -114,6 +129,9 @@ mod tests {
use crate::mir::builder::control_flow::plan::facts::pattern1_simplewhile_facts::{
Pattern1SimpleWhileFacts,
};
use crate::mir::builder::control_flow::plan::facts::pattern3_ifphi_facts::{
Pattern3IfPhiFacts,
};
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,
@ -150,6 +168,7 @@ mod tests {
start_var: "start".to_string(),
}),
pattern1_simplewhile: None,
pattern3_ifphi: None,
pattern2_break: None,
pattern2_loopbodylocal: None,
};
@ -176,6 +195,7 @@ mod tests {
scan_with_init: None,
split_scan: None,
pattern1_simplewhile: None,
pattern3_ifphi: None,
pattern2_break: None,
pattern2_loopbodylocal: None,
};
@ -197,6 +217,7 @@ mod tests {
}),
split_scan: None,
pattern1_simplewhile: None,
pattern3_ifphi: None,
pattern2_break: None,
pattern2_loopbodylocal: None,
};
@ -235,6 +256,7 @@ mod tests {
condition: loop_condition.clone(),
loop_increment: loop_increment.clone(),
}),
pattern3_ifphi: None,
pattern2_break: None,
pattern2_loopbodylocal: None,
};
@ -249,6 +271,69 @@ mod tests {
}
}
#[test]
fn planner_builds_pattern3_ifphi_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 if_condition = ASTNode::BinaryOp {
operator: BinaryOperator::Greater,
left: Box::new(v("i")),
right: Box::new(lit_int(0)),
span: Span::unknown(),
};
let then_update = ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left: Box::new(v("sum")),
right: Box::new(lit_int(1)),
span: Span::unknown(),
};
let else_update = ASTNode::BinaryOp {
operator: BinaryOperator::Add,
left: Box::new(v("sum")),
right: Box::new(lit_int(0)),
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: None,
pattern3_ifphi: Some(Pattern3IfPhiFacts {
loop_var: "i".to_string(),
carrier_var: "sum".to_string(),
condition: loop_condition.clone(),
if_condition: if_condition.clone(),
then_update: then_update.clone(),
else_update: else_update.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::Pattern3IfPhi(plan)) => {
assert_eq!(plan.loop_var, "i");
assert_eq!(plan.carrier_var, "sum");
}
other => panic!("expected pattern3 if-phi plan, got {:?}", other),
}
}
#[test]
fn planner_sets_promotion_hint_for_pattern2_loopbodylocal() {
let loop_condition = ASTNode::BinaryOp {
@ -285,6 +370,7 @@ mod tests {
scan_with_init: None,
split_scan: None,
pattern1_simplewhile: None,
pattern3_ifphi: None,
pattern2_break: Some(Pattern2BreakFacts {
loop_var: "i".to_string(),
carrier_var: "sum".to_string(),

View File

@ -44,7 +44,7 @@ pub(super) fn try_build_domain_plan(ctx: &LoopPatternContext) -> Result<Option<D
},
RuleEntry {
name: "Pattern3_IfPhi (Phase 286 P2.6)",
kind: RuleKind::Simple(extractors::pattern3::extract_pattern3_plan),
kind: RuleKind::Pattern3,
},
RuleEntry {
name: "Pattern4_Continue (Phase 286 P2)",
@ -110,6 +110,12 @@ pub(super) fn try_build_domain_plan(ctx: &LoopPatternContext) -> Result<Option<D
),
}
}
RuleKind::Pattern3 => {
match planner_opt.as_ref() {
Some(DomainPlan::Pattern3IfPhi(_)) => (planner_opt.clone(), false),
_ => (extractors::pattern3::extract_pattern3_plan(ctx.condition, ctx.body)?, true),
}
}
RuleKind::Pattern1 => {
match planner_opt.as_ref() {
Some(DomainPlan::Pattern1SimpleWhile(_)) => (planner_opt.clone(), false),
@ -175,5 +181,6 @@ enum RuleKind {
Pattern6,
Pattern7,
Pattern2,
Pattern3,
Pattern1,
}