refactor(joinir): Phase 286 P3.2 cleanup + normalizer modularization

## Legacy Pattern5 削除 (488行)
- pattern5_infinite_early_exit.rs 完全削除
- LOOP_PATTERNS テーブルからエントリ削除
- Plan extractor が SSOT

## Normalizer 分割 (3294行 → 12ファイル)
- helpers.rs: 共通ヘルパー関数
- pattern*.rs: 各パターン専用ファイル
- mod.rs: ディスパッチャ

## ドキュメント更新
- Phase 286 README: クリーンアップ完了・Fail-Fast方針記載
- Phase 287 README: 将来計画

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:
2025-12-26 10:30:08 +09:00
parent 22945c190c
commit 7a575e30cc
18 changed files with 3394 additions and 4326 deletions

View File

@ -79,7 +79,6 @@ pub(in crate::mir::builder) mod pattern2_with_break;
pub(in crate::mir::builder) mod pattern3_with_if_phi;
pub(in crate::mir::builder) mod pattern4_carrier_analyzer;
pub(in crate::mir::builder) mod pattern4_with_continue;
pub(in crate::mir::builder) mod pattern5_infinite_early_exit; // Phase 131-11
pub(in crate::mir::builder) mod pattern6_scan_with_init; // Phase 254 P0: index_of/find/contains pattern
pub(in crate::mir::builder) mod pattern7_split_scan; // Phase 256 P0: split/tokenization with variable step
pub(in crate::mir::builder) mod pattern8_scan_bool_predicate; // Phase 259 P0: boolean predicate scan (is_integer/is_valid)

View File

@ -1,488 +0,0 @@
//! Pattern 5: Infinite Loop with Early Exit (Phase 131-11)
//!
//! # Pattern Overview
//!
//! Handles `loop(true)` with both `break` and `continue` statements.
//! This is a specialized pattern for infinite loops with early exit conditions.
//!
//! # Example
//!
//! ```nyash
//! local counter = 0
//! loop (true) {
//! counter = counter + 1
//! if counter == 3 { break }
//! continue
//! }
//! ```
//!
//! # Shape Guard (Fail-Fast)
//!
//! - Condition MUST be `true` literal (infinite loop)
//! - MUST have exactly 1 break statement
//! - MUST have exactly 1 continue statement
//! - Carriers: MUST be exactly 1 counter-like variable
//! - No nested loops (not yet supported)
//!
//! # Implementation Status
//!
//! Phase 131-11: Fully implemented
//! - Shape validation with strict break/continue guards
//! - Lowering via JoinIRConversionPipeline with counter carrier tracking
use super::super::trace;
use crate::ast::ASTNode;
use crate::mir::builder::MirBuilder;
use crate::mir::loop_pattern_detection::LoopPatternKind;
use crate::mir::ValueId;
use super::router::LoopPatternContext;
/// Phase 131-11-D: Enhanced shape guard with real count + position constraints
///
/// This function validates the loop structure for Pattern 5 with strict requirements:
/// - Exactly 1 break inside `if counter == N { break }`
/// Phase 282 P7: Pattern detection for InfiniteEarlyExit (ExtractionBased)
///
/// This function checks if the loop matches Pattern 5 characteristics.
/// Uses ExtractionBased strategy with extractor as SSOT.
pub(crate) fn can_lower(_builder: &MirBuilder, ctx: &LoopPatternContext) -> bool {
let debug = ctx.debug;
// Phase 282 P7 Step 1: Pattern kind safety valve (O(1) guard)
if ctx.pattern_kind != LoopPatternKind::InfiniteEarlyExit {
if debug {
trace::trace().debug(
"pattern5/can_lower",
&format!(
"Pattern kind mismatch: expected InfiniteEarlyExit, got {:?}",
ctx.pattern_kind
),
);
}
return false;
}
// Phase 282 P7 Step 2: ExtractionBased detection (SSOT)
use super::extractors::pattern5::extract_infinite_early_exit_parts;
match extract_infinite_early_exit_parts(ctx.condition, ctx.body) {
Ok(Some(parts)) => {
if debug {
trace::trace().debug(
"pattern5/can_lower",
&format!(
"✅ Pattern5 detected: break={}, continue={}, return={}, nested={}, continue_at_end={}, break_in_if={}",
parts.break_count,
parts.continue_count,
parts.return_count,
parts.has_nested_loop,
parts.continue_at_end,
parts.break_in_simple_if
),
);
}
// Phase 282 P7 Step 3: Carrier validation (existing logic preserved)
// Pattern5 requires exactly 1 carrier (counter-like variable)
if ctx.features.carrier_count != 1 {
if debug {
trace::trace().debug(
"pattern5/can_lower",
&format!(
"Carrier count mismatch: expected 1, got {}",
ctx.features.carrier_count
),
);
}
return false;
}
true
}
Ok(None) => {
if debug {
trace::trace().debug(
"pattern5/can_lower",
"Not Pattern5 (extraction returned None - structural mismatch)",
);
}
false
}
Err(e) => {
// USER GUIDANCE: Log "unsupported" for Err cases (e.g., return found)
if debug {
trace::trace().debug(
"pattern5/can_lower",
&format!("Pattern5 unsupported: {}", e),
);
}
false
}
}
}
/// Phase 131-11-D: Extract counter variable name from break condition
///
/// Looks for pattern: `if counter == N { break }`
/// Returns the counter variable name
fn extract_counter_name(body: &[ASTNode]) -> Result<String, String> {
use crate::ast::BinaryOperator;
for stmt in body {
if let ASTNode::If {
condition,
then_body,
..
} = stmt
{
// Check if then_body contains just break
if then_body.len() == 1 && matches!(then_body[0], ASTNode::Break { .. }) {
// Extract counter from condition
if let ASTNode::BinaryOp {
operator: BinaryOperator::Equal,
left,
..
} = condition.as_ref()
{
if let ASTNode::Variable { name, .. } = left.as_ref() {
return Ok(name.clone());
}
}
}
}
}
Err("Could not extract counter variable from break condition".to_string())
}
/// Phase 131-11-D: Extract limit constant from break condition
///
/// Looks for pattern: `if counter == LIMIT { break }`
/// Returns the LIMIT value
fn extract_limit_value(body: &[ASTNode]) -> Result<i64, String> {
use crate::ast::{BinaryOperator, LiteralValue};
for stmt in body {
if let ASTNode::If {
condition,
then_body,
..
} = stmt
{
// Check if then_body contains just break
if then_body.len() == 1 && matches!(then_body[0], ASTNode::Break { .. }) {
// Extract limit from condition
if let ASTNode::BinaryOp {
operator: BinaryOperator::Equal,
right,
..
} = condition.as_ref()
{
if let ASTNode::Literal {
value: LiteralValue::Integer(limit),
..
} = right.as_ref()
{
return Ok(*limit);
}
}
}
}
}
Err("Could not extract limit constant from break condition".to_string())
}
/// Phase 282 P7: Lower InfiniteEarlyExit pattern to JoinIR (ExtractionBased)
///
/// # Implementation Status
///
/// Phase 282 P7: Re-extraction for SSOT enforcement
///
/// # JoinIR Structure (post-increment pattern)
///
/// ```text
/// fn main(counter_init):
/// result = loop_step(counter_init)
/// return Void
///
/// fn loop_step(counter):
/// counter_next = counter + 1
/// break_cond = (counter_next == LIMIT)
/// Jump(k_exit, [counter_next], cond=break_cond)
/// Call(loop_step, [counter_next]) // tail call
///
/// fn k_exit(counter_exit):
/// return Void
/// ```
pub(crate) fn lower(
builder: &mut MirBuilder,
ctx: &LoopPatternContext,
) -> Result<Option<ValueId>, String> {
let debug = ctx.debug;
// Phase 282 P7 Step 4: Re-extract to enforce SSOT
use super::extractors::pattern5::extract_infinite_early_exit_parts;
let parts = match extract_infinite_early_exit_parts(ctx.condition, ctx.body) {
Ok(Some(p)) => p,
Ok(None) => {
return Err(
"[pattern5/lower] Extraction returned None (should not happen - can_lower() passed)"
.to_string(),
);
}
Err(e) => {
return Err(format!("[pattern5/lower] Extraction failed: {}", e));
}
};
if debug {
trace::trace().debug(
"pattern5/lower",
&format!(
"Pattern5 lowering: break={}, continue={}, return={}",
parts.break_count, parts.continue_count, parts.return_count
),
);
}
// Existing lowering logic (zero behavior change)
// Step 1: Extract counter variable name (existing helper)
let counter_name = extract_counter_name(ctx.body)?;
if debug {
trace::trace().debug(
"pattern5/lower",
&format!("Extracted counter variable: '{}'", counter_name),
);
}
// Step 2: Get counter ValueId from variable_ctx.variable_map
let counter_id = builder
.variable_ctx
.variable_map
.get(&counter_name)
.copied()
.ok_or_else(|| {
format!(
"Counter variable '{}' not found in variable_ctx.variable_map",
counter_name
)
})?;
if debug {
trace::trace().debug(
"pattern5/lower",
&format!("Counter ValueId: {:?}", counter_id),
);
}
// Step 3: Extract limit value
let limit = extract_limit_value(ctx.body)?;
if debug {
trace::trace().debug(
"pattern5/lower",
&format!("Extracted limit value: {}", limit),
);
}
// Step 4: Generate JoinIR
use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
use crate::mir::join_ir::{
BinOpKind, CompareOp, ConstValue, JoinFuncId, JoinFunction, JoinInst, JoinModule,
MirLikeInst,
};
let mut join_value_space = JoinValueSpace::new();
let mut join_module = JoinModule::new();
// Function IDs
let main_id = JoinFuncId::new(0);
let loop_step_id = JoinFuncId::new(1);
let k_exit_id = JoinFuncId::new(2);
// ValueId allocation
// Phase 286 P1: Use alloc_param() for function parameters (Param region: 100-999)
// Use alloc_local() for local variables (Local region: 1000+)
// main() params/locals
let counter_init = join_value_space.alloc_param(); // ValueId(100) - initial counter (PARAM)
let loop_result = join_value_space.alloc_local(); // ValueId(1000) - result from loop_step (LOCAL)
// loop_step params/locals
let counter_param = join_value_space.alloc_param(); // ValueId(101) - parameter (PARAM)
let const_1 = join_value_space.alloc_local(); // ValueId(1001) - increment constant (LOCAL)
let counter_next = join_value_space.alloc_local(); // ValueId(1002) - counter + 1 (LOCAL)
let const_limit = join_value_space.alloc_local(); // ValueId(1003) - limit constant (LOCAL)
let break_cond = join_value_space.alloc_local(); // ValueId(1004) - counter_next == LIMIT (LOCAL)
// k_exit params/locals
let counter_exit = join_value_space.alloc_param(); // ValueId(102) - exit parameter (PARAM)
// return 0 constant
let const_0 = join_value_space.alloc_local(); // Local constant (LOCAL region)
// ==================================================================
// main() function
// ==================================================================
let mut main_func = JoinFunction::new(main_id, "main".to_string(), vec![counter_init]);
// result = loop_step(counter_init)
main_func.body.push(JoinInst::Call {
func: loop_step_id,
args: vec![counter_init],
k_next: None,
dst: Some(loop_result),
});
// return 0 (Pattern 5 doesn't produce a value, but we return 0 by convention)
main_func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_0,
value: ConstValue::Integer(0),
}));
main_func.body.push(JoinInst::Ret {
value: Some(const_0),
});
join_module.add_function(main_func);
// ==================================================================
// loop_step(counter) function - post-increment pattern
// ==================================================================
let mut loop_step_func =
JoinFunction::new(loop_step_id, "loop_step".to_string(), vec![counter_param]);
// counter_next = counter + 1
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_1,
value: ConstValue::Integer(1),
}));
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: counter_next,
op: BinOpKind::Add,
lhs: counter_param,
rhs: const_1,
}));
// break_cond = (counter_next == LIMIT)
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Const {
dst: const_limit,
value: ConstValue::Integer(limit),
}));
loop_step_func
.body
.push(JoinInst::Compute(MirLikeInst::Compare {
dst: break_cond,
op: CompareOp::Eq,
lhs: counter_next,
rhs: const_limit,
}));
// Jump(k_exit, [counter_next], cond=break_cond)
loop_step_func.body.push(JoinInst::Jump {
cont: k_exit_id.as_cont(),
args: vec![counter_next],
cond: Some(break_cond),
});
// Call(loop_step, [counter_next]) - tail recursion
loop_step_func.body.push(JoinInst::Call {
func: loop_step_id,
args: vec![counter_next],
k_next: None, // tail call
dst: None,
});
join_module.add_function(loop_step_func);
// ==================================================================
// k_exit(counter_exit) function
// ==================================================================
let mut k_exit_func = JoinFunction::new(k_exit_id, "k_exit".to_string(), vec![counter_exit]);
// Return counter_exit (the final counter value) instead of const 0
k_exit_func.body.push(JoinInst::Ret {
value: Some(counter_exit),
});
join_module.add_function(k_exit_func);
// Set entry point
join_module.entry = Some(main_id);
if debug {
trace::trace().debug(
"pattern5/lower",
"Generated JoinIR: main, loop_step, k_exit",
);
}
// Step 5: Create CarrierInfo for the counter (Phase 131-11-D)
// Note: counter is the loop variable, NOT a separate carrier
use crate::mir::join_ir::lowering::carrier_info::{CarrierInfo, ExitMeta};
let carrier_info = CarrierInfo {
loop_var_name: counter_name.clone(),
loop_var_id: counter_id,
carriers: vec![], // No separate carriers - counter is the loop variable itself
trim_helper: None,
promoted_loopbodylocals: Vec::new(),
#[cfg(feature = "normalized_dev")]
promoted_bindings: std::collections::BTreeMap::new(),
};
// Step 6: Create ExitMeta with counter as exit value
// Phase 131-11-D: The counter flows through jump_args and must be registered in exit_values
let exit_meta = ExitMeta {
exit_values: vec![(counter_name.clone(), counter_exit)],
};
// Step 7: Build boundary and merge
use crate::mir::builder::control_flow::joinir::merge::exit_line::ExitMetaCollector;
use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder;
let exit_bindings = ExitMetaCollector::collect(builder, &exit_meta, Some(&carrier_info), debug);
let boundary = JoinInlineBoundaryBuilder::new()
.with_inputs(
vec![counter_init], // JoinIR main() parameter
vec![counter_id], // Host counter value
)
.with_exit_bindings(exit_bindings)
.with_loop_var_name(Some(counter_name.clone())) // Phase 131-11-D: For LoopHeaderPhiBuilder
.with_carrier_info(carrier_info) // Phase 131-11-D: For exit line reconnection
.build();
// Step 7: Execute conversion pipeline
use super::conversion_pipeline::JoinIRConversionPipeline;
let _ = JoinIRConversionPipeline::execute(
builder,
join_module,
Some(&boundary),
"pattern5",
debug,
)?;
// Step 8: Return Void (loops don't produce values)
let void_val = crate::mir::builder::emission::constant::emit_void(builder);
if debug {
trace::trace().debug(
"pattern5/lower",
&format!("Pattern 5 lowering complete, returning Void {:?}", void_val),
);
}
Ok(Some(void_val))
}

