pub mod method_registry;
pub mod raw_helpers;
pub mod property_access;
pub mod object_creation;
pub mod object_operations;
pub mod array_operations;
pub mod array_aggregation;
pub mod array_basic;
pub mod array_joins;
pub mod array_query;
pub mod array_sets;
pub mod array_sort;
pub mod array_transform;
pub mod datatable_methods;
pub mod indexed_table_methods;
pub mod hashmap_methods;
pub mod deque_methods;
pub mod priority_queue_methods;
pub mod set_methods;
pub mod number_methods;
pub mod string_methods;
pub mod content_methods;
pub mod datetime_methods;
pub mod instant_methods;
pub mod matrix_methods;
pub mod iterator_methods;
pub mod range_methods;
pub mod typed_array_methods;
pub mod concurrency_methods;
pub mod channel_methods;
pub mod concat;
pub mod typed_access;
use crate::{
bytecode::{Instruction, OpCode, Operand},
executor::VirtualMachine,
};
use shape_value::{HeapKind, HeapValue, KindedSlot, NativeKind, TemporalData, ValueSlot, VMError};
fn typed_array_method_registry(
elem_type: crate::executor::v2_handlers::v2_array_detect::V2ElemType,
method_name: &str,
) -> Option<method_registry::MethodHandler> {
use crate::executor::v2_handlers::v2_array_detect::V2ElemType;
match elem_type {
V2ElemType::I64
| V2ElemType::I32
| V2ElemType::I8
| V2ElemType::U8
| V2ElemType::I16
| V2ElemType::U16
| V2ElemType::U32
| V2ElemType::F64
| V2ElemType::F32 => None,
V2ElemType::Bool => method_registry::BOOL_ARRAY_METHODS
.get(method_name)
.copied(),
V2ElemType::Char
| V2ElemType::String
| V2ElemType::Decimal
| V2ElemType::TypedObject => None,
}
}
impl VirtualMachine {
#[inline(always)]
pub(in crate::executor) fn exec_objects(
&mut self,
instruction: &Instruction,
ctx: Option<&mut shape_runtime::context::ExecutionContext>,
) -> Result<(), VMError> {
use OpCode::*;
match instruction.opcode {
NewArray => self.op_new_array(instruction)?,
NewTypedArray => self.op_new_typed_array(instruction)?,
NewMatrix => self.op_new_matrix(instruction)?,
NewObject => self.op_new_object(instruction)?,
GetProp => self.op_get_prop(ctx)?,
SetProp => self.op_set_prop()?,
SetLocalIndex => self.op_set_local_index(instruction)?,
SetModuleBindingIndex => self.op_set_module_binding_index(instruction)?,
Length => self.op_length()?,
ArrayPush => self.op_array_push()?,
ArrayPushLocal => self.op_array_push_local(instruction)?,
ArrayPop => self.op_array_pop()?,
MakeClosure => self.op_make_closure(instruction)?,
MergeObject => self.op_merge_object()?,
NewTypedObject => self.op_new_typed_object(instruction)?,
TypedMergeObject => self.op_typed_merge_object(instruction)?,
WrapTypeAnnotation => self.op_wrap_type_annotation(instruction)?,
SliceAccess => self.op_slice_access()?,
MakeRange => self.op_make_range()?,
_ => unreachable!(
"exec_objects called with non-object opcode: {:?}",
instruction.opcode
),
}
Ok(())
}
fn op_wrap_type_annotation(&mut self, _instruction: &Instruction) -> Result<(), VMError> {
Err(VMError::NotImplemented(
"SURFACE: WrapTypeAnnotation depends on the deleted ValueWord wrapper \
type. Annotation wrapping needs a kinded redesign (ADR-006 §2.7.6 \
/ Q8) — see playbook §8 cross-cluster cascade. D-objects-mod scope \
does not include the compiler emit site."
.into(),
))
}
pub fn op_call_method(
&mut self,
instruction: &Instruction,
ctx: Option<&mut shape_runtime::context::ExecutionContext>,
) -> Result<(), VMError> {
let (arg_count, string_id, _method_id, _receiver_type_tag) = match instruction.operand {
Some(Operand::TypedMethodCall {
method_id,
arg_count,
string_id,
receiver_type_tag,
}) => (
arg_count as usize,
string_id as usize,
method_id,
receiver_type_tag,
),
_ => return Err(VMError::InvalidOperand),
};
if self.sp >= arg_count + 1 {
let receiver_idx_check = self.sp - arg_count - 1;
let (_, receiver_kind_peek) = self.stack_read_kinded_raw(receiver_idx_check);
if receiver_kind_peek
== NativeKind::Ptr(shape_value::HeapKind::TraitObject)
{
return self.exec_trait_object_ops(
&Instruction::new(
crate::bytecode::OpCode::DynMethodCall,
Some(Operand::TypedMethodCall {
method_id: _method_id,
arg_count: arg_count as u16,
string_id: string_id as u16,
receiver_type_tag: _receiver_type_tag,
}),
),
ctx,
);
}
}
let total = arg_count + 1;
let mut args: Vec<KindedSlot> = Vec::with_capacity(total);
for _ in 0..total {
let (bits, kind) = self.pop_kinded()?;
args.push(KindedSlot::new(ValueSlot::from_raw(bits), kind));
}
args.reverse();
let method_name: String = self
.program
.strings
.get(string_id)
.cloned()
.ok_or_else(|| {
VMError::RuntimeError(format!(
"op_call_method: string_id {} out of bounds (pool size {})",
string_id,
self.program.strings.len()
))
})?;
let result = self.dispatch_method_kinded(&args, &method_name, ctx)?;
self.push_kinded(result.raw(), result.kind())?;
std::mem::forget(result);
Ok(())
}
pub(crate) fn dispatch_method_kinded(
&mut self,
args: &[KindedSlot],
method_name: &str,
ctx: Option<&mut shape_runtime::context::ExecutionContext>,
) -> Result<KindedSlot, VMError> {
if let NativeKind::Ptr(HeapKind::TypedObject) = args[0].kind {
if let Some(function_id) = self.resolve_typed_object_ufcs(args, method_name) {
return self.invoke_typed_object_ufcs(args, function_id, ctx);
}
}
let handler = self.resolve_method_handler(args, method_name)?;
handler(self, args, ctx)
}
fn resolve_typed_object_ufcs(
&self,
args: &[KindedSlot],
method_name: &str,
) -> Option<u16> {
let receiver_bits = args[0].slot.raw();
if receiver_bits == 0 {
return None;
}
let schema_id = unsafe {
(*(receiver_bits as *const shape_value::TypedObjectStorage)).schema_id
};
let concrete_type_name = self
.program
.type_schema_registry
.get_by_id(schema_id as u32)
.map(|schema| schema.name.clone())?;
let function_name = format!("{}::{}", concrete_type_name, method_name);
self.function_name_index.get(&function_name).copied()
}
fn invoke_typed_object_ufcs(
&mut self,
args: &[KindedSlot],
function_id: u16,
ctx: Option<&mut shape_runtime::context::ExecutionContext>,
) -> Result<KindedSlot, VMError> {
let mut call_args: Vec<KindedSlot> = Vec::with_capacity(args.len());
for slot in args.iter() {
let bits = slot.slot.raw();
let kind = slot.kind;
crate::executor::vm_impl::stack::clone_with_kind(bits, kind);
call_args.push(KindedSlot::new(ValueSlot::from_raw(bits), kind));
}
self.execute_function_by_id(function_id, call_args, ctx)
}
fn resolve_method_handler(
&self,
args: &[KindedSlot],
method_name: &str,
) -> Result<method_registry::MethodHandler, VMError> {
use crate::executor::v2_handlers::v2_array_detect::as_v2_typed_array;
let receiver = &args[0];
let kind = receiver.kind;
let scalar_handler: Option<method_registry::MethodHandler> = match kind {
NativeKind::Float64
| NativeKind::NullableFloat64
| NativeKind::Int8
| NativeKind::NullableInt8
| NativeKind::UInt8
| NativeKind::NullableUInt8
| NativeKind::Int16
| NativeKind::NullableInt16
| NativeKind::UInt16
| NativeKind::NullableUInt16
| NativeKind::Int32
| NativeKind::NullableInt32
| NativeKind::UInt32
| NativeKind::NullableUInt32
| NativeKind::Int64
| NativeKind::NullableInt64
| NativeKind::NullableUInt64
| NativeKind::IntSize
| NativeKind::NullableIntSize
| NativeKind::UIntSize
| NativeKind::NullableUIntSize => method_registry::NUMBER_METHODS.get(method_name).copied(),
NativeKind::Bool => method_registry::BOOL_METHODS.get(method_name).copied(),
NativeKind::String => method_registry::STRING_METHODS.get(method_name).copied(),
NativeKind::Float32 => method_registry::NUMBER_METHODS.get(method_name).copied(),
NativeKind::Char => method_registry::CHAR_METHODS.get(method_name).copied(),
NativeKind::StringV2 => method_registry::STRING_METHODS.get(method_name).copied(),
NativeKind::DecimalV2 => method_registry::NUMBER_METHODS.get(method_name).copied(),
NativeKind::Ptr(HeapKind::TypedArray) => {
let bits = receiver.slot.raw();
if let Some(view) = as_v2_typed_array(bits, kind) {
typed_array_method_registry(view.elem_type, method_name)
.or_else(|| method_registry::ARRAY_METHODS.get(method_name).copied())
} else {
method_registry::ARRAY_METHODS.get(method_name).copied()
}
}
NativeKind::UInt64 => {
method_registry::NUMBER_METHODS.get(method_name).copied()
}
NativeKind::Ptr(_) => None,
NativeKind::Null => None,
};
if let Some(h) = scalar_handler {
return Ok(h);
}
if let NativeKind::Ptr(hk) = kind {
let heap_handler: Option<method_registry::MethodHandler> = match hk {
HeapKind::String => method_registry::STRING_METHODS.get(method_name).copied(),
HeapKind::Char => method_registry::CHAR_METHODS.get(method_name).copied(),
HeapKind::HashMap => method_registry::HASHMAP_METHODS.get(method_name).copied(),
HeapKind::HashSet => method_registry::SET_METHODS.get(method_name).copied(),
HeapKind::DataTable => method_registry::DATATABLE_METHODS
.get(method_name)
.copied(),
HeapKind::Iterator => method_registry::ITERATOR_METHODS.get(method_name).copied(),
HeapKind::Instant => method_registry::INSTANT_METHODS.get(method_name).copied(),
HeapKind::Content => method_registry::CONTENT_METHODS.get(method_name).copied(),
HeapKind::Decimal => method_registry::NUMBER_METHODS.get(method_name).copied(),
HeapKind::BigInt => method_registry::NUMBER_METHODS.get(method_name).copied(),
HeapKind::TypedArray => {
method_registry::ARRAY_METHODS.get(method_name).copied()
}
HeapKind::Matrix => method_registry::MATRIX_METHODS.get(method_name).copied(),
HeapKind::MatrixSlice => method_registry::FLOAT_ARRAY_METHODS
.get(method_name)
.copied(),
HeapKind::Temporal => {
let bits = receiver.slot.raw();
if bits == 0 {
None
} else {
let td: &TemporalData =
unsafe { &*(bits as *const TemporalData) };
match td {
TemporalData::DateTime(_) => {
method_registry::DATETIME_METHODS
.get(method_name)
.copied()
}
TemporalData::TimeSpan(_) | TemporalData::Duration(_) => {
method_registry::TIMESPAN_METHODS
.get(method_name)
.copied()
}
_ => None,
}
}
}
HeapKind::TypedObject => {
method_registry::DATATABLE_METHODS
.get(method_name)
.copied()
}
HeapKind::TableView => method_registry::DATATABLE_METHODS
.get(method_name)
.copied(),
HeapKind::Deque => method_registry::DEQUE_METHODS.get(method_name).copied(),
HeapKind::Channel => method_registry::CHANNEL_METHODS.get(method_name).copied(),
HeapKind::PriorityQueue => method_registry::PRIORITY_QUEUE_METHODS
.get(method_name)
.copied(),
HeapKind::Mutex => method_registry::MUTEX_METHODS.get(method_name).copied(),
HeapKind::Atomic => method_registry::ATOMIC_METHODS.get(method_name).copied(),
HeapKind::Lazy => method_registry::LAZY_METHODS.get(method_name).copied(),
HeapKind::Range => method_registry::RANGE_METHODS.get(method_name).copied(),
HeapKind::Result | HeapKind::Option => None,
HeapKind::Closure
| HeapKind::Future
| HeapKind::Reference
| HeapKind::SharedCell
| HeapKind::FilterExpr
| HeapKind::TraitObject
| HeapKind::IoHandle
| HeapKind::TaskGroup
| HeapKind::NativeView
| HeapKind::NativeScalar
| HeapKind::ModuleFn => None,
};
if let Some(h) = heap_handler {
return Ok(h);
}
}
Err(VMError::RuntimeError(format!(
"no method '{}' on receiver kind {:?}",
method_name, kind
)))
}
pub(in crate::executor) fn op_make_range(&mut self) -> Result<(), VMError> {
use shape_value::{KindedSlot, NativeKind, ValueSlot, heap_value::RangeData};
let incl_kinded = {
let (bits, kind) = self.pop_kinded()?;
KindedSlot::new(ValueSlot::from_raw(bits), kind)
};
let end_kinded = {
let (bits, kind) = self.pop_kinded()?;
KindedSlot::new(ValueSlot::from_raw(bits), kind)
};
let start_kinded = {
let (bits, kind) = self.pop_kinded()?;
KindedSlot::new(ValueSlot::from_raw(bits), kind)
};
let inclusive = match incl_kinded.kind() {
NativeKind::Bool => incl_kinded.slot().as_bool(),
_ => {
return Err(VMError::RuntimeError(
"MakeRange: inclusive flag operand must be Bool (kind-source bug \
at compile site — `compiler/expressions/misc.rs` emits a \
`PushConst<Bool>` for the inclusive flag)".into(),
));
}
};
let to_i64 = |k: &KindedSlot, side: &str| -> Result<i64, VMError> {
match k.kind() {
NativeKind::Int64 => Ok(k.slot().as_i64()),
NativeKind::Bool if k.slot().raw() == 0 => Err(VMError::NotImplemented(format!(
"MakeRange: open-range bound on {side} side (PushNull placeholder) — \
SURFACE: open ranges (`..n` / `n..` / `..`) need the iterator-tier \
infinite-iter semantic per ADR-006 §2.7.23 follow-up. Closed ranges \
(`start..end` / `start..=end`) work today.",
))),
other => Err(VMError::NotImplemented(format!(
"MakeRange: cross-kind bound on {side} side (got {other:?}) — \
SURFACE: post-strict-typing RangeData only models i64 ranges at \
landing. Cross-kind bounds (Decimal, BigInt, Float64, NativeScalar) \
tracked as ADR-006 §2.7.23 follow-up.",
))),
}
};
let start = to_i64(&start_kinded, "start")?;
let end = to_i64(&end_kinded, "end")?;
let range = std::sync::Arc::new(RangeData::new(start, end, 1, inclusive));
self.push_kinded_slot(KindedSlot::from_range(range))?;
Ok(())
}
}