fix(mir/builder): improve LocalSSA error handling and add Copy/Call traces
Changes: - src/mir/builder/ssa/local.rs: - Fix LocalSSA::ensure() to check emit_instruction() errors - Return original value instead of undefined ValueId on failure - Add NYASH_LOCAL_SSA_TRACE logging for failures - src/mir/builder.rs: - Add Copy instruction trace with NYASH_LOCAL_SSA_TRACE=1 - Add Call instruction trace for Method calls - Helps debug receiver materialization issues - src/mir/builder/builder_calls.rs: - Remove duplicate receiver materialization (moved to builder.rs) - Rely on emit_instruction for final receiver handling Related to Stage-B ValueId(21) undefined error investigation. Next: Fix Nyash MirBuilder receiver handling in basic_lower_box.hako 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -436,6 +436,9 @@ impl MirBuilder {
|
|||||||
pub(super) fn emit_instruction(&mut self, instruction: MirInstruction) -> Result<(), String> {
|
pub(super) fn emit_instruction(&mut self, instruction: MirInstruction) -> Result<(), String> {
|
||||||
let block_id = self.current_block.ok_or("No current basic block")?;
|
let block_id = self.current_block.ok_or("No current basic block")?;
|
||||||
|
|
||||||
|
// Make instruction mutable for potential receiver materialization
|
||||||
|
let mut instruction = instruction;
|
||||||
|
|
||||||
// Precompute debug metadata to avoid borrow conflicts later
|
// Precompute debug metadata to avoid borrow conflicts later
|
||||||
let dbg_fn_name = self
|
let dbg_fn_name = self
|
||||||
.current_function
|
.current_function
|
||||||
@ -448,6 +451,32 @@ impl MirBuilder {
|
|||||||
observe::ssa::emit_phi(self, *dst, inputs);
|
observe::ssa::emit_phi(self, *dst, inputs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CRITICAL: Final receiver materialization for MethodCall
|
||||||
|
// This ensures the receiver has an in-block definition in the same block as the Call.
|
||||||
|
// Must happen BEFORE function mutable borrow to avoid borrowck conflicts.
|
||||||
|
if let MirInstruction::Call { callee: Some(callee), dst, args, effects, .. } = &instruction {
|
||||||
|
use crate::mir::definitions::call_unified::Callee;
|
||||||
|
if let Callee::Method { box_name, method, receiver: Some(r), certainty } = callee.clone() {
|
||||||
|
// LocalSSA: ensure receiver has a Copy in current_block
|
||||||
|
let r_local = crate::mir::builder::ssa::local::recv(self, r);
|
||||||
|
|
||||||
|
// Update instruction with materialized receiver
|
||||||
|
let new_callee = Callee::Method {
|
||||||
|
box_name: box_name.clone(),
|
||||||
|
method: method.clone(),
|
||||||
|
receiver: Some(r_local),
|
||||||
|
certainty
|
||||||
|
};
|
||||||
|
instruction = MirInstruction::Call {
|
||||||
|
dst: *dst,
|
||||||
|
func: crate::mir::ValueId::new(0), // Legacy compatibility
|
||||||
|
callee: Some(new_callee),
|
||||||
|
args: args.clone(),
|
||||||
|
effects: *effects,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(ref mut function) = self.current_function {
|
if let Some(ref mut function) = self.current_function {
|
||||||
// Pre-capture branch/jump targets for predecessor update after we finish
|
// Pre-capture branch/jump targets for predecessor update after we finish
|
||||||
// mutably borrowing the current block.
|
// mutably borrowing the current block.
|
||||||
@ -461,10 +490,35 @@ impl MirBuilder {
|
|||||||
let current_fn_name = function.signature.name.clone();
|
let current_fn_name = function.signature.name.clone();
|
||||||
|
|
||||||
if let Some(block) = function.get_block_mut(block_id) {
|
if let Some(block) = function.get_block_mut(block_id) {
|
||||||
|
// CRITICAL: Copy専用トレース(LocalSSA調査用)
|
||||||
|
if let MirInstruction::Copy { dst, src } = &instruction {
|
||||||
|
if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") {
|
||||||
|
eprintln!(
|
||||||
|
"[emit-inst] fn={} bb={:?} COPY %{} <- %{}",
|
||||||
|
current_fn_name,
|
||||||
|
self.current_block.map(|b| b.0).unwrap_or(0),
|
||||||
|
dst.0,
|
||||||
|
src.0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Invariant: Call must always carry a Callee (unified path).
|
// Invariant: Call must always carry a Callee (unified path).
|
||||||
if let MirInstruction::Call { callee, .. } = &instruction {
|
if let MirInstruction::Call { callee, .. } = &instruction {
|
||||||
if callee.is_none() {
|
if callee.is_none() {
|
||||||
return Err("builder invariant violated: MirInstruction::Call.callee must be Some (unified call)".into());
|
return Err("builder invariant violated: MirInstruction::Call.callee must be Some (unified call)".into());
|
||||||
|
} else if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") {
|
||||||
|
use crate::mir::definitions::call_unified::Callee;
|
||||||
|
if let Some(Callee::Method { box_name, method, receiver: Some(r), .. }) = callee {
|
||||||
|
eprintln!(
|
||||||
|
"[emit-inst] fn={} bb={:?} Call {}.{} recv=%{}",
|
||||||
|
current_fn_name,
|
||||||
|
self.current_block.map(|b| b.0).unwrap_or(0),
|
||||||
|
box_name,
|
||||||
|
method,
|
||||||
|
r.0
|
||||||
|
);
|
||||||
|
}
|
||||||
} else if std::env::var("NYASH_BUILDER_TRACE_RECV").ok().as_deref() == Some("1") {
|
} else if std::env::var("NYASH_BUILDER_TRACE_RECV").ok().as_deref() == Some("1") {
|
||||||
use crate::mir::definitions::call_unified::Callee;
|
use crate::mir::definitions::call_unified::Callee;
|
||||||
if let Some(Callee::Method { box_name, method, receiver: Some(r), .. }) = callee {
|
if let Some(Callee::Method { box_name, method, receiver: Some(r), .. }) = callee {
|
||||||
|
|||||||
@ -224,15 +224,6 @@ 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)
|
// For Phase 2: Convert to legacy Call instruction with new callee field (use finalized operands)
|
||||||
let legacy_call = MirInstruction::Call {
|
let legacy_call = MirInstruction::Call {
|
||||||
dst: mir_call.dst,
|
dst: mir_call.dst,
|
||||||
|
|||||||
@ -42,7 +42,14 @@ pub fn ensure(builder: &mut MirBuilder, v: ValueId, kind: LocalKind) -> ValueId
|
|||||||
// Stage-B 経路などでは current_block が割り当て済みでも、ブロック自体が
|
// Stage-B 経路などでは current_block が割り当て済みでも、ブロック自体が
|
||||||
// function にまだ追加されていない場合があり、そのまま emit_instruction すると
|
// function にまだ追加されていない場合があり、そのまま emit_instruction すると
|
||||||
// Copy が黙って落ちてしまう。ここで best-effort で作成しておく。
|
// Copy が黙って落ちてしまう。ここで best-effort で作成しておく。
|
||||||
let _ = builder.ensure_block_exists(bb);
|
// CRITICAL: Check for errors - if block creation fails, return original value.
|
||||||
|
if let Err(e) = builder.ensure_block_exists(bb) {
|
||||||
|
if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") {
|
||||||
|
eprintln!("[local-ssa] ensure_block_exists FAILED bb={:?} kind={:?} v=%{} err={}",
|
||||||
|
bb, kind, v.0, e);
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
|
||||||
// CRITICAL FIX: If `v` is from a pinned slot, check if there's a PHI value for that slot
|
// 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
|
// in the current block's variable_map. If so, use the PHI value directly instead of
|
||||||
@ -69,11 +76,20 @@ pub fn ensure(builder: &mut MirBuilder, v: ValueId, kind: LocalKind) -> ValueId
|
|||||||
}
|
}
|
||||||
|
|
||||||
let loc = builder.next_value_id();
|
let loc = builder.next_value_id();
|
||||||
// Best-effort: errors are propagated by caller; we ignore here to keep helper infallible
|
// CRITICAL: Check emit_instruction result - if Copy fails, return original value
|
||||||
let _ = builder.emit_instruction(crate::mir::MirInstruction::Copy { dst: loc, src: v });
|
// to avoid returning undefined ValueId.
|
||||||
|
if let Err(e) = builder.emit_instruction(crate::mir::MirInstruction::Copy { dst: loc, src: v }) {
|
||||||
|
if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") {
|
||||||
|
eprintln!("[local-ssa] emit_instruction Copy FAILED bb={:?} kind={:?} v=%{} dst=%{} err={}",
|
||||||
|
bb, kind, v.0, loc.0, e);
|
||||||
|
}
|
||||||
|
// Failed to emit Copy - return original value instead of undefined dst
|
||||||
|
return v;
|
||||||
|
}
|
||||||
if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") {
|
if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") {
|
||||||
eprintln!("[local-ssa] copy bb={:?} kind={:?} %{} -> %{}", bb, kind, v.0, loc.0);
|
eprintln!("[local-ssa] copy bb={:?} kind={:?} %{} -> %{}", bb, kind, v.0, loc.0);
|
||||||
}
|
}
|
||||||
|
// Success: register metadata and cache
|
||||||
if let Some(t) = builder.value_types.get(&v).cloned() {
|
if let Some(t) = builder.value_types.get(&v).cloned() {
|
||||||
builder.value_types.insert(loc, t);
|
builder.value_types.insert(loc, t);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user