View File

@ -358,18 +358,14 @@ pub(crate) struct LoopPatternEntry {
///
/// **IMPORTANT**: Patterns are tried in array order (SSOT).
/// Array order defines priority - earlier entries are tried first.
/// Pattern5 (most specific) → Pattern4 → Pattern3 → Pattern1 → Pattern2
/// Pattern4 → Pattern8 → Pattern9 → Pattern3 → Pattern1 → Pattern2
///
/// # Current Patterns (Structure-based detection, established Phase 131-11+)
///
/// Pattern detection strategies (updated Phase 282 P0):
/// Pattern detection strategies (updated Phase 286):
/// - Pattern6/7: ExtractionBased (Plan line, Phase 273+)
/// - Pattern8/9: ExtractionBased (extraction functions, already implemented)
/// - Pattern1-5: StructureBased (LoopFeatures classification, legacy)
///
/// - Pattern 5: Infinite Loop with Early Exit (llvm_stage3_loop_only.hako) [Phase 131-11]
/// - Detection: pattern_kind == InfiniteEarlyExit
/// - Structure: is_infinite_loop && has_break && has_continue
/// - Pattern5/8/9: ExtractionBased (extraction functions, Plan line Phase 286+)
/// - Pattern1-4: StructureBased (LoopFeatures classification, legacy)
///
/// - Pattern 4: Loop with Continue (loop_continue_pattern4.hako)
/// - Detection: pattern_kind == Pattern4Continue
@ -388,12 +384,8 @@ pub(crate) struct LoopPatternEntry {
/// - Structure: has_break && !has_continue
///
/// Note: func_name is now only used for debug logging, not pattern detection
/// Phase 286: Pattern5 removed (migrated to Plan-based routing)
pub(crate) static LOOP_PATTERNS: &[LoopPatternEntry] = &[
LoopPatternEntry {
name: "Pattern5_InfiniteEarlyExit",
detect: super::pattern5_infinite_early_exit::can_lower,
lower: super::pattern5_infinite_early_exit::lower,
},
LoopPatternEntry {
name: "Pattern4_WithContinue",
detect: super::pattern4_with_continue::can_lower,

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,20 @@
# Plan Normalizer Modules
This directory splits the DomainPlan -> CorePlan normalization into small modules.
Responsibilities:
- Keep pattern-specific knowledge localized per module.
- Share small, stable helpers (AST lowering, block layouts, phi bindings).
- Preserve PlanNormalizer as the single SSOT entry point.
Modules:
- helpers.rs: shared block layouts, phi bindings, AST-to-ValueId lowering helpers.
- pattern_scan_with_init.rs: Pattern6 scan-with-init normalization.
- pattern_split_scan.rs: Pattern7 split-scan normalization.
- pattern4_continue.rs: Pattern4 continue-loop normalization.
- pattern1_simple_while.rs: Pattern1 simple while normalization.
- pattern9_accum_const_loop.rs: Pattern9 accumulator-loop normalization.
- pattern8_bool_predicate_scan.rs: Pattern8 predicate-scan normalization.
- pattern3_if_phi.rs: Pattern3 if-phi normalization.
- pattern2_break.rs: Pattern2 break-loop normalization.
- pattern5_infinite_early_exit.rs: Pattern5 infinite loop with early exit normalization.

View File

@ -0,0 +1,263 @@
use super::CoreEffectPlan;
use crate::mir::builder::MirBuilder;
use crate::mir::{BasicBlockId, BinaryOp, CompareOp, ConstValue, Effect, EffectMask, MirType, ValueId};
use std::collections::BTreeMap;
// ============================================================================
// Phase 286 P2.8: Normalizer Hygiene Helpers
// ============================================================================
/// Standard 5-block layout for simple loops (Pattern1/4/8/9)
///
/// CFG: preheader → header → body → step → header (back-edge)
/// ↓
/// after
#[derive(Debug, Clone, Copy)]
pub(super) struct LoopBlocksStandard5 {
pub(super) preheader_bb: BasicBlockId,
pub(super) header_bb: BasicBlockId,
pub(super) body_bb: BasicBlockId,
pub(super) step_bb: BasicBlockId,
pub(super) after_bb: BasicBlockId,
}
impl LoopBlocksStandard5 {
/// Allocate 5 blocks for a standard loop
pub(super) fn allocate(builder: &mut MirBuilder) -> Result<Self, String> {
let preheader_bb = builder
.current_block
.ok_or_else(|| "[normalizer] No current block for loop entry".to_string())?;
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();
Ok(Self {
preheader_bb,
header_bb,
body_bb,
step_bb,
after_bb,
})
}
}
/// Extended 8-block layout for if-phi loops (Pattern3)
///
/// CFG: preheader → header → body → then/else → merge → step → header
/// ↓
/// after
#[derive(Debug, Clone, Copy)]
pub(super) struct LoopBlocksWithIfPhi {
pub(super) preheader_bb: BasicBlockId,
pub(super) header_bb: BasicBlockId,
pub(super) body_bb: BasicBlockId,
pub(super) then_bb: BasicBlockId,
pub(super) else_bb: BasicBlockId,
pub(super) merge_bb: BasicBlockId,
pub(super) step_bb: BasicBlockId,
pub(super) after_bb: BasicBlockId,
}
impl LoopBlocksWithIfPhi {
/// Allocate 8 blocks for an if-phi loop
pub(super) fn allocate(builder: &mut MirBuilder) -> Result<Self, String> {
let preheader_bb = builder
.current_block
.ok_or_else(|| "[normalizer] No current block for loop entry".to_string())?;
let header_bb = builder.next_block_id();
let body_bb = builder.next_block_id();
let then_bb = builder.next_block_id();
let else_bb = builder.next_block_id();
let merge_bb = builder.next_block_id();
let step_bb = builder.next_block_id();
let after_bb = builder.next_block_id();
Ok(Self {
preheader_bb,
header_bb,
body_bb,
then_bb,
else_bb,
merge_bb,
step_bb,
after_bb,
})
}
}
/// Create phi_bindings map from variable name-ValueId pairs
///
/// phi_bindings are used to override variable_map lookups during AST lowering,
/// ensuring loop variables reference PHI destinations instead of initial values.
pub(super) fn create_phi_bindings(bindings: &[(&str, ValueId)]) -> BTreeMap<String, ValueId> {
bindings
.iter()
.map(|(name, id)| (name.to_string(), *id))
.collect()
}
impl super::PlanNormalizer {
/// Helper: Lower Compare AST to (lhs ValueId, op, rhs ValueId, const_effects)
pub(super) fn lower_compare_ast(
ast: &crate::ast::ASTNode,
builder: &mut MirBuilder,
phi_bindings: &BTreeMap<String, ValueId>,
) -> Result<(ValueId, CompareOp, 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)
pub(super) fn lower_binop_ast(
ast: &crate::ast::ASTNode,
builder: &mut MirBuilder,
phi_bindings: &BTreeMap<String, ValueId>,
) -> Result<(ValueId, BinaryOp, 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)
pub(super) fn lower_value_ast(
ast: &crate::ast::ASTNode,
builder: &mut MirBuilder,
phi_bindings: &BTreeMap<String, ValueId>,
) -> Result<(ValueId, Vec<CoreEffectPlan>), String> {
use crate::ast::{ASTNode, LiteralValue};
match ast {
ASTNode::Variable { name, .. } => {
if let Some(&phi_dst) = phi_bindings.get(name) {
return Ok((phi_dst, vec![]));
}
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, .. } => {
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);
let const_effect = CoreEffectPlan::Const {
dst: value_id,
value: const_value,
};
Ok((value_id, vec![const_effect]))
}
ASTNode::MethodCall { object, method, arguments, .. } => {
let object_name = match object.as_ref() {
ASTNode::Variable { name, .. } => name.clone(),
_ => {
return Err(format!(
"[normalizer] Method call on non-variable object: {:?}",
object
))
}
};
let object_id = if let Some(&phi_dst) = phi_bindings.get(&object_name) {
phi_dst
} else if let Some(&value_id) = builder.variable_ctx.variable_map.get(&object_name) {
value_id
} else {
return Err(format!(
"[normalizer] Method call object {} not found",
object_name
));
};
let mut arg_ids = Vec::new();
let mut arg_effects = Vec::new();
for arg in arguments {
let (arg_id, mut effects) = Self::lower_value_ast(arg, builder, phi_bindings)?;
arg_ids.push(arg_id);
arg_effects.append(&mut effects);
}
let result_id = builder.next_value_id();
builder.type_ctx.value_types.insert(result_id, MirType::Integer);
arg_effects.push(CoreEffectPlan::MethodCall {
dst: Some(result_id),
object: object_id,
method: method.clone(),
args: arg_ids,
effects: EffectMask::PURE.add(Effect::Io),
});
Ok((result_id, arg_effects))
}
_ => Err(format!("[normalizer] Unsupported value AST: {:?}", ast)),
}
}
}

View File

@ -0,0 +1,69 @@
//! Phase 273 P1: PlanNormalizer - DomainPlan → CorePlan 変換 (SSOT)
//!
//! # Responsibilities
//!
//! - Convert DomainPlan to CorePlan (SSOT for pattern-specific knowledge)
//! - Generate ValueIds for CorePlan expressions
//! - Expand pattern-specific operations into generic CoreEffectPlan
//!
//! # Key Design Decision
//!
//! Normalizer is the ONLY place that knows pattern-specific semantics.
//! Lowerer processes CorePlan without any pattern knowledge.
mod helpers;
mod pattern1_simple_while;
mod pattern2_break;
mod pattern3_if_phi;
mod pattern4_continue;
mod pattern5_infinite_early_exit;
mod pattern8_bool_predicate_scan;
mod pattern9_accum_const_loop;
mod pattern_scan_with_init;
mod pattern_split_scan;
use super::{
CoreEffectPlan, CoreLoopPlan, CorePhiInfo, CorePlan, DomainPlan, Pattern1SimpleWhilePlan,
Pattern2BreakPlan, Pattern3IfPhiPlan, Pattern4ContinuePlan, Pattern5ExitKind,
Pattern5InfiniteEarlyExitPlan, Pattern8BoolPredicateScanPlan, Pattern9AccumConstLoopPlan,
ScanWithInitPlan, SplitScanPlan,
};
use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext;
use crate::mir::builder::MirBuilder;
/// Phase 273 P1: PlanNormalizer - DomainPlan → CorePlan 変換 (SSOT)
pub(in crate::mir::builder) struct PlanNormalizer;
impl PlanNormalizer {
/// Normalize DomainPlan to CorePlan
///
/// This is the SSOT for pattern-specific knowledge expansion.
/// All pattern semantics (scan, split, etc.) are expanded here.
pub(in crate::mir::builder) fn normalize(
builder: &mut MirBuilder,
domain: DomainPlan,
ctx: &LoopPatternContext,
) -> Result<CorePlan, String> {
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)
}
DomainPlan::Pattern9AccumConstLoop(parts) => {
Self::normalize_pattern9_accum_const_loop(builder, parts, ctx)
}
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)
}
}
}
}

