**FuncScanner .hako 側改善**: - scan_all_boxes を Region + next_i 形式に統一(continue 多発による SSA/PHI 複雑さ削減) - インデント修正(タブ→スペース統一) - デバッグ print 削除 **SSA テスト追加**: - lang/src/compiler/tests/funcscanner_scan_methods_min.hako - src/tests/mir_funcscanner_ssa.rs (scan_methods & fib_min SSA デバッグテスト) **Phase 25.3 ドキュメント**: - docs/development/roadmap/phases/phase-25.3-funcscanner/ 追加 **関連**: Phase 25.3 FuncScanner 箱化準備作業 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
259 lines
12 KiB
Rust
259 lines
12 KiB
Rust
/*!
|
||
* 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 (via CallMaterializerBox)
|
||
if let Some(result) = super::materializer::CallMaterializerBox::try_global_fallback_handlers(builder, dst, name, &args)? {
|
||
return Ok(result);
|
||
}
|
||
}
|
||
return Err(e);
|
||
}
|
||
};
|
||
|
||
// 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 による構造的分離
|
||
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);
|
||
|
||
// 📦 Hotfix 7: Include receiver in args for Callee::Method
|
||
// VM's exec_function_inner expects receiver as the first parameter (ValueId(0))
|
||
// but finalize_call_operands keeps receiver in Callee, not in args.
|
||
// We must add it to args_local here so VM can bind it correctly.
|
||
if let Callee::Method { receiver: Some(recv), .. } = &callee {
|
||
args_local.insert(0, *recv);
|
||
}
|
||
|
||
// 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::INVALID, // Dummy value for legacy compatibility (not a real SSA use)
|
||
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,
|
||
})
|
||
}
|
||
}
|