refactor(joinir): Phase 286C - Normalized module box modularization

Extract 3 boxes from normalized.rs (1,280 → 563 lines, 56% reduction):
- pattern1_normalizer.rs: Pattern 1 (simple while loops) normalization
- pattern2_normalizer.rs: Pattern 2/3/4 (loops with carriers) normalization
- env_layout_builder.rs: Environment layout construction utilities

Changes:
- Replace large function implementations with re-exports
- Add module declarations for new boxes
- Fix JoinInst import in shape_guard/mod.rs
- Maintain backward compatibility via legacy function wrappers

Acceptance:
- Build passes: cargo build --release
- Quick smoke tests: 17/18 PASS (1 pre-existing failure)
- No regressions in existing functionality

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-25 00:39:08 +09:00
parent f740e6542f
commit 48048425e5
5 changed files with 1183 additions and 736 deletions

View File

@ -25,6 +25,12 @@ pub mod fixtures;
pub mod loop_step_inspector;
#[cfg(feature = "normalized_dev")]
pub mod shape_guard;
// Phase 286C: Box modularization - core normalization boxes
pub mod pattern1_normalizer;
pub mod pattern2_normalizer;
pub mod env_layout_builder;
#[cfg(feature = "normalized_dev")]
use crate::mir::join_ir::normalized::shape_guard::NormalizedDevShape;
@ -122,443 +128,26 @@ impl NormalizedModule {
}
}
// Phase 286C: Re-export from pattern1_normalizer box
#[cfg(feature = "normalized_dev")]
fn verify_normalized_pattern1(module: &NormalizedModule) -> Result<(), String> {
if module.phase != JoinIrPhase::Normalized {
return Err("[joinir/normalized-dev] pattern1: phase must be Normalized".to_string());
}
// Env field bounds check
if let Some(env) = module.env_layouts.get(0) {
let field_count = env.fields.len();
for func in module.functions.values() {
for inst in &func.body {
match inst {
JpInst::EnvLoad { field, .. } | JpInst::EnvStore { field, .. } => {
if *field >= field_count {
return Err(format!(
"[joinir/normalized-dev] pattern1: env field out of range: {} (fields={})",
field, field_count
));
}
}
_ => {}
}
}
}
}
for func in module.functions.values() {
for inst in &func.body {
match inst {
JpInst::Let { .. }
| JpInst::EnvLoad { .. }
| JpInst::EnvStore { .. }
| JpInst::TailCallFn { .. }
| JpInst::TailCallKont { .. }
| JpInst::If { .. } => {}
}
}
// Tail: allow TailCall or If only
if let Some(last) = func.body.last() {
match last {
JpInst::TailCallFn { .. } | JpInst::TailCallKont { .. } | JpInst::If { .. } => {}
_ => {
return Err(format!(
"[joinir/normalized-dev] pattern1: function '{}' does not end with tail call/if",
func.name
))
}
}
}
}
Ok(())
pattern1_normalizer::Pattern1Normalizer::verify(module)
}
// Phase 286C: Re-export from pattern1_normalizer box
/// Pattern1 専用: Normalized → Structured への簡易逆変換。
pub fn normalized_pattern1_to_structured(norm: &NormalizedModule) -> JoinModule {
assert_eq!(
norm.phase,
JoinIrPhase::Normalized,
"normalized_pattern1_to_structured expects Normalized phase"
);
let env_layout = norm
.env_layouts
.get(0)
.expect("normalized_pattern1_to_structured: missing env layout");
let mut module = JoinModule::new();
for (jp_id, jp_fn) in &norm.functions {
let params: Vec<ValueId> = jp_fn
.env_layout
.and_then(|id| norm.env_layouts.iter().find(|layout| layout.id == id))
.unwrap_or(env_layout)
.fields
.iter()
.enumerate()
.map(|(idx, f)| f.value_id.unwrap_or(ValueId(idx as u32)))
.collect();
let mut func = JoinFunction::new(JoinFuncId(jp_id.0), jp_fn.name.clone(), params);
for inst in &jp_fn.body {
match inst {
JpInst::Let { dst, op, args } => match op {
JpOp::Const(v) => func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: *dst,
value: v.clone(),
})),
JpOp::BoxCall { box_name, method } => {
func.body.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: Some(*dst),
box_name: box_name.clone(),
method: method.clone(),
args: args.clone(),
}))
}
JpOp::BinOp(op) => func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: *dst,
op: *op,
lhs: args.get(0).copied().unwrap_or(ValueId(0)),
rhs: args.get(1).copied().unwrap_or(ValueId(0)),
})),
JpOp::Unary(op) => func.body.push(JoinInst::Compute(MirLikeInst::UnaryOp {
dst: *dst,
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,
lhs: args.get(0).copied().unwrap_or(ValueId(0)),
rhs: args.get(1).copied().unwrap_or(ValueId(0)),
})),
},
JpInst::TailCallFn { target, env } => func.body.push(JoinInst::Call {
func: JoinFuncId(target.0),
args: env.clone(),
k_next: None,
dst: None,
}),
JpInst::TailCallKont { target, env } => func.body.push(JoinInst::Jump {
cont: JoinContId(target.0),
args: env.clone(),
cond: None,
}),
JpInst::If {
cond,
then_target,
else_target,
env,
} => {
// Jump to then_target on cond, else jump to else_target (Pattern1 minimal)
func.body.push(JoinInst::Jump {
cont: JoinContId(then_target.0),
args: env.clone(),
cond: Some(*cond),
});
func.body.push(JoinInst::Jump {
cont: JoinContId(else_target.0),
args: env.clone(),
cond: None,
});
}
JpInst::EnvLoad { .. } | JpInst::EnvStore { .. } => {
// Not used in Pattern1 minimal; ignore for now
}
}
}
module.add_function(func);
}
module.entry = norm.entry.map(|e| JoinFuncId(e.0));
module.phase = JoinIrPhase::Structured;
module
pattern1_normalizer::Pattern1Normalizer::to_structured(norm)
}
// Phase 286C: Re-export from pattern2_normalizer box
/// Pattern2 専用のミニ変換(最小サブセット: ループ変数1つ + break、acc などの LoopState を 1 個まで+ホスト 1 個まで)。
///
/// 制約:
/// - structured.phase は Structured であること
/// - main/loop_step/k_exit の 3 関数構成joinir_min_loop 相当)
pub fn normalize_pattern2_minimal(structured: &JoinModule) -> NormalizedModule {
assert!(
structured.is_structured(),
"normalize_pattern2_minimal: expected Structured JoinIR"
);
// Minimal guardrail: Pattern2 mini should have main/loop_step/k_exit only, with 1 loop param.
let func_count = structured.functions.len();
let loop_func = structured
.functions
.values()
.find(|f| f.name == "loop_step")
.or_else(|| structured.functions.get(&JoinFuncId::new(1)))
.expect("normalize_pattern2_minimal: loop_step not found");
assert!(
func_count == 3,
"normalize_pattern2_minimal: expected 3 functions (entry/loop_step/k_exit) but got {}",
func_count
);
let param_max = {
#[allow(unused_mut)]
let mut max = 3;
#[cfg(feature = "normalized_dev")]
{
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);
}
if shapes.iter().any(|s| {
matches!(
s,
NormalizedDevShape::Pattern4ContinueMinimal
| NormalizedDevShape::JsonparserParseArrayContinueSkipWs
| NormalizedDevShape::JsonparserParseObjectContinueSkipWs
)
}) {
max = max.max(6);
}
if shapes.iter().any(|s| {
matches!(
s,
NormalizedDevShape::Pattern3IfSumMinimal
| NormalizedDevShape::Pattern3IfSumMulti
| NormalizedDevShape::Pattern3IfSumJson
| NormalizedDevShape::SelfhostIfSumP3
| NormalizedDevShape::SelfhostIfSumP3Ext
| NormalizedDevShape::SelfhostStmtCountP3
| NormalizedDevShape::SelfhostDetectFormatP3
)
}) {
max = max.max(6);
}
}
max
};
assert!(
(1..=param_max).contains(&loop_func.params.len()),
"normalize_pattern2_minimal: expected 1..={} params (loop var + carriers + optional host)",
param_max
);
let jump_conds = loop_func
.body
.iter()
.filter(|inst| matches!(inst, JoinInst::Jump { cond: Some(_), .. }))
.count();
let tail_calls = loop_func
.body
.iter()
.filter(|inst| matches!(inst, JoinInst::Call { k_next: None, .. }))
.count();
assert!(
jump_conds >= 1 && tail_calls >= 1,
"normalize_pattern2_minimal: expected at least one conditional jump and one tail call"
);
let mut functions = BTreeMap::new();
let mut env_layouts = Vec::new();
for (fid, func) in &structured.functions {
let env_layout_id = if func.params.is_empty() {
None
} else {
let id = env_layouts.len() as u32;
env_layouts.push(EnvLayout {
id,
fields: func
.params
.iter()
.enumerate()
.map(|(idx, vid)| EnvField {
name: format!("field{}", idx),
ty: None,
value_id: Some(*vid),
})
.collect(),
});
Some(id)
};
let mut body = Vec::new();
for inst in &func.body {
match inst {
JoinInst::Compute(MirLikeInst::Const { dst, value }) => body.push(JpInst::Let {
dst: *dst,
op: JpOp::Const(value.clone()),
args: vec![],
}),
JoinInst::Compute(MirLikeInst::BoxCall {
dst,
box_name,
method,
args,
}) => {
if let Some(dst) = dst {
body.push(JpInst::Let {
dst: *dst,
op: JpOp::BoxCall {
box_name: box_name.clone(),
method: method.clone(),
},
args: args.clone(),
})
}
}
JoinInst::Compute(MirLikeInst::BinOp { dst, op, lhs, rhs }) => {
body.push(JpInst::Let {
dst: *dst,
op: JpOp::BinOp(*op),
args: vec![*lhs, *rhs],
})
}
JoinInst::Compute(MirLikeInst::UnaryOp { dst, op, operand }) => {
body.push(JpInst::Let {
dst: *dst,
op: JpOp::Unary(*op),
args: vec![*operand],
})
}
JoinInst::Compute(MirLikeInst::Compare { dst, op, lhs, rhs }) => {
body.push(JpInst::Let {
dst: *dst,
op: JpOp::Compare(*op),
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 {
cond: *cond_val,
then_target: JpFuncId(cont.0),
else_target: JpFuncId(loop_func.id.0),
env: args.clone(),
});
} else {
body.push(JpInst::TailCallKont {
target: JpFuncId(cont.0),
env: args.clone(),
});
}
}
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 {
target: JpFuncId(func.0),
env: args.clone(),
});
}
}
JoinInst::MethodCall {
dst,
receiver,
method,
args,
..
} => {
let mut call_args = Vec::with_capacity(args.len() + 1);
call_args.push(*receiver);
call_args.extend(args.iter().copied());
body.push(JpInst::Let {
dst: *dst,
op: JpOp::BoxCall {
box_name: "unknown".to_string(),
method: method.clone(),
},
args: call_args,
});
}
_ => {
// Ret / other instructions are ignored in this minimal prototype
}
}
}
functions.insert(
JpFuncId(fid.0),
JpFunction {
id: JpFuncId(fid.0),
name: func.name.clone(),
env_layout: env_layout_id,
body,
is_kont: func.name.starts_with("k_"),
},
);
}
let norm = NormalizedModule {
functions,
entry: structured.entry.map(|e| JpFuncId(e.0)),
env_layouts,
phase: JoinIrPhase::Normalized,
structured_backup: Some(structured.clone()),
};
#[cfg(feature = "normalized_dev")]
{
verify_normalized_pattern2(&norm, param_max).expect("normalized Pattern2 verifier");
}
norm
pattern2_normalizer::Pattern2Normalizer::normalize(structured)
}
#[cfg(feature = "normalized_dev")]
@ -720,335 +309,29 @@ fn normalize_pattern4_continue_shape(
Ok(normalize_pattern2_minimal(structured))
}
// Phase 286C: Re-export from pattern2_normalizer box
/// Pattern2 専用: Normalized → Structured への簡易逆変換。
pub fn normalized_pattern2_to_structured(norm: &NormalizedModule) -> JoinModule {
if let Some(backup) = norm.to_structured() {
return backup;
}
let mut module = JoinModule::new();
for (jp_id, jp_fn) in &norm.functions {
let params: Vec<ValueId> = jp_fn
.env_layout
.and_then(|id| norm.env_layouts.iter().find(|layout| layout.id == id))
.map(|layout| {
layout
.fields
.iter()
.enumerate()
.map(|(idx, f)| f.value_id.unwrap_or(ValueId(idx as u32)))
.collect()
})
.unwrap_or_default();
let mut func = JoinFunction::new(JoinFuncId(jp_id.0), jp_fn.name.clone(), params);
for inst in &jp_fn.body {
match inst {
JpInst::Let { dst, op, args } => match op {
JpOp::Const(v) => func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: *dst,
value: v.clone(),
})),
JpOp::BoxCall { box_name, method } => {
func.body.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: Some(*dst),
box_name: box_name.clone(),
method: method.clone(),
args: args.clone(),
}))
}
JpOp::BinOp(op) => func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: *dst,
op: *op,
lhs: args.get(0).copied().unwrap_or(ValueId(0)),
rhs: args.get(1).copied().unwrap_or(ValueId(0)),
})),
JpOp::Unary(op) => func.body.push(JoinInst::Compute(MirLikeInst::UnaryOp {
dst: *dst,
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,
lhs: args.get(0).copied().unwrap_or(ValueId(0)),
rhs: args.get(1).copied().unwrap_or(ValueId(0)),
})),
},
JpInst::TailCallFn { target, env } => func.body.push(JoinInst::Call {
func: JoinFuncId(target.0),
args: env.clone(),
k_next: None,
dst: None,
}),
JpInst::TailCallKont { target, env } => func.body.push(JoinInst::Jump {
cont: JoinContId(target.0),
args: env.clone(),
cond: None,
}),
JpInst::If {
cond,
then_target,
else_target,
env,
} => {
func.body.push(JoinInst::Jump {
cont: JoinContId(then_target.0),
args: env.clone(),
cond: Some(*cond),
});
func.body.push(JoinInst::Jump {
cont: JoinContId(else_target.0),
args: env.clone(),
cond: None,
});
}
JpInst::EnvLoad { .. } | JpInst::EnvStore { .. } => {
// Not used in minimal pattern2 bridge
}
}
}
module.add_function(func);
}
module.entry = norm.entry.map(|e| JoinFuncId(e.0));
module.phase = JoinIrPhase::Structured;
module
pattern2_normalizer::Pattern2Normalizer::to_structured(norm)
}
// Phase 286C: Re-export from pattern2_normalizer box
#[cfg(feature = "normalized_dev")]
fn verify_normalized_pattern2(
module: &NormalizedModule,
max_env_fields: usize,
) -> Result<(), String> {
if module.phase != JoinIrPhase::Normalized {
return Err("[joinir/normalized-dev] pattern2: phase must be Normalized".to_string());
}
let mut layout_sizes: HashMap<u32, usize> = HashMap::new();
for layout in &module.env_layouts {
let size = layout.fields.len();
if !(1..=max_env_fields).contains(&size) {
return Err(format!(
"[joinir/normalized-dev] pattern2: expected 1..={} env fields, got {}",
max_env_fields, size
));
}
layout_sizes.insert(layout.id, size);
}
for func in module.functions.values() {
let expected_env_len = func
.env_layout
.and_then(|id| layout_sizes.get(&id))
.copied();
for inst in &func.body {
match inst {
JpInst::Let { .. }
| JpInst::EnvLoad { .. }
| JpInst::EnvStore { .. }
| JpInst::TailCallFn { .. }
| JpInst::TailCallKont { .. }
| JpInst::If { .. } => {}
}
match inst {
JpInst::TailCallFn { env, .. }
| JpInst::TailCallKont { env, .. }
| JpInst::If { env, .. } => {
if let Some(expected) = expected_env_len {
if env.is_empty() {
return Err("[joinir/normalized-dev] pattern2: env must not be empty"
.to_string());
}
let _ = expected;
}
}
_ => {}
}
}
if let Some(last) = func.body.last() {
match last {
JpInst::TailCallFn { .. } | JpInst::TailCallKont { .. } | JpInst::If { .. } => {}
_ => {
return Err(format!(
"[joinir/normalized-dev] pattern2: function '{}' does not end with tail call/if",
func.name
));
}
}
}
}
Ok(())
pattern2_normalizer::Pattern2Normalizer::verify(module, max_env_fields)
}
// Phase 286C: Re-export from pattern1_normalizer box
/// Pattern1 専用のミニ変換。
///
/// 制約:
/// - structured.phase は Structured であること
/// - 対象は Pattern1 のシンプル whilebreak/continue なし)
pub fn normalize_pattern1_minimal(structured: &JoinModule) -> NormalizedModule {
assert!(
structured.is_structured(),
"normalize_pattern1_minimal: expected Structured JoinIR"
);
// entry/loop_step/k_exit を前提に、loop_step を拾う
let loop_func = structured
.functions
.values()
.find(|f| f.name == "loop_step")
.or_else(|| structured.functions.get(&JoinFuncId::new(1)))
.expect("normalize_pattern1_minimal: loop_step not found");
// EnvLayout をざっくり作る(フィールド名は field0, field1,... で代用)
let env_layout = EnvLayout {
id: 0,
fields: loop_func
.params
.iter()
.enumerate()
.map(|(idx, vid)| EnvField {
name: format!("field{}", idx),
ty: None,
value_id: Some(*vid),
})
.collect(),
};
// loop_step の Compute を Let に写経Pattern1 では Compute/Call/Ret のみ想定)
let mut extra_konts: HashSet<JpFuncId> = HashSet::new();
let mut jp_body = Vec::new();
for inst in &loop_func.body {
match inst {
JoinInst::Compute(MirLikeInst::Const { dst, value }) => jp_body.push(JpInst::Let {
dst: *dst,
op: JpOp::Const(value.clone()),
args: vec![],
}),
JoinInst::Compute(MirLikeInst::BoxCall {
dst,
box_name,
method,
args,
}) => {
if let Some(dst) = dst {
jp_body.push(JpInst::Let {
dst: *dst,
op: JpOp::BoxCall {
box_name: box_name.clone(),
method: method.clone(),
},
args: args.clone(),
})
}
}
JoinInst::Compute(MirLikeInst::BinOp { dst, op, lhs, rhs }) => {
jp_body.push(JpInst::Let {
dst: *dst,
op: JpOp::BinOp(*op),
args: vec![*lhs, *rhs],
})
}
JoinInst::Compute(MirLikeInst::UnaryOp { dst, op, operand }) => {
jp_body.push(JpInst::Let {
dst: *dst,
op: JpOp::Unary(*op),
args: vec![*operand],
})
}
JoinInst::Compute(MirLikeInst::Compare { dst, op, lhs, rhs }) => {
jp_body.push(JpInst::Let {
dst: *dst,
op: JpOp::Compare(*op),
args: vec![*lhs, *rhs],
})
}
// Tail recursion / exit は TailCall と If でざっくり表現
JoinInst::Jump { cont, args, cond } => {
if let Some(cond_val) = cond {
extra_konts.insert(JpFuncId(cont.0));
jp_body.push(JpInst::If {
cond: *cond_val,
then_target: JpFuncId(cont.0),
else_target: JpFuncId(loop_func.id.0),
env: args.clone(),
});
} else {
extra_konts.insert(JpFuncId(cont.0));
jp_body.push(JpInst::TailCallKont {
target: JpFuncId(cont.0),
env: args.clone(),
});
}
}
JoinInst::Call { func, args, .. } => jp_body.push(JpInst::TailCallFn {
target: JpFuncId(func.0),
env: args.clone(),
}),
JoinInst::Ret { value } => {
if let Some(v) = value {
let kont_id = JpFuncId(loop_func.id.0 + 1);
extra_konts.insert(kont_id);
jp_body.push(JpInst::TailCallKont {
target: kont_id,
env: vec![*v],
});
}
}
_ => {
// Pattern1 の最小変換なので他は無視(将来拡張)
}
}
}
let loop_fn = JpFunction {
id: JpFuncId(loop_func.id.0),
name: loop_func.name.clone(),
env_layout: Some(env_layout.id),
body: jp_body,
is_kont: false,
};
let mut functions = BTreeMap::new();
functions.insert(loop_fn.id, loop_fn);
for kont_id in extra_konts {
functions.entry(kont_id).or_insert_with(|| JpFunction {
id: kont_id,
name: format!("kont_{}", kont_id.0),
env_layout: Some(env_layout.id),
body: Vec::new(),
is_kont: true,
});
}
let norm = NormalizedModule {
functions,
entry: Some(JpFuncId(loop_func.id.0)),
env_layouts: vec![env_layout],
phase: JoinIrPhase::Normalized,
structured_backup: Some(structured.clone()),
};
#[cfg(feature = "normalized_dev")]
{
verify_normalized_pattern1(&norm).expect("normalized verifier");
}
norm
pattern1_normalizer::Pattern1Normalizer::normalize(structured)
}
/// Dev helper: Structured → Normalized → Structured roundtrip (Pattern1/2 minis only).

