From 12e2f87c6f77c63cb1c58647ec58d8bfe125892f Mon Sep 17 00:00:00 2001 From: nyash-codex Date: Thu, 11 Dec 2025 22:50:23 +0900 Subject: [PATCH] Refine normalized bridge direct path and env guard --- src/mir/join_ir/normalized.rs | 2 + src/mir/join_ir/normalized/dev_env.rs | 38 ++ src/mir/join_ir/normalized/shape_guard.rs | 10 + src/mir/join_ir_vm_bridge/bridge.rs | 2 +- .../join_ir_vm_bridge/normalized_bridge.rs | 44 +- .../normalized_bridge/direct.rs | 388 ++++++++++++++++++ tests/normalized_joinir_min.rs | 60 +-- 7 files changed, 484 insertions(+), 60 deletions(-) create mode 100644 src/mir/join_ir/normalized/dev_env.rs create mode 100644 src/mir/join_ir_vm_bridge/normalized_bridge/direct.rs diff --git a/src/mir/join_ir/normalized.rs b/src/mir/join_ir/normalized.rs index 1c5607f5..6326af15 100644 --- a/src/mir/join_ir/normalized.rs +++ b/src/mir/join_ir/normalized.rs @@ -17,6 +17,8 @@ use std::panic::{catch_unwind, AssertUnwindSafe}; #[cfg(feature = "normalized_dev")] pub mod fixtures; #[cfg(feature = "normalized_dev")] +pub mod dev_env; +#[cfg(feature = "normalized_dev")] pub(crate) mod shape_guard; #[cfg(feature = "normalized_dev")] use crate::mir::join_ir::normalized::shape_guard::NormalizedDevShape; diff --git a/src/mir/join_ir/normalized/dev_env.rs b/src/mir/join_ir/normalized/dev_env.rs new file mode 100644 index 00000000..b6b63e42 --- /dev/null +++ b/src/mir/join_ir/normalized/dev_env.rs @@ -0,0 +1,38 @@ +#![cfg(feature = "normalized_dev")] + +use once_cell::sync::Lazy; +use std::sync::Mutex; + +/// RAII guard for normalized_dev env toggling (NYASH_JOINIR_NORMALIZED_DEV_RUN). +/// 汚染防止のため tests/runner の両方で再利用できるようにここに置く。 +pub struct NormalizedDevEnvGuard { + _lock: std::sync::MutexGuard<'static, ()>, + prev: Option, +} + +static NORMALIZED_ENV_LOCK: Lazy> = Lazy::new(|| Mutex::new(())); + +impl NormalizedDevEnvGuard { + pub fn new(enabled: bool) -> Self { + let lock = NORMALIZED_ENV_LOCK + .lock() + .expect("normalized env mutex poisoned"); + let prev = std::env::var("NYASH_JOINIR_NORMALIZED_DEV_RUN").ok(); + if enabled { + std::env::set_var("NYASH_JOINIR_NORMALIZED_DEV_RUN", "1"); + } else { + std::env::remove_var("NYASH_JOINIR_NORMALIZED_DEV_RUN"); + } + Self { _lock: lock, prev } + } +} + +impl Drop for NormalizedDevEnvGuard { + fn drop(&mut self) { + if let Some(prev) = &self.prev { + std::env::set_var("NYASH_JOINIR_NORMALIZED_DEV_RUN", prev); + } else { + std::env::remove_var("NYASH_JOINIR_NORMALIZED_DEV_RUN"); + } + } +} diff --git a/src/mir/join_ir/normalized/shape_guard.rs b/src/mir/join_ir/normalized/shape_guard.rs index 6cc489ef..eba7f143 100644 --- a/src/mir/join_ir/normalized/shape_guard.rs +++ b/src/mir/join_ir/normalized/shape_guard.rs @@ -10,6 +10,11 @@ pub(crate) enum NormalizedDevShape { JsonparserAtoiMini, } +/// 直接 Normalized→MIR ブリッジで扱う shape を返す(dev 限定)。 +pub(crate) fn direct_shapes(module: &JoinModule) -> Vec { + supported_shapes(module) +} + pub(crate) fn supported_shapes(module: &JoinModule) -> Vec { let mut shapes = Vec::new(); if is_jsonparser_atoi_mini(module) { @@ -27,6 +32,11 @@ pub(crate) fn supported_shapes(module: &JoinModule) -> Vec { shapes } +#[allow(dead_code)] +pub(crate) fn is_direct_supported(module: &JoinModule) -> bool { + !direct_shapes(module).is_empty() +} + pub(crate) fn is_pattern1_mini(module: &JoinModule) -> bool { module.is_structured() && find_loop_step(module).is_some() } diff --git a/src/mir/join_ir_vm_bridge/bridge.rs b/src/mir/join_ir_vm_bridge/bridge.rs index a701074d..b02fbd2e 100644 --- a/src/mir/join_ir_vm_bridge/bridge.rs +++ b/src/mir/join_ir_vm_bridge/bridge.rs @@ -81,7 +81,7 @@ fn try_normalized_direct_bridge( module: &JoinModule, meta: &JoinFuncMetaMap, ) -> Result, JoinIrVmBridgeError> { - let shapes = shape_guard::supported_shapes(module); + let shapes = shape_guard::direct_shapes(module); if shapes.is_empty() { return Ok(None); } diff --git a/src/mir/join_ir_vm_bridge/normalized_bridge.rs b/src/mir/join_ir_vm_bridge/normalized_bridge.rs index f03c0017..a4940f0b 100644 --- a/src/mir/join_ir_vm_bridge/normalized_bridge.rs +++ b/src/mir/join_ir_vm_bridge/normalized_bridge.rs @@ -9,10 +9,32 @@ use crate::mir::join_ir::{ }; use crate::mir::MirModule; +mod direct; + +/// Direct Normalized → MIR 変換が未対応のときに使うフォールバック。 +fn lower_normalized_via_structured( + norm: &NormalizedModule, + meta: &JoinFuncMetaMap, +) -> Result { + let structured = if let Some(snapshot) = norm.to_structured() { + snapshot + } else if norm.functions.len() <= 2 { + normalized_pattern1_to_structured(norm) + } else { + normalized_pattern2_to_structured(norm) + }; + + if crate::config::env::joinir_dev_enabled() { + eprintln!("[joinir/normalized-bridge/fallback] using structured path (functions={})", structured.functions.len()); + } + + lower_joinir_structured_to_mir_with_meta(&structured, meta) +} + /// Dev-only Normalized → MIR ブリッジ(Pattern1/2 ミニ + JP mini/atoi mini 専用) pub(crate) fn lower_normalized_to_mir_minimal( norm: &NormalizedModule, - _meta: &JoinFuncMetaMap, + meta: &JoinFuncMetaMap, ) -> Result { if norm.phase != JoinIrPhase::Normalized { return Err(JoinIrVmBridgeError::new( @@ -49,14 +71,14 @@ pub(crate) fn lower_normalized_to_mir_minimal( } } - let mut norm_clone = norm.clone(); - norm_clone.structured_backup = None; - - let structured = if norm_clone.functions.len() <= 2 { - normalized_pattern1_to_structured(&norm_clone) - } else { - normalized_pattern2_to_structured(&norm_clone) - }; - - lower_joinir_structured_to_mir_with_meta(&structured, _meta) + // direct 対象は Normalized → MIR をそのまま吐く。未対応 shape は Structured 経由にフォールバック。 + match direct::lower_normalized_direct_minimal(norm) { + Ok(mir) => Ok(mir), + Err(err) => { + if crate::config::env::joinir_dev_enabled() { + eprintln!("[joinir/normalized-bridge/fallback] direct path failed: {}; falling back to Structured path", err.message); + } + lower_normalized_via_structured(norm, meta) + } + } } diff --git a/src/mir/join_ir_vm_bridge/normalized_bridge/direct.rs b/src/mir/join_ir_vm_bridge/normalized_bridge/direct.rs new file mode 100644 index 00000000..d5ec7669 --- /dev/null +++ b/src/mir/join_ir_vm_bridge/normalized_bridge/direct.rs @@ -0,0 +1,388 @@ +#![cfg(feature = "normalized_dev")] + +use super::super::join_func_name; +use super::super::JoinIrVmBridgeError; +use super::super::convert_mir_like_inst; +use crate::ast::Span; +use crate::config::env::joinir_dev_enabled; +use crate::mir::join_ir::normalized::{JpFuncId, JpFunction, JpInst, JpOp, NormalizedModule}; +use crate::mir::join_ir::{JoinFuncId, JoinIrPhase, MirLikeInst}; +use crate::mir::{ + BasicBlock, BasicBlockId, ConstValue as MirConstValue, EffectMask, FunctionSignature, + MirFunction, MirInstruction, MirModule, MirType, ValueId, +}; +use std::collections::BTreeMap; +use std::mem; + +/// Normalized → MIR の最小 direct 変換(Pattern1/2 ミニ+JP mini/atoi mini)。 +pub(crate) fn lower_normalized_direct_minimal( + norm: &NormalizedModule, +) -> Result { + if norm.phase != JoinIrPhase::Normalized { + return Err(JoinIrVmBridgeError::new( + "[joinir/normalized-bridge] expected Normalized JoinIR module", + )); + } + + let debug_dump = std::env::var("JOINIR_TEST_DEBUG").is_ok(); + let verbose = joinir_dev_enabled(); + if verbose { + eprintln!( + "[joinir/normalized-bridge/direct] lowering normalized module (functions={}, env_layouts={})", + norm.functions.len(), + norm.env_layouts.len() + ); + } + + let mut mir_module = MirModule::new("joinir_normalized_direct".to_string()); + + for func in norm.functions.values() { + let mir_func = lower_normalized_function_direct(func, norm)?; + mir_module.add_function(mir_func); + } + + if debug_dump { + eprintln!( + "[joinir/normalized-bridge/direct] produced MIR (debug dump): {:#?}", + mir_module + ); + } + + Ok(mir_module) +} + +fn lower_normalized_function_direct( + func: &JpFunction, + norm: &NormalizedModule, +) -> Result { + let verbose = joinir_dev_enabled(); + let env_fields = func + .env_layout + .and_then(|id| norm.env_layouts.iter().find(|layout| layout.id == id)); + + let params: Vec = env_fields + .map(|layout| { + layout + .fields + .iter() + .enumerate() + .map(|(idx, f)| f.value_id.unwrap_or(ValueId(idx as u32))) + .collect() + }) + .unwrap_or_default(); + + // Build a dense ValueId mapping to ensure VM registersはVoidにならない。 + let mut value_map: BTreeMap = BTreeMap::new(); + for id in ¶ms { + remap_value(*id, &mut value_map); + } + for inst in &func.body { + for vid in value_ids_in_inst(inst) { + remap_value(vid, &mut value_map); + } + } + + let remap = |id: ValueId, map: &mut BTreeMap| remap_value(id, map); + let remap_vec = |ids: &[ValueId], map: &mut BTreeMap| { + ids.iter().map(|id| remap_value(*id, map)).collect::>() + }; + + let remapped_params = remap_vec(¶ms, &mut value_map); + let param_types = vec![MirType::Unknown; remapped_params.len()]; + let signature = FunctionSignature { + name: join_func_name(JoinFuncId(func.id.0)), + params: param_types, + return_type: MirType::Unknown, + effects: EffectMask::PURE, + }; + + let mut mir_func = MirFunction::new(signature, BasicBlockId(0)); + if !remapped_params.is_empty() { + mir_func.params = remapped_params.clone(); + } + + mir_func.next_value_id = value_map.len() as u32; + + let mut current_block_id = BasicBlockId(0); + let mut next_block_id = 1; + let mut current_insts: Vec = Vec::new(); + let mut terminated = false; + + if verbose { + eprintln!( + "[joinir/normalized-bridge/direct] lowering fn={} params={:?} remapped_params={:?} body_len={}", + func.name, params, remapped_params, func.body.len() + ); + } + + for inst in &func.body { + if terminated { + break; + } + + match inst { + JpInst::Let { dst, op, args } => { + let remapped_dst = remap(*dst, &mut value_map); + let remapped_args = remap_vec(args, &mut value_map); + let mir_like = jp_op_to_mir_like(remapped_dst, op, &remapped_args)?; + let mir_inst = convert_mir_like_inst(&mir_like)?; + current_insts.push(mir_inst); + } + JpInst::EnvLoad { .. } | JpInst::EnvStore { .. } => { + return Err(JoinIrVmBridgeError::new( + "[joinir/normalized-bridge/direct] EnvLoad/EnvStore not supported in minimal direct bridge", + )); + } + JpInst::TailCallFn { target, env } => { + let env_remapped = remap_vec(env, &mut value_map); + let (instructions, terminator) = build_tail_call(&mut mir_func, target, &env_remapped); + finalize_block( + &mut mir_func, + current_block_id, + { + let mut insts = mem::take(&mut current_insts); + insts.extend(instructions); + insts + }, + terminator, + None, + ); + terminated = true; + } + JpInst::TailCallKont { env, .. } => { + let env_remapped = remap_vec(env, &mut value_map); + let return_val = env_remapped.first().copied(); + finalize_block( + &mut mir_func, + current_block_id, + mem::take(&mut current_insts), + MirInstruction::Return { value: return_val }, + Some(env_remapped), + ); + terminated = true; + } + JpInst::If { + cond, + then_target, + else_target, + env, + } => { + let then_bb = BasicBlockId(next_block_id); + next_block_id += 1; + let else_bb = BasicBlockId(next_block_id); + next_block_id += 1; + + let cond_remapped = remap(*cond, &mut value_map); + let env_remapped = remap_vec(env, &mut value_map); + + // Branch from current block + finalize_block( + &mut mir_func, + current_block_id, + mem::take(&mut current_insts), + MirInstruction::Branch { + condition: cond_remapped, + then_bb, + else_bb, + }, + None, + ); + + // Decide which branch continues the loop (self) and which exits. + let (exit_bb, exit_target, cont_bb) = if then_target.0 == func.id.0 { + (else_bb, else_target, then_bb) + } else if else_target.0 == func.id.0 { + (then_bb, then_target, else_bb) + } else { + // Both branches exit; build both and stop. + build_exit_or_tail_branch( + &mut mir_func, + then_bb, + then_target, + &env_remapped, + func.id, + )?; + build_exit_or_tail_branch( + &mut mir_func, + else_bb, + else_target, + &env_remapped, + func.id, + )?; + terminated = true; + continue; + }; + + build_exit_or_tail_branch( + &mut mir_func, + exit_bb, + exit_target, + &env_remapped, + func.id, + )?; + + mir_func + .blocks + .entry(cont_bb) + .or_insert_with(|| BasicBlock::new(cont_bb)); + current_block_id = cont_bb; + current_insts = Vec::new(); + } + } + } + + if !terminated { + // Flush remaining instructions into the current block and end with Return None + let block = mir_func + .blocks + .entry(current_block_id) + .or_insert_with(|| BasicBlock::new(current_block_id)); + if !current_insts.is_empty() { + block.instructions = current_insts; + block.instruction_spans = vec![Span::unknown(); block.instructions.len()]; + } + if block.terminator.is_none() { + block.terminator = Some(MirInstruction::Return { value: None }); + } + } + + Ok(mir_func) +} + +fn jp_op_to_mir_like( + dst: ValueId, + op: &JpOp, + args: &[ValueId], +) -> Result { + match op { + JpOp::Const(v) => Ok(MirLikeInst::Const { + dst, + value: v.clone(), + }), + JpOp::BinOp(op) => Ok(MirLikeInst::BinOp { + dst, + op: *op, + lhs: args.get(0).copied().unwrap_or(ValueId(0)), + rhs: args.get(1).copied().unwrap_or(ValueId(0)), + }), + JpOp::Unary(op) => Ok(MirLikeInst::UnaryOp { + dst, + op: *op, + operand: args.get(0).copied().unwrap_or(ValueId(0)), + }), + JpOp::Compare(op) => Ok(MirLikeInst::Compare { + dst, + op: *op, + lhs: args.get(0).copied().unwrap_or(ValueId(0)), + rhs: args.get(1).copied().unwrap_or(ValueId(0)), + }), + JpOp::BoxCall { box_name, method } => Ok(MirLikeInst::BoxCall { + dst: Some(dst), + box_name: box_name.clone(), + method: method.clone(), + args: args.to_vec(), + }), + } +} + +fn build_tail_call( + mir_func: &mut MirFunction, + target: &JpFuncId, + env: &[ValueId], +) -> (Vec, MirInstruction) { + let func_name_id = mir_func.next_value_id(); + let result_id = mir_func.next_value_id(); + let func_name = join_func_name(JoinFuncId(target.0)); + + let mut instructions = Vec::new(); + instructions.push(MirInstruction::Const { + dst: func_name_id, + value: MirConstValue::String(func_name), + }); + instructions.push(MirInstruction::Call { + dst: Some(result_id), + func: func_name_id, + callee: None, + args: env.to_vec(), + effects: EffectMask::PURE, + }); + + (instructions, MirInstruction::Return { value: Some(result_id) }) +} + +fn build_exit_or_tail_branch( + mir_func: &mut MirFunction, + block_id: BasicBlockId, + target: &JpFuncId, + env: &[ValueId], + self_id: JpFuncId, +) -> Result<(), JoinIrVmBridgeError> { + if target.0 == self_id.0 { + // Continue the loop: tail call self with env, then return its result + let (insts, term) = build_tail_call(mir_func, target, env); + let block = mir_func + .blocks + .entry(block_id) + .or_insert_with(|| BasicBlock::new(block_id)); + block.instructions = insts; + block.instruction_spans = vec![Span::unknown(); block.instructions.len()]; + block.terminator = Some(term); + return Ok(()); + } + + // Exit: return first env arg (Pattern2 minis pass loop state in env[0]) + let ret_val = env.first().copied(); + let block = mir_func + .blocks + .entry(block_id) + .or_insert_with(|| BasicBlock::new(block_id)); + block.instructions.clear(); + block.instruction_spans.clear(); + block.terminator = Some(MirInstruction::Return { value: ret_val }); + block.jump_args = Some(env.to_vec()); + Ok(()) +} + +fn value_ids_in_inst(inst: &JpInst) -> Vec { + match inst { + JpInst::Let { dst, args, .. } => { + let mut ids = vec![*dst]; + ids.extend(args.iter().copied()); + ids + } + JpInst::EnvLoad { dst, env, .. } => vec![*dst, *env], + JpInst::EnvStore { env, src, .. } => vec![*env, *src], + JpInst::TailCallFn { env, .. } | JpInst::TailCallKont { env, .. } => env.clone(), + JpInst::If { cond, env, .. } => { + let mut ids = vec![*cond]; + ids.extend(env.iter().copied()); + ids + } + } +} + +fn remap_value(id: ValueId, map: &mut BTreeMap) -> ValueId { + if let Some(mapped) = map.get(&id) { + return *mapped; + } + let next = ValueId(map.len() as u32); + map.insert(id, next); + next +} + +fn finalize_block( + mir_func: &mut MirFunction, + block_id: BasicBlockId, + instructions: Vec, + terminator: MirInstruction, + jump_args: Option>, +) { + let block = mir_func + .blocks + .entry(block_id) + .or_insert_with(|| BasicBlock::new(block_id)); + block.instructions = instructions; + block.instruction_spans = vec![Span::unknown(); block.instructions.len()]; + block.terminator = Some(terminator); + block.jump_args = jump_args; +} diff --git a/tests/normalized_joinir_min.rs b/tests/normalized_joinir_min.rs index c30d86e1..f519172c 100644 --- a/tests/normalized_joinir_min.rs +++ b/tests/normalized_joinir_min.rs @@ -6,6 +6,7 @@ use nyash_rust::mir::join_ir::{ normalized_pattern2_to_structured, BinOpKind, ConstValue, JoinContId, JoinFuncId, JoinFunction, JoinInst, JoinIrPhase, JoinModule, MirLikeInst, }; +use nyash_rust::mir::join_ir::normalized::dev_env::NormalizedDevEnvGuard; use nyash_rust::mir::join_ir::normalized::fixtures::{ build_jsonparser_atoi_structured_for_normalized_dev, build_jsonparser_skip_ws_structured_for_normalized_dev, @@ -15,39 +16,6 @@ use nyash_rust::mir::join_ir_runner::run_joinir_function; use nyash_rust::mir::join_ir_ops::JoinValue; use nyash_rust::mir::join_ir_vm_bridge::run_joinir_via_vm; use nyash_rust::mir::ValueId; -use once_cell::sync::Lazy; -use std::sync::Mutex; - -static NORMALIZED_ENV_LOCK: Lazy> = Lazy::new(|| Mutex::new(())); - -struct NormalizedRunGuard { - _lock: std::sync::MutexGuard<'static, ()>, - prev: Option, -} - -impl NormalizedRunGuard { - fn new(enabled: bool) -> Self { - let lock = NORMALIZED_ENV_LOCK.lock().expect("env mutex poisoned"); - let prev = std::env::var("NYASH_JOINIR_NORMALIZED_DEV_RUN").ok(); - if enabled { - std::env::set_var("NYASH_JOINIR_NORMALIZED_DEV_RUN", "1"); - } else { - std::env::remove_var("NYASH_JOINIR_NORMALIZED_DEV_RUN"); - } - Self { _lock: lock, prev } - } -} - -impl Drop for NormalizedRunGuard { - fn drop(&mut self) { - if let Some(prev) = &self.prev { - std::env::set_var("NYASH_JOINIR_NORMALIZED_DEV_RUN", prev); - } else { - std::env::remove_var("NYASH_JOINIR_NORMALIZED_DEV_RUN"); - } - } -} - fn assert_normalized_dev_ready() { assert!( nyash_rust::config::env::normalized_dev_enabled(), @@ -61,7 +29,7 @@ fn run_joinir_runner( args: &[JoinValue], normalized: bool, ) -> JoinValue { - let _guard = NormalizedRunGuard::new(normalized); + let _guard = NormalizedDevEnvGuard::new(normalized); if normalized { assert_normalized_dev_ready(); } @@ -75,7 +43,7 @@ fn run_joinir_vm_bridge( args: &[JoinValue], normalized: bool, ) -> JoinValue { - let _guard = NormalizedRunGuard::new(normalized); + let _guard = NormalizedDevEnvGuard::new(normalized); if normalized { assert_normalized_dev_ready(); } @@ -160,10 +128,8 @@ fn normalized_pattern1_exec_matches_structured() { let entry = structured.entry.unwrap_or(JoinFuncId::new(1)); let input = [JoinValue::Int(0)]; - let result_structured = - run_joinir_via_vm(&structured, entry, &input).expect("structured run should succeed"); - let result_norm = run_joinir_via_vm(&reconstructed, entry, &input) - .expect("normalized roundtrip run should succeed"); + let result_structured = run_joinir_vm_bridge(&structured, entry, &input, false); + let result_norm = run_joinir_vm_bridge(&reconstructed, entry, &input, false); assert_eq!(result_structured, result_norm); } @@ -180,9 +146,9 @@ fn normalized_pattern1_exec_matches_structured_roundtrip_backup() { let entry = structured.entry.unwrap_or(JoinFuncId::new(1)); let input = [JoinValue::Int(0)]; - let base = run_joinir_via_vm(&structured, entry, &input).expect("structured run"); - let recon = run_joinir_via_vm(&reconstructed, entry, &input).expect("reconstructed run"); - let restored = run_joinir_via_vm(&restored_backup, entry, &input).expect("backup run"); + let base = run_joinir_vm_bridge(&structured, entry, &input, false); + let recon = run_joinir_vm_bridge(&reconstructed, entry, &input, false); + let restored = run_joinir_vm_bridge(&restored_backup, entry, &input, false); assert_eq!(base, recon); assert_eq!(base, restored); @@ -218,8 +184,8 @@ fn normalized_pattern2_exec_matches_structured() { let entry = structured.entry.unwrap_or(JoinFuncId::new(0)); let input = [JoinValue::Int(0)]; - let base = run_joinir_via_vm(&structured, entry, &input).expect("structured run"); - let recon = run_joinir_via_vm(&reconstructed, entry, &input).expect("normalized roundtrip run"); + let base = run_joinir_vm_bridge(&structured, entry, &input, false); + let recon = run_joinir_vm_bridge(&reconstructed, entry, &input, false); assert_eq!(base, recon); } @@ -264,10 +230,8 @@ fn normalized_pattern2_real_loop_exec_matches_structured() { for n in cases { let input = [JoinValue::Int(n)]; - let base = - run_joinir_via_vm(&structured, entry, &input).expect("structured execution should pass"); - let recon = run_joinir_via_vm(&reconstructed, entry, &input) - .expect("normalized roundtrip execution should pass"); + let base = run_joinir_vm_bridge(&structured, entry, &input, false); + let recon = run_joinir_vm_bridge(&reconstructed, entry, &input, false); assert_eq!(base, recon, "mismatch at n={}", n); let expected_sum = n * (n.saturating_sub(1)) / 2;