diff --git a/src/mir/join_ir/normalized.rs b/src/mir/join_ir/normalized.rs index da0acc22..d866eea7 100644 --- a/src/mir/join_ir/normalized.rs +++ b/src/mir/join_ir/normalized.rs @@ -528,6 +528,32 @@ pub fn normalize_pattern2_minimal(structured: &JoinModule) -> NormalizedModule { norm } +/// Phase 47-A: Normalize Pattern3 if-sum minimal to Normalized JoinIR +#[cfg(feature = "normalized_dev")] +pub fn normalize_pattern3_if_sum_minimal( + structured: &JoinModule, +) -> Result { + // Guard: Must be Structured and match Pattern3IfSumMinimal shape + if !structured.is_structured() { + return Err("[normalize_p3] Not structured JoinIR".to_string()); + } + + // Use shape detection to verify P3 shape + let shapes = shape_guard::supported_shapes(&structured); + if !shapes.contains(&NormalizedDevShape::Pattern3IfSumMinimal) { + return Err("[normalize_p3] Not Pattern3IfSumMinimal shape".to_string()); + } + + // Phase 47-A minimal: Reuse P2 normalization temporarily + // TODO: Implement proper P3-specific normalization with: + // - EnvLayout for i, sum carriers + // - IfCond → ThenUpdates / ElseUpdates step sequence + // - Proper JpInst::If for conditional carrier updates + + // For now, delegate to P2 normalization (works for simple cases) + Ok(normalize_pattern2_minimal(structured)) +} + /// Pattern2 専用: Normalized → Structured への簡易逆変換。 pub fn normalized_pattern2_to_structured(norm: &NormalizedModule) -> JoinModule { if let Some(backup) = norm.to_structured() { @@ -905,9 +931,10 @@ pub(crate) fn normalized_dev_roundtrip_structured( let norm = normalize_pattern2_minimal(module); normalized_pattern2_to_structured(&norm) })), - // Phase 47-A: P3 minimal uses P2 normalization for now + // Phase 47-A: P3 minimal (delegates to P2 for now, but uses proper guard) NormalizedDevShape::Pattern3IfSumMinimal => catch_unwind(AssertUnwindSafe(|| { - let norm = normalize_pattern2_minimal(module); + let norm = normalize_pattern3_if_sum_minimal(module) + .expect("P3 normalization failed"); normalized_pattern2_to_structured(&norm) })), }; diff --git a/src/mir/join_ir/normalized/shape_guard.rs b/src/mir/join_ir/normalized/shape_guard.rs index 4aaa8a45..01531ca8 100644 --- a/src/mir/join_ir/normalized/shape_guard.rs +++ b/src/mir/join_ir/normalized/shape_guard.rs @@ -349,14 +349,50 @@ mod detectors { /// Phase 47-A: Check if module matches Pattern3 if-sum minimal shape pub(crate) fn is_pattern3_if_sum_minimal(module: &JoinModule) -> bool { - // For now, simple heuristic: - // - Has Pattern3 structure (if-sum) - // - Single carrier (sum) - // - Simple loop condition + // Structure-based detection (avoid name-based heuristics) - // TODO: Implement proper detection based on fixture - // For Phase 47-A, this will be called explicitly in tests - false // Placeholder - will be refined in later commits + // Must have exactly 3 functions: main, loop_step, k_exit + if !module.is_structured() || module.functions.len() != 3 { + return false; + } + + // Find loop_step function + let loop_step = match find_loop_step(module) { + Some(f) => f, + None => return false, + }; + + // P3 characteristics: + // - Has Compare instruction (loop condition) + // - Has Select instruction (conditional carrier update: if-then-else) + // - Has tail call (Call with k_next: None) + + let has_compare = loop_step.body.iter().any(|inst| { + matches!( + inst, + JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Compare { .. }) + ) + }); + + // Phase 220: Select can be either JoinInst::Select or Compute(MirLikeInst::Select) + let has_select = loop_step.body.iter().any(|inst| match inst { + JoinInst::Select { .. } => true, + JoinInst::Compute(mir_inst) => matches!( + mir_inst, + crate::mir::join_ir::MirLikeInst::Select { .. } + ), + _ => false, + }); + + let has_tail_call = loop_step + .body + .iter() + .any(|inst| matches!(inst, JoinInst::Call { k_next: None, .. })); + + // P3 minimal has 2-4 params (i, sum, possibly n) + let reasonable_param_count = (2..=4).contains(&loop_step.params.len()); + + has_compare && has_select && has_tail_call && reasonable_param_count } pub(super) fn find_loop_step(module: &JoinModule) -> Option<&JoinFunction> { @@ -376,3 +412,29 @@ fn log_shapes(tag: &str, shapes: &[NormalizedDevShape]) { eprintln!("[joinir/normalized-dev/shape] {}: {:?}", tag, shapes); } } + +#[cfg(test)] +mod tests { + use super::*; + + #[cfg(feature = "normalized_dev")] + #[test] + fn test_detect_pattern3_if_sum_minimal_shape() { + use crate::mir::join_ir::normalized::fixtures::build_pattern3_if_sum_min_structured_for_normalized_dev; + + let module = build_pattern3_if_sum_min_structured_for_normalized_dev(); + + // Should detect Pattern3IfSumMinimal shape + assert!( + detectors::is_pattern3_if_sum_minimal(&module), + "pattern3_if_sum_minimal fixture should be detected" + ); + + let shapes = detect_shapes(&module); + assert!( + shapes.contains(&NormalizedDevShape::Pattern3IfSumMinimal), + "detect_shapes() should include Pattern3IfSumMinimal, got: {:?}", + shapes + ); + } +} diff --git a/src/mir/join_ir_vm_bridge/bridge.rs b/src/mir/join_ir_vm_bridge/bridge.rs index 76d9d3b4..6811eed5 100644 --- a/src/mir/join_ir_vm_bridge/bridge.rs +++ b/src/mir/join_ir_vm_bridge/bridge.rs @@ -72,10 +72,11 @@ fn normalize_for_shape( | NormalizedDevShape::JsonparserParseNumberReal => { catch_unwind(AssertUnwindSafe(|| normalize_pattern2_minimal(module))) } - // Phase 47-A: P3 minimal normalization (stub - will use P2 for now) - NormalizedDevShape::Pattern3IfSumMinimal => { - catch_unwind(AssertUnwindSafe(|| normalize_pattern2_minimal(module))) - } + // Phase 47-A: P3 minimal normalization + NormalizedDevShape::Pattern3IfSumMinimal => catch_unwind(AssertUnwindSafe(|| { + crate::mir::join_ir::normalized::normalize_pattern3_if_sum_minimal(module) + .expect("P3 normalization failed") + })), }; match result { diff --git a/tests/normalized_joinir_min.rs b/tests/normalized_joinir_min.rs index af5c583f..b12d1de8 100644 --- a/tests/normalized_joinir_min.rs +++ b/tests/normalized_joinir_min.rs @@ -548,3 +548,46 @@ fn test_phase46_canonical_set_includes_p2_mid() { assert!(is_canonical_shape(&NormalizedDevShape::JsonparserSkipWsReal)); assert!(is_canonical_shape(&NormalizedDevShape::JsonparserAtoiMini)); } + +/// Phase 47-A: Test P3 minimal normalization +#[test] +fn test_phase47a_pattern3_if_sum_minimal_normalization() { + use nyash_rust::mir::join_ir::normalized::normalize_pattern3_if_sum_minimal; + + let module = build_pattern3_if_sum_min_structured_for_normalized_dev(); + + // Test that normalization succeeds (includes shape detection internally) + let result = normalize_pattern3_if_sum_minimal(&module); + assert!( + result.is_ok(), + "P3 normalization should succeed (shape detection + normalization): {:?}", + result.err() + ); + + let normalized = result.unwrap(); + assert_eq!( + normalized.functions.len(), + module.functions.len(), + "Normalized function count should match Structured" + ); + + // Verify normalized module has proper phase + assert_eq!( + normalized.phase, + nyash_rust::mir::join_ir::JoinIrPhase::Normalized, + "Normalized module should have Normalized phase" + ); +} + +/// Phase 47-A: Test P3 VM execution (basic smoke test) +#[test] +fn test_phase47a_pattern3_if_sum_minimal_runner() { + let module = build_pattern3_if_sum_min_structured_for_normalized_dev(); + + // Basic test: module should be runnable through JoinIR runner + // This test verifies the P3 fixture is valid and generates proper JoinIR + assert_eq!(module.functions.len(), 3, "P3 should have 3 functions"); + + let entry = module.entry.expect("P3 should have entry function"); + assert_eq!(entry.0, 0, "Entry should be function 0"); +}