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:
@ -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 のシンプル while(break/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).
|
||||
|
||||
149
src/mir/join_ir/normalized/env_layout_builder.rs
Normal file
149
src/mir/join_ir/normalized/env_layout_builder.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
424
src/mir/join_ir/normalized/pattern1_normalizer.rs
Normal file
424
src/mir/join_ir/normalized/pattern1_normalizer.rs
Normal 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)
|
||||
}
|
||||
591
src/mir/join_ir/normalized/pattern2_normalizer.rs
Normal file
591
src/mir/join_ir/normalized/pattern2_normalizer.rs
Normal 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;
|
||||
@ -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;
|
||||
|
||||
Reference in New Issue
Block a user