diff --git a/src/mir/builder.rs b/src/mir/builder.rs index 01e46c67..e1c341f0 100644 --- a/src/mir/builder.rs +++ b/src/mir/builder.rs @@ -41,6 +41,7 @@ mod observe; // P0: dev-only observability helpers(ssa/resolve) mod rewrite; // P1: Known rewrite & special consolidation mod ssa; // LocalSSA helpers (in-block materialization) mod schedule; // BlockScheduleBox(物理順序: PHI→materialize→body) +mod receiver; // ReceiverMaterializationBox(Method recv の pin+LocalSSA 集約) mod metadata; // MetadataPropagationBox(type/originの伝播) mod emission; // emission::*(Const/Compare/Branch の薄い発行箱) mod types; // types::annotation / inference(型注釈/推論の箱: 推論は後段) diff --git a/src/mir/builder/builder_calls.rs b/src/mir/builder/builder_calls.rs index d12930f8..d59fa5b0 100644 --- a/src/mir/builder/builder_calls.rs +++ b/src/mir/builder/builder_calls.rs @@ -28,45 +28,6 @@ impl super::MirBuilder { return self.emit_legacy_call(dst, target, args); } - // Ensure method receiver is materialized in the current block. - // This avoids "use of undefined recv" across block boundaries for direct Method calls - // that bypass legacy BoxCall emission. Do this before any observation/rewrite. - let mut target = target; - let bb_before = self.current_block; // snapshot for later re-check - if let CallTarget::Method { box_type, method, receiver } = target { - if super::utils::builder_debug_enabled() && method == "advance" { - super::utils::builder_debug_log(&format!("unified-entry Method.advance recv=%{} (pre-pin)", receiver.0)); - } - let receiver_pinned = match self.pin_to_slot(receiver, "@recv") { - Ok(v) => v, - Err(_) => receiver, - }; - 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 == receiver) - .map(|(k, _)| k.clone()) - .collect(); - eprintln!( - "[builder/recv-trace] fn={} bb={:?} method={}.{} recv=%{} aliases={:?}", - current_fn, - bb, - box_type.clone().unwrap_or_else(|| "".to_string()), - method, - receiver.0, - names - ); - } - target = CallTarget::Method { box_type, method, receiver: receiver_pinned }; - } - // 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 { @@ -216,29 +177,6 @@ impl super::MirBuilder { other => other, }; - // Final guard: if current block changed since entry, ensure receiver is copied again - if let (Some(bb0), Some(bb1)) = (bb_before, self.current_block) { - if bb0 != bb1 { - if let Callee::Method { box_name, method, receiver: Some(r), certainty } = callee { - if super::utils::builder_debug_enabled() { - super::utils::builder_debug_log(&format!( - "unified-call bb changed: {:?} -> {:?}; re-materialize recv=%{}", - bb0, bb1, r.0 - )); - } - let r2 = self.pin_to_slot(r, "@recv").unwrap_or(r); - callee = Callee::Method { box_name, method, receiver: Some(r2), certainty }; - } - } - } - - // Debug: trace unified method emission with pinned receiver (dev only) - if super::utils::builder_debug_enabled() { - if let Callee::Method { method, receiver: Some(r), .. } = &callee { - super::utils::builder_debug_log(&format!("unified-call method={} recv=%{} (pinned)", method, r.0)); - } - } - // 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)); @@ -268,18 +206,6 @@ impl super::MirBuilder { } } - // Before creating the call, enforce slot (pin) + LocalSSA for Method receiver in the current block - let callee = match callee { - Callee::Method { box_name, method, receiver: Some(r), certainty } => { - // Pin to a named slot so start_new_block can propagate across entries - let r_pinned = self.pin_to_slot(r, "@recv").unwrap_or(r); - // And ensure in-block materialization for this emission site - let r_local = self.local_recv(r_pinned); - Callee::Method { box_name, method, receiver: Some(r_local), certainty } - } - other => other, - }; - // Finalize operands in current block (EmitGuardBox wrapper) let mut callee = callee; let mut args_local: Vec = args; @@ -298,6 +224,14 @@ impl super::MirBuilder { } } + // Safety: just before emitting the Call, re-materialize Method receiver in *current* block. + // This ensures the receiver has a definition in the same block as the Call instruction, + // even if the block changed between finalize_call_operands() and here. + // Critical for Stage-B and complex control flow where finalize and emit may be in different blocks. + if let Callee::Method { box_name, method, receiver: Some(r), certainty } = callee { + let r_local = crate::mir::builder::ssa::local::recv(self, r); + callee = Callee::Method { box_name, method, receiver: Some(r_local), certainty }; + } // For Phase 2: Convert to legacy Call instruction with new callee field (use finalized operands) let legacy_call = MirInstruction::Call { diff --git a/src/mir/builder/emit_guard/mod.rs b/src/mir/builder/emit_guard/mod.rs index aaa099a5..5ed3a33e 100644 --- a/src/mir/builder/emit_guard/mod.rs +++ b/src/mir/builder/emit_guard/mod.rs @@ -4,10 +4,13 @@ use crate::mir::ValueId; /// Finalize call operands (receiver/args) using LocalSSA; thin wrapper to centralize usage. pub fn finalize_call_operands(builder: &mut MirBuilder, callee: &mut Callee, args: &mut Vec) { - // Step 1: LocalSSA materialization for receiver/args - crate::mir::builder::ssa::local::finalize_callee_and_args(builder, callee, args); + // Step 1: Receiver materialization (pin slot + LocalSSA) in a dedicated box + crate::mir::builder::receiver::finalize_method_receiver(builder, callee); - // Step 2: Disabled - BlockScheduleBox insert-after-phis doesn't work correctly + // Step 2: LocalSSA materialization for args only + crate::mir::builder::ssa::local::finalize_args(builder, args); + + // Step 3: Disabled - BlockScheduleBox insert-after-phis doesn't work correctly // The Copy instructions are being inserted but then lost when blocks are finalized. // Instead, rely solely on LocalSSA which uses emit_instruction (the normal path). // diff --git a/src/mir/builder/receiver.rs b/src/mir/builder/receiver.rs new file mode 100644 index 00000000..1a7328b8 --- /dev/null +++ b/src/mir/builder/receiver.rs @@ -0,0 +1,46 @@ +use crate::mir::builder::MirBuilder; +use crate::mir::definitions::call_unified::Callee; + +/// ReceiverMaterializationBox – centralizes Method receiver pinning + LocalSSA materialization. +/// +/// Contract: +/// - If callee is a Method and has a receiver: +/// - Pin the receiver into a named slot (`__pin$*@recv`) so it participates in PHI/loop merges. +/// - Ensure the receiver has an in-block definition via LocalSSA (Copy in the current block). +/// - Args の LocalSSA は別レイヤ(ssa::local)で扱う。 +pub fn finalize_method_receiver(builder: &mut MirBuilder, callee: &mut Callee) { + if let Callee::Method { box_name, method, receiver: Some(r), certainty } = callee.clone() { + // Pin to a named slot so start_new_block や LoopBuilder が slot 経由で追跡できる + let r_pinned = builder.pin_to_slot(r, "@recv").unwrap_or(r); + + // Optional dev trace for receiver aliases + 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(); + eprintln!( + "[builder/recv-trace] fn={} bb={:?} method={}.{} recv=%{} aliases={:?}", + current_fn, + bb, + box_name.clone(), + method, + r.0, + names + ); + } + + // LocalSSA: ensure an in-block definition in the current block + let r_local = crate::mir::builder::ssa::local::recv(builder, r_pinned); + *callee = Callee::Method { box_name, method, receiver: Some(r_local), certainty }; + } +} + diff --git a/src/mir/builder/ssa/local.rs b/src/mir/builder/ssa/local.rs index 28392e82..615cd40b 100644 --- a/src/mir/builder/ssa/local.rs +++ b/src/mir/builder/ssa/local.rs @@ -1,5 +1,5 @@ use crate::mir::builder::MirBuilder; -use crate::mir::{ValueId, Callee}; +use crate::mir::ValueId; #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub enum LocalKind { @@ -38,6 +38,12 @@ pub fn ensure(builder: &mut MirBuilder, v: ValueId, kind: LocalKind) -> ValueId return loc; } + // Ensure the current basic block exists in the function before emitting a Copy. + // Stage-B 経路などでは current_block が割り当て済みでも、ブロック自体が + // function にまだ追加されていない場合があり、そのまま emit_instruction すると + // Copy が黙って落ちてしまう。ここで best-effort で作成しておく。 + let _ = builder.ensure_block_exists(bb); + // CRITICAL FIX: If `v` is from a pinned slot, check if there's a PHI value for that slot // in the current block's variable_map. If so, use the PHI value directly instead of // emitting a Copy from the old value (which might not be defined in this block). @@ -96,19 +102,6 @@ pub fn field_base(builder: &mut MirBuilder, v: ValueId) -> ValueId { ensure(buil #[inline] pub fn cmp_operand(builder: &mut MirBuilder, v: ValueId) -> ValueId { ensure(builder, v, LocalKind::CompareOperand) } -/// Finalize a callee+args just before emitting a Call instruction: -/// - If Method: ensure receiver is in the current block -/// - All args: ensure in the current block -pub fn finalize_callee_and_args(builder: &mut MirBuilder, callee: &mut Callee, args: &mut Vec) { - if let Callee::Method { receiver: Some(r), box_name, method, certainty } = callee.clone() { - let r_local = recv(builder, r); - *callee = Callee::Method { box_name, method, receiver: Some(r_local), certainty }; - } - for a in args.iter_mut() { - *a = arg(builder, *a); - } -} - /// Finalize only the args (legacy Call paths) pub fn finalize_args(builder: &mut MirBuilder, args: &mut Vec) { for a in args.iter_mut() {