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:
nyash-codex
2025-12-10 00:54:46 +09:00
parent 8394018694
commit 338d1aecf1
7 changed files with 810 additions and 28 deletions

View File

@ -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 patternbranch 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));

View File

@ -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