From 9a4f05adac64077cdb5abe365414bbaf7109ce7d Mon Sep 17 00:00:00 2001 From: nyash-codex Date: Mon, 17 Nov 2025 23:49:18 +0900 Subject: [PATCH] =?UTF-8?q?refactor(builder):=20Phase=203-A=20-=20UnifiedC?= =?UTF-8?q?allEmitterBox=E5=AE=9F=E8=A3=85=E5=AE=8C=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 箱理論の実践:統一Call発行ロジックを独立した箱に集約 - 単一責務:統一Call発行のみ(Legacy Callは別モジュール) - 状態レス:MirBuilderを引数で受け取る設計 - ピュア関数的:入力CallTarget → 解決・発行 → MirCall命令 実装内容: 1. 新規ファイル作成 - src/mir/builder/calls/unified_emitter.rs (~250行) - UnifiedCallEmitterBox構造体 - 4つの主要メソッド: * emit_unified_call (公開API) * emit_unified_call_impl (コア実装) * emit_global_unified (Global関数呼び出し) * emit_value_unified (第一級関数呼び出し) 2. emit.rs からロジック移動 - emit_unified_call → 委譲に変更(1行) - emit_unified_call_impl → 削除(~150行削減) - emit_global_unified → 削除(委譲に変更) - emit_value_unified → 削除(委譲に変更) - try_global_fallback_handlers → pub(super)に変更 - materialize_receiver_in_callee → pub(super)に変更 3. mod.rs更新 - unified_emitter モジュール追加 箱化効果(Phase 3-A単独): - emit.rs: 467行 → 261行(-206行、44%削減!) - unified_emitter.rs: 250行(新規、Unified専用箱) - 読みやすさ大幅向上:統一Call発行ロジックが独立 - 責務分離明確化:Legacy/Unifiedの完全分離 Phase 3 進捗: - Phase 3-A: UnifiedCallEmitterBox ✅ 完了(本コミット) - Phase 3-B: EffectsAnalyzerBox ⏳ 次の目標 - Phase 3-C: CallMaterializerBox ⏳ 最終目標 ビルド・テスト: - cargo build --release: ✅ 成功 - 既存機能互換性: ✅ 完全保持 --- src/mir/builder/calls/emit.rs | 232 ++------------------- src/mir/builder/calls/mod.rs | 11 +- src/mir/builder/calls/unified_emitter.rs | 250 +++++++++++++++++++++++ 3 files changed, 269 insertions(+), 224 deletions(-) create mode 100644 src/mir/builder/calls/unified_emitter.rs diff --git a/src/mir/builder/calls/emit.rs b/src/mir/builder/calls/emit.rs index c1fa7f8f..1efd3fbb 100644 --- a/src/mir/builder/calls/emit.rs +++ b/src/mir/builder/calls/emit.rs @@ -10,183 +10,17 @@ use crate::mir::definitions::call_unified::Callee; use super::{CallTarget, call_unified}; impl MirBuilder { - /// Unified call emission - replaces all emit_*_call methods - /// ChatGPT5 Pro A++ design for complete call unification + /// Unified call emission - delegates to UnifiedCallEmitterBox + /// 箱理論: 統一Call発行ロジックを unified_emitter.rs に集約 pub fn emit_unified_call( &mut self, dst: Option, target: CallTarget, args: Vec, ) -> Result<(), String> { - // Debug: Check recursion depth - const MAX_EMIT_DEPTH: usize = 100; - self.recursion_depth += 1; - if self.recursion_depth > MAX_EMIT_DEPTH { - eprintln!("[FATAL] emit_unified_call recursion depth exceeded {}", MAX_EMIT_DEPTH); - eprintln!("[FATAL] Current depth: {}", self.recursion_depth); - eprintln!("[FATAL] Target: {:?}", target); - return Err(format!("emit_unified_call recursion depth exceeded: {}", self.recursion_depth)); - } - - // Check environment variable for unified call usage - let result = if !call_unified::is_unified_call_enabled() { - // Fall back to legacy implementation - self.emit_legacy_call(dst, target, args) - } else { - self.emit_unified_call_impl(dst, target, args) - }; - self.recursion_depth -= 1; - result + super::unified_emitter::UnifiedCallEmitterBox::emit_unified_call(self, dst, target, args) } - fn emit_unified_call_impl( - &mut self, - dst: Option, - target: CallTarget, - args: Vec, - ) -> Result<(), String> { - - // Emit resolve.try for method targets (dev-only; default OFF) - let arity_for_try = args.len(); - if let CallTarget::Method { ref box_type, ref method, receiver } = target { - let recv_cls = box_type.clone() - .or_else(|| self.value_origin_newbox.get(&receiver).cloned()) - .unwrap_or_default(); - // Use indexed candidate lookup (tail → names) - let candidates: Vec = self.method_candidates(method, arity_for_try); - let meta = serde_json::json!({ - "recv_cls": recv_cls, - "method": method, - "arity": arity_for_try, - "candidates": candidates, - }); - super::super::observe::resolve::emit_try(self, meta); - } - - // Centralized user-box rewrite for method targets (toString/stringify, equals/1, Known→unique) - if let CallTarget::Method { ref box_type, ref method, receiver } = target { - let class_name_opt = box_type.clone() - .or_else(|| self.value_origin_newbox.get(&receiver).cloned()) - .or_else(|| self.value_types.get(&receiver).and_then(|t| if let super::super::MirType::Box(b) = t { Some(b.clone()) } else { None })); - // Early str-like - if let Some(res) = crate::mir::builder::rewrite::special::try_early_str_like_to_dst( - self, dst, receiver, &class_name_opt, method, args.len(), - ) { res?; return Ok(()); } - // equals/1 - if let Some(res) = crate::mir::builder::rewrite::special::try_special_equals_to_dst( - self, dst, receiver, &class_name_opt, method, args.clone(), - ) { res?; return Ok(()); } - // Known or unique - if let Some(res) = crate::mir::builder::rewrite::known::try_known_or_unique_to_dst( - self, dst, receiver, &class_name_opt, method, args.clone(), - ) { res?; return Ok(()); } - } - - // Convert CallTarget to Callee using CalleeResolverBox - if let CallTarget::Global(ref _n) = target { /* dev trace removed */ } - // Fallback: if Global target is unknown, try unique static-method mapping (name/arity) - let resolver = super::resolver::CalleeResolverBox::new( - &self.value_origin_newbox, - &self.value_types, - Some(&self.type_registry), // 🎯 TypeRegistry を渡す - ); - let mut callee = match resolver.resolve(target.clone()) { - Ok(c) => c, - Err(e) => { - if let CallTarget::Global(ref name) = target { - // Try fallback handlers - if let Some(result) = self.try_global_fallback_handlers(dst, name, &args)? { - return Ok(result); - } - } - return Err(e); - } - }; - - // Safety: ensure receiver is materialized even after callee conversion - callee = self.materialize_receiver_in_callee(callee)?; - - // Structural guard: prevent static compiler boxes from being called with runtime receivers - // 箱理論: CalleeGuardBox による構造的分離 - let guard = super::guard::CalleeGuardBox::new(&self.value_types); - callee = guard.apply_static_runtime_guard(callee)?; - - // Emit resolve.choose for method callee (dev-only; default OFF) - if let Callee::Method { box_name, method, certainty, .. } = &callee { - let chosen = format!("{}.{}{}", box_name, method, format!("/{}", arity_for_try)); - let meta = serde_json::json!({ - "recv_cls": box_name, - "method": method, - "arity": arity_for_try, - "chosen": chosen, - "certainty": format!("{:?}", certainty), - "reason": "unified", - }); - super::super::observe::resolve::emit_choose(self, meta); - } - - // Validate call arguments - // 箱理論: CalleeResolverBox で引数検証 - let resolver = super::resolver::CalleeResolverBox::new( - &self.value_origin_newbox, - &self.value_types, - Some(&self.type_registry), - ); - resolver.validate_args(&callee, &args)?; - - // Stability guard: decide route via RouterPolicyBox (behavior-preserving rules) - if let Callee::Method { box_name, method, receiver: Some(r), certainty, .. } = &callee { - let route = crate::mir::builder::router::policy::choose_route(box_name, method, *certainty, arity_for_try); - if let crate::mir::builder::router::policy::Route::BoxCall = route { - if super::super::utils::builder_debug_enabled() || std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") { - eprintln!("[router-guard] {}.{} → BoxCall fallback (recv=%{})", box_name, method, r.0); - } - let effects = EffectMask::READ.add(Effect::ReadHeap); - // Prevent BoxCall helper from bouncing back into emit_unified_call - // for the same call. RouterPolicyBox has already decided on - // Route::BoxCall for this callee, so emit_box_or_plugin_call - // must not re-enter the unified path even if its own heuristics - // would otherwise choose Unified. - let prev_flag = self.in_unified_boxcall_fallback; - self.in_unified_boxcall_fallback = true; - let res = self.emit_box_or_plugin_call(dst, *r, method.clone(), None, args, effects); - self.in_unified_boxcall_fallback = prev_flag; - return res; - } - } - - // Finalize operands in current block (EmitGuardBox wrapper) - let mut callee = callee; - let mut args_local: Vec = args; - crate::mir::builder::emit_guard::finalize_call_operands(self, &mut callee, &mut args_local); - - // Create MirCall instruction using the new module (pure data composition) - let mir_call = call_unified::create_mir_call(dst, callee.clone(), args_local.clone()); - - // Dev trace: show final callee/recv right before emission (guarded) - if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") || super::super::utils::builder_debug_enabled() { - if let Callee::Method { method, receiver, box_name, .. } = &callee { - if let Some(r) = receiver { - eprintln!("[vm-call-final] bb={:?} method={} recv=%{} class={}", - self.current_block, method, r.0, box_name); - } - } - } - - // For Phase 2: Convert to legacy Call instruction with new callee field (use finalized operands) - let legacy_call = MirInstruction::Call { - dst: mir_call.dst, - func: ValueId::new(0), // Dummy value for legacy compatibility - callee: Some(callee), - args: args_local, - effects: mir_call.effects, - }; - - let res = self.emit_instruction(legacy_call); - // Dev-only: verify block schedule invariants after emitting call - crate::mir::builder::emit_guard::verify_after_call(self); - res - } /// Legacy call fallback - preserves existing behavior pub fn emit_legacy_call( @@ -237,10 +71,10 @@ impl MirBuilder { }) }, CallTarget::Global(name) => { - self.emit_global_unified(dst, name, args) + super::unified_emitter::UnifiedCallEmitterBox::emit_global_unified(self, dst, name, args) }, CallTarget::Value(func_val) => { - self.emit_value_unified(dst, func_val, args) + super::unified_emitter::UnifiedCallEmitterBox::emit_value_unified(self, dst, func_val, args) }, CallTarget::Closure { params, captures, me_capture } => { let dst = dst.ok_or("Closure creation must have destination")?; @@ -305,7 +139,7 @@ impl MirBuilder { // ======================================== /// Try fallback handlers for global functions - fn try_global_fallback_handlers( + pub(super) fn try_global_fallback_handlers( &mut self, dst: Option, name: &str, @@ -374,8 +208,8 @@ impl MirBuilder { Ok(None) } - /// Ensure receiver is materialized in Callee::Method - fn materialize_receiver_in_callee( + /// Ensure receiver is materialized in Callee::Method (pub for UnifiedCallEmitterBox) + pub(super) fn materialize_receiver_in_callee( &mut self, callee: Callee, ) -> Result { @@ -419,49 +253,9 @@ impl MirBuilder { } } - /// Emit global call with name constant - fn emit_global_unified( - &mut self, - dst: Option, - name: String, - args: Vec, - ) -> Result<(), String> { - // Create a string constant for the function name via NameConstBox - let name_const = crate::mir::builder::name_const::make_name_const_result(self, &name)?; - // Allocate a destination if not provided so we can annotate it - let actual_dst = if let Some(d) = dst { d } else { self.next_value_id() }; - let mut args = args; - crate::mir::builder::ssa::local::finalize_args(self, &mut args); - self.emit_instruction(MirInstruction::Call { - dst: Some(actual_dst), - func: name_const, - callee: Some(Callee::Global(name.clone())), - args, - effects: EffectMask::IO, - })?; - // Annotate from module signature (if present) - self.annotate_call_result_from_func_name(actual_dst, name); - Ok(()) - } - - /// Emit value call (first-class function) - fn emit_value_unified( - &mut self, - dst: Option, - func_val: ValueId, - args: Vec, - ) -> Result<(), String> { - let mut args = args; - crate::mir::builder::ssa::local::finalize_args(self, &mut args); - self.emit_instruction(MirInstruction::Call { - dst, - func: func_val, - callee: Some(Callee::Value(func_val)), - args, - effects: EffectMask::IO, - }) - } - - // ✅ 箱化完了: apply_static_runtime_guard → CalleeGuardBox::apply_static_runtime_guard - // 構造ガードロジックは src/mir/builder/calls/guard.rs に移動済み + // ✅ 箱化完了: + // - emit_unified_call_impl → UnifiedCallEmitterBox::emit_unified_call_impl (unified_emitter.rs) + // - emit_global_unified → UnifiedCallEmitterBox::emit_global_unified (unified_emitter.rs) + // - emit_value_unified → UnifiedCallEmitterBox::emit_value_unified (unified_emitter.rs) + // - apply_static_runtime_guard → CalleeGuardBox::apply_static_runtime_guard (guard.rs) } diff --git a/src/mir/builder/calls/mod.rs b/src/mir/builder/calls/mod.rs index 128d34b6..c5e8157f 100644 --- a/src/mir/builder/calls/mod.rs +++ b/src/mir/builder/calls/mod.rs @@ -16,13 +16,14 @@ pub mod function_lowering; pub mod method_resolution; pub mod special_handlers; -// New refactored modules (Box Theory Phase 1 & 2 & 25.1d) +// 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 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発行専用箱) // 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 new file mode 100644 index 00000000..1e28dc96 --- /dev/null +++ b/src/mir/builder/calls/unified_emitter.rs @@ -0,0 +1,250 @@ +/*! + * UnifiedCallEmitterBox - 統一Call発行専用箱 + * + * 箱理論の実践: + * - 箱にする: 統一Call発行ロジックを1箱に集約 + * - 境界を作る: Legacy/Unifiedの明確な分離 + * - 状態最小: MirBuilderを引数として受け取る(所有しない) + * + * 責務: + * - emit_unified_call: 統一Call発行の公開API + * - emit_unified_call_impl: コア実装(CallTarget → MirCall変換) + * - emit_global_unified: Global関数呼び出し + * - emit_value_unified: 第一級関数呼び出し + */ + +use crate::mir::builder::{MirBuilder, ValueId, MirInstruction, Effect, EffectMask}; +use crate::mir::definitions::call_unified::Callee; +use super::CallTarget; +use super::call_unified; + +/// 統一Call発行専用箱 +/// +/// 箱理論: +/// - 単一責務: 統一Call発行のみ(Legacy Callは別モジュール) +/// - 状態レス: MirBuilderを引数で受け取る設計 +/// - ピュア関数的: 入力CallTarget → 解決・発行 → MirCall命令 +pub struct UnifiedCallEmitterBox; + +impl UnifiedCallEmitterBox { + /// Unified call emission - replaces all emit_*_call methods + /// ChatGPT5 Pro A++ design for complete call unification + pub fn emit_unified_call( + builder: &mut MirBuilder, + dst: Option, + target: CallTarget, + args: Vec, + ) -> Result<(), String> { + // Debug: Check recursion depth + const MAX_EMIT_DEPTH: usize = 100; + builder.recursion_depth += 1; + if builder.recursion_depth > MAX_EMIT_DEPTH { + eprintln!("[FATAL] emit_unified_call recursion depth exceeded {}", MAX_EMIT_DEPTH); + eprintln!("[FATAL] Current depth: {}", builder.recursion_depth); + eprintln!("[FATAL] Target: {:?}", target); + return Err(format!("emit_unified_call recursion depth exceeded: {}", builder.recursion_depth)); + } + + // Check environment variable for unified call usage + let result = if !call_unified::is_unified_call_enabled() { + // Fall back to legacy implementation + builder.emit_legacy_call(dst, target, args) + } else { + Self::emit_unified_call_impl(builder, dst, target, args) + }; + builder.recursion_depth -= 1; + result + } + + fn emit_unified_call_impl( + builder: &mut MirBuilder, + dst: Option, + target: CallTarget, + args: Vec, + ) -> Result<(), String> { + + // Emit resolve.try for method targets (dev-only; default OFF) + let arity_for_try = args.len(); + if let CallTarget::Method { ref box_type, ref method, receiver } = target { + let recv_cls = box_type.clone() + .or_else(|| builder.value_origin_newbox.get(&receiver).cloned()) + .unwrap_or_default(); + // Use indexed candidate lookup (tail → names) + let candidates: Vec = builder.method_candidates(method, arity_for_try); + let meta = serde_json::json!({ + "recv_cls": recv_cls, + "method": method, + "arity": arity_for_try, + "candidates": candidates, + }); + crate::mir::builder::observe::resolve::emit_try(builder, meta); + } + + // Centralized user-box rewrite for method targets (toString/stringify, equals/1, Known→unique) + if let CallTarget::Method { ref box_type, ref method, receiver } = target { + let class_name_opt = box_type.clone() + .or_else(|| builder.value_origin_newbox.get(&receiver).cloned()) + .or_else(|| builder.value_types.get(&receiver).and_then(|t| if let crate::mir::MirType::Box(b) = t { Some(b.clone()) } else { None })); + // Early str-like + if let Some(res) = crate::mir::builder::rewrite::special::try_early_str_like_to_dst( + builder, dst, receiver, &class_name_opt, method, args.len(), + ) { res?; return Ok(()); } + // equals/1 + if let Some(res) = crate::mir::builder::rewrite::special::try_special_equals_to_dst( + builder, dst, receiver, &class_name_opt, method, args.clone(), + ) { res?; return Ok(()); } + // Known or unique + if let Some(res) = crate::mir::builder::rewrite::known::try_known_or_unique_to_dst( + builder, dst, receiver, &class_name_opt, method, args.clone(), + ) { res?; return Ok(()); } + } + + // Convert CallTarget to Callee using CalleeResolverBox + if let CallTarget::Global(ref _n) = target { /* dev trace removed */ } + // Fallback: if Global target is unknown, try unique static-method mapping (name/arity) + let resolver = super::resolver::CalleeResolverBox::new( + &builder.value_origin_newbox, + &builder.value_types, + Some(&builder.type_registry), // 🎯 TypeRegistry を渡す + ); + let mut callee = match resolver.resolve(target.clone()) { + 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)? { + return Ok(result); + } + } + return Err(e); + } + }; + + // Safety: ensure receiver is materialized even after callee conversion + callee = builder.materialize_receiver_in_callee(callee)?; + + // Structural guard: prevent static compiler boxes from being called with runtime receivers + // 箱理論: CalleeGuardBox による構造的分離 + let guard = super::guard::CalleeGuardBox::new(&builder.value_types); + callee = guard.apply_static_runtime_guard(callee)?; + + // Emit resolve.choose for method callee (dev-only; default OFF) + if let Callee::Method { box_name, method, certainty, .. } = &callee { + let chosen = format!("{}.{}{}", box_name, method, format!("/{}", arity_for_try)); + let meta = serde_json::json!({ + "recv_cls": box_name, + "method": method, + "arity": arity_for_try, + "chosen": chosen, + "certainty": format!("{:?}", certainty), + "reason": "unified", + }); + crate::mir::builder::observe::resolve::emit_choose(builder, meta); + } + + // Validate call arguments + // 箱理論: CalleeResolverBox で引数検証 + let resolver = super::resolver::CalleeResolverBox::new( + &builder.value_origin_newbox, + &builder.value_types, + Some(&builder.type_registry), + ); + resolver.validate_args(&callee, &args)?; + + // Stability guard: decide route via RouterPolicyBox (behavior-preserving rules) + if let Callee::Method { box_name, method, receiver: Some(r), certainty, .. } = &callee { + let route = crate::mir::builder::router::policy::choose_route(box_name, method, *certainty, arity_for_try); + if let crate::mir::builder::router::policy::Route::BoxCall = route { + if crate::mir::builder::utils::builder_debug_enabled() || std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") { + eprintln!("[router-guard] {}.{} → BoxCall fallback (recv=%{})", box_name, method, r.0); + } + let effects = EffectMask::READ.add(Effect::ReadHeap); + // Prevent BoxCall helper from bouncing back into emit_unified_call + // for the same call. RouterPolicyBox has already decided on + // Route::BoxCall for this callee, so emit_box_or_plugin_call + // must not re-enter the unified path even if its own heuristics + // would otherwise choose Unified. + let prev_flag = builder.in_unified_boxcall_fallback; + builder.in_unified_boxcall_fallback = true; + let res = builder.emit_box_or_plugin_call(dst, *r, method.clone(), None, args, effects); + builder.in_unified_boxcall_fallback = prev_flag; + return res; + } + } + + // Finalize operands in current block (EmitGuardBox wrapper) + let mut callee = callee; + let mut args_local: Vec = args; + crate::mir::builder::emit_guard::finalize_call_operands(builder, &mut callee, &mut args_local); + + // Create MirCall instruction using the new module (pure data composition) + let mir_call = call_unified::create_mir_call(dst, callee.clone(), args_local.clone()); + + // Dev trace: show final callee/recv right before emission (guarded) + if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") || crate::mir::builder::utils::builder_debug_enabled() { + if let Callee::Method { method, receiver, box_name, .. } = &callee { + if let Some(r) = receiver { + eprintln!("[vm-call-final] bb={:?} method={} recv=%{} class={}", + builder.current_block, method, r.0, box_name); + } + } + } + + // For Phase 2: Convert to legacy Call instruction with new callee field (use finalized operands) + let legacy_call = MirInstruction::Call { + dst: mir_call.dst, + func: ValueId::new(0), // Dummy value for legacy compatibility + callee: Some(callee), + args: args_local, + effects: mir_call.effects, + }; + + let res = builder.emit_instruction(legacy_call); + // Dev-only: verify block schedule invariants after emitting call + crate::mir::builder::emit_guard::verify_after_call(builder); + res + } + + /// Emit global call with name constant (public for legacy compatibility) + pub fn emit_global_unified( + builder: &mut MirBuilder, + dst: Option, + name: String, + args: Vec, + ) -> Result<(), String> { + // Create a string constant for the function name via NameConstBox + let name_const = crate::mir::builder::name_const::make_name_const_result(builder, &name)?; + // Allocate a destination if not provided so we can annotate it + let actual_dst = if let Some(d) = dst { d } else { builder.next_value_id() }; + let mut args = args; + crate::mir::builder::ssa::local::finalize_args(builder, &mut args); + builder.emit_instruction(MirInstruction::Call { + dst: Some(actual_dst), + func: name_const, + callee: Some(Callee::Global(name.clone())), + args, + effects: EffectMask::IO, + })?; + // Annotate from module signature (if present) + builder.annotate_call_result_from_func_name(actual_dst, name); + Ok(()) + } + + /// Emit value call (first-class function, public for legacy compatibility) + pub fn emit_value_unified( + builder: &mut MirBuilder, + dst: Option, + func_val: ValueId, + args: Vec, + ) -> Result<(), String> { + let mut args = args; + crate::mir::builder::ssa::local::finalize_args(builder, &mut args); + builder.emit_instruction(MirInstruction::Call { + dst, + func: func_val, + callee: Some(Callee::Value(func_val)), + args, + effects: EffectMask::IO, + }) + } +}