Add KeepAlive instruction to fix hidden root problem where x = null
doesn't properly drop the strong reference.
Key changes:
- Add KeepAlive { values, drop_after } instruction to MIR
- Emit KeepAlive[drop=true] in build_assignment() before variable overwrite
- Emit KeepAlive[drop=false] in pop_lexical_scope() at scope end
- VM handler: when drop_after=true, remove ALL ValueIds pointing to
the same Arc (handles SSA Copy chains)
Test results:
- weak_upgrade_fail: exit 1 ✅ (weak_to_strong returns null after x=null)
- weak_basic: exit 2 ✅ (weak_to_strong succeeds while x alive)
- quick smoke: 154/154 PASS ✅
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
193 lines
7.5 KiB
Rust
193 lines
7.5 KiB
Rust
use super::*;
|
|
|
|
// VM dispatch trace macro (used across handlers)
|
|
macro_rules! trace_dispatch {
|
|
($method:expr, $handler:expr) => {
|
|
if $method == "length" && std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
|
|
eprintln!("[vm-trace] length dispatch handler={}", $handler);
|
|
}
|
|
};
|
|
}
|
|
|
|
mod arithmetic;
|
|
mod boxes;
|
|
mod boxes_array;
|
|
mod boxes_instance;
|
|
mod boxes_map;
|
|
mod boxes_object_fields;
|
|
mod boxes_plugin;
|
|
mod boxes_string;
|
|
mod boxes_void_guards;
|
|
mod calls;
|
|
mod extern_provider;
|
|
mod externals;
|
|
mod memory;
|
|
mod misc;
|
|
mod type_ops;
|
|
mod weak; // Phase 285A0: WeakRef handlers
|
|
|
|
impl MirInterpreter {
|
|
pub(super) fn execute_instruction(&mut self, inst: &MirInstruction) -> Result<(), VMError> {
|
|
match inst {
|
|
MirInstruction::Const { dst, value } => self.handle_const(*dst, value)?,
|
|
MirInstruction::NewBox {
|
|
dst,
|
|
box_type,
|
|
args,
|
|
} => self.handle_new_box(*dst, box_type, args)?,
|
|
MirInstruction::PluginInvoke {
|
|
dst,
|
|
box_val,
|
|
method,
|
|
args,
|
|
..
|
|
} => self.handle_plugin_invoke(*dst, *box_val, method, args)?,
|
|
MirInstruction::BoxCall {
|
|
dst,
|
|
box_val,
|
|
method,
|
|
args,
|
|
..
|
|
} => self.handle_box_call(*dst, *box_val, method, args)?,
|
|
MirInstruction::ExternCall {
|
|
dst,
|
|
iface_name,
|
|
method_name,
|
|
args,
|
|
..
|
|
} => self.handle_extern_call(*dst, iface_name, method_name, args)?,
|
|
MirInstruction::RefSet {
|
|
reference,
|
|
field,
|
|
value,
|
|
} => self.handle_ref_set(*reference, field, *value)?,
|
|
MirInstruction::RefGet {
|
|
dst,
|
|
reference,
|
|
field,
|
|
} => self.handle_ref_get(*dst, *reference, field)?,
|
|
MirInstruction::BinOp { dst, op, lhs, rhs } => {
|
|
self.handle_binop(*dst, *op, *lhs, *rhs)?
|
|
}
|
|
MirInstruction::UnaryOp { dst, op, operand } => {
|
|
self.handle_unary_op(*dst, *op, *operand)?
|
|
}
|
|
MirInstruction::Compare { dst, op, lhs, rhs } => {
|
|
self.handle_compare(*dst, *op, *lhs, *rhs)?
|
|
}
|
|
MirInstruction::TypeOp { dst, op, value, ty } => {
|
|
self.handle_type_op(*dst, *op, *value, ty)?
|
|
}
|
|
MirInstruction::Copy { dst, src } => self.handle_copy(*dst, *src)?,
|
|
MirInstruction::Load { dst, ptr } => self.handle_load(*dst, *ptr)?,
|
|
MirInstruction::Store { ptr, value } => self.handle_store(*ptr, *value)?,
|
|
MirInstruction::Call {
|
|
dst,
|
|
func,
|
|
callee,
|
|
args,
|
|
..
|
|
} => self.handle_call(*dst, *func, callee.as_ref(), args)?,
|
|
MirInstruction::Debug { message, value } => {
|
|
self.handle_debug(message, *value)?;
|
|
}
|
|
MirInstruction::DebugLog { message, values } => {
|
|
// Dev-only: MIR-level debug logging (no new values defined).
|
|
if std::env::var("NYASH_MIR_DEBUG_LOG").ok().as_deref() == Some("1") {
|
|
eprint!("[MIR-LOG] {}:", message);
|
|
for vid in values {
|
|
let v = self.reg_load(*vid).unwrap_or(VMValue::Void);
|
|
eprint!(" %{}={:?}", vid.0, v);
|
|
}
|
|
eprintln!();
|
|
}
|
|
}
|
|
MirInstruction::Print { value, .. } => self.handle_print(*value)?,
|
|
// Phase 256 P1.5: Select instruction (ternary conditional)
|
|
MirInstruction::Select {
|
|
dst,
|
|
cond,
|
|
then_val,
|
|
else_val,
|
|
} => {
|
|
let cond_val = self.reg_load(*cond)?;
|
|
let is_true = cond_val.as_bool()?;
|
|
let selected_val = if is_true {
|
|
self.reg_load(*then_val)?
|
|
} else {
|
|
self.reg_load(*else_val)?
|
|
};
|
|
self.regs.insert(*dst, selected_val);
|
|
}
|
|
// Phase 285A0: WeakRef handlers (delegated to weak.rs)
|
|
MirInstruction::WeakNew { dst, box_val } => {
|
|
self.handle_weak_new(*dst, *box_val)?;
|
|
}
|
|
MirInstruction::WeakLoad { dst, weak_ref } => {
|
|
self.handle_weak_load(*dst, *weak_ref)?;
|
|
}
|
|
MirInstruction::WeakRef { dst, op, value } => match op {
|
|
WeakRefOp::New => self.handle_weak_new(*dst, *value)?,
|
|
WeakRefOp::Load => self.handle_weak_load(*dst, *value)?,
|
|
}
|
|
MirInstruction::BarrierRead { .. }
|
|
| MirInstruction::BarrierWrite { .. }
|
|
| MirInstruction::Barrier { .. }
|
|
| MirInstruction::Safepoint => {}
|
|
MirInstruction::KeepAlive { values, drop_after } => {
|
|
// Phase 285 P2.1: Handle KeepAlive based on drop_after flag
|
|
// - drop_after=true: Release values (for variable overwrite, enables weak ref failure)
|
|
// - drop_after=false: Just keep alive (for scope end, values may be needed for PHI)
|
|
if *drop_after {
|
|
// IMPORTANT: Due to SSA Copy instructions, a single Box may have multiple
|
|
// ValueIds pointing to it (e.g., %5 = NewBox, %6 = Copy %5).
|
|
// We need to find and remove ALL ValueIds that point to the same Arc.
|
|
use std::sync::Arc;
|
|
use super::super::vm_types::VMValue;
|
|
|
|
// Collect raw pointers of Arcs being released
|
|
let mut arc_ptrs: Vec<*const dyn crate::box_trait::NyashBox> = Vec::new();
|
|
for v in values {
|
|
if let Some(VMValue::BoxRef(arc)) = self.regs.get(v) {
|
|
arc_ptrs.push(Arc::as_ptr(arc));
|
|
}
|
|
}
|
|
|
|
// Remove the specified values first
|
|
for v in values {
|
|
self.regs.remove(v);
|
|
}
|
|
|
|
// Find and remove ALL other ValueIds that point to the same Arcs
|
|
if !arc_ptrs.is_empty() {
|
|
let to_remove: Vec<crate::mir::ValueId> = self.regs
|
|
.iter()
|
|
.filter_map(|(vid, val)| {
|
|
if let VMValue::BoxRef(arc) = val {
|
|
let ptr = Arc::as_ptr(arc);
|
|
if arc_ptrs.contains(&ptr) {
|
|
return Some(*vid);
|
|
}
|
|
}
|
|
None
|
|
})
|
|
.collect();
|
|
for vid in to_remove {
|
|
self.regs.remove(&vid);
|
|
}
|
|
}
|
|
}
|
|
// If drop_after=false, do nothing (values stay alive)
|
|
}
|
|
MirInstruction::Nop => {}
|
|
other => {
|
|
return Err(self.err_invalid(format!(
|
|
"MIR interp: unimplemented instruction: {:?}",
|
|
other
|
|
)))
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
}
|