Add pattern router tests and tidy pattern4 lowering

This commit is contained in:
nyash-codex
2025-12-11 04:22:08 +09:00
parent d4f90976da
commit 00ecddbbc9
3 changed files with 394 additions and 337 deletions

View File

@ -36,6 +36,8 @@ use crate::mir::builder::MirBuilder;
use crate::mir::ValueId;
use super::super::trace;
use crate::mir::loop_pattern_detection::error_messages;
use crate::mir::join_ir::lowering::loop_update_analyzer::UpdateExpr;
use std::collections::BTreeMap;
/// Phase 194+: Detection function for Pattern 4
///
@ -144,261 +146,258 @@ impl MirBuilder {
_func_name: &str,
debug: bool,
) -> Result<Option<ValueId>, String> {
use crate::mir::join_ir::lowering::loop_with_continue_minimal::lower_loop_with_continue_minimal;
use super::pattern4_carrier_analyzer::Pattern4CarrierAnalyzer;
// Phase 195: Use unified trace
trace::trace().debug("pattern4", "Calling Pattern 4 minimal lowerer");
// Phase 33-23: Use Pattern4CarrierAnalyzer for normalization
// This transforms: if (cond) { body } else { continue }
// into: if (!cond) { continue } else { body }
let normalized_body = Pattern4CarrierAnalyzer::normalize_continue_branches(_body);
let body_to_analyze = &normalized_body;
let prepared = prepare_pattern4_context(self, condition, _body)?;
lower_pattern4_joinir(self, condition, &prepared, debug)
}
}
// Phase 179-B: Use PatternPipelineContext for unified preprocessing
// Note: Pattern 4 still needs inline processing for carrier analysis
use super::pattern_pipeline::{build_pattern_context, PatternVariant};
let ctx = build_pattern_context(
self,
condition,
body_to_analyze,
PatternVariant::Pattern4,
)?;
/// Preprocessed data for Pattern 4 lowering.
struct Pattern4Prepared {
loop_var_name: String,
loop_scope: crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape,
carrier_info: crate::mir::join_ir::lowering::carrier_info::CarrierInfo,
carrier_updates: BTreeMap<String, UpdateExpr>,
}
// Extract from context
let loop_var_name = ctx.loop_var_name.clone();
let _loop_var_id = ctx.loop_var_id;
let carrier_info_prelim = ctx.carrier_info.clone();
let scope = ctx.loop_scope.clone();
/// Normalize, build context, analyze carriers, and promote loop-body locals.
fn prepare_pattern4_context(
builder: &mut MirBuilder,
condition: &ASTNode,
body: &[ASTNode],
) -> Result<Pattern4Prepared, String> {
use super::pattern4_carrier_analyzer::Pattern4CarrierAnalyzer;
use super::pattern_pipeline::{build_pattern_context, PatternVariant};
use crate::mir::loop_pattern_detection::loop_condition_scope::LoopConditionScopeBox;
use crate::mir::loop_pattern_detection::loop_body_cond_promoter::{
LoopBodyCondPromoter, ConditionPromotionRequest, ConditionPromotionResult,
};
// Phase 33-23: Use Pattern4CarrierAnalyzer for carrier filtering
// Analyze carrier update expressions FIRST to identify actual carriers
let carrier_updates = Pattern4CarrierAnalyzer::analyze_carrier_updates(
body_to_analyze,
&carrier_info_prelim.carriers,
);
// Normalize continue branches for analysis/lowering
let normalized_body = Pattern4CarrierAnalyzer::normalize_continue_branches(body);
// Phase 33-23: Filter carriers using the new analyzer
// This prevents constant variables (like M, args) from being treated as carriers
// Phase 171-C-4: carrier_info is now mutable for promotion merging
let mut carrier_info = Pattern4CarrierAnalyzer::analyze_carriers(
body_to_analyze,
&carrier_info_prelim,
)?;
// Build preprocessing context
let ctx = build_pattern_context(
builder,
condition,
&normalized_body,
PatternVariant::Pattern4,
)?;
let loop_var_name = ctx.loop_var_name.clone();
let loop_scope = ctx.loop_scope.clone();
let carrier_info_prelim = ctx.carrier_info.clone();
// Analyze carrier updates and filter carriers
let carrier_updates = Pattern4CarrierAnalyzer::analyze_carrier_updates(
&normalized_body,
&carrier_info_prelim.carriers,
);
let mut carrier_info = Pattern4CarrierAnalyzer::analyze_carriers(
&normalized_body,
&carrier_info_prelim,
)?;
trace::trace().debug(
"pattern4",
&format!(
"CarrierInfo: loop_var={}, carriers={:?}",
carrier_info.loop_var_name,
carrier_info.carriers.iter().map(|c| &c.name).collect::<Vec<_>>()
)
);
trace::trace().debug(
"pattern4",
&format!("Analyzed {} carrier update expressions", carrier_updates.len())
);
for (carrier_name, update_expr) in &carrier_updates {
trace::trace().debug(
"pattern4",
&format!(
"CarrierInfo: loop_var={}, carriers={:?}",
carrier_info.loop_var_name,
carrier_info.carriers.iter().map(|c| &c.name).collect::<Vec<_>>()
)
&format!(" {}{:?}", carrier_name, update_expr)
);
}
trace::trace().debug(
"pattern4",
&format!("Analyzed {} carrier update expressions", carrier_updates.len())
);
for (carrier_name, update_expr) in &carrier_updates {
trace::trace().debug(
"pattern4",
&format!(" {}{:?}", carrier_name, update_expr)
);
}
// LoopBodyLocal promotion for conditions (skip_whitespace etc.)
let continue_cond = LoopBodyCondPromoter::extract_continue_condition(&normalized_body);
let conditions_to_analyze: Vec<&ASTNode> = if let Some(cont_cond) = continue_cond {
vec![condition, cont_cond]
} else {
vec![condition]
};
// Phase 195: Use unified trace
trace::trace().varmap("pattern4_start", &self.variable_map);
let cond_scope = LoopConditionScopeBox::analyze(
&loop_var_name,
&conditions_to_analyze,
Some(&loop_scope),
);
// Phase 223-3: LoopBodyLocal Condition Promotion
//
// Check for LoopBodyLocal in loop/continue conditions and attempt promotion.
// Safe Trim patterns (Category A-3: skip_whitespace) are promoted to carriers.
{
use crate::mir::loop_pattern_detection::loop_condition_scope::LoopConditionScopeBox;
use crate::mir::loop_pattern_detection::loop_body_cond_promoter::{
LoopBodyCondPromoter, ConditionPromotionRequest, ConditionPromotionResult,
};
if cond_scope.has_loop_body_local() {
let promotion_req = ConditionPromotionRequest {
loop_param_name: &loop_var_name,
cond_scope: &cond_scope,
scope_shape: Some(&loop_scope),
break_cond: None, // Pattern 4 has no break
continue_cond,
loop_body: &normalized_body,
};
// Extract continue condition from body (if present)
// Handles skip_whitespace pattern: if ch == " " || ... { continue }
let continue_cond = LoopBodyCondPromoter::extract_continue_condition(body_to_analyze);
match LoopBodyCondPromoter::try_promote_for_condition(promotion_req) {
ConditionPromotionResult::Promoted {
carrier_info: promoted_carrier,
promoted_var,
carrier_name,
} => {
trace::trace().debug(
"pattern4/cond_promoter",
&format!(
"LoopBodyLocal '{}' promoted to carrier '{}'",
promoted_var, carrier_name
),
);
// Analyze both header condition and continue condition for LoopBodyLocal
let conditions_to_analyze: Vec<&ASTNode> = if let Some(cont_cond) = continue_cond {
vec![condition, cont_cond]
} else {
vec![condition]
};
carrier_info.merge_from(&promoted_carrier);
let cond_scope = LoopConditionScopeBox::analyze(
&loop_var_name,
&conditions_to_analyze,
Some(&scope),
);
if cond_scope.has_loop_body_local() {
// Try promotion using LoopBodyCondPromoter
let promotion_req = ConditionPromotionRequest {
loop_param_name: &loop_var_name,
cond_scope: &cond_scope,
scope_shape: Some(&scope),
break_cond: None, // Pattern 4 has no break
continue_cond,
loop_body: body_to_analyze,
};
match LoopBodyCondPromoter::try_promote_for_condition(promotion_req) {
ConditionPromotionResult::Promoted {
carrier_info: promoted_carrier,
promoted_var,
trace::trace().debug(
"pattern4/cond_promoter",
&format!(
"Merged carrier '{}' into CarrierInfo (total carriers: {})",
carrier_name,
} => {
eprintln!(
"[pattern4/cond_promoter] LoopBodyLocal '{}' promoted to carrier '{}'",
promoted_var, carrier_name
carrier_info.carrier_count()
),
);
if let Some(helper) = carrier_info.trim_helper() {
if helper.is_safe_trim() {
trace::trace().debug(
"pattern4/cond_promoter",
"Safe Trim/skip_whitespace pattern detected",
);
// Merge promoted carrier into existing CarrierInfo
carrier_info.merge_from(&promoted_carrier);
eprintln!(
"[pattern4/cond_promoter] Merged carrier '{}' into CarrierInfo (total carriers: {})",
carrier_name,
carrier_info.carrier_count()
trace::trace().debug(
"pattern4/cond_promoter",
&format!(
"Carrier: '{}', original var: '{}', whitespace chars: {:?}",
helper.carrier_name, helper.original_var, helper.whitespace_chars
),
);
// Check if this is a safe Trim pattern
if let Some(helper) = carrier_info.trim_helper() {
if helper.is_safe_trim() {
eprintln!(
"[pattern4/cond_promoter] Safe Trim/skip_whitespace pattern detected"
);
eprintln!(
"[pattern4/cond_promoter] Carrier: '{}', original var: '{}', whitespace chars: {:?}",
helper.carrier_name, helper.original_var, helper.whitespace_chars
);
// Continue with Pattern4 lowering (fall through)
} else {
return Err(error_messages::format_error_pattern4_trim_not_safe(
&helper.carrier_name,
helper.whitespace_count()
));
}
}
// Carrier promoted and merged, proceed with normal lowering
}
ConditionPromotionResult::CannotPromote { reason, vars } => {
// Fail-Fast on promotion failure
return Err(error_messages::format_error_pattern4_promotion_failed(&vars, &reason));
} else {
return Err(error_messages::format_error_pattern4_trim_not_safe(
&helper.carrier_name,
helper.whitespace_count()
));
}
}
}
}
// Phase 202-C: Create JoinValueSpace for unified ValueId allocation
use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
let mut join_value_space = JoinValueSpace::new();
// Phase 169 / Phase 202-C: Call Pattern 4 lowerer with JoinValueSpace
let (join_module, exit_meta) = match lower_loop_with_continue_minimal(
scope,
condition,
self,
&carrier_info,
&carrier_updates,
&mut join_value_space,
) {
Ok(result) => result,
Err(e) => {
// Phase 195: Use unified trace
trace::trace().debug("pattern4", &format!("Pattern 4 lowerer failed: {}", e));
return Err(error_messages::format_error_pattern4_lowering_failed(&e));
}
};
trace::trace().debug(
"pattern4",
&format!("ExitMeta: {} exit bindings", exit_meta.exit_values.len())
);
for (carrier_name, join_value) in &exit_meta.exit_values {
trace::trace().debug(
"pattern4",
&format!(" {} → ValueId({})", carrier_name, join_value.0)
);
}
// Phase 179 Task 2: Use ExitMetaCollector for unified exit binding generation
// Replaces manual loop (Phase 196 implementation) with modular Box approach
// Phase 228-8: Pass carrier_info to include ConditionOnly carriers
use super::super::merge::exit_line::meta_collector::ExitMetaCollector;
let exit_bindings = ExitMetaCollector::collect(
self,
&exit_meta,
Some(&carrier_info), // Phase 228-8: Include ConditionOnly carriers
debug,
);
// Validate that all expected carriers have bindings
// (ExitMetaCollector silently skips missing carriers, but Pattern 4 expects all)
for carrier in &carrier_info.carriers {
if !exit_bindings.iter().any(|b| b.carrier_name == carrier.name) {
return Err(error_messages::format_error_pattern4_carrier_not_found(&carrier.name));
ConditionPromotionResult::CannotPromote { reason, vars } => {
return Err(error_messages::format_error_pattern4_promotion_failed(&vars, &reason));
}
}
// Phase 196: Build host_inputs dynamically
// Order: [loop_var, carrier1, carrier2, ...]
let mut host_inputs = vec![carrier_info.loop_var_id];
for carrier in &carrier_info.carriers {
host_inputs.push(carrier.host_id);
}
trace::trace().debug(
"pattern4",
&format!("host_inputs: {:?}", host_inputs.iter().map(|v| v.0).collect::<Vec<_>>())
);
// Merge JoinIR blocks into current function
// Phase 196: Use dynamically generated exit_bindings and host_inputs
// Build join_inputs dynamically to match host_inputs length:
// [ValueId(0), ValueId(1), ValueId(2), ...] for i + N carriers
let mut join_inputs = vec![ValueId(0)]; // ValueId(0) = i_init in JoinIR
for idx in 0..carrier_info.carriers.len() {
join_inputs.push(ValueId((idx + 1) as u32)); // ValueId(1..N) = carrier inits
}
trace::trace().debug(
"pattern4",
&format!("join_inputs: {:?}", join_inputs.iter().map(|v| v.0).collect::<Vec<_>>())
);
// Phase 201: Use JoinInlineBoundaryBuilder for clean construction
// Canonical Builder pattern - see docs/development/current/main/joinir-boundary-builder-pattern.md
use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder;
let boundary = JoinInlineBoundaryBuilder::new()
.with_inputs(join_inputs, host_inputs) // Dynamic carrier count
.with_exit_bindings(exit_bindings)
.with_loop_var_name(Some(loop_var_name.clone())) // Phase 33-19: Enable exit PHI collection
.with_carrier_info(carrier_info.clone()) // Phase 228-6: Pass carrier_info for ConditionOnly header PHI init
.build();
// Phase 33-22: Use JoinIRConversionPipeline for unified conversion flow
use super::conversion_pipeline::JoinIRConversionPipeline;
let _result_val = JoinIRConversionPipeline::execute(
self,
join_module,
Some(&boundary),
"pattern4",
debug,
)?;
// Phase 33-19: Like Pattern3, return void (loop result is read via variable_map)
let void_val = crate::mir::builder::emission::constant::emit_void(self);
// Phase 195: Use unified trace
trace::trace().debug("pattern4", &format!("Loop complete, returning Void {:?}", void_val));
Ok(Some(void_val))
}
Ok(Pattern4Prepared {
loop_var_name,
loop_scope,
carrier_info,
carrier_updates,
})
}
/// JoinIR lowering stage for Pattern 4 using prepared context.
fn lower_pattern4_joinir(
builder: &mut MirBuilder,
condition: &ASTNode,
prepared: &Pattern4Prepared,
debug: bool,
) -> Result<Option<ValueId>, String> {
use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
use crate::mir::join_ir::lowering::loop_with_continue_minimal::lower_loop_with_continue_minimal;
use super::super::merge::exit_line::meta_collector::ExitMetaCollector;
use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder;
use super::conversion_pipeline::JoinIRConversionPipeline;
trace::trace().varmap("pattern4_start", &builder.variable_map);
let mut join_value_space = JoinValueSpace::new();
let (join_module, exit_meta) = match lower_loop_with_continue_minimal(
prepared.loop_scope.clone(),
condition,
builder,
&prepared.carrier_info,
&prepared.carrier_updates,
&mut join_value_space,
) {
Ok(result) => result,
Err(e) => {
trace::trace().debug("pattern4", &format!("Pattern 4 lowerer failed: {}", e));
return Err(error_messages::format_error_pattern4_lowering_failed(&e));
}
};
trace::trace().debug(
"pattern4",
&format!("ExitMeta: {} exit bindings", exit_meta.exit_values.len())
);
for (carrier_name, join_value) in &exit_meta.exit_values {
trace::trace().debug(
"pattern4",
&format!(" {} → ValueId({})", carrier_name, join_value.0)
);
}
let exit_bindings = ExitMetaCollector::collect(
builder,
&exit_meta,
Some(&prepared.carrier_info), // Phase 228-8: Include ConditionOnly carriers
debug,
);
for carrier in &prepared.carrier_info.carriers {
if !exit_bindings.iter().any(|b| b.carrier_name == carrier.name) {
return Err(error_messages::format_error_pattern4_carrier_not_found(&carrier.name));
}
}
// Build inputs for boundary: [loop_var, carriers...]
let mut host_inputs = vec![prepared.carrier_info.loop_var_id];
for carrier in &prepared.carrier_info.carriers {
host_inputs.push(carrier.host_id);
}
trace::trace().debug(
"pattern4",
&format!("host_inputs: {:?}", host_inputs.iter().map(|v| v.0).collect::<Vec<_>>())
);
let mut join_inputs = vec![ValueId(0)]; // ValueId(0) = i_init in JoinIR
for idx in 0..prepared.carrier_info.carriers.len() {
join_inputs.push(ValueId((idx + 1) as u32)); // ValueId(1..N) = carrier inits
}
trace::trace().debug(
"pattern4",
&format!("join_inputs: {:?}", join_inputs.iter().map(|v| v.0).collect::<Vec<_>>())
);
let boundary = JoinInlineBoundaryBuilder::new()
.with_inputs(join_inputs, host_inputs) // Dynamic carrier count
.with_exit_bindings(exit_bindings)
.with_loop_var_name(Some(prepared.loop_var_name.clone()))
.with_carrier_info(prepared.carrier_info.clone())
.build();
let _result_val = JoinIRConversionPipeline::execute(
builder,
join_module,
Some(&boundary),
"pattern4",
debug,
)?;
let void_val = crate::mir::builder::emission::constant::emit_void(builder);
trace::trace().debug("pattern4", &format!("Loop complete, returning Void {:?}", void_val));
Ok(Some(void_val))
}