2025-12-11 20:54:33 +09:00
|
|
|
|
#![cfg(feature = "normalized_dev")]
|
|
|
|
|
|
|
2025-12-12 03:15:45 +09:00
|
|
|
|
use crate::config::env::joinir_dev_enabled;
|
|
|
|
|
|
use crate::mir::join_ir::normalized::dev_env;
|
2025-12-11 20:54:33 +09:00
|
|
|
|
use crate::mir::join_ir::{JoinFuncId, JoinFunction, JoinInst, JoinModule};
|
|
|
|
|
|
|
2025-12-12 04:06:03 +09:00
|
|
|
|
/// 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,
|
|
|
|
|
|
|
2025-12-12 04:40:46 +09:00
|
|
|
|
// Future: Other P2 patterns
|
2025-12-12 04:06:03 +09:00
|
|
|
|
// 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<CarrierRole>,
|
|
|
|
|
|
// pub method_calls: Vec<MethodCallSignature>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl ShapeCapability {
|
|
|
|
|
|
pub fn new(kind: ShapeCapabilityKind) -> Self {
|
|
|
|
|
|
Self { kind }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-11 20:54:33 +09:00
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
2025-12-12 04:40:46 +09:00
|
|
|
|
pub enum NormalizedDevShape {
|
2025-12-11 20:54:33 +09:00
|
|
|
|
Pattern1Mini,
|
|
|
|
|
|
Pattern2Mini,
|
|
|
|
|
|
JsonparserSkipWsMini,
|
2025-12-12 03:15:45 +09:00
|
|
|
|
JsonparserSkipWsReal,
|
2025-12-11 22:12:46 +09:00
|
|
|
|
JsonparserAtoiMini,
|
2025-12-12 03:15:45 +09:00
|
|
|
|
JsonparserAtoiReal,
|
|
|
|
|
|
JsonparserParseNumberReal,
|
2025-12-11 20:54:33 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 03:15:45 +09:00
|
|
|
|
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,
|
|
|
|
|
|
),
|
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
|
|
/// direct ブリッジで扱う shape(dev 限定)。
|
2025-12-11 22:50:23 +09:00
|
|
|
|
pub(crate) fn direct_shapes(module: &JoinModule) -> Vec<NormalizedDevShape> {
|
2025-12-12 03:15:45 +09:00
|
|
|
|
let shapes = detect_shapes(module);
|
|
|
|
|
|
log_shapes("direct", &shapes);
|
|
|
|
|
|
shapes
|
2025-12-11 22:50:23 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 03:15:45 +09:00
|
|
|
|
/// Structured→Normalized の対象 shape(dev 限定)。
|
2025-12-11 20:54:33 +09:00
|
|
|
|
pub(crate) fn supported_shapes(module: &JoinModule) -> Vec<NormalizedDevShape> {
|
2025-12-12 03:15:45 +09:00
|
|
|
|
let shapes = detect_shapes(module);
|
|
|
|
|
|
log_shapes("roundtrip", &shapes);
|
|
|
|
|
|
shapes
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 04:06:03 +09:00
|
|
|
|
/// 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
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
ShapeCapability::new(kind)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 04:40:46 +09:00
|
|
|
|
/// Phase 46: Canonical shapes that ALWAYS use Normalized→MIR(direct)
|
|
|
|
|
|
/// regardless of feature flags or mode.
|
2025-12-12 04:06:03 +09:00
|
|
|
|
///
|
2025-12-12 04:40:46 +09:00
|
|
|
|
/// 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).
|
2025-12-12 04:06:03 +09:00
|
|
|
|
pub fn is_canonical_shape(shape: &NormalizedDevShape) -> bool {
|
|
|
|
|
|
use NormalizedDevShape::*;
|
|
|
|
|
|
matches!(
|
|
|
|
|
|
shape,
|
2025-12-12 04:40:46 +09:00
|
|
|
|
Pattern2Mini
|
|
|
|
|
|
| JsonparserSkipWsMini
|
|
|
|
|
|
| JsonparserSkipWsReal
|
|
|
|
|
|
| JsonparserAtoiMini
|
|
|
|
|
|
// Phase 46: Add P2-Mid patterns
|
|
|
|
|
|
| JsonparserAtoiReal
|
|
|
|
|
|
| JsonparserParseNumberReal
|
2025-12-12 04:06:03 +09:00
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 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)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 03:15:45 +09:00
|
|
|
|
/// canonical(常時 Normalized 経路を通す)対象。
|
2025-12-12 04:40:46 +09:00
|
|
|
|
/// 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).
|
2025-12-12 03:15:45 +09:00
|
|
|
|
pub(crate) fn canonical_shapes(module: &JoinModule) -> Vec<NormalizedDevShape> {
|
|
|
|
|
|
let shapes: Vec<_> = detect_shapes(module)
|
|
|
|
|
|
.into_iter()
|
2025-12-12 04:06:03 +09:00
|
|
|
|
.filter(|s| is_canonical_shape(s))
|
2025-12-12 03:15:45 +09:00
|
|
|
|
.collect();
|
|
|
|
|
|
log_shapes("canonical", &shapes);
|
2025-12-11 20:54:33 +09:00
|
|
|
|
shapes
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-11 22:50:23 +09:00
|
|
|
|
#[allow(dead_code)]
|
|
|
|
|
|
pub(crate) fn is_direct_supported(module: &JoinModule) -> bool {
|
2025-12-12 03:15:45 +09:00
|
|
|
|
!detect_shapes(module).is_empty()
|
2025-12-11 22:50:23 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 03:15:45 +09:00
|
|
|
|
fn detect_shapes(module: &JoinModule) -> Vec<NormalizedDevShape> {
|
|
|
|
|
|
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
|
2025-12-11 20:54:33 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 03:15:45 +09:00
|
|
|
|
// --- 判定ロジック(共通) ---
|
|
|
|
|
|
mod detectors {
|
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
|
|
pub(super) fn is_pattern1_mini(module: &JoinModule) -> bool {
|
|
|
|
|
|
module.is_structured() && find_loop_step(module).is_some()
|
2025-12-11 20:54:33 +09:00
|
|
|
|
}
|
2025-12-12 03:15:45 +09:00
|
|
|
|
|
|
|
|
|
|
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
|
2025-12-11 20:54:33 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 03:15:45 +09:00
|
|
|
|
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")
|
|
|
|
|
|
}
|
2025-12-11 20:54:33 +09:00
|
|
|
|
|
2025-12-12 03:15:45 +09:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2025-12-11 20:54:33 +09:00
|
|
|
|
|
2025-12-12 03:15:45 +09:00
|
|
|
|
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, .. }));
|
2025-12-11 20:54:33 +09:00
|
|
|
|
|
2025-12-12 03:15:45 +09:00
|
|
|
|
has_cond_jump
|
|
|
|
|
|
&& has_tail_call
|
|
|
|
|
|
&& module
|
|
|
|
|
|
.functions
|
|
|
|
|
|
.values()
|
|
|
|
|
|
.any(|f| f.name == "jsonparser_skip_ws_real")
|
2025-12-11 22:12:46 +09:00
|
|
|
|
}
|
2025-12-12 03:15:45 +09:00
|
|
|
|
|
|
|
|
|
|
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")
|
2025-12-11 22:12:46 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 03:15:45 +09:00
|
|
|
|
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;
|
|
|
|
|
|
}
|
2025-12-11 22:12:46 +09:00
|
|
|
|
|
2025-12-12 03:15:45 +09:00
|
|
|
|
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")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub(super) fn find_loop_step(module: &JoinModule) -> Option<&JoinFunction> {
|
|
|
|
|
|
module
|
2025-12-11 22:12:46 +09:00
|
|
|
|
.functions
|
|
|
|
|
|
.values()
|
2025-12-12 03:15:45 +09:00
|
|
|
|
.find(|f| f.name == "loop_step")
|
|
|
|
|
|
.or_else(|| module.functions.get(&JoinFuncId::new(1)))
|
|
|
|
|
|
}
|
2025-12-11 22:12:46 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 03:15:45 +09:00
|
|
|
|
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);
|
|
|
|
|
|
}
|
2025-12-11 20:54:33 +09:00
|
|
|
|
}
|