View File

@ -0,0 +1,149 @@
//! Environment Layout Builder
//!
//! Builds environment layouts for normalized functions.
//!
//! ## Responsibilities
//! - Build env layouts from function parameters
//! - Create default env layouts
//! - Manage env field construction
use crate::mir::ValueId;
use crate::mir::join_ir::JoinFunction;
use super::{EnvField, EnvLayout};
/// Environment Layout Builder
///
/// Constructs environment layouts for normalized functions.
///
/// The env layout represents the closure environment for a function,
/// mapping parameter names to ValueIds.
pub struct EnvLayoutBuilder;
impl EnvLayoutBuilder {
/// Build env layout from function parameters
///
/// # Arguments
/// * `func` - JoinFunction to extract parameters from
/// * `layout_id` - Unique ID for the layout
///
/// # Returns
/// EnvLayout with fields named "field0", "field1", etc.
///
/// # Example
/// ```ignore
/// let layout = EnvLayoutBuilder::build_from_params(&func, 0);
/// assert_eq!(layout.fields.len(), func.params.len());
/// ```
pub fn build_from_params(func: &JoinFunction, layout_id: u32) -> EnvLayout {
EnvLayout {
id: layout_id,
fields: func
.params
.iter()
.enumerate()
.map(|(idx, vid)| EnvField {
name: format!("field{}", idx),
ty: None,
value_id: Some(*vid),
})
.collect(),
}
}
/// Build a default empty env layout
///
/// # Arguments
/// * `layout_id` - Unique ID for the layout
///
/// # Returns
/// Empty EnvLayout
pub fn build_default(layout_id: u32) -> EnvLayout {
EnvLayout {
id: layout_id,
fields: Vec::new(),
}
}
/// Build env layout from explicit fields
///
/// # Arguments
/// * `layout_id` - Unique ID for the layout
/// * `fields` - Vector of env fields
///
/// # Returns
/// EnvLayout with the specified fields
pub fn build_from_fields(layout_id: u32, fields: Vec<EnvField>) -> EnvLayout {
EnvLayout { id: layout_id, fields }
}
}
// Re-export for backward compatibility
#[allow(deprecated)]
pub use self::EnvLayoutBuilder as EnvLayoutBuilderBox;
/// Legacy helper: Build env layout with minimal field naming
#[deprecated(note = "Use EnvLayoutBuilder::build_from_params instead")]
pub fn build_env_layout_from_params(func: &JoinFunction, layout_id: u32) -> EnvLayout {
EnvLayoutBuilder::build_from_params(func, layout_id)
}
/// Legacy helper: Create default empty env layout
#[deprecated(note = "Use EnvLayoutBuilder::build_default instead")]
pub fn build_default_env_layout(layout_id: u32) -> EnvLayout {
EnvLayoutBuilder::build_default(layout_id)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_build_from_params() {
let func = JoinFunction::new(
crate::mir::join_ir::JoinFuncId::new(0),
"test".to_string(),
vec![ValueId(1), ValueId(2), ValueId(3)],
);
let layout = EnvLayoutBuilder::build_from_params(&func, 0);
assert_eq!(layout.id, 0);
assert_eq!(layout.fields.len(), 3);
assert_eq!(layout.fields[0].name, "field0");
assert_eq!(layout.fields[0].value_id, Some(ValueId(1)));
assert_eq!(layout.fields[1].name, "field1");
assert_eq!(layout.fields[1].value_id, Some(ValueId(2)));
assert_eq!(layout.fields[2].name, "field2");
assert_eq!(layout.fields[2].value_id, Some(ValueId(3)));
}
#[test]
fn test_build_default() {
let layout = EnvLayoutBuilder::build_default(42);
assert_eq!(layout.id, 42);
assert_eq!(layout.fields.len(), 0);
}
#[test]
fn test_build_from_fields() {
let fields = vec![
EnvField {
name: "x".to_string(),
ty: None,
value_id: Some(ValueId(10)),
},
EnvField {
name: "y".to_string(),
ty: None,
value_id: Some(ValueId(20)),
},
];
let layout = EnvLayoutBuilder::build_from_fields(1, fields);
assert_eq!(layout.id, 1);
assert_eq!(layout.fields.len(), 2);
}
}

