feat(joinir): Phase 286 P2/P2.1/P2.2 - JoinIR Line Absorption (Pattern1/4 Plan化 PoC + hygiene)

Phase 286 P2: Pattern4 (Loop with Continue) を Plan/Frag SSOT に移行
- DomainPlan::Pattern4Continue 追加
- PlanNormalizer::normalize_pattern4_continue() 実装(phi_bindings による PHI dst 優先参照)
- Router integration(Plan line routing → legacy fallback)
- Integration test PASS (output: 6), quick smoke 154/154 PASS

Phase 286 P2.1: Pattern1 (SimpleWhile) を Plan/Frag SSOT に移行
- DomainPlan::Pattern1SimpleWhile 追加
- PlanNormalizer::normalize_pattern1_simple_while() 実装(4ブロック、1 PHI、phi_bindings 流用)
- Router integration(Plan line routing → legacy fallback)
- Integration test PASS (return: 3), quick smoke 154/154 PASS

Phase 286 P2.2: hygiene(extractor重複排除 + router小整理)
- extractor helper化: extract_loop_increment_plan を common_helpers.rs に統一
  - Pattern1/Pattern4 が呼ぶだけに変更(重複排除 ~25行)
- router helper化: lower_via_plan() を追加し Pattern6/7/4/1 で共用
  - 3行パターン(normalize→verify→lower)を1関数に集約(ボイラープレート削減 ~40行)

成果物:
- DomainPlan 2パターン新規追加(Pattern1SimpleWhile, Pattern4Continue)
- Normalizer 2つの normalize 関数追加
- Router に Plan line ブロック追加 + lower_via_plan() helper
- Extractor に extract_pattern1_plan() 追加
- Integration fixtures 2個 + smoke tests 2個

検証:
- quick smoke: 154/154 PASS
- integration: phase286_pattern1_frag_poc PASS, phase286_pattern4_frag_poc PASS
- Plan line routing: route=plan strategy=extract で Pattern1/4 検出確認

🤖 Generated with Claude Code

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2025-12-26 02:03:22 +09:00
parent 55d30c9845
commit a824346e30
11 changed files with 1108 additions and 22 deletions

View File

@ -45,7 +45,10 @@ pub(in crate::mir::builder) enum DomainPlan {
ScanWithInit(ScanWithInitPlan),
/// Pattern7: split / tokenization scan
SplitScan(SplitScanPlan),
// P2+: BoolPredicate(BoolPredicatePlan), etc.
/// Pattern4: Loop with Continue (Phase 286 P2)
Pattern4Continue(Pattern4ContinuePlan),
/// Pattern1: Simple While Loop (Phase 286 P2.1)
Pattern1SimpleWhile(Pattern1SimpleWhilePlan),
}
/// Phase 273 P0: Scan direction for forward/reverse scan
@ -98,6 +101,39 @@ pub(in crate::mir::builder) struct SplitScanPlan {
pub start_var: String,
}
/// Phase 286 P2: Extracted structure for Pattern4 (Loop with Continue)
///
/// This structure contains all the information needed to lower a continue-style loop.
#[derive(Debug, Clone)]
pub(in crate::mir::builder) struct Pattern4ContinuePlan {
/// Loop variable name (e.g., "i")
pub loop_var: String,
/// Carrier variable names (e.g., ["sum"])
pub carrier_vars: Vec<String>,
/// Loop condition AST (e.g., `i < 6`)
pub condition: ASTNode,
/// Continue condition AST (e.g., `i == 0`)
pub continue_condition: ASTNode,
/// Carrier update expressions (var -> update AST)
pub carrier_updates: std::collections::BTreeMap<String, ASTNode>,
/// Loop increment expression (e.g., `i + 1`)
pub loop_increment: ASTNode,
}
/// Phase 286 P2.1: Extracted structure for Pattern1 (Simple While Loop)
///
/// This structure contains all the information needed to lower a simple while loop.
/// Pattern1 is the simplest loop: no break, no continue, no if-else-phi.
#[derive(Debug, Clone)]
pub(in crate::mir::builder) struct Pattern1SimpleWhilePlan {
/// Loop variable name (e.g., "i")
pub loop_var: String,
/// Loop condition AST (e.g., `i < 3`)
pub condition: ASTNode,
/// Loop increment expression AST (e.g., `i + 1`)
pub loop_increment: ASTNode,
}
// ============================================================================
// CorePlan (固定語彙 - 構造ノードのみ)
// ============================================================================

