Add pattern router tests and tidy pattern4 lowering
This commit is contained in:
@ -36,6 +36,8 @@ use crate::mir::builder::MirBuilder;
|
|||||||
use crate::mir::ValueId;
|
use crate::mir::ValueId;
|
||||||
use super::super::trace;
|
use super::super::trace;
|
||||||
use crate::mir::loop_pattern_detection::error_messages;
|
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
|
/// Phase 194+: Detection function for Pattern 4
|
||||||
///
|
///
|
||||||
@ -144,261 +146,258 @@ impl MirBuilder {
|
|||||||
_func_name: &str,
|
_func_name: &str,
|
||||||
debug: bool,
|
debug: bool,
|
||||||
) -> Result<Option<ValueId>, String> {
|
) -> 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
|
// Phase 195: Use unified trace
|
||||||
trace::trace().debug("pattern4", "Calling Pattern 4 minimal lowerer");
|
trace::trace().debug("pattern4", "Calling Pattern 4 minimal lowerer");
|
||||||
|
|
||||||
// Phase 33-23: Use Pattern4CarrierAnalyzer for normalization
|
let prepared = prepare_pattern4_context(self, condition, _body)?;
|
||||||
// This transforms: if (cond) { body } else { continue }
|
lower_pattern4_joinir(self, condition, &prepared, debug)
|
||||||
// into: if (!cond) { continue } else { body }
|
}
|
||||||
let normalized_body = Pattern4CarrierAnalyzer::normalize_continue_branches(_body);
|
}
|
||||||
let body_to_analyze = &normalized_body;
|
|
||||||
|
|
||||||
// Phase 179-B: Use PatternPipelineContext for unified preprocessing
|
/// Preprocessed data for Pattern 4 lowering.
|
||||||
// Note: Pattern 4 still needs inline processing for carrier analysis
|
struct Pattern4Prepared {
|
||||||
use super::pattern_pipeline::{build_pattern_context, PatternVariant};
|
loop_var_name: String,
|
||||||
let ctx = build_pattern_context(
|
loop_scope: crate::mir::join_ir::lowering::loop_scope_shape::LoopScopeShape,
|
||||||
self,
|
carrier_info: crate::mir::join_ir::lowering::carrier_info::CarrierInfo,
|
||||||
condition,
|
carrier_updates: BTreeMap<String, UpdateExpr>,
|
||||||
body_to_analyze,
|
}
|
||||||
PatternVariant::Pattern4,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Extract from context
|
/// Normalize, build context, analyze carriers, and promote loop-body locals.
|
||||||
let loop_var_name = ctx.loop_var_name.clone();
|
fn prepare_pattern4_context(
|
||||||
let _loop_var_id = ctx.loop_var_id;
|
builder: &mut MirBuilder,
|
||||||
let carrier_info_prelim = ctx.carrier_info.clone();
|
condition: &ASTNode,
|
||||||
let scope = ctx.loop_scope.clone();
|
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
|
// Normalize continue branches for analysis/lowering
|
||||||
// Analyze carrier update expressions FIRST to identify actual carriers
|
let normalized_body = Pattern4CarrierAnalyzer::normalize_continue_branches(body);
|
||||||
let carrier_updates = Pattern4CarrierAnalyzer::analyze_carrier_updates(
|
|
||||||
body_to_analyze,
|
|
||||||
&carrier_info_prelim.carriers,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Phase 33-23: Filter carriers using the new analyzer
|
// Build preprocessing context
|
||||||
// This prevents constant variables (like M, args) from being treated as carriers
|
let ctx = build_pattern_context(
|
||||||
// Phase 171-C-4: carrier_info is now mutable for promotion merging
|
builder,
|
||||||
let mut carrier_info = Pattern4CarrierAnalyzer::analyze_carriers(
|
condition,
|
||||||
body_to_analyze,
|
&normalized_body,
|
||||||
&carrier_info_prelim,
|
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(
|
trace::trace().debug(
|
||||||
"pattern4",
|
"pattern4",
|
||||||
&format!(
|
&format!(" {} → {:?}", carrier_name, update_expr)
|
||||||
"CarrierInfo: loop_var={}, carriers={:?}",
|
|
||||||
carrier_info.loop_var_name,
|
|
||||||
carrier_info.carriers.iter().map(|c| &c.name).collect::<Vec<_>>()
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
trace::trace().debug(
|
// LoopBodyLocal promotion for conditions (skip_whitespace etc.)
|
||||||
"pattern4",
|
let continue_cond = LoopBodyCondPromoter::extract_continue_condition(&normalized_body);
|
||||||
&format!("Analyzed {} carrier update expressions", carrier_updates.len())
|
let conditions_to_analyze: Vec<&ASTNode> = if let Some(cont_cond) = continue_cond {
|
||||||
);
|
vec![condition, cont_cond]
|
||||||
for (carrier_name, update_expr) in &carrier_updates {
|
} else {
|
||||||
trace::trace().debug(
|
vec![condition]
|
||||||
"pattern4",
|
};
|
||||||
&format!(" {} → {:?}", carrier_name, update_expr)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 195: Use unified trace
|
let cond_scope = LoopConditionScopeBox::analyze(
|
||||||
trace::trace().varmap("pattern4_start", &self.variable_map);
|
&loop_var_name,
|
||||||
|
&conditions_to_analyze,
|
||||||
|
Some(&loop_scope),
|
||||||
|
);
|
||||||
|
|
||||||
// Phase 223-3: LoopBodyLocal Condition Promotion
|
if cond_scope.has_loop_body_local() {
|
||||||
//
|
let promotion_req = ConditionPromotionRequest {
|
||||||
// Check for LoopBodyLocal in loop/continue conditions and attempt promotion.
|
loop_param_name: &loop_var_name,
|
||||||
// Safe Trim patterns (Category A-3: skip_whitespace) are promoted to carriers.
|
cond_scope: &cond_scope,
|
||||||
{
|
scope_shape: Some(&loop_scope),
|
||||||
use crate::mir::loop_pattern_detection::loop_condition_scope::LoopConditionScopeBox;
|
break_cond: None, // Pattern 4 has no break
|
||||||
use crate::mir::loop_pattern_detection::loop_body_cond_promoter::{
|
continue_cond,
|
||||||
LoopBodyCondPromoter, ConditionPromotionRequest, ConditionPromotionResult,
|
loop_body: &normalized_body,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Extract continue condition from body (if present)
|
match LoopBodyCondPromoter::try_promote_for_condition(promotion_req) {
|
||||||
// Handles skip_whitespace pattern: if ch == " " || ... { continue }
|
ConditionPromotionResult::Promoted {
|
||||||
let continue_cond = LoopBodyCondPromoter::extract_continue_condition(body_to_analyze);
|
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
|
carrier_info.merge_from(&promoted_carrier);
|
||||||
let conditions_to_analyze: Vec<&ASTNode> = if let Some(cont_cond) = continue_cond {
|
|
||||||
vec![condition, cont_cond]
|
|
||||||
} else {
|
|
||||||
vec![condition]
|
|
||||||
};
|
|
||||||
|
|
||||||
let cond_scope = LoopConditionScopeBox::analyze(
|
trace::trace().debug(
|
||||||
&loop_var_name,
|
"pattern4/cond_promoter",
|
||||||
&conditions_to_analyze,
|
&format!(
|
||||||
Some(&scope),
|
"Merged carrier '{}' into CarrierInfo (total carriers: {})",
|
||||||
);
|
|
||||||
|
|
||||||
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,
|
|
||||||
carrier_name,
|
carrier_name,
|
||||||
} => {
|
carrier_info.carrier_count()
|
||||||
eprintln!(
|
),
|
||||||
"[pattern4/cond_promoter] LoopBodyLocal '{}' promoted to carrier '{}'",
|
);
|
||||||
promoted_var, carrier_name
|
|
||||||
|
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",
|
||||||
);
|
);
|
||||||
|
trace::trace().debug(
|
||||||
// Merge promoted carrier into existing CarrierInfo
|
"pattern4/cond_promoter",
|
||||||
carrier_info.merge_from(&promoted_carrier);
|
&format!(
|
||||||
|
"Carrier: '{}', original var: '{}', whitespace chars: {:?}",
|
||||||
eprintln!(
|
helper.carrier_name, helper.original_var, helper.whitespace_chars
|
||||||
"[pattern4/cond_promoter] Merged carrier '{}' into CarrierInfo (total carriers: {})",
|
),
|
||||||
carrier_name,
|
|
||||||
carrier_info.carrier_count()
|
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
// Check if this is a safe Trim pattern
|
return Err(error_messages::format_error_pattern4_trim_not_safe(
|
||||||
if let Some(helper) = carrier_info.trim_helper() {
|
&helper.carrier_name,
|
||||||
if helper.is_safe_trim() {
|
helper.whitespace_count()
|
||||||
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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
ConditionPromotionResult::CannotPromote { reason, vars } => {
|
||||||
|
return Err(error_messages::format_error_pattern4_promotion_failed(&vars, &reason));
|
||||||
// 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));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
//! ## 使用例
|
//! ## 使用例
|
||||||
//!
|
//!
|
||||||
//! ```ignore
|
//! ```ignore
|
||||||
//! let summary = analyze_loop_updates(&carrier_names);
|
//! let summary = analyze_loop_updates_by_name(&carrier_names);
|
||||||
//! if summary.has_single_counter() {
|
//! if summary.has_single_counter() {
|
||||||
//! // StringExamination パターン
|
//! // StringExamination パターン
|
||||||
//! }
|
//! }
|
||||||
@ -345,24 +345,11 @@ pub fn analyze_loop_updates_from_ast(
|
|||||||
LoopUpdateSummary { carriers }
|
LoopUpdateSummary { carriers }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Phase 219: Legacy wrapper for backward compatibility
|
/// Name-only heuristic for loop update summary (legacy compatibility)
|
||||||
///
|
///
|
||||||
/// # Deprecated (Phase 219)
|
/// This path is kept for callers that only have carrier names (no AST).
|
||||||
///
|
/// Prefer `analyze_loop_updates_from_ast()` when loop body is available.
|
||||||
/// This function uses name-based heuristics and is deprecated.
|
pub fn analyze_loop_updates_by_name(carrier_names: &[String]) -> LoopUpdateSummary {
|
||||||
/// Use `analyze_loop_updates_from_ast()` instead.
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
///
|
|
||||||
/// * `carrier_names` - キャリア変数名のリスト
|
|
||||||
///
|
|
||||||
/// # Returns
|
|
||||||
///
|
|
||||||
/// 各キャリアの更新パターンをまとめた LoopUpdateSummary
|
|
||||||
#[deprecated(since = "Phase 219", note = "Use analyze_loop_updates_from_ast() instead")]
|
|
||||||
pub fn analyze_loop_updates(carrier_names: &[String]) -> LoopUpdateSummary {
|
|
||||||
// Phase 219: Fallback to simple heuristic (for legacy call sites)
|
|
||||||
// This will be removed once all call sites are migrated
|
|
||||||
let carriers = carrier_names
|
let carriers = carrier_names
|
||||||
.iter()
|
.iter()
|
||||||
.map(|name| CarrierUpdateInfo {
|
.map(|name| CarrierUpdateInfo {
|
||||||
|
|||||||
@ -219,7 +219,7 @@ pub(crate) fn extract_features(loop_form: &LoopForm, scope: Option<&LoopScopeSha
|
|||||||
// Note: carriers is BTreeSet<String>, so each item is already a String
|
// Note: carriers is BTreeSet<String>, so each item is already a String
|
||||||
let update_summary = scope.map(|s| {
|
let update_summary = scope.map(|s| {
|
||||||
let carrier_names: Vec<String> = s.carriers.iter().cloned().collect();
|
let carrier_names: Vec<String> = s.carriers.iter().cloned().collect();
|
||||||
crate::mir::join_ir::lowering::loop_update_summary::analyze_loop_updates(&carrier_names)
|
crate::mir::join_ir::lowering::loop_update_summary::analyze_loop_updates_by_name(&carrier_names)
|
||||||
});
|
});
|
||||||
|
|
||||||
LoopFeatures {
|
LoopFeatures {
|
||||||
@ -664,107 +664,178 @@ fn has_simple_condition(_loop_form: &LoopForm) -> bool {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::ast::{ASTNode, BinaryOperator, LiteralValue, Span};
|
||||||
|
use crate::mir::loop_pattern_detection::LoopFeatures;
|
||||||
|
|
||||||
// ========================================================================
|
fn span() -> Span {
|
||||||
// Pattern 1: Simple While Loop Tests
|
Span::unknown()
|
||||||
// ========================================================================
|
}
|
||||||
|
|
||||||
#[test]
|
fn var(name: &str) -> ASTNode {
|
||||||
#[ignore] // TODO: Implement test after detection logic is complete
|
ASTNode::Variable {
|
||||||
fn test_pattern1_simple_while_detection() {
|
name: name.to_string(),
|
||||||
// TODO: Add unit test for simple while pattern detection
|
span: span(),
|
||||||
// Step 1: Create mock LoopForm with:
|
}
|
||||||
// - Empty break_targets
|
}
|
||||||
// - Empty continue_targets
|
|
||||||
// - Single latch
|
fn lit_i(n: i64) -> ASTNode {
|
||||||
// Step 2: Call is_simple_while_pattern()
|
ASTNode::Literal {
|
||||||
// Step 3: Assert returns true
|
value: LiteralValue::Integer(n),
|
||||||
|
span: span(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bin(op: BinaryOperator, left: ASTNode, right: ASTNode) -> ASTNode {
|
||||||
|
ASTNode::BinaryOp {
|
||||||
|
operator: op,
|
||||||
|
left: Box::new(left),
|
||||||
|
right: Box::new(right),
|
||||||
|
span: span(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assignment(target: ASTNode, value: ASTNode) -> ASTNode {
|
||||||
|
ASTNode::Assignment {
|
||||||
|
target: Box::new(target),
|
||||||
|
value: Box::new(value),
|
||||||
|
span: span(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_continue(node: &ASTNode) -> bool {
|
||||||
|
match node {
|
||||||
|
ASTNode::Continue { .. } => true,
|
||||||
|
ASTNode::If { then_body, else_body, .. } => {
|
||||||
|
then_body.iter().any(has_continue) || else_body.as_ref().map_or(false, |b| b.iter().any(has_continue))
|
||||||
|
}
|
||||||
|
ASTNode::Loop { body, .. } => body.iter().any(has_continue),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_break(node: &ASTNode) -> bool {
|
||||||
|
match node {
|
||||||
|
ASTNode::Break { .. } => true,
|
||||||
|
ASTNode::If { then_body, else_body, .. } => {
|
||||||
|
then_body.iter().any(has_break) || else_body.as_ref().map_or(false, |b| b.iter().any(has_break))
|
||||||
|
}
|
||||||
|
ASTNode::Loop { body, .. } => body.iter().any(has_break),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn has_if(body: &[ASTNode]) -> bool {
|
||||||
|
body.iter().any(|n| matches!(n, ASTNode::If { .. }))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn carrier_count(body: &[ASTNode]) -> usize {
|
||||||
|
fn count(nodes: &[ASTNode]) -> usize {
|
||||||
|
let mut c = 0;
|
||||||
|
for n in nodes {
|
||||||
|
match n {
|
||||||
|
ASTNode::Assignment { .. } => c += 1,
|
||||||
|
ASTNode::If { then_body, else_body, .. } => {
|
||||||
|
c += count(then_body);
|
||||||
|
if let Some(else_body) = else_body {
|
||||||
|
c += count(else_body);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
c
|
||||||
|
}
|
||||||
|
if count(body) > 0 { 1 } else { 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn classify_body(body: &[ASTNode]) -> LoopPatternKind {
|
||||||
|
let has_continue_flag = body.iter().any(has_continue);
|
||||||
|
let has_break_flag = body.iter().any(has_break);
|
||||||
|
let features = LoopFeatures {
|
||||||
|
has_break: has_break_flag,
|
||||||
|
has_continue: has_continue_flag,
|
||||||
|
has_if: has_if(body),
|
||||||
|
has_if_else_phi: false,
|
||||||
|
carrier_count: carrier_count(body),
|
||||||
|
break_count: if has_break_flag { 1 } else { 0 },
|
||||||
|
continue_count: if has_continue_flag { 1 } else { 0 },
|
||||||
|
update_summary: None,
|
||||||
|
};
|
||||||
|
classify(&features)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore] // TODO: Implement test after detection logic is complete
|
fn pattern1_simple_while_is_detected() {
|
||||||
fn test_pattern1_rejects_break() {
|
// loop(i < len) { i = i + 1 }
|
||||||
// TODO: Add test that rejects loop with break
|
let body = vec![assignment(var("i"), bin(BinaryOperator::Add, var("i"), lit_i(1)))];
|
||||||
// Step 1: Create mock LoopForm with non-empty break_targets
|
let kind = classify_body(&body);
|
||||||
// Step 2: Call is_simple_while_pattern()
|
assert_eq!(kind, LoopPatternKind::Pattern1SimpleWhile);
|
||||||
// Step 3: Assert returns false
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========================================================================
|
|
||||||
// Pattern 2: Loop with Break Tests
|
|
||||||
// ========================================================================
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[ignore] // TODO: Implement test after detection logic is complete
|
|
||||||
fn test_pattern2_break_detection() {
|
|
||||||
// TODO: Add unit test for break pattern detection
|
|
||||||
// Step 1: Create mock LoopForm with:
|
|
||||||
// - Non-empty break_targets (exactly 1)
|
|
||||||
// - Empty continue_targets
|
|
||||||
// - If statement with break
|
|
||||||
// Step 2: Call is_loop_with_break_pattern()
|
|
||||||
// Step 3: Assert returns true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore] // TODO: Implement test after detection logic is complete
|
fn pattern2_break_loop_is_detected() {
|
||||||
fn test_pattern2_rejects_no_break() {
|
// loop(i < len) { if i > 0 { break } i = i + 1 }
|
||||||
// TODO: Add test that rejects loop without break
|
let cond = bin(BinaryOperator::Greater, var("i"), lit_i(0));
|
||||||
// Step 1: Create mock LoopForm with empty break_targets
|
let body = vec![
|
||||||
// Step 2: Call is_loop_with_break_pattern()
|
ASTNode::If {
|
||||||
// Step 3: Assert returns false
|
condition: Box::new(cond),
|
||||||
}
|
then_body: vec![ASTNode::Break { span: span() }],
|
||||||
|
else_body: None,
|
||||||
// ========================================================================
|
span: span(),
|
||||||
// Pattern 3: Loop with If-Else PHI Tests
|
},
|
||||||
// ========================================================================
|
assignment(var("i"), bin(BinaryOperator::Add, var("i"), lit_i(1))),
|
||||||
|
];
|
||||||
#[test]
|
let kind = classify_body(&body);
|
||||||
#[ignore] // TODO: Implement test after detection logic is complete
|
assert_eq!(kind, LoopPatternKind::Pattern2Break);
|
||||||
fn test_pattern3_if_else_phi_detection() {
|
|
||||||
// TODO: Add unit test for if-else phi pattern detection
|
|
||||||
// Step 1: Create mock LoopForm with:
|
|
||||||
// - Empty break_targets
|
|
||||||
// - Empty continue_targets
|
|
||||||
// - If-else statement in body
|
|
||||||
// - Multiple carrier variables
|
|
||||||
// Step 2: Call is_loop_with_conditional_phi_pattern()
|
|
||||||
// Step 3: Assert returns true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore] // TODO: Implement test after detection logic is complete
|
fn pattern3_if_sum_shape_is_detected() {
|
||||||
fn test_pattern3_rejects_break() {
|
// loop(i < len) { if i % 2 == 1 { sum = sum + 1 } i = i + 1 }
|
||||||
// TODO: Add test that rejects loop with break
|
let cond = bin(
|
||||||
// Step 1: Create mock LoopForm with non-empty break_targets
|
BinaryOperator::Equal,
|
||||||
// Step 2: Call is_loop_with_conditional_phi_pattern()
|
bin(BinaryOperator::Modulo, var("i"), lit_i(2)),
|
||||||
// Step 3: Assert returns false
|
lit_i(1),
|
||||||
}
|
);
|
||||||
|
let body = vec![
|
||||||
// ========================================================================
|
ASTNode::If {
|
||||||
// Pattern 4: Loop with Continue Tests
|
condition: Box::new(cond),
|
||||||
// ========================================================================
|
then_body: vec![assignment(
|
||||||
|
var("sum"),
|
||||||
#[test]
|
bin(BinaryOperator::Add, var("sum"), lit_i(1)),
|
||||||
#[ignore] // TODO: Implement test after detection logic is complete
|
)],
|
||||||
fn test_pattern4_continue_detection() {
|
else_body: None,
|
||||||
// TODO: Add unit test for continue pattern detection
|
span: span(),
|
||||||
// Step 1: Create mock LoopForm with:
|
},
|
||||||
// - Non-empty continue_targets (at least 1)
|
assignment(var("i"), bin(BinaryOperator::Add, var("i"), lit_i(1))),
|
||||||
// - Empty break_targets
|
];
|
||||||
// - If statement with continue
|
let kind = classify_body(&body);
|
||||||
// Step 2: Call is_loop_with_continue_pattern()
|
assert_eq!(kind, LoopPatternKind::Pattern3IfPhi);
|
||||||
// Step 3: Assert returns true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore] // TODO: Implement test after detection logic is complete
|
fn pattern4_continue_loop_is_detected() {
|
||||||
fn test_pattern4_rejects_no_continue() {
|
// loop(i < len) { if (i % 2 == 0) { continue } sum = sum + i; i = i + 1 }
|
||||||
// TODO: Add test that rejects loop without continue
|
let cond = bin(
|
||||||
// Step 1: Create mock LoopForm with empty continue_targets
|
BinaryOperator::Equal,
|
||||||
// Step 2: Call is_loop_with_continue_pattern()
|
bin(BinaryOperator::Modulo, var("i"), lit_i(2)),
|
||||||
// Step 3: Assert returns false
|
lit_i(0),
|
||||||
|
);
|
||||||
|
let body = vec![
|
||||||
|
ASTNode::If {
|
||||||
|
condition: Box::new(cond),
|
||||||
|
then_body: vec![ASTNode::Continue { span: span() }],
|
||||||
|
else_body: Some(vec![assignment(
|
||||||
|
var("sum"),
|
||||||
|
bin(BinaryOperator::Add, var("sum"), var("i")),
|
||||||
|
)]),
|
||||||
|
span: span(),
|
||||||
|
},
|
||||||
|
assignment(var("i"), bin(BinaryOperator::Add, var("i"), lit_i(1))),
|
||||||
|
];
|
||||||
|
let kind = classify_body(&body);
|
||||||
|
assert_eq!(kind, LoopPatternKind::Pattern4Continue);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user