use core::cmp::Ordering;
use core::fmt;
use core::mem::replace;
use core::ptr::NonNull;
use ::rust_alloc::sync::Arc;
use crate as rune;
use crate::alloc::prelude::*;
use crate::alloc::{self, String};
use crate::hash;
use crate::hash::{Hash, IntoHash, ToTypeHash};
use crate::modules::{option, result};
use crate::runtime;
mod ops;
use self::ops::*;
use super::{
budget, Args, Awaited, BorrowMut, Bytes, Call, ControlFlow, DynArgs, DynGuardedArgs, Dynamic,
Format, FormatSpec, Formatter, FromValue, Function, Future, Generator, GeneratorState,
GuardedArgs, Inline, Inst, InstAddress, InstArithmeticOp, InstBitwiseOp, InstOp, InstRange,
InstShiftOp, InstTarget, InstValue, InstVariant, Object, Output, OwnedTuple, Pair, Panic,
Protocol, ProtocolCaller, Range, RangeFrom, RangeFull, RangeInclusive, RangeTo,
RangeToInclusive, Repr, RttiKind, RuntimeContext, Select, SelectFuture, Stack, Stream, Type,
TypeCheck, TypeHash, TypeInfo, TypeOf, Unit, UnitFn, UnitStorage, Value, Vec, VmDiagnostics,
VmDiagnosticsObj, VmError, VmErrorKind, VmExecution, VmHalt, VmIntegerRepr, VmResult,
VmSendExecution,
};
#[inline(always)]
fn take(value: &mut Value) -> Value {
replace(value, Value::empty())
}
#[inline(always)]
fn consume(value: &mut Value) {
*value = Value::empty();
}
#[derive(Debug, Clone, Copy)]
pub enum Isolated {
Isolated,
None,
}
impl Isolated {
#[inline]
pub(crate) fn new(value: bool) -> Self {
if value {
Self::Isolated
} else {
Self::None
}
}
#[inline]
pub(crate) fn then_some<T>(self, value: T) -> Option<T> {
match self {
Self::Isolated => Some(value),
Self::None => None,
}
}
}
impl fmt::Display for Isolated {
#[inline]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Isolated => write!(f, "isolated"),
Self::None => write!(f, "none"),
}
}
}
fn err<T, E>(error: E) -> VmResult<T>
where
VmErrorKind: From<E>,
{
VmResult::err(error)
}
#[derive(Debug)]
pub(crate) enum CallResultOnly<T> {
Ok(T),
Unsupported(Value),
}
#[derive(Debug)]
pub(crate) enum CallResult<T> {
Ok(T),
Frame,
Unsupported(Value),
}
#[derive(Debug)]
pub struct Vm {
context: Arc<RuntimeContext>,
unit: Arc<Unit>,
ip: usize,
last_ip_len: u8,
stack: Stack,
call_frames: alloc::Vec<CallFrame>,
}
impl Vm {
pub const fn new(context: Arc<RuntimeContext>, unit: Arc<Unit>) -> Self {
Self::with_stack(context, unit, Stack::new())
}
pub const fn with_stack(context: Arc<RuntimeContext>, unit: Arc<Unit>, stack: Stack) -> Self {
Self {
context,
unit,
ip: 0,
last_ip_len: 0,
stack,
call_frames: alloc::Vec::new(),
}
}
pub fn without_runtime(unit: Arc<Unit>) -> Self {
Self::new(Default::default(), unit)
}
pub fn is_same(&self, context: &Arc<RuntimeContext>, unit: &Arc<Unit>) -> bool {
Arc::ptr_eq(&self.context, context) && Arc::ptr_eq(&self.unit, unit)
}
pub fn is_same_context(&self, context: &Arc<RuntimeContext>) -> bool {
Arc::ptr_eq(&self.context, context)
}
pub fn is_same_unit(&self, unit: &Arc<Unit>) -> bool {
Arc::ptr_eq(&self.unit, unit)
}
#[inline]
pub fn set_ip(&mut self, ip: usize) {
self.ip = ip;
}
#[inline]
pub fn call_frames(&self) -> &[CallFrame] {
&self.call_frames
}
#[inline]
pub fn stack(&self) -> &Stack {
&self.stack
}
#[inline]
pub fn stack_mut(&mut self) -> &mut Stack {
&mut self.stack
}
#[inline]
pub fn context_mut(&mut self) -> &mut Arc<RuntimeContext> {
&mut self.context
}
#[inline]
pub fn context(&self) -> &Arc<RuntimeContext> {
&self.context
}
#[inline]
pub fn unit_mut(&mut self) -> &mut Arc<Unit> {
&mut self.unit
}
#[inline]
pub fn unit(&self) -> &Arc<Unit> {
&self.unit
}
#[inline]
pub fn ip(&self) -> usize {
self.ip
}
#[inline]
pub fn last_ip(&self) -> usize {
self.ip.wrapping_sub(self.last_ip_len as usize)
}
pub fn clear(&mut self) {
self.ip = 0;
self.stack.clear();
self.call_frames.clear();
}
pub fn lookup_function<N>(&self, name: N) -> Result<Function, VmError>
where
N: ToTypeHash,
{
Ok(self.lookup_function_by_hash(name.to_type_hash())?)
}
pub(crate) fn into_execution(self) -> VmExecution<Self> {
VmExecution::new(self)
}
pub fn complete(self) -> Result<Value, VmError> {
self.into_execution().complete().into_result()
}
pub async fn async_complete(self) -> Result<Value, VmError> {
self.into_execution().async_complete().await.into_result()
}
pub fn execute(
&mut self,
name: impl ToTypeHash,
args: impl Args,
) -> Result<VmExecution<&mut Self>, VmError> {
self.set_entrypoint(name, args.count())?;
args.into_stack(&mut self.stack).into_result()?;
Ok(VmExecution::new(self))
}
pub fn send_execute(
mut self,
name: impl ToTypeHash,
args: impl Args + Send,
) -> Result<VmSendExecution, VmError> {
self.stack.clear();
self.set_entrypoint(name, args.count())?;
args.into_stack(&mut self.stack).into_result()?;
Ok(VmSendExecution(VmExecution::new(self)))
}
pub fn call(
&mut self,
name: impl ToTypeHash,
args: impl GuardedArgs,
) -> Result<Value, VmError> {
self.set_entrypoint(name, args.count())?;
let guard = unsafe { args.guarded_into_stack(&mut self.stack).into_result()? };
let value = {
let vm = ClearStack(self);
VmExecution::new(&mut *vm.0).complete().into_result()?
};
drop(guard);
Ok(value)
}
pub fn call_with_diagnostics(
&mut self,
name: impl ToTypeHash,
args: impl GuardedArgs,
diagnostics: Option<&mut dyn VmDiagnostics>,
) -> Result<Value, VmError> {
self.set_entrypoint(name, args.count())?;
let guard = unsafe { args.guarded_into_stack(&mut self.stack).into_result()? };
let value = {
let vm = ClearStack(self);
VmExecution::new(&mut *vm.0)
.complete_with_diagnostics(diagnostics)
.into_result()?
};
drop(guard);
Ok(value)
}
pub async fn async_call<A, N>(&mut self, name: N, args: A) -> Result<Value, VmError>
where
N: ToTypeHash,
A: GuardedArgs,
{
self.set_entrypoint(name, args.count())?;
let guard = unsafe { args.guarded_into_stack(&mut self.stack).into_result()? };
let value = {
let vm = ClearStack(self);
VmExecution::new(&mut *vm.0)
.async_complete()
.await
.into_result()?
};
drop(guard);
Ok(value)
}
fn set_entrypoint<N>(&mut self, name: N, count: usize) -> Result<(), VmErrorKind>
where
N: ToTypeHash,
{
let hash = name.to_type_hash();
let Some(info) = self.unit.function(&hash) else {
return Err(if let Some(item) = name.to_item()? {
VmErrorKind::MissingEntry { hash, item }
} else {
VmErrorKind::MissingEntryHash { hash }
});
};
let offset = match info {
UnitFn::Offset {
offset,
args: expected,
..
} => {
check_args(count, *expected)?;
*offset
}
_ => {
return Err(VmErrorKind::MissingFunction { hash });
}
};
self.ip = offset;
self.stack.clear();
self.call_frames.clear();
Ok(())
}
#[inline]
pub(crate) fn call_instance_fn(
&mut self,
isolated: Isolated,
target: Value,
hash: impl ToTypeHash,
args: &mut dyn DynArgs,
out: Output,
) -> VmResult<CallResult<()>> {
let count = args.count().wrapping_add(1);
let type_hash = target.type_hash();
let hash = Hash::associated_function(type_hash, hash.to_type_hash());
self.call_hash_with(isolated, hash, target, args, count, out)
}
#[inline]
fn call_field_fn(
&mut self,
protocol: impl IntoHash,
target: Value,
name: impl IntoHash,
args: &mut dyn DynArgs,
out: Output,
) -> VmResult<CallResult<()>> {
let count = args.count().wrapping_add(1);
let hash = Hash::field_function(protocol, target.type_hash(), name);
self.call_hash_with(Isolated::None, hash, target, args, count, out)
}
#[inline]
fn call_index_fn(
&mut self,
protocol: impl IntoHash,
target: Value,
index: usize,
args: &mut dyn DynArgs,
out: Output,
) -> VmResult<CallResult<()>> {
let count = args.count().wrapping_add(1);
let hash = Hash::index_function(protocol, target.type_hash(), Hash::index(index));
self.call_hash_with(Isolated::None, hash, target, args, count, out)
}
fn called_function_hook(&self, hash: Hash) -> VmResult<()> {
runtime::env::exclusive(|_, _, diagnostics| {
if let Some(diagnostics) = diagnostics {
vm_try!(diagnostics.function_used(hash, self.ip()));
}
VmResult::Ok(())
})
}
#[inline(never)]
fn call_hash_with(
&mut self,
isolated: Isolated,
hash: Hash,
target: Value,
args: &mut dyn DynArgs,
count: usize,
out: Output,
) -> VmResult<CallResult<()>> {
if let Some(handler) = self.context.function(&hash) {
let addr = self.stack.addr();
vm_try!(self.called_function_hook(hash));
vm_try!(self.stack.push(target));
vm_try!(args.push_to_stack(&mut self.stack));
let result = handler(&mut self.stack, addr, count, out);
self.stack.truncate(addr);
vm_try!(result);
return VmResult::Ok(CallResult::Ok(()));
}
if let Some(UnitFn::Offset {
offset,
call,
args: expected,
..
}) = self.unit.function(&hash)
{
vm_try!(check_args(count, *expected));
let addr = self.stack.addr();
vm_try!(self.called_function_hook(hash));
vm_try!(self.stack.push(target));
vm_try!(args.push_to_stack(&mut self.stack));
let result = self.call_offset_fn(*offset, *call, addr, count, isolated, out);
if vm_try!(result) {
self.stack.truncate(addr);
return VmResult::Ok(CallResult::Frame);
} else {
return VmResult::Ok(CallResult::Ok(()));
}
}
VmResult::Ok(CallResult::Unsupported(target))
}
#[cfg_attr(feature = "bench", inline(never))]
fn internal_cmp(
&mut self,
match_ordering: fn(Ordering) -> bool,
lhs: InstAddress,
rhs: InstAddress,
out: Output,
) -> VmResult<()> {
let rhs = self.stack.at(rhs);
let lhs = self.stack.at(lhs);
let ordering = match (lhs.as_inline_unchecked(), rhs.as_inline_unchecked()) {
(Some(lhs), Some(rhs)) => vm_try!(lhs.partial_cmp(rhs)),
_ => {
let lhs = lhs.clone();
let rhs = rhs.clone();
vm_try!(Value::partial_cmp_with(&lhs, &rhs, self))
}
};
vm_try!(out.store(&mut self.stack, || match ordering {
Some(ordering) => match_ordering(ordering),
None => false,
}));
VmResult::Ok(())
}
#[tracing::instrument(skip(self), fields(call_frames = self.call_frames.len(), top = self.stack.top(), stack = self.stack.len(), self.ip))]
pub(crate) fn push_call_frame(
&mut self,
ip: usize,
addr: InstAddress,
args: usize,
isolated: Isolated,
out: Output,
) -> Result<(), VmErrorKind> {
tracing::trace!("pushing call frame");
let top = self.stack.swap_top(addr, args)?;
let ip = replace(&mut self.ip, ip);
let frame = CallFrame {
ip,
top,
isolated,
out,
};
self.call_frames.try_push(frame)?;
Ok(())
}
#[tracing::instrument(skip(self), fields(call_frames = self.call_frames.len(), top = self.stack.top(), stack = self.stack.len(), self.ip))]
pub(crate) fn pop_call_frame_from_call(&mut self) -> Option<usize> {
tracing::trace!("popping call frame from call");
let frame = self.call_frames.pop()?;
tracing::trace!(?frame);
self.stack.pop_stack_top(frame.top);
Some(replace(&mut self.ip, frame.ip))
}
#[tracing::instrument(skip(self), fields(call_frames = self.call_frames.len(), top = self.stack.top(), stack = self.stack.len(), self.ip))]
pub(crate) fn pop_call_frame(&mut self) -> (Isolated, Option<Output>) {
tracing::trace!("popping call frame");
let Some(frame) = self.call_frames.pop() else {
self.stack.pop_stack_top(0);
return (Isolated::Isolated, None);
};
tracing::trace!(?frame);
self.stack.pop_stack_top(frame.top);
self.ip = frame.ip;
(frame.isolated, Some(frame.out))
}
fn try_object_like_index_get(target: &Value, field: &str) -> VmResult<Option<Value>> {
match target.as_ref() {
Repr::Dynamic(data) if matches!(data.rtti().kind, RttiKind::Struct) => {
let Some(value) = vm_try!(data.get_field_ref(field)) else {
return err(VmErrorKind::MissingField {
target: data.type_info(),
field: vm_try!(field.try_to_owned()),
});
};
VmResult::Ok(Some(value.clone()))
}
Repr::Any(value) => match value.type_hash() {
Object::HASH => {
let target = vm_try!(value.borrow_ref::<Object>());
let Some(value) = target.get(field) else {
return err(VmErrorKind::MissingField {
target: TypeInfo::any::<Object>(),
field: vm_try!(field.try_to_owned()),
});
};
VmResult::Ok(Some(value.clone()))
}
_ => VmResult::Ok(None),
},
_ => VmResult::Ok(None),
}
}
fn try_tuple_like_index_get(target: &Value, index: usize) -> VmResult<Option<Value>> {
let result = match target.as_ref() {
Repr::Inline(target) => match target {
Inline::Unit => Err(target.type_info()),
_ => return VmResult::Ok(None),
},
Repr::Dynamic(data) if matches!(data.rtti().kind, RttiKind::Tuple) => {
match vm_try!(data.get_ref(index)) {
Some(value) => Ok(value.clone()),
None => Err(data.type_info()),
}
}
Repr::Dynamic(data) => Err(data.type_info()),
Repr::Any(target) => match target.type_hash() {
Result::<Value, Value>::HASH => {
match (
index,
&*vm_try!(target.borrow_ref::<Result<Value, Value>>()),
) {
(0, Ok(value)) => Ok(value.clone()),
(0, Err(value)) => Ok(value.clone()),
_ => Err(target.type_info()),
}
}
Option::<Value>::HASH => {
match (index, &*vm_try!(target.borrow_ref::<Option<Value>>())) {
(0, Some(value)) => Ok(value.clone()),
_ => Err(target.type_info()),
}
}
GeneratorState::HASH => match (index, &*vm_try!(target.borrow_ref())) {
(0, GeneratorState::Yielded(value)) => Ok(value.clone()),
(0, GeneratorState::Complete(value)) => Ok(value.clone()),
_ => Err(target.type_info()),
},
runtime::Vec::HASH => {
let vec = vm_try!(target.borrow_ref::<runtime::Vec>());
match vec.get(index) {
Some(value) => Ok(value.clone()),
None => Err(target.type_info()),
}
}
runtime::OwnedTuple::HASH => {
let tuple = vm_try!(target.borrow_ref::<runtime::OwnedTuple>());
match tuple.get(index) {
Some(value) => Ok(value.clone()),
None => Err(target.type_info()),
}
}
_ => {
return VmResult::Ok(None);
}
},
};
match result {
Ok(value) => VmResult::Ok(Some(value)),
Err(target) => err(VmErrorKind::MissingIndexInteger {
target,
index: VmIntegerRepr::from(index),
}),
}
}
fn try_tuple_like_index_set(target: &Value, index: usize, from: &Value) -> VmResult<bool> {
match target.as_ref() {
Repr::Inline(target) => match target {
Inline::Unit => VmResult::Ok(false),
_ => VmResult::Ok(false),
},
Repr::Dynamic(data) if matches!(data.rtti().kind, RttiKind::Tuple) => {
if let Some(target) = vm_try!(data.borrow_mut()).get_mut(index) {
target.clone_from(from);
return VmResult::Ok(true);
}
VmResult::Ok(false)
}
Repr::Dynamic(..) => VmResult::Ok(false),
Repr::Any(value) => match value.type_hash() {
Result::<Value, Value>::HASH => {
let mut result = vm_try!(value.borrow_mut::<Result<Value, Value>>());
let target = match &mut *result {
Ok(ok) if index == 0 => ok,
Err(err) if index == 1 => err,
_ => return VmResult::Ok(false),
};
target.clone_from(from);
VmResult::Ok(true)
}
Option::<Value>::HASH => {
let mut option = vm_try!(value.borrow_mut::<Option<Value>>());
let target = match &mut *option {
Some(some) if index == 0 => some,
_ => return VmResult::Ok(false),
};
target.clone_from(from);
VmResult::Ok(true)
}
runtime::Vec::HASH => {
let mut vec = vm_try!(value.borrow_mut::<runtime::Vec>());
if let Some(target) = vec.get_mut(index) {
target.clone_from(from);
return VmResult::Ok(true);
}
VmResult::Ok(false)
}
runtime::OwnedTuple::HASH => {
let mut tuple = vm_try!(value.borrow_mut::<runtime::OwnedTuple>());
if let Some(target) = tuple.get_mut(index) {
target.clone_from(from);
return VmResult::Ok(true);
}
VmResult::Ok(false)
}
_ => VmResult::Ok(false),
},
}
}
fn try_object_slot_index_set(
target: &Value,
field: &str,
value: &Value,
) -> Result<bool, VmErrorKind> {
match target.as_ref() {
Repr::Dynamic(data) if matches!(data.rtti().kind, RttiKind::Struct) => {
if let Some(mut v) = data.get_field_mut(field)? {
v.clone_from(value);
return Ok(true);
}
Err(VmErrorKind::MissingField {
target: data.type_info(),
field: field.try_to_owned()?,
})
}
Repr::Any(target) => match target.type_hash() {
Object::HASH => {
let mut target = target.borrow_mut::<Object>()?;
if let Some(target) = target.get_mut(field) {
target.clone_from(value);
} else {
let key = field.try_to_owned()?;
target.insert(key, value.clone())?;
}
Ok(true)
}
_ => Ok(false),
},
target => Err(VmErrorKind::MissingField {
target: target.type_info(),
field: field.try_to_owned()?,
}),
}
}
fn on_tuple<F, O>(&self, ty: TypeCheck, value: &Value, f: F) -> VmResult<Option<O>>
where
F: FnOnce(&[Value]) -> O,
{
let value = match value.as_ref() {
Repr::Inline(value) => match (ty, value) {
(TypeCheck::Unit, Inline::Unit) => Some(f(&[])),
_ => None,
},
Repr::Any(value) => match (ty, value.type_hash()) {
(TypeCheck::Vec, runtime::Vec::HASH) => {
let vec = vm_try!(value.borrow_ref::<runtime::Vec>());
Some(f(&vec))
}
(TypeCheck::Tuple, runtime::OwnedTuple::HASH) => {
let tuple = vm_try!(value.borrow_ref::<runtime::OwnedTuple>());
Some(f(&tuple))
}
_ => None,
},
_ => None,
};
VmResult::Ok(value)
}
fn as_op(&mut self, lhs: InstAddress, rhs: InstAddress) -> VmResult<Value> {
let b = self.stack.at(rhs);
let a = self.stack.at(lhs);
let Repr::Inline(Inline::Type(ty)) = b.as_ref() else {
return err(VmErrorKind::UnsupportedIs {
value: a.type_info(),
test_type: b.type_info(),
});
};
macro_rules! convert {
($from:ty, $value:expr) => {
match ty.into_hash() {
f64::HASH => Value::from($value as f64),
u64::HASH => Value::from($value as u64),
i64::HASH => Value::from($value as i64),
ty => {
return err(VmErrorKind::UnsupportedAs {
value: TypeInfo::from(<$from as TypeOf>::STATIC_TYPE_INFO),
type_hash: ty,
});
}
}
};
}
let value = match a.as_ref() {
Repr::Inline(Inline::Unsigned(a)) => convert!(u64, *a),
Repr::Inline(Inline::Signed(a)) => convert!(i64, *a),
Repr::Inline(Inline::Float(a)) => convert!(f64, *a),
value => {
return err(VmErrorKind::UnsupportedAs {
value: value.type_info(),
type_hash: ty.into_hash(),
});
}
};
VmResult::Ok(value)
}
fn test_is_instance(&mut self, lhs: InstAddress, rhs: InstAddress) -> VmResult<bool> {
let b = self.stack.at(rhs);
let a = self.stack.at(lhs);
let Some(Inline::Type(ty)) = b.as_inline() else {
return err(VmErrorKind::UnsupportedIs {
value: a.type_info(),
test_type: b.type_info(),
});
};
VmResult::Ok(a.type_hash() == ty.into_hash())
}
fn internal_bool(
&mut self,
bool_op: impl FnOnce(bool, bool) -> bool,
op: &'static str,
lhs: InstAddress,
rhs: InstAddress,
out: Output,
) -> VmResult<()> {
let rhs = self.stack.at(rhs);
let lhs = self.stack.at(lhs);
let inline = match (lhs.as_ref(), rhs.as_ref()) {
(Repr::Inline(Inline::Bool(lhs)), Repr::Inline(Inline::Bool(rhs))) => {
let value = bool_op(*lhs, *rhs);
Inline::Bool(value)
}
(lhs, rhs) => {
return err(VmErrorKind::UnsupportedBinaryOperation {
op,
lhs: lhs.type_info(),
rhs: rhs.type_info(),
});
}
};
vm_try!(out.store(&mut self.stack, inline));
VmResult::Ok(())
}
fn call_generator_fn(
&mut self,
offset: usize,
addr: InstAddress,
args: usize,
out: Output,
) -> Result<(), VmErrorKind> {
let values = self.stack.slice_at_mut(addr, args)?;
if let Some(at) = out.as_addr() {
let stack = values.iter_mut().map(take).try_collect::<Stack>()?;
let mut vm = Self::with_stack(self.context.clone(), self.unit.clone(), stack);
vm.ip = offset;
*self.stack.at_mut(at)? = Value::try_from(Generator::new(vm))?;
} else {
values.iter_mut().for_each(consume);
}
Ok(())
}
fn call_stream_fn(
&mut self,
offset: usize,
addr: InstAddress,
args: usize,
out: Output,
) -> Result<(), VmErrorKind> {
let values = self.stack.slice_at_mut(addr, args)?;
if let Some(at) = out.as_addr() {
let stack = values.iter_mut().map(take).try_collect::<Stack>()?;
let mut vm = Self::with_stack(self.context.clone(), self.unit.clone(), stack);
vm.ip = offset;
*self.stack.at_mut(at)? = Value::try_from(Stream::new(vm))?;
} else {
values.iter_mut().for_each(consume);
}
Ok(())
}
fn call_async_fn(
&mut self,
offset: usize,
addr: InstAddress,
args: usize,
out: Output,
) -> Result<(), VmErrorKind> {
let values = self.stack.slice_at_mut(addr, args)?;
if let Some(at) = out.as_addr() {
let stack = values.iter_mut().map(take).try_collect::<Stack>()?;
let mut vm = Self::with_stack(self.context.clone(), self.unit.clone(), stack);
vm.ip = offset;
let mut execution = vm.into_execution();
let future = Future::new(async move { execution.async_complete().await })?;
*self.stack.at_mut(at)? = Value::try_from(future)?;
} else {
values.iter_mut().for_each(consume);
}
Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn call_offset_fn(
&mut self,
offset: usize,
call: Call,
addr: InstAddress,
args: usize,
isolated: Isolated,
out: Output,
) -> Result<bool, VmErrorKind> {
let moved = match call {
Call::Async => {
self.call_async_fn(offset, addr, args, out)?;
false
}
Call::Immediate => {
self.push_call_frame(offset, addr, args, isolated, out)?;
true
}
Call::Stream => {
self.call_stream_fn(offset, addr, args, out)?;
false
}
Call::Generator => {
self.call_generator_fn(offset, addr, args, out)?;
false
}
};
Ok(moved)
}
#[cfg_attr(feature = "bench", inline(never))]
fn target_fallback_assign(
&mut self,
fallback: TargetFallback,
protocol: &Protocol,
) -> VmResult<()> {
match fallback {
TargetFallback::Value(lhs, rhs) => {
let mut args = DynGuardedArgs::new((rhs.clone(),));
if let CallResult::Unsupported(lhs) = vm_try!(self.call_instance_fn(
Isolated::None,
lhs,
protocol.hash,
&mut args,
Output::discard()
)) {
return err(VmErrorKind::UnsupportedBinaryOperation {
op: protocol.name,
lhs: lhs.type_info(),
rhs: rhs.type_info(),
});
};
VmResult::Ok(())
}
TargetFallback::Field(lhs, hash, slot, rhs) => {
let mut args = DynGuardedArgs::new((rhs,));
if let CallResult::Unsupported(lhs) = vm_try!(self.call_field_fn(
protocol,
lhs.clone(),
hash,
&mut args,
Output::discard()
)) {
let Some(field) = self.unit.lookup_string(slot) else {
return err(VmErrorKind::MissingStaticString { slot });
};
return err(VmErrorKind::UnsupportedObjectSlotIndexGet {
target: lhs.type_info(),
field: field.clone(),
});
}
VmResult::Ok(())
}
TargetFallback::Index(lhs, index, rhs) => {
let mut args = DynGuardedArgs::new((rhs,));
if let CallResult::Unsupported(lhs) = vm_try!(self.call_index_fn(
protocol.hash,
lhs.clone(),
index,
&mut args,
Output::discard()
)) {
return err(VmErrorKind::UnsupportedTupleIndexGet {
target: lhs.type_info(),
index,
});
}
VmResult::Ok(())
}
}
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_await(&mut self, addr: InstAddress) -> VmResult<Future> {
VmResult::Ok(vm_try!(self.stack.at(addr).clone().into_future()))
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_select(
&mut self,
addr: InstAddress,
len: usize,
value: Output,
) -> VmResult<Option<Select>> {
let futures = futures_util::stream::FuturesUnordered::new();
for (branch, value) in vm_try!(self.stack.slice_at(addr, len)).iter().enumerate() {
let future = vm_try!(value.clone().into_mut::<Future>());
if !future.is_completed() {
futures.push(SelectFuture::new(self.ip + branch, future));
}
}
if futures.is_empty() {
vm_try!(value.store(&mut self.stack, ()));
self.ip = self.ip.wrapping_add(len);
return VmResult::Ok(None);
}
VmResult::Ok(Some(Select::new(futures)))
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_store(&mut self, value: InstValue, out: Output) -> VmResult<()> {
vm_try!(out.store(&mut self.stack, value.into_value()));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_copy(&mut self, addr: InstAddress, out: Output) -> VmResult<()> {
vm_try!(self.stack.copy(addr, out));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_move(&mut self, addr: InstAddress, out: Output) -> VmResult<()> {
let value = self.stack.at(addr).clone();
let value = vm_try!(value.move_());
vm_try!(out.store(&mut self.stack, value));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_drop(&mut self, set: usize) -> VmResult<()> {
let Some(addresses) = self.unit.lookup_drop_set(set) else {
return err(VmErrorKind::MissingDropSet { set });
};
for &addr in addresses {
*vm_try!(self.stack.at_mut(addr)) = Value::empty();
}
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_swap(&mut self, a: InstAddress, b: InstAddress) -> VmResult<()> {
vm_try!(self.stack.swap(a, b));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_jump(&mut self, jump: usize) -> VmResult<()> {
self.ip = vm_try!(self.unit.translate(jump));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
#[cfg_attr(not(feature = "bench"), inline)]
fn op_jump_if(&mut self, cond: InstAddress, jump: usize) -> Result<(), VmErrorKind> {
if matches!(
self.stack.at(cond).as_ref(),
Repr::Inline(Inline::Bool(true))
) {
self.ip = self.unit.translate(jump)?;
}
Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_jump_if_not(&mut self, cond: InstAddress, jump: usize) -> Result<(), VmErrorKind> {
if matches!(
self.stack.at(cond).as_ref(),
Repr::Inline(Inline::Bool(false))
) {
self.ip = self.unit.translate(jump)?;
}
Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_vec(&mut self, addr: InstAddress, count: usize, out: Output) -> VmResult<()> {
let vec = vm_try!(self.stack.slice_at_mut(addr, count));
let vec = vm_try!(vec.iter_mut().map(take).try_collect::<alloc::Vec<Value>>());
vm_try!(out.store(&mut self.stack, Vec::from(vec)));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_tuple(&mut self, addr: InstAddress, count: usize, out: Output) -> VmResult<()> {
let tuple = vm_try!(self.stack.slice_at_mut(addr, count));
let tuple = vm_try!(tuple
.iter_mut()
.map(take)
.try_collect::<alloc::Vec<Value>>());
vm_try!(out.store(&mut self.stack, || OwnedTuple::try_from(tuple)));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_tuple_n(&mut self, addr: &[InstAddress], out: Output) -> VmResult<()> {
let mut tuple = vm_try!(alloc::Vec::<Value>::try_with_capacity(addr.len()));
for &arg in addr {
let value = self.stack.at(arg).clone();
vm_try!(tuple.try_push(value));
}
vm_try!(out.store(&mut self.stack, || OwnedTuple::try_from(tuple)));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_environment(&mut self, addr: InstAddress, count: usize, out: Output) -> VmResult<()> {
let tuple = self.stack.at(addr).clone();
let tuple = vm_try!(tuple.borrow_tuple_ref());
if tuple.len() != count {
return err(VmErrorKind::BadEnvironmentCount {
expected: count,
actual: tuple.len(),
});
}
if let Some(addr) = out.as_addr() {
let out = vm_try!(self.stack.slice_at_mut(addr, count));
for (value, out) in tuple.iter().zip(out.iter_mut()) {
out.clone_from(value);
}
}
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_allocate(&mut self, size: usize) -> VmResult<()> {
vm_try!(self.stack.resize(size));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_not(&mut self, operand: InstAddress, out: Output) -> VmResult<()> {
let value = self.stack.at(operand);
let value = match value.as_ref() {
Repr::Inline(Inline::Bool(value)) => Value::from(!value),
Repr::Inline(Inline::Unsigned(value)) => Value::from(!value),
Repr::Inline(Inline::Signed(value)) => Value::from(!value),
value => {
let operand = value.type_info();
return err(VmErrorKind::UnsupportedUnaryOperation { op: "!", operand });
}
};
vm_try!(out.store(&mut self.stack, value));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_neg(&mut self, addr: InstAddress, out: Output) -> VmResult<()> {
let value = self.stack.at(addr);
let value = match value.as_ref() {
Repr::Inline(Inline::Float(value)) => Value::from(-value),
Repr::Inline(Inline::Signed(value)) => Value::from(-value),
actual => {
let operand = actual.type_info();
return err(VmErrorKind::UnsupportedUnaryOperation { op: "-", operand });
}
};
vm_try!(out.store(&mut self.stack, value));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_op(
&mut self,
op: InstOp,
lhs: InstAddress,
rhs: InstAddress,
out: Output,
) -> VmResult<()> {
match op {
InstOp::Lt => {
vm_try!(self.internal_cmp(|o| matches!(o, Ordering::Less), lhs, rhs, out));
}
InstOp::Le => {
vm_try!(self.internal_cmp(
|o| matches!(o, Ordering::Less | Ordering::Equal),
lhs,
rhs,
out
));
}
InstOp::Gt => {
vm_try!(self.internal_cmp(|o| matches!(o, Ordering::Greater), lhs, rhs, out));
}
InstOp::Ge => {
vm_try!(self.internal_cmp(
|o| matches!(o, Ordering::Greater | Ordering::Equal),
lhs,
rhs,
out
));
}
InstOp::Eq => {
let rhs = self.stack.at(rhs);
let lhs = self.stack.at(lhs);
let test = if let (Some(lhs), Some(rhs)) = (lhs.as_inline(), rhs.as_inline()) {
vm_try!(lhs.partial_eq(rhs))
} else {
let lhs = lhs.clone();
let rhs = rhs.clone();
vm_try!(Value::partial_eq_with(&lhs, &rhs, self))
};
vm_try!(out.store(&mut self.stack, test));
}
InstOp::Neq => {
let rhs = self.stack.at(rhs);
let lhs = self.stack.at(lhs);
let test = if let (Some(lhs), Some(rhs)) = (lhs.as_inline(), rhs.as_inline()) {
vm_try!(lhs.partial_eq(rhs))
} else {
let lhs = lhs.clone();
let rhs = rhs.clone();
vm_try!(Value::partial_eq_with(&lhs, &rhs, self))
};
vm_try!(out.store(&mut self.stack, !test));
}
InstOp::And => {
vm_try!(self.internal_bool(|a, b| a && b, "&&", lhs, rhs, out));
}
InstOp::Or => {
vm_try!(self.internal_bool(|a, b| a || b, "||", lhs, rhs, out));
}
InstOp::As => {
let value = vm_try!(self.as_op(lhs, rhs));
vm_try!(out.store(&mut self.stack, value));
}
InstOp::Is => {
let is_instance = vm_try!(self.test_is_instance(lhs, rhs));
vm_try!(out.store(&mut self.stack, is_instance));
}
InstOp::IsNot => {
let is_instance = vm_try!(self.test_is_instance(lhs, rhs));
vm_try!(out.store(&mut self.stack, !is_instance));
}
}
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_arithmetic(
&mut self,
op: InstArithmeticOp,
lhs: InstAddress,
rhs: InstAddress,
out: Output,
) -> VmResult<()> {
let ops = ArithmeticOps::from_op(op);
let lhs = self.stack.at(lhs);
let rhs = self.stack.at(rhs);
'fallback: {
let inline = match (lhs.as_ref(), rhs.as_ref()) {
(Repr::Inline(lhs), Repr::Inline(rhs)) => match (lhs, rhs) {
(Inline::Unsigned(lhs), rhs) => {
let rhs = vm_try!(rhs.as_integer());
let value = vm_try!((ops.u64)(*lhs, rhs).ok_or_else(ops.error));
Inline::Unsigned(value)
}
(Inline::Signed(lhs), rhs) => {
let rhs = vm_try!(rhs.as_integer());
let value = vm_try!((ops.i64)(*lhs, rhs).ok_or_else(ops.error));
Inline::Signed(value)
}
(Inline::Float(lhs), Inline::Float(rhs)) => {
let value = (ops.f64)(*lhs, *rhs);
Inline::Float(value)
}
(lhs, rhs) => {
return err(VmErrorKind::UnsupportedBinaryOperation {
op: ops.protocol.name,
lhs: lhs.type_info(),
rhs: rhs.type_info(),
});
}
},
(Repr::Any(..), ..) => {
break 'fallback;
}
(lhs, rhs) => {
return err(VmErrorKind::UnsupportedBinaryOperation {
op: ops.protocol.name,
lhs: lhs.type_info(),
rhs: rhs.type_info(),
});
}
};
vm_try!(out.store(&mut self.stack, inline));
return VmResult::Ok(());
}
let lhs = lhs.clone();
let rhs = rhs.clone();
let mut args = DynGuardedArgs::new((rhs.clone(),));
if let CallResult::Unsupported(lhs) =
vm_try!(self.call_instance_fn(Isolated::None, lhs, &ops.protocol, &mut args, out))
{
return err(VmErrorKind::UnsupportedBinaryOperation {
op: ops.protocol.name,
lhs: lhs.type_info(),
rhs: rhs.type_info(),
});
}
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_bitwise(
&mut self,
op: InstBitwiseOp,
lhs: InstAddress,
rhs: InstAddress,
out: Output,
) -> VmResult<()> {
let ops = BitwiseOps::from_op(op);
let lhs = self.stack.at(lhs);
let rhs = self.stack.at(rhs);
'fallback: {
let inline = match (lhs.as_ref(), rhs.as_ref()) {
(Repr::Inline(Inline::Unsigned(lhs)), Repr::Inline(rhs)) => {
let rhs = vm_try!(rhs.as_integer());
let value = (ops.u64)(*lhs, rhs);
Inline::Unsigned(value)
}
(Repr::Inline(Inline::Signed(lhs)), Repr::Inline(rhs)) => {
let rhs = vm_try!(rhs.as_integer());
let value = (ops.i64)(*lhs, rhs);
Inline::Signed(value)
}
(Repr::Inline(Inline::Bool(lhs)), Repr::Inline(Inline::Bool(rhs))) => {
let value = (ops.bool)(*lhs, *rhs);
Inline::Bool(value)
}
(Repr::Any(_), _) => {
break 'fallback;
}
(lhs, rhs) => {
return err(VmErrorKind::UnsupportedBinaryOperation {
op: ops.protocol.name,
lhs: lhs.type_info(),
rhs: rhs.type_info(),
});
}
};
vm_try!(out.store(&mut self.stack, inline));
return VmResult::Ok(());
};
let lhs = lhs.clone();
let rhs = rhs.clone();
let mut args = DynGuardedArgs::new((&rhs,));
if let CallResult::Unsupported(lhs) =
vm_try!(self.call_instance_fn(Isolated::None, lhs, &ops.protocol, &mut args, out))
{
return err(VmErrorKind::UnsupportedBinaryOperation {
op: ops.protocol.name,
lhs: lhs.type_info(),
rhs: rhs.type_info(),
});
}
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_shift(
&mut self,
op: InstShiftOp,
lhs: InstAddress,
rhs: InstAddress,
out: Output,
) -> VmResult<()> {
let ops = ShiftOps::from_op(op);
let (lhs, rhs) = 'fallback: {
let inline = {
match vm_try!(self.stack.pair(lhs, rhs)) {
Pair::Same(value) => match value.as_mut() {
Repr::Inline(Inline::Unsigned(value)) => {
let shift = vm_try!(u32::try_from(*value).ok().ok_or_else(ops.error));
let value = vm_try!((ops.u64)(*value, shift).ok_or_else(ops.error));
Inline::Unsigned(value)
}
Repr::Inline(Inline::Signed(value)) => {
let shift = vm_try!(u32::try_from(*value).ok().ok_or_else(ops.error));
let value = vm_try!((ops.i64)(*value, shift).ok_or_else(ops.error));
Inline::Signed(value)
}
Repr::Any(..) => break 'fallback (value.clone(), value.clone()),
value => {
return err(VmErrorKind::UnsupportedBinaryOperation {
op: ops.protocol.name,
lhs: value.type_info(),
rhs: value.type_info(),
});
}
},
Pair::Pair(lhs, rhs) => match (lhs.as_mut(), rhs.as_ref()) {
(Repr::Inline(Inline::Unsigned(lhs)), Repr::Inline(rhs)) => {
let rhs = vm_try!(rhs.as_integer());
let value = vm_try!((ops.u64)(*lhs, rhs).ok_or_else(ops.error));
Inline::Unsigned(value)
}
(Repr::Inline(Inline::Signed(lhs)), Repr::Inline(rhs)) => {
let rhs = vm_try!(rhs.as_integer());
let value = vm_try!((ops.i64)(*lhs, rhs).ok_or_else(ops.error));
Inline::Signed(value)
}
(Repr::Any(..), _) => {
break 'fallback (lhs.clone(), rhs.clone());
}
(lhs, rhs) => {
return err(VmErrorKind::UnsupportedBinaryOperation {
op: ops.protocol.name,
lhs: lhs.type_info(),
rhs: rhs.type_info(),
});
}
},
}
};
vm_try!(out.store(&mut self.stack, inline));
return VmResult::Ok(());
};
let mut args = DynGuardedArgs::new((rhs.clone(),));
if let CallResult::Unsupported(lhs) =
vm_try!(self.call_instance_fn(Isolated::None, lhs, &ops.protocol, &mut args, out))
{
return err(VmErrorKind::UnsupportedBinaryOperation {
op: ops.protocol.name,
lhs: lhs.type_info(),
rhs: rhs.type_info(),
});
}
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_assign_arithmetic(
&mut self,
op: InstArithmeticOp,
target: InstTarget,
rhs: InstAddress,
) -> VmResult<()> {
let ops = AssignArithmeticOps::from_op(op);
let fallback = match vm_try!(target_value(&mut self.stack, &self.unit, target, rhs)) {
TargetValue::Same(value) => match value.as_mut() {
Repr::Inline(Inline::Signed(value)) => {
let out = vm_try!((ops.i64)(*value, *value).ok_or_else(ops.error));
*value = out;
return VmResult::Ok(());
}
Repr::Inline(Inline::Unsigned(value)) => {
let out = vm_try!((ops.u64)(*value, *value).ok_or_else(ops.error));
*value = out;
return VmResult::Ok(());
}
Repr::Inline(Inline::Float(value)) => {
let out = (ops.f64)(*value, *value);
*value = out;
return VmResult::Ok(());
}
Repr::Any(..) => TargetFallback::Value(value.clone(), value.clone()),
value => {
return err(VmErrorKind::UnsupportedBinaryOperation {
op: ops.protocol.name,
lhs: value.type_info(),
rhs: value.type_info(),
});
}
},
TargetValue::Pair(mut lhs, rhs) => match (lhs.as_mut(), rhs.as_ref()) {
(Repr::Inline(Inline::Signed(lhs)), Repr::Inline(rhs)) => {
let rhs = vm_try!(rhs.as_integer());
let out = vm_try!((ops.i64)(*lhs, rhs).ok_or_else(ops.error));
*lhs = out;
return VmResult::Ok(());
}
(Repr::Inline(Inline::Unsigned(lhs)), Repr::Inline(rhs)) => {
let rhs = vm_try!(rhs.as_integer());
let out = vm_try!((ops.u64)(*lhs, rhs).ok_or_else(ops.error));
*lhs = out;
return VmResult::Ok(());
}
(Repr::Inline(Inline::Float(lhs)), Repr::Inline(Inline::Float(rhs))) => {
let out = (ops.f64)(*lhs, *rhs);
*lhs = out;
return VmResult::Ok(());
}
(Repr::Any(..), _) => TargetFallback::Value(lhs.clone(), rhs.clone()),
(lhs, rhs) => {
return err(VmErrorKind::UnsupportedBinaryOperation {
op: ops.protocol.name,
lhs: lhs.type_info(),
rhs: rhs.type_info(),
});
}
},
TargetValue::Fallback(fallback) => fallback,
};
self.target_fallback_assign(fallback, &ops.protocol)
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_assign_bitwise(
&mut self,
op: InstBitwiseOp,
target: InstTarget,
rhs: InstAddress,
) -> VmResult<()> {
let ops = AssignBitwiseOps::from_ops(op);
let fallback = match vm_try!(target_value(&mut self.stack, &self.unit, target, rhs)) {
TargetValue::Same(value) => match value.as_mut() {
Repr::Inline(Inline::Unsigned(value)) => {
let rhs = *value;
(ops.u64)(value, rhs);
return VmResult::Ok(());
}
Repr::Inline(Inline::Signed(value)) => {
let rhs = *value;
(ops.i64)(value, rhs);
return VmResult::Ok(());
}
Repr::Inline(Inline::Bool(value)) => {
let rhs = *value;
(ops.bool)(value, rhs);
return VmResult::Ok(());
}
Repr::Any(..) => TargetFallback::Value(value.clone(), value.clone()),
value => {
return err(VmErrorKind::UnsupportedBinaryOperation {
op: ops.protocol.name,
lhs: value.type_info(),
rhs: value.type_info(),
});
}
},
TargetValue::Pair(mut lhs, rhs) => match (lhs.as_mut(), rhs.as_ref()) {
(Repr::Inline(Inline::Unsigned(lhs)), Repr::Inline(rhs)) => {
let rhs = vm_try!(rhs.as_integer());
(ops.u64)(lhs, rhs);
return VmResult::Ok(());
}
(Repr::Inline(Inline::Signed(lhs)), Repr::Inline(rhs)) => {
let rhs = vm_try!(rhs.as_integer());
(ops.i64)(lhs, rhs);
return VmResult::Ok(());
}
(Repr::Inline(Inline::Bool(lhs)), Repr::Inline(Inline::Bool(rhs))) => {
(ops.bool)(lhs, *rhs);
return VmResult::Ok(());
}
(Repr::Any(..), ..) => TargetFallback::Value(lhs.clone(), rhs.clone()),
(lhs, rhs) => {
return err(VmErrorKind::UnsupportedBinaryOperation {
op: ops.protocol.name,
lhs: lhs.type_info(),
rhs: rhs.type_info(),
});
}
},
TargetValue::Fallback(fallback) => fallback,
};
self.target_fallback_assign(fallback, &ops.protocol)
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_assign_shift(
&mut self,
op: InstShiftOp,
target: InstTarget,
rhs: InstAddress,
) -> VmResult<()> {
let ops = AssignShiftOps::from_op(op);
let fallback = match vm_try!(target_value(&mut self.stack, &self.unit, target, rhs)) {
TargetValue::Same(value) => match value.as_mut() {
Repr::Inline(Inline::Unsigned(value)) => {
let shift = vm_try!(u32::try_from(*value).ok().ok_or_else(ops.error));
let out = vm_try!((ops.u64)(*value, shift).ok_or_else(ops.error));
*value = out;
return VmResult::Ok(());
}
Repr::Inline(Inline::Signed(value)) => {
let shift = vm_try!(u32::try_from(*value).ok().ok_or_else(ops.error));
let out = vm_try!((ops.i64)(*value, shift).ok_or_else(ops.error));
*value = out;
return VmResult::Ok(());
}
Repr::Any(..) => TargetFallback::Value(value.clone(), value.clone()),
value => {
return err(VmErrorKind::UnsupportedBinaryOperation {
op: ops.protocol.name,
lhs: value.type_info(),
rhs: value.type_info(),
});
}
},
TargetValue::Pair(mut lhs, rhs) => match (lhs.as_mut(), rhs.as_ref()) {
(Repr::Inline(Inline::Unsigned(lhs)), Repr::Inline(rhs)) => {
let rhs = vm_try!(rhs.as_integer());
let out = vm_try!((ops.u64)(*lhs, rhs).ok_or_else(ops.error));
*lhs = out;
return VmResult::Ok(());
}
(Repr::Inline(Inline::Signed(lhs)), Repr::Inline(rhs)) => {
let rhs = vm_try!(rhs.as_integer());
let out = vm_try!((ops.i64)(*lhs, rhs).ok_or_else(ops.error));
*lhs = out;
return VmResult::Ok(());
}
(Repr::Any(..), _) => TargetFallback::Value(lhs.clone(), rhs.clone()),
(lhs, rhs) => {
return err(VmErrorKind::UnsupportedBinaryOperation {
op: ops.protocol.name,
lhs: lhs.type_info(),
rhs: rhs.type_info(),
});
}
},
TargetValue::Fallback(fallback) => fallback,
};
self.target_fallback_assign(fallback, &ops.protocol)
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_index_set(
&mut self,
target: InstAddress,
index: InstAddress,
value: InstAddress,
) -> VmResult<()> {
let target = self.stack.at(target);
let index = self.stack.at(index);
let value = self.stack.at(value);
if let Some(field) = vm_try!(index.try_borrow_ref::<String>()) {
if vm_try!(Self::try_object_slot_index_set(target, &field, value)) {
return VmResult::Ok(());
}
}
let target = target.clone();
let index = index.clone();
let value = value.clone();
let mut args = DynGuardedArgs::new((&index, &value));
if let CallResult::Unsupported(target) = vm_try!(self.call_instance_fn(
Isolated::None,
target,
&Protocol::INDEX_SET,
&mut args,
Output::discard()
)) {
return err(VmErrorKind::UnsupportedIndexSet {
target: target.type_info(),
index: index.type_info(),
value: value.type_info(),
});
}
VmResult::Ok(())
}
#[inline]
#[tracing::instrument(skip(self, return_value))]
fn op_return_internal(&mut self, return_value: Value) -> VmResult<Option<Output>> {
let (exit, out) = self.pop_call_frame();
let out = if let Some(out) = out {
vm_try!(out.store(&mut self.stack, return_value));
out
} else {
let addr = self.stack.addr();
vm_try!(self.stack.push(return_value));
addr.output()
};
VmResult::Ok(exit.then_some(out))
}
fn lookup_function_by_hash(&self, hash: Hash) -> Result<Function, VmErrorKind> {
let Some(info) = self.unit.function(&hash) else {
let Some(handler) = self.context.function(&hash) else {
return Err(VmErrorKind::MissingContextFunction { hash });
};
return Ok(Function::from_handler(handler.clone(), hash));
};
let f = match info {
UnitFn::Offset {
offset, call, args, ..
} => Function::from_vm_offset(
self.context.clone(),
self.unit.clone(),
*offset,
*call,
*args,
hash,
),
UnitFn::EmptyStruct { hash } => {
let Some(rtti) = self.unit.lookup_rtti(hash) else {
return Err(VmErrorKind::MissingRtti { hash: *hash });
};
Function::from_unit_struct(rtti.clone())
}
UnitFn::TupleStruct { hash, args } => {
let Some(rtti) = self.unit.lookup_rtti(hash) else {
return Err(VmErrorKind::MissingRtti { hash: *hash });
};
Function::from_tuple_struct(rtti.clone(), *args)
}
};
Ok(f)
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_return(&mut self, addr: InstAddress) -> VmResult<Option<Output>> {
let return_value = self.stack.at(addr).clone();
self.op_return_internal(return_value)
}
#[cfg_attr(feature = "bench", inline(never))]
#[tracing::instrument(skip(self))]
fn op_return_unit(&mut self) -> VmResult<Option<Output>> {
let (exit, out) = self.pop_call_frame();
let out = if let Some(out) = out {
vm_try!(out.store(&mut self.stack, ()));
out
} else {
let addr = self.stack.addr();
vm_try!(self.stack.push(()));
addr.output()
};
VmResult::Ok(exit.then_some(out))
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_load_instance_fn(&mut self, addr: InstAddress, hash: Hash, out: Output) -> VmResult<()> {
let instance = self.stack.at(addr);
let ty = instance.type_hash();
let hash = Hash::associated_function(ty, hash);
vm_try!(out.store(&mut self.stack, || Type::new(hash)));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_index_get(
&mut self,
target: InstAddress,
index: InstAddress,
out: Output,
) -> VmResult<()> {
let value = 'store: {
let index = self.stack.at(index);
let target = self.stack.at(target);
match index.as_ref() {
Repr::Inline(inline) => {
let index = vm_try!(inline.as_integer::<usize>());
if let Some(value) = vm_try!(Self::try_tuple_like_index_get(target, index)) {
break 'store value;
}
}
Repr::Any(value) => {
if let Some(index) = vm_try!(value.try_borrow_ref::<String>()) {
if let Some(value) =
vm_try!(Self::try_object_like_index_get(target, index.as_str()))
{
break 'store value;
}
}
}
_ => {}
}
let target = target.clone();
let index = index.clone();
let mut args = DynGuardedArgs::new((&index,));
if let CallResult::Unsupported(target) = vm_try!(self.call_instance_fn(
Isolated::None,
target,
&Protocol::INDEX_GET,
&mut args,
out
)) {
return err(VmErrorKind::UnsupportedIndexGet {
target: target.type_info(),
index: index.type_info(),
});
}
return VmResult::Ok(());
};
vm_try!(out.store(&mut self.stack, value));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_tuple_index_set(
&mut self,
target: InstAddress,
index: usize,
value: InstAddress,
) -> VmResult<()> {
let value = self.stack.at(value);
let target = self.stack.at(target);
if vm_try!(Self::try_tuple_like_index_set(target, index, value)) {
return VmResult::Ok(());
}
err(VmErrorKind::UnsupportedTupleIndexSet {
target: target.type_info(),
})
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_tuple_index_get_at(
&mut self,
addr: InstAddress,
index: usize,
out: Output,
) -> VmResult<()> {
let value = self.stack.at(addr);
if let Some(value) = vm_try!(Self::try_tuple_like_index_get(value, index)) {
vm_try!(out.store(&mut self.stack, value));
return VmResult::Ok(());
}
let value = value.clone();
if let CallResult::Unsupported(value) =
vm_try!(self.call_index_fn(&Protocol::GET, value, index, &mut (), out))
{
return err(VmErrorKind::UnsupportedTupleIndexGet {
target: value.type_info(),
index,
});
}
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_object_index_set(
&mut self,
target: InstAddress,
slot: usize,
value: InstAddress,
) -> VmResult<()> {
let target = self.stack.at(target);
let value = self.stack.at(value);
let Some(field) = self.unit.lookup_string(slot) else {
return err(VmErrorKind::MissingStaticString { slot });
};
if vm_try!(Self::try_object_slot_index_set(target, field, value)) {
return VmResult::Ok(());
}
let target = target.clone();
let value = value.clone();
let hash = field.hash();
let mut args = DynGuardedArgs::new((value,));
let result =
vm_try!(self.call_field_fn(&Protocol::SET, target, hash, &mut args, Output::discard()));
if let CallResult::Unsupported(target) = result {
let Some(field) = self.unit.lookup_string(slot) else {
return err(VmErrorKind::MissingStaticString { slot });
};
return err(VmErrorKind::UnsupportedObjectSlotIndexSet {
target: target.type_info(),
field: field.clone(),
});
};
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_object_index_get_at(
&mut self,
addr: InstAddress,
slot: usize,
out: Output,
) -> VmResult<()> {
let target = self.stack.at(addr);
let Some(index) = self.unit.lookup_string(slot) else {
return err(VmErrorKind::MissingStaticString { slot });
};
match target.as_ref() {
Repr::Dynamic(data) if matches!(data.rtti().kind, RttiKind::Struct) => {
let Some(value) = vm_try!(data.get_field_ref(index.as_str())) else {
return err(VmErrorKind::ObjectIndexMissing { slot });
};
let value = value.clone();
vm_try!(out.store(&mut self.stack, value));
return VmResult::Ok(());
}
Repr::Any(value) if value.type_hash() == Object::HASH => {
let object = vm_try!(value.borrow_ref::<Object>());
let Some(value) = object.get(index.as_str()) else {
return err(VmErrorKind::ObjectIndexMissing { slot });
};
let value = value.clone();
vm_try!(out.store(&mut self.stack, value));
return VmResult::Ok(());
}
Repr::Any(..) => {}
target => {
return err(VmErrorKind::UnsupportedObjectSlotIndexGet {
target: target.type_info(),
field: index.clone(),
});
}
}
let target = target.clone();
if let CallResult::Unsupported(target) =
vm_try!(self.call_field_fn(&Protocol::GET, target, index.hash(), &mut (), out))
{
let Some(field) = self.unit.lookup_string(slot) else {
return err(VmErrorKind::MissingStaticString { slot });
};
return err(VmErrorKind::UnsupportedObjectSlotIndexGet {
target: target.type_info(),
field: field.clone(),
});
}
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_object(&mut self, addr: InstAddress, slot: usize, out: Output) -> VmResult<()> {
let Some(keys) = self.unit.lookup_object_keys(slot) else {
return err(VmErrorKind::MissingStaticObjectKeys { slot });
};
let mut object = vm_try!(Object::with_capacity(keys.len()));
let values = vm_try!(self.stack.slice_at_mut(addr, keys.len()));
for (key, value) in keys.iter().zip(values) {
let key = vm_try!(String::try_from(key.as_str()));
vm_try!(object.insert(key, take(value)));
}
vm_try!(out.store(&mut self.stack, object));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_range(&mut self, range: InstRange, out: Output) -> VmResult<()> {
let value = match range {
InstRange::RangeFrom { start } => {
let s = self.stack.at(start).clone();
vm_try!(Value::new(RangeFrom::new(s.clone())))
}
InstRange::RangeFull => {
vm_try!(Value::new(RangeFull::new()))
}
InstRange::RangeInclusive { start, end } => {
let s = self.stack.at(start).clone();
let e = self.stack.at(end).clone();
vm_try!(Value::new(RangeInclusive::new(s.clone(), e.clone())))
}
InstRange::RangeToInclusive { end } => {
let e = self.stack.at(end).clone();
vm_try!(Value::new(RangeToInclusive::new(e.clone())))
}
InstRange::RangeTo { end } => {
let e = self.stack.at(end).clone();
vm_try!(Value::new(RangeTo::new(e.clone())))
}
InstRange::Range { start, end } => {
let s = self.stack.at(start).clone();
let e = self.stack.at(end).clone();
vm_try!(Value::new(Range::new(s.clone(), e.clone())))
}
};
vm_try!(out.store(&mut self.stack, value));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_empty_struct(&mut self, hash: Hash, out: Output) -> VmResult<()> {
let Some(rtti) = self.unit.lookup_rtti(&hash) else {
return err(VmErrorKind::MissingRtti { hash });
};
let value = vm_try!(Dynamic::<_, Value>::new(rtti.clone(), []));
vm_try!(out.store(&mut self.stack, value));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_struct(&mut self, addr: InstAddress, hash: Hash, out: Output) -> VmResult<()> {
let Some(rtti) = self.unit.lookup_rtti(&hash) else {
return err(VmErrorKind::MissingRtti { hash });
};
let values = vm_try!(self.stack.slice_at_mut(addr, rtti.fields.len()));
let value = vm_try!(Dynamic::new(rtti.clone(), values.iter_mut().map(take)));
vm_try!(out.store(&mut self.stack, value));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_const_construct(
&mut self,
addr: InstAddress,
hash: Hash,
count: usize,
out: Output,
) -> VmResult<()> {
let values = vm_try!(self.stack.slice_at_mut(addr, count));
let Some(construct) = self.context.construct(&hash) else {
return err(VmErrorKind::MissingConstantConstructor { hash });
};
let value = vm_try!(construct.runtime_construct(values));
vm_try!(out.store(&mut self.stack, value));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_string(&mut self, slot: usize, out: Output) -> VmResult<()> {
let Some(string) = self.unit.lookup_string(slot) else {
return err(VmErrorKind::MissingStaticString { slot });
};
vm_try!(out.store(&mut self.stack, string.as_str()));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_bytes(&mut self, slot: usize, out: Output) -> VmResult<()> {
let Some(bytes) = self.unit.lookup_bytes(slot) else {
return err(VmErrorKind::MissingStaticBytes { slot });
};
vm_try!(out.store(&mut self.stack, bytes));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_string_concat(
&mut self,
addr: InstAddress,
len: usize,
size_hint: usize,
out: Output,
) -> VmResult<()> {
let values = vm_try!(self.stack.slice_at(addr, len));
let values = vm_try!(values.iter().cloned().try_collect::<alloc::Vec<_>>());
let mut s = vm_try!(String::try_with_capacity(size_hint));
let result = Formatter::format_with(&mut s, |f| {
for value in values {
vm_try!(value.display_fmt_with(f, &mut *self));
}
VmResult::Ok(())
});
vm_try!(result);
vm_try!(out.store(&mut self.stack, s));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_format(&mut self, addr: InstAddress, spec: FormatSpec, out: Output) -> VmResult<()> {
let value = self.stack.at(addr).clone();
vm_try!(out.store(&mut self.stack, || Format { value, spec }));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_is_unit(&mut self, addr: InstAddress, out: Output) -> VmResult<()> {
let value = self.stack.at(addr);
let is_unit = matches!(value.as_inline(), Some(Inline::Unit));
vm_try!(out.store(&mut self.stack, is_unit));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_try(&mut self, addr: InstAddress, out: Output) -> VmResult<Option<Output>> {
let result = 'out: {
let value = {
let value = self.stack.at(addr);
if let Repr::Any(value) = value.as_ref() {
match value.type_hash() {
Result::<Value, Value>::HASH => {
let result = vm_try!(value.borrow_ref::<Result<Value, Value>>());
break 'out vm_try!(result::result_try(&result));
}
Option::<Value>::HASH => {
let option = vm_try!(value.borrow_ref::<Option<Value>>());
break 'out vm_try!(option::option_try(&option));
}
_ => {}
}
}
value.clone()
};
match vm_try!(self.try_call_protocol_fn(&Protocol::TRY, value, &mut ())) {
CallResultOnly::Ok(value) => vm_try!(ControlFlow::from_value(value)),
CallResultOnly::Unsupported(target) => {
return err(VmErrorKind::UnsupportedTryOperand {
actual: target.type_info(),
})
}
}
};
match result {
ControlFlow::Continue(value) => {
vm_try!(out.store(&mut self.stack, value));
VmResult::Ok(None)
}
ControlFlow::Break(error) => VmResult::Ok(vm_try!(self.op_return_internal(error))),
}
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_eq_character(&mut self, addr: InstAddress, value: char, out: Output) -> VmResult<()> {
let v = self.stack.at(addr);
let is_match = match v.as_inline() {
Some(Inline::Char(actual)) => *actual == value,
_ => false,
};
vm_try!(out.store(&mut self.stack, is_match));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_eq_unsigned(&mut self, addr: InstAddress, value: u64, out: Output) -> VmResult<()> {
let v = self.stack.at(addr);
let is_match = match v.as_inline() {
Some(Inline::Unsigned(actual)) => *actual == value,
_ => false,
};
vm_try!(out.store(&mut self.stack, is_match));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_eq_signed(&mut self, addr: InstAddress, value: i64, out: Output) -> VmResult<()> {
let is_match = match self.stack.at(addr).as_inline() {
Some(Inline::Signed(actual)) => *actual == value,
_ => false,
};
vm_try!(out.store(&mut self.stack, is_match));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_eq_bool(&mut self, addr: InstAddress, value: bool, out: Output) -> VmResult<()> {
let v = self.stack.at(addr);
let is_match = match v.as_inline() {
Some(Inline::Bool(actual)) => *actual == value,
_ => false,
};
vm_try!(out.store(&mut self.stack, is_match));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_eq_string(&mut self, addr: InstAddress, slot: usize, out: Output) -> VmResult<()> {
let v = self.stack.at(addr);
let is_match = 'out: {
let Some(actual) = vm_try!(v.try_borrow_ref::<String>()) else {
break 'out false;
};
let Some(string) = self.unit.lookup_string(slot) else {
return err(VmErrorKind::MissingStaticString { slot });
};
actual.as_str() == string.as_str()
};
vm_try!(out.store(&mut self.stack, is_match));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_eq_bytes(&mut self, addr: InstAddress, slot: usize, out: Output) -> VmResult<()> {
let v = self.stack.at(addr);
let is_match = 'out: {
let Some(value) = vm_try!(v.try_borrow_ref::<Bytes>()) else {
break 'out false;
};
let Some(bytes) = self.unit.lookup_bytes(slot) else {
return err(VmErrorKind::MissingStaticBytes { slot });
};
value.as_slice() == bytes
};
vm_try!(out.store(&mut self.stack, is_match));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_match_sequence(
&mut self,
ty: TypeCheck,
len: usize,
exact: bool,
addr: InstAddress,
out: Output,
) -> VmResult<()> {
let value = self.stack.at(addr);
let result = vm_try!(self.on_tuple(ty, value, move |tuple| {
if exact {
tuple.len() == len
} else {
tuple.len() >= len
}
}));
vm_try!(out.store(&mut self.stack, result.unwrap_or_default()));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_match_type(&mut self, hash: Hash, addr: InstAddress, out: Output) -> VmResult<()> {
let value = self.stack.at(addr);
let is_match = value.type_hash() == hash;
vm_try!(out.store(&mut self.stack, is_match));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_match_variant(
&mut self,
enum_hash: Hash,
variant_hash: Hash,
index: usize,
addr: InstAddress,
out: Output,
) -> VmResult<()> {
let value = self.stack.at(addr);
let is_match = 'out: {
match value.as_ref() {
Repr::Dynamic(value) => {
break 'out value.rtti().is(enum_hash, variant_hash);
}
Repr::Any(any) => match enum_hash {
Result::<Value, Value>::HASH => {
let result = vm_try!(any.borrow_ref::<Result<Value, Value>>());
break 'out match (variant_hash, &*result) {
(hash!(::std::result::Result::Ok), Ok(..)) => true,
(hash!(::std::result::Result::Err), Err(..)) => true,
_ => false,
};
}
Option::<Value>::HASH => {
let option = vm_try!(any.borrow_ref::<Option<Value>>());
break 'out match (variant_hash, &*option) {
(hash!(::std::option::Option::None), None) => true,
(hash!(::std::option::Option::Some), Some(..)) => true,
_ => false,
};
}
_ => {
if any.type_hash() != enum_hash {
break 'out false;
}
}
},
_ => {
break 'out false;
}
}
let value = value.clone();
match vm_try!(self.try_call_protocol_fn(
&Protocol::IS_VARIANT,
value,
&mut Some((index,))
)) {
CallResultOnly::Ok(value) => vm_try!(bool::from_value(value)),
CallResultOnly::Unsupported(..) => false,
}
};
vm_try!(out.store(&mut self.stack, is_match));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_match_builtin(
&mut self,
type_check: TypeCheck,
addr: InstAddress,
out: Output,
) -> VmResult<()> {
let value = self.stack.at(addr);
let is_match = match value.as_ref() {
Repr::Inline(value) => match (type_check, value) {
(TypeCheck::Unit, Inline::Unit) => true,
_ => false,
},
Repr::Dynamic(..) => false,
Repr::Any(value) => match (type_check, value.type_hash()) {
(TypeCheck::Vec, runtime::Vec::HASH) => true,
(TypeCheck::Tuple, runtime::OwnedTuple::HASH) => true,
_ => false,
},
};
vm_try!(out.store(&mut self.stack, is_match));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_match_object(
&mut self,
slot: usize,
exact: bool,
addr: InstAddress,
out: Output,
) -> VmResult<()> {
fn test(object: &Object, keys: &[alloc::String], exact: bool) -> bool {
if exact {
if object.len() != keys.len() {
return false;
}
} else if object.len() < keys.len() {
return false;
}
for key in keys {
if !object.contains_key(key.as_str()) {
return false;
}
}
true
}
let value = self.stack.at(addr);
let is_match = match vm_try!(value.try_borrow_ref::<Object>()) {
Some(object) => {
let Some(keys) = self.unit.lookup_object_keys(slot) else {
return err(VmErrorKind::MissingStaticObjectKeys { slot });
};
test(&object, keys, exact)
}
None => false,
};
vm_try!(out.store(&mut self.stack, is_match));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_variant(&mut self, addr: InstAddress, variant: InstVariant, out: Output) -> VmResult<()> {
match variant {
InstVariant::Some => {
let some = self.stack.at(addr).clone();
vm_try!(out.store(&mut self.stack, || Value::try_from(Some(some))));
}
InstVariant::None => {
vm_try!(out.store(&mut self.stack, || Value::try_from(None)));
}
InstVariant::Ok => {
let ok = self.stack.at(addr).clone();
vm_try!(out.store(&mut self.stack, || Value::try_from(Ok(ok))));
}
InstVariant::Err => {
let err = self.stack.at(addr).clone();
vm_try!(out.store(&mut self.stack, || Value::try_from(Err(err))));
}
}
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_load_fn(&mut self, hash: Hash, out: Output) -> VmResult<()> {
let function = vm_try!(self.lookup_function_by_hash(hash));
vm_try!(out.store(&mut self.stack, function));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_closure(
&mut self,
hash: Hash,
addr: InstAddress,
count: usize,
out: Output,
) -> VmResult<()> {
let Some(UnitFn::Offset {
offset,
call,
args,
captures: Some(captures),
}) = self.unit.function(&hash)
else {
return err(VmErrorKind::MissingFunction { hash });
};
if *captures != count {
return err(VmErrorKind::BadEnvironmentCount {
expected: *captures,
actual: count,
});
}
let environment = vm_try!(self.stack.slice_at(addr, count));
let environment = vm_try!(environment
.iter()
.cloned()
.try_collect::<alloc::Vec<Value>>());
let environment = vm_try!(environment.try_into_boxed_slice());
let function = Function::from_vm_closure(
self.context.clone(),
self.unit.clone(),
*offset,
*call,
*args,
environment,
hash,
);
vm_try!(out.store(&mut self.stack, function));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_call(&mut self, hash: Hash, addr: InstAddress, args: usize, out: Output) -> VmResult<()> {
let Some(info) = self.unit.function(&hash) else {
let Some(handler) = self.context.function(&hash) else {
return err(VmErrorKind::MissingFunction { hash });
};
vm_try!(handler(&mut self.stack, addr, args, out));
return VmResult::Ok(());
};
match info {
UnitFn::Offset {
offset,
call,
args: expected,
..
} => {
vm_try!(check_args(args, *expected));
vm_try!(self.call_offset_fn(*offset, *call, addr, args, Isolated::None, out));
}
UnitFn::EmptyStruct { hash } => {
vm_try!(check_args(args, 0));
let Some(rtti) = self.unit.lookup_rtti(hash) else {
return err(VmErrorKind::MissingRtti { hash: *hash });
};
vm_try!(out.store(&mut self.stack, || Value::empty_struct(rtti.clone())));
}
UnitFn::TupleStruct {
hash,
args: expected,
} => {
vm_try!(check_args(args, *expected));
let Some(rtti) = self.unit.lookup_rtti(hash) else {
return err(VmErrorKind::MissingRtti { hash: *hash });
};
let tuple = vm_try!(self.stack.slice_at_mut(addr, args));
let data = tuple.iter_mut().map(take);
let value = vm_try!(Dynamic::new(rtti.clone(), data));
vm_try!(out.store(&mut self.stack, value));
}
}
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_call_offset(
&mut self,
offset: usize,
call: Call,
addr: InstAddress,
args: usize,
out: Output,
) -> VmResult<()> {
vm_try!(self.call_offset_fn(offset, call, addr, args, Isolated::None, out));
VmResult::Ok(())
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_call_associated(
&mut self,
hash: Hash,
addr: InstAddress,
args: usize,
out: Output,
) -> VmResult<()> {
let instance = self.stack.at(addr);
let type_hash = instance.type_hash();
let hash = Hash::associated_function(type_hash, hash);
if let Some(handler) = self.context.function(&hash) {
vm_try!(self.called_function_hook(hash));
vm_try!(handler(&mut self.stack, addr, args, out));
return VmResult::Ok(());
}
if let Some(UnitFn::Offset {
offset,
call,
args: expected,
..
}) = self.unit.function(&hash)
{
vm_try!(self.called_function_hook(hash));
vm_try!(check_args(args, *expected));
vm_try!(self.call_offset_fn(*offset, *call, addr, args, Isolated::None, out));
return VmResult::Ok(());
}
err(VmErrorKind::MissingInstanceFunction {
instance: instance.type_info(),
hash,
})
}
#[cfg_attr(feature = "bench", inline(never))]
#[tracing::instrument(skip(self))]
fn op_call_fn(
&mut self,
function: InstAddress,
addr: InstAddress,
args: usize,
out: Output,
) -> VmResult<Option<VmHalt>> {
let function = self.stack.at(function);
match function.as_ref() {
Repr::Inline(Inline::Type(ty)) => {
vm_try!(self.op_call(ty.into_hash(), addr, args, out));
VmResult::Ok(None)
}
Repr::Any(value) if value.type_hash() == Function::HASH => {
let value = value.clone();
let f = vm_try!(value.borrow_ref::<Function>());
f.call_with_vm(self, addr, args, out)
}
value => err(VmErrorKind::UnsupportedCallFn {
actual: value.type_info(),
}),
}
}
#[cfg_attr(feature = "bench", inline(never))]
fn op_iter_next(&mut self, addr: InstAddress, jump: usize, out: Output) -> VmResult<()> {
let value = self.stack.at(addr);
let some = match value.as_ref() {
Repr::Any(value) => match value.type_hash() {
Option::<Value>::HASH => {
let option = vm_try!(value.borrow_ref::<Option<Value>>());
let Some(some) = &*option else {
self.ip = vm_try!(self.unit.translate(jump));
return VmResult::Ok(());
};
some.clone()
}
_ => {
return err(VmErrorKind::UnsupportedIterNextOperand {
actual: value.type_info(),
});
}
},
actual => {
return err(VmErrorKind::UnsupportedIterNextOperand {
actual: actual.type_info(),
});
}
};
vm_try!(out.store(&mut self.stack, some));
VmResult::Ok(())
}
pub fn with<F, T>(&self, f: F) -> T
where
F: FnOnce() -> T,
{
let _guard = runtime::env::Guard::new(self.context.clone(), self.unit.clone(), None);
f()
}
pub(crate) fn run(&mut self, diagnostics: Option<&mut dyn VmDiagnostics>) -> VmResult<VmHalt> {
let mut vm_diagnostics_obj;
let diagnostics = match diagnostics {
Some(diagnostics) => {
vm_diagnostics_obj = VmDiagnosticsObj::new(diagnostics);
Some(NonNull::from(&mut vm_diagnostics_obj))
}
None => None,
};
let _guard = runtime::env::Guard::new(self.context.clone(), self.unit.clone(), diagnostics);
let mut budget = budget::acquire();
loop {
if !budget.take() {
return VmResult::Ok(VmHalt::Limited);
}
let Some((inst, inst_len)) = vm_try!(self.unit.instruction_at(self.ip)) else {
return VmResult::err(VmErrorKind::IpOutOfBounds {
ip: self.ip,
length: self.unit.instructions().end(),
});
};
tracing::trace!(ip = ?self.ip, ?inst);
self.ip = self.ip.wrapping_add(inst_len);
self.last_ip_len = inst_len as u8;
match inst {
Inst::Allocate { size } => {
vm_try!(self.op_allocate(size));
}
Inst::Not { addr, out } => {
vm_try!(self.op_not(addr, out));
}
Inst::Neg { addr, out } => {
vm_try!(self.op_neg(addr, out));
}
Inst::Closure {
hash,
addr,
count,
out,
} => {
vm_try!(self.op_closure(hash, addr, count, out));
}
Inst::Call {
hash,
addr,
args,
out,
} => {
vm_try!(self.op_call(hash, addr, args, out));
}
Inst::CallOffset {
offset,
call,
addr,
args,
out,
} => {
vm_try!(self.op_call_offset(offset, call, addr, args, out));
}
Inst::CallAssociated {
hash,
addr,
args,
out,
} => {
vm_try!(self.op_call_associated(hash, addr, args, out));
}
Inst::CallFn {
function,
addr,
args,
out,
} => {
if let Some(reason) = vm_try!(self.op_call_fn(function, addr, args, out)) {
return VmResult::Ok(reason);
}
}
Inst::LoadInstanceFn { addr, hash, out } => {
vm_try!(self.op_load_instance_fn(addr, hash, out));
}
Inst::IndexGet { target, index, out } => {
vm_try!(self.op_index_get(target, index, out));
}
Inst::TupleIndexSet {
target,
index,
value,
} => {
vm_try!(self.op_tuple_index_set(target, index, value));
}
Inst::TupleIndexGetAt { addr, index, out } => {
vm_try!(self.op_tuple_index_get_at(addr, index, out));
}
Inst::ObjectIndexSet {
target,
slot,
value,
} => {
vm_try!(self.op_object_index_set(target, slot, value));
}
Inst::ObjectIndexGetAt { addr, slot, out } => {
vm_try!(self.op_object_index_get_at(addr, slot, out));
}
Inst::IndexSet {
target,
index,
value,
} => {
vm_try!(self.op_index_set(target, index, value));
}
Inst::Return { addr } => {
if let Some(out) = vm_try!(self.op_return(addr)) {
return VmResult::Ok(VmHalt::Exited(out.as_addr()));
}
}
Inst::ReturnUnit => {
if let Some(out) = vm_try!(self.op_return_unit()) {
return VmResult::Ok(VmHalt::Exited(out.as_addr()));
}
}
Inst::Await { addr, out } => {
let future = vm_try!(self.op_await(addr));
return VmResult::Ok(VmHalt::Awaited(Awaited::Future(future, out)));
}
Inst::Select { addr, len, value } => {
if let Some(select) = vm_try!(self.op_select(addr, len, value)) {
return VmResult::Ok(VmHalt::Awaited(Awaited::Select(select, value)));
}
}
Inst::LoadFn { hash, out } => {
vm_try!(self.op_load_fn(hash, out));
}
Inst::Store { value, out } => {
vm_try!(self.op_store(value, out));
}
Inst::Copy { addr, out } => {
vm_try!(self.op_copy(addr, out));
}
Inst::Move { addr, out } => {
vm_try!(self.op_move(addr, out));
}
Inst::Drop { set } => {
vm_try!(self.op_drop(set));
}
Inst::Swap { a, b } => {
vm_try!(self.op_swap(a, b));
}
Inst::Jump { jump } => {
vm_try!(self.op_jump(jump));
}
Inst::JumpIf { cond, jump } => {
vm_try!(self.op_jump_if(cond, jump));
}
Inst::JumpIfNot { cond, jump } => {
vm_try!(self.op_jump_if_not(cond, jump));
}
Inst::Vec { addr, count, out } => {
vm_try!(self.op_vec(addr, count, out));
}
Inst::Tuple { addr, count, out } => {
vm_try!(self.op_tuple(addr, count, out));
}
Inst::Tuple1 { addr, out } => {
vm_try!(self.op_tuple_n(&addr[..], out));
}
Inst::Tuple2 { addr, out } => {
vm_try!(self.op_tuple_n(&addr[..], out));
}
Inst::Tuple3 { addr, out } => {
vm_try!(self.op_tuple_n(&addr[..], out));
}
Inst::Tuple4 { addr, out } => {
vm_try!(self.op_tuple_n(&addr[..], out));
}
Inst::Environment { addr, count, out } => {
vm_try!(self.op_environment(addr, count, out));
}
Inst::Object { addr, slot, out } => {
vm_try!(self.op_object(addr, slot, out));
}
Inst::Range { range, out } => {
vm_try!(self.op_range(range, out));
}
Inst::EmptyStruct { hash, out } => {
vm_try!(self.op_empty_struct(hash, out));
}
Inst::Struct { addr, hash, out } => {
vm_try!(self.op_struct(addr, hash, out));
}
Inst::ConstConstruct {
addr,
hash,
count,
out,
} => {
vm_try!(self.op_const_construct(addr, hash, count, out));
}
Inst::String { slot, out } => {
vm_try!(self.op_string(slot, out));
}
Inst::Bytes { slot, out } => {
vm_try!(self.op_bytes(slot, out));
}
Inst::StringConcat {
addr,
len,
size_hint,
out,
} => {
vm_try!(self.op_string_concat(addr, len, size_hint, out));
}
Inst::Format { addr, spec, out } => {
vm_try!(self.op_format(addr, spec, out));
}
Inst::IsUnit { addr, out } => {
vm_try!(self.op_is_unit(addr, out));
}
Inst::Try { addr, out } => {
if let Some(out) = vm_try!(self.op_try(addr, out)) {
return VmResult::Ok(VmHalt::Exited(out.as_addr()));
}
}
Inst::EqChar { addr, value, out } => {
vm_try!(self.op_eq_character(addr, value, out));
}
Inst::EqUnsigned { addr, value, out } => {
vm_try!(self.op_eq_unsigned(addr, value, out));
}
Inst::EqSigned { addr, value, out } => {
vm_try!(self.op_eq_signed(addr, value, out));
}
Inst::EqBool {
addr,
value: boolean,
out,
} => {
vm_try!(self.op_eq_bool(addr, boolean, out));
}
Inst::EqString { addr, slot, out } => {
vm_try!(self.op_eq_string(addr, slot, out));
}
Inst::EqBytes { addr, slot, out } => {
vm_try!(self.op_eq_bytes(addr, slot, out));
}
Inst::MatchSequence {
type_check,
len,
exact,
addr,
out,
} => {
vm_try!(self.op_match_sequence(type_check, len, exact, addr, out));
}
Inst::MatchType { hash, addr, out } => {
vm_try!(self.op_match_type(hash, addr, out));
}
Inst::MatchVariant {
enum_hash,
variant_hash,
index,
addr,
out,
} => {
vm_try!(self.op_match_variant(enum_hash, variant_hash, index, addr, out));
}
Inst::MatchBuiltIn {
type_check,
addr,
out,
} => {
vm_try!(self.op_match_builtin(type_check, addr, out));
}
Inst::MatchObject {
slot,
exact,
addr,
out,
} => {
vm_try!(self.op_match_object(slot, exact, addr, out));
}
Inst::Yield { addr, out } => {
return VmResult::Ok(VmHalt::Yielded(Some(addr), out));
}
Inst::YieldUnit { out } => {
return VmResult::Ok(VmHalt::Yielded(None, out));
}
Inst::Variant { addr, variant, out } => {
vm_try!(self.op_variant(addr, variant, out));
}
Inst::Op { op, a, b, out } => {
vm_try!(self.op_op(op, a, b, out));
}
Inst::Arithmetic { op, a, b, out } => {
vm_try!(self.op_arithmetic(op, a, b, out));
}
Inst::Bitwise { op, a, b, out } => {
vm_try!(self.op_bitwise(op, a, b, out));
}
Inst::Shift { op, a, b, out } => {
vm_try!(self.op_shift(op, a, b, out));
}
Inst::AssignArithmetic { op, target, rhs } => {
vm_try!(self.op_assign_arithmetic(op, target, rhs));
}
Inst::AssignBitwise { op, target, rhs } => {
vm_try!(self.op_assign_bitwise(op, target, rhs));
}
Inst::AssignShift { op, target, rhs } => {
vm_try!(self.op_assign_shift(op, target, rhs));
}
Inst::IterNext { addr, jump, out } => {
vm_try!(self.op_iter_next(addr, jump, out));
}
Inst::Panic { reason } => {
return err(VmErrorKind::Panic {
reason: Panic::from(reason),
});
}
}
}
}
}
impl TryClone for Vm {
fn try_clone(&self) -> alloc::Result<Self> {
Ok(Self {
context: self.context.clone(),
unit: self.unit.clone(),
ip: self.ip,
last_ip_len: self.last_ip_len,
stack: self.stack.try_clone()?,
call_frames: self.call_frames.try_clone()?,
})
}
}
impl AsMut<Vm> for Vm {
#[inline]
fn as_mut(&mut self) -> &mut Vm {
self
}
}
impl AsRef<Vm> for Vm {
#[inline]
fn as_ref(&self) -> &Vm {
self
}
}
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub struct CallFrame {
pub ip: usize,
pub top: usize,
pub isolated: Isolated,
pub out: Output,
}
impl TryClone for CallFrame {
#[inline]
fn try_clone(&self) -> alloc::Result<Self> {
Ok(*self)
}
}
struct ClearStack<'a>(&'a mut Vm);
impl Drop for ClearStack<'_> {
fn drop(&mut self) {
self.0.stack.clear();
}
}
#[inline(always)]
fn check_args(args: usize, expected: usize) -> Result<(), VmErrorKind> {
if args != expected {
return Err(VmErrorKind::BadArgumentCount {
actual: args,
expected,
});
}
Ok(())
}
enum TargetFallback {
Value(Value, Value),
Field(Value, Hash, usize, Value),
Index(Value, usize, Value),
}
enum TargetValue<'a> {
Same(&'a mut Value),
Pair(BorrowMut<'a, Value>, &'a Value),
Fallback(TargetFallback),
}
#[inline]
fn target_value<'a>(
stack: &'a mut Stack,
unit: &Unit,
target: InstTarget,
rhs: InstAddress,
) -> Result<TargetValue<'a>, VmErrorKind> {
match target {
InstTarget::Address(addr) => match stack.pair(addr, rhs)? {
Pair::Same(value) => Ok(TargetValue::Same(value)),
Pair::Pair(lhs, rhs) => Ok(TargetValue::Pair(BorrowMut::from_ref(lhs), rhs)),
},
InstTarget::TupleField(lhs, index) => {
let lhs = stack.at(lhs);
let rhs = stack.at(rhs);
if let Some(value) = try_tuple_like_index_get_mut(lhs, index)? {
Ok(TargetValue::Pair(value, rhs))
} else {
Ok(TargetValue::Fallback(TargetFallback::Index(
lhs.clone(),
index,
rhs.clone(),
)))
}
}
InstTarget::Field(lhs, slot) => {
let rhs = stack.at(rhs);
let Some(field) = unit.lookup_string(slot) else {
return Err(VmErrorKind::MissingStaticString { slot });
};
let lhs = stack.at(lhs);
if let Some(value) = try_object_like_index_get_mut(lhs, field)? {
Ok(TargetValue::Pair(value, rhs))
} else {
Ok(TargetValue::Fallback(TargetFallback::Field(
lhs.clone(),
field.hash(),
slot,
rhs.clone(),
)))
}
}
}
}
fn try_tuple_like_index_get_mut(
target: &Value,
index: usize,
) -> Result<Option<BorrowMut<'_, Value>>, VmErrorKind> {
match target.as_ref() {
Repr::Dynamic(data) if matches!(data.rtti().kind, RttiKind::Tuple) => {
let Some(value) = data.get_mut(index)? else {
return Err(VmErrorKind::MissingIndexInteger {
target: data.type_info(),
index: VmIntegerRepr::from(index),
});
};
Ok(Some(value))
}
Repr::Dynamic(data) => Err(VmErrorKind::MissingIndexInteger {
target: data.type_info(),
index: VmIntegerRepr::from(index),
}),
Repr::Any(value) => match value.type_hash() {
Result::<Value, Value>::HASH => {
let result = BorrowMut::try_map(
value.borrow_mut::<Result<Value, Value>>()?,
|value| match (index, value) {
(0, Ok(value)) => Some(value),
(0, Err(value)) => Some(value),
_ => None,
},
);
if let Ok(value) = result {
return Ok(Some(value));
}
Err(VmErrorKind::MissingIndexInteger {
target: TypeInfo::any::<Result<Value, Value>>(),
index: VmIntegerRepr::from(index),
})
}
Option::<Value>::HASH => {
let result =
BorrowMut::try_map(value.borrow_mut::<Option<Value>>()?, |value| {
match (index, value) {
(0, Some(value)) => Some(value),
_ => None,
}
});
if let Ok(value) = result {
return Ok(Some(value));
}
Err(VmErrorKind::MissingIndexInteger {
target: TypeInfo::any::<Option<Value>>(),
index: VmIntegerRepr::from(index),
})
}
GeneratorState::HASH => {
let result = BorrowMut::try_map(value.borrow_mut::<GeneratorState>()?, |value| {
match (index, value) {
(0, GeneratorState::Yielded(value)) => Some(value),
(0, GeneratorState::Complete(value)) => Some(value),
_ => None,
}
});
if let Ok(value) = result {
return Ok(Some(value));
}
Err(VmErrorKind::MissingIndexInteger {
target: TypeInfo::any::<GeneratorState>(),
index: VmIntegerRepr::from(index),
})
}
runtime::Vec::HASH => {
let vec = value.borrow_mut::<runtime::Vec>()?;
let result = BorrowMut::try_map(vec, |vec| vec.get_mut(index));
if let Ok(value) = result {
return Ok(Some(value));
}
Err(VmErrorKind::MissingIndexInteger {
target: TypeInfo::any::<runtime::Vec>(),
index: VmIntegerRepr::from(index),
})
}
runtime::OwnedTuple::HASH => {
let tuple = value.borrow_mut::<runtime::OwnedTuple>()?;
let result = BorrowMut::try_map(tuple, |tuple| tuple.get_mut(index));
if let Ok(value) = result {
return Ok(Some(value));
}
Err(VmErrorKind::MissingIndexInteger {
target: TypeInfo::any::<runtime::OwnedTuple>(),
index: VmIntegerRepr::from(index),
})
}
_ => Ok(None),
},
_ => Ok(None),
}
}
fn try_object_like_index_get_mut<'a>(
target: &'a Value,
field: &str,
) -> Result<Option<BorrowMut<'a, Value>>, VmErrorKind> {
match target.as_ref() {
Repr::Inline(value) => Err(VmErrorKind::MissingField {
target: value.type_info(),
field: field.try_to_owned()?,
}),
Repr::Dynamic(data) if matches!(data.rtti().kind, RttiKind::Struct) => {
Ok(data.get_field_mut(field)?)
}
Repr::Dynamic(data) => Err(VmErrorKind::MissingField {
target: data.type_info(),
field: field.try_to_owned()?,
}),
Repr::Any(value) => match value.type_hash() {
Object::HASH => {
let object = value.borrow_mut::<Object>()?;
let Ok(value) = BorrowMut::try_map(object, |object| object.get_mut(field)) else {
return Err(VmErrorKind::MissingField {
target: value.type_info(),
field: field.try_to_owned()?,
});
};
Ok(Some(value))
}
_ => Ok(None),
},
}
}