View File

@ -0,0 +1,424 @@
//! Pattern 1 Normalization - Simple While Loops
//!
//! Normalizes Pattern 1 JoinIR to Normalized form.
//! Pattern 1 is the simplest while loop pattern (no break/continue).
//!
//! ## Responsibilities
//! - Convert Structured JoinIR → Normalized JoinIR (Pattern 1)
//! - Convert Normalized JoinIR → Structured JoinIR (reverse)
//! - Verify normalized module invariants
use std::collections::{BTreeMap, HashSet};
use crate::mir::join_ir::{
BinOpKind, CompareOp, ConstValue, JoinContId, JoinFuncId, JoinFunction, JoinInst, JoinIrPhase,
JoinModule, MirLikeInst, UnaryOp,
};
use crate::mir::ValueId;
use super::{EnvField, EnvLayout, JpFuncId, JpFunction, JpInst, JpOp, NormalizedModule};
/// Pattern 1 Normalizer
///
/// Normalizes Pattern 1 (simple while loops) to Normalized form.
///
/// Pattern 1 constraints:
/// - Simple while loop (no break/continue)
/// - Entry/loop_step/k_exit function structure
/// - Only Compute/Call/Ret/Join instructions
pub struct Pattern1Normalizer;
impl Pattern1Normalizer {
/// Normalize Pattern 1 Structured JoinIR to Normalized form
///
/// # Arguments
/// * `structured` - Structured JoinIR module (must be Pattern 1)
///
/// # Returns
/// Normalized module with backup
///
/// # Panics
/// - If structured.phase is not Structured
/// - If loop_step function is not found
pub fn normalize(structured: &JoinModule) -> NormalizedModule {
assert!(
structured.is_structured(),
"normalize_pattern1_minimal: expected Structured JoinIR"
);
// entry/loop_step/k_exit を前提に、loop_step を拾う
let loop_func = structured
.functions
.values()
.find(|f| f.name == "loop_step")
.or_else(|| structured.functions.get(&JoinFuncId::new(1)))
.expect("normalize_pattern1_minimal: loop_step not found");
// EnvLayout をざっくり作る(フィールド名は field0, field1,... で代用)
let env_layout = EnvLayout {
id: 0,
fields: loop_func
.params
.iter()
.enumerate()
.map(|(idx, vid)| EnvField {
name: format!("field{}", idx),
ty: None,
value_id: Some(*vid),
})
.collect(),
};
// loop_step の Compute を Let に写経Pattern1 では Compute/Call/Ret のみ想定)
let mut extra_konts: HashSet<JpFuncId> = HashSet::new();
let mut jp_body = Vec::new();
for inst in &loop_func.body {
match inst {
JoinInst::Compute(MirLikeInst::Const { dst, value }) => {
jp_body.push(JpInst::Let {
dst: *dst,
op: JpOp::Const(value.clone()),
args: vec![],
})
}
JoinInst::Compute(MirLikeInst::BoxCall {
dst,
box_name,
method,
args,
}) => {
if let Some(dst) = dst {
jp_body.push(JpInst::Let {
dst: *dst,
op: JpOp::BoxCall {
box_name: box_name.clone(),
method: method.clone(),
},
args: args.clone(),
})
}
}
JoinInst::Compute(MirLikeInst::BinOp { dst, op, lhs, rhs }) => {
jp_body.push(JpInst::Let {
dst: *dst,
op: JpOp::BinOp(*op),
args: vec![*lhs, *rhs],
})
}
JoinInst::Compute(MirLikeInst::UnaryOp { dst, op, operand }) => {
jp_body.push(JpInst::Let {
dst: *dst,
op: JpOp::Unary(*op),
args: vec![*operand],
})
}
JoinInst::Compute(MirLikeInst::Compare { dst, op, lhs, rhs }) => {
jp_body.push(JpInst::Let {
dst: *dst,
op: JpOp::Compare(*op),
args: vec![*lhs, *rhs],
})
}
// Tail recursion / exit は TailCall と If でざっくり表現
JoinInst::Jump { cont, args, cond } => {
if let Some(cond_val) = cond {
extra_konts.insert(JpFuncId(cont.0));
jp_body.push(JpInst::If {
cond: *cond_val,
then_target: JpFuncId(cont.0),
else_target: JpFuncId(loop_func.id.0),
env: args.clone(),
});
} else {
extra_konts.insert(JpFuncId(cont.0));
jp_body.push(JpInst::TailCallKont {
target: JpFuncId(cont.0),
env: args.clone(),
});
}
}
JoinInst::Call { func, args, .. } => jp_body.push(JpInst::TailCallFn {
target: JpFuncId(func.0),
env: args.clone(),
}),
JoinInst::Ret { value } => {
if let Some(v) = value {
let kont_id = JpFuncId(loop_func.id.0 + 1);
extra_konts.insert(kont_id);
jp_body.push(JpInst::TailCallKont {
target: kont_id,
env: vec![*v],
});
}
}
_ => {
// Pattern1 の最小変換なので他は無視(将来拡張)
}
}
}
let loop_fn = JpFunction {
id: JpFuncId(loop_func.id.0),
name: loop_func.name.clone(),
env_layout: Some(env_layout.id),
body: jp_body,
is_kont: false,
};
let mut functions = BTreeMap::new();
functions.insert(loop_fn.id, loop_fn);
for kont_id in extra_konts {
functions
.entry(kont_id)
.or_insert_with(|| JpFunction {
id: kont_id,
name: format!("kont_{}", kont_id.0),
env_layout: Some(env_layout.id),
body: Vec::new(),
is_kont: true,
});
}
let norm = NormalizedModule {
functions,
entry: Some(JpFuncId(loop_func.id.0)),
env_layouts: vec![env_layout],
phase: JoinIrPhase::Normalized,
structured_backup: Some(structured.clone()),
};
#[cfg(feature = "normalized_dev")]
{
Self::verify(&norm).expect("normalized verifier");
}
norm
}
/// Convert Normalized Pattern 1 back to Structured JoinIR
///
/// # Arguments
/// * `norm` - Normalized module (must be Pattern 1, Normalized phase)
///
/// # Returns
/// Structured JoinIR module
///
/// # Panics
/// - If norm.phase is not Normalized
/// - If env layout is missing
pub fn to_structured(norm: &NormalizedModule) -> JoinModule {
assert_eq!(
norm.phase,
JoinIrPhase::Normalized,
"normalized_pattern1_to_structured expects Normalized phase"
);
let env_layout = norm
.env_layouts
.get(0)
.expect("normalized_pattern1_to_structured: missing env layout");
let mut module = JoinModule::new();
for (jp_id, jp_fn) in &norm.functions {
let params: Vec<ValueId> = jp_fn
.env_layout
.and_then(|id| norm.env_layouts.iter().find(|layout| layout.id == id))
.unwrap_or(env_layout)
.fields
.iter()
.enumerate()
.map(|(idx, f)| f.value_id.unwrap_or(ValueId(idx as u32)))
.collect();
let mut func =
JoinFunction::new(JoinFuncId(jp_id.0), jp_fn.name.clone(), params);
for inst in &jp_fn.body {
match inst {
JpInst::Let { dst, op, args } => match op {
JpOp::Const(v) => func
.body
.push(JoinInst::Compute(MirLikeInst::Const {
dst: *dst,
value: v.clone(),
})),
JpOp::BoxCall { box_name, method } => {
func.body.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: Some(*dst),
box_name: box_name.clone(),
method: method.clone(),
args: args.clone(),
}))
}
JpOp::BinOp(op) => func
.body
.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: *dst,
op: *op,
lhs: args.get(0).copied().unwrap_or(ValueId(0)),
rhs: args.get(1).copied().unwrap_or(ValueId(0)),
})),
JpOp::Unary(op) => func
.body
.push(JoinInst::Compute(MirLikeInst::UnaryOp {
dst: *dst,
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,
lhs: args.get(0).copied().unwrap_or(ValueId(0)),
rhs: args.get(1).copied().unwrap_or(ValueId(0)),
})),
},
JpInst::TailCallFn { target, env } => {
func.body.push(JoinInst::Call {
func: JoinFuncId(target.0),
args: env.clone(),
k_next: None,
dst: None,
})
}
JpInst::TailCallKont { target, env } => {
func.body.push(JoinInst::Jump {
cont: JoinContId(target.0),
args: env.clone(),
cond: None,
})
}
JpInst::If {
cond,
then_target,
else_target,
env,
} => {
// Jump to then_target on cond, else jump to else_target (Pattern1 minimal)
func.body.push(JoinInst::Jump {
cont: JoinContId(then_target.0),
args: env.clone(),
cond: Some(*cond),
});
func.body.push(JoinInst::Jump {
cont: JoinContId(else_target.0),
args: env.clone(),
cond: None,
});
}
JpInst::EnvLoad { .. } | JpInst::EnvStore { .. } => {
// Not used in Pattern1 minimal; ignore for now
}
}
}
module.add_function(func);
}
module.entry = norm.entry.map(|e| JoinFuncId(e.0));
module.phase = JoinIrPhase::Structured;
module
}
/// Verify Pattern 1 normalized module invariants
///
/// # Arguments
/// * `module` - Normalized module to verify
///
/// # Returns
/// Ok(()) if invariants hold, Err(String) otherwise
///
/// # Checks
/// - Phase must be Normalized
/// - Env field bounds checking
/// - Only valid instruction types
/// - Functions end with tail call or if
#[cfg(feature = "normalized_dev")]
pub fn verify(module: &NormalizedModule) -> Result<(), String> {
if module.phase != JoinIrPhase::Normalized {
return Err("[joinir/normalized-dev] pattern1: phase must be Normalized".to_string());
}
// Env field bounds check
if let Some(env) = module.env_layouts.get(0) {
let field_count = env.fields.len();
for func in module.functions.values() {
for inst in &func.body {
match inst {
JpInst::EnvLoad { field, .. } | JpInst::EnvStore { field, .. } => {
if *field >= field_count {
return Err(format!(
"[joinir/normalized-dev] pattern1: env field out of range: {} (fields={})",
field, field_count
));
}
}
_ => {}
}
}
}
}
for func in module.functions.values() {
for inst in &func.body {
match inst {
JpInst::Let { .. }
| JpInst::EnvLoad { .. }
| JpInst::EnvStore { .. }
| JpInst::TailCallFn { .. }
| JpInst::TailCallKont { .. }
| JpInst::If { .. } => {}
}
}
// Tail: allow TailCall or If only
if let Some(last) = func.body.last() {
match last {
JpInst::TailCallFn { .. } | JpInst::TailCallKont { .. } | JpInst::If { .. } => {}
_ => {
return Err(format!(
"[joinir/normalized-dev] pattern1: function '{}' does not end with tail call/if",
func.name
))
}
}
}
}
Ok(())
}
}
// Re-export the original functions for backward compatibility
#[allow(deprecated)]
pub use self::Pattern1Normalizer as Pattern1NormalizerBox;
/// Legacy function wrapper for backward compatibility
#[deprecated(note = "Use Pattern1Normalizer::normalize instead")]
pub fn normalize_pattern1_minimal(structured: &JoinModule) -> NormalizedModule {
Pattern1Normalizer::normalize(structured)
}
/// Legacy function wrapper for backward compatibility
#[deprecated(note = "Use Pattern1Normalizer::to_structured instead")]
pub fn normalized_pattern1_to_structured(norm: &NormalizedModule) -> JoinModule {
Pattern1Normalizer::to_structured(norm)
}
/// Legacy function wrapper for backward compatibility
#[deprecated(note = "Use Pattern1Normalizer::verify instead")]
#[cfg(feature = "normalized_dev")]
pub fn verify_normalized_pattern1(module: &NormalizedModule) -> Result<(), String> {
Pattern1Normalizer::verify(module)
}

