use crate::{
bytecode::{Instruction, OpCode, Operand},
executor::VirtualMachine,
};
use shape_value::{
HeapKind, KindedSlot, NativeKind, VMError, ValueSlot,
heap_value::{OptionData, ResultData, TraitObjectStorage, TypedObjectStorage},
value::{VTable, VTableEntry, WrapTarget},
};
use smallvec::SmallVec;
use std::sync::Arc;
impl VirtualMachine {
#[inline(always)]
pub(in crate::executor) fn exec_trait_object_ops(
&mut self,
instruction: &Instruction,
ctx: Option<&mut shape_runtime::context::ExecutionContext>,
) -> Result<(), VMError> {
match instruction.opcode {
OpCode::BoxTraitObject => self.op_box_trait_object(instruction),
OpCode::DynMethodCall => self.op_dyn_method_call(instruction, ctx),
OpCode::DropCall => self.op_drop_call_sync(instruction, ctx),
OpCode::DropCallAsync => self.op_drop_call_async(instruction, ctx),
_ => unreachable!(
"exec_trait_object_ops called with non-trait-object opcode: {:?}",
instruction.opcode
),
}
}
fn op_box_trait_object(&mut self, instruction: &Instruction) -> Result<(), VMError> {
let trait_name = match instruction.operand {
Some(Operand::Name(sid)) => self
.program
.strings
.get(sid.0 as usize)
.cloned()
.ok_or_else(|| {
VMError::RuntimeError(
"BoxTraitObject: trait-name StringId out of range".to_string(),
)
})?,
_ => {
return Err(VMError::RuntimeError(
"BoxTraitObject: missing trait-name operand".to_string(),
));
}
};
let (bits, kind) = self.pop_kinded()?;
let typed_object_ptr: *const shape_value::heap_value::TypedObjectStorage = match kind {
NativeKind::Ptr(HeapKind::TypedObject) => {
if bits == 0 {
return Err(VMError::RuntimeError(
"BoxTraitObject: null TypedObject pointer".to_string(),
));
}
bits as *const shape_value::heap_value::TypedObjectStorage
}
other => {
drop_kinded(bits, kind);
return Err(VMError::NotImplemented(format!(
"BoxTraitObject: universal-dyn auto-boxing of non-TypedObject \
kinds is deferred per ADR-006 §2.7.24 Q25.C.1 (boxed value \
must currently be a TypedObject). Got kind: {:?}",
other
)));
}
};
let schema_id = unsafe { (*typed_object_ptr).schema_id };
let type_name = self
.program
.type_schema_registry
.get_by_id(schema_id as u32)
.map(|s| s.name.clone())
.ok_or_else(|| {
VMError::RuntimeError(format!(
"BoxTraitObject: no type schema registered for schema_id {}",
schema_id
))
})?;
let key = format!("{}::{}", trait_name, type_name);
let vtable = self
.program
.trait_vtables
.get(&key)
.cloned()
.ok_or_else(|| {
VMError::RuntimeError(format!(
"BoxTraitObject: no vtable registered for '{}' \
(looked up key '{}'). Per ADR-006 §2.7.24 Q25.C this \
indicates an impl-block compile-tier gap.",
trait_name, key
))
})?;
let ptr = TraitObjectStorage::_new(typed_object_ptr, vtable);
let to_bits = ptr as u64;
self.push_kinded(to_bits, NativeKind::Ptr(HeapKind::TraitObject))?;
Ok(())
}
fn op_dyn_method_call(
&mut self,
instruction: &Instruction,
ctx: Option<&mut shape_runtime::context::ExecutionContext>,
) -> Result<(), VMError> {
let (arg_count, method_name) = match instruction.operand {
Some(Operand::TypedMethodCall {
arg_count,
string_id,
..
}) => {
let name = self.program.strings.get(string_id as usize).cloned().ok_or_else(
|| VMError::RuntimeError(
"DynMethodCall: method-name StringId out of range".to_string(),
),
)?;
(arg_count as usize, name)
}
_ => {
return Err(VMError::RuntimeError(
"DynMethodCall: missing TypedMethodCall operand".to_string(),
));
}
};
if self.sp < arg_count + 1 {
return Err(VMError::StackUnderflow);
}
let receiver_idx = self.sp - arg_count - 1;
let (receiver_bits, receiver_kind) = self.stack_read_kinded_raw(receiver_idx);
let trait_object_ptr: *const TraitObjectStorage = match receiver_kind {
NativeKind::Ptr(HeapKind::TraitObject) => {
if receiver_bits == 0 {
return Err(VMError::RuntimeError(
"DynMethodCall: null TraitObject pointer".to_string(),
));
}
receiver_bits as *const TraitObjectStorage
}
other => {
return Err(VMError::RuntimeError(format!(
"DynMethodCall: receiver must be NativeKind::Ptr(HeapKind::TraitObject), \
got {:?}",
other
)));
}
};
let trait_object: &TraitObjectStorage = unsafe { &*trait_object_ptr };
let entry = trait_object
.vtable
.methods
.get(&method_name)
.cloned()
.ok_or_else(|| {
VMError::RuntimeError(format!(
"DynMethodCall: method '{}' not in vtable for trait(s) {:?}",
method_name, trait_object.vtable.trait_names
))
})?;
let trait_name = trait_object
.vtable
.trait_names
.first()
.cloned()
.unwrap_or_default();
let inner_schema_id = unsafe { (*trait_object.value).schema_id };
let concrete_type_name = self
.program
.type_schema_registry
.get_by_id(inner_schema_id as u32)
.map(|s| s.name.clone())
.ok_or_else(|| {
VMError::RuntimeError(format!(
"DynMethodCall: no type schema for schema_id {}",
inner_schema_id
))
})?;
let resolved_fn_name = self
.program
.trait_method_symbols
.get(&format!(
"{}::{}::__default__::{}",
trait_name, concrete_type_name, method_name
))
.cloned()
.unwrap_or_else(|| format!("{}::{}", concrete_type_name, method_name));
let runtime_function_id = self
.function_name_index
.get(&resolved_fn_name)
.copied()
.ok_or_else(|| {
VMError::RuntimeError(format!(
"DynMethodCall: function '{}' not in function_name_index",
resolved_fn_name
))
})?;
match entry {
VTableEntry::Direct { .. } => self.invoke_dyn_unified(
runtime_function_id,
trait_object,
arg_count,
receiver_idx,
ctx,
&[],
&[],
),
VTableEntry::BoxedReturn { ref wrap_targets, .. } => self.invoke_dyn_unified(
runtime_function_id,
trait_object,
arg_count,
receiver_idx,
ctx,
wrap_targets.as_slice(),
&[],
),
VTableEntry::SelfArg {
ref self_arg_positions,
..
} => self.invoke_dyn_unified(
runtime_function_id,
trait_object,
arg_count,
receiver_idx,
ctx,
&[],
self_arg_positions.as_slice(),
),
VTableEntry::Generic { .. } => {
self.invoke_dyn_unified(
runtime_function_id,
trait_object,
arg_count,
receiver_idx,
ctx,
&[],
&[],
)
}
VTableEntry::Compound {
ref wrap_targets,
ref self_arg_positions,
..
} => self.invoke_dyn_unified(
runtime_function_id,
trait_object,
arg_count,
receiver_idx,
ctx,
wrap_targets.as_slice(),
self_arg_positions.as_slice(),
),
VTableEntry::Closure {
function_id,
type_id: _,
} => self.invoke_dyn_closure(
function_id,
trait_object,
arg_count,
receiver_idx,
ctx,
),
}
}
fn invoke_dyn_unified(
&mut self,
function_id: u16,
trait_object: &TraitObjectStorage,
arg_count: usize,
receiver_idx: usize,
ctx: Option<&mut shape_runtime::context::ExecutionContext>,
wrap_targets: &[WrapTarget],
self_arg_positions: &[u8],
) -> Result<(), VMError> {
for &pos in self_arg_positions {
let arg_idx = receiver_idx
.checked_add(1)
.and_then(|x| x.checked_add(pos as usize))
.ok_or_else(|| {
VMError::RuntimeError(
"DynMethodCall SelfArg: arg_idx arithmetic overflow"
.to_string(),
)
})?;
if arg_idx >= self.sp {
return Err(VMError::RuntimeError(format!(
"DynMethodCall SelfArg: position {} out of range \
(receiver_idx={}, sp={})",
pos, receiver_idx, self.sp
)));
}
let (arg_bits, arg_kind) = self.stack_read_kinded_raw(arg_idx);
let arg_trait_object: &TraitObjectStorage = match arg_kind {
NativeKind::Ptr(HeapKind::TraitObject) => {
if arg_bits == 0 {
return Err(VMError::RuntimeError(format!(
"DynMethodCall SelfArg: null TraitObject \
pointer at arg position {}",
pos
)));
}
unsafe { &*(arg_bits as *const TraitObjectStorage) }
}
other => {
return Err(VMError::RuntimeError(format!(
"DynMethodCall SelfArg: position {} expected \
trait object (NativeKind::Ptr(HeapKind::TraitObject)), \
got {:?}. Per ADR-006 §2.7.24 Q25.C.2 a Self-typed \
argument flowing through `dyn T` must itself be a \
trait object so the vtable-identity check can run.",
pos, other
)));
}
};
if !trait_object.vtable_eq(arg_trait_object) {
return Err(VMError::RuntimeError(format!(
"DynMethodCall SelfArg: vtable identity mismatch at \
argument position {}. Per ADR-006 §2.7.24 Q25.C.2 \
`Self` in argument position requires the argument's \
concrete type to match the receiver's. Receiver \
trait(s): {:?}; argument trait(s): {:?}.",
pos, trait_object.vtable.trait_names,
arg_trait_object.vtable.trait_names
)));
}
}
unsafe { shape_value::v2::refcount::v2_retain(&(*trait_object.value).header); }
let new_bits = trait_object.value as u64;
let new_kind = NativeKind::Ptr(HeapKind::TypedObject);
self.stack_write_kinded(receiver_idx, new_bits, new_kind);
for &pos in self_arg_positions {
let arg_idx = receiver_idx + 1 + (pos as usize);
let (arg_bits, arg_kind) = self.stack_read_kinded_raw(arg_idx);
debug_assert_eq!(arg_kind, NativeKind::Ptr(HeapKind::TraitObject));
let arg_to: &TraitObjectStorage = unsafe {
&*(arg_bits as *const TraitObjectStorage)
};
let arg_inner_ptr = arg_to.value;
unsafe { shape_value::v2::refcount::v2_retain(&(*arg_inner_ptr).header); }
let new_arg_bits = arg_inner_ptr as u64;
self.stack_write_kinded(arg_idx, new_arg_bits, new_kind);
}
let total = arg_count + 1;
let mut args: Vec<KindedSlot> = Vec::with_capacity(total);
for i in 0..total {
let (b, k) = self.stack_take_kinded(receiver_idx + i);
args.push(KindedSlot::new(ValueSlot::from_raw(b), k));
}
self.sp = receiver_idx;
let saved_depth = self.call_stack.len();
self.call_function_with_nb_args(function_id, &args)?;
self.execute_until_call_depth(saved_depth, ctx)?;
drop(args);
let (ret_bits, ret_kind) = self.pop_kinded()?;
if wrap_targets.is_empty() {
self.push_kinded(ret_bits, ret_kind)?;
return Ok(());
}
let (wrapped_bits, wrapped_kind) = rewrap_return_value(
ret_bits,
ret_kind,
wrap_targets,
&trait_object.vtable,
)?;
self.push_kinded(wrapped_bits, wrapped_kind)?;
Ok(())
}
fn invoke_dyn_closure(
&mut self,
_function_id: u32,
_trait_object: &TraitObjectStorage,
arg_count: usize,
receiver_idx: usize,
ctx: Option<&mut shape_runtime::context::ExecutionContext>,
) -> Result<(), VMError> {
let _ = (arg_count, receiver_idx, ctx);
Err(VMError::NotImplemented(
"SURFACE: DynMethodCall Closure variant per ADR-006 §2.7.24 \
Q25.C.5 + §2.7.11/Q12 — W7 closure-trait-impl dispatch through \
dyn requires receiver-as-closure routing through \
`call_value_immediate_nb`. The thunks tier (Wave 3 \
W17-trait-object-thunks) reserves dispatch wire-through for \
a future sub-cluster pending W7 emission. Storage shapes \
ready; emission gates the dispatch.".to_string(),
))
}
fn op_drop_call_sync(
&mut self,
instruction: &Instruction,
ctx: Option<&mut shape_runtime::context::ExecutionContext>,
) -> Result<(), VMError> {
self.op_drop_call_impl(instruction, ctx, false)
}
fn op_drop_call_async(
&mut self,
instruction: &Instruction,
ctx: Option<&mut shape_runtime::context::ExecutionContext>,
) -> Result<(), VMError> {
self.op_drop_call_impl(instruction, ctx, true)
}
fn op_drop_call_impl(
&mut self,
instruction: &Instruction,
ctx: Option<&mut shape_runtime::context::ExecutionContext>,
is_async: bool,
) -> Result<(), VMError> {
let type_name_opt: Option<String> = match instruction.operand {
Some(Operand::Property(sid)) => {
self.program.strings.get(sid as usize).cloned()
}
_ => None,
};
let (bits, kind) = self.pop_kinded()?;
let drop_fn_name = type_name_opt.as_ref().and_then(|tn| {
let method_name = if is_async { "drop_async" } else { "drop" };
self.program
.trait_method_symbols
.get(&format!("Drop::{}::__default__::{}", tn, method_name))
.cloned()
.or_else(|| {
if is_async {
self.program
.trait_method_symbols
.get(&format!("Drop::{}::__default__::drop", tn))
.cloned()
} else {
None
}
})
});
let Some(fn_name) = drop_fn_name else {
drop_kinded(bits, kind);
return Ok(());
};
let function_id = self
.program
.functions
.iter()
.position(|f| f.name == fn_name)
.map(|idx| idx as u16);
let Some(function_id) = function_id else {
drop_kinded(bits, kind);
return Err(VMError::RuntimeError(format!(
"DropCall: trait_method_symbols points to '{}' but it has no \
entry in program.functions",
fn_name
)));
};
let self_arg = KindedSlot::new(ValueSlot::from_raw(bits), kind);
let args = vec![self_arg];
let saved_depth = self.call_stack.len();
self.call_function_with_nb_args(function_id, &args)?;
self.execute_until_call_depth(saved_depth, ctx)?;
drop(args);
let (rbits, rkind) = self.pop_kinded()?;
drop_kinded(rbits, rkind);
Ok(())
}
}
#[inline]
fn drop_kinded(bits: u64, kind: NativeKind) {
crate::executor::vm_impl::stack::drop_with_kind(bits, kind);
}
fn rewrap_return_value(
ret_bits: u64,
ret_kind: NativeKind,
wrap_targets: &[WrapTarget],
receiver_vtable: &Arc<VTable>,
) -> Result<(u64, NativeKind), VMError> {
let has_top_level = wrap_targets.iter().any(|w| w.path.is_empty());
if has_top_level {
return rebox_self_value(ret_bits, ret_kind, receiver_vtable);
}
match ret_kind {
NativeKind::Ptr(HeapKind::Result) => {
rewrap_result_payload(ret_bits, wrap_targets, receiver_vtable)
}
NativeKind::Ptr(HeapKind::Option) => {
rewrap_option_payload(ret_bits, wrap_targets, receiver_vtable)
}
NativeKind::Ptr(HeapKind::TypedObject) => {
rewrap_typed_object_fields(ret_bits, wrap_targets, receiver_vtable)
}
NativeKind::Ptr(HeapKind::HashMap) => {
rewrap_hashmap_values(ret_bits, wrap_targets, receiver_vtable)
}
NativeKind::Ptr(HeapKind::TypedArray) => {
rewrap_typed_array_elements(ret_bits, wrap_targets, receiver_vtable)
}
other => {
drop_kinded(ret_bits, ret_kind);
Err(VMError::NotImplemented(format!(
"SURFACE: DynMethodCall nested BoxedReturn dispatch on \
return kind {:?} per ADR-006 §2.7.24 Q25.C.5 — the \
structural carrier doesn't have a registered wrap-target \
descent path. Supported: Result / Option / TypedObject \
(tuples & records) / HashMap / TypedArray. Wrap-targets: \
{:?}.",
other,
wrap_targets.iter().map(|w| w.path.as_slice()).collect::<Vec<_>>()
)))
}
}
}
fn rebox_self_value(
bits: u64,
kind: NativeKind,
receiver_vtable: &Arc<VTable>,
) -> Result<(u64, NativeKind), VMError> {
match kind {
NativeKind::Ptr(HeapKind::TypedObject) => {
if bits == 0 {
return Err(VMError::RuntimeError(
"DynMethodCall BoxedReturn: null TypedObject return"
.to_string(),
));
}
let inner_ptr = bits as *const TypedObjectStorage;
let ptr = TraitObjectStorage::_new(inner_ptr, Arc::clone(receiver_vtable));
let to_bits = ptr as u64;
Ok((to_bits, NativeKind::Ptr(HeapKind::TraitObject)))
}
NativeKind::Ptr(HeapKind::TraitObject) => {
Ok((bits, kind))
}
_ => {
drop_kinded(bits, kind);
Err(VMError::NotImplemented(format!(
"SURFACE: DynMethodCall BoxedReturn with scalar leaf \
kind {:?} requires universal-dyn auto-boxing of non-\
TypedObject kinds per ADR-006 §2.7.24 Q25.C.1; scalar \
trait-object payloads are deferred (emission-shell \
currently surfaces these at the `op_box_trait_object` \
path; lifting this requires emission-tier scalar-\
to-TypedObject auto-box).",
kind
)))
}
}
}
fn rewrap_result_payload(
ret_bits: u64,
wrap_targets: &[WrapTarget],
receiver_vtable: &Arc<VTable>,
) -> Result<(u64, NativeKind), VMError> {
if ret_bits == 0 {
return Err(VMError::RuntimeError(
"DynMethodCall BoxedReturn: null Result return".to_string(),
));
}
let result: Arc<ResultData> =
unsafe { Arc::from_raw(ret_bits as *const ResultData) };
let arm_index: u8 = if result.is_ok { 0 } else { 1 };
let descendants: SmallVec<[WrapTarget; 2]> = wrap_targets
.iter()
.filter(|w| !w.path.is_empty() && w.path[0] == arm_index)
.map(|w| WrapTarget {
path: w.path[1..].iter().copied().collect(),
wrap_as_trait_id: w.wrap_as_trait_id,
})
.collect();
if descendants.is_empty() {
let raw = Arc::into_raw(result) as u64;
return Ok((raw, NativeKind::Ptr(HeapKind::Result)));
}
let mut new_result = (*result).clone();
let payload_bits = new_result.payload.raw();
let payload_kind = new_result.payload.kind();
std::mem::forget(std::mem::replace(
&mut new_result.payload,
KindedSlot::none(),
));
let (new_payload_bits, new_payload_kind) =
rewrap_return_value(payload_bits, payload_kind, &descendants, receiver_vtable)?;
new_result.payload = KindedSlot::new(
ValueSlot::from_raw(new_payload_bits),
new_payload_kind,
);
drop(result);
let new_arc = Arc::new(new_result);
let raw = Arc::into_raw(new_arc) as u64;
Ok((raw, NativeKind::Ptr(HeapKind::Result)))
}
fn rewrap_option_payload(
ret_bits: u64,
wrap_targets: &[WrapTarget],
receiver_vtable: &Arc<VTable>,
) -> Result<(u64, NativeKind), VMError> {
if ret_bits == 0 {
return Err(VMError::RuntimeError(
"DynMethodCall BoxedReturn: null Option return".to_string(),
));
}
let option: Arc<OptionData> =
unsafe { Arc::from_raw(ret_bits as *const OptionData) };
if !option.is_some {
let raw = Arc::into_raw(option) as u64;
return Ok((raw, NativeKind::Ptr(HeapKind::Option)));
}
let descendants: SmallVec<[WrapTarget; 2]> = wrap_targets
.iter()
.filter(|w| !w.path.is_empty() && w.path[0] == 0)
.map(|w| WrapTarget {
path: w.path[1..].iter().copied().collect(),
wrap_as_trait_id: w.wrap_as_trait_id,
})
.collect();
if descendants.is_empty() {
let raw = Arc::into_raw(option) as u64;
return Ok((raw, NativeKind::Ptr(HeapKind::Option)));
}
let mut new_option = (*option).clone();
let payload_bits = new_option.payload.raw();
let payload_kind = new_option.payload.kind();
std::mem::forget(std::mem::replace(
&mut new_option.payload,
KindedSlot::none(),
));
let (new_payload_bits, new_payload_kind) =
rewrap_return_value(payload_bits, payload_kind, &descendants, receiver_vtable)?;
new_option.payload = KindedSlot::new(
ValueSlot::from_raw(new_payload_bits),
new_payload_kind,
);
drop(option);
let new_arc = Arc::new(new_option);
let raw = Arc::into_raw(new_arc) as u64;
Ok((raw, NativeKind::Ptr(HeapKind::Option)))
}
fn rewrap_typed_object_fields(
ret_bits: u64,
wrap_targets: &[WrapTarget],
_receiver_vtable: &Arc<VTable>,
) -> Result<(u64, NativeKind), VMError> {
drop_kinded(ret_bits, NativeKind::Ptr(HeapKind::TypedObject));
Err(VMError::NotImplemented(format!(
"SURFACE: DynMethodCall BoxedReturn with TypedObject-carrier \
return + wrap_targets {:?} per ADR-006 §2.7.24 Q25.C.5 — \
in-place tuple/record field re-box requires \
`TypedObjectStorage::write_slot_in_place` integration with \
a trait-declared field-index lookup. The dispatch shell \
surfaces; lifting this is a follow-up sub-cluster pending \
the typed-record-rewrap recipe.",
wrap_targets.iter().map(|w| w.path.as_slice()).collect::<Vec<_>>()
)))
}
fn rewrap_hashmap_values(
ret_bits: u64,
wrap_targets: &[WrapTarget],
_receiver_vtable: &Arc<VTable>,
) -> Result<(u64, NativeKind), VMError> {
drop_kinded(ret_bits, NativeKind::Ptr(HeapKind::HashMap));
Err(VMError::NotImplemented(format!(
"SURFACE: DynMethodCall BoxedReturn with HashMap<K, Self> \
return + wrap_targets {:?} per ADR-006 §2.7.24 Q25.B SUPERSEDED \
+ Q25.C.5 — the prior values-buffer rewrap path targeted \
`HashMapValueBuf::TraitObject`, which is deleted (Wave 2 Round 1 \
Agent F 2026-05-14 dead-arm wholesale deletion + Agent C 2026-05-15 \
Q25.B SUPERSEDED amendment). Values-buffer rewrap requires the \
full HashMapData<V> generic monomorphization (audit §C.4 option \
(a.2) HashMapKindedRef carrier) which is gated on Wave 2 Agent \
C2a (runtime tier) + C2b (JIT FFI tier) close. The dispatch \
shell surfaces; lifting this pairs with the \
`rewrap_typed_object_fields` follow-up.",
wrap_targets.iter().map(|w| w.path.as_slice()).collect::<Vec<_>>()
)))
}
fn rewrap_typed_array_elements(
ret_bits: u64,
wrap_targets: &[WrapTarget],
_receiver_vtable: &Arc<VTable>,
) -> Result<(u64, NativeKind), VMError> {
drop_kinded(ret_bits, NativeKind::Ptr(HeapKind::TypedArray));
Err(VMError::NotImplemented(format!(
"SURFACE: DynMethodCall BoxedReturn with Array<Self> return + \
wrap_targets {:?} per ADR-006 §2.7.24 Q25.A SUPERSEDED #1 — \
the prior re-box pathway produced the deleted typed-array-data \
TraitObject variant, \
which is deleted (Wave 2 Round 1 Agent F, 2026-05-14, dead-arm \
wholesale deletion). A user-facing Array<dyn T> carrier lands \
per audit §A.3 row when reachability is required.",
wrap_targets.iter().map(|w| w.path.as_slice()).collect::<Vec<_>>()
)))
}