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:
@ -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)
|
||||
|
||||
@ -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))
|
||||
}
|
||||
@ -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,
|
||||
|
||||
Reference in New Issue
Block a user