feat(joinir): Phase 213 AST-based if-sum lowerer for Pattern 3
Implement dual-mode architecture for Pattern 3 (Loop with If-Else PHI): - Add is_simple_if_sum_pattern() detection helper - Detects 1 CounterLike + 1-2 AccumulationLike carrier patterns - Unit tests for various carrier compositions - Add dual-mode dispatch in Pattern3 lowerer - ctx.is_if_sum_pattern() branches to AST-based vs legacy PoC - Legacy mode preserved for backward compatibility - Create loop_with_if_phi_if_sum.rs (~420 lines) - AST extraction: loop condition, if condition, updates - JoinIR generation: main, loop_step, k_exit structure - Helper functions: extract_loop_condition, extract_if_condition, etc. - Extend PatternPipelineContext for Pattern 3 - is_if_sum_pattern() detection using LoopUpdateSummary - extract_if_statement() helper for body analysis Note: E2E RC=2 not yet achieved due to pre-existing Pattern 3 pipeline issue (loop back branch targets wrong block). This affects both if-sum and legacy modes. Fix planned for Phase 214. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@ -1,4 +1,19 @@
|
||||
//! Pattern 3: Loop with If-Else PHI minimal lowerer
|
||||
//!
|
||||
//! # Phase 213: Dual-Mode Architecture
|
||||
//!
|
||||
//! Pattern 3 supports two lowering modes:
|
||||
//!
|
||||
//! 1. **AST-based if-sum mode** (Phase 213+)
|
||||
//! - Triggered when `ctx.is_if_sum_pattern()` returns true
|
||||
//! - Uses AST from `ctx.loop_condition` and `ctx.loop_body`
|
||||
//! - Dynamically lowers loop condition, if condition, and carrier updates
|
||||
//! - Target: `phase212_if_sum_min.hako` (RC=2)
|
||||
//!
|
||||
//! 2. **Legacy PoC mode** (Phase 188-195)
|
||||
//! - Fallback for test-only patterns (e.g., `loop_if_phi.hako`)
|
||||
//! - Hardcoded loop condition (i <= 5), if condition (i % 2 == 1)
|
||||
//! - Kept for backward compatibility with existing tests
|
||||
|
||||
use crate::ast::ASTNode;
|
||||
use crate::mir::builder::MirBuilder;
|
||||
@ -37,11 +52,17 @@ impl MirBuilder {
|
||||
///
|
||||
/// **Refactored**: Now uses PatternPipelineContext for unified preprocessing
|
||||
///
|
||||
/// # Phase 213: Dual-Mode Architecture
|
||||
///
|
||||
/// - **if-sum mode**: When `ctx.is_if_sum_pattern()` is true, uses AST-based lowering
|
||||
/// - **legacy mode**: Otherwise, uses hardcoded PoC lowering for backward compatibility
|
||||
///
|
||||
/// # Pipeline (Phase 179-B)
|
||||
/// 1. Build preprocessing context → PatternPipelineContext
|
||||
/// 2. Call JoinIR lowerer → JoinModule
|
||||
/// 3. Create boundary from context → JoinInlineBoundary
|
||||
/// 4. Merge MIR blocks → JoinIRConversionPipeline
|
||||
/// 2. Check if-sum pattern → branch to appropriate lowerer
|
||||
/// 3. Call JoinIR lowerer → JoinModule
|
||||
/// 4. Create boundary from context → JoinInlineBoundary
|
||||
/// 5. Merge MIR blocks → JoinIRConversionPipeline
|
||||
pub(in crate::mir::builder) fn cf_loop_pattern3_with_if_phi(
|
||||
&mut self,
|
||||
condition: &ASTNode,
|
||||
@ -49,11 +70,6 @@ impl MirBuilder {
|
||||
_func_name: &str,
|
||||
debug: bool,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
use crate::mir::join_ir::lowering::loop_with_if_phi_minimal::lower_loop_with_if_phi_pattern;
|
||||
|
||||
// Phase 195: Use unified trace
|
||||
trace::trace().debug("pattern3", "Calling Pattern 3 minimal lowerer");
|
||||
|
||||
// Phase 179-B: Use PatternPipelineContext for unified preprocessing
|
||||
use super::pattern_pipeline::{build_pattern_context, PatternVariant};
|
||||
let ctx = build_pattern_context(
|
||||
@ -63,6 +79,115 @@ impl MirBuilder {
|
||||
PatternVariant::Pattern3,
|
||||
)?;
|
||||
|
||||
// Phase 213: Dual-mode dispatch based on if-sum pattern detection
|
||||
if ctx.is_if_sum_pattern() {
|
||||
trace::trace().debug("pattern3", "Detected if-sum pattern, using AST-based lowerer");
|
||||
return self.lower_pattern3_if_sum(&ctx, condition, body, debug);
|
||||
}
|
||||
|
||||
// Legacy mode: Use hardcoded PoC lowering (e.g., loop_if_phi.hako)
|
||||
trace::trace().debug("pattern3", "Using legacy PoC lowerer (hardcoded conditions)");
|
||||
self.lower_pattern3_legacy(&ctx, debug)
|
||||
}
|
||||
|
||||
/// Phase 213: AST-based if-sum lowerer
|
||||
///
|
||||
/// Dynamically lowers loop condition, if condition, and carrier updates from AST.
|
||||
/// Target: `phase212_if_sum_min.hako` (RC=2)
|
||||
fn lower_pattern3_if_sum(
|
||||
&mut self,
|
||||
ctx: &super::pattern_pipeline::PatternPipelineContext,
|
||||
condition: &ASTNode,
|
||||
body: &[ASTNode],
|
||||
debug: bool,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
use crate::mir::join_ir::lowering::loop_with_if_phi_if_sum::lower_if_sum_pattern;
|
||||
|
||||
// Phase 202-B: Create JoinValueSpace for unified ValueId allocation
|
||||
use crate::mir::join_ir::lowering::join_value_space::JoinValueSpace;
|
||||
let mut join_value_space = JoinValueSpace::new();
|
||||
|
||||
// Extract if statement from loop body
|
||||
let if_stmt = ctx.extract_if_statement().ok_or_else(|| {
|
||||
"[cf_loop/pattern3] if-sum pattern detected but no if statement found".to_string()
|
||||
})?;
|
||||
|
||||
// Call AST-based if-sum lowerer
|
||||
let (join_module, fragment_meta) = lower_if_sum_pattern(
|
||||
condition,
|
||||
if_stmt,
|
||||
body,
|
||||
&mut join_value_space,
|
||||
)?;
|
||||
|
||||
let exit_meta = &fragment_meta.exit_meta;
|
||||
|
||||
trace::trace().debug(
|
||||
"pattern3/if-sum",
|
||||
&format!("ExitMeta: {} exit values", exit_meta.exit_values.len())
|
||||
);
|
||||
for (carrier_name, join_value) in &exit_meta.exit_values {
|
||||
trace::trace().debug(
|
||||
"pattern3/if-sum",
|
||||
&format!(" {} → ValueId({})", carrier_name, join_value.0)
|
||||
);
|
||||
}
|
||||
|
||||
// Build exit bindings using ExitMetaCollector
|
||||
let exit_bindings = ExitMetaCollector::collect(self, exit_meta, debug);
|
||||
|
||||
// Build boundary with carrier inputs
|
||||
use crate::mir::join_ir::lowering::JoinInlineBoundaryBuilder;
|
||||
use crate::mir::builder::emission::constant;
|
||||
|
||||
// Phase 213: Build join_inputs and host_inputs based on carriers
|
||||
let join_inputs = vec![ValueId(0), ValueId(1), ValueId(2)];
|
||||
let mut host_inputs = vec![ctx.loop_var_id];
|
||||
|
||||
// Add accumulator carriers (sum, optionally count)
|
||||
for carrier in &ctx.carrier_info.carriers {
|
||||
if carrier.name != ctx.loop_var_name {
|
||||
host_inputs.push(carrier.host_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Pad to 3 inputs if needed (for legacy compatibility)
|
||||
while host_inputs.len() < 3 {
|
||||
host_inputs.push(constant::emit_void(self));
|
||||
}
|
||||
|
||||
let boundary = JoinInlineBoundaryBuilder::new()
|
||||
.with_inputs(join_inputs, host_inputs)
|
||||
.with_exit_bindings(exit_bindings)
|
||||
.with_loop_var_name(Some(ctx.loop_var_name.clone()))
|
||||
.build();
|
||||
|
||||
// Execute JoinIR conversion pipeline
|
||||
use super::conversion_pipeline::JoinIRConversionPipeline;
|
||||
let _ = JoinIRConversionPipeline::execute(
|
||||
self,
|
||||
join_module,
|
||||
Some(&boundary),
|
||||
"pattern3/if-sum",
|
||||
debug,
|
||||
)?;
|
||||
|
||||
// Return Void (loop doesn't produce values)
|
||||
let void_val = constant::emit_void(self);
|
||||
trace::trace().debug("pattern3/if-sum", &format!("Loop complete, returning Void {:?}", void_val));
|
||||
Ok(Some(void_val))
|
||||
}
|
||||
|
||||
/// Phase 188-195: Legacy PoC lowerer (hardcoded conditions)
|
||||
///
|
||||
/// Kept for backward compatibility with existing tests like `loop_if_phi.hako`.
|
||||
fn lower_pattern3_legacy(
|
||||
&mut self,
|
||||
ctx: &super::pattern_pipeline::PatternPipelineContext,
|
||||
debug: bool,
|
||||
) -> Result<Option<ValueId>, String> {
|
||||
use crate::mir::join_ir::lowering::loop_with_if_phi_minimal::lower_loop_with_if_phi_pattern;
|
||||
|
||||
// Phase 195: Extract carrier var_ids dynamically based on what exists
|
||||
// This maintains backward compatibility with single-carrier (sum only) and multi-carrier (sum+count) tests
|
||||
let sum_carrier = ctx.carrier_info.carriers.iter()
|
||||
@ -86,7 +211,7 @@ impl MirBuilder {
|
||||
let mut join_value_space = JoinValueSpace::new();
|
||||
|
||||
// Call Pattern 3 lowerer with preprocessed scope
|
||||
let (join_module, fragment_meta) = match lower_loop_with_if_phi_pattern(ctx.loop_scope, &mut join_value_space) {
|
||||
let (join_module, fragment_meta) = match lower_loop_with_if_phi_pattern(ctx.loop_scope.clone(), &mut join_value_space) {
|
||||
Ok(result) => result,
|
||||
Err(e) => {
|
||||
trace::trace().debug("pattern3", &format!("Pattern 3 lowerer failed: {}", e));
|
||||
|
||||
@ -173,6 +173,47 @@ impl PatternPipelineContext {
|
||||
pub fn has_carrier_updates(&self) -> bool {
|
||||
self.carrier_updates.is_some()
|
||||
}
|
||||
|
||||
/// Phase 213: Check if this is a simple if-sum pattern for AST-based lowering
|
||||
///
|
||||
/// Returns true if:
|
||||
/// 1. loop_body contains an if statement
|
||||
/// 2. carrier composition matches if-sum pattern (1 counter + 1-2 accumulators)
|
||||
///
|
||||
/// This determines whether to use AST-based lowering or legacy PoC lowering.
|
||||
pub fn is_if_sum_pattern(&self) -> bool {
|
||||
// Check if loop_body has if statement
|
||||
let has_if = self.loop_body.as_ref().map_or(false, |body| {
|
||||
body.iter().any(|stmt| matches!(stmt, ASTNode::If { .. }))
|
||||
});
|
||||
|
||||
if !has_if {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check carrier pattern using name heuristics
|
||||
// (1 counter like "i" + 1-2 accumulators like "sum", "count")
|
||||
use crate::mir::join_ir::lowering::loop_update_summary::analyze_loop_updates;
|
||||
let carrier_names: Vec<String> = self.carrier_info.carriers.iter()
|
||||
.map(|c| c.name.clone())
|
||||
.collect();
|
||||
|
||||
// Add loop variable to carrier list (it's also part of the pattern)
|
||||
let mut all_names = vec![self.loop_var_name.clone()];
|
||||
all_names.extend(carrier_names);
|
||||
|
||||
let summary = analyze_loop_updates(&all_names);
|
||||
summary.is_simple_if_sum_pattern()
|
||||
}
|
||||
|
||||
/// Phase 213: Extract if statement from loop body
|
||||
///
|
||||
/// Returns the first if statement found in loop_body, if any.
|
||||
pub fn extract_if_statement(&self) -> Option<&ASTNode> {
|
||||
self.loop_body.as_ref().and_then(|body| {
|
||||
body.iter().find(|stmt| matches!(stmt, ASTNode::If { .. }))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Build pattern preprocessing context
|
||||
|
||||
Reference in New Issue
Block a user