Phase 25.1b: VM undefined-value diagnostics and builder SSA helpers
This commit is contained in:
@ -29,8 +29,9 @@ impl MirInterpreter {
|
||||
.map(|k| format!("{:?}", k))
|
||||
.collect();
|
||||
eprintln!(
|
||||
"[vm-trace] reg_load undefined id={:?} last_block={:?} last_inst={:?} regs={}",
|
||||
"[vm-trace] reg_load undefined id={:?} fn={} last_block={:?} last_inst={:?} regs={}",
|
||||
id,
|
||||
self.cur_fn.as_deref().unwrap_or("<unknown>"),
|
||||
self.last_block,
|
||||
self.last_inst,
|
||||
keys.join(", ")
|
||||
@ -43,9 +44,13 @@ impl MirInterpreter {
|
||||
if tolerate {
|
||||
return Ok(VMValue::Void);
|
||||
}
|
||||
let fn_name = self.cur_fn.as_deref().unwrap_or("<unknown>");
|
||||
Err(VMError::InvalidValue(format!(
|
||||
"use of undefined value {:?}",
|
||||
id
|
||||
"use of undefined value {:?} (fn={}, last_block={:?}, last_inst={:?})",
|
||||
id,
|
||||
fn_name,
|
||||
self.last_block,
|
||||
self.last_inst,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -215,6 +215,12 @@ impl BasicBlock {
|
||||
/// Insert instruction at the beginning (after phi instructions)
|
||||
pub fn insert_instruction_after_phis(&mut self, instruction: MirInstruction) {
|
||||
let phi_count = self.phi_instructions().count();
|
||||
if std::env::var("NYASH_SCHEDULE_TRACE").ok().as_deref() == Some("1") {
|
||||
if let MirInstruction::Copy { dst, src } = &instruction {
|
||||
eprintln!("[insert-after-phis] bb={:?} phi_count={} inserting Copy dst=%{} src=%{} total_inst={}",
|
||||
self.id, phi_count, dst.0, src.0, self.instructions.len());
|
||||
}
|
||||
}
|
||||
self.effects = self.effects | instruction.effects();
|
||||
self.instructions.insert(phi_count, instruction);
|
||||
}
|
||||
|
||||
@ -451,11 +451,33 @@ impl MirBuilder {
|
||||
_ => (None, None, None),
|
||||
};
|
||||
|
||||
// Extract function name before mutable borrow to avoid borrowck error
|
||||
let current_fn_name = function.signature.name.clone();
|
||||
|
||||
if let Some(block) = function.get_block_mut(block_id) {
|
||||
// Invariant: Call must always carry a Callee (unified path).
|
||||
if let MirInstruction::Call { callee, .. } = &instruction {
|
||||
if callee.is_none() {
|
||||
return Err("builder invariant violated: MirInstruction::Call.callee must be Some (unified call)".into());
|
||||
} else if std::env::var("NYASH_BUILDER_TRACE_RECV").ok().as_deref() == Some("1") {
|
||||
use crate::mir::definitions::call_unified::Callee;
|
||||
if let Some(Callee::Method { box_name, method, receiver: Some(r), .. }) = callee {
|
||||
let names: Vec<String> = self
|
||||
.variable_map
|
||||
.iter()
|
||||
.filter(|(_, &vid)| vid == *r)
|
||||
.map(|(k, _)| k.clone())
|
||||
.collect();
|
||||
eprintln!(
|
||||
"[builder/recv-trace] fn={} bb={:?} method={}.{} recv=%{} aliases={:?}",
|
||||
current_fn_name,
|
||||
self.current_block,
|
||||
box_name,
|
||||
method,
|
||||
r.0,
|
||||
names
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if utils::builder_debug_enabled() {
|
||||
|
||||
@ -41,6 +41,29 @@ impl super::MirBuilder {
|
||||
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(|| "<none>".to_string());
|
||||
let bb = self.current_block;
|
||||
let names: Vec<String> = 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 };
|
||||
}
|
||||
|
||||
@ -163,6 +186,29 @@ impl super::MirBuilder {
|
||||
// (covers rare paths where earlier pin did not take effect)
|
||||
callee = match callee {
|
||||
Callee::Method { box_name, method, receiver: Some(r), certainty } => {
|
||||
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(|| "<none>".to_string());
|
||||
let bb = self.current_block;
|
||||
let names: Vec<String> = self
|
||||
.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
|
||||
);
|
||||
}
|
||||
// Prefer pinning to a slot so start_new_block can propagate it across entries.
|
||||
let r_pinned = self.pin_to_slot(r, "@recv").unwrap_or(r);
|
||||
Callee::Method { box_name, method, receiver: Some(r_pinned), certainty }
|
||||
|
||||
@ -7,11 +7,15 @@ pub fn finalize_call_operands(builder: &mut MirBuilder, callee: &mut Callee, arg
|
||||
// Step 1: LocalSSA materialization for receiver/args
|
||||
crate::mir::builder::ssa::local::finalize_callee_and_args(builder, callee, args);
|
||||
|
||||
// Step 2: Stabilize receiver placement within the current block
|
||||
// Ensure a Copy right after PHIs and a tail Copy just before Call emission, so that
|
||||
// dominance/order invariants are satisfied without duplicating logic at call sites.
|
||||
if let Callee::Method { box_name, method, receiver: Some(r0), certainty } = callee.clone() {
|
||||
if let Ok(r_after) = crate::mir::builder::schedule::block::BlockScheduleBox::ensure_after_phis_copy(builder, r0) {
|
||||
// Step 2: 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).
|
||||
//
|
||||
// TODO: Fix BlockScheduleBox or remove it entirely if LocalSSA is sufficient.
|
||||
|
||||
/* DISABLED - causes ValueId(22) undefined error
|
||||
if let Callee::Method { box_name, method, receiver: Some(r_local), certainty } = callee.clone() {
|
||||
if let Ok(r_after) = crate::mir::builder::schedule::block::BlockScheduleBox::ensure_after_phis_copy(builder, r_local) {
|
||||
if let Ok(r_tail) = crate::mir::builder::schedule::block::BlockScheduleBox::emit_before_call_copy(builder, r_after) {
|
||||
*callee = Callee::Method { box_name, method, receiver: Some(r_tail), certainty };
|
||||
} else {
|
||||
@ -19,6 +23,7 @@ pub fn finalize_call_operands(builder: &mut MirBuilder, callee: &mut Callee, arg
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
/// Verify block schedule invariants after emitting a call (dev-only WARNs inside).
|
||||
|
||||
@ -10,9 +10,16 @@ impl BlockScheduleBox {
|
||||
pub fn ensure_after_phis_copy(builder: &mut MirBuilder, src: ValueId) -> Result<ValueId, String> {
|
||||
if let Some(bb) = builder.current_block {
|
||||
if let Some(&cached) = builder.schedule_mat_map.get(&(bb, src)) {
|
||||
if std::env::var("NYASH_SCHEDULE_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[schedule/after-phis] bb={:?} src=%{} cached dst=%{}", bb, src.0, cached.0);
|
||||
}
|
||||
return Ok(cached);
|
||||
}
|
||||
let dst = builder.next_value_id();
|
||||
if std::env::var("NYASH_SCHEDULE_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[schedule/after-phis] bb={:?} src=%{} new dst=%{} (inserting Copy) builder.current_block={:?}",
|
||||
bb, src.0, dst.0, builder.current_block);
|
||||
}
|
||||
builder.insert_copy_after_phis(dst, src)?;
|
||||
builder.schedule_mat_map.insert((bb, src), dst);
|
||||
return Ok(dst);
|
||||
@ -26,6 +33,10 @@ impl BlockScheduleBox {
|
||||
// Prefer to reuse the after-phis materialized id for this src in this block
|
||||
let base = Self::ensure_after_phis_copy(builder, src)?;
|
||||
let dst = builder.next_value_id();
|
||||
if std::env::var("NYASH_SCHEDULE_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[schedule/before-call] bb={:?} src=%{} base=%{} dst=%{} (emitting Copy)",
|
||||
builder.current_block, src.0, base.0, dst.0);
|
||||
}
|
||||
builder.emit_instruction(MirInstruction::Copy { dst, src: base })?;
|
||||
// Propagate metadata to keep dst consistent with base
|
||||
crate::mir::builder::metadata::propagate::propagate(builder, base, dst);
|
||||
|
||||
@ -37,6 +37,31 @@ pub fn ensure(builder: &mut MirBuilder, v: ValueId, kind: LocalKind) -> ValueId
|
||||
if let Some(&loc) = builder.local_ssa_map.get(&key) {
|
||||
return loc;
|
||||
}
|
||||
|
||||
// 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).
|
||||
let names_for_v: Vec<String> = builder.variable_map.iter()
|
||||
.filter(|(k, &vid)| vid == v && k.starts_with("__pin$"))
|
||||
.map(|(k, _)| k.clone())
|
||||
.collect();
|
||||
|
||||
if let Some(first_pin_name) = names_for_v.first() {
|
||||
// This value is from a pinned slot. Check if the slot has been updated
|
||||
// (e.g., by a PHI) in the current block.
|
||||
if let Some(¤t_val) = builder.variable_map.get(first_pin_name) {
|
||||
if current_val != v {
|
||||
// The slot has been updated (likely by a PHI). Use the updated value.
|
||||
if std::env::var("NYASH_LOCAL_SSA_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[local-ssa] phi-redirect bb={:?} kind={:?} slot={} %{} -> %{}",
|
||||
bb, kind, first_pin_name, v.0, current_val.0);
|
||||
}
|
||||
builder.local_ssa_map.insert(key, current_val);
|
||||
return current_val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let loc = builder.next_value_id();
|
||||
// Best-effort: errors are propagated by caller; we ignore here to keep helper infallible
|
||||
let _ = builder.emit_instruction(crate::mir::MirInstruction::Copy { dst: loc, src: v });
|
||||
|
||||
@ -76,9 +76,22 @@ impl super::MirBuilder {
|
||||
self.local_ssa_map.clear();
|
||||
// BlockSchedule materialize cache is per-block as well
|
||||
self.schedule_mat_map.clear();
|
||||
// Entry materialization for pinned slots only when not suppressed.
|
||||
// This provides block-local defs in single-predecessor flows without touching user vars.
|
||||
// Entry materialization for pinned slots: re-read from variable_map after PHIs are emitted.
|
||||
// This ensures pinned slots reflect the correct PHI values in merge blocks.
|
||||
//
|
||||
// Strategy: Instead of emitting Copy instructions (which would be before PHIs),
|
||||
// we simply update the variable_map to point to the current block's values.
|
||||
// LoopBuilder and IfBuilder already update variable_map with PHI values, so
|
||||
// pinned slots will automatically pick up the correct values.
|
||||
//
|
||||
// No action needed here - just clear caches.
|
||||
if !self.suppress_pin_entry_copy_next {
|
||||
// Cache clearing is already done above, so nothing more to do here.
|
||||
// The key insight: pinned slot variables are part of variable_map,
|
||||
// and LoopBuilder/IfBuilder already manage PHIs for ALL variables in variable_map,
|
||||
// including pinned slots.
|
||||
}
|
||||
if false && !self.suppress_pin_entry_copy_next { // Keep old code for reference
|
||||
// First pass: copy all pin slots and remember old->new mapping
|
||||
let names: Vec<String> = self.variable_map.keys().cloned().collect();
|
||||
let mut pin_renames: Vec<(super::ValueId, super::ValueId)> = Vec::new();
|
||||
@ -328,12 +341,25 @@ impl super::MirBuilder {
|
||||
/// Insert a Copy immediately after PHI nodes in the current block (position-stable).
|
||||
pub(crate) fn insert_copy_after_phis(&mut self, dst: super::ValueId, src: super::ValueId) -> Result<(), String> {
|
||||
if let (Some(ref mut function), Some(bb)) = (&mut self.current_function, self.current_block) {
|
||||
if std::env::var("NYASH_SCHEDULE_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[utils/insert-copy-after-phis] bb={:?} dst=%{} src=%{} attempting...",
|
||||
bb, dst.0, src.0);
|
||||
}
|
||||
if let Some(block) = function.get_block_mut(bb) {
|
||||
if std::env::var("NYASH_SCHEDULE_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[utils/insert-copy-after-phis] bb={:?} dst=%{} src=%{} phi_count={} SUCCESS",
|
||||
bb, dst.0, src.0, block.phi_instructions().count());
|
||||
}
|
||||
// Propagate effects on the block
|
||||
block.insert_instruction_after_phis(super::MirInstruction::Copy { dst, src });
|
||||
// Lightweight metadata propagation (unified)
|
||||
crate::mir::builder::metadata::propagate::propagate(self, src, dst);
|
||||
return Ok(());
|
||||
} else {
|
||||
if std::env::var("NYASH_SCHEDULE_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[utils/insert-copy-after-phis] bb={:?} dst=%{} src=%{} FAILED: block not found",
|
||||
bb, dst.0, src.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
Err("No current function/block to insert copy".to_string())
|
||||
|
||||
@ -52,11 +52,9 @@ pub fn register_user_defined_factory(factory: Arc<dyn crate::box_factory::BoxFac
|
||||
let registry = get_global_unified_registry();
|
||||
let mut registry_lock = registry.lock().unwrap();
|
||||
|
||||
// Insert at position 1 (after builtin, before plugin)
|
||||
// This maintains priority: builtin > user > plugin
|
||||
if registry_lock.factories.len() >= 2 {
|
||||
registry_lock.factories.insert(1, factory);
|
||||
} else {
|
||||
registry_lock.register(factory);
|
||||
}
|
||||
// Phase 25.1b: delegate to policy-aware register() so that
|
||||
// type_cache is rebuilt and user-defined Box types (HakoCli など)
|
||||
// are correctly advertised to the registry. Priorityは
|
||||
// FactoryPolicy + factory_type に従って決まる。
|
||||
registry_lock.register(factory);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user