View File

@ -13,7 +13,7 @@
use super::{
CoreEffectPlan, CoreLoopPlan, CorePhiInfo, CorePlan, DomainPlan,
ScanWithInitPlan, SplitScanPlan,
ScanWithInitPlan, SplitScanPlan, Pattern1SimpleWhilePlan,
};
use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext;
use crate::mir::builder::MirBuilder;
@ -39,6 +39,8 @@ impl PlanNormalizer {
match domain {
DomainPlan::ScanWithInit(parts) => Self::normalize_scan_with_init(builder, parts, ctx),
DomainPlan::SplitScan(parts) => Self::normalize_split_scan(builder, parts, ctx),
DomainPlan::Pattern4Continue(parts) => Self::normalize_pattern4_continue(builder, parts, ctx),
DomainPlan::Pattern1SimpleWhile(parts) => Self::normalize_pattern1_simple_while(builder, parts, ctx),
}
}
@ -856,4 +858,610 @@ impl PlanNormalizer {
Ok(CorePlan::Loop(loop_plan))
}
/// Phase 286 P2: Pattern4Continue → CorePlan 変換
///
/// Expands Pattern4 (Loop with Continue) semantics into generic CorePlan:
/// - CFG structure: 2-step branching + header PHI merge
/// - NO Select instruction (not in CoreEffectPlan)
/// - NO after PHI (header PHI handles all paths)
fn normalize_pattern4_continue(
builder: &mut MirBuilder,
parts: crate::mir::builder::control_flow::plan::Pattern4ContinuePlan,
ctx: &LoopPatternContext,
) -> Result<CorePlan, String> {
use crate::mir::builder::control_flow::joinir::trace;
let trace_logger = trace::trace();
let debug = ctx.debug;
if debug {
trace_logger.debug(
"normalizer/pattern4_continue",
&format!(
"Phase 286 P2: Normalizing Pattern4Continue for {} (loop_var: {}, carriers: {:?})",
ctx.func_name, parts.loop_var, parts.carrier_vars
),
);
}
// P2 PoC Scope: Single carrier only
if parts.carrier_vars.len() != 1 {
return Err(format!(
"[normalizer] P2 PoC scope: only single carrier supported (found: {})",
parts.carrier_vars.len()
));
}
let carrier_var = &parts.carrier_vars[0];
// Step 1: Get host ValueIds for variables
let loop_var_init = builder
.variable_ctx
.variable_map
.get(&parts.loop_var)
.copied()
.ok_or_else(|| format!("[normalizer] Loop variable {} not found", parts.loop_var))?;
let carrier_init = builder
.variable_ctx
.variable_map
.get(carrier_var)
.copied()
.ok_or_else(|| format!("[normalizer] Carrier variable {} not found", carrier_var))?;
// Step 2: Capture preheader block
let preheader_bb = builder
.current_block
.ok_or_else(|| "[normalizer] No current block for loop entry".to_string())?;
// Step 3: Allocate BasicBlockIds for 8 blocks (2-step branching architecture)
let header_bb = builder.next_block_id();
let body_bb = builder.next_block_id();
let continue_path_bb = builder.next_block_id();
let normal_path_bb = builder.next_block_id();
let step_continue_bb = builder.next_block_id();
let step_normal_bb = builder.next_block_id();
let after_bb = builder.next_block_id();
if debug {
trace_logger.debug(
"normalizer/pattern4_continue",
&format!(
"Allocated: preheader={:?}, header={:?}, body={:?}, continue_path={:?}, normal_path={:?}, step_continue={:?}, step_normal={:?}, after={:?}",
preheader_bb, header_bb, body_bb, continue_path_bb, normal_path_bb, step_continue_bb, step_normal_bb, after_bb
),
);
}
// Step 4: Allocate ValueIds for loop control and carriers
// Loop variable PHI dst (header PHI)
let loop_var_current = builder.alloc_typed(MirType::Integer);
// Carrier PHI dst (header PHI)
let carrier_current = builder.alloc_typed(MirType::Integer);
// Step 4.5: Build phi_bindings - PHI dst takes precedence over variable_map
// This ensures loop condition/body reference PHI dst, not initial values
let mut phi_bindings: BTreeMap<String, crate::mir::ValueId> = BTreeMap::new();
phi_bindings.insert(parts.loop_var.clone(), loop_var_current);
phi_bindings.insert(carrier_var.clone(), carrier_current);
// Loop condition value
let cond_loop = builder.alloc_typed(MirType::Bool);
// Continue condition value
let cond_continue = builder.alloc_typed(MirType::Bool);
// Loop increment values (continue path)
let loop_var_cont_next = builder.alloc_typed(MirType::Integer);
// Carrier update value (normal path)
let carrier_updated = builder.alloc_typed(MirType::Integer);
// Loop increment values (normal path)
let loop_var_norm_next = builder.alloc_typed(MirType::Integer);
// Step 5: Lower AST expressions to get operands and const definitions
// Pass phi_bindings to ensure loop variables reference PHI dst
// Lower loop condition (e.g., `i < 6`)
let (loop_cond_lhs, loop_cond_op, loop_cond_rhs, loop_cond_consts) =
Self::lower_compare_ast(&parts.condition, builder, &phi_bindings)?;
// Lower continue condition (e.g., `i == 0`)
let (cont_cond_lhs, cont_cond_op, cont_cond_rhs, cont_cond_consts) =
Self::lower_compare_ast(&parts.continue_condition, builder, &phi_bindings)?;
// Lower carrier update expression (e.g., `sum = sum + i`)
let carrier_update_ast = parts.carrier_updates.get(carrier_var)
.ok_or_else(|| format!("[normalizer] Carrier update for {} not found", carrier_var))?;
let (carrier_update_lhs, carrier_update_op, carrier_update_rhs, carrier_update_consts) =
Self::lower_binop_ast(carrier_update_ast, builder, &phi_bindings)?;
// Lower loop increment (e.g., `i = i + 1`)
let (loop_inc_lhs, loop_inc_op, loop_inc_rhs, loop_inc_consts) =
Self::lower_binop_ast(&parts.loop_increment, builder, &phi_bindings)?;
// Step 6: Build header_effects (const definitions + loop condition check)
let mut header_effects = loop_cond_consts;
header_effects.push(CoreEffectPlan::Compare {
dst: cond_loop,
lhs: loop_cond_lhs,
op: loop_cond_op,
rhs: loop_cond_rhs,
});
// Step 7: Build body (as CorePlan items, NOT block_effects)
let mut body = Vec::new();
// Add const definitions
for const_effect in cont_cond_consts {
body.push(CorePlan::Effect(const_effect));
}
// Add continue condition check
body.push(CorePlan::Effect(CoreEffectPlan::Compare {
dst: cond_continue,
lhs: cont_cond_lhs,
op: cont_cond_op,
rhs: cont_cond_rhs,
}));
// Step 8: Build continue_path effects (const definitions + increment loop var + passthrough carrier)
let mut continue_path_effects = loop_inc_consts.clone();
continue_path_effects.push(CoreEffectPlan::BinOp {
dst: loop_var_cont_next,
lhs: loop_inc_lhs,
op: loop_inc_op,
rhs: loop_inc_rhs,
});
// Step 8.5: step_continue has NO effects
// Carrier passthrough: carrier PHI input directly references carrier_current
// No Add 0 workaround needed - PHI inputs can reference the PHI dst from another path
// Step 9: Build normal_path effects (const definitions + carrier update + loop increment)
let mut normal_path_effects = carrier_update_consts;
normal_path_effects.push(CoreEffectPlan::BinOp {
dst: carrier_updated,
lhs: carrier_update_lhs,
op: carrier_update_op,
rhs: carrier_update_rhs,
});
normal_path_effects.extend(loop_inc_consts);
normal_path_effects.push(CoreEffectPlan::BinOp {
dst: loop_var_norm_next,
lhs: loop_inc_lhs,
op: loop_inc_op,
rhs: loop_inc_rhs,
});
// Step 10: Build block_effects
let block_effects = vec![
(preheader_bb, vec![]),
(header_bb, header_effects),
(body_bb, vec![]), // Body effects are in body CorePlan field
(continue_path_bb, continue_path_effects),
(normal_path_bb, normal_path_effects),
(step_continue_bb, vec![]), // No effects - carrier passthrough is handled by PHI
(step_normal_bb, vec![]),
];
// Step 11: Build PHIs (header PHI with 3 inputs for 2-step branching)
let phis = vec![
// Loop variable PHI: (preheader, init), (step_continue, cont), (step_normal, norm)
CorePhiInfo {
block: header_bb,
dst: loop_var_current,
inputs: vec![
(preheader_bb, loop_var_init),
(step_continue_bb, loop_var_cont_next),
(step_normal_bb, loop_var_norm_next),
],
tag: format!("loop_var_{}", parts.loop_var),
},
// Carrier PHI: (preheader, init), (step_continue, current), (step_normal, updated)
// Continue path: carrier unchanged - directly reference carrier_current (no intermediate value)
CorePhiInfo {
block: header_bb,
dst: carrier_current,
inputs: vec![
(preheader_bb, carrier_init),
(step_continue_bb, carrier_current), // Continue path: carrier unchanged (self-reference OK)
(step_normal_bb, carrier_updated), // Normal path: carrier updated
],
tag: format!("carrier_{}", carrier_var),
},
];
// Step 12: Build Frag (2-step branching structure)
let empty_args = EdgeArgs {
layout: JumpArgsLayout::CarriersOnly,
values: vec![],
};
let branches = vec![
// Header: loop condition check
BranchStub {
from: header_bb,
cond: cond_loop,
then_target: body_bb,
then_args: empty_args.clone(),
else_target: after_bb,
else_args: empty_args.clone(),
},
// Body: continue condition check (2-step branching starts here)
BranchStub {
from: body_bb,
cond: cond_continue,
then_target: continue_path_bb, // Continue path
then_args: empty_args.clone(),
else_target: normal_path_bb, // Normal path
else_args: empty_args.clone(),
},
];
let wires = vec![
// Continue path: continue_path → step_continue → header
EdgeStub {
from: continue_path_bb,
kind: ExitKind::Normal,
target: Some(step_continue_bb),
args: empty_args.clone(),
},
EdgeStub {
from: step_continue_bb,
kind: ExitKind::Normal,
target: Some(header_bb),
args: empty_args.clone(),
},
// Normal path: normal_path → step_normal → header
EdgeStub {
from: normal_path_bb,
kind: ExitKind::Normal,
target: Some(step_normal_bb),
args: empty_args.clone(),
},
EdgeStub {
from: step_normal_bb,
kind: ExitKind::Normal,
target: Some(header_bb),
args: empty_args.clone(),
},
];
let frag = Frag {
entry: header_bb,
exits: BTreeMap::new(),
wires,
branches,
};
// Step 13: Build final_values
let final_values = vec![
(parts.loop_var.clone(), loop_var_current),
(carrier_var.clone(), carrier_current),
];
// Step 14: Build CoreLoopPlan
let loop_plan = CoreLoopPlan {
preheader_bb,
header_bb,
body_bb,
step_bb: step_normal_bb, // Use step_normal as primary step block
after_bb,
found_bb: after_bb, // No early exit, so found = after
body, // Body CorePlan with continue condition check
cond_loop,
cond_match: cond_continue, // Use continue condition as match condition
block_effects,
phis,
frag,
final_values,
};
if debug {
trace_logger.debug(
"normalizer/pattern4_continue",
"CorePlan construction complete (2-step branching with header PHI merge)",
);
}
Ok(CorePlan::Loop(loop_plan))
}
/// Helper: Lower Compare AST to (lhs ValueId, op, rhs ValueId, const_effects)
fn lower_compare_ast(
ast: &crate::ast::ASTNode,
builder: &mut MirBuilder,
phi_bindings: &BTreeMap<String, crate::mir::ValueId>,
) -> Result<(crate::mir::ValueId, CompareOp, crate::mir::ValueId, Vec<CoreEffectPlan>), String> {
use crate::ast::{ASTNode, BinaryOperator};
match ast {
ASTNode::BinaryOp { operator, left, right, .. } => {
let op = match operator {
BinaryOperator::Less => CompareOp::Lt,
BinaryOperator::LessEqual => CompareOp::Le,
BinaryOperator::Greater => CompareOp::Gt,
BinaryOperator::GreaterEqual => CompareOp::Ge,
BinaryOperator::Equal => CompareOp::Eq,
BinaryOperator::NotEqual => CompareOp::Ne,
_ => return Err(format!("[normalizer] Unsupported compare operator: {:?}", operator)),
};
let (lhs, mut lhs_consts) = Self::lower_value_ast(left, builder, phi_bindings)?;
let (rhs, rhs_consts) = Self::lower_value_ast(right, builder, phi_bindings)?;
lhs_consts.extend(rhs_consts);
Ok((lhs, op, rhs, lhs_consts))
}
_ => Err(format!("[normalizer] Expected BinaryOp for compare, got: {:?}", ast)),
}
}
/// Helper: Lower BinOp AST to (lhs ValueId, op, rhs ValueId, const_effects)
fn lower_binop_ast(
ast: &crate::ast::ASTNode,
builder: &mut MirBuilder,
phi_bindings: &BTreeMap<String, crate::mir::ValueId>,
) -> Result<(crate::mir::ValueId, BinaryOp, crate::mir::ValueId, Vec<CoreEffectPlan>), String> {
use crate::ast::{ASTNode, BinaryOperator};
match ast {
ASTNode::BinaryOp { operator, left, right, .. } => {
let op = match operator {
BinaryOperator::Add => BinaryOp::Add,
BinaryOperator::Subtract => BinaryOp::Sub,
BinaryOperator::Multiply => BinaryOp::Mul,
BinaryOperator::Divide => BinaryOp::Div,
_ => return Err(format!("[normalizer] Unsupported binary operator: {:?}", operator)),
};
let (lhs, mut lhs_consts) = Self::lower_value_ast(left, builder, phi_bindings)?;
let (rhs, rhs_consts) = Self::lower_value_ast(right, builder, phi_bindings)?;
lhs_consts.extend(rhs_consts);
Ok((lhs, op, rhs, lhs_consts))
}
_ => Err(format!("[normalizer] Expected BinOp, got: {:?}", ast)),
}
}
/// Helper: Lower value AST to (ValueId, const_effects)
/// Returns the ValueId and any Const instructions needed to define literals
///
/// phi_bindings: PHI dst for loop variables (takes precedence over variable_map)
fn lower_value_ast(
ast: &crate::ast::ASTNode,
builder: &mut MirBuilder,
phi_bindings: &BTreeMap<String, crate::mir::ValueId>,
) -> Result<(crate::mir::ValueId, Vec<CoreEffectPlan>), String> {
use crate::ast::{ASTNode, LiteralValue};
match ast {
ASTNode::Variable { name, .. } => {
// PHI bindings take precedence (for loop variables in header/body)
if let Some(&phi_dst) = phi_bindings.get(name) {
return Ok((phi_dst, vec![]));
}
// Fallback to variable_map (for pre-loop init values)
if let Some(&value_id) = builder.variable_ctx.variable_map.get(name) {
Ok((value_id, vec![]))
} else {
Err(format!("[normalizer] Variable {} not found", name))
}
}
ASTNode::Literal { value, .. } => {
// Allocate ValueId for literal and create Const instruction
let value_id = builder.next_value_id();
let const_value = match value {
LiteralValue::Integer(n) => ConstValue::Integer(*n),
LiteralValue::String(s) => ConstValue::String(s.clone()),
LiteralValue::Bool(b) => ConstValue::Bool(*b),
_ => return Err(format!("[normalizer] Unsupported literal type: {:?}", value)),
};
builder.type_ctx.value_types.insert(value_id, MirType::Integer);
// Return the ValueId and the Const instruction
let const_effect = CoreEffectPlan::Const {
dst: value_id,
value: const_value,
};
Ok((value_id, vec![const_effect]))
}
_ => Err(format!("[normalizer] Unsupported value AST: {:?}", ast)),
}
}
/// Phase 286 P2.1: Pattern1SimpleWhile → CorePlan 変換
///
/// Expands Pattern1 (Simple While Loop) semantics into generic CorePlan:
/// - CFG structure: preheader → header → body → step → header (back-edge)
/// - 1 PHI for loop variable in header
/// - No 2-step branching (simpler than Pattern4)
fn normalize_pattern1_simple_while(
builder: &mut MirBuilder,
parts: Pattern1SimpleWhilePlan,
ctx: &LoopPatternContext,
) -> Result<CorePlan, String> {
use crate::mir::builder::control_flow::joinir::trace;
let trace_logger = trace::trace();
let debug = ctx.debug;
if debug {
trace_logger.debug(
"normalizer/pattern1_simple_while",
&format!(
"Phase 286 P2.1: Normalizing Pattern1SimpleWhile for {} (loop_var: {})",
ctx.func_name, parts.loop_var
),
);
}
// Step 1: Get host ValueId for loop variable
let loop_var_init = builder
.variable_ctx
.variable_map
.get(&parts.loop_var)
.copied()
.ok_or_else(|| format!("[normalizer] Loop variable {} not found", parts.loop_var))?;
// Step 2: Capture preheader block
let preheader_bb = builder
.current_block
.ok_or_else(|| "[normalizer] No current block for loop entry".to_string())?;
// Step 3: Allocate BasicBlockIds for 5 blocks
let header_bb = builder.next_block_id();
let body_bb = builder.next_block_id();
let step_bb = builder.next_block_id();
let after_bb = builder.next_block_id();
if debug {
trace_logger.debug(
"normalizer/pattern1_simple_while",
&format!(
"Allocated: preheader={:?}, header={:?}, body={:?}, step={:?}, after={:?}",
preheader_bb, header_bb, body_bb, step_bb, after_bb
),
);
}
// Step 4: Allocate ValueIds for loop control
let loop_var_current = builder.alloc_typed(MirType::Integer); // PHI destination
let cond_loop = builder.alloc_typed(MirType::Bool); // Loop condition
let loop_var_next = builder.alloc_typed(MirType::Integer); // Incremented loop var
// Step 5: Build phi_bindings - PHI dst takes precedence over variable_map
let mut phi_bindings: BTreeMap<String, crate::mir::ValueId> = BTreeMap::new();
phi_bindings.insert(parts.loop_var.clone(), loop_var_current);
// Step 6: Lower AST expressions
// Lower loop condition (e.g., `i < 3`)
let (loop_cond_lhs, loop_cond_op, loop_cond_rhs, loop_cond_consts) =
Self::lower_compare_ast(&parts.condition, builder, &phi_bindings)?;
// Lower loop increment (e.g., `i + 1`)
let (loop_inc_lhs, loop_inc_op, loop_inc_rhs, loop_inc_consts) =
Self::lower_binop_ast(&parts.loop_increment, builder, &phi_bindings)?;
// Step 7: Build header_effects (const definitions + loop condition check)
let mut header_effects = loop_cond_consts;
header_effects.push(CoreEffectPlan::Compare {
dst: cond_loop,
lhs: loop_cond_lhs,
op: loop_cond_op,
rhs: loop_cond_rhs,
});
// Step 8: Build step_effects (const definitions + increment)
let mut step_effects = loop_inc_consts;
step_effects.push(CoreEffectPlan::BinOp {
dst: loop_var_next,
lhs: loop_inc_lhs,
op: loop_inc_op,
rhs: loop_inc_rhs,
});
// Step 9: Build block_effects
let block_effects = vec![
(preheader_bb, vec![]),
(header_bb, header_effects),
(body_bb, vec![]), // Body is empty for simple increment-only loop
(step_bb, step_effects),
];
// Step 10: Build PHI (single PHI for loop variable)
let phis = vec![
CorePhiInfo {
block: header_bb,
dst: loop_var_current,
inputs: vec![
(preheader_bb, loop_var_init),
(step_bb, loop_var_next),
],
tag: format!("loop_var_{}", parts.loop_var),
},
];
// Step 11: Build Frag
let empty_args = EdgeArgs {
layout: JumpArgsLayout::CarriersOnly,
values: vec![],
};
let branches = vec![
BranchStub {
from: header_bb,
cond: cond_loop,
then_target: body_bb,
then_args: empty_args.clone(),
else_target: after_bb,
else_args: empty_args.clone(),
},
];
let wires = vec![
// body → step
EdgeStub {
from: body_bb,
kind: ExitKind::Normal,
target: Some(step_bb),
args: empty_args.clone(),
},
// step → header (back-edge)
EdgeStub {
from: step_bb,
kind: ExitKind::Normal,
target: Some(header_bb),
args: empty_args.clone(),
},
];
let frag = Frag {
entry: header_bb,
exits: BTreeMap::new(),
wires,
branches,
};
// Step 12: Build final_values
let final_values = vec![
(parts.loop_var.clone(), loop_var_current),
];
// Step 13: Build CoreLoopPlan
// Note: found_bb = after_bb (no early exit in Pattern1)
// Note: cond_match unused but required by struct, use cond_loop as placeholder
let loop_plan = CoreLoopPlan {
preheader_bb,
header_bb,
body_bb,
step_bb,
after_bb,
found_bb: after_bb, // No early exit
body: vec![], // Body is empty for simple loop
cond_loop,
cond_match: cond_loop, // Placeholder (unused in Pattern1)
block_effects,
phis,
frag,
final_values,
};
if debug {
trace_logger.debug(
"normalizer/pattern1_simple_while",
"CorePlan construction complete (4 blocks, 1 PHI)",
);
}
Ok(CorePlan::Loop(loop_plan))
}
}