View File

@ -0,0 +1,181 @@
use super::helpers::{create_phi_bindings, LoopBlocksStandard5};
use super::{CoreEffectPlan, CoreLoopPlan, CorePhiInfo, CorePlan, Pattern1SimpleWhilePlan};
use crate::mir::basic_block::EdgeArgs;
use crate::mir::builder::control_flow::edgecfg::api::{BranchStub, EdgeStub, ExitKind, Frag};
use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext;
use crate::mir::builder::MirBuilder;
use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout;
use crate::mir::MirType;
use std::collections::BTreeMap;
impl super::PlanNormalizer {
/// 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)
pub(super) 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-3: Allocate BasicBlockIds
let blocks = LoopBlocksStandard5::allocate(builder)?;
let LoopBlocksStandard5 {
preheader_bb,
header_bb,
body_bb,
step_bb,
after_bb,
} = blocks;
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);
let cond_loop = builder.alloc_typed(MirType::Bool);
let loop_var_next = builder.alloc_typed(MirType::Integer);
// Step 5: Build phi_bindings
let phi_bindings = create_phi_bindings(&[(&parts.loop_var, loop_var_current)]);
// Step 6: Lower AST expressions
let (loop_cond_lhs, loop_cond_op, loop_cond_rhs, loop_cond_consts) =
Self::lower_compare_ast(&parts.condition, builder, &phi_bindings)?;
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
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
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![]),
(step_bb, step_effects),
];
// Step 10: Build PHI
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![
EdgeStub {
from: body_bb,
kind: ExitKind::Normal,
target: Some(step_bb),
args: empty_args.clone(),
},
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
let loop_plan = CoreLoopPlan {
preheader_bb,
header_bb,
body_bb,
step_bb,
after_bb,
found_bb: after_bb,
body: vec![],
cond_loop,
cond_match: cond_loop,
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))
}
}

View File

