refactor(builder): Phase 3-A - UnifiedCallEmitterBox実装完了

箱理論の実践:統一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:  成功
- 既存機能互換性:  完全保持
This commit is contained in:
nyash-codex
2025-11-17 23:49:18 +09:00
parent 96a17c616d
commit 9a4f05adac
3 changed files with 269 additions and 224 deletions

View File

@ -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<ValueId>,
target: CallTarget,
args: Vec<ValueId>,
) -> 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<ValueId>,
target: CallTarget,
args: Vec<ValueId>,
) -> 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<String> = 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<ValueId> = 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<ValueId>,
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<Callee, String> {
@ -419,49 +253,9 @@ impl MirBuilder {
}
}
/// Emit global call with name constant
fn emit_global_unified(
&mut self,
dst: Option<ValueId>,
name: String,
args: Vec<ValueId>,
) -> 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<ValueId>,
func_val: ValueId,
args: Vec<ValueId>,
) -> 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)
}

View File

@ -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;

View File

@ -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<ValueId>,
target: CallTarget,
args: Vec<ValueId>,
) -> 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<ValueId>,
target: CallTarget,
args: Vec<ValueId>,
) -> 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<String> = 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<ValueId> = 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<ValueId>,
name: String,
args: Vec<ValueId>,
) -> 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<ValueId>,
func_val: ValueId,
args: Vec<ValueId>,
) -> 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,
})
}
}