View File

@ -0,0 +1,591 @@
//! Pattern 2 Normalization - Loops with Carriers
//!
//! Normalizes Pattern 2 JoinIR to Normalized form.
//! Pattern 2 extends Pattern 1 with carriers (accumulator variables) and break.
//!
//! ## Responsibilities
//! - Convert Structured JoinIR → Normalized JoinIR (Pattern 2/3/4)
//! - Convert Normalized JoinIR → Structured JoinIR (reverse)
//! - Verify normalized module invariants
use std::collections::{BTreeMap, HashMap};
use crate::mir::join_ir::{
BinOpKind, CompareOp, ConstValue, JoinContId, JoinFuncId, JoinFunction, JoinInst, JoinIrPhase,
JoinModule, MirLikeInst, UnaryOp,
};
use crate::mir::ValueId;
use super::{EnvField, EnvLayout, JpFuncId, JpFunction, JpInst, JpOp, NormalizedModule};
#[cfg(feature = "normalized_dev")]
use crate::mir::join_ir::normalized::shape_guard::NormalizedDevShape;
/// Pattern 2 Normalizer
///
/// Normalizes Pattern 2/3/4 (loops with carriers) to Normalized form.
///
/// Pattern 2 constraints:
/// - Loops with carriers (accumulator variables like sum, count)
/// - Break support (via k_exit)
/// - Main/loop_step/k_exit function structure
/// - Env layout for parameters
pub struct Pattern2Normalizer;
impl Pattern2Normalizer {
/// Normalize Pattern 2 Structured JoinIR to Normalized form
///
/// # Arguments
/// * `structured` - Structured JoinIR module (must be Pattern 2/3/4)
///
/// # Returns
/// Normalized module with backup
///
/// # Panics
/// - If structured.phase is not Structured
/// - If loop_step function is not found
/// - If function count is not 3
/// - If param count is out of range
/// - If no conditional jump or tail call found
pub fn normalize(structured: &JoinModule) -> NormalizedModule {
assert!(
structured.is_structured(),
"normalize_pattern2_minimal: expected Structured JoinIR"
);
// Minimal guardrail: Pattern2 mini should have main/loop_step/k_exit only, with 1 loop param.
let func_count = structured.functions.len();
let loop_func = structured
.functions
.values()
.find(|f| f.name == "loop_step")
.or_else(|| structured.functions.get(&JoinFuncId::new(1)))
.expect("normalize_pattern2_minimal: loop_step not found");
assert!(
func_count == 3,
"normalize_pattern2_minimal: expected 3 functions (entry/loop_step/k_exit) but got {}",
func_count
);
let param_max = {
#[allow(unused_mut)]
let mut max = 3;
#[cfg(feature = "normalized_dev")]
{
use crate::mir::join_ir::normalized::shape_guard;
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);
}
if shapes.iter().any(|s| {
matches!(
s,
NormalizedDevShape::Pattern4ContinueMinimal
| NormalizedDevShape::JsonparserParseArrayContinueSkipWs
| NormalizedDevShape::JsonparserParseObjectContinueSkipWs
)
}) {
max = max.max(6);
}
if shapes.iter().any(|s| {
matches!(
s,
NormalizedDevShape::Pattern3IfSumMinimal
| NormalizedDevShape::Pattern3IfSumMulti
| NormalizedDevShape::Pattern3IfSumJson
| NormalizedDevShape::SelfhostIfSumP3
| NormalizedDevShape::SelfhostIfSumP3Ext
| NormalizedDevShape::SelfhostStmtCountP3
| NormalizedDevShape::SelfhostDetectFormatP3
)
}) {
max = max.max(6);
}
}
max
};
assert!(
(1..=param_max).contains(&loop_func.params.len()),
"normalize_pattern2_minimal: expected 1..={} params (loop var + carriers + optional host)",
param_max
);
let jump_conds = loop_func
.body
.iter()
.filter(|inst| matches!(inst, JoinInst::Jump { cond: Some(_), .. }))
.count();
let tail_calls = loop_func
.body
.iter()
.filter(|inst| matches!(inst, JoinInst::Call { k_next: None, .. }))
.count();
assert!(
jump_conds >= 1 && tail_calls >= 1,
"normalize_pattern2_minimal: expected at least one conditional jump and one tail call"
);
let mut functions = BTreeMap::new();
let mut env_layouts = Vec::new();
for (fid, func) in &structured.functions {
let env_layout_id = if func.params.is_empty() {
None
} else {
let id = env_layouts.len() as u32;
env_layouts.push(EnvLayout {
id,
fields: func
.params
.iter()
.enumerate()
.map(|(idx, vid)| EnvField {
name: format!("field{}", idx),
ty: None,
value_id: Some(*vid),
})
.collect(),
});
Some(id)
};
let mut body = Vec::new();
for inst in &func.body {
match inst {
JoinInst::Compute(MirLikeInst::Const { dst, value }) => {
body.push(JpInst::Let {
dst: *dst,
op: JpOp::Const(value.clone()),
args: vec![],
})
}
JoinInst::Compute(MirLikeInst::BoxCall {
dst,
box_name,
method,
args,
}) => {
if let Some(dst) = dst {
body.push(JpInst::Let {
dst: *dst,
op: JpOp::BoxCall {
box_name: box_name.clone(),
method: method.clone(),
},
args: args.clone(),
})
}
}
JoinInst::Compute(MirLikeInst::BinOp { dst, op, lhs, rhs }) => {
body.push(JpInst::Let {
dst: *dst,
op: JpOp::BinOp(*op),
args: vec![*lhs, *rhs],
})
}
JoinInst::Compute(MirLikeInst::UnaryOp { dst, op, operand }) => {
body.push(JpInst::Let {
dst: *dst,
op: JpOp::Unary(*op),
args: vec![*operand],
})
}
JoinInst::Compute(MirLikeInst::Compare { dst, op, lhs, rhs }) => {
body.push(JpInst::Let {
dst: *dst,
op: JpOp::Compare(*op),
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 {
cond: *cond_val,
then_target: JpFuncId(cont.0),
else_target: JpFuncId(loop_func.id.0),
env: args.clone(),
});
} else {
body.push(JpInst::TailCallKont {
target: JpFuncId(cont.0),
env: args.clone(),
});
}
}
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 {
target: JpFuncId(func.0),
env: args.clone(),
});
}
}
JoinInst::MethodCall {
dst,
receiver,
method,
args,
..
} => {
let mut call_args = Vec::with_capacity(args.len() + 1);
call_args.push(*receiver);
call_args.extend(args.iter().copied());
body.push(JpInst::Let {
dst: *dst,
op: JpOp::BoxCall {
box_name: "unknown".to_string(),
method: method.clone(),
},
args: call_args,
});
}
_ => {
// Ret / other instructions are ignored in this minimal prototype
}
}
}
functions.insert(
JpFuncId(fid.0),
JpFunction {
id: JpFuncId(fid.0),
name: func.name.clone(),
env_layout: env_layout_id,
body,
is_kont: func.name.starts_with("k_"),
},
);
}
let norm = NormalizedModule {
functions,
entry: structured.entry.map(|e| JpFuncId(e.0)),
env_layouts,
phase: JoinIrPhase::Normalized,
structured_backup: Some(structured.clone()),
};
#[cfg(feature = "normalized_dev")]
{
Self::verify(&norm, param_max).expect("normalized Pattern2 verifier");
}
norm
}
/// Convert Normalized Pattern 2 back to Structured JoinIR
///
/// # Arguments
/// * `norm` - Normalized module (must be Pattern 2/3/4, Normalized phase)
///
/// # Returns
/// Structured JoinIR module
pub fn to_structured(norm: &NormalizedModule) -> JoinModule {
if let Some(backup) = norm.to_structured() {
return backup;
}
let mut module = JoinModule::new();
for (jp_id, jp_fn) in &norm.functions {
let params: Vec<ValueId> = jp_fn
.env_layout
.and_then(|id| norm.env_layouts.iter().find(|layout| layout.id == id))
.map(|layout| {
layout
.fields
.iter()
.enumerate()
.map(|(idx, f)| f.value_id.unwrap_or(ValueId(idx as u32)))
.collect()
})
.unwrap_or_default();
let mut func = JoinFunction::new(JoinFuncId(jp_id.0), jp_fn.name.clone(), params);
for inst in &jp_fn.body {
match inst {
JpInst::Let { dst, op, args } => match op {
JpOp::Const(v) => {
func.body.push(JoinInst::Compute(MirLikeInst::Const {
dst: *dst,
value: v.clone(),
}))
}
JpOp::BoxCall { box_name, method } => {
func.body.push(JoinInst::Compute(MirLikeInst::BoxCall {
dst: Some(*dst),
box_name: box_name.clone(),
method: method.clone(),
args: args.clone(),
}))
}
JpOp::BinOp(op) => {
func.body.push(JoinInst::Compute(MirLikeInst::BinOp {
dst: *dst,
op: *op,
lhs: args.get(0).copied().unwrap_or(ValueId(0)),
rhs: args.get(1).copied().unwrap_or(ValueId(0)),
}))
}
JpOp::Unary(op) => {
func.body.push(JoinInst::Compute(MirLikeInst::UnaryOp {
dst: *dst,
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,
lhs: args.get(0).copied().unwrap_or(ValueId(0)),
rhs: args.get(1).copied().unwrap_or(ValueId(0)),
}))
}
},
JpInst::TailCallFn { target, env } => {
func.body.push(JoinInst::Call {
func: JoinFuncId(target.0),
args: env.clone(),
k_next: None,
dst: None,
})
}
JpInst::TailCallKont { target, env } => {
func.body.push(JoinInst::Jump {
cont: JoinContId(target.0),
args: env.clone(),
cond: None,
})
}
JpInst::If {
cond,
then_target,
else_target,
env,
} => {
func.body.push(JoinInst::Jump {
cont: JoinContId(then_target.0),
args: env.clone(),
cond: Some(*cond),
});
func.body.push(JoinInst::Jump {
cont: JoinContId(else_target.0),
args: env.clone(),
cond: None,
});
}
JpInst::EnvLoad { .. } | JpInst::EnvStore { .. } => {
// Not used in minimal pattern2 bridge
}
}
}
module.add_function(func);
}
module.entry = norm.entry.map(|e| JoinFuncId(e.0));
module.phase = JoinIrPhase::Structured;
module
}
/// Verify Pattern 2 normalized module invariants
///
/// # Arguments
/// * `module` - Normalized module to verify
/// * `max_env_fields` - Maximum allowed env fields (shape-dependent)
///
/// # Returns
/// Ok(()) if invariants hold, Err(String) otherwise
///
/// # Checks
/// - Phase must be Normalized
/// - Env field count within range
/// - Env is not empty for tail calls
/// - Functions end with tail call or if
#[cfg(feature = "normalized_dev")]
pub fn verify(module: &NormalizedModule, max_env_fields: usize) -> Result<(), String> {
if module.phase != JoinIrPhase::Normalized {
return Err("[joinir/normalized-dev] pattern2: phase must be Normalized".to_string());
}
let mut layout_sizes: HashMap<u32, usize> = HashMap::new();
for layout in &module.env_layouts {
let size = layout.fields.len();
if !(1..=max_env_fields).contains(&size) {
return Err(format!(
"[joinir/normalized-dev] pattern2: expected 1..={} env fields, got {}",
max_env_fields, size
));
}
layout_sizes.insert(layout.id, size);
}
for func in module.functions.values() {
let expected_env_len = func
.env_layout
.and_then(|id| layout_sizes.get(&id))
.copied();
for inst in &func.body {
match inst {
JpInst::Let { .. }
| JpInst::EnvLoad { .. }
| JpInst::EnvStore { .. }
| JpInst::TailCallFn { .. }
| JpInst::TailCallKont { .. }
| JpInst::If { .. } => {}
}
match inst {
JpInst::TailCallFn { env, .. }
| JpInst::TailCallKont { env, .. }
| JpInst::If { env, .. } => {
if let Some(expected) = expected_env_len {
if env.is_empty() {
return Err(
"[joinir/normalized-dev] pattern2: env must not be empty"
.to_string(),
);
}
let _ = expected;
}
}
_ => {}
}
}
if let Some(last) = func.body.last() {
match last {
JpInst::TailCallFn { .. } | JpInst::TailCallKont { .. } | JpInst::If { .. } => {}
_ => {
return Err(format!(
"[joinir/normalized-dev] pattern2: function '{}' does not end with tail call/if",
func.name
));
}
}
}
}
Ok(())
}
}
// Re-export the original functions for backward compatibility
#[allow(deprecated)]
pub use self::Pattern2Normalizer as Pattern2NormalizerBox;
/// Legacy function wrapper for backward compatibility
#[deprecated(note = "Use Pattern2Normalizer::normalize instead")]
pub fn normalize_pattern2_minimal(structured: &JoinModule) -> NormalizedModule {
Pattern2Normalizer::normalize(structured)
}
/// Legacy function wrapper for backward compatibility
#[deprecated(note = "Use Pattern2Normalizer::to_structured instead")]
pub fn normalized_pattern2_to_structured(norm: &NormalizedModule) -> JoinModule {
Pattern2Normalizer::to_structured(norm)
}
/// Legacy function wrapper for backward compatibility
#[deprecated(note = "Use Pattern2Normalizer::verify instead")]
#[cfg(feature = "normalized_dev")]
pub fn verify_normalized_pattern2(
module: &NormalizedModule,
max_env_fields: usize,
) -> Result<(), String> {
Pattern2Normalizer::verify(module, max_env_fields)
}
/// Shape-based normalization for Pattern 2/3/4
///
/// These functions provide shape-specific entry points that delegate to
/// the core Pattern2Normalizer with proper guards.
#[cfg(feature = "normalized_dev")]
pub mod shapes {
use super::*;
/// Normalize with shape guard
pub fn normalize_with_shape(
structured: &JoinModule,
target_shape: NormalizedDevShape,
) -> Result<NormalizedModule, String> {
if !structured.is_structured() {
return Err("[normalize_p2] Not structured JoinIR".to_string());
}
let shapes = crate::mir::join_ir::normalized::shape_guard::supported_shapes(structured);
if !shapes.contains(&target_shape) {
return Err(format!(
"[normalize_p2] shape mismatch: expected {:?}, got {:?}",
target_shape, shapes
));
}
Ok(Pattern2Normalizer::normalize(structured))
}
}
// Public shape-specific normalization functions (re-exported)
#[cfg(feature = "normalized_dev")]
pub use shapes::normalize_with_shape;

View File

@ -11,7 +11,7 @@
use crate::config::env::joinir_dev_enabled;
use crate::mir::join_ir::normalized::dev_env;
use crate::mir::join_ir::JoinModule;
use crate::mir::join_ir::{JoinFunction, JoinInst, JoinModule};
mod pattern2;
mod pattern3;