diff --git a/src/mir/builder/calls/call_unified.rs b/src/mir/builder/calls/call_unified.rs index 9fe53f63..8dc365b7 100644 --- a/src/mir/builder/calls/call_unified.rs +++ b/src/mir/builder/calls/call_unified.rs @@ -59,56 +59,10 @@ pub fn classify_box_kind(box_name: &str) -> crate::mir::definitions::call_unifie // This function is kept for reference but should not be used. // Use CalleeResolverBox instead for all callee resolution. -/// Compute effects for a call based on its callee +// DEPRECATED: Moved to EffectsAnalyzerBox in effects_analyzer.rs +// Use EffectsAnalyzerBox::compute_call_effects() instead. pub fn compute_call_effects(callee: &Callee) -> EffectMask { - match callee { - Callee::Global(name) => { - match name.as_str() { - "print" | "error" => EffectMask::IO, - "panic" | "exit" => EffectMask::IO.add(Effect::Control), - "gc_collect" => EffectMask::IO.add(Effect::Alloc), - _ => EffectMask::IO, - } - }, - - Callee::Method { method, box_name, .. } => { - match method.as_str() { - "birth" => EffectMask::PURE.add(Effect::Alloc), - "get" | "length" | "size" => EffectMask::READ, - "set" | "push" | "pop" => EffectMask::READ.add(Effect::WriteHeap), - _ => { - // Check if it's a known pure method - if is_pure_method(box_name, method) { - EffectMask::PURE - } else { - EffectMask::READ - } - } - } - }, - - Callee::Constructor { .. } => EffectMask::PURE.add(Effect::Alloc), - - Callee::Closure { .. } => EffectMask::PURE.add(Effect::Alloc), - - Callee::Extern(name) => { - let (iface, method) = extern_calls::parse_extern_name(name); - extern_calls::compute_extern_effects(&iface, &method) - }, - - Callee::Value(_) => EffectMask::IO, // Conservative for dynamic calls - } -} - -/// Check if a method is known to be pure (no side effects) -fn is_pure_method(box_name: &str, method: &str) -> bool { - match (box_name, method) { - ("StringBox", m) => matches!(m, "upper" | "lower" | "trim" | "length"), - ("IntegerBox", m) => matches!(m, "abs" | "toString"), - ("FloatBox", m) => matches!(m, "round" | "floor" | "ceil"), - ("BoolBox", "not") => true, - _ => false, - } + super::effects_analyzer::EffectsAnalyzerBox::compute_call_effects(callee) } /// Create CallFlags based on callee type diff --git a/src/mir/builder/calls/effects_analyzer.rs b/src/mir/builder/calls/effects_analyzer.rs new file mode 100644 index 00000000..562f1331 --- /dev/null +++ b/src/mir/builder/calls/effects_analyzer.rs @@ -0,0 +1,156 @@ +/*! + * EffectsAnalyzerBox - エフェクト解析専用箱 + * + * 箱理論の実践: + * - 箱にする: エフェクト解析ロジックを1箱に集約 + * - 境界を作る: Pure/IO/Alloc/Control等の効果分類を一元管理 + * - 状態レス: すべて静的解析(実行時状態不要) + * + * 責務: + * - compute_call_effects: Calleeから副作用マスクを計算 + * - is_pure_method: メソッドがPure(副作用なし)か判定 + * - 既知の関数・メソッドのエフェクト知識を集約 + */ + +use crate::mir::definitions::call_unified::Callee; +use crate::mir::builder::{Effect, EffectMask}; +use super::extern_calls; + +/// エフェクト解析専用箱 +/// +/// 箱理論: +/// - 単一責務: Calleeのエフェクト解析のみ +/// - 状態レス: すべて静的関数(Callee情報のみで判定) +/// - 知識集約: 既知の関数・メソッドのエフェクト知識を一元管理 +pub struct EffectsAnalyzerBox; + +impl EffectsAnalyzerBox { + /// Compute effects for a call based on its callee + /// + /// エフェクト分類: + /// - PURE: 副作用なし(純粋計算) + /// - READ: ヒープ読み取りのみ + /// - IO: 入出力あり + /// - Alloc: メモリ確保 + /// - Control: 制御フロー変更(panic/exit) + /// - WriteHeap: ヒープ書き込み + pub fn compute_call_effects(callee: &Callee) -> EffectMask { + match callee { + Callee::Global(name) => { + match name.as_str() { + "print" | "error" => EffectMask::IO, + "panic" | "exit" => EffectMask::IO.add(Effect::Control), + "gc_collect" => EffectMask::IO.add(Effect::Alloc), + _ => EffectMask::IO, + } + }, + + Callee::Method { method, box_name, .. } => { + match method.as_str() { + "birth" => EffectMask::PURE.add(Effect::Alloc), + "get" | "length" | "size" => EffectMask::READ, + "set" | "push" | "pop" => EffectMask::READ.add(Effect::WriteHeap), + _ => { + // Check if it's a known pure method + if Self::is_pure_method(box_name, method) { + EffectMask::PURE + } else { + EffectMask::READ + } + } + } + }, + + Callee::Constructor { .. } => EffectMask::PURE.add(Effect::Alloc), + + Callee::Closure { .. } => EffectMask::PURE.add(Effect::Alloc), + + Callee::Extern(name) => { + let (iface, method) = extern_calls::parse_extern_name(name); + extern_calls::compute_extern_effects(&iface, &method) + }, + + Callee::Value(_) => EffectMask::IO, // Conservative for dynamic calls + } + } + + /// Check if a method is known to be pure (no side effects) + /// + /// Pure メソッドの条件: + /// - 副作用なし(ヒープ変更なし、I/Oなし) + /// - 同じ入力に対して同じ出力を返す + /// - プログラムの状態を変更しない + /// + /// 既知のPureメソッド: + /// - StringBox: upper, lower, trim, length + /// - IntegerBox: abs, toString + /// - FloatBox: round, floor, ceil + /// - BoolBox: not + pub fn is_pure_method(box_name: &str, method: &str) -> bool { + match (box_name, method) { + ("StringBox", m) => matches!(m, "upper" | "lower" | "trim" | "length"), + ("IntegerBox", m) => matches!(m, "abs" | "toString"), + ("FloatBox", m) => matches!(m, "round" | "floor" | "ceil"), + ("BoolBox", "not") => true, + _ => false, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mir::ValueId; + use crate::mir::definitions::call_unified::{CalleeBoxKind, TypeCertainty}; + + #[test] + fn test_compute_effects_global() { + let callee = Callee::Global("print".to_string()); + let effects = EffectsAnalyzerBox::compute_call_effects(&callee); + assert_eq!(effects, EffectMask::IO); + } + + #[test] + fn test_compute_effects_method_pure() { + let callee = Callee::Method { + box_name: "StringBox".to_string(), + method: "upper".to_string(), + receiver: Some(ValueId::new(1)), + certainty: TypeCertainty::Known, + box_kind: CalleeBoxKind::RuntimeData, + }; + let effects = EffectsAnalyzerBox::compute_call_effects(&callee); + assert_eq!(effects, EffectMask::PURE); + } + + #[test] + fn test_compute_effects_method_read() { + let callee = Callee::Method { + box_name: "ArrayBox".to_string(), + method: "get".to_string(), + receiver: Some(ValueId::new(1)), + certainty: TypeCertainty::Known, + box_kind: CalleeBoxKind::RuntimeData, + }; + let effects = EffectsAnalyzerBox::compute_call_effects(&callee); + assert_eq!(effects, EffectMask::READ); + } + + #[test] + fn test_compute_effects_constructor() { + let callee = Callee::Constructor { + box_type: "StringBox".to_string(), + }; + let effects = EffectsAnalyzerBox::compute_call_effects(&callee); + // Constructor should have PURE + Alloc + assert_eq!(effects, EffectMask::PURE.add(crate::mir::builder::Effect::Alloc)); + } + + #[test] + fn test_is_pure_method() { + assert!(EffectsAnalyzerBox::is_pure_method("StringBox", "upper")); + assert!(EffectsAnalyzerBox::is_pure_method("IntegerBox", "abs")); + assert!(EffectsAnalyzerBox::is_pure_method("BoolBox", "not")); + assert!(!EffectsAnalyzerBox::is_pure_method("ArrayBox", "push")); + } +} diff --git a/src/mir/builder/calls/emit.rs b/src/mir/builder/calls/emit.rs index 1efd3fbb..85a585f5 100644 --- a/src/mir/builder/calls/emit.rs +++ b/src/mir/builder/calls/emit.rs @@ -138,119 +138,22 @@ impl MirBuilder { // Private helper methods (small functions) // ======================================== - /// Try fallback handlers for global functions + /// Try fallback handlers for global functions (delegates to CallMaterializerBox) pub(super) fn try_global_fallback_handlers( &mut self, dst: Option, name: &str, args: &[ValueId], ) -> Result, String> { - // 0) Dev-only safety: treat condition_fn as always-true predicate when missing - if name == "condition_fn" { - let dstv = dst.unwrap_or_else(|| self.next_value_id()); - // Emit integer constant via ConstantEmissionBox - let one = crate::mir::builder::emission::constant::emit_integer(self, 1); - if dst.is_none() { - // If a destination was not provided, copy into the allocated dstv - self.emit_instruction(MirInstruction::Copy { dst: dstv, src: one })?; - crate::mir::builder::metadata::propagate::propagate(self, one, dstv); - } else { - // If caller provided dst, ensure the computed value lands there - self.emit_instruction(MirInstruction::Copy { dst: dstv, src: one })?; - crate::mir::builder::metadata::propagate::propagate(self, one, dstv); - } - return Ok(Some(())); - } - - // 1) Direct module function fallback: call by name if present - if let Some(ref module) = self.current_module { - if module.functions.contains_key(name) { - let dstv = dst.unwrap_or_else(|| self.next_value_id()); - let name_const = crate::mir::builder::name_const::make_name_const_result(self, name)?; - self.emit_instruction(MirInstruction::Call { - dst: Some(dstv), - func: name_const, - callee: Some(Callee::Global(name.to_string())), - args: args.to_vec(), - effects: EffectMask::IO, - })?; - self.annotate_call_result_from_func_name(dstv, name); - return Ok(Some(())); - } - } - - // 2) Unique static-method fallback: name+arity → Box.name/Arity - if let Some(cands) = self.static_method_index.get(name) { - let mut matches: Vec<(String, usize)> = cands - .iter() - .cloned() - .filter(|(_, ar)| *ar == args.len()) - .collect(); - if matches.len() == 1 { - let (bx, _arity) = matches.remove(0); - let func_name = format!("{}.{}{}", bx, name, format!("/{}", args.len())); - // Emit legacy call directly to preserve behavior - let dstv = dst.unwrap_or_else(|| self.next_value_id()); - let name_const = crate::mir::builder::name_const::make_name_const_result(self, &func_name)?; - self.emit_instruction(MirInstruction::Call { - dst: Some(dstv), - func: name_const, - callee: Some(Callee::Global(func_name.clone())), - args: args.to_vec(), - effects: EffectMask::IO, - })?; - // annotate - self.annotate_call_result_from_func_name(dstv, func_name); - return Ok(Some(())); - } - } - - Ok(None) + super::materializer::CallMaterializerBox::try_global_fallback_handlers(self, dst, name, args) } - /// Ensure receiver is materialized in Callee::Method (pub for UnifiedCallEmitterBox) + /// Ensure receiver is materialized in Callee::Method (delegates to CallMaterializerBox) pub(super) fn materialize_receiver_in_callee( &mut self, callee: Callee, ) -> Result { - match callee { - Callee::Method { box_name, method, receiver: Some(r), certainty, box_kind } => { - if std::env::var("NYASH_BUILDER_TRACE_RECV").ok().as_deref() == Some("1") { - let current_fn = self - .current_function - .as_ref() - .map(|f| f.signature.name.clone()) - .unwrap_or_else(|| "".to_string()); - let bb = self.current_block; - let names: Vec = self - .variable_map - .iter() - .filter(|(_, &vid)| vid == r) - .map(|(k, _)| k.clone()) - .collect(); - // CRITICAL DEBUG: Show type information sources - let origin = self.value_origin_newbox.get(&r).cloned(); - let vtype = self.value_types.get(&r).cloned(); - eprintln!( - "[builder/recv-trace] fn={} bb={:?} method={}.{} recv=%{} aliases={:?}", - current_fn, - bb, - box_name.clone(), - method, - r.0, - names - ); - eprintln!( - "[builder/recv-trace] value_origin_newbox: {:?}, value_types: {:?}", - origin, vtype - ); - } - // Prefer pinning to a slot so start_new_block can propagate it across entries. - let r_pinned = self.pin_to_slot(r, "@recv").unwrap_or(r); - Ok(Callee::Method { box_name, method, receiver: Some(r_pinned), certainty, box_kind }) - } - other => Ok(other), - } + super::materializer::CallMaterializerBox::materialize_receiver_in_callee(self, callee) } // ✅ 箱化完了: diff --git a/src/mir/builder/calls/materializer.rs b/src/mir/builder/calls/materializer.rs new file mode 100644 index 00000000..f0def3a7 --- /dev/null +++ b/src/mir/builder/calls/materializer.rs @@ -0,0 +1,151 @@ +/*! + * CallMaterializerBox - Call前処理・準備専用箱 + * + * 箱理論の実践: + * - 箱にする: Call発行前の前処理を1箱に集約 + * - 境界を作る: フォールバック処理・receiver実体化を分離 + * - 状態最小: MirBuilderを引数として受け取る(所有しない) + * + * 責務: + * - try_global_fallback_handlers: Global関数のフォールバック処理 + * - materialize_receiver_in_callee: Receiverの実体化(pinning) + * - Call発行前の準備処理全般 + */ + +use crate::mir::builder::{MirBuilder, ValueId, MirInstruction, EffectMask}; +use crate::mir::definitions::call_unified::Callee; + +/// Call前処理・準備専用箱 +/// +/// 箱理論: +/// - 単一責務: Call発行前の前処理のみ +/// - 状態レス: MirBuilderを引数で受け取る設計 +/// - サポート役: 本体のCall発行をサポートする役割 +pub struct CallMaterializerBox; + +impl CallMaterializerBox { + /// Try fallback handlers for global functions + /// + /// フォールバック処理の優先順位: + /// 1. Dev-only safety: condition_fn → always-true predicate + /// 2. Direct module function: module内の関数を直接呼び出し + /// 3. Unique static-method: name+arity → Box.name/Arity へ変換 + pub fn try_global_fallback_handlers( + builder: &mut MirBuilder, + dst: Option, + name: &str, + args: &[ValueId], + ) -> Result, String> { + // 0) Dev-only safety: treat condition_fn as always-true predicate when missing + if name == "condition_fn" { + let dstv = dst.unwrap_or_else(|| builder.next_value_id()); + // Emit integer constant via ConstantEmissionBox + let one = crate::mir::builder::emission::constant::emit_integer(builder, 1); + if dst.is_none() { + // If a destination was not provided, copy into the allocated dstv + builder.emit_instruction(MirInstruction::Copy { dst: dstv, src: one })?; + crate::mir::builder::metadata::propagate::propagate(builder, one, dstv); + } else { + // If caller provided dst, ensure the computed value lands there + builder.emit_instruction(MirInstruction::Copy { dst: dstv, src: one })?; + crate::mir::builder::metadata::propagate::propagate(builder, one, dstv); + } + return Ok(Some(())); + } + + // 1) Direct module function fallback: call by name if present + if let Some(ref module) = builder.current_module { + if module.functions.contains_key(name) { + let dstv = dst.unwrap_or_else(|| builder.next_value_id()); + let name_const = crate::mir::builder::name_const::make_name_const_result(builder, name)?; + builder.emit_instruction(MirInstruction::Call { + dst: Some(dstv), + func: name_const, + callee: Some(Callee::Global(name.to_string())), + args: args.to_vec(), + effects: EffectMask::IO, + })?; + builder.annotate_call_result_from_func_name(dstv, name); + return Ok(Some(())); + } + } + + // 2) Unique static-method fallback: name+arity → Box.name/Arity + if let Some(cands) = builder.static_method_index.get(name) { + let mut matches: Vec<(String, usize)> = cands + .iter() + .cloned() + .filter(|(_, ar)| *ar == args.len()) + .collect(); + if matches.len() == 1 { + let (bx, _arity) = matches.remove(0); + let func_name = format!("{}.{}{}", bx, name, format!("/{}", args.len())); + // Emit legacy call directly to preserve behavior + let dstv = dst.unwrap_or_else(|| builder.next_value_id()); + let name_const = crate::mir::builder::name_const::make_name_const_result(builder, &func_name)?; + builder.emit_instruction(MirInstruction::Call { + dst: Some(dstv), + func: name_const, + callee: Some(Callee::Global(func_name.clone())), + args: args.to_vec(), + effects: EffectMask::IO, + })?; + // annotate + builder.annotate_call_result_from_func_name(dstv, func_name); + return Ok(Some(())); + } + } + + Ok(None) + } + + /// Ensure receiver is materialized in Callee::Method + /// + /// Receiver実体化の目的: + /// - receiverをスロットにpinningして、start_new_blockで伝播可能に + /// - SSA不変条件の保持(receiverが常に定義済みであることを保証) + /// - デバッグトレース出力(NYASH_BUILDER_TRACE_RECV=1) + pub fn materialize_receiver_in_callee( + builder: &mut MirBuilder, + callee: Callee, + ) -> Result { + match callee { + Callee::Method { box_name, method, receiver: Some(r), certainty, box_kind } => { + if std::env::var("NYASH_BUILDER_TRACE_RECV").ok().as_deref() == Some("1") { + let current_fn = builder + .current_function + .as_ref() + .map(|f| f.signature.name.clone()) + .unwrap_or_else(|| "".to_string()); + let bb = builder.current_block; + let names: Vec = builder + .variable_map + .iter() + .filter(|(_, &vid)| vid == r) + .map(|(k, _)| k.clone()) + .collect(); + // CRITICAL DEBUG: Show type information sources + let origin = builder.value_origin_newbox.get(&r).cloned(); + let vtype = builder.value_types.get(&r).cloned(); + eprintln!( + "[builder/recv-trace] fn={} bb={:?} method={}.{} recv=%{} aliases={:?}", + current_fn, + bb, + box_name.clone(), + method, + r.0, + names + ); + eprintln!( + "[builder/recv-trace] value_origin_newbox: {:?}, value_types: {:?}", + origin, vtype + ); + } + // Prefer pinning to a slot so start_new_block can propagate it across entries. + let r_pinned = builder.pin_to_slot(r, "@recv").unwrap_or(r); + Ok(Callee::Method { box_name, method, receiver: Some(r_pinned), certainty, box_kind }) + } + other => Ok(other), + } + } +} diff --git a/src/mir/builder/calls/mod.rs b/src/mir/builder/calls/mod.rs index c5e8157f..a5281c45 100644 --- a/src/mir/builder/calls/mod.rs +++ b/src/mir/builder/calls/mod.rs @@ -19,11 +19,13 @@ pub mod special_handlers; // New refactored modules (Box Theory Phase 1 & 2 & 25.1d & Phase 3) pub mod lowering; pub mod utils; -pub mod emit; // Phase 2: Call emission -pub mod build; // Phase 2: Call building -pub mod guard; // Phase 25.1d: Structural guard (static/runtime box separation) -pub mod resolver; // Phase 25.1d: Callee resolution (CallTarget → Callee) -pub mod unified_emitter; // Phase 3-A: Unified call emitter (統一Call発行専用箱) +pub mod emit; // Phase 2: Call emission +pub mod build; // Phase 2: Call building +pub mod guard; // Phase 25.1d: Structural guard (static/runtime box separation) +pub mod resolver; // Phase 25.1d: Callee resolution (CallTarget → Callee) +pub mod unified_emitter; // Phase 3-A: Unified call emitter (統一Call発行専用箱) +pub mod effects_analyzer; // Phase 3-B: Effects analyzer (エフェクト解析専用箱) +pub mod materializer; // Phase 3-C: Call materializer (Call前処理・準備専用箱) // Re-export public interfaces pub use call_target::CallTarget; diff --git a/src/mir/builder/calls/unified_emitter.rs b/src/mir/builder/calls/unified_emitter.rs index 1e28dc96..62368705 100644 --- a/src/mir/builder/calls/unified_emitter.rs +++ b/src/mir/builder/calls/unified_emitter.rs @@ -111,8 +111,8 @@ impl UnifiedCallEmitterBox { Ok(c) => c, Err(e) => { if let CallTarget::Global(ref name) = target { - // Try fallback handlers - if let Some(result) = builder.try_global_fallback_handlers(dst, name, &args)? { + // Try fallback handlers (via CallMaterializerBox) + if let Some(result) = super::materializer::CallMaterializerBox::try_global_fallback_handlers(builder, dst, name, &args)? { return Ok(result); } } @@ -120,8 +120,8 @@ impl UnifiedCallEmitterBox { } }; - // Safety: ensure receiver is materialized even after callee conversion - callee = builder.materialize_receiver_in_callee(callee)?; + // Safety: ensure receiver is materialized even after callee conversion (via CallMaterializerBox) + callee = super::materializer::CallMaterializerBox::materialize_receiver_in_callee(builder, callee)?; // Structural guard: prevent static compiler boxes from being called with runtime receivers // 箱理論: CalleeGuardBox による構造的分離