feat(joinir): Phase 248 - Normalized JoinIR infrastructure

Major refactoring of JoinIR normalization pipeline:

Key changes:
- Structured→Normalized→MIR(direct) pipeline established
- ShapeGuard enhanced with Pattern2 loop validation
- dev_env.rs: New development fixtures and env control
- fixtures.rs: jsonparser_parse_number_real fixture
- normalized_bridge/direct.rs: Direct MIR generation from Normalized
- pattern2_step_schedule.rs: Extracted step scheduling logic

Files changed:
- normalized.rs: Enhanced NormalizedJoinModule with DevEnv support
- shape_guard.rs: Pattern2-specific validation (+300 lines)
- normalized_bridge.rs: Unified bridge with direct path
- loop_with_break_minimal.rs: Integrated step scheduling
- Deleted: step_schedule.rs (moved to pattern2_step_schedule.rs)

New files:
- param_guess.rs: Loop parameter inference
- pattern2_step_schedule.rs: Step scheduling for Pattern2
- phase43-norm-canon-p2-mid.md: Design doc

Tests: 937/937 PASS (+6 from baseline 931)

🤖 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-12 03:15:45 +09:00
parent 59caf5864c
commit ed8e2d3142
32 changed files with 1559 additions and 421 deletions

View File

@ -93,6 +93,8 @@ pub enum JpOp {
Unary(UnaryOp),
Compare(CompareOp),
BoxCall { box_name: String, method: String },
/// 三項演算子cond ? then : else
Select,
}
/// Normalized JoinIR モジュール(テスト専用)。
@ -115,7 +117,7 @@ impl NormalizedModule {
#[cfg(feature = "normalized_dev")]
fn verify_normalized_pattern1(module: &NormalizedModule) -> Result<(), String> {
if module.phase != JoinIrPhase::Normalized {
return Err("Normalized verifier: phase must be Normalized".to_string());
return Err("[joinir/normalized-dev] pattern1: phase must be Normalized".to_string());
}
// Env field bounds check
@ -127,7 +129,7 @@ fn verify_normalized_pattern1(module: &NormalizedModule) -> Result<(), String> {
JpInst::EnvLoad { field, .. } | JpInst::EnvStore { field, .. } => {
if *field >= field_count {
return Err(format!(
"Env field out of range: {} (fields={})",
"[joinir/normalized-dev] pattern1: env field out of range: {} (fields={})",
field, field_count
));
}
@ -156,7 +158,7 @@ fn verify_normalized_pattern1(module: &NormalizedModule) -> Result<(), String> {
JpInst::TailCallFn { .. } | JpInst::TailCallKont { .. } | JpInst::If { .. } => {}
_ => {
return Err(format!(
"Function '{}' does not end with tail call/if",
"[joinir/normalized-dev] pattern1: function '{}' does not end with tail call/if",
func.name
))
}
@ -221,6 +223,12 @@ pub fn normalized_pattern1_to_structured(norm: &NormalizedModule) -> JoinModule
op: *op,
operand: args.get(0).copied().unwrap_or(ValueId(0)),
})),
JpOp::Select => func.body.push(JoinInst::Compute(MirLikeInst::Select {
dst: *dst,
cond: args.get(0).copied().unwrap_or(ValueId(0)),
then_val: args.get(1).copied().unwrap_or(ValueId(0)),
else_val: args.get(2).copied().unwrap_or(ValueId(0)),
})),
JpOp::Compare(op) => func.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: *dst,
op: *op,
@ -300,8 +308,30 @@ pub fn normalize_pattern2_minimal(structured: &JoinModule) -> NormalizedModule {
let mut max = 3;
#[cfg(feature = "normalized_dev")]
{
if shape_guard::is_jsonparser_atoi_mini(structured) {
max = 8;
let shapes = shape_guard::supported_shapes(structured);
if shapes
.iter()
.any(|s| matches!(s, NormalizedDevShape::JsonparserAtoiMini))
{
max = max.max(8);
}
if shapes
.iter()
.any(|s| matches!(s, NormalizedDevShape::JsonparserAtoiReal))
{
max = max.max(10);
}
if shapes
.iter()
.any(|s| matches!(s, NormalizedDevShape::JsonparserParseNumberReal))
{
max = max.max(12);
}
if shapes
.iter()
.any(|s| matches!(s, NormalizedDevShape::JsonparserSkipWsReal))
{
max = max.max(6);
}
}
max
@ -397,6 +427,18 @@ pub fn normalize_pattern2_minimal(structured: &JoinModule) -> NormalizedModule {
args: vec![*lhs, *rhs],
})
}
JoinInst::Compute(MirLikeInst::Select {
dst,
cond,
then_val,
else_val,
}) => {
body.push(JpInst::Let {
dst: *dst,
op: JpOp::Select,
args: vec![*cond, *then_val, *else_val],
})
}
JoinInst::Jump { cont, args, cond } => {
if let Some(cond_val) = cond {
body.push(JpInst::If {
@ -412,6 +454,19 @@ pub fn normalize_pattern2_minimal(structured: &JoinModule) -> NormalizedModule {
});
}
}
JoinInst::Select {
dst,
cond,
then_val,
else_val,
..
} => {
body.push(JpInst::Let {
dst: *dst,
op: JpOp::Select,
args: vec![*cond, *then_val, *else_val],
});
}
JoinInst::Call { func, args, k_next, .. } => {
if k_next.is_none() {
body.push(JpInst::TailCallFn {
@ -523,6 +578,14 @@ pub fn normalized_pattern2_to_structured(norm: &NormalizedModule) -> JoinModule
op: *op,
operand: args.get(0).copied().unwrap_or(ValueId(0)),
})),
JpOp::Select => func
.body
.push(JoinInst::Compute(MirLikeInst::Select {
dst: *dst,
cond: args.get(0).copied().unwrap_or(ValueId(0)),
then_val: args.get(1).copied().unwrap_or(ValueId(0)),
else_val: args.get(2).copied().unwrap_or(ValueId(0)),
})),
JpOp::Compare(op) => func.body.push(JoinInst::Compute(MirLikeInst::Compare {
dst: *dst,
op: *op,
@ -578,7 +641,9 @@ fn verify_normalized_pattern2(
max_env_fields: usize,
) -> Result<(), String> {
if module.phase != JoinIrPhase::Normalized {
return Err("Normalized verifier (Pattern2): phase must be Normalized".to_string());
return Err(
"[joinir/normalized-dev] pattern2: phase must be Normalized".to_string(),
);
}
let mut layout_sizes: HashMap<u32, usize> = HashMap::new();
@ -586,7 +651,7 @@ fn verify_normalized_pattern2(
let size = layout.fields.len();
if !(1..=max_env_fields).contains(&size) {
return Err(format!(
"Normalized Pattern2 expects 1..={} env fields, got {}",
"[joinir/normalized-dev] pattern2: expected 1..={} env fields, got {}",
max_env_fields, size
));
}
@ -615,7 +680,10 @@ fn verify_normalized_pattern2(
| JpInst::If { env, .. } => {
if let Some(expected) = expected_env_len {
if env.is_empty() {
return Err("Normalized Pattern2 env must not be empty".to_string());
return Err(
"[joinir/normalized-dev] pattern2: env must not be empty"
.to_string(),
);
}
let _ = expected;
}
@ -631,7 +699,7 @@ fn verify_normalized_pattern2(
| JpInst::If { .. } => {}
_ => {
return Err(format!(
"Function '{}' does not end with tail call/if",
"[joinir/normalized-dev] pattern2: function '{}' does not end with tail call/if",
func.name
));
}
@ -813,11 +881,14 @@ pub(crate) fn normalized_dev_roundtrip_structured(
return Err("[joinir/normalized-dev] module shape is not supported by normalized_dev".into());
}
let verbose = crate::config::env::joinir_dev_enabled();
let debug = dev_env::normalized_dev_logs_enabled() && crate::config::env::joinir_dev_enabled();
for shape in shapes {
if verbose {
eprintln!("[joinir/normalized-dev] attempting {:?} normalization", shape);
if debug {
eprintln!(
"[joinir/normalized-dev/roundtrip] attempting {:?} normalization",
shape
);
}
let attempt = match shape {
@ -827,7 +898,10 @@ pub(crate) fn normalized_dev_roundtrip_structured(
})),
NormalizedDevShape::Pattern2Mini
| NormalizedDevShape::JsonparserSkipWsMini
| NormalizedDevShape::JsonparserAtoiMini => catch_unwind(AssertUnwindSafe(|| {
| NormalizedDevShape::JsonparserSkipWsReal
| NormalizedDevShape::JsonparserAtoiMini
| NormalizedDevShape::JsonparserAtoiReal
| NormalizedDevShape::JsonparserParseNumberReal => catch_unwind(AssertUnwindSafe(|| {
let norm = normalize_pattern2_minimal(module);
normalized_pattern2_to_structured(&norm)
})),
@ -835,9 +909,9 @@ pub(crate) fn normalized_dev_roundtrip_structured(
match attempt {
Ok(structured) => {
if verbose {
if debug {
eprintln!(
"[joinir/normalized-dev] {:?} normalization succeeded (functions={})",
"[joinir/normalized-dev/roundtrip] {:?} normalization succeeded (functions={})",
shape,
structured.functions.len()
);
@ -845,9 +919,9 @@ pub(crate) fn normalized_dev_roundtrip_structured(
return Ok(structured);
}
Err(_) => {
if verbose {
if debug {
eprintln!(
"[joinir/normalized-dev] {:?} normalization failed (unsupported)",
"[joinir/normalized-dev/roundtrip] {:?} normalization failed (unsupported)",
shape
);
}