use crate::bytecode::{Instruction, Operand};
use crate::executor::VirtualMachine;
use crate::executor::vm_impl::stack::{clone_with_kind, drop_with_kind};
use shape_value::{
NativeKind, VMError,
heap_value::{HashMapKindedRef, HeapKind},
};
use std::sync::Arc;
impl VirtualMachine {
pub(in crate::executor) fn op_get_prop(
&mut self,
_ctx: Option<&mut shape_runtime::context::ExecutionContext>,
) -> Result<(), VMError> {
let (key_bits, key_kind) = self.pop_kinded()?;
let (obj_bits, obj_kind) = self.pop_kinded()?;
let key_str: Option<&str> = match key_kind {
NativeKind::String
| NativeKind::Ptr(HeapKind::String) => {
if key_bits == 0 {
None
} else {
let s: &String = unsafe { &*(key_bits as *const String) };
Some(s.as_str())
}
}
_ => None,
};
let result = self.dispatch_get_prop(obj_bits, obj_kind, key_bits, key_kind, key_str);
drop_with_kind(key_bits, key_kind);
drop_with_kind(obj_bits, obj_kind);
result
}
#[inline]
fn dispatch_get_prop(
&mut self,
obj_bits: u64,
obj_kind: NativeKind,
key_bits: u64,
key_kind: NativeKind,
key_str: Option<&str>,
) -> Result<(), VMError> {
match obj_kind {
NativeKind::Ptr(HeapKind::TypedObject) => {
let ks = key_str.ok_or_else(|| VMError::TypeError {
expected: "string property name",
got: "non-string key",
})?;
if obj_bits == 0 {
return Err(VMError::RuntimeError(
"GetProp on null TypedObject".to_string(),
));
}
let storage_arc: Arc<shape_value::heap_value::TypedObjectStorage> =
unsafe { Arc::from_raw(obj_bits as *const _) };
let result = self.read_typed_object_field(&storage_arc, ks);
let _ = Arc::into_raw(storage_arc);
result
}
NativeKind::Ptr(HeapKind::TypedArray) => {
use crate::executor::v2_handlers::v2_array_detect::{
as_v2_typed_array, read_element,
};
let view = match as_v2_typed_array(obj_bits, obj_kind) {
Some(v) => v,
None => {
return Err(VMError::TypeError {
expected: "object, array, string, or other heap value",
got: "scalar",
});
}
};
let idx = numeric_index_from_kinded(key_bits, key_kind)?;
if (idx as u64) >= view.len as u64 {
return Err(VMError::IndexOutOfBounds {
index: idx as i32,
length: view.len as usize,
});
}
match read_element(&view, idx as u32) {
Some((bits, kind)) => self.push_kinded(bits, kind),
None => Err(VMError::IndexOutOfBounds {
index: idx as i32,
length: view.len as usize,
}),
}
}
NativeKind::String
| NativeKind::Ptr(_) => Err(VMError::NotImplemented(format!(
"SURFACE: GetProp on {:?} not yet kinded — requires the \
W17-typed-carrier-monomorphization replacement for the \
deleted HashMapData::values: `Arc<Buf<Arc<HeapValue>>>` \
carrier (ADR-006 §2.7.24 Q25.B) or the per-receiver \
heterogeneous-kind body. Key kind observed: {:?}.",
obj_kind, key_kind
))),
_ => Err(VMError::TypeError {
expected: "object, array, string, or other heap value",
got: "scalar",
}),
}
}
fn read_typed_object_field(
&mut self,
storage: &shape_value::heap_value::TypedObjectStorage,
key: &str,
) -> Result<(), VMError> {
let schema = self
.program
.type_schema_registry
.get_by_id(storage.schema_id as u32)
.cloned()
.or_else(|| {
shape_runtime::type_schema::lookup_schema_by_id_public(
storage.schema_id as u32,
)
})
.ok_or_else(|| {
VMError::RuntimeError(format!(
"Schema {} not found in registry",
storage.schema_id
))
})?;
let field = schema
.get_field(key)
.ok_or_else(|| VMError::UndefinedProperty(key.to_string()))?;
let idx = field.index as usize;
if idx >= storage.slots.len() {
return Err(VMError::RuntimeError(format!(
"Field '{}' index {} exceeds slot count {}",
key,
idx,
storage.slots.len()
)));
}
if idx >= storage.field_kinds.len() {
return Err(VMError::RuntimeError(format!(
"Field '{}' index {} exceeds field_kinds length {}",
key,
idx,
storage.field_kinds.len()
)));
}
let bits = storage.slots[idx].raw();
let kind = storage.field_kinds[idx];
clone_with_kind(bits, kind);
self.push_kinded(bits, kind)
}
pub(in crate::executor) fn op_set_prop(&mut self) -> Result<(), VMError> {
let (val_bits, val_kind) = self.pop_kinded()?;
let (key_bits, key_kind) = self.pop_kinded()?;
let (obj_bits, obj_kind) = self.pop_kinded()?;
if obj_kind == NativeKind::Ptr(HeapKind::TypedObject) {
let key_str: Option<&str> = match key_kind {
NativeKind::String | NativeKind::Ptr(HeapKind::String) => {
if key_bits == 0 {
None
} else {
let s: &String = unsafe { &*(key_bits as *const String) };
Some(s.as_str())
}
}
_ => None,
};
let Some(ks) = key_str else {
drop_with_kind(val_bits, val_kind);
drop_with_kind(key_bits, key_kind);
drop_with_kind(obj_bits, obj_kind);
return Err(VMError::TypeError {
expected: "string property name",
got: "non-string key",
});
};
if obj_bits == 0 {
drop_with_kind(val_bits, val_kind);
drop_with_kind(key_bits, key_kind);
return Err(VMError::RuntimeError(
"SetProp on null TypedObject".to_string(),
));
}
let storage_arc: Arc<shape_value::heap_value::TypedObjectStorage> =
unsafe { Arc::from_raw(obj_bits as *const _) };
let write_result = self.write_typed_object_field_by_name(
&storage_arc,
ks,
val_bits,
val_kind,
);
let obj_bits_back = Arc::into_raw(storage_arc) as u64;
drop_with_kind(key_bits, key_kind);
return match write_result {
Ok(()) => self.push_kinded(obj_bits_back, obj_kind),
Err(e) => {
drop_with_kind(obj_bits_back, obj_kind);
Err(e)
}
};
}
drop_with_kind(val_bits, val_kind);
drop_with_kind(key_bits, key_kind);
drop_with_kind(obj_bits, obj_kind);
Err(VMError::NotImplemented(format!(
"SURFACE: SetProp on {:?} not yet kinded — Phase-2c reentry \
(ADR-006 §2.7.4 + §2.7.24 Q25.A). TypedObject receivers are \
filled in W17-typed-object-mutation; other heap receivers \
(HashMap, etc.) require the W17-typed-carrier-monomorphization \
sub-cluster's per-receiver heterogeneous-kind body. Key kind \
observed: {:?}.",
obj_kind, key_kind,
)))
}
fn write_typed_object_field_by_name(
&mut self,
storage: &Arc<shape_value::heap_value::TypedObjectStorage>,
key: &str,
val_bits: u64,
val_kind: NativeKind,
) -> Result<(), VMError> {
let schema_owned = self
.program
.type_schema_registry
.get_by_id(storage.schema_id as u32)
.cloned()
.or_else(|| {
shape_runtime::type_schema::lookup_schema_by_id_public(
storage.schema_id as u32,
)
});
let Some(schema) = schema_owned.as_ref()
else {
drop_with_kind(val_bits, val_kind);
return Err(VMError::RuntimeError(format!(
"Schema {} not found in registry",
storage.schema_id
)));
};
let Some(field) = schema.get_field(key) else {
drop_with_kind(val_bits, val_kind);
return Err(VMError::UndefinedProperty(key.to_string()));
};
let idx = field.index as usize;
if idx >= storage.slots.len() {
drop_with_kind(val_bits, val_kind);
return Err(VMError::RuntimeError(format!(
"Field '{}' index {} exceeds slot count {}",
key,
idx,
storage.slots.len()
)));
}
if idx >= storage.field_kinds.len() {
drop_with_kind(val_bits, val_kind);
return Err(VMError::RuntimeError(format!(
"Field '{}' index {} exceeds field_kinds length {}",
key,
idx,
storage.field_kinds.len()
)));
}
let stored_kind = storage.field_kinds[idx];
let kind_compatible = val_kind == stored_kind
|| matches!(
(stored_kind, val_kind),
(
NativeKind::Int64,
NativeKind::Int8
| NativeKind::Int16
| NativeKind::Int32
| NativeKind::UInt8
| NativeKind::UInt16
| NativeKind::UInt32
| NativeKind::UInt64,
) | (
NativeKind::String,
NativeKind::Ptr(HeapKind::String),
) | (
NativeKind::Ptr(HeapKind::String),
NativeKind::String,
)
);
if !kind_compatible {
drop_with_kind(val_bits, val_kind);
return Err(VMError::TypeError {
expected: "value kind matching field schema",
got: "mismatched kind",
});
}
let prior_bits = storage.slots[idx].raw();
crate::memory::write_barrier_slot(prior_bits, val_bits);
let _returned_prior = unsafe { storage.write_slot_in_place(idx, val_bits) };
debug_assert_eq!(
_returned_prior, prior_bits,
"SetProp: write_slot_in_place prior_bits mismatch — \
concurrent write detected? ADR-006 §2.7.13 / Q14",
);
drop_with_kind(prior_bits, stored_kind);
Ok(())
}
pub(in crate::executor) fn op_set_local_index(
&mut self,
_instruction: &Instruction,
) -> Result<(), VMError> {
let (val_bits, val_kind) = self.pop_kinded()?;
let (key_bits, key_kind) = self.pop_kinded()?;
drop_with_kind(val_bits, val_kind);
drop_with_kind(key_bits, key_kind);
Err(VMError::NotImplemented(format!(
"SURFACE: SetLocalIndex requires the W17-typed-carrier-\
monomorphization replacement for the deleted \
the-deleted-heterogeneous-element-carrier heterogeneous-element carrier \
(ADR-006 §2.7.24 Q25.A). Typed-array fast path \
(TypedArraySet{{I64,F64,Bool,...}}) is the supported \
surface today; this opcode covers the fallback shapes \
that need the carrier-monomorphization rebuild. Key \
kind observed: {:?}.",
key_kind,
)))
}
pub(in crate::executor) fn op_set_module_binding_index(
&mut self,
instruction: &Instruction,
) -> Result<(), VMError> {
let (val_bits, val_kind) = self.pop_kinded()?;
let (key_bits, key_kind) = self.pop_kinded()?;
drop_with_kind(val_bits, val_kind);
drop_with_kind(key_bits, key_kind);
let _binding_idx = match instruction.operand {
Some(Operand::ModuleBinding(idx)) => idx as usize,
_ => return Err(VMError::InvalidOperand),
};
Err(VMError::NotImplemented(format!(
"SURFACE: SetModuleBindingIndex requires the W17-typed-\
carrier-monomorphization replacement for the deleted \
the-deleted-heterogeneous-element-carrier heterogeneous-element carrier \
(ADR-006 §2.7.24 Q25.A). Key kind observed: {:?}.",
key_kind,
)))
}
pub(in crate::executor) fn op_length(&mut self) -> Result<(), VMError> {
let (bits, kind) = self.pop_kinded()?;
let result = match kind {
NativeKind::Ptr(HeapKind::TypedObject) => {
if bits == 0 {
Err(VMError::RuntimeError(
"length() on null TypedObject".to_string(),
))
} else {
let storage: Arc<shape_value::heap_value::TypedObjectStorage> =
unsafe { Arc::from_raw(bits as *const _) };
let len = storage.slots.len() as i64;
let _ = Arc::into_raw(storage);
self.push_kinded(len as u64, NativeKind::Int64)
}
}
NativeKind::Ptr(HeapKind::TypedArray) => {
if bits == 0 {
Err(VMError::RuntimeError(
"length() on null TypedArray".to_string(),
))
} else {
use crate::executor::v2_handlers::v2_array_detect::as_v2_typed_array;
match as_v2_typed_array(bits, kind) {
Some(view) => {
self.push_kinded(view.len as u64, NativeKind::Int64)
}
None => Err(VMError::TypeError {
expected: "array, object, or string",
got: "scalar",
}),
}
}
}
NativeKind::String | NativeKind::Ptr(HeapKind::String) => {
if bits == 0 {
Err(VMError::RuntimeError(
"length() on null string".to_string(),
))
} else {
let s: Arc<String> = unsafe { Arc::from_raw(bits as *const String) };
let len = s.chars().count() as i64;
let _ = Arc::into_raw(s);
self.push_kinded(len as u64, NativeKind::Int64)
}
}
NativeKind::Ptr(HeapKind::HashMap) => {
if bits == 0 {
Err(VMError::RuntimeError(
"length() on null HashMap".to_string(),
))
} else {
let map: Arc<HashMapKindedRef> = unsafe {
Arc::from_raw(bits as *const HashMapKindedRef)
};
let len = map.len() as i64;
let _ = Arc::into_raw(map);
self.push_kinded(len as u64, NativeKind::Int64)
}
}
NativeKind::Ptr(_) => Err(VMError::TypeError {
expected: "array, object, string, or hashmap",
got: "heap value without length semantics",
}),
_ => Err(VMError::TypeError {
expected: "array, object, or string",
got: "scalar",
}),
};
drop_with_kind(bits, kind);
result
}
}
#[inline]
fn numeric_index_from_kinded(bits: u64, kind: NativeKind) -> Result<usize, VMError> {
let i = match kind {
NativeKind::Int64 => bits as i64,
NativeKind::Int8 => (bits as i8) as i64,
NativeKind::Int16 => (bits as i16) as i64,
NativeKind::Int32 => (bits as i32) as i64,
NativeKind::UInt8 => (bits as u8) as i64,
NativeKind::UInt16 => (bits as u16) as i64,
NativeKind::UInt32 => (bits as u32) as i64,
NativeKind::UInt64 => bits as i64,
NativeKind::Float64 => {
let f = f64::from_bits(bits);
if !f.is_finite() {
return Err(VMError::TypeError {
expected: "finite numeric index",
got: "non-finite float",
});
}
f as i64
}
NativeKind::Bool => {
if bits != 0 {
1
} else {
0
}
}
_ => {
return Err(VMError::TypeError {
expected: "numeric array index",
got: "non-numeric key kind",
});
}
};
if i < 0 {
return Err(VMError::IndexOutOfBounds {
index: i as i32,
length: 0,
});
}
Ok(i as usize)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::executor::VMConfig;
use shape_value::ValueSlot;
use shape_value::heap_value::TypedObjectStorage;
#[test]
fn length_typed_object_empty() {
let mut vm = VirtualMachine::new(VMConfig::default());
let ptr = TypedObjectStorage::_new(
0,
Vec::<ValueSlot>::new().into_boxed_slice(),
0,
Arc::from(Vec::<NativeKind>::new().into_boxed_slice()),
);
let bits = ptr as u64;
vm.push_kinded(bits, NativeKind::Ptr(HeapKind::TypedObject))
.unwrap();
vm.op_length().unwrap();
let (len_bits, len_kind) = vm.pop_kinded().unwrap();
assert_eq!(len_bits, 0);
assert_eq!(len_kind, NativeKind::Int64);
}
#[test]
fn length_string_returns_chars_count() {
let mut vm = VirtualMachine::new(VMConfig::default());
let s: Arc<String> = Arc::new("hello".to_string());
let bits = Arc::into_raw(s) as u64;
vm.push_kinded(bits, NativeKind::String).unwrap();
vm.op_length().unwrap();
let (len_bits, len_kind) = vm.pop_kinded().unwrap();
assert_eq!(len_bits, 5);
assert_eq!(len_kind, NativeKind::Int64);
}
#[test]
fn set_prop_typed_object_int_field() {
use shape_runtime::type_schema::{FieldType, TypeSchema};
let mut vm = VirtualMachine::new(VMConfig::default());
let schema = TypeSchema::new(
"Probe".to_string(),
vec![("x".to_string(), FieldType::I64)],
);
let schema_id = schema.id;
vm.program.type_schema_registry.register(schema);
let slot = ValueSlot::from_raw(7u64);
let ptr = TypedObjectStorage::_new(
schema_id as u64,
vec![slot].into_boxed_slice(),
0, Arc::from(vec![NativeKind::Int64].into_boxed_slice()),
);
let recv_bits = ptr as u64;
vm.push_kinded(recv_bits, NativeKind::Ptr(HeapKind::TypedObject))
.unwrap();
let key_arc: Arc<String> = Arc::new("x".to_string());
let key_bits = Arc::into_raw(key_arc) as u64;
vm.push_kinded(key_bits, NativeKind::String).unwrap();
vm.push_kinded(42u64, NativeKind::Int64).unwrap();
vm.op_set_prop().unwrap();
let (obj_bits_back, obj_kind_back) = vm.pop_kinded().unwrap();
assert_eq!(obj_kind_back, NativeKind::Ptr(HeapKind::TypedObject));
let storage_back: &TypedObjectStorage =
unsafe { &*(obj_bits_back as *const TypedObjectStorage) };
assert_eq!(storage_back.slots[0].raw(), 42u64);
crate::executor::vm_impl::stack::drop_with_kind(obj_bits_back, obj_kind_back);
}
#[test]
fn set_prop_typed_object_non_string_key_errors() {
let mut vm = VirtualMachine::new(VMConfig::default());
let ptr = TypedObjectStorage::_new(
0,
Vec::<ValueSlot>::new().into_boxed_slice(),
0,
Arc::from(Vec::<NativeKind>::new().into_boxed_slice()),
);
let recv_bits = ptr as u64;
vm.push_kinded(recv_bits, NativeKind::Ptr(HeapKind::TypedObject))
.unwrap();
vm.push_kinded(0u64, NativeKind::Int64).unwrap(); vm.push_kinded(1u64, NativeKind::Int64).unwrap();
let err = vm.op_set_prop().unwrap_err();
assert!(matches!(err, VMError::TypeError { .. }));
}
}