feat(joinir): Phase 286 P3.2 - Pattern5 Plan line (loop(true) + early exit)
- Pattern5InfiniteEarlyExitPlan (Return/Break variants)
- extract_pattern5_plan() for loop(true) literal only
- normalize_pattern5_return(): 5 blocks CFG (header→body→found/step)
- normalize_pattern5_break(): 6 blocks CFG with carrier PHI
- NormalizationPlanBox exclusion for Pattern5-style loops
- Fixtures: phase286_pattern5_{return,break}_min.hako
- quick smoke 154/154 PASS
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -163,6 +163,141 @@ fn count_control_flow_recursive(body: &[ASTNode]) -> (usize, usize, usize, bool)
|
||||
// Phase 282 P9a: validate_continue_at_end moved to common_helpers
|
||||
// Phase 282 P9a: validate_break_in_simple_if moved to common_helpers
|
||||
|
||||
// ============================================================================
|
||||
// Phase 286 P3.2: Plan line extractor (PoC subset)
|
||||
// ============================================================================
|
||||
|
||||
use crate::mir::builder::control_flow::plan::{
|
||||
DomainPlan, Pattern5InfiniteEarlyExitPlan, Pattern5ExitKind,
|
||||
};
|
||||
|
||||
/// Extract variable name and increment expression from assignment
|
||||
///
|
||||
/// Returns (variable_name, increment_expr) for `var = expr` form
|
||||
fn extract_assignment_parts(stmt: &ASTNode) -> Option<(String, ASTNode)> {
|
||||
if let ASTNode::Assignment { target, value, .. } = stmt {
|
||||
if let ASTNode::Variable { name, .. } = target.as_ref() {
|
||||
return Some((name.clone(), value.as_ref().clone()));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Extract Pattern5 Plan (Phase 286 P3.2)
|
||||
///
|
||||
/// # PoC Subset (strict)
|
||||
///
|
||||
/// - `loop(true)` literal ONLY (not `loop(1)` or truthy)
|
||||
/// - Return version: `if (cond) { return <expr> }` + `i = i + 1`
|
||||
/// - Break version: `if (cond) { break }` + `sum = sum + 1` + `i = i + 1` (carrier_update required)
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// - `Ok(Some(DomainPlan))`: Pattern5 Plan match
|
||||
/// - `Ok(None)`: Not PoC subset (fall back to legacy or other patterns)
|
||||
/// - `Err(msg)`: Close-but-unsupported (Fail-Fast)
|
||||
pub(crate) fn extract_pattern5_plan(
|
||||
condition: &ASTNode,
|
||||
body: &[ASTNode],
|
||||
) -> Result<Option<DomainPlan>, String> {
|
||||
// ========================================================================
|
||||
// Block 1: Check condition is `true` literal (loop(true) ONLY)
|
||||
// ========================================================================
|
||||
if !is_true_literal(condition) {
|
||||
return Ok(None); // Not loop(true) → Not Pattern5 Plan
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Block 2: Find exit statement (if at first position)
|
||||
// ========================================================================
|
||||
// PoC subset: first statement must be `if (cond) { return/break }`
|
||||
if body.is_empty() {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let first_stmt = &body[0];
|
||||
let (exit_kind, exit_condition, exit_value) = match first_stmt {
|
||||
ASTNode::If { condition: if_cond, then_body, else_body: None, .. } => {
|
||||
// Must be single-statement then body
|
||||
if then_body.len() != 1 {
|
||||
return Ok(None);
|
||||
}
|
||||
match &then_body[0] {
|
||||
ASTNode::Return { value, .. } => {
|
||||
(Pattern5ExitKind::Return, if_cond.as_ref().clone(), value.clone())
|
||||
}
|
||||
ASTNode::Break { .. } => {
|
||||
(Pattern5ExitKind::Break, if_cond.as_ref().clone(), None)
|
||||
}
|
||||
_ => return Ok(None),
|
||||
}
|
||||
}
|
||||
_ => return Ok(None),
|
||||
};
|
||||
|
||||
// ========================================================================
|
||||
// Block 3: Parse remaining body for carrier update and loop increment
|
||||
// ========================================================================
|
||||
let remaining = &body[1..];
|
||||
|
||||
match exit_kind {
|
||||
Pattern5ExitKind::Return => {
|
||||
// Return version: just need loop increment
|
||||
// Expected: [increment]
|
||||
if remaining.len() != 1 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let (loop_var, loop_increment) = match extract_assignment_parts(&remaining[0]) {
|
||||
Some(result) => result,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
// Unbox exit_value if present
|
||||
let exit_value_unboxed = exit_value.map(|boxed| boxed.as_ref().clone());
|
||||
|
||||
Ok(Some(DomainPlan::Pattern5InfiniteEarlyExit(Pattern5InfiniteEarlyExitPlan {
|
||||
loop_var,
|
||||
exit_kind,
|
||||
exit_condition,
|
||||
exit_value: exit_value_unboxed,
|
||||
carrier_var: None,
|
||||
carrier_update: None,
|
||||
loop_increment,
|
||||
})))
|
||||
}
|
||||
Pattern5ExitKind::Break => {
|
||||
// Break version: need carrier update + loop increment
|
||||
// Expected: [carrier_update, increment]
|
||||
if remaining.len() != 2 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// Parse carrier update (sum = sum + 1)
|
||||
let (carrier_var, carrier_update) = match extract_assignment_parts(&remaining[0]) {
|
||||
Some(result) => result,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
// Parse loop increment (i = i + 1)
|
||||
let (loop_var, loop_increment) = match extract_assignment_parts(&remaining[1]) {
|
||||
Some(result) => result,
|
||||
None => return Ok(None),
|
||||
};
|
||||
|
||||
Ok(Some(DomainPlan::Pattern5InfiniteEarlyExit(Pattern5InfiniteEarlyExitPlan {
|
||||
loop_var,
|
||||
exit_kind,
|
||||
exit_condition,
|
||||
exit_value: None,
|
||||
carrier_var: Some(carrier_var),
|
||||
carrier_update: Some(carrier_update),
|
||||
loop_increment,
|
||||
})))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Unit Tests
|
||||
// ============================================================================
|
||||
|
||||
@ -203,6 +203,10 @@ static PLAN_EXTRACTORS: &[PlanExtractorEntry] = &[
|
||||
name: "Pattern7_SplitScan (Phase 273)",
|
||||
extractor: PlanExtractorVariant::WithPostLoop(super::pattern7_split_scan::extract_split_scan_plan),
|
||||
},
|
||||
PlanExtractorEntry {
|
||||
name: "Pattern5_InfiniteEarlyExit (Phase 286 P3.2)",
|
||||
extractor: PlanExtractorVariant::Simple(super::extractors::pattern5::extract_pattern5_plan),
|
||||
},
|
||||
PlanExtractorEntry {
|
||||
name: "Pattern8_BoolPredicateScan (Phase 286 P2.4)",
|
||||
extractor: PlanExtractorVariant::Simple(super::extractors::pattern8::extract_pattern8_plan),
|
||||
|
||||
@ -52,15 +52,16 @@ impl NormalizationPlanBox {
|
||||
|
||||
// First statement must be a loop with condition `true`
|
||||
// Phase 131-136 ONLY support loop(true), not loop(i < n) etc.
|
||||
let is_loop_true = match &remaining[0] {
|
||||
ASTNode::Loop { condition, .. } => {
|
||||
let (is_loop_true, loop_body) = match &remaining[0] {
|
||||
ASTNode::Loop { condition, body, .. } => {
|
||||
// Only accept loop(true) - literal Bool true
|
||||
matches!(
|
||||
let is_true = matches!(
|
||||
condition.as_ref(),
|
||||
ASTNode::Literal { value: LiteralValue::Bool(true), .. }
|
||||
)
|
||||
);
|
||||
(is_true, Some(body.as_slice()))
|
||||
}
|
||||
_ => false,
|
||||
_ => (false, None),
|
||||
};
|
||||
if !is_loop_true {
|
||||
if debug {
|
||||
@ -73,6 +74,31 @@ impl NormalizationPlanBox {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
// Phase 286 P3.2: Reject Pattern5-style loops (if with early return/break)
|
||||
// These are handled by the Plan line (Pattern5InfiniteEarlyExit), not StepTree
|
||||
if let Some(body) = loop_body {
|
||||
if !body.is_empty() {
|
||||
if let ASTNode::If { then_body, else_body, .. } = &body[0] {
|
||||
// Check if it's a Pattern5-style if (no else, then contains return or break)
|
||||
if else_body.is_none() {
|
||||
let has_early_exit = then_body.iter().any(|stmt| {
|
||||
matches!(stmt, ASTNode::Return { .. } | ASTNode::Break { .. })
|
||||
});
|
||||
if has_early_exit {
|
||||
if debug {
|
||||
trace.routing(
|
||||
"normalization/plan",
|
||||
func_name,
|
||||
"Loop body has if with early return/break - Pattern5 (Plan line), returning None",
|
||||
);
|
||||
}
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Phase 142 P0: Always return loop_only for loop(true), regardless of what follows
|
||||
// Normalization unit is now "statement (loop 1個)" not "block suffix"
|
||||
// Subsequent statements (return, assignments, etc.) handled by normal MIR lowering
|
||||
|
||||
@ -57,6 +57,8 @@ pub(in crate::mir::builder) enum DomainPlan {
|
||||
Pattern3IfPhi(Pattern3IfPhiPlan),
|
||||
/// Pattern2: Loop with Conditional Break (Phase 286 P3.1)
|
||||
Pattern2Break(Pattern2BreakPlan),
|
||||
/// Pattern5: Infinite Loop with Early Exit (Phase 286 P3.2)
|
||||
Pattern5InfiniteEarlyExit(Pattern5InfiniteEarlyExitPlan),
|
||||
}
|
||||
|
||||
/// Phase 273 P0: Scan direction for forward/reverse scan
|
||||
@ -269,6 +271,61 @@ pub(in crate::mir::builder) struct Pattern2BreakPlan {
|
||||
pub loop_increment: ASTNode,
|
||||
}
|
||||
|
||||
/// Phase 286 P3.2: Exit kind for Pattern5 infinite loop
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(in crate::mir::builder) enum Pattern5ExitKind {
|
||||
/// Early return from function
|
||||
Return,
|
||||
/// Break from loop
|
||||
Break,
|
||||
}
|
||||
|
||||
/// Phase 286 P3.2: Extracted structure for Pattern5 (Infinite Loop with Early Exit)
|
||||
///
|
||||
/// This structure contains all the information needed to lower a loop(true) pattern
|
||||
/// with early exit (return or break).
|
||||
///
|
||||
/// # PoC Subset
|
||||
///
|
||||
/// - `loop(true)` literal only (not `loop(1)` or truthy)
|
||||
/// - Return version: `if (cond) { return <expr> }` + `i = i + 1`
|
||||
/// - Break version: `if (cond) { break }` + `sum = sum + 1` + `i = i + 1` (carrier_update required)
|
||||
///
|
||||
/// # CFG Structure (Return version)
|
||||
/// ```text
|
||||
/// preheader → header(PHI: i_current) → body(exit_cond)
|
||||
/// ↑ ↓
|
||||
/// └───── step ←──────── else path
|
||||
/// ↓
|
||||
/// then path: CoreExitPlan::Return
|
||||
/// ```
|
||||
///
|
||||
/// # CFG Structure (Break version)
|
||||
/// ```text
|
||||
/// preheader → header(PHI: i, carrier) → body(exit_cond)
|
||||
/// ↑ ↓
|
||||
/// └───── step ←────────── else path
|
||||
/// ↓
|
||||
/// then path → after_bb(PHI: carrier_out)
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub(in crate::mir::builder) struct Pattern5InfiniteEarlyExitPlan {
|
||||
/// Loop variable name (e.g., "i")
|
||||
pub loop_var: String,
|
||||
/// Exit kind (Return or Break)
|
||||
pub exit_kind: Pattern5ExitKind,
|
||||
/// Exit condition AST (e.g., `i == 3`)
|
||||
pub exit_condition: ASTNode,
|
||||
/// Return value expression (Some for Return, None for Break)
|
||||
pub exit_value: Option<ASTNode>,
|
||||
/// Carrier variable name (Some for Break with carrier, None for Return)
|
||||
pub carrier_var: Option<String>,
|
||||
/// Carrier update expression (Some for Break, None for Return)
|
||||
pub carrier_update: Option<ASTNode>,
|
||||
/// Loop increment expression AST (e.g., `i + 1`)
|
||||
pub loop_increment: ASTNode,
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CorePlan (固定語彙 - 構造ノードのみ)
|
||||
// ============================================================================
|
||||
|
||||
@ -15,6 +15,7 @@ use super::{
|
||||
CoreEffectPlan, CoreLoopPlan, CorePhiInfo, CorePlan, DomainPlan,
|
||||
ScanWithInitPlan, SplitScanPlan, Pattern1SimpleWhilePlan, Pattern9AccumConstLoopPlan,
|
||||
Pattern8BoolPredicateScanPlan, Pattern3IfPhiPlan, Pattern2BreakPlan,
|
||||
Pattern5InfiniteEarlyExitPlan, Pattern5ExitKind,
|
||||
};
|
||||
use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext;
|
||||
use crate::mir::builder::MirBuilder;
|
||||
@ -139,6 +140,7 @@ impl PlanNormalizer {
|
||||
DomainPlan::Pattern8BoolPredicateScan(parts) => Self::normalize_pattern8_bool_predicate_scan(builder, parts, ctx),
|
||||
DomainPlan::Pattern3IfPhi(parts) => Self::normalize_pattern3_if_phi(builder, parts, ctx),
|
||||
DomainPlan::Pattern2Break(parts) => Self::normalize_pattern2_break(builder, parts, ctx),
|
||||
DomainPlan::Pattern5InfiniteEarlyExit(parts) => Self::normalize_pattern5_infinite_early_exit(builder, parts, ctx),
|
||||
}
|
||||
}
|
||||
|
||||
@ -2740,4 +2742,553 @@ impl PlanNormalizer {
|
||||
|
||||
Ok(CorePlan::Loop(loop_plan))
|
||||
}
|
||||
|
||||
/// Pattern5InfiniteEarlyExit → CorePlan 変換 (Phase 286 P3.2)
|
||||
///
|
||||
/// Expands infinite loop with early exit into CorePlan.
|
||||
///
|
||||
/// # Return version CFG (5 blocks)
|
||||
/// ```text
|
||||
/// preheader → header(PHI: i_current) → body(exit_cond)
|
||||
/// ↑ ↓
|
||||
/// └───── step ←──────── else path
|
||||
/// ↓
|
||||
/// then path: Return (found_bb)
|
||||
/// ```
|
||||
///
|
||||
/// # Break version CFG (6 blocks)
|
||||
/// ```text
|
||||
/// preheader → header(PHI: i, carrier) → body(exit_cond)
|
||||
/// ↑ ↓
|
||||
/// └───── step ←────────── else path
|
||||
/// ↓
|
||||
/// then path → after_bb(PHI: carrier_out)
|
||||
/// ```
|
||||
fn normalize_pattern5_infinite_early_exit(
|
||||
builder: &mut MirBuilder,
|
||||
parts: Pattern5InfiniteEarlyExitPlan,
|
||||
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/pattern5_infinite_early_exit",
|
||||
&format!(
|
||||
"Phase 286 P3.2: Normalizing Pattern5 for {} (loop_var={}, exit_kind={:?})",
|
||||
ctx.func_name, parts.loop_var, parts.exit_kind
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
match parts.exit_kind {
|
||||
Pattern5ExitKind::Return => {
|
||||
Self::normalize_pattern5_return(builder, parts, ctx, debug, &trace_logger)
|
||||
}
|
||||
Pattern5ExitKind::Break => {
|
||||
Self::normalize_pattern5_break(builder, parts, ctx, debug, &trace_logger)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pattern5 Return version normalizer
|
||||
///
|
||||
/// CFG: preheader → header → body → found_bb (return) / step → header
|
||||
fn normalize_pattern5_return(
|
||||
builder: &mut MirBuilder,
|
||||
parts: Pattern5InfiniteEarlyExitPlan,
|
||||
ctx: &LoopPatternContext,
|
||||
debug: bool,
|
||||
trace_logger: &crate::mir::builder::control_flow::joinir::trace::JoinLoopTrace,
|
||||
) -> Result<CorePlan, String> {
|
||||
// Step 1: Block allocation (5 blocks)
|
||||
let preheader_bb = builder
|
||||
.current_block
|
||||
.ok_or_else(|| "Pattern5Return: no current block".to_string())?;
|
||||
let header_bb = builder.next_block_id();
|
||||
let body_bb = builder.next_block_id();
|
||||
let found_bb = builder.next_block_id(); // Return block
|
||||
let step_bb = builder.next_block_id();
|
||||
let after_bb = builder.next_block_id(); // Unreachable for infinite loop
|
||||
|
||||
if debug {
|
||||
trace_logger.debug(
|
||||
"normalizer/pattern5_return",
|
||||
&format!(
|
||||
"Block allocation: preheader={:?}, header={:?}, body={:?}, found={:?}, step={:?}, after={:?}",
|
||||
preheader_bb, header_bb, body_bb, found_bb, step_bb, after_bb
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Step 2: Get initial values from variable_map
|
||||
let loop_var_init = builder
|
||||
.variable_ctx
|
||||
.variable_map
|
||||
.get(&parts.loop_var)
|
||||
.copied()
|
||||
.ok_or_else(|| format!("Pattern5Return: loop_var '{}' not in variable_map", parts.loop_var))?;
|
||||
|
||||
// Step 3: ValueId allocation
|
||||
let loop_var_current = builder.alloc_typed(MirType::Integer); // header PHI dst
|
||||
let cond_exit = builder.alloc_typed(MirType::Bool); // exit condition
|
||||
let loop_var_next = builder.alloc_typed(MirType::Integer); // loop var next
|
||||
let true_val = builder.alloc_typed(MirType::Bool); // for infinite loop
|
||||
|
||||
// Step 4: phi_bindings for AST lowering
|
||||
let phi_bindings = create_phi_bindings(&[(&parts.loop_var, loop_var_current)]);
|
||||
|
||||
// Step 5: Lower AST expressions
|
||||
// 5.1: Exit condition (e.g., `i == 3`)
|
||||
let (exit_cond_lhs, exit_cond_op, exit_cond_rhs, exit_cond_consts) =
|
||||
Self::lower_compare_ast(&parts.exit_condition, builder, &phi_bindings)?;
|
||||
|
||||
// 5.2: Return value (if any)
|
||||
let return_value = if let Some(ref exit_val_ast) = parts.exit_value {
|
||||
let return_val_id = builder.alloc_typed(MirType::Integer);
|
||||
let return_val_ast = exit_val_ast.clone();
|
||||
Some((return_val_id, return_val_ast))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// 5.3: Loop increment (e.g., `i + 1`)
|
||||
let (inc_lhs, inc_op, inc_rhs, inc_consts) =
|
||||
Self::lower_binop_ast(&parts.loop_increment, builder, &phi_bindings)?;
|
||||
|
||||
// Step 6: Build header_effects
|
||||
// For infinite loop, we just need a dummy true condition for loop control
|
||||
let header_effects = vec![
|
||||
CoreEffectPlan::Const {
|
||||
dst: true_val,
|
||||
value: ConstValue::Bool(true),
|
||||
},
|
||||
];
|
||||
|
||||
// Step 7: Build body plans
|
||||
let mut body_plans: Vec<CorePlan> = Vec::new();
|
||||
|
||||
// Add exit condition consts
|
||||
for eff in exit_cond_consts {
|
||||
body_plans.push(CorePlan::Effect(eff));
|
||||
}
|
||||
|
||||
// Add exit condition compare
|
||||
body_plans.push(CorePlan::Effect(CoreEffectPlan::Compare {
|
||||
dst: cond_exit,
|
||||
lhs: exit_cond_lhs,
|
||||
op: exit_cond_op,
|
||||
rhs: exit_cond_rhs,
|
||||
}));
|
||||
|
||||
// Step 8: Build found_bb effects (return value computation + Return)
|
||||
let mut found_effects: Vec<CoreEffectPlan> = Vec::new();
|
||||
let return_val_id = if let Some((ret_val_id, ref ret_val_ast)) = return_value {
|
||||
// Lower return value as const if it's a literal
|
||||
if let crate::ast::ASTNode::Literal { value: crate::ast::LiteralValue::Integer(n), .. } = ret_val_ast {
|
||||
found_effects.push(CoreEffectPlan::Const {
|
||||
dst: ret_val_id,
|
||||
value: ConstValue::Integer(*n),
|
||||
});
|
||||
} else {
|
||||
// For non-literal, use binop lowering (simplified)
|
||||
let (lhs, op, rhs, consts) = Self::lower_binop_ast(ret_val_ast, builder, &phi_bindings)?;
|
||||
for c in consts {
|
||||
found_effects.push(c);
|
||||
}
|
||||
found_effects.push(CoreEffectPlan::BinOp {
|
||||
dst: ret_val_id,
|
||||
lhs,
|
||||
op,
|
||||
rhs,
|
||||
});
|
||||
}
|
||||
Some(ret_val_id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Step 9: Build step_effects (increment loop var)
|
||||
let mut step_effects = inc_consts;
|
||||
step_effects.push(CoreEffectPlan::BinOp {
|
||||
dst: loop_var_next,
|
||||
lhs: inc_lhs,
|
||||
op: inc_op,
|
||||
rhs: inc_rhs,
|
||||
});
|
||||
|
||||
// Step 10: Build PHIs
|
||||
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_carrier_{}", parts.loop_var),
|
||||
},
|
||||
];
|
||||
|
||||
// Step 11: Build block_effects (V10 contract: body effects in loop_plan.body)
|
||||
let block_effects = vec![
|
||||
(preheader_bb, vec![]),
|
||||
(header_bb, header_effects),
|
||||
(body_bb, vec![]), // V10: body effects in loop_plan.body
|
||||
(found_bb, found_effects), // Return value computation
|
||||
(step_bb, step_effects),
|
||||
(after_bb, vec![]), // Unreachable for infinite loop (needed for CFG)
|
||||
];
|
||||
|
||||
// Step 12: Build Frag with Return exit
|
||||
let empty_args = EdgeArgs {
|
||||
layout: JumpArgsLayout::CarriersOnly,
|
||||
values: vec![],
|
||||
};
|
||||
|
||||
let branches = vec![
|
||||
// header → body (always, infinite loop)
|
||||
BranchStub {
|
||||
from: header_bb,
|
||||
cond: true_val,
|
||||
then_target: body_bb,
|
||||
else_target: after_bb, // Never taken for infinite loop
|
||||
then_args: empty_args.clone(),
|
||||
else_args: empty_args.clone(),
|
||||
},
|
||||
// body → found_bb (exit) / step (continue)
|
||||
BranchStub {
|
||||
from: body_bb,
|
||||
cond: cond_exit,
|
||||
then_target: found_bb,
|
||||
else_target: step_bb,
|
||||
then_args: empty_args.clone(),
|
||||
else_args: empty_args.clone(),
|
||||
},
|
||||
];
|
||||
|
||||
// Build Return value args
|
||||
let return_args = EdgeArgs {
|
||||
layout: JumpArgsLayout::CarriersOnly,
|
||||
values: if let Some(val_id) = return_val_id {
|
||||
vec![val_id]
|
||||
} else {
|
||||
vec![]
|
||||
},
|
||||
};
|
||||
|
||||
// Wires: internal jumps + Return terminators (emit_wires handles Return with target=None)
|
||||
let wires = vec![
|
||||
// step → header (back-edge)
|
||||
EdgeStub {
|
||||
from: step_bb,
|
||||
kind: ExitKind::Normal,
|
||||
target: Some(header_bb),
|
||||
args: empty_args.clone(),
|
||||
},
|
||||
// found_bb → Return (early exit)
|
||||
EdgeStub {
|
||||
from: found_bb,
|
||||
kind: ExitKind::Return,
|
||||
target: None,
|
||||
args: return_args,
|
||||
},
|
||||
// after_bb → Return (unreachable for infinite loop, but CFG needs terminator)
|
||||
EdgeStub {
|
||||
from: after_bb,
|
||||
kind: ExitKind::Return,
|
||||
target: None,
|
||||
args: empty_args.clone(),
|
||||
},
|
||||
];
|
||||
|
||||
let frag = Frag {
|
||||
entry: header_bb,
|
||||
exits: BTreeMap::new(), // No exits - all terminators are in wires
|
||||
wires,
|
||||
branches,
|
||||
};
|
||||
|
||||
// Step 13: Build final_values
|
||||
let final_values = vec![
|
||||
(parts.loop_var.clone(), loop_var_current),
|
||||
];
|
||||
|
||||
// Step 14: Build CoreLoopPlan
|
||||
let loop_plan = CoreLoopPlan {
|
||||
preheader_bb,
|
||||
header_bb,
|
||||
body_bb,
|
||||
step_bb,
|
||||
after_bb,
|
||||
found_bb,
|
||||
body: body_plans,
|
||||
cond_loop: true_val, // Infinite loop
|
||||
cond_match: cond_exit, // Exit condition for body branching
|
||||
block_effects,
|
||||
phis,
|
||||
frag,
|
||||
final_values,
|
||||
};
|
||||
|
||||
if debug {
|
||||
trace_logger.debug(
|
||||
"normalizer/pattern5_return",
|
||||
"CorePlan construction complete (5 blocks, 1 PHI, Return exit)",
|
||||
);
|
||||
}
|
||||
|
||||
Ok(CorePlan::Loop(loop_plan))
|
||||
}
|
||||
|
||||
/// Pattern5 Break version normalizer
|
||||
///
|
||||
/// CFG: preheader → header → body → break_then → after / step → header
|
||||
fn normalize_pattern5_break(
|
||||
builder: &mut MirBuilder,
|
||||
parts: Pattern5InfiniteEarlyExitPlan,
|
||||
ctx: &LoopPatternContext,
|
||||
debug: bool,
|
||||
trace_logger: &crate::mir::builder::control_flow::joinir::trace::JoinLoopTrace,
|
||||
) -> Result<CorePlan, String> {
|
||||
// Step 1: Block allocation (6 blocks)
|
||||
let preheader_bb = builder
|
||||
.current_block
|
||||
.ok_or_else(|| "Pattern5Break: no current block".to_string())?;
|
||||
let header_bb = builder.next_block_id();
|
||||
let body_bb = builder.next_block_id();
|
||||
let break_then_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/pattern5_break",
|
||||
&format!(
|
||||
"Block allocation: preheader={:?}, header={:?}, body={:?}, break_then={:?}, step={:?}, after={:?}",
|
||||
preheader_bb, header_bb, body_bb, break_then_bb, step_bb, after_bb
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// Step 2: Get carrier_var (required for Break version)
|
||||
let carrier_var = parts.carrier_var.as_ref()
|
||||
.ok_or_else(|| "Pattern5Break: carrier_var required for Break version".to_string())?;
|
||||
let carrier_update_ast = parts.carrier_update.as_ref()
|
||||
.ok_or_else(|| "Pattern5Break: carrier_update required for Break version".to_string())?;
|
||||
|
||||
// Step 3: Get initial values from variable_map
|
||||
let loop_var_init = builder
|
||||
.variable_ctx
|
||||
.variable_map
|
||||
.get(&parts.loop_var)
|
||||
.copied()
|
||||
.ok_or_else(|| format!("Pattern5Break: loop_var '{}' not in variable_map", parts.loop_var))?;
|
||||
|
||||
let carrier_init = builder
|
||||
.variable_ctx
|
||||
.variable_map
|
||||
.get(carrier_var)
|
||||
.copied()
|
||||
.ok_or_else(|| format!("Pattern5Break: carrier_var '{}' not in variable_map", carrier_var))?;
|
||||
|
||||
// Step 4: ValueId allocation
|
||||
let loop_var_current = builder.alloc_typed(MirType::Integer); // header PHI dst
|
||||
let carrier_current = builder.alloc_typed(MirType::Integer); // header PHI dst
|
||||
let cond_exit = builder.alloc_typed(MirType::Bool); // exit condition
|
||||
let carrier_step = builder.alloc_typed(MirType::Integer); // step path carrier
|
||||
let loop_var_next = builder.alloc_typed(MirType::Integer); // loop var next
|
||||
let carrier_out = builder.alloc_typed(MirType::Integer); // after_bb PHI dst
|
||||
let true_val = builder.alloc_typed(MirType::Bool); // for infinite loop
|
||||
|
||||
// Step 5: phi_bindings for AST lowering
|
||||
let phi_bindings = create_phi_bindings(&[
|
||||
(&parts.loop_var, loop_var_current),
|
||||
(carrier_var, carrier_current),
|
||||
]);
|
||||
|
||||
// Step 6: Lower AST expressions
|
||||
// 6.1: Exit condition (e.g., `i == 3`)
|
||||
let (exit_cond_lhs, exit_cond_op, exit_cond_rhs, exit_cond_consts) =
|
||||
Self::lower_compare_ast(&parts.exit_condition, builder, &phi_bindings)?;
|
||||
|
||||
// 6.2: Carrier update (e.g., `sum + 1`)
|
||||
let (carrier_lhs, carrier_op, carrier_rhs, carrier_consts) =
|
||||
Self::lower_binop_ast(carrier_update_ast, builder, &phi_bindings)?;
|
||||
|
||||
// 6.3: Loop increment (e.g., `i + 1`)
|
||||
let (inc_lhs, inc_op, inc_rhs, inc_consts) =
|
||||
Self::lower_binop_ast(&parts.loop_increment, builder, &phi_bindings)?;
|
||||
|
||||
// Step 7: Build header_effects
|
||||
let header_effects = vec![
|
||||
CoreEffectPlan::Const {
|
||||
dst: true_val,
|
||||
value: ConstValue::Bool(true),
|
||||
},
|
||||
];
|
||||
|
||||
// Step 8: Build body plans (exit condition check)
|
||||
let mut body_plans: Vec<CorePlan> = Vec::new();
|
||||
for eff in exit_cond_consts {
|
||||
body_plans.push(CorePlan::Effect(eff));
|
||||
}
|
||||
body_plans.push(CorePlan::Effect(CoreEffectPlan::Compare {
|
||||
dst: cond_exit,
|
||||
lhs: exit_cond_lhs,
|
||||
op: exit_cond_op,
|
||||
rhs: exit_cond_rhs,
|
||||
}));
|
||||
|
||||
// Step 9: Build break_then_effects (empty - carrier_current goes directly to after PHI)
|
||||
let break_then_effects = vec![];
|
||||
|
||||
// Step 10: Build step_effects (carrier update + loop increment)
|
||||
let mut step_effects = carrier_consts;
|
||||
step_effects.push(CoreEffectPlan::BinOp {
|
||||
dst: carrier_step,
|
||||
lhs: carrier_lhs,
|
||||
op: carrier_op,
|
||||
rhs: carrier_rhs,
|
||||
});
|
||||
for c in inc_consts {
|
||||
step_effects.push(c);
|
||||
}
|
||||
step_effects.push(CoreEffectPlan::BinOp {
|
||||
dst: loop_var_next,
|
||||
lhs: inc_lhs,
|
||||
op: inc_op,
|
||||
rhs: inc_rhs,
|
||||
});
|
||||
|
||||
// Step 11: Build PHIs (3 total)
|
||||
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_carrier_{}", parts.loop_var),
|
||||
},
|
||||
CorePhiInfo {
|
||||
block: header_bb,
|
||||
dst: carrier_current,
|
||||
inputs: vec![
|
||||
(preheader_bb, carrier_init),
|
||||
(step_bb, carrier_step),
|
||||
],
|
||||
tag: format!("loop_carrier_{}", carrier_var),
|
||||
},
|
||||
// after_bb PHI: carrier_out from break_then (carrier_current) or step (carrier_step)
|
||||
// But for infinite loop with break, only break_then reaches after_bb
|
||||
CorePhiInfo {
|
||||
block: after_bb,
|
||||
dst: carrier_out,
|
||||
inputs: vec![
|
||||
(break_then_bb, carrier_current), // break path: current carrier value
|
||||
],
|
||||
tag: format!("exit_carrier_{}", carrier_var),
|
||||
},
|
||||
];
|
||||
|
||||
// Step 12: Build block_effects (V10 contract)
|
||||
let block_effects = vec![
|
||||
(preheader_bb, vec![]),
|
||||
(header_bb, header_effects),
|
||||
(body_bb, vec![]), // V10: body effects in loop_plan.body
|
||||
(break_then_bb, break_then_effects),
|
||||
(step_bb, step_effects),
|
||||
];
|
||||
|
||||
// Step 13: Build Frag
|
||||
let empty_args = EdgeArgs {
|
||||
layout: JumpArgsLayout::CarriersOnly,
|
||||
values: vec![],
|
||||
};
|
||||
|
||||
let branches = vec![
|
||||
// header → body (always, infinite loop)
|
||||
BranchStub {
|
||||
from: header_bb,
|
||||
cond: true_val,
|
||||
then_target: body_bb,
|
||||
else_target: after_bb, // Never taken for infinite loop
|
||||
then_args: empty_args.clone(),
|
||||
else_args: empty_args.clone(),
|
||||
},
|
||||
// body → break_then (exit) / step (continue)
|
||||
BranchStub {
|
||||
from: body_bb,
|
||||
cond: cond_exit,
|
||||
then_target: break_then_bb,
|
||||
else_target: step_bb,
|
||||
then_args: empty_args.clone(),
|
||||
else_args: empty_args.clone(),
|
||||
},
|
||||
];
|
||||
|
||||
// Use fixed LoopId(0) for internal break wiring
|
||||
let loop_id = crate::mir::control_form::LoopId(0);
|
||||
|
||||
let wires = vec![
|
||||
// break_then → after
|
||||
EdgeStub {
|
||||
from: break_then_bb,
|
||||
kind: ExitKind::Break(loop_id),
|
||||
target: Some(after_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 14: Build final_values
|
||||
let final_values = vec![
|
||||
(parts.loop_var.clone(), loop_var_current),
|
||||
(carrier_var.clone(), carrier_out), // KEY: after PHI dst!
|
||||
];
|
||||
|
||||
// Step 15: Build CoreLoopPlan
|
||||
let loop_plan = CoreLoopPlan {
|
||||
preheader_bb,
|
||||
header_bb,
|
||||
body_bb,
|
||||
step_bb,
|
||||
after_bb,
|
||||
found_bb: after_bb, // No early exit via found (break goes to after)
|
||||
body: body_plans,
|
||||
cond_loop: true_val, // Infinite loop
|
||||
cond_match: cond_exit, // Exit condition for body branching
|
||||
block_effects,
|
||||
phis,
|
||||
frag,
|
||||
final_values,
|
||||
};
|
||||
|
||||
if debug {
|
||||
trace_logger.debug(
|
||||
"normalizer/pattern5_break",
|
||||
"CorePlan construction complete (6 blocks, 3 PHIs, after_bb PHI for carrier)",
|
||||
);
|
||||
}
|
||||
|
||||
Ok(CorePlan::Loop(loop_plan))
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user