2025-12-11 20:54:33 +09:00
|
|
|
|
//! Minimal Normalized JoinIR model (Phase 26-H.B).
|
|
|
|
|
|
//!
|
|
|
|
|
|
//! テスト専用の極小サブセット。Pattern1 の while だけを Structured → Normalized に
|
|
|
|
|
|
//! 変換して遊ぶための足場だよ。本線の Structured→MIR 経路には影響しない。
|
|
|
|
|
|
|
2025-12-12 16:40:20 +09:00
|
|
|
|
use std::collections::{BTreeMap, HashSet};
|
2025-12-11 20:54:33 +09:00
|
|
|
|
|
|
|
|
|
|
use crate::mir::join_ir::{
|
|
|
|
|
|
BinOpKind, CompareOp, ConstValue, JoinContId, JoinFuncId, JoinFunction, JoinInst, JoinIrPhase,
|
|
|
|
|
|
JoinModule, MirLikeInst, UnaryOp,
|
|
|
|
|
|
};
|
|
|
|
|
|
use crate::mir::ValueId;
|
2025-12-12 16:40:20 +09:00
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
|
use std::collections::HashMap;
|
2025-12-11 20:54:33 +09:00
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
|
use std::panic::{catch_unwind, AssertUnwindSafe};
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
|
pub mod fixtures;
|
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
2025-12-11 22:50:23 +09:00
|
|
|
|
pub mod dev_env;
|
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
2025-12-12 04:40:46 +09:00
|
|
|
|
pub mod shape_guard;
|
2025-12-11 20:54:33 +09:00
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
|
use crate::mir::join_ir::normalized::shape_guard::NormalizedDevShape;
|
|
|
|
|
|
|
|
|
|
|
|
/// 環境レイアウト(最小)。
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
|
pub struct EnvLayout {
|
|
|
|
|
|
pub id: u32,
|
|
|
|
|
|
pub fields: Vec<EnvField>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
|
pub struct EnvField {
|
|
|
|
|
|
pub name: String,
|
|
|
|
|
|
pub ty: Option<crate::mir::MirType>,
|
|
|
|
|
|
pub value_id: Option<ValueId>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 正規化済み関数 ID
|
|
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
|
|
|
|
pub struct JpFuncId(pub u32);
|
|
|
|
|
|
|
|
|
|
|
|
/// 正規化済み関数(Kont 兼用、is_kont で区別)。
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
|
pub struct JpFunction {
|
|
|
|
|
|
pub id: JpFuncId,
|
|
|
|
|
|
pub name: String,
|
|
|
|
|
|
pub env_layout: Option<u32>,
|
|
|
|
|
|
pub body: Vec<JpInst>,
|
|
|
|
|
|
pub is_kont: bool,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 正規化済み命令(最小セット)。
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
|
pub enum JpInst {
|
|
|
|
|
|
Let {
|
|
|
|
|
|
dst: ValueId,
|
|
|
|
|
|
op: JpOp,
|
|
|
|
|
|
args: Vec<ValueId>,
|
|
|
|
|
|
},
|
|
|
|
|
|
EnvLoad {
|
|
|
|
|
|
dst: ValueId,
|
|
|
|
|
|
env: ValueId,
|
|
|
|
|
|
field: usize,
|
|
|
|
|
|
},
|
|
|
|
|
|
EnvStore {
|
|
|
|
|
|
env: ValueId,
|
|
|
|
|
|
field: usize,
|
|
|
|
|
|
src: ValueId,
|
|
|
|
|
|
},
|
|
|
|
|
|
TailCallFn {
|
|
|
|
|
|
target: JpFuncId,
|
|
|
|
|
|
env: Vec<ValueId>,
|
|
|
|
|
|
},
|
|
|
|
|
|
TailCallKont {
|
|
|
|
|
|
target: JpFuncId,
|
|
|
|
|
|
env: Vec<ValueId>,
|
|
|
|
|
|
},
|
|
|
|
|
|
If {
|
|
|
|
|
|
cond: ValueId,
|
|
|
|
|
|
then_target: JpFuncId,
|
|
|
|
|
|
else_target: JpFuncId,
|
|
|
|
|
|
env: Vec<ValueId>,
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 演算(Let 用の最小サブセット)。
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
|
pub enum JpOp {
|
|
|
|
|
|
Const(ConstValue),
|
|
|
|
|
|
BinOp(BinOpKind),
|
|
|
|
|
|
Unary(UnaryOp),
|
|
|
|
|
|
Compare(CompareOp),
|
2025-12-11 22:12:46 +09:00
|
|
|
|
BoxCall { box_name: String, method: String },
|
2025-12-12 03:15:45 +09:00
|
|
|
|
/// 三項演算子(cond ? then : else)
|
|
|
|
|
|
Select,
|
2025-12-11 20:54:33 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Normalized JoinIR モジュール(テスト専用)。
|
|
|
|
|
|
#[derive(Debug, Clone)]
|
|
|
|
|
|
pub struct NormalizedModule {
|
|
|
|
|
|
pub functions: BTreeMap<JpFuncId, JpFunction>,
|
|
|
|
|
|
pub entry: Option<JpFuncId>,
|
|
|
|
|
|
pub env_layouts: Vec<EnvLayout>,
|
|
|
|
|
|
pub phase: JoinIrPhase,
|
|
|
|
|
|
/// Structured に戻すためのスナップショット(テスト専用)。
|
|
|
|
|
|
pub structured_backup: Option<JoinModule>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl NormalizedModule {
|
|
|
|
|
|
pub fn to_structured(&self) -> Option<JoinModule> {
|
|
|
|
|
|
self.structured_backup.clone()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
|
fn verify_normalized_pattern1(module: &NormalizedModule) -> Result<(), String> {
|
|
|
|
|
|
if module.phase != JoinIrPhase::Normalized {
|
2025-12-12 03:15:45 +09:00
|
|
|
|
return Err("[joinir/normalized-dev] pattern1: phase must be Normalized".to_string());
|
2025-12-11 20:54:33 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 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!(
|
2025-12-12 03:15:45 +09:00
|
|
|
|
"[joinir/normalized-dev] pattern1: env field out of range: {} (fields={})",
|
2025-12-11 20:54:33 +09:00
|
|
|
|
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!(
|
2025-12-12 03:15:45 +09:00
|
|
|
|
"[joinir/normalized-dev] pattern1: function '{}' does not end with tail call/if",
|
2025-12-11 20:54:33 +09:00
|
|
|
|
func.name
|
|
|
|
|
|
))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 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 {
|
2025-12-11 22:12:46 +09:00
|
|
|
|
let params: Vec<ValueId> = jp_fn
|
|
|
|
|
|
.env_layout
|
|
|
|
|
|
.and_then(|id| norm.env_layouts.iter().find(|layout| layout.id == id))
|
|
|
|
|
|
.unwrap_or(env_layout)
|
2025-12-11 20:54:33 +09:00
|
|
|
|
.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(),
|
|
|
|
|
|
})),
|
2025-12-11 22:12:46 +09:00
|
|
|
|
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(),
|
|
|
|
|
|
}))
|
|
|
|
|
|
}
|
2025-12-11 20:54:33 +09:00
|
|
|
|
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)),
|
|
|
|
|
|
})),
|
2025-12-12 03:15:45 +09:00
|
|
|
|
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)),
|
|
|
|
|
|
})),
|
2025-12-11 20:54:33 +09:00
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 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
|
|
|
|
|
|
);
|
2025-12-11 22:12:46 +09:00
|
|
|
|
let param_max = {
|
2025-12-12 16:40:20 +09:00
|
|
|
|
#[allow(unused_mut)]
|
2025-12-11 22:12:46 +09:00
|
|
|
|
let mut max = 3;
|
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
|
{
|
2025-12-12 03:15:45 +09:00
|
|
|
|
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);
|
2025-12-11 22:12:46 +09:00
|
|
|
|
}
|
2025-12-12 16:40:20 +09:00
|
|
|
|
if shapes.iter().any(|s| {
|
|
|
|
|
|
matches!(
|
|
|
|
|
|
s,
|
|
|
|
|
|
NormalizedDevShape::Pattern4ContinueMinimal
|
|
|
|
|
|
| NormalizedDevShape::JsonparserParseArrayContinueSkipWs
|
|
|
|
|
|
| NormalizedDevShape::JsonparserParseObjectContinueSkipWs
|
|
|
|
|
|
)
|
|
|
|
|
|
}) {
|
|
|
|
|
|
max = max.max(6);
|
|
|
|
|
|
}
|
2025-12-12 07:13:34 +09:00
|
|
|
|
if shapes.iter().any(|s| {
|
|
|
|
|
|
matches!(
|
|
|
|
|
|
s,
|
|
|
|
|
|
NormalizedDevShape::Pattern3IfSumMinimal
|
|
|
|
|
|
| NormalizedDevShape::Pattern3IfSumMulti
|
|
|
|
|
|
| NormalizedDevShape::Pattern3IfSumJson
|
2025-12-12 22:15:41 +09:00
|
|
|
|
| NormalizedDevShape::SelfhostIfSumP3
|
|
|
|
|
|
| NormalizedDevShape::SelfhostIfSumP3Ext
|
|
|
|
|
|
| NormalizedDevShape::SelfhostStmtCountP3
|
|
|
|
|
|
| NormalizedDevShape::SelfhostDetectFormatP3
|
2025-12-12 07:13:34 +09:00
|
|
|
|
)
|
|
|
|
|
|
}) {
|
|
|
|
|
|
max = max.max(6);
|
|
|
|
|
|
}
|
2025-12-11 22:12:46 +09:00
|
|
|
|
}
|
|
|
|
|
|
max
|
|
|
|
|
|
};
|
2025-12-11 20:54:33 +09:00
|
|
|
|
assert!(
|
2025-12-11 22:12:46 +09:00
|
|
|
|
(1..=param_max).contains(&loop_func.params.len()),
|
|
|
|
|
|
"normalize_pattern2_minimal: expected 1..={} params (loop var + carriers + optional host)",
|
|
|
|
|
|
param_max
|
2025-12-11 20:54:33 +09:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
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();
|
2025-12-11 22:12:46 +09:00
|
|
|
|
let mut env_layouts = Vec::new();
|
2025-12-11 20:54:33 +09:00
|
|
|
|
|
|
|
|
|
|
for (fid, func) in &structured.functions {
|
|
|
|
|
|
let env_layout_id = if func.params.is_empty() {
|
|
|
|
|
|
None
|
|
|
|
|
|
} else {
|
2025-12-11 22:12:46 +09:00
|
|
|
|
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)
|
2025-12-11 20:54:33 +09:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
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![],
|
|
|
|
|
|
}),
|
2025-12-11 22:12:46 +09:00
|
|
|
|
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(),
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-11 20:54:33 +09:00
|
|
|
|
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],
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2025-12-12 03:15:45 +09:00
|
|
|
|
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],
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
2025-12-11 20:54:33 +09:00
|
|
|
|
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(),
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-12 03:15:45 +09:00
|
|
|
|
JoinInst::Select {
|
|
|
|
|
|
dst,
|
|
|
|
|
|
cond,
|
|
|
|
|
|
then_val,
|
|
|
|
|
|
else_val,
|
|
|
|
|
|
..
|
|
|
|
|
|
} => {
|
|
|
|
|
|
body.push(JpInst::Let {
|
|
|
|
|
|
dst: *dst,
|
|
|
|
|
|
op: JpOp::Select,
|
|
|
|
|
|
args: vec![*cond, *then_val, *else_val],
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-12-11 20:54:33 +09:00
|
|
|
|
JoinInst::Call { func, args, k_next, .. } => {
|
|
|
|
|
|
if k_next.is_none() {
|
|
|
|
|
|
body.push(JpInst::TailCallFn {
|
|
|
|
|
|
target: JpFuncId(func.0),
|
|
|
|
|
|
env: args.clone(),
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-11 22:12:46 +09:00
|
|
|
|
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,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-12-11 20:54:33 +09:00
|
|
|
|
_ => {
|
|
|
|
|
|
// 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)),
|
2025-12-11 22:12:46 +09:00
|
|
|
|
env_layouts,
|
2025-12-11 20:54:33 +09:00
|
|
|
|
phase: JoinIrPhase::Normalized,
|
|
|
|
|
|
structured_backup: Some(structured.clone()),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
|
{
|
2025-12-11 22:12:46 +09:00
|
|
|
|
verify_normalized_pattern2(&norm, param_max).expect("normalized Pattern2 verifier");
|
2025-12-11 20:54:33 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
norm
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 16:40:20 +09:00
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
|
fn normalize_pattern2_shape(
|
|
|
|
|
|
structured: &JoinModule,
|
|
|
|
|
|
target_shape: NormalizedDevShape,
|
|
|
|
|
|
) -> Result<NormalizedModule, String> {
|
|
|
|
|
|
if !structured.is_structured() {
|
|
|
|
|
|
return Err("[normalize_p2] Not structured JoinIR".to_string());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let shapes = shape_guard::supported_shapes(structured);
|
|
|
|
|
|
if !shapes.contains(&target_shape) {
|
|
|
|
|
|
return Err(format!(
|
|
|
|
|
|
"[normalize_p2] shape mismatch: expected {:?}, got {:?}",
|
|
|
|
|
|
target_shape, shapes
|
|
|
|
|
|
));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Ok(normalize_pattern2_minimal(structured))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Phase 50: selfhost token-scan P2 を Normalized に載せる(dev-only)。
|
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
|
pub fn normalize_selfhost_token_scan_p2(
|
|
|
|
|
|
structured: &JoinModule,
|
|
|
|
|
|
) -> Result<NormalizedModule, String> {
|
|
|
|
|
|
normalize_pattern2_shape(structured, NormalizedDevShape::SelfhostTokenScanP2)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Phase 51: selfhost token-scan P2(accum 拡張)を Normalized に載せる(dev-only)。
|
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
|
pub fn normalize_selfhost_token_scan_p2_accum(
|
|
|
|
|
|
structured: &JoinModule,
|
|
|
|
|
|
) -> Result<NormalizedModule, String> {
|
|
|
|
|
|
normalize_pattern2_shape(structured, NormalizedDevShape::SelfhostTokenScanP2Accum)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 07:13:34 +09:00
|
|
|
|
/// Phase 47-A/B: Normalize Pattern3 if-sum shapes to Normalized JoinIR
|
2025-12-12 05:53:23 +09:00
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
|
pub fn normalize_pattern3_if_sum_minimal(
|
|
|
|
|
|
structured: &JoinModule,
|
|
|
|
|
|
) -> Result<NormalizedModule, String> {
|
2025-12-12 07:13:34 +09:00
|
|
|
|
normalize_pattern3_if_sum_shape(structured, NormalizedDevShape::Pattern3IfSumMinimal)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Phase 47-B: Normalize Pattern3 if-sum multi-carrier (sum+count) shape.
|
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
|
pub fn normalize_pattern3_if_sum_multi_minimal(
|
|
|
|
|
|
structured: &JoinModule,
|
|
|
|
|
|
) -> Result<NormalizedModule, String> {
|
|
|
|
|
|
normalize_pattern3_if_sum_shape(structured, NormalizedDevShape::Pattern3IfSumMulti)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Phase 47-B: Normalize JsonParser if-sum (mini) shape.
|
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
|
pub fn normalize_pattern3_if_sum_json_minimal(
|
|
|
|
|
|
structured: &JoinModule,
|
|
|
|
|
|
) -> Result<NormalizedModule, String> {
|
|
|
|
|
|
normalize_pattern3_if_sum_shape(structured, NormalizedDevShape::Pattern3IfSumJson)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 16:40:20 +09:00
|
|
|
|
/// Phase 50: selfhost if-sum P3 を Normalized に載せる(dev-only)。
|
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
|
pub fn normalize_selfhost_if_sum_p3(
|
|
|
|
|
|
structured: &JoinModule,
|
|
|
|
|
|
) -> Result<NormalizedModule, String> {
|
|
|
|
|
|
normalize_pattern3_if_sum_shape(structured, NormalizedDevShape::SelfhostIfSumP3)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Phase 51: selfhost if-sum P3(ext 拡張)を Normalized に載せる(dev-only)。
|
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
|
pub fn normalize_selfhost_if_sum_p3_ext(
|
|
|
|
|
|
structured: &JoinModule,
|
|
|
|
|
|
) -> Result<NormalizedModule, String> {
|
|
|
|
|
|
normalize_pattern3_if_sum_shape(structured, NormalizedDevShape::SelfhostIfSumP3Ext)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 07:13:34 +09:00
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
|
fn normalize_pattern3_if_sum_shape(
|
|
|
|
|
|
structured: &JoinModule,
|
|
|
|
|
|
target_shape: NormalizedDevShape,
|
|
|
|
|
|
) -> Result<NormalizedModule, String> {
|
2025-12-12 05:53:23 +09:00
|
|
|
|
if !structured.is_structured() {
|
|
|
|
|
|
return Err("[normalize_p3] Not structured JoinIR".to_string());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 07:13:34 +09:00
|
|
|
|
let shapes = shape_guard::supported_shapes(structured);
|
|
|
|
|
|
if !shapes.contains(&target_shape) {
|
|
|
|
|
|
return Err(format!(
|
|
|
|
|
|
"[normalize_p3] shape mismatch: expected {:?}, got {:?}",
|
|
|
|
|
|
target_shape, shapes
|
|
|
|
|
|
));
|
2025-12-12 05:53:23 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 07:13:34 +09:00
|
|
|
|
// Phase 47-B: P3 if-sum は既存の P2 ミニ正規化器で十分に表現できる
|
|
|
|
|
|
// (Select/If/Compare/BinOp をそのまま JpInst に写す)。
|
2025-12-12 05:53:23 +09:00
|
|
|
|
Ok(normalize_pattern2_minimal(structured))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 06:31:13 +09:00
|
|
|
|
/// Phase 48-A: Pattern4 (continue) minimal ループの正規化。
|
|
|
|
|
|
///
|
|
|
|
|
|
/// ガード:
|
|
|
|
|
|
/// - structured.phase は Structured であること
|
|
|
|
|
|
/// - 対象は Pattern4ContinueMinimal のシンプル continue パターン
|
|
|
|
|
|
///
|
|
|
|
|
|
/// 実装方針:
|
|
|
|
|
|
/// - Phase 48-A minimal: P2 正規化に委譲(P4 は P2 の逆制御フローなので同じインフラ利用可)
|
|
|
|
|
|
/// - TODO (Phase 48-B): P4 固有の正規化実装
|
|
|
|
|
|
/// - EnvLayout for i, count carriers
|
|
|
|
|
|
/// - HeaderCond → ContinueCheck → Updates → Tail step sequence
|
|
|
|
|
|
/// - continue = early TailCallFn (skip Updates)
|
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
|
pub fn normalize_pattern4_continue_minimal(
|
|
|
|
|
|
structured: &JoinModule,
|
|
|
|
|
|
) -> Result<NormalizedModule, String> {
|
2025-12-12 16:40:20 +09:00
|
|
|
|
normalize_pattern4_continue_shape(structured, NormalizedDevShape::Pattern4ContinueMinimal)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Phase 48-B: JsonParser _parse_array continue skip_ws を Normalized に載せる(dev-only)。
|
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
|
pub fn normalize_jsonparser_parse_array_continue_skip_ws(
|
|
|
|
|
|
structured: &JoinModule,
|
|
|
|
|
|
) -> Result<NormalizedModule, String> {
|
|
|
|
|
|
normalize_pattern4_continue_shape(
|
|
|
|
|
|
structured,
|
|
|
|
|
|
NormalizedDevShape::JsonparserParseArrayContinueSkipWs,
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Phase 48-B: JsonParser _parse_object continue skip_ws を Normalized に載せる(dev-only)。
|
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
|
pub fn normalize_jsonparser_parse_object_continue_skip_ws(
|
|
|
|
|
|
structured: &JoinModule,
|
|
|
|
|
|
) -> Result<NormalizedModule, String> {
|
|
|
|
|
|
normalize_pattern4_continue_shape(
|
|
|
|
|
|
structured,
|
|
|
|
|
|
NormalizedDevShape::JsonparserParseObjectContinueSkipWs,
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
|
fn normalize_pattern4_continue_shape(
|
|
|
|
|
|
structured: &JoinModule,
|
|
|
|
|
|
target_shape: NormalizedDevShape,
|
|
|
|
|
|
) -> Result<NormalizedModule, String> {
|
2025-12-12 06:31:13 +09:00
|
|
|
|
if !structured.is_structured() {
|
|
|
|
|
|
return Err("[normalize_p4] Not structured JoinIR".to_string());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Use shape detection to verify P4 shape
|
2025-12-12 16:40:20 +09:00
|
|
|
|
let shapes = shape_guard::supported_shapes(structured);
|
|
|
|
|
|
if !shapes.contains(&target_shape) {
|
|
|
|
|
|
return Err(format!(
|
|
|
|
|
|
"[normalize_p4] shape mismatch: expected {:?}, got {:?}",
|
|
|
|
|
|
target_shape, shapes
|
|
|
|
|
|
));
|
2025-12-12 06:31:13 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 16:40:20 +09:00
|
|
|
|
// Phase 48-B: reuse Pattern2 minimal normalizer (continue is early tail-call).
|
2025-12-12 06:31:13 +09:00
|
|
|
|
Ok(normalize_pattern2_minimal(structured))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-11 20:54:33 +09:00
|
|
|
|
/// 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
|
2025-12-11 22:12:46 +09:00
|
|
|
|
.and_then(|id| norm.env_layouts.iter().find(|layout| layout.id == id))
|
2025-12-11 20:54:33 +09:00
|
|
|
|
.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(),
|
|
|
|
|
|
})),
|
2025-12-11 22:12:46 +09:00
|
|
|
|
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(),
|
|
|
|
|
|
}))
|
|
|
|
|
|
}
|
2025-12-11 20:54:33 +09:00
|
|
|
|
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)),
|
|
|
|
|
|
})),
|
2025-12-12 03:15:45 +09:00
|
|
|
|
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)),
|
|
|
|
|
|
})),
|
2025-12-11 20:54:33 +09:00
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
2025-12-11 22:12:46 +09:00
|
|
|
|
fn verify_normalized_pattern2(
|
|
|
|
|
|
module: &NormalizedModule,
|
|
|
|
|
|
max_env_fields: usize,
|
|
|
|
|
|
) -> Result<(), String> {
|
2025-12-11 20:54:33 +09:00
|
|
|
|
if module.phase != JoinIrPhase::Normalized {
|
2025-12-12 03:15:45 +09:00
|
|
|
|
return Err(
|
|
|
|
|
|
"[joinir/normalized-dev] pattern2: phase must be Normalized".to_string(),
|
|
|
|
|
|
);
|
2025-12-11 20:54:33 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-11 22:12:46 +09:00
|
|
|
|
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) {
|
2025-12-11 20:54:33 +09:00
|
|
|
|
return Err(format!(
|
2025-12-12 03:15:45 +09:00
|
|
|
|
"[joinir/normalized-dev] pattern2: expected 1..={} env fields, got {}",
|
2025-12-11 22:12:46 +09:00
|
|
|
|
max_env_fields, size
|
2025-12-11 20:54:33 +09:00
|
|
|
|
));
|
|
|
|
|
|
}
|
2025-12-11 22:12:46 +09:00
|
|
|
|
layout_sizes.insert(layout.id, size);
|
2025-12-11 20:54:33 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for func in module.functions.values() {
|
2025-12-11 22:12:46 +09:00
|
|
|
|
let expected_env_len = func
|
|
|
|
|
|
.env_layout
|
|
|
|
|
|
.and_then(|id| layout_sizes.get(&id))
|
|
|
|
|
|
.copied();
|
|
|
|
|
|
|
2025-12-11 20:54:33 +09:00
|
|
|
|
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, .. } => {
|
2025-12-11 22:12:46 +09:00
|
|
|
|
if let Some(expected) = expected_env_len {
|
|
|
|
|
|
if env.is_empty() {
|
2025-12-12 03:15:45 +09:00
|
|
|
|
return Err(
|
|
|
|
|
|
"[joinir/normalized-dev] pattern2: env must not be empty"
|
|
|
|
|
|
.to_string(),
|
|
|
|
|
|
);
|
2025-12-11 20:54:33 +09:00
|
|
|
|
}
|
2025-12-11 22:12:46 +09:00
|
|
|
|
let _ = expected;
|
2025-12-11 20:54:33 +09:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
_ => {}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if let Some(last) = func.body.last() {
|
|
|
|
|
|
match last {
|
|
|
|
|
|
JpInst::TailCallFn { .. }
|
|
|
|
|
|
| JpInst::TailCallKont { .. }
|
|
|
|
|
|
| JpInst::If { .. } => {}
|
|
|
|
|
|
_ => {
|
|
|
|
|
|
return Err(format!(
|
2025-12-12 03:15:45 +09:00
|
|
|
|
"[joinir/normalized-dev] pattern2: function '{}' does not end with tail call/if",
|
2025-12-11 20:54:33 +09:00
|
|
|
|
func.name
|
|
|
|
|
|
));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// 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![],
|
|
|
|
|
|
}),
|
2025-12-11 22:12:46 +09:00
|
|
|
|
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(),
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-12-11 20:54:33 +09:00
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Dev helper: Structured → Normalized → Structured roundtrip (Pattern1/2 minis only).
|
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
|
pub(crate) fn normalized_dev_roundtrip_structured(
|
|
|
|
|
|
module: &JoinModule,
|
|
|
|
|
|
) -> Result<JoinModule, String> {
|
|
|
|
|
|
if !module.is_structured() {
|
|
|
|
|
|
return Err("[joinir/normalized-dev] expected Structured JoinModule".to_string());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let shapes = shape_guard::supported_shapes(module);
|
|
|
|
|
|
if shapes.is_empty() {
|
|
|
|
|
|
return Err("[joinir/normalized-dev] module shape is not supported by normalized_dev".into());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-12 03:15:45 +09:00
|
|
|
|
let debug = dev_env::normalized_dev_logs_enabled() && crate::config::env::joinir_dev_enabled();
|
2025-12-11 20:54:33 +09:00
|
|
|
|
|
|
|
|
|
|
for shape in shapes {
|
2025-12-12 03:15:45 +09:00
|
|
|
|
if debug {
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"[joinir/normalized-dev/roundtrip] attempting {:?} normalization",
|
|
|
|
|
|
shape
|
|
|
|
|
|
);
|
2025-12-11 20:54:33 +09:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let attempt = match shape {
|
|
|
|
|
|
NormalizedDevShape::Pattern1Mini => catch_unwind(AssertUnwindSafe(|| {
|
|
|
|
|
|
let norm = normalize_pattern1_minimal(module);
|
|
|
|
|
|
normalized_pattern1_to_structured(&norm)
|
|
|
|
|
|
})),
|
2025-12-11 22:12:46 +09:00
|
|
|
|
NormalizedDevShape::Pattern2Mini
|
|
|
|
|
|
| NormalizedDevShape::JsonparserSkipWsMini
|
2025-12-12 03:15:45 +09:00
|
|
|
|
| NormalizedDevShape::JsonparserSkipWsReal
|
|
|
|
|
|
| NormalizedDevShape::JsonparserAtoiMini
|
|
|
|
|
|
| NormalizedDevShape::JsonparserAtoiReal
|
2025-12-12 16:40:20 +09:00
|
|
|
|
| NormalizedDevShape::JsonparserParseNumberReal => catch_unwind(AssertUnwindSafe(
|
|
|
|
|
|
|| {
|
|
|
|
|
|
let norm = normalize_pattern2_minimal(module);
|
|
|
|
|
|
normalized_pattern2_to_structured(&norm)
|
|
|
|
|
|
},
|
|
|
|
|
|
)),
|
|
|
|
|
|
NormalizedDevShape::SelfhostTokenScanP2 => catch_unwind(AssertUnwindSafe(|| {
|
|
|
|
|
|
let norm =
|
|
|
|
|
|
normalize_selfhost_token_scan_p2(module).expect("selfhost P2 normalization failed");
|
|
|
|
|
|
normalized_pattern2_to_structured(&norm)
|
|
|
|
|
|
})),
|
|
|
|
|
|
NormalizedDevShape::SelfhostTokenScanP2Accum => catch_unwind(AssertUnwindSafe(|| {
|
|
|
|
|
|
let norm = normalize_selfhost_token_scan_p2_accum(module)
|
|
|
|
|
|
.expect("selfhost P2 accum normalization failed");
|
2025-12-11 22:12:46 +09:00
|
|
|
|
normalized_pattern2_to_structured(&norm)
|
2025-12-12 05:23:18 +09:00
|
|
|
|
})),
|
2025-12-12 05:53:23 +09:00
|
|
|
|
// Phase 47-A: P3 minimal (delegates to P2 for now, but uses proper guard)
|
2025-12-12 05:23:18 +09:00
|
|
|
|
NormalizedDevShape::Pattern3IfSumMinimal => catch_unwind(AssertUnwindSafe(|| {
|
2025-12-12 05:53:23 +09:00
|
|
|
|
let norm = normalize_pattern3_if_sum_minimal(module)
|
|
|
|
|
|
.expect("P3 normalization failed");
|
2025-12-12 05:23:18 +09:00
|
|
|
|
normalized_pattern2_to_structured(&norm)
|
2025-12-12 06:31:13 +09:00
|
|
|
|
})),
|
2025-12-12 07:13:34 +09:00
|
|
|
|
NormalizedDevShape::Pattern3IfSumMulti => catch_unwind(AssertUnwindSafe(|| {
|
|
|
|
|
|
let norm = normalize_pattern3_if_sum_multi_minimal(module)
|
|
|
|
|
|
.expect("P3 multi normalization failed");
|
|
|
|
|
|
normalized_pattern2_to_structured(&norm)
|
|
|
|
|
|
})),
|
|
|
|
|
|
NormalizedDevShape::Pattern3IfSumJson => catch_unwind(AssertUnwindSafe(|| {
|
|
|
|
|
|
let norm = normalize_pattern3_if_sum_json_minimal(module)
|
|
|
|
|
|
.expect("P3 json normalization failed");
|
|
|
|
|
|
normalized_pattern2_to_structured(&norm)
|
|
|
|
|
|
})),
|
2025-12-12 16:40:20 +09:00
|
|
|
|
NormalizedDevShape::SelfhostIfSumP3 => catch_unwind(AssertUnwindSafe(|| {
|
|
|
|
|
|
let norm = normalize_selfhost_if_sum_p3(module)
|
|
|
|
|
|
.expect("selfhost P3 normalization failed");
|
|
|
|
|
|
normalized_pattern2_to_structured(&norm)
|
|
|
|
|
|
})),
|
|
|
|
|
|
NormalizedDevShape::SelfhostIfSumP3Ext => catch_unwind(AssertUnwindSafe(|| {
|
|
|
|
|
|
let norm = normalize_selfhost_if_sum_p3_ext(module)
|
|
|
|
|
|
.expect("selfhost P3 ext normalization failed");
|
|
|
|
|
|
normalized_pattern2_to_structured(&norm)
|
|
|
|
|
|
})),
|
|
|
|
|
|
// Phase 53: selfhost P2/P3 practical variations (delegate to existing normalizers)
|
|
|
|
|
|
NormalizedDevShape::SelfhostArgsParseP2 => catch_unwind(AssertUnwindSafe(|| {
|
|
|
|
|
|
let norm = normalize_pattern2_minimal(module);
|
|
|
|
|
|
normalized_pattern2_to_structured(&norm)
|
|
|
|
|
|
})),
|
|
|
|
|
|
NormalizedDevShape::SelfhostStmtCountP3 => catch_unwind(AssertUnwindSafe(|| {
|
|
|
|
|
|
let norm = normalize_selfhost_if_sum_p3_ext(module)
|
|
|
|
|
|
.expect("selfhost stmt_count P3 normalization failed");
|
|
|
|
|
|
normalized_pattern2_to_structured(&norm)
|
|
|
|
|
|
})),
|
2025-12-12 17:12:58 +09:00
|
|
|
|
// Phase 54: selfhost P2/P3 shape growth (delegate to existing normalizers)
|
|
|
|
|
|
NormalizedDevShape::SelfhostVerifySchemaP2 => catch_unwind(AssertUnwindSafe(|| {
|
|
|
|
|
|
let norm = normalize_pattern2_minimal(module);
|
|
|
|
|
|
normalized_pattern2_to_structured(&norm)
|
|
|
|
|
|
})),
|
|
|
|
|
|
NormalizedDevShape::SelfhostDetectFormatP3 => catch_unwind(AssertUnwindSafe(|| {
|
|
|
|
|
|
let norm = normalize_selfhost_if_sum_p3_ext(module)
|
|
|
|
|
|
.expect("selfhost detect_format P3 normalization failed");
|
|
|
|
|
|
normalized_pattern2_to_structured(&norm)
|
|
|
|
|
|
})),
|
2025-12-12 06:31:13 +09:00
|
|
|
|
// Phase 48-A: P4 minimal (delegates to P2 for now, but uses proper guard)
|
|
|
|
|
|
NormalizedDevShape::Pattern4ContinueMinimal => catch_unwind(AssertUnwindSafe(|| {
|
|
|
|
|
|
let norm = normalize_pattern4_continue_minimal(module)
|
|
|
|
|
|
.expect("P4 normalization failed");
|
|
|
|
|
|
normalized_pattern2_to_structured(&norm)
|
2025-12-11 22:12:46 +09:00
|
|
|
|
})),
|
2025-12-12 16:40:20 +09:00
|
|
|
|
NormalizedDevShape::JsonparserParseArrayContinueSkipWs => {
|
|
|
|
|
|
catch_unwind(AssertUnwindSafe(|| {
|
|
|
|
|
|
let norm =
|
|
|
|
|
|
normalize_jsonparser_parse_array_continue_skip_ws(module)
|
|
|
|
|
|
.expect("P4 array normalization failed");
|
|
|
|
|
|
normalized_pattern2_to_structured(&norm)
|
|
|
|
|
|
}))
|
|
|
|
|
|
}
|
|
|
|
|
|
NormalizedDevShape::JsonparserParseObjectContinueSkipWs => {
|
|
|
|
|
|
catch_unwind(AssertUnwindSafe(|| {
|
|
|
|
|
|
let norm =
|
|
|
|
|
|
normalize_jsonparser_parse_object_continue_skip_ws(module)
|
|
|
|
|
|
.expect("P4 object normalization failed");
|
|
|
|
|
|
normalized_pattern2_to_structured(&norm)
|
|
|
|
|
|
}))
|
|
|
|
|
|
}
|
2025-12-11 20:54:33 +09:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
match attempt {
|
|
|
|
|
|
Ok(structured) => {
|
2025-12-12 03:15:45 +09:00
|
|
|
|
if debug {
|
2025-12-11 20:54:33 +09:00
|
|
|
|
eprintln!(
|
2025-12-12 03:15:45 +09:00
|
|
|
|
"[joinir/normalized-dev/roundtrip] {:?} normalization succeeded (functions={})",
|
2025-12-11 20:54:33 +09:00
|
|
|
|
shape,
|
|
|
|
|
|
structured.functions.len()
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
return Ok(structured);
|
|
|
|
|
|
}
|
|
|
|
|
|
Err(_) => {
|
2025-12-12 03:15:45 +09:00
|
|
|
|
if debug {
|
2025-12-11 20:54:33 +09:00
|
|
|
|
eprintln!(
|
2025-12-12 03:15:45 +09:00
|
|
|
|
"[joinir/normalized-dev/roundtrip] {:?} normalization failed (unsupported)",
|
2025-12-11 20:54:33 +09:00
|
|
|
|
shape
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Err("[joinir/normalized-dev] all normalization attempts failed".into())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
|
mod tests {
|
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
|
|
|
|
fn build_structured_pattern1() -> JoinModule {
|
|
|
|
|
|
let mut module = JoinModule::new();
|
|
|
|
|
|
let mut loop_fn = JoinFunction::new(
|
|
|
|
|
|
JoinFuncId::new(1),
|
|
|
|
|
|
"loop_step".to_string(),
|
|
|
|
|
|
vec![ValueId(10)],
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
loop_fn.body.push(JoinInst::Compute(MirLikeInst::Const {
|
|
|
|
|
|
dst: ValueId(11),
|
|
|
|
|
|
value: ConstValue::Integer(0),
|
|
|
|
|
|
}));
|
|
|
|
|
|
loop_fn.body.push(JoinInst::Compute(MirLikeInst::BinOp {
|
|
|
|
|
|
dst: ValueId(12),
|
|
|
|
|
|
op: BinOpKind::Add,
|
|
|
|
|
|
lhs: ValueId(10),
|
|
|
|
|
|
rhs: ValueId(11),
|
|
|
|
|
|
}));
|
|
|
|
|
|
loop_fn.body.push(JoinInst::Jump {
|
|
|
|
|
|
cont: JoinContId(2),
|
|
|
|
|
|
args: vec![ValueId(12)],
|
|
|
|
|
|
cond: Some(ValueId(12)), // dummy
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
let mut k_exit =
|
|
|
|
|
|
JoinFunction::new(JoinFuncId::new(2), "k_exit".to_string(), vec![ValueId(12)]);
|
|
|
|
|
|
k_exit.body.push(JoinInst::Ret {
|
|
|
|
|
|
value: Some(ValueId(12)),
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
module.entry = Some(loop_fn.id);
|
|
|
|
|
|
module.add_function(loop_fn);
|
|
|
|
|
|
module.add_function(k_exit);
|
|
|
|
|
|
module
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn normalized_pattern1_minimal_smoke() {
|
|
|
|
|
|
let structured = build_structured_pattern1();
|
|
|
|
|
|
let normalized = normalize_pattern1_minimal(&structured);
|
|
|
|
|
|
assert_eq!(normalized.phase, JoinIrPhase::Normalized);
|
|
|
|
|
|
assert!(!normalized.env_layouts.is_empty());
|
|
|
|
|
|
assert!(!normalized.functions.is_empty());
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(feature = "normalized_dev")]
|
|
|
|
|
|
{
|
|
|
|
|
|
verify_normalized_pattern1(&normalized).expect("verifier should pass");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let restored = normalized.to_structured().expect("backup");
|
|
|
|
|
|
assert!(restored.is_structured());
|
|
|
|
|
|
assert_eq!(restored.functions.len(), structured.functions.len());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn normalized_pattern1_roundtrip_structured_equivalent() {
|
|
|
|
|
|
let structured = build_structured_pattern1();
|
|
|
|
|
|
let normalized = normalize_pattern1_minimal(&structured);
|
|
|
|
|
|
let reconstructed = normalized_pattern1_to_structured(&normalized);
|
|
|
|
|
|
|
|
|
|
|
|
assert!(reconstructed.is_structured());
|
|
|
|
|
|
assert_eq!(reconstructed.functions.len(), structured.functions.len());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|