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,