@ -0,0 +1,299 @@
use super::helpers::create_phi_bindings;
use super::{CoreEffectPlan, CoreLoopPlan, CorePhiInfo, CorePlan, Pattern2BreakPlan};
use crate::mir::basic_block::EdgeArgs;
use crate::mir::builder::control_flow::edgecfg::api::{BranchStub, EdgeStub, ExitKind, Frag};
use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext;
use crate::mir::builder::MirBuilder;
use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout;
use crate::mir::{BinaryOp, ConstValue, MirType};
use std::collections::BTreeMap;
impl super::PlanNormalizer {
/// Pattern2Break → CorePlan 変換
///
/// CFG structure (6 blocks):
/// ```
/// preheader → header(PHI: i_current, carrier_current)
/// ↓
/// body(break_cond check)
/// ↓
/// ┌────┴────┐
/// break_then step
/// (optional ↓
/// update) header (back-edge)
/// ↓
/// after_bb(PHI: carrier_out)
/// ↑
/// header (natural exit when !cond_loop)
/// ```
///
/// Key: after_bb PHI merges break path and natural exit path carrier values.
pub(super) fn normalize_pattern2_break(
builder: &mut MirBuilder,
parts: Pattern2BreakPlan,
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/pattern2_break",
&format!(
"Phase 286 P3.1: Normalizing Pattern2Break for {} (loop_var={}, carrier_var={})",
ctx.func_name, parts.loop_var, parts.carrier_var
),
);
}
// Step 1: Block allocation (6 blocks)
let preheader_bb = builder
.current_block
.ok_or_else(|| "Pattern2Break: 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/pattern2_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 initial values from variable_map
let loop_var_init = builder
.variable_ctx
.variable_map
.get(&parts.loop_var)
.copied()
.ok_or_else(|| {
format!(
"Pattern2Break: loop_var '{}' not in variable_map",
parts.loop_var
)
})?;
let carrier_init = builder
.variable_ctx
.variable_map
.get(&parts.carrier_var)
.copied()
.ok_or_else(|| {
format!(
"Pattern2Break: carrier_var '{}' not in variable_map",
parts.carrier_var
)
})?;
// Step 3: ValueId allocation
let loop_var_current = builder.alloc_typed(MirType::Integer);
let carrier_current = builder.alloc_typed(MirType::Integer);
let cond_loop = builder.alloc_typed(MirType::Bool);
let cond_break = builder.alloc_typed(MirType::Bool);
let carrier_break = builder.alloc_typed(MirType::Integer);
let carrier_step = builder.alloc_typed(MirType::Integer);
let loop_var_next = builder.alloc_typed(MirType::Integer);
let carrier_out = builder.alloc_typed(MirType::Integer);
// Step 4: phi_bindings for AST lowering
let phi_bindings = create_phi_bindings(&[
(&parts.loop_var, loop_var_current),
(&parts.carrier_var, carrier_current),
]);
// Step 5: Lower AST expressions
let (loop_cond_lhs, loop_cond_op, loop_cond_rhs, loop_cond_consts) =
Self::lower_compare_ast(&parts.loop_condition, builder, &phi_bindings)?;
let (break_cond_lhs, break_cond_op, break_cond_rhs, break_cond_consts) =
Self::lower_compare_ast(&parts.break_condition, builder, &phi_bindings)?;
let break_then_effects = if let Some(ref break_update_ast) = parts.carrier_update_in_break {
let (lhs, op, rhs, consts) =
Self::lower_binop_ast(break_update_ast, builder, &phi_bindings)?;
let mut effects = consts;
effects.push(CoreEffectPlan::BinOp {
dst: carrier_break,
lhs,
op,
rhs,
});
effects
} else {
let zero = builder.alloc_typed(MirType::Integer);
vec![
CoreEffectPlan::Const {
dst: zero,
value: ConstValue::Integer(0),
},
CoreEffectPlan::BinOp {
dst: carrier_break,
lhs: carrier_current,
op: BinaryOp::Add,
rhs: zero,
},
]
};
let (carrier_lhs, carrier_op, carrier_rhs, carrier_consts) =
Self::lower_binop_ast(&parts.carrier_update_in_body, builder, &phi_bindings)?;
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
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 plans (break condition check)
let mut body_plans: Vec<CorePlan> = Vec::new();
for const_effect in break_cond_consts {
body_plans.push(CorePlan::Effect(const_effect));
}
body_plans.push(CorePlan::Effect(CoreEffectPlan::Compare {
dst: cond_break,
lhs: break_cond_lhs,
op: break_cond_op,
rhs: break_cond_rhs,
}));
// Step 8: Build step_effects
let mut step_effects = carrier_consts;
step_effects.push(CoreEffectPlan::BinOp {
dst: carrier_step,
lhs: carrier_lhs,
op: carrier_op,
rhs: carrier_rhs,
});
step_effects.extend(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![]),
(break_then_bb, break_then_effects),
(step_bb, step_effects),
];
// 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_var_{}", parts.loop_var),
},
CorePhiInfo {
block: header_bb,
dst: carrier_current,
inputs: vec![(preheader_bb, carrier_init), (step_bb, carrier_step)],
tag: format!("carrier_{}", parts.carrier_var),
},
CorePhiInfo {
block: after_bb,
dst: carrier_out,
inputs: vec![(header_bb, carrier_current), (break_then_bb, carrier_break)],
tag: format!("after_{}", parts.carrier_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,
else_target: after_bb,
then_args: empty_args.clone(),
else_args: empty_args.clone(),
},
BranchStub {
from: body_bb,
cond: cond_break,
then_target: break_then_bb,
else_target: step_bb,
then_args: empty_args.clone(),
else_args: empty_args.clone(),
},
];
let wires = vec![
EdgeStub {
from: break_then_bb,
kind: ExitKind::Normal,
target: Some(after_bb),
args: empty_args.clone(),
},
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),
(parts.carrier_var.clone(), carrier_out),
];
// Step 13: Build CoreLoopPlan
let loop_plan = CoreLoopPlan {
preheader_bb,
header_bb,
body_bb,
step_bb,
after_bb,
found_bb: after_bb,
body: body_plans,
cond_loop,
cond_match: cond_break,
block_effects,
phis,
frag,
final_values,
};
if debug {
trace_logger.debug(
"normalizer/pattern2_break",
"CorePlan construction complete (6 blocks, 3 PHIs, after_bb PHI is key!)",
);
}
Ok(CorePlan::Loop(loop_plan))
}
}

View File

@ -0,0 +1,287 @@
use super::helpers::{create_phi_bindings, LoopBlocksWithIfPhi};
use super::{CoreEffectPlan, CoreLoopPlan, CorePhiInfo, CorePlan, Pattern3IfPhiPlan};
use crate::mir::basic_block::EdgeArgs;
use crate::mir::builder::control_flow::edgecfg::api::{BranchStub, EdgeStub, ExitKind, Frag};
use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext;
use crate::mir::builder::MirBuilder;
use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout;
use crate::mir::MirType;
use std::collections::BTreeMap;
impl super::PlanNormalizer {
/// Phase 286 P2.6.1: Pattern3IfPhi → CorePlan 変換
///
/// Expands Pattern3 (Loop with If-Phi) semantics into generic CorePlan:
/// - CFG structure: preheader → header → body → then/else → merge → step → header
/// - 3 PHIs: 2 in header (loop_var, carrier), 1 in merge (carrier_next)
/// - If-else branching with PHI merge (no Select instruction)
pub(super) fn normalize_pattern3_if_phi(
builder: &mut MirBuilder,
parts: Pattern3IfPhiPlan,
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/pattern3_if_phi",
&format!(
"Phase 286 P2.6.1: Normalizing Pattern3IfPhi for {} (loop_var: {}, carrier_var: {})",
ctx.func_name, parts.loop_var, parts.carrier_var
),
);
}
// 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(&parts.carrier_var)
.copied()
.ok_or_else(|| format!("[normalizer] Carrier variable {} not found", parts.carrier_var))?;
// Step 2-3: Allocate BasicBlockIds
let blocks = LoopBlocksWithIfPhi::allocate(builder)?;
let LoopBlocksWithIfPhi {
preheader_bb,
header_bb,
body_bb,
then_bb,
else_bb,
merge_bb,
step_bb,
after_bb,
} = blocks;
if debug {
trace_logger.debug(
"normalizer/pattern3_if_phi",
&format!(
"Allocated: preheader={:?}, header={:?}, body={:?}, then={:?}, else={:?}, merge={:?}, step={:?}, after={:?}",
preheader_bb,
header_bb,
body_bb,
then_bb,
else_bb,
merge_bb,
step_bb,
after_bb
),
);
}
// Step 4: Allocate ValueIds for loop control
let loop_var_current = builder.alloc_typed(MirType::Integer);
let carrier_current = builder.alloc_typed(MirType::Integer);
let cond_loop = builder.alloc_typed(MirType::Bool);
let cond_if = builder.alloc_typed(MirType::Bool);
let carrier_then = builder.alloc_typed(MirType::Integer);
let carrier_else = builder.alloc_typed(MirType::Integer);
let carrier_next = builder.alloc_typed(MirType::Integer);
let loop_var_next = builder.alloc_typed(MirType::Integer);
// Step 5: Build phi_bindings
let phi_bindings = create_phi_bindings(&[
(&parts.loop_var, loop_var_current),
(&parts.carrier_var, carrier_current),
]);
// Step 6: Lower AST expressions
let (loop_cond_lhs, loop_cond_op, loop_cond_rhs, loop_cond_consts) =
Self::lower_compare_ast(&parts.condition, builder, &phi_bindings)?;
let (if_cond_lhs, if_cond_op, if_cond_rhs, if_cond_consts) =
Self::lower_compare_ast(&parts.if_condition, builder, &phi_bindings)?;
let (then_lhs, then_op, then_rhs, then_consts) =
Self::lower_binop_ast(&parts.then_update, builder, &phi_bindings)?;
let (else_lhs, else_op, else_rhs, else_consts) =
Self::lower_binop_ast(&parts.else_update, builder, &phi_bindings)?;
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
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 body plans
let mut body_plans: Vec<CorePlan> = Vec::new();
for const_effect in if_cond_consts {
body_plans.push(CorePlan::Effect(const_effect));
}
body_plans.push(CorePlan::Effect(CoreEffectPlan::Compare {
dst: cond_if,
lhs: if_cond_lhs,
op: if_cond_op,
rhs: if_cond_rhs,
}));
// Step 9: Build then_effects
let mut then_effects = then_consts;
then_effects.push(CoreEffectPlan::BinOp {
dst: carrier_then,
lhs: then_lhs,
op: then_op,
rhs: then_rhs,
});
// Step 10: Build else_effects
let mut else_effects = else_consts;
else_effects.push(CoreEffectPlan::BinOp {
dst: carrier_else,
lhs: else_lhs,
op: else_op,
rhs: else_rhs,
});
// Step 11: Build step_effects
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 12: Build block_effects
let block_effects = vec![
(preheader_bb, vec![]),
(header_bb, header_effects),
(body_bb, vec![]),
(then_bb, then_effects),
(else_bb, else_effects),
(merge_bb, vec![]),
(step_bb, step_effects),
];
// Step 13: 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_var_{}", parts.loop_var),
},
CorePhiInfo {
block: header_bb,
dst: carrier_current,
inputs: vec![(preheader_bb, carrier_init), (step_bb, carrier_next)],
tag: format!("carrier_{}", parts.carrier_var),
},
CorePhiInfo {
block: merge_bb,
dst: carrier_next,
inputs: vec![(then_bb, carrier_then), (else_bb, carrier_else)],
tag: format!("merge_{}", parts.carrier_var),
},
];
// Step 14: 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(),
},
BranchStub {
from: body_bb,
cond: cond_if,
then_target: then_bb,
then_args: empty_args.clone(),
else_target: else_bb,
else_args: empty_args.clone(),
},
];
let wires = vec![
EdgeStub {
from: then_bb,
kind: ExitKind::Normal,
target: Some(merge_bb),
args: empty_args.clone(),
},
EdgeStub {
from: else_bb,
kind: ExitKind::Normal,
target: Some(merge_bb),
args: empty_args.clone(),
},
EdgeStub {
from: merge_bb,
kind: ExitKind::Normal,
target: Some(step_bb),
args: empty_args.clone(),
},
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 15: Build final_values
let final_values = vec![
(parts.loop_var.clone(), loop_var_current),
(parts.carrier_var.clone(), carrier_current),
];
// Step 16: Build CoreLoopPlan
let loop_plan = CoreLoopPlan {
preheader_bb,
header_bb,
body_bb,
step_bb,
after_bb,
found_bb: after_bb,
body: body_plans,
cond_loop,
cond_match: cond_if,
block_effects,
phis,
frag,
final_values,
};
if debug {
trace_logger.debug(
"normalizer/pattern3_if_phi",
"CorePlan construction complete (7 blocks, 3 PHIs)",
);
}
Ok(CorePlan::Loop(loop_plan))
}
}

