#![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, /// P3 If-Sum family (minimal/multi/json) P3IfSum, // 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, // Phase 47-B: Pattern3 extended (multi/json) Pattern3IfSumMulti, Pattern3IfSumJson, // Phase 48-A: Pattern4 (continue) minimal Pattern4ContinueMinimal, } 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, ), ( NormalizedDevShape::Pattern3IfSumMulti, detectors::is_pattern3_if_sum_multi, ), ( NormalizedDevShape::Pattern3IfSumJson, detectors::is_pattern3_if_sum_json, ), // Phase 48-A: Pattern4 continue minimal ( NormalizedDevShape::Pattern4ContinueMinimal, detectors::is_pattern4_continue_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-B: P3 if-sum family Pattern3IfSumMinimal | Pattern3IfSumMulti | Pattern3IfSumJson => P3IfSum, // Phase 48-A: P4 minimal maps to P2CoreSimple for now (future: P4CoreSimple) Pattern4ContinueMinimal => 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 47-C: P3 if-sum canonical set | Pattern3IfSumMinimal | Pattern3IfSumMulti | Pattern3IfSumJson ) } /// 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 | P3IfSum ) } /// 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/multi/json: typically 2-6 params (i + carriers + len/host) let reasonable_param_count = (2..=6).contains(&loop_step.params.len()); has_compare && has_select && has_tail_call && reasonable_param_count } /// Phase 47-B: P3 if-sum (multi-carrier) shape detector pub(crate) fn is_pattern3_if_sum_multi(module: &JoinModule) -> bool { if !is_pattern3_if_sum_minimal(module) { return false; } module .functions .values() .any(|f| f.name == "pattern3_if_sum_multi_min") } /// Phase 47-B: P3 if-sum (JsonParser mini) shape detector pub(crate) fn is_pattern3_if_sum_json(module: &JoinModule) -> bool { if !is_pattern3_if_sum_minimal(module) { return false; } module .functions .values() .any(|f| f.name == "jsonparser_if_sum_min") } /// Phase 48-A: Check if module matches Pattern4 continue minimal shape pub(crate) fn is_pattern4_continue_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, }; // P4 characteristics: // - Has Compare instruction (loop condition or continue check) // - Has conditional Jump (for continue/break semantics) // - Has tail call (loop back) // // Note: Simplified detector - relies on Continue being present in original AST // which gets lowered to conditional tail call structure. let has_compare = loop_step.body.iter().any(|inst| { matches!( inst, JoinInst::Compute(crate::mir::join_ir::MirLikeInst::Compare { .. }) ) }); // Has conditional jump or call (for continue/break check) let has_conditional_flow = loop_step.body.iter().any(|inst| { matches!(inst, JoinInst::Jump { cond: Some(_), .. }) || matches!(inst, JoinInst::Call { k_next: None, .. }) }); // P4 minimal has 2-4 params (i, acc, possibly n) let reasonable_param_count = (2..=4).contains(&loop_step.params.len()); has_compare && has_conditional_flow && 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 ); } #[cfg(feature = "normalized_dev")] #[test] fn test_detect_pattern4_continue_minimal_shape() { use crate::mir::join_ir::normalized::fixtures::build_pattern4_continue_min_structured_for_normalized_dev; let module = build_pattern4_continue_min_structured_for_normalized_dev(); // Should detect Pattern4ContinueMinimal shape assert!( detectors::is_pattern4_continue_minimal(&module), "pattern4_continue_minimal fixture should be detected" ); let shapes = detect_shapes(&module); assert!( shapes.contains(&NormalizedDevShape::Pattern4ContinueMinimal), "detect_shapes() should include Pattern4ContinueMinimal, got: {:?}", shapes ); } }