Phase 25.1b: VM undefined-value diagnostics and builder SSA helpers

This commit is contained in:
nyash-codex
2025-11-17 03:19:03 +09:00
parent 4b078e6df9
commit 82b6c4e834
12 changed files with 231 additions and 41 deletions

View File

@ -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,
)))
}
}

View File

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

View File

@ -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() {

View File

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

View File

@ -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).

View File

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

View File

@ -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(&current_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 });

View File

@ -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())

View File

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