View File

@ -0,0 +1,299 @@
use super::helpers::create_phi_bindings;
use super::{CoreEffectPlan, CoreLoopPlan, CorePhiInfo, CorePlan, Pattern4ContinuePlan};
use crate::mir::basic_block::EdgeArgs;
use crate::mir::builder::control_flow::edgecfg::api::{BranchStub, EdgeStub, ExitKind, Frag};
use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext;
use crate::mir::builder::MirBuilder;
use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout;
use crate::mir::MirType;
use std::collections::BTreeMap;
impl super::PlanNormalizer {
/// 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)
pub(super) fn normalize_pattern4_continue(
builder: &mut MirBuilder,
parts: 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
let loop_var_current = builder.alloc_typed(MirType::Integer);
let carrier_current = builder.alloc_typed(MirType::Integer);
// Step 4.5: Build phi_bindings
let phi_bindings = create_phi_bindings(&[
(&parts.loop_var, loop_var_current),
(carrier_var, carrier_current),
]);
let cond_loop = builder.alloc_typed(MirType::Bool);
let cond_continue = builder.alloc_typed(MirType::Bool);
let loop_var_cont_next = builder.alloc_typed(MirType::Integer);
let carrier_updated = builder.alloc_typed(MirType::Integer);
let loop_var_norm_next = builder.alloc_typed(MirType::Integer);
// Step 5: Lower AST expressions to get operands and const definitions
let (loop_cond_lhs, loop_cond_op, loop_cond_rhs, loop_cond_consts) =
Self::lower_compare_ast(&parts.condition, builder, &phi_bindings)?;
let (cont_cond_lhs, cont_cond_op, cont_cond_rhs, cont_cond_consts) =
Self::lower_compare_ast(&parts.continue_condition, builder, &phi_bindings)?;
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)?;
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();
for const_effect in cont_cond_consts {
body.push(CorePlan::Effect(const_effect));
}
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
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 9: Build normal_path effects
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![]),
(continue_path_bb, continue_path_effects),
(normal_path_bb, normal_path_effects),
(step_continue_bb, vec![]),
(step_normal_bb, vec![]),
];
// Step 11: Build PHIs
let phis = vec![
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),
},
CorePhiInfo {
block: header_bb,
dst: carrier_current,
inputs: vec![
(preheader_bb, carrier_init),
(step_continue_bb, carrier_current),
(step_normal_bb, carrier_updated),
],
tag: format!("carrier_{}", carrier_var),
},
];
// Step 12: 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(),
},
BranchStub {
from: body_bb,
cond: cond_continue,
then_target: continue_path_bb,
then_args: empty_args.clone(),
else_target: normal_path_bb,
else_args: empty_args.clone(),
},
];
let wires = vec![
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(),
},
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,
after_bb,
found_bb: after_bb,
body,
cond_loop,
cond_match: cond_continue,
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))
}
}

View File

@ -0,0 +1,542 @@
use super::helpers::create_phi_bindings;
use super::{
CoreEffectPlan, CoreLoopPlan, CorePhiInfo, CorePlan, Pattern5ExitKind,
Pattern5InfiniteEarlyExitPlan,
};
use crate::mir::basic_block::EdgeArgs;
use crate::mir::builder::control_flow::edgecfg::api::{BranchStub, EdgeStub, ExitKind, Frag};
use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext;
use crate::mir::builder::MirBuilder;
use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout;
use crate::mir::{ConstValue, MirType, ValueId};
use std::collections::BTreeMap;
impl super::PlanNormalizer {
/// 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)
/// ```
pub(super) 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();
let step_bb = builder.next_block_id();
let after_bb = builder.next_block_id();
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);
let cond_exit = builder.alloc_typed(MirType::Bool);
let loop_var_next = builder.alloc_typed(MirType::Integer);
let true_val = builder.alloc_typed(MirType::Bool);
// Step 4: phi_bindings for AST lowering
let phi_bindings = create_phi_bindings(&[(&parts.loop_var, loop_var_current)]);
// Step 5: Lower AST expressions
let (exit_cond_lhs, exit_cond_op, exit_cond_rhs, exit_cond_consts) =
Self::lower_compare_ast(&parts.exit_condition, builder, &phi_bindings)?;
let return_value: Option<(ValueId, crate::ast::ASTNode)> =
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
};
let (inc_lhs, inc_op, inc_rhs, inc_consts) =
Self::lower_binop_ast(&parts.loop_increment, builder, &phi_bindings)?;
// Step 6: Build header_effects
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();
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 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 {
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 {
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
let block_effects = vec![
(preheader_bb, vec![]),
(header_bb, header_effects),
(body_bb, vec![]),
(found_bb, found_effects),
(step_bb, step_effects),
(after_bb, vec![]),
];
// Step 12: Build Frag with Return exit
let empty_args = EdgeArgs {
layout: JumpArgsLayout::CarriersOnly,
values: vec![],
};
let branches = vec![
BranchStub {
from: header_bb,
cond: true_val,
then_target: body_bb,
else_target: after_bb,
then_args: empty_args.clone(),
else_args: empty_args.clone(),
},
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(),
},
];
let return_args = EdgeArgs {
layout: JumpArgsLayout::CarriersOnly,
values: if let Some(val_id) = return_val_id {
vec![val_id]
} else {
vec![]
},
};
let wires = vec![
EdgeStub {
from: step_bb,
kind: ExitKind::Normal,
target: Some(header_bb),
args: empty_args.clone(),
},
EdgeStub {
from: found_bb,
kind: ExitKind::Return,
target: None,
args: return_args,
},
EdgeStub {
from: after_bb,
kind: ExitKind::Return,
target: None,
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)];
// 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,
cond_match: cond_exit,
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);
let carrier_current = builder.alloc_typed(MirType::Integer);
let cond_exit = builder.alloc_typed(MirType::Bool);
let carrier_step = builder.alloc_typed(MirType::Integer);
let loop_var_next = builder.alloc_typed(MirType::Integer);
let carrier_out = builder.alloc_typed(MirType::Integer);
let true_val = builder.alloc_typed(MirType::Bool);
// 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
let (exit_cond_lhs, exit_cond_op, exit_cond_rhs, exit_cond_consts) =
Self::lower_compare_ast(&parts.exit_condition, builder, &phi_bindings)?;
let (carrier_lhs, carrier_op, carrier_rhs, carrier_consts) =
Self::lower_binop_ast(carrier_update_ast, builder, &phi_bindings)?;
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)
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
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),
},
CorePhiInfo {
block: after_bb,
dst: carrier_out,
inputs: vec![(break_then_bb, carrier_current)],
tag: format!("exit_carrier_{}", carrier_var),
},
];
// Step 12: Build block_effects
let block_effects = vec![
(preheader_bb, vec![]),
(header_bb, header_effects),
(body_bb, vec![]),
(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![
BranchStub {
from: header_bb,
cond: true_val,
then_target: body_bb,
else_target: after_bb,
then_args: empty_args.clone(),
else_args: empty_args.clone(),
},
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(),
},
];
let loop_id = crate::mir::control_form::LoopId(0);
let wires = vec![
EdgeStub {
from: break_then_bb,
kind: ExitKind::Break(loop_id),
target: Some(after_bb),
args: empty_args.clone(),
},
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),
];
// Step 15: Build CoreLoopPlan
let loop_plan = CoreLoopPlan {
preheader_bb,
header_bb,
body_bb,
step_bb,
after_bb,
found_bb: after_bb,
body: body_plans,
cond_loop: true_val,
cond_match: cond_exit,
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))
}
}

View File

