#![cfg(feature = "normalized_dev")] use crate::config::env::joinir_dev_enabled; use crate::mir::join_ir::normalized::dev_env; use crate::mir::join_ir::{JoinFuncId, JoinFunction, JoinInst, JoinModule}; /// Phase 44: Shape capability kinds (capability-based routing) #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ShapeCapabilityKind { /// P2 Core: Simple mini patterns (i/acc/n etc) P2CoreSimple, /// P2 Core: skip_whitespace mini/real P2CoreSkipWs, /// P2 Core: _atoi mini/real P2CoreAtoi, /// P2 Mid: _parse_number real (p + num_str + result) P2MidParseNumber, // Future: Other P2 patterns // P2MidAtOfLoop, // P2HeavyString, } /// Phase 44: Shape capability descriptor #[derive(Debug, Clone)] pub struct ShapeCapability { pub kind: ShapeCapabilityKind, // Future extensibility fields (not all used yet): // pub pattern_kind: LoopPatternKind, // pub loop_param_count: usize, // pub carrier_roles: Vec, // pub method_calls: Vec, } impl ShapeCapability { pub fn new(kind: ShapeCapabilityKind) -> Self { Self { kind } } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum NormalizedDevShape { Pattern1Mini, Pattern2Mini, JsonparserSkipWsMini, JsonparserSkipWsReal, JsonparserAtoiMini, JsonparserAtoiReal, JsonparserParseNumberReal, // Phase 47-A: Pattern3 (if-sum) minimal Pattern3IfSumMinimal, } type Detector = fn(&JoinModule) -> bool; const SHAPE_DETECTORS: &[(NormalizedDevShape, Detector)] = &[ (NormalizedDevShape::Pattern1Mini, detectors::is_pattern1_mini), (NormalizedDevShape::Pattern2Mini, detectors::is_pattern2_mini), ( NormalizedDevShape::JsonparserSkipWsMini, detectors::is_jsonparser_skip_ws_mini, ), ( NormalizedDevShape::JsonparserSkipWsReal, detectors::is_jsonparser_skip_ws_real, ), ( NormalizedDevShape::JsonparserAtoiMini, detectors::is_jsonparser_atoi_mini, ), ( NormalizedDevShape::JsonparserAtoiReal, detectors::is_jsonparser_atoi_real, ), ( NormalizedDevShape::JsonparserParseNumberReal, detectors::is_jsonparser_parse_number_real, ), // Phase 47-A: Pattern3 if-sum minimal ( NormalizedDevShape::Pattern3IfSumMinimal, detectors::is_pattern3_if_sum_minimal, ), ]; /// direct ブリッジで扱う shape(dev 限定)。 pub(crate) fn direct_shapes(module: &JoinModule) -> Vec { let shapes = detect_shapes(module); log_shapes("direct", &shapes); shapes } /// Structured→Normalized の対象 shape(dev 限定)。 pub(crate) fn supported_shapes(module: &JoinModule) -> Vec { let shapes = detect_shapes(module); log_shapes("roundtrip", &shapes); shapes } /// Phase 44: Map NormalizedDevShape to ShapeCapability pub fn capability_for_shape(shape: &NormalizedDevShape) -> ShapeCapability { use NormalizedDevShape::*; use ShapeCapabilityKind::*; let kind = match shape { Pattern2Mini => P2CoreSimple, JsonparserSkipWsMini | JsonparserSkipWsReal => P2CoreSkipWs, JsonparserAtoiMini | JsonparserAtoiReal => P2CoreAtoi, JsonparserParseNumberReal => P2MidParseNumber, Pattern1Mini => P2CoreSimple, // Also core simple pattern // Phase 47-A: P3 minimal maps to P2CoreSimple for now (future: P3CoreSimple) Pattern3IfSumMinimal => P2CoreSimple, }; ShapeCapability::new(kind) } /// Phase 46: Canonical shapes that ALWAYS use Normalized→MIR(direct) /// regardless of feature flags or mode. /// /// Canonical set (Phase 46): /// - P2-Core: Pattern2Mini, JsonparserSkipWsMini, JsonparserSkipWsReal, JsonparserAtoiMini /// - P2-Mid: JsonparserAtoiReal, JsonparserParseNumberReal /// /// P3/P4 patterns are NOT canonical (deferred to NORM-P3/NORM-P4 phases). pub fn is_canonical_shape(shape: &NormalizedDevShape) -> bool { use NormalizedDevShape::*; matches!( shape, Pattern2Mini | JsonparserSkipWsMini | JsonparserSkipWsReal | JsonparserAtoiMini // Phase 46: Add P2-Mid patterns | JsonparserAtoiReal | JsonparserParseNumberReal ) } /// Phase 44: Check if capability kind is in P2-Core family /// /// This checks capability-level membership, not granular canonical status. /// Use `is_canonical_shape()` for exact canonical filtering. pub fn is_p2_core_capability(cap: &ShapeCapability) -> bool { use ShapeCapabilityKind::*; matches!(cap.kind, P2CoreSimple | P2CoreSkipWs | P2CoreAtoi | P2MidParseNumber) } /// Phase 44: Check if capability is supported by Normalized dev pub fn is_supported_by_normalized(cap: &ShapeCapability) -> bool { // Currently same as P2-Core family is_p2_core_capability(cap) } /// canonical(常時 Normalized 経路を通す)対象。 /// Phase 46: Extract canonical shapes from JoinModule. /// /// Canonical set (P2-Core + P2-Mid): /// - Pattern2Mini, skip_ws mini/real, atoi mini/real, parse_number real /// /// These shapes ALWAYS use Normalized→MIR(direct) regardless of mode. /// P3/P4 patterns are NOT canonical (future NORM-P3/NORM-P4 phases). pub(crate) fn canonical_shapes(module: &JoinModule) -> Vec { let shapes: Vec<_> = detect_shapes(module) .into_iter() .filter(|s| is_canonical_shape(s)) .collect(); log_shapes("canonical", &shapes); shapes } #[allow(dead_code)] pub(crate) fn is_direct_supported(module: &JoinModule) -> bool { !detect_shapes(module).is_empty() } fn detect_shapes(module: &JoinModule) -> Vec { let mut shapes: Vec<_> = SHAPE_DETECTORS .iter() .filter_map(|(shape, detector)| if detector(module) { Some(*shape) } else { None }) .collect(); // Pattern1 は「最小の後方互換」なので、より具体的な shape が見つかった場合は外しておく。 if shapes.len() > 1 { shapes.retain(|s| *s != NormalizedDevShape::Pattern1Mini); } shapes } // --- 判定ロジック(共通) --- mod detectors { use super::*; pub(super) fn is_pattern1_mini(module: &JoinModule) -> bool { module.is_structured() && find_loop_step(module).is_some() } pub(super) fn is_pattern2_mini(module: &JoinModule) -> bool { if !module.is_structured() || module.functions.len() != 3 { return false; } let loop_func = match find_loop_step(module) { Some(f) => f, None => return false, }; if !(1..=3).contains(&loop_func.params.len()) { return false; } let has_cond_jump = loop_func .body .iter() .any(|inst| matches!(inst, JoinInst::Jump { cond: Some(_), .. })); let has_tail_call = loop_func .body .iter() .any(|inst| matches!(inst, JoinInst::Call { k_next: None, .. })); has_cond_jump && has_tail_call } pub(super) fn is_jsonparser_skip_ws_mini(module: &JoinModule) -> bool { is_pattern2_mini(module) && module .functions .values() .any(|f| f.name == "jsonparser_skip_ws_mini") } pub(crate) fn is_jsonparser_skip_ws_real(module: &JoinModule) -> bool { if !module.is_structured() || module.functions.len() != 3 { return false; } let loop_func = match find_loop_step(module) { Some(f) => f, None => return false, }; if !(2..=6).contains(&loop_func.params.len()) { return false; } let has_cond_jump = loop_func .body .iter() .any(|inst| matches!(inst, JoinInst::Jump { cond: Some(_), .. })); let has_tail_call = loop_func .body .iter() .any(|inst| matches!(inst, JoinInst::Call { k_next: None, .. })); has_cond_jump && has_tail_call && module .functions .values() .any(|f| f.name == "jsonparser_skip_ws_real") } pub(crate) fn is_jsonparser_atoi_mini(module: &JoinModule) -> bool { if !module.is_structured() || module.functions.len() != 3 { return false; } let loop_func = match find_loop_step(module) { Some(f) => f, None => return false, }; if !(3..=8).contains(&loop_func.params.len()) { return false; } let has_cond_jump = loop_func .body .iter() .any(|inst| matches!(inst, JoinInst::Jump { cond: Some(_), .. })); let has_tail_call = loop_func .body .iter() .any(|inst| matches!(inst, JoinInst::Call { k_next: None, .. })); has_cond_jump && has_tail_call && module .functions .values() .any(|f| f.name == "jsonparser_atoi_mini") } pub(crate) fn is_jsonparser_atoi_real(module: &JoinModule) -> bool { if !module.is_structured() || module.functions.len() != 3 { return false; } let loop_func = match find_loop_step(module) { Some(f) => f, None => return false, }; if !(3..=10).contains(&loop_func.params.len()) { return false; } let has_cond_jump = loop_func .body .iter() .any(|inst| matches!(inst, JoinInst::Jump { cond: Some(_), .. })); let has_tail_call = loop_func .body .iter() .any(|inst| matches!(inst, JoinInst::Call { k_next: None, .. })); has_cond_jump && has_tail_call && module .functions .values() .any(|f| f.name == "jsonparser_atoi_real") } pub(crate) fn is_jsonparser_parse_number_real(module: &JoinModule) -> bool { if !module.is_structured() || module.functions.len() != 3 { return false; } let loop_func = match find_loop_step(module) { Some(f) => f, None => return false, }; if !(3..=12).contains(&loop_func.params.len()) { return false; } let has_cond_jump = loop_func .body .iter() .any(|inst| matches!(inst, JoinInst::Jump { cond: Some(_), .. })); let has_tail_call = loop_func .body .iter() .any(|inst| matches!(inst, JoinInst::Call { k_next: None, .. })); has_cond_jump && has_tail_call && module .functions .values() .any(|f| f.name == "jsonparser_parse_number_real") } /// Phase 47-A: Check if module matches Pattern3 if-sum minimal shape pub(crate) fn is_pattern3_if_sum_minimal(module: &JoinModule) -> bool { // Structure-based detection (avoid name-based heuristics) // 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> { module .functions .values() .find(|f| f.name == "loop_step") .or_else(|| module.functions.get(&JoinFuncId::new(1))) } } fn log_shapes(tag: &str, shapes: &[NormalizedDevShape]) { if shapes.is_empty() { return; } if dev_env::normalized_dev_logs_enabled() && joinir_dev_enabled() { 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 ); } }