@ -0,0 +1,301 @@
use super::helpers::create_phi_bindings;
use super::{CoreEffectPlan, CoreLoopPlan, CorePhiInfo, CorePlan, Pattern8BoolPredicateScanPlan};
use crate::mir::basic_block::EdgeArgs;
use crate::mir::builder::control_flow::edgecfg::api::{BranchStub, EdgeStub, ExitKind, Frag};
use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext;
use crate::mir::builder::MirBuilder;
use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout;
use crate::mir::{BinaryOp, CompareOp, ConstValue, Effect, EffectMask, MirType};
use std::collections::BTreeMap;
impl super::PlanNormalizer {
/// Pattern8BoolPredicateScan → CorePlan 変換 (Phase 286 P2.4.1)
///
/// Expands Pattern8 (Bool Predicate Scan) semantics into generic CorePlan:
/// - CFG structure: preheader → header → body → found (return false) / step → header
/// - 1 PHI for loop variable (i)
/// - Early exit: return false in found_bb
/// - Post-loop: return true in after_bb
pub(super) fn normalize_pattern8_bool_predicate_scan(
builder: &mut MirBuilder,
parts: Pattern8BoolPredicateScanPlan,
ctx: &LoopPatternContext,
) -> Result<CorePlan, String> {
use crate::mir::builder::control_flow::edgecfg::api::compose;
use crate::mir::builder::control_flow::joinir::trace;
let trace_logger = trace::trace();
let debug = ctx.debug;
if debug {
trace_logger.debug(
"normalizer/pattern8_bool_predicate_scan",
&format!(
"Phase 286 P2.4.1: Normalizing Pattern8 for {} (loop_var: {}, predicate: {}.{})",
ctx.func_name, parts.loop_var, parts.predicate_receiver, parts.predicate_method
),
);
}
// P0 Scope: Forward scan (step=1) only
if parts.step_lit != 1 {
return Err(format!(
"[normalizer/pattern8] P0 scope: only forward scan supported (step={})",
parts.step_lit
));
}
// Step 1: Get host ValueIds for variables
let haystack_host = builder
.variable_ctx
.variable_map
.get(&parts.haystack)
.copied()
.ok_or_else(|| format!("[normalizer/pattern8] Variable {} not found", parts.haystack))?;
let predicate_receiver_host = builder
.variable_ctx
.variable_map
.get(&parts.predicate_receiver)
.copied()
.ok_or_else(|| {
format!(
"[normalizer/pattern8] Variable {} not found",
parts.predicate_receiver
)
})?;
let loop_var_init = builder
.variable_ctx
.variable_map
.get(&parts.loop_var)
.copied()
.ok_or_else(|| format!("[normalizer/pattern8] Loop variable {} not found", parts.loop_var))?;
// Step 2: Capture preheader block
let preheader_bb = builder
.current_block
.ok_or_else(|| "[normalizer/pattern8] No current block for loop entry".to_string())?;
// Step 3: Allocate BasicBlockIds for 6 blocks
let header_bb = builder.next_block_id();
let body_bb = builder.next_block_id();
let found_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/pattern8_bool_predicate_scan",
&format!(
"Allocated: preheader={:?}, header={:?}, body={:?}, found={:?}, step={:?}, after={:?}",
preheader_bb, header_bb, body_bb, found_bb, step_bb, after_bb
),
);
}
// Step 4: Allocate ValueIds
let loop_var_current = builder.alloc_typed(MirType::Integer);
let cond_loop = builder.alloc_typed(MirType::Bool);
let one_val = builder.alloc_typed(MirType::Integer);
let i_plus_one = builder.alloc_typed(MirType::Integer);
let ch = builder.alloc_typed(MirType::String);
let cond_predicate = builder.alloc_typed(MirType::Bool);
let cond_not_predicate = builder.alloc_typed(MirType::Bool);
let loop_var_next = builder.alloc_typed(MirType::Integer);
let true_val = builder.alloc_typed(MirType::Bool);
let false_val = builder.alloc_typed(MirType::Bool);
// Step 5: Build phi_bindings
let phi_bindings = create_phi_bindings(&[(&parts.loop_var, loop_var_current)]);
// Step 6: Lower loop condition
let (loop_cond_lhs, loop_cond_op, loop_cond_rhs, loop_cond_consts) =
Self::lower_compare_ast(&parts.condition, builder, &phi_bindings)?;
// Step 7: Build header_effects
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 body (predicate check)
let body = vec![
CorePlan::Effect(CoreEffectPlan::Const {
dst: one_val,
value: ConstValue::Integer(1),
}),
CorePlan::Effect(CoreEffectPlan::Const {
dst: false_val,
value: ConstValue::Bool(false),
}),
CorePlan::Effect(CoreEffectPlan::BinOp {
dst: i_plus_one,
lhs: loop_var_current,
op: BinaryOp::Add,
rhs: one_val,
}),
CorePlan::Effect(CoreEffectPlan::MethodCall {
dst: Some(ch),
object: haystack_host,
method: "substring".to_string(),
args: vec![loop_var_current, i_plus_one],
effects: EffectMask::PURE.add(Effect::Io),
}),
CorePlan::Effect(CoreEffectPlan::MethodCall {
dst: Some(cond_predicate),
object: predicate_receiver_host,
method: parts.predicate_method.clone(),
args: vec![ch],
effects: EffectMask::PURE.add(Effect::Io),
}),
CorePlan::Effect(CoreEffectPlan::Compare {
dst: cond_not_predicate,
lhs: cond_predicate,
op: CompareOp::Eq,
rhs: false_val,
}),
];
// Step 9: Build step_effects
let step_effects = vec![CoreEffectPlan::BinOp {
dst: loop_var_next,
lhs: loop_var_current,
op: BinaryOp::Add,
rhs: one_val,
}];
// Step 10: Build found_bb effects (empty)
let found_effects = vec![];
// Step 11: Build after_bb effects
let after_effects = vec![CoreEffectPlan::Const {
dst: true_val,
value: ConstValue::Bool(true),
}];
// Step 12: Build block_effects
let block_effects = vec![
(preheader_bb, vec![]),
(header_bb, header_effects),
(body_bb, vec![]),
(found_bb, found_effects),
(step_bb, step_effects),
(after_bb, after_effects),
];
// Step 13: Build PHI
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 14: Build Frag using compose::cleanup for early exit
let empty_args = EdgeArgs {
layout: JumpArgsLayout::CarriersOnly,
values: vec![],
};
let ret_false_args = EdgeArgs {
layout: JumpArgsLayout::CarriersOnly,
values: vec![false_val],
};
let ret_true_args = EdgeArgs {
layout: JumpArgsLayout::CarriersOnly,
values: vec![true_val],
};
let main_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(),
},
BranchStub {
from: body_bb,
cond: cond_not_predicate,
then_target: found_bb,
then_args: empty_args.clone(),
else_target: step_bb,
else_args: empty_args.clone(),
},
];
let main_wires = vec![EdgeStub {
from: step_bb,
kind: ExitKind::Normal,
target: Some(header_bb),
args: empty_args.clone(),
}];
let main_frag = Frag {
entry: header_bb,
exits: BTreeMap::new(),
wires: main_wires,
branches: main_branches,
};
let cleanup_exits = vec![
EdgeStub {
from: found_bb,
kind: ExitKind::Return,
target: None,
args: ret_false_args,
},
EdgeStub {
from: after_bb,
kind: ExitKind::Return,
target: None,
args: ret_true_args,
},
];
let cleanup_frag = Frag {
entry: found_bb,
exits: BTreeMap::from([(ExitKind::Return, cleanup_exits)]),
wires: vec![],
branches: vec![],
};
let frag = compose::cleanup(main_frag, cleanup_frag, None, None)
.expect("compose::cleanup() failed in normalize_pattern8_bool_predicate_scan");
// Step 15: Build final_values
let final_values = vec![(parts.loop_var.clone(), loop_var_current)];
// Step 16: Build CoreLoopPlan
let loop_plan = CoreLoopPlan {
preheader_bb,
header_bb,
body_bb,
step_bb,
after_bb,
found_bb,
body,
cond_loop,
cond_match: cond_not_predicate,
block_effects,
phis,
frag,
final_values,
};
if debug {
trace_logger.debug(
"normalizer/pattern8_bool_predicate_scan",
"CorePlan construction complete (5 blocks, 1 PHI, 2 Return exits)",
);
}
Ok(CorePlan::Loop(loop_plan))
}
}

View File

@ -0,0 +1,214 @@
use super::helpers::{create_phi_bindings, LoopBlocksStandard5};
use super::{CoreEffectPlan, CoreLoopPlan, CorePhiInfo, CorePlan, Pattern9AccumConstLoopPlan};
use crate::mir::basic_block::EdgeArgs;
use crate::mir::builder::control_flow::edgecfg::api::{BranchStub, EdgeStub, ExitKind, Frag};
use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext;
use crate::mir::builder::MirBuilder;
use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout;
use crate::mir::MirType;
use std::collections::BTreeMap;
impl super::PlanNormalizer {
/// Phase 286 P2.3: Pattern9AccumConstLoop → CorePlan 変換
///
/// Expands Pattern9 (Accumulator Const Loop) semantics into generic CorePlan:
/// - CFG structure: preheader → header → body → step → header (back-edge)
/// - 2 PHIs in header: loop variable (i) and accumulator (sum)
/// - Similar to Pattern1 but with an additional carrier
pub(super) fn normalize_pattern9_accum_const_loop(
builder: &mut MirBuilder,
parts: Pattern9AccumConstLoopPlan,
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/pattern9_accum_const_loop",
&format!(
"Phase 286 P2.3: Normalizing Pattern9AccumConstLoop for {} (loop_var: {}, acc_var: {})",
ctx.func_name, parts.loop_var, parts.acc_var
),
);
}
// 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 acc_var_init = builder
.variable_ctx
.variable_map
.get(&parts.acc_var)
.copied()
.ok_or_else(|| format!("[normalizer] Accumulator variable {} not found", parts.acc_var))?;
// Step 2-3: Allocate BasicBlockIds
let blocks = LoopBlocksStandard5::allocate(builder)?;
let LoopBlocksStandard5 {
preheader_bb,
header_bb,
body_bb,
step_bb,
after_bb,
} = blocks;
if debug {
trace_logger.debug(
"normalizer/pattern9_accum_const_loop",
&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);
let acc_var_current = builder.alloc_typed(MirType::Integer);
let cond_loop = builder.alloc_typed(MirType::Bool);
let acc_var_next = builder.alloc_typed(MirType::Integer);
let loop_var_next = builder.alloc_typed(MirType::Integer);
// Step 5: Build phi_bindings
let phi_bindings = create_phi_bindings(&[
(&parts.loop_var, loop_var_current),
(&parts.acc_var, acc_var_current),
]);
// Step 6: Lower AST expressions
let (loop_cond_lhs, loop_cond_op, loop_cond_rhs, loop_cond_consts) =
Self::lower_compare_ast(&parts.condition, builder, &phi_bindings)?;
let (acc_update_lhs, acc_update_op, acc_update_rhs, acc_update_consts) =
Self::lower_binop_ast(&parts.acc_update, builder, &phi_bindings)?;
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
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
let mut step_effects = acc_update_consts;
step_effects.push(CoreEffectPlan::BinOp {
dst: acc_var_next,
lhs: acc_update_lhs,
op: acc_update_op,
rhs: acc_update_rhs,
});
step_effects.extend(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![]),
(step_bb, step_effects),
];
// 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_var_{}", parts.loop_var),
},
CorePhiInfo {
block: header_bb,
dst: acc_var_current,
inputs: vec![(preheader_bb, acc_var_init), (step_bb, acc_var_next)],
tag: format!("acc_var_{}", parts.acc_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![
EdgeStub {
from: body_bb,
kind: ExitKind::Normal,
target: Some(step_bb),
args: empty_args.clone(),
},
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),
(parts.acc_var.clone(), acc_var_current),
];
// Step 13: Build CoreLoopPlan
let loop_plan = CoreLoopPlan {
preheader_bb,
header_bb,
body_bb,
step_bb,
after_bb,
found_bb: after_bb,
body: vec![],
cond_loop,
cond_match: cond_loop,
block_effects,
phis,
frag,
final_values,
};
if debug {
trace_logger.debug(
"normalizer/pattern9_accum_const_loop",
"CorePlan construction complete (4 blocks, 2 PHIs)",
);
}
Ok(CorePlan::Loop(loop_plan))
}
}

View File

@ -0,0 +1,342 @@
use super::{CoreEffectPlan, CoreLoopPlan, CorePhiInfo, CorePlan, ScanWithInitPlan};
use crate::mir::basic_block::EdgeArgs;
use crate::mir::builder::control_flow::edgecfg::api::{BranchStub, EdgeStub, ExitKind, Frag};
use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext;
use crate::mir::builder::MirBuilder;
use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout;
use crate::mir::{BinaryOp, CompareOp, ConstValue, Effect, EffectMask, MirType};
use std::collections::BTreeMap;
impl super::PlanNormalizer {
/// ScanWithInit → CorePlan 変換
///
/// Expands scan-specific semantics into generic CorePlan:
/// - header_effects: one=1, needle_len, len, bound, cond_loop
/// - body: i+needle_len, substring, cond_match
/// - step_effects: i_next = i + 1
pub(super) fn normalize_scan_with_init(
builder: &mut MirBuilder,
parts: ScanWithInitPlan,
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/scan_with_init",
&format!(
"Phase 273 P1: Normalizing ScanWithInit for {}",
ctx.func_name
),
);
}
// P1 Scope: Forward scan (step=1) only
if parts.step_lit != 1 {
return Err(format!(
"[normalizer] P1 scope: only forward scan supported (step={})",
parts.step_lit
));
}
// Step 1: Get host ValueIds for variables
let s_host = builder
.variable_ctx
.variable_map
.get(&parts.haystack)
.copied()
.ok_or_else(|| format!("[normalizer] Variable {} not found", parts.haystack))?;
let needle_host = builder
.variable_ctx
.variable_map
.get(&parts.needle)
.copied()
.ok_or_else(|| format!("[normalizer] Variable {} not found", parts.needle))?;
let i_init_val = 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 (entry to loop) for PHI input
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();
let found_bb = builder.next_block_id();
// Step 4: Allocate ValueIds for CorePlan
let i_current = builder.next_value_id(); // PHI destination
builder
.type_ctx
.value_types
.insert(i_current, MirType::Integer);
let one_val = builder.next_value_id();
builder
.type_ctx
.value_types
.insert(one_val, MirType::Integer);
let needle_len_val = if parts.dynamic_needle {
let v = builder.next_value_id();
builder.type_ctx.value_types.insert(v, MirType::Integer);
v
} else {
one_val // reuse one_val for fixed needle
};
let len_val = builder.next_value_id();
builder
.type_ctx
.value_types
.insert(len_val, MirType::Integer);
let bound_val = builder.next_value_id();
builder
.type_ctx
.value_types
.insert(bound_val, MirType::Integer);
let cond_loop = builder.next_value_id();
builder
.type_ctx
.value_types
.insert(cond_loop, MirType::Bool);
let i_plus_needle_len = builder.next_value_id();
builder
.type_ctx
.value_types
.insert(i_plus_needle_len, MirType::Integer);
let window_val = builder.next_value_id();
builder
.type_ctx
.value_types
.insert(window_val, MirType::String);
let cond_match = builder.next_value_id();
builder
.type_ctx
.value_types
.insert(cond_match, MirType::Bool);
let i_next_val = builder.next_value_id();
builder
.type_ctx
.value_types
.insert(i_next_val, MirType::Integer);
if debug {
trace_logger.debug(
"normalizer/scan_with_init",
&format!(
"Allocated: preheader={:?}, header={:?}, body={:?}, step={:?}, after={:?}, found={:?}",
preheader_bb, header_bb, body_bb, step_bb, after_bb, found_bb
),
);
}
// Step 5: Build header_effects (emitted in header_bb)
let mut header_effects = vec![
// one = 1
CoreEffectPlan::Const {
dst: one_val,
value: ConstValue::Integer(1),
},
];
// needle_len = needle.length() if dynamic, else reuse one_val
if parts.dynamic_needle {
header_effects.push(CoreEffectPlan::MethodCall {
dst: Some(needle_len_val),
object: needle_host,
method: "length".to_string(),
args: vec![],
effects: EffectMask::PURE.add(Effect::Io),
});
}
// len = s.length()
header_effects.push(CoreEffectPlan::MethodCall {
dst: Some(len_val),
object: s_host,
method: "length".to_string(),
args: vec![],
effects: EffectMask::PURE.add(Effect::Io),
});
// bound = len - needle_len
header_effects.push(CoreEffectPlan::BinOp {
dst: bound_val,
lhs: len_val,
op: BinaryOp::Sub,
rhs: needle_len_val,
});
// cond_loop = i <= bound
header_effects.push(CoreEffectPlan::Compare {
dst: cond_loop,
lhs: i_current,
op: CompareOp::Le,
rhs: bound_val,
});
// Step 6: Build body (emitted in body_bb)
let body = vec![
// i_plus_needle_len = i + needle_len
CorePlan::Effect(CoreEffectPlan::BinOp {
dst: i_plus_needle_len,
lhs: i_current,
op: BinaryOp::Add,
rhs: needle_len_val,
}),
// window = s.substring(i, i + needle_len)
CorePlan::Effect(CoreEffectPlan::MethodCall {
dst: Some(window_val),
object: s_host,
method: "substring".to_string(),
args: vec![i_current, i_plus_needle_len],
effects: EffectMask::PURE.add(Effect::Io),
}),
// cond_match = window == needle
CorePlan::Effect(CoreEffectPlan::Compare {
dst: cond_match,
lhs: window_val,
op: CompareOp::Eq,
rhs: needle_host,
}),
];
// Step 7: Build step_effects (emitted in step_bb)
let step_effects = vec![
// i_next = i + 1
CoreEffectPlan::BinOp {
dst: i_next_val,
lhs: i_current,
op: BinaryOp::Add,
rhs: one_val,
},
];
// Step 8: Build block_effects (Phase 273 P2)
let block_effects = vec![
(preheader_bb, vec![]),
(header_bb, header_effects.clone()),
(body_bb, vec![]),
(step_bb, step_effects.clone()),
];
// Step 9: Build phis (Phase 273 P2)
let phis = vec![CorePhiInfo {
block: header_bb,
dst: i_current,
inputs: vec![(preheader_bb, i_init_val), (step_bb, i_next_val)],
tag: format!("loop_carrier_{}", parts.loop_var),
}];
// Step 10: Build Frag (Phase 273 P2)
let empty_args = EdgeArgs {
layout: JumpArgsLayout::CarriersOnly,
values: vec![],
};
let ret_found_args = EdgeArgs {
layout: JumpArgsLayout::CarriersOnly,
values: vec![i_current],
};
use crate::mir::builder::control_flow::edgecfg::api::compose;
let main_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(),
},
BranchStub {
from: body_bb,
cond: cond_match,
then_target: found_bb,
then_args: empty_args.clone(),
else_target: step_bb,
else_args: empty_args.clone(),
},
];
let main_wires = vec![EdgeStub {
from: step_bb,
kind: ExitKind::Normal,
target: Some(header_bb),
args: empty_args.clone(),
}];
let main_frag = Frag {
entry: header_bb,
exits: BTreeMap::new(),
wires: main_wires,
branches: main_branches,
};
let cleanup_exits = vec![EdgeStub {
from: found_bb,
kind: ExitKind::Return,
target: None,
args: ret_found_args,
}];
let cleanup_frag = Frag {
entry: found_bb,
exits: BTreeMap::from([(ExitKind::Return, cleanup_exits)]),
wires: vec![],
branches: vec![],
};
let frag = compose::cleanup(main_frag, cleanup_frag, None, None)
.expect("compose::cleanup() failed in normalize_scan_with_init");
// Step 11: Build final_values (Phase 273 P2)
let final_values = vec![(parts.loop_var.clone(), i_current)];
// Step 12: Build CoreLoopPlan (Phase 273 P3: all generalized fields REQUIRED)
let loop_plan = CoreLoopPlan {
preheader_bb,
header_bb,
body_bb,
step_bb,
after_bb,
found_bb,
body,
cond_loop,
cond_match,
block_effects,
phis,
frag,
final_values,
};
if debug {
trace_logger.debug(
"normalizer/scan_with_init",
"CorePlan construction complete",
);
}
Ok(CorePlan::Loop(loop_plan))
}
}

View File

@ -0,0 +1,439 @@
use super::{CoreEffectPlan, CoreLoopPlan, CorePhiInfo, CorePlan, SplitScanPlan};
use crate::mir::basic_block::EdgeArgs;
use crate::mir::builder::control_flow::edgecfg::api::{BranchStub, EdgeStub, ExitKind, Frag};
use crate::mir::builder::control_flow::joinir::patterns::router::LoopPatternContext;
use crate::mir::builder::MirBuilder;
use crate::mir::join_ir::lowering::inline_boundary::JumpArgsLayout;
use crate::mir::{BinaryOp, CompareOp, ConstValue, Effect, EffectMask, MirType};
use std::collections::BTreeMap;
impl super::PlanNormalizer {
/// SplitScan → CorePlan 変換
///
/// Expands split-specific semantics into generic CorePlan:
/// - 2 carriers: i (loop index), start (segment start)
/// - 6 blocks: preheader, header, body, then, else, step, after
/// - 4 PHI nodes: header (i_current, start_current) + step (i_next, start_next)
/// - Side effect: result.push(segment) in then_bb
pub(super) fn normalize_split_scan(
builder: &mut MirBuilder,
parts: SplitScanPlan,
ctx: &LoopPatternContext,
) -> Result<CorePlan, String> {
use crate::mir::builder::control_flow::joinir::trace;
use crate::mir::builder::control_flow::edgecfg::api::compose;
let trace_logger = trace::trace();
let debug = ctx.debug;
if debug {
trace_logger.debug(
"normalizer/split_scan",
&format!(
"Phase 273 P2: Normalizing SplitScan for {}",
ctx.func_name
),
);
}
// Step 1: Get host ValueIds for variables
let s_host = builder
.variable_ctx
.variable_map
.get(&parts.s_var)
.copied()
.ok_or_else(|| format!("[normalizer] Variable {} not found", parts.s_var))?;
let sep_host = builder
.variable_ctx
.variable_map
.get(&parts.sep_var)
.copied()
.ok_or_else(|| format!("[normalizer] Variable {} not found", parts.sep_var))?;
let result_host = builder
.variable_ctx
.variable_map
.get(&parts.result_var)
.copied()
.ok_or_else(|| format!("[normalizer] Variable {} not found", parts.result_var))?;
let i_init_val = builder
.variable_ctx
.variable_map
.get(&parts.i_var)
.copied()
.ok_or_else(|| format!("[normalizer] Variable {} not found", parts.i_var))?;
let start_init_val = builder
.variable_ctx
.variable_map
.get(&parts.start_var)
.copied()
.ok_or_else(|| format!("[normalizer] Variable {} not found", parts.start_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 6 blocks
let header_bb = builder.next_block_id();
let body_bb = builder.next_block_id();
let then_bb = builder.next_block_id();
let else_bb = builder.next_block_id();
let step_bb = builder.next_block_id();
let after_bb = builder.next_block_id();
// Step 4: Allocate ValueIds for PHI destinations (before blocks)
let i_current = builder.next_value_id();
builder
.type_ctx
.value_types
.insert(i_current, MirType::Integer);
let start_current = builder.next_value_id();
builder
.type_ctx
.value_types
.insert(start_current, MirType::Integer);
let i_next = builder.next_value_id();
builder
.type_ctx
.value_types
.insert(i_next, MirType::Integer);
let start_next = builder.next_value_id();
builder
.type_ctx
.value_types
.insert(start_next, MirType::Integer);
// Step 5: Allocate ValueIds for expressions
let sep_len = builder.next_value_id();
builder
.type_ctx
.value_types
.insert(sep_len, MirType::Integer);
let s_len = builder.next_value_id();
builder.type_ctx.value_types.insert(s_len, MirType::Integer);
let limit = builder.next_value_id();
builder.type_ctx.value_types.insert(limit, MirType::Integer);
let cond_loop = builder.next_value_id();
builder
.type_ctx
.value_types
.insert(cond_loop, MirType::Bool);
let i_plus_sep = builder.next_value_id();
builder
.type_ctx
.value_types
.insert(i_plus_sep, MirType::Integer);
let chunk = builder.next_value_id();
builder.type_ctx.value_types.insert(chunk, MirType::String);
let cond_match = builder.next_value_id();
builder
.type_ctx
.value_types
.insert(cond_match, MirType::Bool);
let segment = builder.next_value_id();
builder
.type_ctx
.value_types
.insert(segment, MirType::String);
let start_next_then = builder.next_value_id();
builder
.type_ctx
.value_types
.insert(start_next_then, MirType::Integer);
let one = builder.next_value_id();
builder.type_ctx.value_types.insert(one, MirType::Integer);
let i_next_else = builder.next_value_id();
builder
.type_ctx
.value_types
.insert(i_next_else, MirType::Integer);
if debug {
trace_logger.debug(
"normalizer/split_scan",
&format!(
"Allocated: preheader={:?}, header={:?}, body={:?}, then={:?}, else={:?}, step={:?}, after={:?}",
preheader_bb, header_bb, body_bb, then_bb, else_bb, step_bb, after_bb
),
);
}
// Step 6: Build header_effects
let header_effects = vec![
// sep_len = sep.length()
CoreEffectPlan::MethodCall {
dst: Some(sep_len),
object: sep_host,
method: "length".to_string(),
args: vec![],
effects: EffectMask::PURE.add(Effect::Io),
},
// s_len = s.length()
CoreEffectPlan::MethodCall {
dst: Some(s_len),
object: s_host,
method: "length".to_string(),
args: vec![],
effects: EffectMask::PURE.add(Effect::Io),
},
// limit = s_len - sep_len
CoreEffectPlan::BinOp {
dst: limit,
lhs: s_len,
op: BinaryOp::Sub,
rhs: sep_len,
},
// cond_loop = i <= limit
CoreEffectPlan::Compare {
dst: cond_loop,
lhs: i_current,
op: CompareOp::Le,
rhs: limit,
},
];
// Step 7: Build body effects and plans
let body = vec![
// i_plus_sep = i + sep_len
CorePlan::Effect(CoreEffectPlan::BinOp {
dst: i_plus_sep,
lhs: i_current,
op: BinaryOp::Add,
rhs: sep_len,
}),
// chunk = s.substring(i, i_plus_sep)
CorePlan::Effect(CoreEffectPlan::MethodCall {
dst: Some(chunk),
object: s_host,
method: "substring".to_string(),
args: vec![i_current, i_plus_sep],
effects: EffectMask::PURE.add(Effect::Io),
}),
// cond_match = chunk == sep
CorePlan::Effect(CoreEffectPlan::Compare {
dst: cond_match,
lhs: chunk,
op: CompareOp::Eq,
rhs: sep_host,
}),
];
// Step 8: Build then_effects (push + updates)
let then_effects = vec![
// segment = s.substring(start, i)
CoreEffectPlan::MethodCall {
dst: Some(segment),
object: s_host,
method: "substring".to_string(),
args: vec![start_current, i_current],
effects: EffectMask::PURE.add(Effect::Io),
},
// result.push(segment) - Side effect!
CoreEffectPlan::MethodCall {
dst: None,
object: result_host,
method: "push".to_string(),
args: vec![segment],
effects: EffectMask::MUT,
},
// start_next_then = i + sep_len
CoreEffectPlan::BinOp {
dst: start_next_then,
lhs: i_current,
op: BinaryOp::Add,
rhs: sep_len,
},
];
// Step 9: Build else_effects (increment i)
let else_effects = vec![
// one = const 1
CoreEffectPlan::Const {
dst: one,
value: ConstValue::Integer(1),
},
// i_next_else = i + 1
CoreEffectPlan::BinOp {
dst: i_next_else,
lhs: i_current,
op: BinaryOp::Add,
rhs: one,
},
];
// Step 9.5: Build Frags for compose::if_() (Phase 281 P0)
let empty_args = EdgeArgs {
layout: JumpArgsLayout::CarriersOnly,
values: vec![],
};
let mut then_exits = BTreeMap::new();
then_exits.insert(
ExitKind::Normal,
vec![EdgeStub {
from: then_bb,
kind: ExitKind::Normal,
target: None,
args: empty_args.clone(),
}],
);
let then_frag = Frag {
entry: then_bb,
exits: then_exits,
wires: vec![],
branches: vec![],
};
let mut else_exits = BTreeMap::new();
else_exits.insert(
ExitKind::Normal,
vec![EdgeStub {
from: else_bb,
kind: ExitKind::Normal,
target: None,
args: empty_args.clone(),
}],
);
let else_frag = Frag {
entry: else_bb,
exits: else_exits,
wires: vec![],
branches: vec![],
};
let step_frag = Frag {
entry: step_bb,
exits: BTreeMap::new(),
wires: vec![],
branches: vec![],
};
let body_if_frag = compose::if_(
body_bb,
cond_match,
then_frag,
empty_args.clone(),
else_frag,
empty_args.clone(),
step_frag,
);
// Step 10: Build block_effects
let block_effects = vec![
(preheader_bb, vec![]),
(header_bb, header_effects.clone()),
(body_bb, vec![]),
(then_bb, then_effects),
(else_bb, else_effects),
(step_bb, vec![]),
];
// Step 11: Build phis (4 PHIs: 2 in header + 2 in step)
let phis = vec![
CorePhiInfo {
block: header_bb,
dst: i_current,
inputs: vec![(preheader_bb, i_init_val), (step_bb, i_next)],
tag: format!("loop_carrier_i_{}", parts.i_var),
},
CorePhiInfo {
block: header_bb,
dst: start_current,
inputs: vec![(preheader_bb, start_init_val), (step_bb, start_next)],
tag: format!("loop_carrier_start_{}", parts.start_var),
},
CorePhiInfo {
block: step_bb,
dst: i_next,
inputs: vec![(then_bb, start_next_then), (else_bb, i_next_else)],
tag: format!("step_phi_i_{}", parts.i_var),
},
CorePhiInfo {
block: step_bb,
dst: start_next,
inputs: vec![(then_bb, start_next_then), (else_bb, start_current)],
tag: format!("step_phi_start_{}", parts.start_var),
},
];
// Step 12: Build Frag
let mut 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(),
}];
branches.extend(body_if_frag.branches);
let mut wires = Vec::new();
wires.extend(body_if_frag.wires);
wires.push(EdgeStub {
from: step_bb,
kind: ExitKind::Normal,
target: Some(header_bb),
args: empty_args.clone(),
});
let mut exits = BTreeMap::new();
for (kind, stubs) in body_if_frag.exits {
exits.insert(kind, stubs);
}
let frag = Frag {
entry: header_bb,
exits,
wires,
branches,
};
// Step 13: Build final_values (i, start for post-loop)
let final_values = vec![(parts.i_var.clone(), i_current), (parts.start_var.clone(), start_current)];
// Step 14: Build CoreLoopPlan
let loop_plan = CoreLoopPlan {
preheader_bb,
header_bb,
body_bb,
step_bb,
after_bb,
found_bb: after_bb,
body,
cond_loop,
cond_match,
block_effects,
phis,
frag,
final_values,
};
if debug {
trace_logger.debug(
"normalizer/split_scan",
"CorePlan construction complete (6 blocks, 4 PHIs)",
);
}
Ok(CorePlan::Loop(loop_plan))
}
}