From 9a05b1ef813f6c1a615666b27f08a4de0952dbb4 Mon Sep 17 00:00:00 2001 From: Kevin Date: Sat, 22 Oct 2022 19:42:39 +0000 Subject: [PATCH] Split vm/opcode into modules (#2343) Hi! This isn't really related to a pull request that I know of. I was trying to better wrap my head around Boa's VM and thought I'd break it apart so that the file wasn't 2500+ lines. I figured I'd submit it as a draft and get feedback/see if anyone was interested in it. The way the modules were broken apart was primarily based off the opcode name (`GetFunction` & `GetFunctionAsync` -> `./get/function.rs`). It changes the following: - Adds an `Operation` trait to opcode/mod.rs - Implements `Operation` for each Opcode variant, moving the executable instruction code from `vm/mod.rs` to the respective module Co-authored-by: raskad <32105367+raskad@users.noreply.github.com> --- boa_engine/src/vm/mod.rs | 2702 ++--------------- boa_engine/src/vm/opcode/await_stm/mod.rs | 119 + .../src/vm/opcode/binary_ops/logical.rs | 70 + .../src/vm/opcode/binary_ops/macro_defined.rs | 46 + boa_engine/src/vm/opcode/binary_ops/mod.rs | 120 + boa_engine/src/vm/opcode/call/mod.rs | 212 ++ boa_engine/src/vm/opcode/concat/mod.rs | 33 + boa_engine/src/vm/opcode/copy/mod.rs | 42 + .../src/vm/opcode/define/class/getter.rs | 96 + .../src/vm/opcode/define/class/method.rs | 90 + boa_engine/src/vm/opcode/define/class/mod.rs | 7 + .../src/vm/opcode/define/class/setter.rs | 96 + boa_engine/src/vm/opcode/define/mod.rs | 140 + .../src/vm/opcode/define/own_property.rs | 79 + boa_engine/src/vm/opcode/delete/mod.rs | 63 + boa_engine/src/vm/opcode/dup/mod.rs | 23 + boa_engine/src/vm/opcode/environment/mod.rs | 275 ++ boa_engine/src/vm/opcode/generator/mod.rs | 226 ++ .../src/vm/opcode/generator/yield_stm.rs | 20 + boa_engine/src/vm/opcode/get/function.rs | 44 + boa_engine/src/vm/opcode/get/generator.rs | 44 + boa_engine/src/vm/opcode/get/mod.rs | 11 + boa_engine/src/vm/opcode/get/name.rs | 136 + boa_engine/src/vm/opcode/get/private.rs | 54 + boa_engine/src/vm/opcode/get/property.rs | 96 + .../src/vm/opcode/iteration/for_await.rs | 78 + boa_engine/src/vm/opcode/iteration/for_in.rs | 86 + boa_engine/src/vm/opcode/iteration/init.rs | 47 + .../src/vm/opcode/iteration/iterator.rs | 109 + .../src/vm/opcode/iteration/loop_ops.rs | 76 + boa_engine/src/vm/opcode/iteration/mod.rs | 11 + boa_engine/src/vm/opcode/jump/mod.rs | 64 + .../src/vm/{opcode.rs => opcode/mod.rs} | 755 +++-- boa_engine/src/vm/opcode/new/mod.rs | 89 + boa_engine/src/vm/opcode/nop/mod.rs | 20 + boa_engine/src/vm/opcode/pop/mod.rs | 95 + boa_engine/src/vm/opcode/promise/mod.rs | 80 + boa_engine/src/vm/opcode/push/array.rs | 106 + boa_engine/src/vm/opcode/push/class/field.rs | 85 + boa_engine/src/vm/opcode/push/class/mod.rs | 65 + .../src/vm/opcode/push/class/private.rs | 101 + boa_engine/src/vm/opcode/push/environment.rs | 56 + boa_engine/src/vm/opcode/push/literal.rs | 23 + boa_engine/src/vm/opcode/push/mod.rs | 67 + boa_engine/src/vm/opcode/push/new_target.rs | 34 + boa_engine/src/vm/opcode/push/numbers.rs | 54 + boa_engine/src/vm/opcode/push/object.rs | 21 + boa_engine/src/vm/opcode/require/mod.rs | 23 + .../src/vm/opcode/rest_parameter/mod.rs | 62 + boa_engine/src/vm/opcode/return_stm/mod.rs | 52 + .../src/vm/opcode/set/class_prototype.rs | 74 + boa_engine/src/vm/opcode/set/home_object.rs | 33 + boa_engine/src/vm/opcode/set/mod.rs | 11 + boa_engine/src/vm/opcode/set/name.rs | 77 + boa_engine/src/vm/opcode/set/private.rs | 187 ++ boa_engine/src/vm/opcode/set/property.rs | 220 ++ boa_engine/src/vm/opcode/swap/mod.rs | 25 + boa_engine/src/vm/opcode/switch/mod.rs | 49 + boa_engine/src/vm/opcode/throw/mod.rs | 21 + boa_engine/src/vm/opcode/to/mod.rs | 41 + boa_engine/src/vm/opcode/try_catch/mod.rs | 164 + .../src/vm/opcode/unary_ops/decrement.rs | 53 + .../src/vm/opcode/unary_ops/increment.rs | 53 + boa_engine/src/vm/opcode/unary_ops/logical.rs | 22 + boa_engine/src/vm/opcode/unary_ops/mod.rs | 96 + boa_engine/src/vm/opcode/unary_ops/void.rs | 22 + boa_engine/src/vm/opcode/value/mod.rs | 33 + 67 files changed, 5431 insertions(+), 2853 deletions(-) create mode 100644 boa_engine/src/vm/opcode/await_stm/mod.rs create mode 100644 boa_engine/src/vm/opcode/binary_ops/logical.rs create mode 100644 boa_engine/src/vm/opcode/binary_ops/macro_defined.rs create mode 100644 boa_engine/src/vm/opcode/binary_ops/mod.rs create mode 100644 boa_engine/src/vm/opcode/call/mod.rs create mode 100644 boa_engine/src/vm/opcode/concat/mod.rs create mode 100644 boa_engine/src/vm/opcode/copy/mod.rs create mode 100644 boa_engine/src/vm/opcode/define/class/getter.rs create mode 100644 boa_engine/src/vm/opcode/define/class/method.rs create mode 100644 boa_engine/src/vm/opcode/define/class/mod.rs create mode 100644 boa_engine/src/vm/opcode/define/class/setter.rs create mode 100644 boa_engine/src/vm/opcode/define/mod.rs create mode 100644 boa_engine/src/vm/opcode/define/own_property.rs create mode 100644 boa_engine/src/vm/opcode/delete/mod.rs create mode 100644 boa_engine/src/vm/opcode/dup/mod.rs create mode 100644 boa_engine/src/vm/opcode/environment/mod.rs create mode 100644 boa_engine/src/vm/opcode/generator/mod.rs create mode 100644 boa_engine/src/vm/opcode/generator/yield_stm.rs create mode 100644 boa_engine/src/vm/opcode/get/function.rs create mode 100644 boa_engine/src/vm/opcode/get/generator.rs create mode 100644 boa_engine/src/vm/opcode/get/mod.rs create mode 100644 boa_engine/src/vm/opcode/get/name.rs create mode 100644 boa_engine/src/vm/opcode/get/private.rs create mode 100644 boa_engine/src/vm/opcode/get/property.rs create mode 100644 boa_engine/src/vm/opcode/iteration/for_await.rs create mode 100644 boa_engine/src/vm/opcode/iteration/for_in.rs create mode 100644 boa_engine/src/vm/opcode/iteration/init.rs create mode 100644 boa_engine/src/vm/opcode/iteration/iterator.rs create mode 100644 boa_engine/src/vm/opcode/iteration/loop_ops.rs create mode 100644 boa_engine/src/vm/opcode/iteration/mod.rs create mode 100644 boa_engine/src/vm/opcode/jump/mod.rs rename boa_engine/src/vm/{opcode.rs => opcode/mod.rs} (56%) create mode 100644 boa_engine/src/vm/opcode/new/mod.rs create mode 100644 boa_engine/src/vm/opcode/nop/mod.rs create mode 100644 boa_engine/src/vm/opcode/pop/mod.rs create mode 100644 boa_engine/src/vm/opcode/promise/mod.rs create mode 100644 boa_engine/src/vm/opcode/push/array.rs create mode 100644 boa_engine/src/vm/opcode/push/class/field.rs create mode 100644 boa_engine/src/vm/opcode/push/class/mod.rs create mode 100644 boa_engine/src/vm/opcode/push/class/private.rs create mode 100644 boa_engine/src/vm/opcode/push/environment.rs create mode 100644 boa_engine/src/vm/opcode/push/literal.rs create mode 100644 boa_engine/src/vm/opcode/push/mod.rs create mode 100644 boa_engine/src/vm/opcode/push/new_target.rs create mode 100644 boa_engine/src/vm/opcode/push/numbers.rs create mode 100644 boa_engine/src/vm/opcode/push/object.rs create mode 100644 boa_engine/src/vm/opcode/require/mod.rs create mode 100644 boa_engine/src/vm/opcode/rest_parameter/mod.rs create mode 100644 boa_engine/src/vm/opcode/return_stm/mod.rs create mode 100644 boa_engine/src/vm/opcode/set/class_prototype.rs create mode 100644 boa_engine/src/vm/opcode/set/home_object.rs create mode 100644 boa_engine/src/vm/opcode/set/mod.rs create mode 100644 boa_engine/src/vm/opcode/set/name.rs create mode 100644 boa_engine/src/vm/opcode/set/private.rs create mode 100644 boa_engine/src/vm/opcode/set/property.rs create mode 100644 boa_engine/src/vm/opcode/swap/mod.rs create mode 100644 boa_engine/src/vm/opcode/switch/mod.rs create mode 100644 boa_engine/src/vm/opcode/throw/mod.rs create mode 100644 boa_engine/src/vm/opcode/to/mod.rs create mode 100644 boa_engine/src/vm/opcode/try_catch/mod.rs create mode 100644 boa_engine/src/vm/opcode/unary_ops/decrement.rs create mode 100644 boa_engine/src/vm/opcode/unary_ops/increment.rs create mode 100644 boa_engine/src/vm/opcode/unary_ops/logical.rs create mode 100644 boa_engine/src/vm/opcode/unary_ops/mod.rs create mode 100644 boa_engine/src/vm/opcode/unary_ops/void.rs create mode 100644 boa_engine/src/vm/opcode/value/mod.rs diff --git a/boa_engine/src/vm/mod.rs b/boa_engine/src/vm/mod.rs index 1fe3d50782..bc22b9688f 100644 --- a/boa_engine/src/vm/mod.rs +++ b/boa_engine/src/vm/mod.rs @@ -3,31 +3,21 @@ //! plus an interpreter to execute those instructions use crate::{ - builtins::{ - async_generator::{AsyncGenerator, AsyncGeneratorState}, - function::{ConstructorKind, Function}, - iterable::{IteratorHint, IteratorRecord, IteratorResult}, - Array, ForInIterator, JsArgs, Number, Promise, - }, - environments::EnvironmentSlots, - error::JsNativeError, - object::{FunctionBuilder, JsFunction, JsObject, ObjectData, PrivateElement}, - property::{DescriptorKind, PropertyDescriptor, PropertyDescriptorBuilder, PropertyKey}, - value::Numeric, - vm::{ - call_frame::CatchAddresses, - code_block::{initialize_instance_elements, Readable}, - }, - Context, JsBigInt, JsError, JsResult, JsString, JsValue, + builtins::async_generator::{AsyncGenerator, AsyncGeneratorState}, + vm::{call_frame::CatchAddresses, code_block::Readable}, + Context, JsResult, JsValue, }; use boa_interner::ToInternedString; use boa_profiler::Profiler; -use std::{convert::TryInto, mem::size_of, ops::Neg, time::Instant}; +use std::{convert::TryInto, mem::size_of, time::Instant}; mod call_frame; mod code_block; mod opcode; +#[allow(clippy::wildcard_imports)] +use opcode::*; + pub use {call_frame::CallFrame, code_block::CodeBlock, opcode::Opcode}; pub(crate) use { @@ -109,7 +99,7 @@ impl Vm { /// Indicates if the execution should continue, exit or yield. #[derive(Debug, Clone, Copy)] -enum ShouldExit { +pub(crate) enum ShouldExit { True, False, Yield, @@ -125,15 +115,6 @@ pub(crate) enum ReturnType { impl Context { fn execute_instruction(&mut self) -> JsResult { - macro_rules! bin_op { - ($op:ident) => {{ - let rhs = self.vm.pop(); - let lhs = self.vm.pop(); - let value = lhs.$op(&rhs, self)?; - self.vm.push(value) - }}; - } - let opcode: Opcode = { let _timer = Profiler::global().start_event("Opcode retrieval", "vm"); let opcode = self.vm.frame().code.code[self.vm.frame().pc] @@ -145,2509 +126,172 @@ impl Context { let _timer = Profiler::global().start_event(opcode.as_instruction_str(), "vm"); - match opcode { - Opcode::Nop => {} - Opcode::Pop => { - let _val = self.vm.pop(); - } - Opcode::PopIfThrown => { - let frame = self.vm.frame_mut(); - if frame.thrown { - frame.thrown = false; - self.vm.pop(); - } - } - Opcode::Dup => { - let value = self.vm.pop(); - self.vm.push(value.clone()); - self.vm.push(value); - } - Opcode::Swap => { - let first = self.vm.pop(); - let second = self.vm.pop(); - - self.vm.push(first); - self.vm.push(second); - } - Opcode::PushUndefined => self.vm.push(JsValue::undefined()), - Opcode::PushNull => self.vm.push(JsValue::null()), - Opcode::PushTrue => self.vm.push(true), - Opcode::PushFalse => self.vm.push(false), - Opcode::PushZero => self.vm.push(0), - Opcode::PushOne => self.vm.push(1), - Opcode::PushInt8 => { - let value = self.vm.read::(); - self.vm.push(i32::from(value)); - } - Opcode::PushInt16 => { - let value = self.vm.read::(); - self.vm.push(i32::from(value)); - } - Opcode::PushInt32 => { - let value = self.vm.read::(); - self.vm.push(value); - } - Opcode::PushRational => { - let value = self.vm.read::(); - self.vm.push(value); - } - Opcode::PushNaN => self.vm.push(JsValue::nan()), - Opcode::PushPositiveInfinity => self.vm.push(JsValue::positive_infinity()), - Opcode::PushNegativeInfinity => self.vm.push(JsValue::negative_infinity()), - Opcode::PushLiteral => { - let index = self.vm.read::() as usize; - let value = self.vm.frame().code.literals[index].clone(); - self.vm.push(value); - } - Opcode::PushEmptyObject => self.vm.push(self.construct_object()), - Opcode::PushClassPrototype => { - let superclass = self.vm.pop(); - - if let Some(superclass) = superclass.as_constructor() { - let proto = superclass.get("prototype", self)?; - if !proto.is_object() && !proto.is_null() { - return Err(JsNativeError::typ() - .with_message("superclass prototype must be an object or null") - .into()); - } - - let class = self.vm.pop(); - { - let class_object = class.as_object().expect("class must be object"); - class_object.set_prototype(Some(superclass.clone())); - - let mut class_object_mut = class_object.borrow_mut(); - let class_function = class_object_mut - .as_function_mut() - .expect("class must be function object"); - if let Function::Ordinary { - constructor_kind, .. - } = class_function - { - *constructor_kind = ConstructorKind::Derived; - } - } - - self.vm.push(class); - self.vm.push(proto); - } else if superclass.is_null() { - self.vm.push(JsValue::Null); - } else { - return Err(JsNativeError::typ() - .with_message("superclass must be a constructor") - .into()); - } - } - Opcode::SetClassPrototype => { - let prototype_value = self.vm.pop(); - let prototype = match &prototype_value { - JsValue::Object(proto) => Some(proto.clone()), - JsValue::Null => None, - JsValue::Undefined => { - Some(self.intrinsics().constructors().object().prototype.clone()) - } - _ => unreachable!(), - }; - - let proto = JsObject::from_proto_and_data(prototype, ObjectData::ordinary()); - let class = self.vm.pop(); - - { - let class_object = class.as_object().expect("class must be object"); - class_object - .define_property_or_throw( - "prototype", - PropertyDescriptorBuilder::new() - .value(proto.clone()) - .writable(false) - .enumerable(false) - .configurable(false), - self, - ) - .expect("cannot fail per spec"); - let mut class_object_mut = class_object.borrow_mut(); - let class_function = class_object_mut - .as_function_mut() - .expect("class must be function object"); - class_function.set_home_object(proto.clone()); - } - - proto - .__define_own_property__( - "constructor".into(), - PropertyDescriptorBuilder::new() - .value(class) - .writable(true) - .enumerable(false) - .configurable(true) - .build(), - self, - ) - .expect("cannot fail per spec"); - - self.vm.push(proto); - } - Opcode::SetHomeObject => { - let function = self.vm.pop(); - let function_object = function.as_object().expect("must be object"); - let home = self.vm.pop(); - let home_object = home.as_object().expect("must be object"); - - function_object - .borrow_mut() - .as_function_mut() - .expect("must be function object") - .set_home_object(home_object.clone()); - - self.vm.push(home); - self.vm.push(function); - } - Opcode::PushNewArray => { - let array = Array::array_create(0, None, self) - .expect("Array creation with 0 length should never fail"); - self.vm.push(array); - } - Opcode::PushValueToArray => { - let value = self.vm.pop(); - let array = self.vm.pop(); - let o = array.as_object().expect("should be an object"); - let len = o - .length_of_array_like(self) - .expect("should have 'length' property"); - o.create_data_property_or_throw(len, value, self) - .expect("should be able to create new data property"); - self.vm.push(array); - } - Opcode::PushElisionToArray => { - let array = self.vm.pop(); - let o = array.as_object().expect("should always be an object"); - - let len = o - .length_of_array_like(self) - .expect("arrays should always have a 'length' property"); - - o.set("length", len + 1, true, self)?; - self.vm.push(array); - } - Opcode::PushIteratorToArray => { - let done = self - .vm - .pop() - .as_boolean() - .expect("iterator [[Done]] was not a boolean"); - let next_method = self.vm.pop(); - let iterator = self.vm.pop(); - let iterator = iterator.as_object().expect("iterator was not an object"); - let array = self.vm.pop(); - - let iterator = IteratorRecord::new(iterator.clone(), next_method, done); - while let Some(next) = iterator.step(self)? { - Array::push(&array, &[next.value(self)?], self)?; - } - - self.vm.push(array); - } - Opcode::Add => bin_op!(add), - Opcode::Sub => bin_op!(sub), - Opcode::Mul => bin_op!(mul), - Opcode::Div => bin_op!(div), - Opcode::Pow => bin_op!(pow), - Opcode::Mod => bin_op!(rem), - Opcode::BitAnd => bin_op!(bitand), - Opcode::BitOr => bin_op!(bitor), - Opcode::BitXor => bin_op!(bitxor), - Opcode::ShiftLeft => bin_op!(shl), - Opcode::ShiftRight => bin_op!(shr), - Opcode::UnsignedShiftRight => bin_op!(ushr), - Opcode::Eq => { - let rhs = self.vm.pop(); - let lhs = self.vm.pop(); - let value = lhs.equals(&rhs, self)?; - self.vm.push(value); - } - Opcode::NotEq => { - let rhs = self.vm.pop(); - let lhs = self.vm.pop(); - let value = !lhs.equals(&rhs, self)?; - self.vm.push(value); - } - Opcode::StrictEq => { - let rhs = self.vm.pop(); - let lhs = self.vm.pop(); - self.vm.push(lhs.strict_equals(&rhs)); - } - Opcode::StrictNotEq => { - let rhs = self.vm.pop(); - let lhs = self.vm.pop(); - self.vm.push(!lhs.strict_equals(&rhs)); - } - Opcode::GreaterThan => bin_op!(gt), - Opcode::GreaterThanOrEq => bin_op!(ge), - Opcode::LessThan => bin_op!(lt), - Opcode::LessThanOrEq => bin_op!(le), - Opcode::In => { - let rhs = self.vm.pop(); - let lhs = self.vm.pop(); - - if !rhs.is_object() { - return Err(JsNativeError::typ() - .with_message(format!( - "right-hand side of 'in' should be an object, got {}", - rhs.type_of().to_std_string_escaped() - )) - .into()); - } - let key = lhs.to_property_key(self)?; - let value = self.has_property(&rhs, &key)?; - self.vm.push(value); - } - Opcode::InstanceOf => { - let target = self.vm.pop(); - let v = self.vm.pop(); - let value = v.instance_of(&target, self)?; - - self.vm.push(value); - } - Opcode::Void => { - let _old = self.vm.pop(); - self.vm.push(JsValue::undefined()); - } - Opcode::TypeOf => { - let value = self.vm.pop(); - self.vm.push(value.type_of()); - } - Opcode::Pos => { - let value = self.vm.pop(); - let value = value.to_number(self)?; - self.vm.push(value); - } - Opcode::Neg => { - let value = self.vm.pop(); - match value.to_numeric(self)? { - Numeric::Number(number) => self.vm.push(number.neg()), - Numeric::BigInt(bigint) => self.vm.push(JsBigInt::neg(&bigint)), - } - } - Opcode::Inc => { - let value = self.vm.pop(); - match value.to_numeric(self)? { - Numeric::Number(number) => self.vm.push(number + 1f64), - Numeric::BigInt(bigint) => { - self.vm.push(JsBigInt::add(&bigint, &JsBigInt::one())); - } - } - } - Opcode::IncPost => { - let value = self.vm.pop(); - let value = value.to_numeric(self)?; - self.vm.push(value.clone()); - match value { - Numeric::Number(number) => self.vm.push(number + 1f64), - Numeric::BigInt(bigint) => { - self.vm.push(JsBigInt::add(&bigint, &JsBigInt::one())); - } - } - } - Opcode::Dec => { - let value = self.vm.pop(); - match value.to_numeric(self)? { - Numeric::Number(number) => self.vm.push(number - 1f64), - Numeric::BigInt(bigint) => { - self.vm.push(JsBigInt::sub(&bigint, &JsBigInt::one())); - } - } - } - Opcode::DecPost => { - let value = self.vm.pop(); - let value = value.to_numeric(self)?; - self.vm.push(value.clone()); - match value { - Numeric::Number(number) => self.vm.push(number - 1f64), - Numeric::BigInt(bigint) => { - self.vm.push(JsBigInt::sub(&bigint, &JsBigInt::one())); - } - } - } - Opcode::LogicalNot => { - let value = self.vm.pop(); - self.vm.push(!value.to_boolean()); - } - Opcode::BitNot => { - let value = self.vm.pop(); - match value.to_numeric(self)? { - Numeric::Number(number) => self.vm.push(Number::not(number)), - Numeric::BigInt(bigint) => self.vm.push(JsBigInt::not(&bigint)), - } - } - Opcode::DefVar => { - let index = self.vm.read::(); - let binding_locator = self.vm.frame().code.bindings[index as usize]; - - if binding_locator.is_global() { - let key = self - .interner() - .resolve_expect(binding_locator.name().sym()) - .into_common(false); - self.global_bindings_mut().entry(key).or_insert( - PropertyDescriptor::builder() - .value(JsValue::Undefined) - .writable(true) - .enumerable(true) - .configurable(true) - .build(), - ); - } else { - self.realm.environments.put_value_if_uninitialized( - binding_locator.environment_index(), - binding_locator.binding_index(), - JsValue::Undefined, - ); - } - } - Opcode::DefInitVar => { - let index = self.vm.read::(); - let value = self.vm.pop(); - let binding_locator = self.vm.frame().code.bindings[index as usize]; - binding_locator.throw_mutate_immutable(self)?; - - if binding_locator.is_global() { - let key = self - .interner() - .resolve_expect(binding_locator.name().sym()) - .into_common::(false) - .into(); - crate::object::internal_methods::global::global_set_no_receiver( - &key, value, self, - )?; - } else { - self.realm.environments.put_value( - binding_locator.environment_index(), - binding_locator.binding_index(), - value, - ); - } - } - Opcode::DefLet => { - let index = self.vm.read::(); - let binding_locator = self.vm.frame().code.bindings[index as usize]; - self.realm.environments.put_value( - binding_locator.environment_index(), - binding_locator.binding_index(), - JsValue::Undefined, - ); - } - Opcode::DefInitLet | Opcode::DefInitConst | Opcode::DefInitArg => { - let index = self.vm.read::(); - let value = self.vm.pop(); - let binding_locator = self.vm.frame().code.bindings[index as usize]; - self.realm.environments.put_value( - binding_locator.environment_index(), - binding_locator.binding_index(), - value, - ); - } - Opcode::GetName => { - let index = self.vm.read::(); - let binding_locator = self.vm.frame().code.bindings[index as usize]; - binding_locator.throw_mutate_immutable(self)?; - - let value = if binding_locator.is_global() { - if let Some(value) = self - .realm - .environments - .get_value_global_poisoned(binding_locator.name()) - { - value - } else { - let key: JsString = self - .interner() - .resolve_expect(binding_locator.name().sym()) - .into_common(false); - match self.global_bindings_mut().get(&key) { - Some(desc) => match desc.kind() { - DescriptorKind::Data { - value: Some(value), .. - } => value.clone(), - DescriptorKind::Accessor { get: Some(get), .. } - if !get.is_undefined() => - { - let get = get.clone(); - self.call(&get, &self.global_object().clone().into(), &[])? - } - _ => { - return Err(JsNativeError::reference() - .with_message(format!( - "{} is not defined", - key.to_std_string_escaped() - )) - .into()) - } - }, - _ => { - return Err(JsNativeError::reference() - .with_message(format!( - "{} is not defined", - key.to_std_string_escaped() - )) - .into()) - } - } - } - } else if let Some(value) = self.realm.environments.get_value_optional( - binding_locator.environment_index(), - binding_locator.binding_index(), - binding_locator.name(), - ) { - value - } else { - let name = self - .interner() - .resolve_expect(binding_locator.name().sym()) - .to_string(); - return Err(JsNativeError::reference() - .with_message(format!("{name} is not initialized")) - .into()); - }; - - self.vm.push(value); - } - Opcode::GetNameOrUndefined => { - let index = self.vm.read::(); - let binding_locator = self.vm.frame().code.bindings[index as usize]; - binding_locator.throw_mutate_immutable(self)?; - let value = if binding_locator.is_global() { - if let Some(value) = self - .realm - .environments - .get_value_global_poisoned(binding_locator.name()) - { - value - } else { - let key: JsString = self - .interner() - .resolve_expect(binding_locator.name().sym()) - .into_common(false); - match self.global_bindings_mut().get(&key) { - Some(desc) => match desc.kind() { - DescriptorKind::Data { - value: Some(value), .. - } => value.clone(), - DescriptorKind::Accessor { get: Some(get), .. } - if !get.is_undefined() => - { - let get = get.clone(); - self.call(&get, &self.global_object().clone().into(), &[])? - } - _ => JsValue::undefined(), - }, - _ => JsValue::undefined(), - } - } - } else if let Some(value) = self.realm.environments.get_value_optional( - binding_locator.environment_index(), - binding_locator.binding_index(), - binding_locator.name(), - ) { - value - } else { - JsValue::undefined() - }; - - self.vm.push(value); - } - Opcode::SetName => { - let index = self.vm.read::(); - let binding_locator = self.vm.frame().code.bindings[index as usize]; - let value = self.vm.pop(); - binding_locator.throw_mutate_immutable(self)?; - - if binding_locator.is_global() { - if !self - .realm - .environments - .put_value_global_poisoned(binding_locator.name(), &value) - { - let key: JsString = self - .interner() - .resolve_expect(binding_locator.name().sym()) - .into_common(false); - let exists = self.global_bindings_mut().contains_key(&key); - - if !exists && self.vm.frame().code.strict { - return Err(JsNativeError::reference() - .with_message(format!( - "assignment to undeclared variable {}", - key.to_std_string_escaped() - )) - .into()); - } - - let success = - crate::object::internal_methods::global::global_set_no_receiver( - &key.clone().into(), - value, - self, - )?; - - if !success && self.vm.frame().code.strict { - return Err(JsNativeError::typ() - .with_message(format!( - "cannot set non-writable property: {}", - key.to_std_string_escaped() - )) - .into()); - } - } - } else if !self.realm.environments.put_value_if_initialized( - binding_locator.environment_index(), - binding_locator.binding_index(), - binding_locator.name(), - value, - ) { - return Err(JsNativeError::reference() - .with_message(format!( - "cannot access '{}' before initialization", - self.interner().resolve_expect(binding_locator.name().sym()) - )) - .into()); - } - } - Opcode::Jump => { - let address = self.vm.read::(); - self.vm.frame_mut().pc = address as usize; - } - Opcode::JumpIfFalse => { - let address = self.vm.read::(); - if !self.vm.pop().to_boolean() { - self.vm.frame_mut().pc = address as usize; - } - } - Opcode::JumpIfNotUndefined => { - let address = self.vm.read::(); - let value = self.vm.pop(); - if !value.is_undefined() { - self.vm.frame_mut().pc = address as usize; - self.vm.push(value); - } - } - Opcode::LogicalAnd => { - let exit = self.vm.read::(); - let lhs = self.vm.pop(); - if !lhs.to_boolean() { - self.vm.frame_mut().pc = exit as usize; - self.vm.push(lhs); - } - } - Opcode::LogicalOr => { - let exit = self.vm.read::(); - let lhs = self.vm.pop(); - if lhs.to_boolean() { - self.vm.frame_mut().pc = exit as usize; - self.vm.push(lhs); - } - } - Opcode::Coalesce => { - let exit = self.vm.read::(); - let lhs = self.vm.pop(); - if !lhs.is_null_or_undefined() { - self.vm.frame_mut().pc = exit as usize; - self.vm.push(lhs); - } - } - Opcode::ToBoolean => { - let value = self.vm.pop(); - self.vm.push(value.to_boolean()); - } - Opcode::GetPropertyByName => { - let index = self.vm.read::(); - - let value = self.vm.pop(); - let object = if let Some(object) = value.as_object() { - object.clone() - } else { - value.to_object(self)? - }; - - let name = self.vm.frame().code.names[index as usize]; - let name: PropertyKey = self - .interner() - .resolve_expect(name.sym()) - .into_common::(false) - .into(); - let result = object.get(name, self)?; - - self.vm.push(result); - } - Opcode::GetPropertyByValue => { - let object = self.vm.pop(); - let key = self.vm.pop(); - let object = if let Some(object) = object.as_object() { - object.clone() - } else { - object.to_object(self)? - }; - - let key = key.to_property_key(self)?; - let value = object.get(key, self)?; - - self.vm.push(value); - } - Opcode::GetPropertyByValuePush => { - let object = self.vm.pop(); - let key = self.vm.pop(); - let object = if let Some(object) = object.as_object() { - object.clone() - } else { - object.to_object(self)? - }; - - let property_key = key.to_property_key(self)?; - let value = object.get(property_key, self)?; - - self.vm.push(key); - self.vm.push(value); - } - Opcode::SetPropertyByName => { - let index = self.vm.read::(); - - let object = self.vm.pop(); - let value = self.vm.pop(); - let object = if let Some(object) = object.as_object() { - object.clone() - } else { - object.to_object(self)? - }; - - let name = self.vm.frame().code.names[index as usize]; - let name: PropertyKey = self - .interner() - .resolve_expect(name.sym()) - .into_common::(false) - .into(); - - object.set(name, value, self.vm.frame().code.strict, self)?; - } - Opcode::DefineOwnPropertyByName => { - let index = self.vm.read::(); - let object = self.vm.pop(); - let value = self.vm.pop(); - let object = if let Some(object) = object.as_object() { - object.clone() - } else { - object.to_object(self)? - }; - let name = self.vm.frame().code.names[index as usize]; - let name = self - .interner() - .resolve_expect(name.sym()) - .into_common::(false); - object.__define_own_property__( - name.into(), - PropertyDescriptor::builder() - .value(value) - .writable(true) - .enumerable(true) - .configurable(true) - .build(), - self, - )?; - } - Opcode::DefineClassMethodByName => { - let index = self.vm.read::(); - let object = self.vm.pop(); - let value = self.vm.pop(); - let object = if let Some(object) = object.as_object() { - object.clone() - } else { - object.to_object(self)? - }; - value - .as_object() - .expect("method must be function object") - .borrow_mut() - .as_function_mut() - .expect("method must be function object") - .set_home_object(object.clone()); - let name = self.vm.frame().code.names[index as usize]; - let name = self.interner().resolve_expect(name.sym()); - object.__define_own_property__( - name.into_common::(false).into(), - PropertyDescriptor::builder() - .value(value) - .writable(true) - .enumerable(false) - .configurable(true) - .build(), - self, - )?; - } - Opcode::SetPropertyByValue => { - let object = self.vm.pop(); - let key = self.vm.pop(); - let value = self.vm.pop(); - let object = if let Some(object) = object.as_object() { - object.clone() - } else { - object.to_object(self)? - }; - - let key = key.to_property_key(self)?; - object.set(key, value, self.vm.frame().code.strict, self)?; - } - Opcode::DefineOwnPropertyByValue => { - let value = self.vm.pop(); - let key = self.vm.pop(); - let object = self.vm.pop(); - let object = if let Some(object) = object.as_object() { - object.clone() - } else { - object.to_object(self)? - }; - let key = key.to_property_key(self)?; - object.__define_own_property__( - key, - PropertyDescriptor::builder() - .value(value) - .writable(true) - .enumerable(true) - .configurable(true) - .build(), - self, - )?; - } - Opcode::DefineClassMethodByValue => { - let value = self.vm.pop(); - let key = self.vm.pop(); - let object = self.vm.pop(); - let object = if let Some(object) = object.as_object() { - object.clone() - } else { - object.to_object(self)? - }; - value - .as_object() - .expect("method must be function object") - .borrow_mut() - .as_function_mut() - .expect("method must be function object") - .set_home_object(object.clone()); - let key = key.to_property_key(self)?; - object.__define_own_property__( - key, - PropertyDescriptor::builder() - .value(value) - .writable(true) - .enumerable(false) - .configurable(true) - .build(), - self, - )?; - } - Opcode::SetPropertyGetterByName => { - let index = self.vm.read::(); - let object = self.vm.pop(); - let value = self.vm.pop(); - let object = object.to_object(self)?; - let name = self.vm.frame().code.names[index as usize]; - let name = self - .interner() - .resolve_expect(name.sym()) - .into_common::(false) - .into(); - let set = object - .__get_own_property__(&name, self)? - .as_ref() - .and_then(PropertyDescriptor::set) - .cloned(); - object.__define_own_property__( - name, - PropertyDescriptor::builder() - .maybe_get(Some(value)) - .maybe_set(set) - .enumerable(true) - .configurable(true) - .build(), - self, - )?; - } - Opcode::DefineClassGetterByName => { - let index = self.vm.read::(); - let object = self.vm.pop(); - let value = self.vm.pop(); - let object = object.to_object(self)?; - value - .as_object() - .expect("method must be function object") - .borrow_mut() - .as_function_mut() - .expect("method must be function object") - .set_home_object(object.clone()); - let name = self.vm.frame().code.names[index as usize]; - let name = self - .interner() - .resolve_expect(name.sym()) - .into_common::(false) - .into(); - let set = object - .__get_own_property__(&name, self)? - .as_ref() - .and_then(PropertyDescriptor::set) - .cloned(); - object.__define_own_property__( - name, - PropertyDescriptor::builder() - .maybe_get(Some(value)) - .maybe_set(set) - .enumerable(false) - .configurable(true) - .build(), - self, - )?; - } - Opcode::SetPropertyGetterByValue => { - let value = self.vm.pop(); - let key = self.vm.pop(); - let object = self.vm.pop(); - let object = object.to_object(self)?; - let name = key.to_property_key(self)?; - let set = object - .__get_own_property__(&name, self)? - .as_ref() - .and_then(PropertyDescriptor::set) - .cloned(); - object.__define_own_property__( - name, - PropertyDescriptor::builder() - .maybe_get(Some(value)) - .maybe_set(set) - .enumerable(true) - .configurable(true) - .build(), - self, - )?; - } - Opcode::DefineClassGetterByValue => { - let value = self.vm.pop(); - let key = self.vm.pop(); - let object = self.vm.pop(); - let object = object.to_object(self)?; - value - .as_object() - .expect("method must be function object") - .borrow_mut() - .as_function_mut() - .expect("method must be function object") - .set_home_object(object.clone()); - let name = key.to_property_key(self)?; - let set = object - .__get_own_property__(&name, self)? - .as_ref() - .and_then(PropertyDescriptor::set) - .cloned(); - object.__define_own_property__( - name, - PropertyDescriptor::builder() - .maybe_get(Some(value)) - .maybe_set(set) - .enumerable(false) - .configurable(true) - .build(), - self, - )?; - } - Opcode::SetPropertySetterByName => { - let index = self.vm.read::(); - let object = self.vm.pop(); - let value = self.vm.pop(); - let object = object.to_object(self)?; - let name = self.vm.frame().code.names[index as usize]; - let name = self - .interner() - .resolve_expect(name.sym()) - .into_common::(false) - .into(); - let get = object - .__get_own_property__(&name, self)? - .as_ref() - .and_then(PropertyDescriptor::get) - .cloned(); - object.__define_own_property__( - name, - PropertyDescriptor::builder() - .maybe_set(Some(value)) - .maybe_get(get) - .enumerable(true) - .configurable(true) - .build(), - self, - )?; - } - Opcode::DefineClassSetterByName => { - let index = self.vm.read::(); - let object = self.vm.pop(); - let value = self.vm.pop(); - let object = object.to_object(self)?; - value - .as_object() - .expect("method must be function object") - .borrow_mut() - .as_function_mut() - .expect("method must be function object") - .set_home_object(object.clone()); - let name = self.vm.frame().code.names[index as usize]; - let name = self - .interner() - .resolve_expect(name.sym()) - .into_common::(false) - .into(); - let get = object - .__get_own_property__(&name, self)? - .as_ref() - .and_then(PropertyDescriptor::get) - .cloned(); - object.__define_own_property__( - name, - PropertyDescriptor::builder() - .maybe_set(Some(value)) - .maybe_get(get) - .enumerable(false) - .configurable(true) - .build(), - self, - )?; - } - Opcode::SetPropertySetterByValue => { - let value = self.vm.pop(); - let key = self.vm.pop(); - let object = self.vm.pop(); - let object = object.to_object(self)?; - let name = key.to_property_key(self)?; - let get = object - .__get_own_property__(&name, self)? - .as_ref() - .and_then(PropertyDescriptor::get) - .cloned(); - object.__define_own_property__( - name, - PropertyDescriptor::builder() - .maybe_set(Some(value)) - .maybe_get(get) - .enumerable(true) - .configurable(true) - .build(), - self, - )?; - } - Opcode::DefineClassSetterByValue => { - let value = self.vm.pop(); - let key = self.vm.pop(); - let object = self.vm.pop(); - let object = object.to_object(self)?; - value - .as_object() - .expect("method must be function object") - .borrow_mut() - .as_function_mut() - .expect("method must be function object") - .set_home_object(object.clone()); - let name = key.to_property_key(self)?; - let get = object - .__get_own_property__(&name, self)? - .as_ref() - .and_then(PropertyDescriptor::get) - .cloned(); - object.__define_own_property__( - name, - PropertyDescriptor::builder() - .maybe_set(Some(value)) - .maybe_get(get) - .enumerable(false) - .configurable(true) - .build(), - self, - )?; - } - Opcode::AssignPrivateField => { - let index = self.vm.read::(); - let name = self.vm.frame().code.names[index as usize]; - let value = self.vm.pop(); - let object = self.vm.pop(); - if let Some(object) = object.as_object() { - let mut object_borrow_mut = object.borrow_mut(); - match object_borrow_mut.get_private_element(name.sym()) { - Some(PrivateElement::Field(_)) => { - object_borrow_mut - .set_private_element(name.sym(), PrivateElement::Field(value)); - } - Some(PrivateElement::Method(_)) => { - return Err(JsNativeError::typ() - .with_message("private method is not writable") - .into()); - } - Some(PrivateElement::Accessor { - setter: Some(setter), - .. - }) => { - let setter = setter.clone(); - drop(object_borrow_mut); - setter.call(&object.clone().into(), &[value], self)?; - } - None => { - return Err(JsNativeError::typ() - .with_message("private field not defined") - .into()); - } - _ => { - return Err(JsNativeError::typ() - .with_message("private field defined without a setter") - .into()); - } - } - } else { - return Err(JsNativeError::typ() - .with_message("cannot set private property on non-object") - .into()); - } - } - Opcode::SetPrivateField => { - let index = self.vm.read::(); - let name = self.vm.frame().code.names[index as usize]; - let value = self.vm.pop(); - let object = self.vm.pop(); - if let Some(object) = object.as_object() { - let mut object_borrow_mut = object.borrow_mut(); - if let Some(PrivateElement::Accessor { - getter: _, - setter: Some(setter), - }) = object_borrow_mut.get_private_element(name.sym()) - { - let setter = setter.clone(); - drop(object_borrow_mut); - setter.call(&object.clone().into(), &[value], self)?; - } else { - object_borrow_mut - .set_private_element(name.sym(), PrivateElement::Field(value)); - } - } else { - return Err(JsNativeError::typ() - .with_message("cannot set private property on non-object") - .into()); - } - } - Opcode::SetPrivateMethod => { - let index = self.vm.read::(); - let name = self.vm.frame().code.names[index as usize]; - let value = self.vm.pop(); - let value = value.as_callable().expect("method must be callable"); - let object = self.vm.pop(); - if let Some(object) = object.as_object() { - let mut object_borrow_mut = object.borrow_mut(); - object_borrow_mut - .set_private_element(name.sym(), PrivateElement::Method(value.clone())); - } else { - return Err(JsNativeError::typ() - .with_message("cannot set private setter on non-object") - .into()); - } - } - Opcode::SetPrivateSetter => { - let index = self.vm.read::(); - let name = self.vm.frame().code.names[index as usize]; - let value = self.vm.pop(); - let value = value.as_callable().expect("setter must be callable"); - let object = self.vm.pop(); - if let Some(object) = object.as_object() { - let mut object_borrow_mut = object.borrow_mut(); - object_borrow_mut.set_private_element_setter(name.sym(), value.clone()); - } else { - return Err(JsNativeError::typ() - .with_message("cannot set private setter on non-object") - .into()); - } - } - Opcode::SetPrivateGetter => { - let index = self.vm.read::(); - let name = self.vm.frame().code.names[index as usize]; - let value = self.vm.pop(); - let value = value.as_callable().expect("getter must be callable"); - let object = self.vm.pop(); - if let Some(object) = object.as_object() { - let mut object_borrow_mut = object.borrow_mut(); - object_borrow_mut.set_private_element_getter(name.sym(), value.clone()); - } else { - return Err(JsNativeError::typ() - .with_message("cannot set private getter on non-object") - .into()); - } - } - Opcode::GetPrivateField => { - let index = self.vm.read::(); - let name = self.vm.frame().code.names[index as usize]; - let value = self.vm.pop(); - if let Some(object) = value.as_object() { - let object_borrow_mut = object.borrow(); - if let Some(element) = object_borrow_mut.get_private_element(name.sym()) { - match element { - PrivateElement::Field(value) => self.vm.push(value), - PrivateElement::Method(method) => self.vm.push(method.clone()), - PrivateElement::Accessor { - getter: Some(getter), - setter: _, - } => { - let value = getter.call(&value, &[], self)?; - self.vm.push(value); - } - PrivateElement::Accessor { .. } => { - return Err(JsNativeError::typ() - .with_message("private property was defined without a getter") - .into()); - } - } - } else { - return Err(JsNativeError::typ() - .with_message("private property does not exist") - .into()); - } - } else { - return Err(JsNativeError::typ() - .with_message("cannot read private property from non-object") - .into()); - } - } - Opcode::PushClassField => { - let field_function_value = self.vm.pop(); - let field_name_value = self.vm.pop(); - let class_value = self.vm.pop(); - - let field_name_key = field_name_value.to_property_key(self)?; - let field_function_object = field_function_value - .as_object() - .expect("field value must be function object"); - let mut field_function_object_borrow = field_function_object.borrow_mut(); - let field_function = field_function_object_borrow - .as_function_mut() - .expect("field value must be function object"); - let class_object = class_value - .as_object() - .expect("class must be function object"); - field_function.set_home_object(class_object.clone()); - class_object - .borrow_mut() - .as_function_mut() - .expect("class must be function object") - .push_field( - field_name_key, - JsFunction::from_object_unchecked(field_function_object.clone()), - ); - } - Opcode::PushClassFieldPrivate => { - let index = self.vm.read::(); - let name = self.vm.frame().code.names[index as usize]; - let field_function_value = self.vm.pop(); - let class_value = self.vm.pop(); - - let field_function_object = field_function_value - .as_object() - .expect("field value must be function object"); - let mut field_function_object_borrow = field_function_object.borrow_mut(); - let field_function = field_function_object_borrow - .as_function_mut() - .expect("field value must be function object"); - let class_object = class_value - .as_object() - .expect("class must be function object"); - field_function.set_home_object(class_object.clone()); - class_object - .borrow_mut() - .as_function_mut() - .expect("class must be function object") - .push_field_private( - name.sym(), - JsFunction::from_object_unchecked(field_function_object.clone()), - ); - } - Opcode::PushClassPrivateGetter => { - let index = self.vm.read::(); - let name = self.vm.frame().code.names[index as usize]; - let getter = self.vm.pop(); - let getter_object = getter.as_callable().expect("getter must be callable"); - let class = self.vm.pop(); - class - .as_object() - .expect("class must be function object") - .borrow_mut() - .as_function_mut() - .expect("class must be function object") - .push_private_method( - name.sym(), - PrivateElement::Accessor { - getter: Some(getter_object.clone()), - setter: None, - }, - ); - } - Opcode::PushClassPrivateSetter => { - let index = self.vm.read::(); - let name = self.vm.frame().code.names[index as usize]; - let setter = self.vm.pop(); - let setter_object = setter.as_callable().expect("getter must be callable"); - let class = self.vm.pop(); - class - .as_object() - .expect("class must be function object") - .borrow_mut() - .as_function_mut() - .expect("class must be function object") - .push_private_method( - name.sym(), - PrivateElement::Accessor { - getter: None, - setter: Some(setter_object.clone()), - }, - ); - } - Opcode::PushClassPrivateMethod => { - let index = self.vm.read::(); - let name = self.vm.frame().code.names[index as usize]; - let method = self.vm.pop(); - let method_object = method.as_callable().expect("method must be callable"); - let class = self.vm.pop(); - class - .as_object() - .expect("class must be function object") - .borrow_mut() - .as_function_mut() - .expect("class must be function object") - .push_private_method(name.sym(), PrivateElement::Method(method_object.clone())); - } - Opcode::DeletePropertyByName => { - let index = self.vm.read::(); - let key = self.vm.frame().code.names[index as usize]; - let key = self - .interner() - .resolve_expect(key.sym()) - .into_common::(false) - .into(); - let object = self.vm.pop(); - let result = object.to_object(self)?.__delete__(&key, self)?; - if !result && self.vm.frame().code.strict { - return Err(JsNativeError::typ() - .with_message("Cannot delete property") - .into()); - } - self.vm.push(result); - } - Opcode::DeletePropertyByValue => { - let object = self.vm.pop(); - let key = self.vm.pop(); - let result = object - .to_object(self)? - .__delete__(&key.to_property_key(self)?, self)?; - if !result && self.vm.frame().code.strict { - return Err(JsNativeError::typ() - .with_message("Cannot delete property") - .into()); - } - self.vm.push(result); - } - Opcode::CopyDataProperties => { - let excluded_key_count = self.vm.read::(); - let excluded_key_count_computed = self.vm.read::(); - let mut excluded_keys = Vec::with_capacity(excluded_key_count as usize); - for _ in 0..excluded_key_count { - let key = self.vm.pop(); - excluded_keys - .push(key.to_property_key(self).expect("key must be property key")); - } - let value = self.vm.pop(); - let object = value.as_object().expect("not an object"); - let source = self.vm.pop(); - for _ in 0..excluded_key_count_computed { - let key = self.vm.pop(); - excluded_keys - .push(key.to_property_key(self).expect("key must be property key")); - } - object.copy_data_properties(&source, excluded_keys, self)?; - self.vm.push(value); - } - Opcode::ToPropertyKey => { - let value = self.vm.pop(); - let key = value.to_property_key(self)?; - self.vm.push(key); - } - Opcode::Throw => { - let value = self.vm.pop(); - return Err(JsError::from_opaque(value)); - } - Opcode::TryStart => { - let next = self.vm.read::(); - let finally = self.vm.read::(); - let finally = if finally == 0 { None } else { Some(finally) }; - self.vm - .frame_mut() - .catch - .push(CatchAddresses { next, finally }); - self.vm.frame_mut().finally_jump.push(None); - self.vm.frame_mut().finally_return = FinallyReturn::None; - self.vm.frame_mut().try_env_stack.push(TryStackEntry { - num_env: 0, - num_loop_stack_entries: 0, - }); - } - Opcode::TryEnd | Opcode::CatchEnd => { - self.vm.frame_mut().catch.pop(); - let try_stack_entry = self.vm.frame_mut().try_env_stack.pop().expect("must exist"); - for _ in 0..try_stack_entry.num_env { - self.realm.environments.pop(); - } - let mut num_env = try_stack_entry.num_env; - for _ in 0..try_stack_entry.num_loop_stack_entries { - num_env -= self - .vm - .frame_mut() - .loop_env_stack - .pop() - .expect("must exist"); - } - *self - .vm - .frame_mut() - .loop_env_stack - .last_mut() - .expect("must exist") -= num_env; - self.vm.frame_mut().finally_return = FinallyReturn::None; - } - Opcode::CatchStart => { - let finally = self.vm.read::(); - self.vm.frame_mut().catch.push(CatchAddresses { - next: finally, - finally: Some(finally), - }); - self.vm.frame_mut().try_env_stack.push(TryStackEntry { - num_env: 0, - num_loop_stack_entries: 0, - }); - self.vm.frame_mut().thrown = false; - } - Opcode::CatchEnd2 => { - let frame = self.vm.frame_mut(); - if frame.finally_return == FinallyReturn::Err { - frame.finally_return = FinallyReturn::None; - } - } - Opcode::FinallyStart => { - *self - .vm - .frame_mut() - .finally_jump - .last_mut() - .expect("finally jump must exist here") = None; - } - Opcode::FinallyEnd => { - let address = self - .vm - .frame_mut() - .finally_jump - .pop() - .expect("finally jump must exist here"); - match self.vm.frame_mut().finally_return { - FinallyReturn::None => { - if let Some(address) = address { - self.vm.frame_mut().pc = address as usize; - } - } - FinallyReturn::Ok => { - return Ok(ShouldExit::True); - } - FinallyReturn::Err => { - return Err(JsError::from_opaque(self.vm.pop())); - } - } - } - Opcode::FinallySetJump => { - let address = self.vm.read::(); - *self - .vm - .frame_mut() - .finally_jump - .last_mut() - .expect("finally jump must exist here") = Some(address); - } - Opcode::This => { - let env = self.realm.environments.get_this_environment(); - match env { - EnvironmentSlots::Function(env) => { - self.vm.push(env.borrow().get_this_binding()?); - } - EnvironmentSlots::Global => { - let this = self.realm.global_object(); - self.vm.push(this.clone()); - } - } - } - Opcode::Super => { - let home = { - let env = self - .realm - .environments - .get_this_environment() - .as_function_slots() - .expect("super access must be in a function environment"); - let env = env.borrow(); - let this = env.get_this_binding()?; - let function_object = env.function_object().borrow(); - let function = function_object - .as_function() - .expect("must be function object"); - - function.get_home_object().or(this.as_object()).cloned() - }; - - if let Some(home) = home { - if let Some(proto) = home.__get_prototype_of__(self)? { - self.vm.push(JsValue::from(proto)); - } else { - self.vm.push(JsValue::Null); - } - } else { - self.vm.push(JsValue::Null); - }; - } - Opcode::SuperCall => { - let argument_count = self.vm.read::(); - let mut arguments = Vec::with_capacity(argument_count as usize); - for _ in 0..argument_count { - arguments.push(self.vm.pop()); - } - arguments.reverse(); - - let (new_target, active_function) = { - let this_env = self - .realm - .environments - .get_this_environment() - .as_function_slots() - .expect("super call must be in function environment"); - let this_env_borrow = this_env.borrow(); - let new_target = this_env_borrow - .new_target() - .expect("must have new target") - .clone(); - let active_function = this_env.borrow().function_object().clone(); - (new_target, active_function) - }; - let super_constructor = active_function - .__get_prototype_of__(self) - .expect("function object must have prototype") - .expect("function object must have prototype"); - - if !super_constructor.is_constructor() { - return Err(JsNativeError::typ() - .with_message("super constructor object must be constructor") - .into()); - } - - let result = super_constructor.__construct__(&arguments, &new_target, self)?; - - initialize_instance_elements(&result, &active_function, self)?; - - let this_env = self - .realm - .environments - .get_this_environment() - .as_function_slots() - .expect("super call must be in function environment"); - - if !this_env.borrow_mut().bind_this_value(&result) { - return Err(JsNativeError::reference() - .with_message("this already initialized") - .into()); - } - self.vm.push(result); - } - Opcode::SuperCallSpread => { - // Get the arguments that are stored as an array object on the stack. - let arguments_array = self.vm.pop(); - let arguments_array_object = arguments_array - .as_object() - .expect("arguments array in call spread function must be an object"); - let arguments = arguments_array_object - .borrow() - .properties() - .dense_indexed_properties() - .expect("arguments array in call spread function must be dense") - .clone(); - - let (new_target, active_function) = { - let this_env = self - .realm - .environments - .get_this_environment() - .as_function_slots() - .expect("super call must be in function environment"); - let this_env_borrow = this_env.borrow(); - let new_target = this_env_borrow - .new_target() - .expect("must have new target") - .clone(); - let active_function = this_env.borrow().function_object().clone(); - (new_target, active_function) - }; - let super_constructor = active_function - .__get_prototype_of__(self) - .expect("function object must have prototype") - .expect("function object must have prototype"); - - if !super_constructor.is_constructor() { - return Err(JsNativeError::typ() - .with_message("super constructor object must be constructor") - .into()); - } - - let result = super_constructor.__construct__(&arguments, &new_target, self)?; - - initialize_instance_elements(&result, &active_function, self)?; - - let this_env = self - .realm - .environments - .get_this_environment() - .as_function_slots() - .expect("super call must be in function environment"); - - if !this_env.borrow_mut().bind_this_value(&result) { - return Err(JsNativeError::reference() - .with_message("this already initialized") - .into()); - } - self.vm.push(result); - } - Opcode::SuperCallDerived => { - let argument_count = self.vm.frame().arg_count; - let mut arguments = Vec::with_capacity(argument_count); - for _ in 0..argument_count { - arguments.push(self.vm.pop()); - } - arguments.reverse(); - - let (new_target, active_function) = { - let this_env = self - .realm - .environments - .get_this_environment() - .as_function_slots() - .expect("super call must be in function environment"); - let this_env_borrow = this_env.borrow(); - let new_target = this_env_borrow - .new_target() - .expect("must have new target") - .clone(); - let active_function = this_env.borrow().function_object().clone(); - (new_target, active_function) - }; - let super_constructor = active_function - .__get_prototype_of__(self) - .expect("function object must have prototype") - .expect("function object must have prototype"); - - if !super_constructor.is_constructor() { - return Err(JsNativeError::typ() - .with_message("super constructor object must be constructor") - .into()); - } - - let result = super_constructor.__construct__(&arguments, &new_target, self)?; - - initialize_instance_elements(&result, &active_function, self)?; - - let this_env = self - .realm - .environments - .get_this_environment() - .as_function_slots() - .expect("super call must be in function environment"); - if !this_env.borrow_mut().bind_this_value(&result) { - return Err(JsNativeError::reference() - .with_message("this already initialized") - .into()); - } - - self.vm.push(result); - } - Opcode::Case => { - let address = self.vm.read::(); - let cond = self.vm.pop(); - let value = self.vm.pop(); - - if value.strict_equals(&cond) { - self.vm.frame_mut().pc = address as usize; - } else { - self.vm.push(value); - } - } - Opcode::Default => { - let exit = self.vm.read::(); - let _val = self.vm.pop(); - self.vm.frame_mut().pc = exit as usize; - } - Opcode::GetFunction => { - let index = self.vm.read::(); - let code = self.vm.frame().code.functions[index as usize].clone(); - let function = create_function_object(code, false, None, self); - self.vm.push(function); - } - Opcode::GetFunctionAsync => { - let index = self.vm.read::(); - let code = self.vm.frame().code.functions[index as usize].clone(); - let function = create_function_object(code, true, None, self); - self.vm.push(function); - } - Opcode::GetGenerator => { - let index = self.vm.read::(); - let code = self.vm.frame().code.functions[index as usize].clone(); - let function = create_generator_function_object(code, false, self); - self.vm.push(function); - } - Opcode::GetGeneratorAsync => { - let index = self.vm.read::(); - let code = self.vm.frame().code.functions[index as usize].clone(); - let function = create_generator_function_object(code, true, self); - self.vm.push(function); - } - Opcode::CallEval => { - if self.vm.stack_size_limit <= self.vm.stack.len() { - return Err(JsNativeError::range() - .with_message("Maximum call stack size exceeded") - .into()); - } - let argument_count = self.vm.read::(); - let mut arguments = Vec::with_capacity(argument_count as usize); - for _ in 0..argument_count { - arguments.push(self.vm.pop()); - } - arguments.reverse(); - - let func = self.vm.pop(); - let this = self.vm.pop(); - - let object = match func { - JsValue::Object(ref object) if object.is_callable() => object.clone(), - _ => { - return Err(JsNativeError::typ() - .with_message("not a callable function") - .into()) - } - }; - - // A native function with the name "eval" implies, that is this the built-in eval function. - let eval = matches!(object.borrow().as_function(), Some(Function::Native { .. })); - - let strict = self.vm.frame().code.strict; - - if eval { - if let Some(x) = arguments.get(0) { - let result = - crate::builtins::eval::Eval::perform_eval(x, true, strict, self)?; - self.vm.push(result); - } else { - self.vm.push(JsValue::Undefined); - } - } else { - let result = object.__call__(&this, &arguments, self)?; - self.vm.push(result); - } - } - Opcode::CallEvalSpread => { - if self.vm.stack_size_limit <= self.vm.stack.len() { - return Err(JsNativeError::range() - .with_message("Maximum call stack size exceeded") - .into()); - } - - // Get the arguments that are stored as an array object on the stack. - let arguments_array = self.vm.pop(); - let arguments_array_object = arguments_array - .as_object() - .expect("arguments array in call spread function must be an object"); - let arguments = arguments_array_object - .borrow() - .properties() - .dense_indexed_properties() - .expect("arguments array in call spread function must be dense") - .clone(); - - let func = self.vm.pop(); - let this = self.vm.pop(); - - let object = match func { - JsValue::Object(ref object) if object.is_callable() => object.clone(), - _ => { - return Err(JsNativeError::typ() - .with_message("not a callable function") - .into()) - } - }; - - // A native function with the name "eval" implies, that is this the built-in eval function. - let eval = matches!(object.borrow().as_function(), Some(Function::Native { .. })); - - let strict = self.vm.frame().code.strict; - - if eval { - if let Some(x) = arguments.get(0) { - let result = - crate::builtins::eval::Eval::perform_eval(x, true, strict, self)?; - self.vm.push(result); - } else { - self.vm.push(JsValue::Undefined); - } - } else { - let result = object.__call__(&this, &arguments, self)?; - self.vm.push(result); - } - } - Opcode::Call => { - if self.vm.stack_size_limit <= self.vm.stack.len() { - return Err(JsNativeError::range() - .with_message("Maximum call stack size exceeded") - .into()); - } - let argument_count = self.vm.read::(); - let mut arguments = Vec::with_capacity(argument_count as usize); - for _ in 0..argument_count { - arguments.push(self.vm.pop()); - } - arguments.reverse(); - - let func = self.vm.pop(); - let this = self.vm.pop(); - - let object = match func { - JsValue::Object(ref object) if object.is_callable() => object.clone(), - _ => { - return Err(JsNativeError::typ() - .with_message("not a callable function") - .into()) - } - }; - - let result = object.__call__(&this, &arguments, self)?; - - self.vm.push(result); - } - Opcode::CallSpread => { - if self.vm.stack_size_limit <= self.vm.stack.len() { - return Err(JsNativeError::range() - .with_message("Maximum call stack size exceeded") - .into()); - } - - // Get the arguments that are stored as an array object on the stack. - let arguments_array = self.vm.pop(); - let arguments_array_object = arguments_array - .as_object() - .expect("arguments array in call spread function must be an object"); - let arguments = arguments_array_object - .borrow() - .properties() - .dense_indexed_properties() - .expect("arguments array in call spread function must be dense") - .clone(); - - let func = self.vm.pop(); - let this = self.vm.pop(); - - let object = match func { - JsValue::Object(ref object) if object.is_callable() => object.clone(), - _ => { - return Err(JsNativeError::typ() - .with_message("not a callable function") - .into()) - } - }; - - let result = object.__call__(&this, &arguments, self)?; - - self.vm.push(result); - } - Opcode::New => { - if self.vm.stack_size_limit <= self.vm.stack.len() { - return Err(JsNativeError::range() - .with_message("Maximum call stack size exceeded") - .into()); - } - let argument_count = self.vm.read::(); - let mut arguments = Vec::with_capacity(argument_count as usize); - for _ in 0..argument_count { - arguments.push(self.vm.pop()); - } - arguments.reverse(); - let func = self.vm.pop(); - - let result = func - .as_constructor() - .ok_or_else(|| { - JsNativeError::typ() - .with_message("not a constructor") - .into() - }) - .and_then(|cons| cons.__construct__(&arguments, cons, self))?; - - self.vm.push(result); - } - Opcode::NewSpread => { - if self.vm.stack_size_limit <= self.vm.stack.len() { - return Err(JsNativeError::range() - .with_message("Maximum call stack size exceeded") - .into()); - } - // Get the arguments that are stored as an array object on the stack. - let arguments_array = self.vm.pop(); - let arguments_array_object = arguments_array - .as_object() - .expect("arguments array in call spread function must be an object"); - let arguments = arguments_array_object - .borrow() - .properties() - .dense_indexed_properties() - .expect("arguments array in call spread function must be dense") - .clone(); - - let func = self.vm.pop(); - - let result = func - .as_constructor() - .ok_or_else(|| { - JsNativeError::typ() - .with_message("not a constructor") - .into() - }) - .and_then(|cons| cons.__construct__(&arguments, cons, self))?; - self.vm.push(result); - } - Opcode::Return => { - if let Some(finally_address) = self.vm.frame().catch.last().and_then(|c| c.finally) - { - let frame = self.vm.frame_mut(); - frame.pc = finally_address as usize; - frame.finally_return = FinallyReturn::Ok; - frame.catch.pop(); - let try_stack_entry = - self.vm.frame_mut().try_env_stack.pop().expect("must exist"); - for _ in 0..try_stack_entry.num_env { - self.realm.environments.pop(); - } - let mut num_env = try_stack_entry.num_env; - for _ in 0..try_stack_entry.num_loop_stack_entries { - num_env -= self - .vm - .frame_mut() - .loop_env_stack - .pop() - .expect("must exist"); - } - *self - .vm - .frame_mut() - .loop_env_stack - .last_mut() - .expect("must exist") -= num_env; - } else { - return Ok(ShouldExit::True); - } - } - Opcode::PushDeclarativeEnvironment => { - let num_bindings = self.vm.read::(); - let compile_environments_index = self.vm.read::(); - let compile_environment = self.vm.frame().code.compile_environments - [compile_environments_index as usize] - .clone(); - self.realm - .environments - .push_declarative(num_bindings as usize, compile_environment); - self.vm.frame_mut().loop_env_stack_inc(); - self.vm.frame_mut().try_env_stack_inc(); - } - Opcode::PushFunctionEnvironment => { - let num_bindings = self.vm.read::(); - let compile_environments_index = self.vm.read::(); - let compile_environment = self.vm.frame().code.compile_environments - [compile_environments_index as usize] - .clone(); - self.realm - .environments - .push_function_inherit(num_bindings as usize, compile_environment); - } - Opcode::PopEnvironment => { - self.realm.environments.pop(); - self.vm.frame_mut().loop_env_stack_dec(); - self.vm.frame_mut().try_env_stack_dec(); - } - Opcode::LoopStart => { - self.vm.frame_mut().loop_env_stack.push(0); - self.vm.frame_mut().try_env_stack_loop_inc(); - } - Opcode::LoopContinue => { - let env_num = self - .vm - .frame_mut() - .loop_env_stack - .last_mut() - .expect("loop env stack entry must exist"); - let env_num_copy = *env_num; - *env_num = 0; - for _ in 0..env_num_copy { - self.realm.environments.pop(); - } - } - Opcode::LoopEnd => { - let env_num = self - .vm - .frame_mut() - .loop_env_stack - .pop() - .expect("loop env stack entry must exist"); - for _ in 0..env_num { - self.realm.environments.pop(); - self.vm.frame_mut().try_env_stack_dec(); - } - self.vm.frame_mut().try_env_stack_loop_dec(); - } - Opcode::ForInLoopInitIterator => { - let address = self.vm.read::(); - - let object = self.vm.pop(); - if object.is_null_or_undefined() { - self.vm.frame_mut().pc = address as usize; - return Ok(ShouldExit::False); - } - - let object = object.to_object(self)?; - let iterator = ForInIterator::create_for_in_iterator(JsValue::new(object), self); - let next_method = iterator - .get_property("next") - .as_ref() - .map(PropertyDescriptor::expect_value) - .cloned() - .ok_or_else(|| { - JsNativeError::typ().with_message("Could not find property `next`") - })?; - - self.vm.push(iterator); - self.vm.push(next_method); - self.vm.push(false); - } - Opcode::InitIterator => { - let object = self.vm.pop(); - let iterator = object.get_iterator(self, None, None)?; - self.vm.push(iterator.iterator().clone()); - self.vm.push(iterator.next_method().clone()); - self.vm.push(iterator.done()); - } - Opcode::InitIteratorAsync => { - let object = self.vm.pop(); - let iterator = object.get_iterator(self, Some(IteratorHint::Async), None)?; - self.vm.push(iterator.iterator().clone()); - self.vm.push(iterator.next_method().clone()); - self.vm.push(iterator.done()); - } - Opcode::IteratorNext => { - let done = self - .vm - .pop() - .as_boolean() - .expect("iterator [[Done]] was not a boolean"); - let next_method = self.vm.pop(); - let iterator = self.vm.pop(); - let iterator = iterator.as_object().expect("iterator was not an object"); - - let iterator_record = - IteratorRecord::new(iterator.clone(), next_method.clone(), done); - let next = iterator_record.step(self)?; - - self.vm.push(iterator.clone()); - self.vm.push(next_method); - if let Some(next) = next { - let value = next.value(self)?; - self.vm.push(false); - self.vm.push(value); - } else { - self.vm.push(true); - self.vm.push(JsValue::undefined()); - } - } - Opcode::IteratorClose => { - let done = self - .vm - .pop() - .as_boolean() - .expect("iterator [[Done]] was not a boolean"); - let next_method = self.vm.pop(); - let iterator = self.vm.pop(); - let iterator = iterator.as_object().expect("iterator was not an object"); - if !done { - let iterator_record = IteratorRecord::new(iterator.clone(), next_method, done); - iterator_record.close(Ok(JsValue::Null), self)?; - } - } - Opcode::IteratorToArray => { - let done = self - .vm - .pop() - .as_boolean() - .expect("iterator [[Done]] was not a boolean"); - let next_method = self.vm.pop(); - let iterator = self.vm.pop(); - let iterator = iterator.as_object().expect("iterator was not an object"); - - let iterator_record = - IteratorRecord::new(iterator.clone(), next_method.clone(), done); - let mut values = Vec::new(); - - while let Some(result) = iterator_record.step(self)? { - values.push(result.value(self)?); - } - - let array = Array::create_array_from_list(values, self); - - self.vm.push(iterator.clone()); - self.vm.push(next_method); - self.vm.push(true); - self.vm.push(array); - } - Opcode::ForInLoopNext => { - let address = self.vm.read::(); - - let done = self - .vm - .pop() - .as_boolean() - .expect("iterator [[Done]] was not a boolean"); - let next_method = self.vm.pop(); - let iterator = self.vm.pop(); - let iterator = iterator.as_object().expect("iterator was not an object"); - - let iterator_record = - IteratorRecord::new(iterator.clone(), next_method.clone(), done); - if let Some(next) = iterator_record.step(self)? { - self.vm.push(iterator.clone()); - self.vm.push(next_method); - self.vm.push(done); - let value = next.value(self)?; - self.vm.push(value); - } else { - self.vm.frame_mut().pc = address as usize; - self.vm.frame_mut().loop_env_stack_dec(); - self.vm.frame_mut().try_env_stack_dec(); - self.realm.environments.pop(); - self.vm.push(iterator.clone()); - self.vm.push(next_method); - self.vm.push(done); - } - } - Opcode::ForAwaitOfLoopIterate => { - let _done = self - .vm - .pop() - .as_boolean() - .expect("iterator [[Done]] was not a boolean"); - let next_method = self.vm.pop(); - let next_method_object = if let Some(object) = next_method.as_callable() { - object - } else { - return Err(JsNativeError::typ() - .with_message("iterable next method not a function") - .into()); - }; - let iterator = self.vm.pop(); - let next_result = next_method_object.call(&iterator, &[], self)?; - self.vm.push(iterator); - self.vm.push(next_method); - self.vm.push(next_result); - } - Opcode::ForAwaitOfLoopNext => { - let address = self.vm.read::(); - - let next_result = self.vm.pop(); - let next_result = if let Some(next_result) = next_result.as_object() { - IteratorResult::new(next_result.clone()) - } else { - return Err(JsNativeError::typ() - .with_message("next value should be an object") - .into()); - }; - - if next_result.complete(self)? { - self.vm.frame_mut().pc = address as usize; - self.vm.frame_mut().loop_env_stack_dec(); - self.vm.frame_mut().try_env_stack_dec(); - self.realm.environments.pop(); - self.vm.push(true); - } else { - self.vm.push(false); - let value = next_result.value(self)?; - self.vm.push(value); - } - } - Opcode::ConcatToString => { - let value_count = self.vm.read::(); - let mut strings = Vec::with_capacity(value_count as usize); - for _ in 0..value_count { - strings.push(self.vm.pop().to_string(self)?); - } - strings.reverse(); - let s = JsString::concat_array( - &strings - .iter() - .map(JsString::as_slice) - .collect::>(), - ); - self.vm.push(s); - } - Opcode::RequireObjectCoercible => { - let value = self.vm.pop(); - let value = value.require_object_coercible()?; - self.vm.push(value); - } - Opcode::ValueNotNullOrUndefined => { - let value = self.vm.pop(); - if value.is_null() { - return Err(JsNativeError::typ() - .with_message("Cannot destructure 'null' value") - .into()); - } - if value.is_undefined() { - return Err(JsNativeError::typ() - .with_message("Cannot destructure 'undefined' value") - .into()); - } - self.vm.push(value); - } - Opcode::RestParameterInit => { - let arg_count = self.vm.frame().arg_count; - let param_count = self.vm.frame().param_count; - if arg_count >= param_count { - let rest_count = arg_count - param_count + 1; - let mut args = Vec::with_capacity(rest_count); - for _ in 0..rest_count { - args.push(self.vm.pop()); - } - let array: _ = Array::create_array_from_list(args, self); - - self.vm.push(array); - } else { - self.vm.pop(); - - let array = Array::array_create(0, None, self) - .expect("could not create an empty array"); - self.vm.push(array); - } - } - Opcode::RestParameterPop => { - let arg_count = self.vm.frame().arg_count; - let param_count = self.vm.frame().param_count; - if arg_count > param_count { - for _ in 0..(arg_count - param_count) { - self.vm.pop(); - } - } - } - Opcode::PopOnReturnAdd => { - self.vm.frame_mut().pop_on_return += 1; - } - Opcode::PopOnReturnSub => { - self.vm.frame_mut().pop_on_return -= 1; - } - Opcode::Yield => return Ok(ShouldExit::Yield), - Opcode::GeneratorNext => match self.vm.frame().generator_resume_kind { - GeneratorResumeKind::Normal => return Ok(ShouldExit::False), - GeneratorResumeKind::Throw => { - let received = self.vm.pop(); - return Err(JsError::from_opaque(received)); - } - GeneratorResumeKind::Return => { - let mut finally_left = false; - - while let Some(catch_addresses) = self.vm.frame().catch.last() { - if let Some(finally_address) = catch_addresses.finally { - let frame = self.vm.frame_mut(); - frame.pc = finally_address as usize; - frame.finally_return = FinallyReturn::Ok; - frame.catch.pop(); - finally_left = true; - break; - } - self.vm.frame_mut().catch.pop(); - } - - if finally_left { - return Ok(ShouldExit::False); - } - return Ok(ShouldExit::True); - } - }, - Opcode::AsyncGeneratorNext => { - let value = self.vm.pop(); - - if self.vm.frame().generator_resume_kind == GeneratorResumeKind::Throw { - return Err(JsError::from_opaque(value)); - } - - let completion = Ok(value); - let generator_object = self - .vm - .frame() - .async_generator - .as_ref() - .expect("must be in generator context here") - .clone(); - let next = generator_object - .borrow_mut() - .as_async_generator_mut() - .expect("must be async generator object") - .queue - .pop_front() - .expect("must have item in queue"); - AsyncGenerator::complete_step(&next, completion, false, self); - - let mut generator_object_mut = generator_object.borrow_mut(); - let gen = generator_object_mut - .as_async_generator_mut() - .expect("must be async generator object"); - - if let Some(next) = gen.queue.front() { - let (completion, r#return) = &next.completion; - if *r#return { - let value = match completion { - Ok(value) => value.clone(), - Err(e) => e.clone().to_opaque(self), - }; - self.vm.push(value); - self.vm.push(true); - } else { - self.vm.push(completion.clone()?); - self.vm.push(false); - } - - self.vm.push(false); - } else { - gen.state = AsyncGeneratorState::SuspendedYield; - self.vm.push(true); - self.vm.push(true); - } - } - Opcode::GeneratorNextDelegate => { - let done_address = self.vm.read::(); - let received = self.vm.pop(); - let done = self - .vm - .pop() - .as_boolean() - .expect("iterator [[Done]] was not a boolean"); - let next_method = self.vm.pop(); - let iterator = self.vm.pop(); - let iterator = iterator.as_object().expect("iterator was not an object"); - - match self.vm.frame().generator_resume_kind { - GeneratorResumeKind::Normal => { - let result = - self.call(&next_method, &iterator.clone().into(), &[received])?; - let result_object = result.as_object().ok_or_else(|| { - JsNativeError::typ() - .with_message("generator next method returned non-object") - })?; - let done = result_object.get("done", self)?.to_boolean(); - if done { - self.vm.frame_mut().pc = done_address as usize; - let value = result_object.get("value", self)?; - self.vm.push(value); - return Ok(ShouldExit::False); - } - let value = result_object.get("value", self)?; - self.vm.push(iterator.clone()); - self.vm.push(next_method.clone()); - self.vm.push(done); - self.vm.push(value); - return Ok(ShouldExit::Yield); - } - GeneratorResumeKind::Throw => { - let throw = iterator.get_method("throw", self)?; - if let Some(throw) = throw { - let result = throw.call(&iterator.clone().into(), &[received], self)?; - let result_object = result.as_object().ok_or_else(|| { - JsNativeError::typ() - .with_message("generator throw method returned non-object") - })?; - let done = result_object.get("done", self)?.to_boolean(); - if done { - self.vm.frame_mut().pc = done_address as usize; - let value = result_object.get("value", self)?; - self.vm.push(value); - return Ok(ShouldExit::False); - } - let value = result_object.get("value", self)?; - self.vm.push(iterator.clone()); - self.vm.push(next_method.clone()); - self.vm.push(done); - self.vm.push(value); - return Ok(ShouldExit::Yield); - } - self.vm.frame_mut().pc = done_address as usize; - let iterator_record = - IteratorRecord::new(iterator.clone(), next_method, done); - iterator_record.close(Ok(JsValue::Undefined), self)?; - return Err(JsNativeError::typ() - .with_message("iterator does not have a throw method") - .into()); - } - GeneratorResumeKind::Return => { - let r#return = iterator.get_method("return", self)?; - if let Some(r#return) = r#return { - let result = - r#return.call(&iterator.clone().into(), &[received], self)?; - let result_object = result.as_object().ok_or_else(|| { - JsNativeError::typ() - .with_message("generator return method returned non-object") - })?; - let done = result_object.get("done", self)?.to_boolean(); - if done { - self.vm.frame_mut().pc = done_address as usize; - let value = result_object.get("value", self)?; - self.vm.push(value); - return Ok(ShouldExit::True); - } - let value = result_object.get("value", self)?; - self.vm.push(iterator.clone()); - self.vm.push(next_method.clone()); - self.vm.push(done); - self.vm.push(value); - return Ok(ShouldExit::Yield); - } - self.vm.frame_mut().pc = done_address as usize; - self.vm.push(received); - return Ok(ShouldExit::True); - } - } - } - Opcode::Await => { - let value = self.vm.pop(); - - // 2. Let promise be ? PromiseResolve(%Promise%, value). - let promise = Promise::promise_resolve( - self.intrinsics().constructors().promise().constructor(), - value, - self, - )?; - - // 3. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures asyncContext and performs the following steps when called: - // 4. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »). - let on_fulfilled = FunctionBuilder::closure_with_captures( - self, - |_this, args, (environment, stack, frame), context| { - // a. Let prevContext be the running execution context. - // b. Suspend prevContext. - // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context. - // d. Resume the suspended evaluation of asyncContext using NormalCompletion(value) as the result of the operation that suspended it. - // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context. - // f. Return undefined. - - std::mem::swap(&mut context.realm.environments, environment); - std::mem::swap(&mut context.vm.stack, stack); - context.vm.push_frame(frame.clone()); - - context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Normal; - context.vm.push(args.get_or_undefined(0)); - context.run()?; - - *frame = context - .vm - .pop_frame() - .expect("generator call frame must exist"); - std::mem::swap(&mut context.realm.environments, environment); - std::mem::swap(&mut context.vm.stack, stack); - - Ok(JsValue::undefined()) - }, - ( - self.realm.environments.clone(), - self.vm.stack.clone(), - self.vm.frame().clone(), - ), - ) - .name("") - .length(1) - .build(); - - // 5. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures asyncContext and performs the following steps when called: - // 6. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »). - let on_rejected = FunctionBuilder::closure_with_captures( - self, - |_this, args, (environment, stack, frame), context| { - // a. Let prevContext be the running execution context. - // b. Suspend prevContext. - // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context. - // d. Resume the suspended evaluation of asyncContext using ThrowCompletion(reason) as the result of the operation that suspended it. - // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context. - // f. Return undefined. - - std::mem::swap(&mut context.realm.environments, environment); - std::mem::swap(&mut context.vm.stack, stack); - context.vm.push_frame(frame.clone()); - - context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Throw; - context.vm.push(args.get_or_undefined(0)); - context.run()?; - - *frame = context - .vm - .pop_frame() - .expect("generator call frame must exist"); - std::mem::swap(&mut context.realm.environments, environment); - std::mem::swap(&mut context.vm.stack, stack); - - Ok(JsValue::undefined()) - }, - ( - self.realm.environments.clone(), - self.vm.stack.clone(), - self.vm.frame().clone(), - ), - ) - .name("") - .length(1) - .build(); - - // 7. Perform PerformPromiseThen(promise, onFulfilled, onRejected). - promise - .as_object() - .expect("promise was not an object") - .borrow_mut() - .as_promise_mut() - .expect("promise was not a promise") - .perform_promise_then(&on_fulfilled.into(), &on_rejected.into(), None, self); - - self.vm.push(JsValue::undefined()); - return Ok(ShouldExit::Await); - } - Opcode::PushNewTarget => { - if let Some(env) = self - .realm - .environments - .get_this_environment() - .as_function_slots() - { - if let Some(new_target) = env.borrow().new_target() { - self.vm.push(new_target.clone()); - } else { - self.vm.push(JsValue::undefined()); - } - } else { - self.vm.push(JsValue::undefined()); - } - } - } + let result = match opcode { + Opcode::Nop => Nop::execute(self)?, + Opcode::Pop => Pop::execute(self)?, + Opcode::PopIfThrown => PopIfThrown::execute(self)?, + Opcode::Dup => Dup::execute(self)?, + Opcode::Swap => Swap::execute(self)?, + Opcode::PushUndefined => PushUndefined::execute(self)?, + Opcode::PushNull => PushNull::execute(self)?, + Opcode::PushTrue => PushTrue::execute(self)?, + Opcode::PushFalse => PushFalse::execute(self)?, + Opcode::PushZero => PushZero::execute(self)?, + Opcode::PushOne => PushOne::execute(self)?, + Opcode::PushInt8 => PushInt8::execute(self)?, + Opcode::PushInt16 => PushInt16::execute(self)?, + Opcode::PushInt32 => PushInt32::execute(self)?, + Opcode::PushRational => PushRational::execute(self)?, + Opcode::PushNaN => PushNaN::execute(self)?, + Opcode::PushPositiveInfinity => PushPositiveInfinity::execute(self)?, + Opcode::PushNegativeInfinity => PushNegativeInfinity::execute(self)?, + Opcode::PushLiteral => PushLiteral::execute(self)?, + Opcode::PushEmptyObject => PushEmptyObject::execute(self)?, + Opcode::PushClassPrototype => PushClassPrototype::execute(self)?, + Opcode::SetClassPrototype => SetClassPrototype::execute(self)?, + Opcode::SetHomeObject => SetHomeObject::execute(self)?, + Opcode::PushNewArray => PushNewArray::execute(self)?, + Opcode::PushValueToArray => PushValueToArray::execute(self)?, + Opcode::PushElisionToArray => PushElisionToArray::execute(self)?, + Opcode::PushIteratorToArray => PushIteratorToArray::execute(self)?, + Opcode::Add => Add::execute(self)?, + Opcode::Sub => Sub::execute(self)?, + Opcode::Mul => Mul::execute(self)?, + Opcode::Div => Div::execute(self)?, + Opcode::Pow => Pow::execute(self)?, + Opcode::Mod => Mod::execute(self)?, + Opcode::BitAnd => BitAnd::execute(self)?, + Opcode::BitOr => BitOr::execute(self)?, + Opcode::BitXor => BitXor::execute(self)?, + Opcode::ShiftLeft => ShiftLeft::execute(self)?, + Opcode::ShiftRight => ShiftRight::execute(self)?, + Opcode::UnsignedShiftRight => UnsignedShiftRight::execute(self)?, + Opcode::Eq => Eq::execute(self)?, + Opcode::NotEq => NotEq::execute(self)?, + Opcode::StrictEq => StrictEq::execute(self)?, + Opcode::StrictNotEq => StrictNotEq::execute(self)?, + Opcode::GreaterThan => GreaterThan::execute(self)?, + Opcode::GreaterThanOrEq => GreaterThanOrEq::execute(self)?, + Opcode::LessThan => LessThan::execute(self)?, + Opcode::LessThanOrEq => LessThanOrEq::execute(self)?, + Opcode::In => In::execute(self)?, + Opcode::InstanceOf => InstanceOf::execute(self)?, + Opcode::Void => Void::execute(self)?, + Opcode::TypeOf => TypeOf::execute(self)?, + Opcode::Pos => Pos::execute(self)?, + Opcode::Neg => Neg::execute(self)?, + Opcode::Inc => Inc::execute(self)?, + Opcode::IncPost => IncPost::execute(self)?, + Opcode::Dec => Dec::execute(self)?, + Opcode::DecPost => DecPost::execute(self)?, + Opcode::LogicalNot => LogicalNot::execute(self)?, + Opcode::BitNot => BitNot::execute(self)?, + Opcode::DefVar => DefVar::execute(self)?, + Opcode::DefInitVar => DefInitVar::execute(self)?, + Opcode::DefLet => DefLet::execute(self)?, + Opcode::DefInitLet => DefInitLet::execute(self)?, + Opcode::DefInitConst => DefInitConst::execute(self)?, + Opcode::DefInitArg => DefInitArg::execute(self)?, + Opcode::GetName => GetName::execute(self)?, + Opcode::GetNameOrUndefined => GetNameOrUndefined::execute(self)?, + Opcode::SetName => SetName::execute(self)?, + Opcode::Jump => Jump::execute(self)?, + Opcode::JumpIfFalse => JumpIfFalse::execute(self)?, + Opcode::JumpIfNotUndefined => JumpIfNotUndefined::execute(self)?, + Opcode::LogicalAnd => LogicalAnd::execute(self)?, + Opcode::LogicalOr => LogicalOr::execute(self)?, + Opcode::Coalesce => Coalesce::execute(self)?, + Opcode::ToBoolean => ToBoolean::execute(self)?, + Opcode::GetPropertyByName => GetPropertyByName::execute(self)?, + Opcode::GetPropertyByValue => GetPropertyByValue::execute(self)?, + Opcode::GetPropertyByValuePush => GetPropertyByValuePush::execute(self)?, + Opcode::SetPropertyByName => SetPropertyByName::execute(self)?, + Opcode::DefineOwnPropertyByName => DefineOwnPropertyByName::execute(self)?, + Opcode::DefineClassMethodByName => DefineClassMethodByName::execute(self)?, + Opcode::SetPropertyByValue => SetPropertyByValue::execute(self)?, + Opcode::DefineOwnPropertyByValue => DefineOwnPropertyByValue::execute(self)?, + Opcode::DefineClassMethodByValue => DefineClassMethodByValue::execute(self)?, + Opcode::SetPropertyGetterByName => SetPropertyGetterByName::execute(self)?, + Opcode::DefineClassGetterByName => DefineClassGetterByName::execute(self)?, + Opcode::SetPropertyGetterByValue => SetPropertyGetterByValue::execute(self)?, + Opcode::DefineClassGetterByValue => DefineClassGetterByValue::execute(self)?, + Opcode::SetPropertySetterByName => SetPropertySetterByName::execute(self)?, + Opcode::DefineClassSetterByName => DefineClassSetterByName::execute(self)?, + Opcode::SetPropertySetterByValue => SetPropertySetterByValue::execute(self)?, + Opcode::DefineClassSetterByValue => DefineClassSetterByValue::execute(self)?, + Opcode::AssignPrivateField => AssignPrivateField::execute(self)?, + Opcode::SetPrivateField => SetPrivateField::execute(self)?, + Opcode::SetPrivateMethod => SetPrivateMethod::execute(self)?, + Opcode::SetPrivateSetter => SetPrivateSetter::execute(self)?, + Opcode::SetPrivateGetter => SetPrivateGetter::execute(self)?, + Opcode::GetPrivateField => GetPrivateField::execute(self)?, + Opcode::PushClassField => PushClassField::execute(self)?, + Opcode::PushClassFieldPrivate => PushClassFieldPrivate::execute(self)?, + Opcode::PushClassPrivateGetter => PushClassPrivateGetter::execute(self)?, + Opcode::PushClassPrivateSetter => PushClassPrivateSetter::execute(self)?, + Opcode::PushClassPrivateMethod => PushClassPrivateMethod::execute(self)?, + Opcode::DeletePropertyByName => DeletePropertyByName::execute(self)?, + Opcode::DeletePropertyByValue => DeletePropertyByValue::execute(self)?, + Opcode::CopyDataProperties => CopyDataProperties::execute(self)?, + Opcode::ToPropertyKey => ToPropertyKey::execute(self)?, + Opcode::Throw => Throw::execute(self)?, + Opcode::TryStart => TryStart::execute(self)?, + Opcode::TryEnd => TryEnd::execute(self)?, + Opcode::CatchEnd => CatchEnd::execute(self)?, + Opcode::CatchStart => CatchStart::execute(self)?, + Opcode::CatchEnd2 => CatchEnd2::execute(self)?, + Opcode::FinallyStart => FinallyStart::execute(self)?, + Opcode::FinallyEnd => FinallyEnd::execute(self)?, + Opcode::FinallySetJump => FinallySetJump::execute(self)?, + Opcode::This => This::execute(self)?, + Opcode::Super => Super::execute(self)?, + Opcode::SuperCall => SuperCall::execute(self)?, + Opcode::SuperCallSpread => SuperCallSpread::execute(self)?, + Opcode::SuperCallDerived => SuperCallDerived::execute(self)?, + Opcode::Case => Case::execute(self)?, + Opcode::Default => Default::execute(self)?, + Opcode::GetFunction => GetFunction::execute(self)?, + Opcode::GetFunctionAsync => GetFunctionAsync::execute(self)?, + Opcode::GetGenerator => GetGenerator::execute(self)?, + Opcode::GetGeneratorAsync => GetGeneratorAsync::execute(self)?, + Opcode::CallEval => CallEval::execute(self)?, + Opcode::CallEvalSpread => CallEvalSpread::execute(self)?, + Opcode::Call => Call::execute(self)?, + Opcode::CallSpread => CallSpread::execute(self)?, + Opcode::New => New::execute(self)?, + Opcode::NewSpread => NewSpread::execute(self)?, + Opcode::Return => Return::execute(self)?, + Opcode::PushDeclarativeEnvironment => PushDeclarativeEnvironment::execute(self)?, + Opcode::PushFunctionEnvironment => PushFunctionEnvironment::execute(self)?, + Opcode::PopEnvironment => PopEnvironment::execute(self)?, + Opcode::LoopStart => LoopStart::execute(self)?, + Opcode::LoopContinue => LoopContinue::execute(self)?, + Opcode::LoopEnd => LoopEnd::execute(self)?, + Opcode::ForInLoopInitIterator => ForInLoopInitIterator::execute(self)?, + Opcode::InitIterator => InitIterator::execute(self)?, + Opcode::InitIteratorAsync => InitIteratorAsync::execute(self)?, + Opcode::IteratorNext => IteratorNext::execute(self)?, + Opcode::IteratorClose => IteratorClose::execute(self)?, + Opcode::IteratorToArray => IteratorToArray::execute(self)?, + Opcode::ForInLoopNext => ForInLoopNext::execute(self)?, + Opcode::ForAwaitOfLoopIterate => ForAwaitOfLoopIterate::execute(self)?, + Opcode::ForAwaitOfLoopNext => ForAwaitOfLoopNext::execute(self)?, + Opcode::ConcatToString => ConcatToString::execute(self)?, + Opcode::RequireObjectCoercible => RequireObjectCoercible::execute(self)?, + Opcode::ValueNotNullOrUndefined => ValueNotNullOrUndefined::execute(self)?, + Opcode::RestParameterInit => RestParameterInit::execute(self)?, + Opcode::RestParameterPop => RestParameterPop::execute(self)?, + Opcode::PopOnReturnAdd => PopOnReturnAdd::execute(self)?, + Opcode::PopOnReturnSub => PopOnReturnSub::execute(self)?, + Opcode::Yield => Yield::execute(self)?, + Opcode::GeneratorNext => GeneratorNext::execute(self)?, + Opcode::AsyncGeneratorNext => AsyncGeneratorNext::execute(self)?, + Opcode::GeneratorNextDelegate => GeneratorNextDelegate::execute(self)?, + Opcode::Await => Await::execute(self)?, + Opcode::PushNewTarget => PushNewTarget::execute(self)?, + }; - Ok(ShouldExit::False) + Ok(result) } pub(crate) fn run(&mut self) -> JsResult<(JsValue, ReturnType)> { diff --git a/boa_engine/src/vm/opcode/await_stm/mod.rs b/boa_engine/src/vm/opcode/await_stm/mod.rs new file mode 100644 index 0000000000..fa5b2fd215 --- /dev/null +++ b/boa_engine/src/vm/opcode/await_stm/mod.rs @@ -0,0 +1,119 @@ +use crate::{ + builtins::{JsArgs, Promise}, + object::FunctionBuilder, + vm::{call_frame::GeneratorResumeKind, opcode::Operation, ShouldExit}, + Context, JsResult, JsValue, +}; + +/// `Await` implements the Opcode Operation for `Opcode::Await` +/// +/// Operation: +/// - Stops the current Async function and schedules it to resume later. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct Await; + +impl Operation for Await { + const NAME: &'static str = "Await"; + const INSTRUCTION: &'static str = "INST - Await"; + + fn execute(context: &mut Context) -> JsResult { + let value = context.vm.pop(); + + // 2. Let promise be ? PromiseResolve(%Promise%, value). + let promise = Promise::promise_resolve( + context.intrinsics().constructors().promise().constructor(), + value, + context, + )?; + + // 3. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures asyncContext and performs the following steps when called: + // 4. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »). + let on_fulfilled = FunctionBuilder::closure_with_captures( + context, + |_this, args, (environment, stack, frame), context| { + // a. Let prevContext be the running execution context. + // b. Suspend prevContext. + // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context. + // d. Resume the suspended evaluation of asyncContext using NormalCompletion(value) as the result of the operation that suspended it. + // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context. + // f. Return undefined. + + std::mem::swap(&mut context.realm.environments, environment); + std::mem::swap(&mut context.vm.stack, stack); + context.vm.push_frame(frame.clone()); + + context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Normal; + context.vm.push(args.get_or_undefined(0)); + context.run()?; + + *frame = context + .vm + .pop_frame() + .expect("generator call frame must exist"); + std::mem::swap(&mut context.realm.environments, environment); + std::mem::swap(&mut context.vm.stack, stack); + + Ok(JsValue::undefined()) + }, + ( + context.realm.environments.clone(), + context.vm.stack.clone(), + context.vm.frame().clone(), + ), + ) + .name("") + .length(1) + .build(); + + // 5. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures asyncContext and performs the following steps when called: + // 6. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »). + let on_rejected = FunctionBuilder::closure_with_captures( + context, + |_this, args, (environment, stack, frame), context| { + // a. Let prevContext be the running execution context. + // b. Suspend prevContext. + // c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context. + // d. Resume the suspended evaluation of asyncContext using ThrowCompletion(reason) as the result of the operation that suspended it. + // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context. + // f. Return undefined. + + std::mem::swap(&mut context.realm.environments, environment); + std::mem::swap(&mut context.vm.stack, stack); + context.vm.push_frame(frame.clone()); + + context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Throw; + context.vm.push(args.get_or_undefined(0)); + context.run()?; + + *frame = context + .vm + .pop_frame() + .expect("generator call frame must exist"); + std::mem::swap(&mut context.realm.environments, environment); + std::mem::swap(&mut context.vm.stack, stack); + + Ok(JsValue::undefined()) + }, + ( + context.realm.environments.clone(), + context.vm.stack.clone(), + context.vm.frame().clone(), + ), + ) + .name("") + .length(1) + .build(); + + // 7. Perform PerformPromiseThen(promise, onFulfilled, onRejected). + promise + .as_object() + .expect("promise was not an object") + .borrow_mut() + .as_promise_mut() + .expect("promise was not a promise") + .perform_promise_then(&on_fulfilled.into(), &on_rejected.into(), None, context); + + context.vm.push(JsValue::undefined()); + Ok(ShouldExit::Await) + } +} diff --git a/boa_engine/src/vm/opcode/binary_ops/logical.rs b/boa_engine/src/vm/opcode/binary_ops/logical.rs new file mode 100644 index 0000000000..e498821dfe --- /dev/null +++ b/boa_engine/src/vm/opcode/binary_ops/logical.rs @@ -0,0 +1,70 @@ +use crate::{ + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, +}; + +/// `LogicalAnd` implements the Opcode Operation for `Opcode::LogicalAnd` +/// +/// Operation: +/// - Binary logical `&&` operation +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct LogicalAnd; + +impl Operation for LogicalAnd { + const NAME: &'static str = "LogicalAnd"; + const INSTRUCTION: &'static str = "INST - LogicalAnd"; + + fn execute(context: &mut Context) -> JsResult { + let exit = context.vm.read::(); + let lhs = context.vm.pop(); + if !lhs.to_boolean() { + context.vm.frame_mut().pc = exit as usize; + context.vm.push(lhs); + } + Ok(ShouldExit::False) + } +} + +/// `LogicalOr` implements the Opcode Operation for `Opcode::LogicalOr` +/// +/// Operation: +/// - Binary logical `||` operation +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct LogicalOr; + +impl Operation for LogicalOr { + const NAME: &'static str = "LogicalOr"; + const INSTRUCTION: &'static str = "INST - LogicalOr"; + + fn execute(context: &mut Context) -> JsResult { + let exit = context.vm.read::(); + let lhs = context.vm.pop(); + if lhs.to_boolean() { + context.vm.frame_mut().pc = exit as usize; + context.vm.push(lhs); + } + Ok(ShouldExit::False) + } +} + +/// `Coalesce` implements the Opcode Operation for `Opcode::Coalesce` +/// +/// Operation: +/// - Binary logical `||` operation +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct Coalesce; + +impl Operation for Coalesce { + const NAME: &'static str = "Coalesce"; + const INSTRUCTION: &'static str = "INST - Coalesce"; + + fn execute(context: &mut Context) -> JsResult { + let exit = context.vm.read::(); + let lhs = context.vm.pop(); + if !lhs.is_null_or_undefined() { + context.vm.frame_mut().pc = exit as usize; + context.vm.push(lhs); + } + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/binary_ops/macro_defined.rs b/boa_engine/src/vm/opcode/binary_ops/macro_defined.rs new file mode 100644 index 0000000000..20fd7baf19 --- /dev/null +++ b/boa_engine/src/vm/opcode/binary_ops/macro_defined.rs @@ -0,0 +1,46 @@ +use crate::{ + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, +}; + +macro_rules! implement_bin_ops { + ($name:ident, $op:ident, $doc_string:literal) => { + #[doc= concat!("`", stringify!($name), "` implements the OpCode Operation for `Opcode::", stringify!($name), "`\n")] + #[doc= "\n"] + #[doc="Operation:\n"] + #[doc= concat!(" - ", $doc_string)] + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub(crate) struct $name; + + impl Operation for $name { + const NAME: &'static str = stringify!($name); + const INSTRUCTION: &'static str = stringify!("INST - " + $name); + + fn execute(context: &mut Context) -> JsResult { + let rhs = context.vm.pop(); + let lhs = context.vm.pop(); + let value = lhs.$op(&rhs, context)?; + context.vm.push(value); + Ok(ShouldExit::False) + } + } + }; +} + +implement_bin_ops!(Add, add, "Binary `+` operator."); +implement_bin_ops!(Sub, sub, "Binary `-` operator."); +implement_bin_ops!(Mul, mul, "Binary `*` operator."); +implement_bin_ops!(Div, div, "Binary `/` operator."); +implement_bin_ops!(Pow, pow, "Binary `**` operator."); +implement_bin_ops!(Mod, rem, "Binary `%` operator."); +implement_bin_ops!(BitAnd, bitand, "Binary `&` operator."); +implement_bin_ops!(BitOr, bitor, "Binary `|` operator."); +implement_bin_ops!(BitXor, bitxor, "Binary `^` operator."); +implement_bin_ops!(ShiftLeft, shl, "Binary `<<` operator."); +implement_bin_ops!(ShiftRight, shr, "Binary `>>` operator."); +implement_bin_ops!(UnsignedShiftRight, ushr, "Binary `>>>` operator."); +implement_bin_ops!(Eq, equals, "Binary `==` operator."); +implement_bin_ops!(GreaterThan, gt, "Binary `>` operator."); +implement_bin_ops!(GreaterThanOrEq, ge, "Binary `>=` operator."); +implement_bin_ops!(LessThan, lt, "Binary `<` operator."); +implement_bin_ops!(LessThanOrEq, le, "Binary `<=` operator."); diff --git a/boa_engine/src/vm/opcode/binary_ops/mod.rs b/boa_engine/src/vm/opcode/binary_ops/mod.rs new file mode 100644 index 0000000000..fa8d9644d9 --- /dev/null +++ b/boa_engine/src/vm/opcode/binary_ops/mod.rs @@ -0,0 +1,120 @@ +use crate::{ + error::JsNativeError, + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, +}; + +pub(crate) mod logical; +pub(crate) mod macro_defined; + +pub(crate) use logical::*; +pub(crate) use macro_defined::*; + +/// `NotEq` implements the Opcode Operation for `Opcode::NotEq` +/// +/// Operation: +/// - Binary `!=` operation +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct NotEq; + +impl Operation for NotEq { + const NAME: &'static str = "NotEq"; + const INSTRUCTION: &'static str = "INST - NotEq"; + + fn execute(context: &mut Context) -> JsResult { + let rhs = context.vm.pop(); + let lhs = context.vm.pop(); + let value = !lhs.equals(&rhs, context)?; + context.vm.push(value); + Ok(ShouldExit::False) + } +} + +/// `StrictEq` implements the Opcode Operation for `Opcode::StrictEq` +/// +/// Operation: +/// - Binary `===` operation +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct StrictEq; + +impl Operation for StrictEq { + const NAME: &'static str = "StrictEq"; + const INSTRUCTION: &'static str = "INST - StrictEq"; + + fn execute(context: &mut Context) -> JsResult { + let rhs = context.vm.pop(); + let lhs = context.vm.pop(); + context.vm.push(lhs.strict_equals(&rhs)); + Ok(ShouldExit::False) + } +} + +/// `StrictNotEq` implements the Opcode Operation for `Opcode::StrictNotEq` +/// +/// Operation: +/// - Binary `!==` operation +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct StrictNotEq; + +impl Operation for StrictNotEq { + const NAME: &'static str = "StrictNotEq"; + const INSTRUCTION: &'static str = "INST - StrictNotEq"; + + fn execute(context: &mut Context) -> JsResult { + let rhs = context.vm.pop(); + let lhs = context.vm.pop(); + context.vm.push(!lhs.strict_equals(&rhs)); + Ok(ShouldExit::False) + } +} + +/// `In` implements the Opcode Operation for `Opcode::In` +/// +/// Operation: +/// - Binary `in` operation +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct In; + +impl Operation for In { + const NAME: &'static str = "In"; + const INSTRUCTION: &'static str = "INST - In"; + + fn execute(context: &mut Context) -> JsResult { + let rhs = context.vm.pop(); + let lhs = context.vm.pop(); + + if !rhs.is_object() { + return Err(JsNativeError::typ() + .with_message(format!( + "right-hand side of 'in' should be an object, got {}", + rhs.type_of().to_std_string_escaped() + )) + .into()); + } + let key = lhs.to_property_key(context)?; + let value = context.has_property(&rhs, &key)?; + context.vm.push(value); + Ok(ShouldExit::False) + } +} + +/// `InstanceOf` implements the Opcode Operation for `Opcode::InstanceOf` +/// +/// Operation: +/// - Binary `instanceof` operation +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct InstanceOf; + +impl Operation for InstanceOf { + const NAME: &'static str = "InstanceOf"; + const INSTRUCTION: &'static str = "INST - InstanceOf"; + + fn execute(context: &mut Context) -> JsResult { + let target = context.vm.pop(); + let v = context.vm.pop(); + let value = v.instance_of(&target, context)?; + + context.vm.push(value); + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/call/mod.rs b/boa_engine/src/vm/opcode/call/mod.rs new file mode 100644 index 0000000000..3f808ba666 --- /dev/null +++ b/boa_engine/src/vm/opcode/call/mod.rs @@ -0,0 +1,212 @@ +use crate::{ + builtins::function::Function, + error::JsNativeError, + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, JsValue, +}; + +/// `CallEval` implements the Opcode Operation for `Opcode::CallEval` +/// +/// Operation: +/// - Call a function named "eval". +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct CallEval; + +impl Operation for CallEval { + const NAME: &'static str = "CallEval"; + const INSTRUCTION: &'static str = "INST - CallEval"; + + fn execute(context: &mut Context) -> JsResult { + if context.vm.stack_size_limit <= context.vm.stack.len() { + return Err(JsNativeError::range() + .with_message("Maximum call stack size exceeded") + .into()); + } + let argument_count = context.vm.read::(); + let mut arguments = Vec::with_capacity(argument_count as usize); + for _ in 0..argument_count { + arguments.push(context.vm.pop()); + } + arguments.reverse(); + + let func = context.vm.pop(); + let this = context.vm.pop(); + + let object = match func { + JsValue::Object(ref object) if object.is_callable() => object.clone(), + _ => { + return Err(JsNativeError::typ() + .with_message("not a callable function") + .into()) + } + }; + + // A native function with the name "eval" implies, that is this the built-in eval function. + let eval = matches!(object.borrow().as_function(), Some(Function::Native { .. })); + + let strict = context.vm.frame().code.strict; + + if eval { + if let Some(x) = arguments.get(0) { + let result = crate::builtins::eval::Eval::perform_eval(x, true, strict, context)?; + context.vm.push(result); + } else { + context.vm.push(JsValue::Undefined); + } + } else { + let result = object.__call__(&this, &arguments, context)?; + context.vm.push(result); + } + Ok(ShouldExit::False) + } +} + +/// `CallEvalSpread` implements the Opcode Operation for `Opcode::CallEvalSpread` +/// +/// Operation: +/// - Call a function named "eval" where the arguments contain spreads. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct CallEvalSpread; + +impl Operation for CallEvalSpread { + const NAME: &'static str = "CallEvalSpread"; + const INSTRUCTION: &'static str = "INST - CallEvalSpread"; + + fn execute(context: &mut Context) -> JsResult { + if context.vm.stack_size_limit <= context.vm.stack.len() { + return Err(JsNativeError::range() + .with_message("Maximum call stack size exceeded") + .into()); + } + + // Get the arguments that are stored as an array object on the stack. + let arguments_array = context.vm.pop(); + let arguments_array_object = arguments_array + .as_object() + .expect("arguments array in call spread function must be an object"); + let arguments = arguments_array_object + .borrow() + .properties() + .dense_indexed_properties() + .expect("arguments array in call spread function must be dense") + .clone(); + + let func = context.vm.pop(); + let this = context.vm.pop(); + + let object = match func { + JsValue::Object(ref object) if object.is_callable() => object.clone(), + _ => { + return Err(JsNativeError::typ() + .with_message("not a callable function") + .into()) + } + }; + + // A native function with the name "eval" implies, that is this the built-in eval function. + let eval = matches!(object.borrow().as_function(), Some(Function::Native { .. })); + + let strict = context.vm.frame().code.strict; + + if eval { + if let Some(x) = arguments.get(0) { + let result = crate::builtins::eval::Eval::perform_eval(x, true, strict, context)?; + context.vm.push(result); + } else { + context.vm.push(JsValue::Undefined); + } + } else { + let result = object.__call__(&this, &arguments, context)?; + context.vm.push(result); + } + Ok(ShouldExit::False) + } +} + +/// `Call` implements the Opcode Operation for `Opcode::Call` +/// +/// Operation: +/// - Call a function +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct Call; + +impl Operation for Call { + const NAME: &'static str = "Call"; + const INSTRUCTION: &'static str = "INST - Call"; + + fn execute(context: &mut Context) -> JsResult { + if context.vm.stack_size_limit <= context.vm.stack.len() { + return Err(JsNativeError::range() + .with_message("Maximum call stack size exceeded") + .into()); + } + let argument_count = context.vm.read::(); + let mut arguments = Vec::with_capacity(argument_count as usize); + for _ in 0..argument_count { + arguments.push(context.vm.pop()); + } + arguments.reverse(); + + let func = context.vm.pop(); + let this = context.vm.pop(); + + let object = match func { + JsValue::Object(ref object) if object.is_callable() => object.clone(), + _ => { + return Err(JsNativeError::typ() + .with_message("not a callable function") + .into()) + } + }; + + let result = object.__call__(&this, &arguments, context)?; + + context.vm.push(result); + Ok(ShouldExit::False) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct CallSpread; + +impl Operation for CallSpread { + const NAME: &'static str = "CallSpread"; + const INSTRUCTION: &'static str = "INST - CallSpread"; + + fn execute(context: &mut Context) -> JsResult { + if context.vm.stack_size_limit <= context.vm.stack.len() { + return Err(JsNativeError::range() + .with_message("Maximum call stack size exceeded") + .into()); + } + + // Get the arguments that are stored as an array object on the stack. + let arguments_array = context.vm.pop(); + let arguments_array_object = arguments_array + .as_object() + .expect("arguments array in call spread function must be an object"); + let arguments = arguments_array_object + .borrow() + .properties() + .dense_indexed_properties() + .expect("arguments array in call spread function must be dense") + .clone(); + + let func = context.vm.pop(); + let this = context.vm.pop(); + + let object = match func { + JsValue::Object(ref object) if object.is_callable() => object.clone(), + _ => { + return Err(JsNativeError::typ() + .with_message("not a callable function") + .into()) + } + }; + + let result = object.__call__(&this, &arguments, context)?; + + context.vm.push(result); + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/concat/mod.rs b/boa_engine/src/vm/opcode/concat/mod.rs new file mode 100644 index 0000000000..b2d3f1265c --- /dev/null +++ b/boa_engine/src/vm/opcode/concat/mod.rs @@ -0,0 +1,33 @@ +use crate::{ + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, JsString, +}; + +/// `ConcatToString` implements the Opcode Operation for `Opcode::ConcatToString` +/// +/// Operation: +/// - Concat multiple stack objects into a string. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct ConcatToString; + +impl Operation for ConcatToString { + const NAME: &'static str = "ConcatToString"; + const INSTRUCTION: &'static str = "INST - ConcatToString"; + + fn execute(context: &mut Context) -> JsResult { + let value_count = context.vm.read::(); + let mut strings = Vec::with_capacity(value_count as usize); + for _ in 0..value_count { + strings.push(context.vm.pop().to_string(context)?); + } + strings.reverse(); + let s = JsString::concat_array( + &strings + .iter() + .map(JsString::as_slice) + .collect::>(), + ); + context.vm.push(s); + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/copy/mod.rs b/boa_engine/src/vm/opcode/copy/mod.rs new file mode 100644 index 0000000000..9c27a207de --- /dev/null +++ b/boa_engine/src/vm/opcode/copy/mod.rs @@ -0,0 +1,42 @@ +use crate::{ + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, +}; + +/// `CopyDataProperties` implements the Opcode Operation for `Opcode::CopyDataProperties` +/// +/// Operation: +/// - Copy all properties of one object to another object. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct CopyDataProperties; + +impl Operation for CopyDataProperties { + const NAME: &'static str = "CopyDataProperties"; + const INSTRUCTION: &'static str = "INST - CopyDataProperties"; + + fn execute(context: &mut Context) -> JsResult { + let excluded_key_count = context.vm.read::(); + let excluded_key_count_computed = context.vm.read::(); + let mut excluded_keys = Vec::with_capacity(excluded_key_count as usize); + for _ in 0..excluded_key_count { + let key = context.vm.pop(); + excluded_keys.push( + key.to_property_key(context) + .expect("key must be property key"), + ); + } + let value = context.vm.pop(); + let object = value.as_object().expect("not an object"); + let source = context.vm.pop(); + for _ in 0..excluded_key_count_computed { + let key = context.vm.pop(); + excluded_keys.push( + key.to_property_key(context) + .expect("key must be property key"), + ); + } + object.copy_data_properties(&source, excluded_keys, context)?; + context.vm.push(value); + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/define/class/getter.rs b/boa_engine/src/vm/opcode/define/class/getter.rs new file mode 100644 index 0000000000..52f75b87ed --- /dev/null +++ b/boa_engine/src/vm/opcode/define/class/getter.rs @@ -0,0 +1,96 @@ +use crate::{ + property::PropertyDescriptor, + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, JsString, +}; + +/// `DefineClassGetterByName` implements the Opcode Operation for `Opcode::DefineClassGetterByName` +/// +/// Operation: +/// - Defines a class getter by name. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct DefineClassGetterByName; + +impl Operation for DefineClassGetterByName { + const NAME: &'static str = "DefineClassGetterByName"; + const INSTRUCTION: &'static str = "INST - DefineClassGetterByName"; + + fn execute(context: &mut Context) -> JsResult { + let index = context.vm.read::(); + let object = context.vm.pop(); + let value = context.vm.pop(); + let object = object.to_object(context)?; + value + .as_object() + .expect("method must be function object") + .borrow_mut() + .as_function_mut() + .expect("method must be function object") + .set_home_object(object.clone()); + let name = context.vm.frame().code.names[index as usize]; + let name = context + .interner() + .resolve_expect(name.sym()) + .into_common::(false) + .into(); + let set = object + .__get_own_property__(&name, context)? + .as_ref() + .and_then(PropertyDescriptor::set) + .cloned(); + object.__define_own_property__( + name, + PropertyDescriptor::builder() + .maybe_get(Some(value)) + .maybe_set(set) + .enumerable(false) + .configurable(true) + .build(), + context, + )?; + Ok(ShouldExit::False) + } +} + +/// `DefineClassGetterByValue` implements the Opcode Operation for `Opcode::DefineClassGetterByValue` +/// +/// Operation: +/// - Defines a class getter by value. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct DefineClassGetterByValue; + +impl Operation for DefineClassGetterByValue { + const NAME: &'static str = "DefineClassGetterByValue"; + const INSTRUCTION: &'static str = "INST - DefineClassGetterByValue"; + + fn execute(context: &mut Context) -> JsResult { + let value = context.vm.pop(); + let key = context.vm.pop(); + let object = context.vm.pop(); + let object = object.to_object(context)?; + value + .as_object() + .expect("method must be function object") + .borrow_mut() + .as_function_mut() + .expect("method must be function object") + .set_home_object(object.clone()); + let name = key.to_property_key(context)?; + let set = object + .__get_own_property__(&name, context)? + .as_ref() + .and_then(PropertyDescriptor::set) + .cloned(); + object.__define_own_property__( + name, + PropertyDescriptor::builder() + .maybe_get(Some(value)) + .maybe_set(set) + .enumerable(false) + .configurable(true) + .build(), + context, + )?; + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/define/class/method.rs b/boa_engine/src/vm/opcode/define/class/method.rs new file mode 100644 index 0000000000..e9d2886240 --- /dev/null +++ b/boa_engine/src/vm/opcode/define/class/method.rs @@ -0,0 +1,90 @@ +use crate::{ + property::PropertyDescriptor, + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, JsString, +}; + +/// `DefineClassMethodByName` implements the Opcode Operation for `Opcode::DefineClassMethodByName` +/// +/// Operation: +/// - Defines a class method by name. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct DefineClassMethodByName; + +impl Operation for DefineClassMethodByName { + const NAME: &'static str = "DefineClassMethodByName"; + const INSTRUCTION: &'static str = "INST - DefineClassMethodByName"; + + fn execute(context: &mut Context) -> JsResult { + let index = context.vm.read::(); + let object = context.vm.pop(); + let value = context.vm.pop(); + let object = if let Some(object) = object.as_object() { + object.clone() + } else { + object.to_object(context)? + }; + value + .as_object() + .expect("method must be function object") + .borrow_mut() + .as_function_mut() + .expect("method must be function object") + .set_home_object(object.clone()); + let name = context.vm.frame().code.names[index as usize]; + let name = context.interner().resolve_expect(name.sym()); + object.__define_own_property__( + name.into_common::(false).into(), + PropertyDescriptor::builder() + .value(value) + .writable(true) + .enumerable(false) + .configurable(true) + .build(), + context, + )?; + Ok(ShouldExit::False) + } +} + +/// `DefineClassMethodByValue` implements the Opcode Operation for `Opcode::DefineClassMethodByValue` +/// +/// Operation: +/// - Defines a class method by value. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct DefineClassMethodByValue; + +impl Operation for DefineClassMethodByValue { + const NAME: &'static str = "DefineClassMethodByName"; + const INSTRUCTION: &'static str = "INST - DefineClassMethodByName"; + + fn execute(context: &mut Context) -> JsResult { + let value = context.vm.pop(); + let key = context.vm.pop(); + let object = context.vm.pop(); + let object = if let Some(object) = object.as_object() { + object.clone() + } else { + object.to_object(context)? + }; + value + .as_object() + .expect("method must be function object") + .borrow_mut() + .as_function_mut() + .expect("method must be function object") + .set_home_object(object.clone()); + let key = key.to_property_key(context)?; + object.__define_own_property__( + key, + PropertyDescriptor::builder() + .value(value) + .writable(true) + .enumerable(false) + .configurable(true) + .build(), + context, + )?; + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/define/class/mod.rs b/boa_engine/src/vm/opcode/define/class/mod.rs new file mode 100644 index 0000000000..7d461feee0 --- /dev/null +++ b/boa_engine/src/vm/opcode/define/class/mod.rs @@ -0,0 +1,7 @@ +pub(crate) mod getter; +pub(crate) mod method; +pub(crate) mod setter; + +pub(crate) use getter::*; +pub(crate) use method::*; +pub(crate) use setter::*; diff --git a/boa_engine/src/vm/opcode/define/class/setter.rs b/boa_engine/src/vm/opcode/define/class/setter.rs new file mode 100644 index 0000000000..bd76ba87c0 --- /dev/null +++ b/boa_engine/src/vm/opcode/define/class/setter.rs @@ -0,0 +1,96 @@ +use crate::{ + property::PropertyDescriptor, + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, JsString, +}; + +/// `DefineClassSetterByName` implements the Opcode Operation for `Opcode::DefineClassSetterByName` +/// +/// Operation: +/// - Defines a class setter by name. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct DefineClassSetterByName; + +impl Operation for DefineClassSetterByName { + const NAME: &'static str = "DefineClassSetterByName"; + const INSTRUCTION: &'static str = "INST - DefineClassSetterByName"; + + fn execute(context: &mut Context) -> JsResult { + let index = context.vm.read::(); + let object = context.vm.pop(); + let value = context.vm.pop(); + let object = object.to_object(context)?; + value + .as_object() + .expect("method must be function object") + .borrow_mut() + .as_function_mut() + .expect("method must be function object") + .set_home_object(object.clone()); + let name = context.vm.frame().code.names[index as usize]; + let name = context + .interner() + .resolve_expect(name.sym()) + .into_common::(false) + .into(); + let get = object + .__get_own_property__(&name, context)? + .as_ref() + .and_then(PropertyDescriptor::get) + .cloned(); + object.__define_own_property__( + name, + PropertyDescriptor::builder() + .maybe_set(Some(value)) + .maybe_get(get) + .enumerable(false) + .configurable(true) + .build(), + context, + )?; + Ok(ShouldExit::False) + } +} + +/// `DefineClassSetterByValue` implements the Opcode Operation for `Opcode::DefineClassSetterByValue` +/// +/// Operation: +/// - Defines a class setter by value. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct DefineClassSetterByValue; + +impl Operation for DefineClassSetterByValue { + const NAME: &'static str = "DefineClassSetterByValue"; + const INSTRUCTION: &'static str = "INST - DefineClassSetterByValue"; + + fn execute(context: &mut Context) -> JsResult { + let value = context.vm.pop(); + let key = context.vm.pop(); + let object = context.vm.pop(); + let object = object.to_object(context)?; + value + .as_object() + .expect("method must be function object") + .borrow_mut() + .as_function_mut() + .expect("method must be function object") + .set_home_object(object.clone()); + let name = key.to_property_key(context)?; + let get = object + .__get_own_property__(&name, context)? + .as_ref() + .and_then(PropertyDescriptor::get) + .cloned(); + object.__define_own_property__( + name, + PropertyDescriptor::builder() + .maybe_set(Some(value)) + .maybe_get(get) + .enumerable(false) + .configurable(true) + .build(), + context, + )?; + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/define/mod.rs b/boa_engine/src/vm/opcode/define/mod.rs new file mode 100644 index 0000000000..65fc6182a8 --- /dev/null +++ b/boa_engine/src/vm/opcode/define/mod.rs @@ -0,0 +1,140 @@ +use crate::{ + property::PropertyDescriptor, + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, JsString, JsValue, +}; + +pub(crate) mod class; +pub(crate) mod own_property; + +pub(crate) use class::*; +pub(crate) use own_property::*; + +/// `DefVar` implements the Opcode Operation for `Opcode::DefVar` +/// +/// Operation: +/// - Declare `var` type variable. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct DefVar; + +impl Operation for DefVar { + const NAME: &'static str = "DefVar"; + const INSTRUCTION: &'static str = "INST - DefVar"; + + fn execute(context: &mut Context) -> JsResult { + let index = context.vm.read::(); + let binding_locator = context.vm.frame().code.bindings[index as usize]; + + if binding_locator.is_global() { + let key = context + .interner() + .resolve_expect(binding_locator.name().sym()) + .into_common(false); + context.global_bindings_mut().entry(key).or_insert( + PropertyDescriptor::builder() + .value(JsValue::Undefined) + .writable(true) + .enumerable(true) + .configurable(true) + .build(), + ); + } else { + context.realm.environments.put_value_if_uninitialized( + binding_locator.environment_index(), + binding_locator.binding_index(), + JsValue::Undefined, + ); + } + Ok(ShouldExit::False) + } +} + +/// `DefInitVar` implements the Opcode Operation for `Opcode::DefInitVar` +/// +/// Operation: +/// - Declare and initialize a function argument. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct DefInitVar; + +impl Operation for DefInitVar { + const NAME: &'static str = "DefInitVar"; + const INSTRUCTION: &'static str = "INST - DefInitVar"; + + fn execute(context: &mut Context) -> JsResult { + let index = context.vm.read::(); + let value = context.vm.pop(); + let binding_locator = context.vm.frame().code.bindings[index as usize]; + binding_locator.throw_mutate_immutable(context)?; + + if binding_locator.is_global() { + let key = context + .interner() + .resolve_expect(binding_locator.name().sym()) + .into_common::(false) + .into(); + crate::object::internal_methods::global::global_set_no_receiver(&key, value, context)?; + } else { + context.realm.environments.put_value( + binding_locator.environment_index(), + binding_locator.binding_index(), + value, + ); + } + Ok(ShouldExit::False) + } +} + +/// `DefLet` implements the Opcode Operation for `Opcode::DefLet` +/// +/// Operation: +/// - Declare `let` type variable. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct DefLet; + +impl Operation for DefLet { + const NAME: &'static str = "DefLet"; + const INSTRUCTION: &'static str = "INST - DefLet"; + + fn execute(context: &mut Context) -> JsResult { + let index = context.vm.read::(); + let binding_locator = context.vm.frame().code.bindings[index as usize]; + context.realm.environments.put_value( + binding_locator.environment_index(), + binding_locator.binding_index(), + JsValue::Undefined, + ); + Ok(ShouldExit::False) + } +} + +macro_rules! implement_declaritives { + ($name:ident, $doc_string:literal) => { + #[doc= concat!("`", stringify!($name), "` implements the OpCode Operation for `Opcode::", stringify!($name), "`\n")] + #[doc= "\n"] + #[doc="Operation:\n"] + #[doc= concat!(" - ", $doc_string)] + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub(crate) struct $name; + + impl Operation for $name { + const NAME: &'static str = stringify!($name); + const INSTRUCTION: &'static str = stringify!("INST - " + $name); + + fn execute(context: &mut Context) -> JsResult { + let index = context.vm.read::(); + let value = context.vm.pop(); + let binding_locator = context.vm.frame().code.bindings[index as usize]; + context.realm.environments.put_value( + binding_locator.environment_index(), + binding_locator.binding_index(), + value, + ); + Ok(ShouldExit::False) + } + } + }; +} + +implement_declaritives!(DefInitLet, "Declare and initialize `let` type variable"); +implement_declaritives!(DefInitConst, "Declare and initialize `const` type variable"); +implement_declaritives!(DefInitArg, "Declare and initialize function arguments"); diff --git a/boa_engine/src/vm/opcode/define/own_property.rs b/boa_engine/src/vm/opcode/define/own_property.rs new file mode 100644 index 0000000000..c89c063b75 --- /dev/null +++ b/boa_engine/src/vm/opcode/define/own_property.rs @@ -0,0 +1,79 @@ +use crate::{ + property::PropertyDescriptor, + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, JsString, +}; + +/// `DefineOwnPropertyByName` implements the Opcode Operation for `Opcode::DefineOwnPropertyByName` +/// +/// Operation: +/// - Defines a own property of an object by name. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct DefineOwnPropertyByName; + +impl Operation for DefineOwnPropertyByName { + const NAME: &'static str = "DefineOwnPropertyByName"; + const INSTRUCTION: &'static str = "INST - DefineOwnPropertyByName"; + + fn execute(context: &mut Context) -> JsResult { + let index = context.vm.read::(); + let object = context.vm.pop(); + let value = context.vm.pop(); + let object = if let Some(object) = object.as_object() { + object.clone() + } else { + object.to_object(context)? + }; + let name = context.vm.frame().code.names[index as usize]; + let name = context + .interner() + .resolve_expect(name.sym()) + .into_common::(false); + object.__define_own_property__( + name.into(), + PropertyDescriptor::builder() + .value(value) + .writable(true) + .enumerable(true) + .configurable(true) + .build(), + context, + )?; + Ok(ShouldExit::False) + } +} + +/// `DefineOwnPropertyByValue` implements the Opcode Operation for `Opcode::DefineOwnPropertyByValue` +/// +/// Operation: +/// - Defines a own property of an object by value. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct DefineOwnPropertyByValue; + +impl Operation for DefineOwnPropertyByValue { + const NAME: &'static str = "DefineOwnPropertyByValue"; + const INSTRUCTION: &'static str = "INST - DefineOwnPropertyByValue"; + + fn execute(context: &mut Context) -> JsResult { + let value = context.vm.pop(); + let key = context.vm.pop(); + let object = context.vm.pop(); + let object = if let Some(object) = object.as_object() { + object.clone() + } else { + object.to_object(context)? + }; + let key = key.to_property_key(context)?; + object.__define_own_property__( + key, + PropertyDescriptor::builder() + .value(value) + .writable(true) + .enumerable(true) + .configurable(true) + .build(), + context, + )?; + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/delete/mod.rs b/boa_engine/src/vm/opcode/delete/mod.rs new file mode 100644 index 0000000000..6b0d5f3d79 --- /dev/null +++ b/boa_engine/src/vm/opcode/delete/mod.rs @@ -0,0 +1,63 @@ +use crate::{ + error::JsNativeError, + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, JsString, +}; + +/// `DeletePropertyByName` implements the Opcode Operation for `Opcode::DeletePropertyByName` +/// +/// Operation: +/// - Deletes a property by name of an object +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct DeletePropertyByName; + +impl Operation for DeletePropertyByName { + const NAME: &'static str = "DeletePropertyByName"; + const INSTRUCTION: &'static str = "INST - DeletePropertyByName"; + + fn execute(context: &mut Context) -> JsResult { + let index = context.vm.read::(); + let key = context.vm.frame().code.names[index as usize]; + let key = context + .interner() + .resolve_expect(key.sym()) + .into_common::(false) + .into(); + let object = context.vm.pop(); + let result = object.to_object(context)?.__delete__(&key, context)?; + if !result && context.vm.frame().code.strict { + return Err(JsNativeError::typ() + .with_message("Cannot delete property") + .into()); + } + context.vm.push(result); + Ok(ShouldExit::False) + } +} + +/// `DeletePropertyByValue` implements the Opcode Operation for `Opcode::DeletePropertyByValue` +/// +/// Operation: +/// - Deletes a property by value of an object +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct DeletePropertyByValue; + +impl Operation for DeletePropertyByValue { + const NAME: &'static str = "DeletePropertyByValue"; + const INSTRUCTION: &'static str = "INST - DeletePropertyByValue"; + + fn execute(context: &mut Context) -> JsResult { + let object = context.vm.pop(); + let key = context.vm.pop(); + let result = object + .to_object(context)? + .__delete__(&key.to_property_key(context)?, context)?; + if !result && context.vm.frame().code.strict { + return Err(JsNativeError::typ() + .with_message("Cannot delete property") + .into()); + } + context.vm.push(result); + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/dup/mod.rs b/boa_engine/src/vm/opcode/dup/mod.rs new file mode 100644 index 0000000000..c2f5f641a9 --- /dev/null +++ b/boa_engine/src/vm/opcode/dup/mod.rs @@ -0,0 +1,23 @@ +use crate::{ + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, +}; + +/// `Dup` implements the Opcode Operation for `Opcode::Dup` +/// +/// Operation: +/// - Push a copy of the top value on the stack. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct Dup; + +impl Operation for Dup { + const NAME: &'static str = "Dup"; + const INSTRUCTION: &'static str = "INST - Dup"; + + fn execute(context: &mut Context) -> JsResult { + let value = context.vm.pop(); + context.vm.push(value.clone()); + context.vm.push(value); + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/environment/mod.rs b/boa_engine/src/vm/opcode/environment/mod.rs new file mode 100644 index 0000000000..de7ef7b209 --- /dev/null +++ b/boa_engine/src/vm/opcode/environment/mod.rs @@ -0,0 +1,275 @@ +use crate::{ + environments::EnvironmentSlots, + error::JsNativeError, + vm::{code_block::initialize_instance_elements, opcode::Operation, ShouldExit}, + Context, JsResult, JsValue, +}; + +/// `This` implements the Opcode Operation for `Opcode::This` +/// +/// Operation: +/// - Pushes `this` value. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct This; + +impl Operation for This { + const NAME: &'static str = "This"; + const INSTRUCTION: &'static str = "INST - This"; + + fn execute(context: &mut Context) -> JsResult { + let env = context.realm.environments.get_this_environment(); + match env { + EnvironmentSlots::Function(env) => context.vm.push(env.borrow().get_this_binding()?), + EnvironmentSlots::Global => { + let this = context.realm.global_object(); + context.vm.push(this.clone()); + } + } + Ok(ShouldExit::False) + } +} + +/// `Super` implements the Opcode Operation for `Opcode::Super` +/// +/// Operation: +/// - Pushes the current `super` value to the stack. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct Super; + +impl Operation for Super { + const NAME: &'static str = "Super"; + const INSTRUCTION: &'static str = "INST - Super"; + + fn execute(context: &mut Context) -> JsResult { + let home = { + let env = context + .realm + .environments + .get_this_environment() + .as_function_slots() + .expect("super access must be in a function environment"); + let env = env.borrow(); + let this = env.get_this_binding()?; + let function_object = env.function_object().borrow(); + let function = function_object + .as_function() + .expect("must be function object"); + + function.get_home_object().or(this.as_object()).cloned() + }; + + if let Some(home) = home { + if let Some(proto) = home.__get_prototype_of__(context)? { + context.vm.push(JsValue::from(proto)); + } else { + context.vm.push(JsValue::Null); + } + } else { + context.vm.push(JsValue::Null); + }; + Ok(ShouldExit::False) + } +} + +/// `SuperCall` implements the Opcode Operation for `Opcode::SuperCall` +/// +/// Operation: +/// - Execute the `super()` method. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct SuperCall; + +impl Operation for SuperCall { + const NAME: &'static str = "SuperCall"; + const INSTRUCTION: &'static str = "INST - SuperCall"; + + fn execute(context: &mut Context) -> JsResult { + let argument_count = context.vm.read::(); + let mut arguments = Vec::with_capacity(argument_count as usize); + for _ in 0..argument_count { + arguments.push(context.vm.pop()); + } + arguments.reverse(); + + let (new_target, active_function) = { + let this_env = context + .realm + .environments + .get_this_environment() + .as_function_slots() + .expect("super call must be in function environment"); + let this_env_borrow = this_env.borrow(); + let new_target = this_env_borrow + .new_target() + .expect("must have new target") + .clone(); + let active_function = this_env.borrow().function_object().clone(); + (new_target, active_function) + }; + let super_constructor = active_function + .__get_prototype_of__(context) + .expect("function object must have prototype") + .expect("function object must have prototype"); + + if !super_constructor.is_constructor() { + return Err(JsNativeError::typ() + .with_message("super constructor object must be constructor") + .into()); + } + + let result = super_constructor.__construct__(&arguments, &new_target, context)?; + + initialize_instance_elements(&result, &active_function, context)?; + + let this_env = context + .realm + .environments + .get_this_environment() + .as_function_slots() + .expect("super call must be in function environment"); + + if !this_env.borrow_mut().bind_this_value(&result) { + return Err(JsNativeError::reference() + .with_message("this already initialized") + .into()); + } + context.vm.push(result); + Ok(ShouldExit::False) + } +} + +/// `SuperCallSpread` implements the Opcode Operation for `Opcode::SuperCallSpread` +/// +/// Operation: +/// - Execute the `super()` method where the arguments contain spreads. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct SuperCallSpread; + +impl Operation for SuperCallSpread { + const NAME: &'static str = "SuperCallWithRest"; + const INSTRUCTION: &'static str = "INST - SuperCallWithRest"; + + fn execute(context: &mut Context) -> JsResult { + // Get the arguments that are stored as an array object on the stack. + let arguments_array = context.vm.pop(); + let arguments_array_object = arguments_array + .as_object() + .expect("arguments array in call spread function must be an object"); + let arguments = arguments_array_object + .borrow() + .properties() + .dense_indexed_properties() + .expect("arguments array in call spread function must be dense") + .clone(); + + let (new_target, active_function) = { + let this_env = context + .realm + .environments + .get_this_environment() + .as_function_slots() + .expect("super call must be in function environment"); + let this_env_borrow = this_env.borrow(); + let new_target = this_env_borrow + .new_target() + .expect("must have new target") + .clone(); + let active_function = this_env.borrow().function_object().clone(); + (new_target, active_function) + }; + let super_constructor = active_function + .__get_prototype_of__(context) + .expect("function object must have prototype") + .expect("function object must have prototype"); + + if !super_constructor.is_constructor() { + return Err(JsNativeError::typ() + .with_message("super constructor object must be constructor") + .into()); + } + + let result = super_constructor.__construct__(&arguments, &new_target, context)?; + + initialize_instance_elements(&result, &active_function, context)?; + + let this_env = context + .realm + .environments + .get_this_environment() + .as_function_slots() + .expect("super call must be in function environment"); + + if !this_env.borrow_mut().bind_this_value(&result) { + return Err(JsNativeError::reference() + .with_message("this already initialized") + .into()); + } + context.vm.push(result); + Ok(ShouldExit::False) + } +} + +/// `SuperCallDerived` implements the Opcode Operation for `Opcode::SuperCallDerived` +/// +/// Operation: +/// - Execute the `super()` method when no constructor of the class is defined. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct SuperCallDerived; + +impl Operation for SuperCallDerived { + const NAME: &'static str = "SuperCallDerived"; + const INSTRUCTION: &'static str = "INST - SuperCallDerived"; + + fn execute(context: &mut Context) -> JsResult { + let argument_count = context.vm.frame().arg_count; + let mut arguments = Vec::with_capacity(argument_count); + for _ in 0..argument_count { + arguments.push(context.vm.pop()); + } + arguments.reverse(); + + let (new_target, active_function) = { + let this_env = context + .realm + .environments + .get_this_environment() + .as_function_slots() + .expect("super call must be in function environment"); + let this_env_borrow = this_env.borrow(); + let new_target = this_env_borrow + .new_target() + .expect("must have new target") + .clone(); + let active_function = this_env.borrow().function_object().clone(); + (new_target, active_function) + }; + let super_constructor = active_function + .__get_prototype_of__(context) + .expect("function object must have prototype") + .expect("function object must have prototype"); + + if !super_constructor.is_constructor() { + return Err(JsNativeError::typ() + .with_message("super constructor object must be constructor") + .into()); + } + + let result = super_constructor.__construct__(&arguments, &new_target, context)?; + + initialize_instance_elements(&result, &active_function, context)?; + + let this_env = context + .realm + .environments + .get_this_environment() + .as_function_slots() + .expect("super call must be in function environment"); + if !this_env.borrow_mut().bind_this_value(&result) { + return Err(JsNativeError::reference() + .with_message("this already initialized") + .into()); + } + + context.vm.push(result); + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/generator/mod.rs b/boa_engine/src/vm/opcode/generator/mod.rs new file mode 100644 index 0000000000..91ffb206eb --- /dev/null +++ b/boa_engine/src/vm/opcode/generator/mod.rs @@ -0,0 +1,226 @@ +use crate::{ + builtins::{ + async_generator::{AsyncGenerator, AsyncGeneratorState}, + iterable::IteratorRecord, + }, + error::JsNativeError, + vm::{ + call_frame::{FinallyReturn, GeneratorResumeKind}, + opcode::Operation, + ShouldExit, + }, + Context, JsError, JsResult, JsValue, +}; + +pub(crate) mod yield_stm; + +pub(crate) use yield_stm::*; + +/// `GeneratorNext` implements the Opcode Operation for `Opcode::GeneratorNext` +/// +/// Operation: +/// - Resumes the current generator function. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct GeneratorNext; + +impl Operation for GeneratorNext { + const NAME: &'static str = "GeneratorNext"; + const INSTRUCTION: &'static str = "INST - GeneratorNext"; + + fn execute(context: &mut Context) -> JsResult { + match context.vm.frame().generator_resume_kind { + GeneratorResumeKind::Normal => Ok(ShouldExit::False), + GeneratorResumeKind::Throw => { + let received = context.vm.pop(); + Err(JsError::from_opaque(received)) + } + GeneratorResumeKind::Return => { + let mut finally_left = false; + + while let Some(catch_addresses) = context.vm.frame().catch.last() { + if let Some(finally_address) = catch_addresses.finally { + let frame = context.vm.frame_mut(); + frame.pc = finally_address as usize; + frame.finally_return = FinallyReturn::Ok; + frame.catch.pop(); + finally_left = true; + break; + } + context.vm.frame_mut().catch.pop(); + } + + if finally_left { + return Ok(ShouldExit::False); + } + Ok(ShouldExit::True) + } + } + } +} + +/// `AsyncGeneratorNext` implements the Opcode Operation for `Opcode::AsyncGeneratorNext` +/// +/// Operation: +/// - Resumes the current generator function. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct AsyncGeneratorNext; + +impl Operation for AsyncGeneratorNext { + const NAME: &'static str = "AsyncGeneratorNext"; + const INSTRUCTION: &'static str = "INST - AsyncGeneratorNext"; + + fn execute(context: &mut Context) -> JsResult { + let value = context.vm.pop(); + + if context.vm.frame().generator_resume_kind == GeneratorResumeKind::Throw { + return Err(JsError::from_opaque(value)); + } + + let completion = Ok(value); + let generator_object = context + .vm + .frame() + .async_generator + .as_ref() + .expect("must be in generator context here") + .clone(); + let next = generator_object + .borrow_mut() + .as_async_generator_mut() + .expect("must be async generator object") + .queue + .pop_front() + .expect("must have item in queue"); + AsyncGenerator::complete_step(&next, completion, false, context); + + let mut generator_object_mut = generator_object.borrow_mut(); + let gen = generator_object_mut + .as_async_generator_mut() + .expect("must be async generator object"); + + if let Some(next) = gen.queue.front() { + let (completion, r#return) = &next.completion; + if *r#return { + let value = match completion { + Ok(value) => value.clone(), + Err(e) => e.clone().to_opaque(context), + }; + context.vm.push(value); + context.vm.push(true); + } else { + context.vm.push(completion.clone()?); + context.vm.push(false); + } + + context.vm.push(false); + } else { + gen.state = AsyncGeneratorState::SuspendedYield; + context.vm.push(true); + context.vm.push(true); + } + Ok(ShouldExit::False) + } +} + +/// `GeneratorNextDelegate` implements the Opcode Operation for `Opcode::GeneratorNextDelegate` +/// +/// Operation: +/// - Delegates the current generator function another generator. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct GeneratorNextDelegate; + +impl Operation for GeneratorNextDelegate { + const NAME: &'static str = "GeneratorNextDelegate"; + const INSTRUCTION: &'static str = "INST - GeneratorNextDelegate"; + + fn execute(context: &mut Context) -> JsResult { + let done_address = context.vm.read::(); + let received = context.vm.pop(); + let done = context + .vm + .pop() + .as_boolean() + .expect("iterator [[Done]] was not a boolean"); + let next_method = context.vm.pop(); + let iterator = context.vm.pop(); + let iterator = iterator.as_object().expect("iterator was not an object"); + + match context.vm.frame().generator_resume_kind { + GeneratorResumeKind::Normal => { + let result = context.call(&next_method, &iterator.clone().into(), &[received])?; + let result_object = result.as_object().ok_or_else(|| { + JsNativeError::typ().with_message("generator next method returned non-object") + })?; + let done = result_object.get("done", context)?.to_boolean(); + if done { + context.vm.frame_mut().pc = done_address as usize; + let value = result_object.get("value", context)?; + context.vm.push(value); + return Ok(ShouldExit::False); + } + let value = result_object.get("value", context)?; + context.vm.push(iterator.clone()); + context.vm.push(next_method.clone()); + context.vm.push(done); + context.vm.push(value); + Ok(ShouldExit::Yield) + } + GeneratorResumeKind::Throw => { + let throw = iterator.get_method("throw", context)?; + if let Some(throw) = throw { + let result = throw.call(&iterator.clone().into(), &[received], context)?; + let result_object = result.as_object().ok_or_else(|| { + JsNativeError::typ() + .with_message("generator throw method returned non-object") + })?; + let done = result_object.get("done", context)?.to_boolean(); + if done { + context.vm.frame_mut().pc = done_address as usize; + let value = result_object.get("value", context)?; + context.vm.push(value); + return Ok(ShouldExit::False); + } + let value = result_object.get("value", context)?; + context.vm.push(iterator.clone()); + context.vm.push(next_method.clone()); + context.vm.push(done); + context.vm.push(value); + return Ok(ShouldExit::Yield); + } + context.vm.frame_mut().pc = done_address as usize; + let iterator_record = IteratorRecord::new(iterator.clone(), next_method, done); + iterator_record.close(Ok(JsValue::Undefined), context)?; + + Err(JsNativeError::typ() + .with_message("iterator does not have a throw method") + .into()) + } + GeneratorResumeKind::Return => { + let r#return = iterator.get_method("return", context)?; + if let Some(r#return) = r#return { + let result = r#return.call(&iterator.clone().into(), &[received], context)?; + let result_object = result.as_object().ok_or_else(|| { + JsNativeError::typ() + .with_message("generator return method returned non-object") + })?; + let done = result_object.get("done", context)?.to_boolean(); + if done { + context.vm.frame_mut().pc = done_address as usize; + let value = result_object.get("value", context)?; + context.vm.push(value); + return Ok(ShouldExit::True); + } + let value = result_object.get("value", context)?; + context.vm.push(iterator.clone()); + context.vm.push(next_method.clone()); + context.vm.push(done); + context.vm.push(value); + return Ok(ShouldExit::Yield); + } + context.vm.frame_mut().pc = done_address as usize; + context.vm.push(received); + Ok(ShouldExit::True) + } + } + } +} diff --git a/boa_engine/src/vm/opcode/generator/yield_stm.rs b/boa_engine/src/vm/opcode/generator/yield_stm.rs new file mode 100644 index 0000000000..1f8fd0543a --- /dev/null +++ b/boa_engine/src/vm/opcode/generator/yield_stm.rs @@ -0,0 +1,20 @@ +use crate::{ + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, +}; + +/// `Yield` implements the Opcode Operation for `Opcode::Yield` +/// +/// Operation: +/// - Yield from the current execution. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct Yield; + +impl Operation for Yield { + const NAME: &'static str = "Yield"; + const INSTRUCTION: &'static str = "INST - Yield"; + + fn execute(_context: &mut Context) -> JsResult { + Ok(ShouldExit::Yield) + } +} diff --git a/boa_engine/src/vm/opcode/get/function.rs b/boa_engine/src/vm/opcode/get/function.rs new file mode 100644 index 0000000000..ce9dfe99e2 --- /dev/null +++ b/boa_engine/src/vm/opcode/get/function.rs @@ -0,0 +1,44 @@ +use crate::{ + vm::{code_block::create_function_object, opcode::Operation, ShouldExit}, + Context, JsResult, +}; + +/// `GetFunction` implements the Opcode Operation for `Opcode::GetFunction` +/// +/// Operation: +/// - Get function from the pre-compiled inner functions. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct GetFunction; + +impl Operation for GetFunction { + const NAME: &'static str = "GetFunction"; + const INSTRUCTION: &'static str = "INST - GetFunction"; + + fn execute(context: &mut Context) -> JsResult { + let index = context.vm.read::(); + let code = context.vm.frame().code.functions[index as usize].clone(); + let function = create_function_object(code, false, None, context); + context.vm.push(function); + Ok(ShouldExit::False) + } +} + +/// `GetFunctionAsync` implements the Opcode Operation for `Opcode::GetFunctionAsync` +/// +/// Operation: +/// - Get async function from the pre-compiled inner functions. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct GetFunctionAsync; + +impl Operation for GetFunctionAsync { + const NAME: &'static str = "GetFunctionAsync"; + const INSTRUCTION: &'static str = "INST - GetFunctionAsync"; + + fn execute(context: &mut Context) -> JsResult { + let index = context.vm.read::(); + let code = context.vm.frame().code.functions[index as usize].clone(); + let function = create_function_object(code, true, None, context); + context.vm.push(function); + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/get/generator.rs b/boa_engine/src/vm/opcode/get/generator.rs new file mode 100644 index 0000000000..c63662e422 --- /dev/null +++ b/boa_engine/src/vm/opcode/get/generator.rs @@ -0,0 +1,44 @@ +use crate::{ + vm::{code_block::create_generator_function_object, opcode::Operation, ShouldExit}, + Context, JsResult, +}; + +/// `GetGenerator` implements the Opcode Operation for `Opcode::GetGenerator` +/// +/// Operation: +/// - Get generator function from the pre-compiled inner functions. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct GetGenerator; + +impl Operation for GetGenerator { + const NAME: &'static str = "GetGenerator"; + const INSTRUCTION: &'static str = "INST - GetGenerator"; + + fn execute(context: &mut Context) -> JsResult { + let index = context.vm.read::(); + let code = context.vm.frame().code.functions[index as usize].clone(); + let function = create_generator_function_object(code, false, context); + context.vm.push(function); + Ok(ShouldExit::False) + } +} + +/// `GetGeneratorAsync` implements the Opcode Operation for `Opcode::GetGeneratorAsync` +/// +/// Operation: +/// - Get async generator function from the pre-compiled inner functions. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct GetGeneratorAsync; + +impl Operation for GetGeneratorAsync { + const NAME: &'static str = "GetGeneratorAsync"; + const INSTRUCTION: &'static str = "INST - GetGeneratorAsync"; + + fn execute(context: &mut Context) -> JsResult { + let index = context.vm.read::(); + let code = context.vm.frame().code.functions[index as usize].clone(); + let function = create_generator_function_object(code, true, context); + context.vm.push(function); + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/get/mod.rs b/boa_engine/src/vm/opcode/get/mod.rs new file mode 100644 index 0000000000..a3f09232e5 --- /dev/null +++ b/boa_engine/src/vm/opcode/get/mod.rs @@ -0,0 +1,11 @@ +pub(crate) mod function; +pub(crate) mod generator; +pub(crate) mod name; +pub(crate) mod private; +pub(crate) mod property; + +pub(crate) use function::*; +pub(crate) use generator::*; +pub(crate) use name::*; +pub(crate) use private::*; +pub(crate) use property::*; diff --git a/boa_engine/src/vm/opcode/get/name.rs b/boa_engine/src/vm/opcode/get/name.rs new file mode 100644 index 0000000000..6b11a00419 --- /dev/null +++ b/boa_engine/src/vm/opcode/get/name.rs @@ -0,0 +1,136 @@ +use crate::{ + error::JsNativeError, + property::DescriptorKind, + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, JsString, JsValue, +}; + +/// `GetName` implements the Opcode Operation for `Opcode::GetName` +/// +/// Operation: +/// - Find a binding on the environment chain and push its value. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct GetName; + +impl Operation for GetName { + const NAME: &'static str = "GetName"; + const INSTRUCTION: &'static str = "INST - GetName"; + + fn execute(context: &mut Context) -> JsResult { + let index = context.vm.read::(); + let binding_locator = context.vm.frame().code.bindings[index as usize]; + binding_locator.throw_mutate_immutable(context)?; + + let value = if binding_locator.is_global() { + if let Some(value) = context + .realm + .environments + .get_value_global_poisoned(binding_locator.name()) + { + value + } else { + let key: JsString = context + .interner() + .resolve_expect(binding_locator.name().sym()) + .into_common(false); + match context.global_bindings_mut().get(&key) { + Some(desc) => match desc.kind() { + DescriptorKind::Data { + value: Some(value), .. + } => value.clone(), + DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => { + let get = get.clone(); + context.call(&get, &context.global_object().clone().into(), &[])? + } + _ => { + return Err(JsNativeError::reference() + .with_message(format!( + "{} is not defined", + key.to_std_string_escaped() + )) + .into()) + } + }, + _ => { + return Err(JsNativeError::reference() + .with_message(format!("{} is not defined", key.to_std_string_escaped())) + .into()) + } + } + } + } else if let Some(value) = context.realm.environments.get_value_optional( + binding_locator.environment_index(), + binding_locator.binding_index(), + binding_locator.name(), + ) { + value + } else { + let name = context + .interner() + .resolve_expect(binding_locator.name().sym()) + .to_string(); + return Err(JsNativeError::reference() + .with_message(format!("{name} is not initialized")) + .into()); + }; + + context.vm.push(value); + Ok(ShouldExit::False) + } +} + +/// `GetNameOrUndefined` implements the Opcode Operation for `Opcode::GetNameOrUndefined` +/// +/// Operation: +/// - Find a binding on the environment chain and push its value. If the binding does not exist push undefined. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct GetNameOrUndefined; + +impl Operation for GetNameOrUndefined { + const NAME: &'static str = "GetNameOrUndefined"; + const INSTRUCTION: &'static str = "INST - GetNameOrUndefined"; + + fn execute(context: &mut Context) -> JsResult { + let index = context.vm.read::(); + let binding_locator = context.vm.frame().code.bindings[index as usize]; + binding_locator.throw_mutate_immutable(context)?; + let value = if binding_locator.is_global() { + if let Some(value) = context + .realm + .environments + .get_value_global_poisoned(binding_locator.name()) + { + value + } else { + let key: JsString = context + .interner() + .resolve_expect(binding_locator.name().sym()) + .into_common(false); + match context.global_bindings_mut().get(&key) { + Some(desc) => match desc.kind() { + DescriptorKind::Data { + value: Some(value), .. + } => value.clone(), + DescriptorKind::Accessor { get: Some(get), .. } if !get.is_undefined() => { + let get = get.clone(); + context.call(&get, &context.global_object().clone().into(), &[])? + } + _ => JsValue::undefined(), + }, + _ => JsValue::undefined(), + } + } + } else if let Some(value) = context.realm.environments.get_value_optional( + binding_locator.environment_index(), + binding_locator.binding_index(), + binding_locator.name(), + ) { + value + } else { + JsValue::undefined() + }; + + context.vm.push(value); + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/get/private.rs b/boa_engine/src/vm/opcode/get/private.rs new file mode 100644 index 0000000000..ea61107916 --- /dev/null +++ b/boa_engine/src/vm/opcode/get/private.rs @@ -0,0 +1,54 @@ +use crate::{ + error::JsNativeError, + object::PrivateElement, + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, +}; + +/// `GetPrivateField` implements the Opcode Operation for `Opcode::GetPrivateField` +/// +/// Operation: +/// - Get a private property by name from an object an push it on the stack. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct GetPrivateField; + +impl Operation for GetPrivateField { + const NAME: &'static str = "GetPrivateField"; + const INSTRUCTION: &'static str = "INST - GetPrivateField"; + + fn execute(context: &mut Context) -> JsResult { + let index = context.vm.read::(); + let name = context.vm.frame().code.names[index as usize]; + let value = context.vm.pop(); + if let Some(object) = value.as_object() { + let object_borrow_mut = object.borrow(); + if let Some(element) = object_borrow_mut.get_private_element(name.sym()) { + match element { + PrivateElement::Field(value) => context.vm.push(value), + PrivateElement::Method(method) => context.vm.push(method.clone()), + PrivateElement::Accessor { + getter: Some(getter), + setter: _, + } => { + let value = getter.call(&value, &[], context)?; + context.vm.push(value); + } + PrivateElement::Accessor { .. } => { + return Err(JsNativeError::typ() + .with_message("private property was defined without a getter") + .into()); + } + } + } else { + return Err(JsNativeError::typ() + .with_message("private property does not exist") + .into()); + } + } else { + return Err(JsNativeError::typ() + .with_message("cannot read private property from non-object") + .into()); + } + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/get/property.rs b/boa_engine/src/vm/opcode/get/property.rs new file mode 100644 index 0000000000..87c01c4a72 --- /dev/null +++ b/boa_engine/src/vm/opcode/get/property.rs @@ -0,0 +1,96 @@ +use crate::{ + property::PropertyKey, + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, JsString, +}; + +/// `GetPropertyByName` implements the Opcode Operation for `Opcode::GetPropertyByName` +/// +/// Operation: +/// - Get a property by name from an object an push it on the stack. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct GetPropertyByName; + +impl Operation for GetPropertyByName { + const NAME: &'static str = "GetPropertyName"; + const INSTRUCTION: &'static str = "INST - GetPropertyName"; + + fn execute(context: &mut Context) -> JsResult { + let index = context.vm.read::(); + + let value = context.vm.pop(); + let object = if let Some(object) = value.as_object() { + object.clone() + } else { + value.to_object(context)? + }; + + let name = context.vm.frame().code.names[index as usize]; + let name: PropertyKey = context + .interner() + .resolve_expect(name.sym()) + .into_common::(false) + .into(); + let result = object.get(name, context)?; + + context.vm.push(result); + Ok(ShouldExit::False) + } +} + +/// `GetPropertyByValue` implements the Opcode Operation for `Opcode::GetPropertyByValue` +/// +/// Operation: +/// - Get a property by value from an object an push it on the stack. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct GetPropertyByValue; + +impl Operation for GetPropertyByValue { + const NAME: &'static str = "GetPropertyByValue"; + const INSTRUCTION: &'static str = "INST - GetPropertyByValue"; + + fn execute(context: &mut Context) -> JsResult { + let object = context.vm.pop(); + let key = context.vm.pop(); + let object = if let Some(object) = object.as_object() { + object.clone() + } else { + object.to_object(context)? + }; + + let key = key.to_property_key(context)?; + let value = object.get(key, context)?; + + context.vm.push(value); + Ok(ShouldExit::False) + } +} + +/// `GetPropertyByValuePush` implements the Opcode Operation for `Opcode::GetPropertyByValuePush` +/// +/// Operation: +/// - Get a property by value from an object an push the key and value on the stack. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct GetPropertyByValuePush; + +impl Operation for GetPropertyByValuePush { + const NAME: &'static str = "GetPropertyByValuePush"; + const INSTRUCTION: &'static str = "INST - GetPropertyByValuePush"; + + fn execute(context: &mut Context) -> JsResult { + let object = context.vm.pop(); + let key = context.vm.pop(); + let object = if let Some(object) = object.as_object() { + object.clone() + } else { + object.to_object(context)? + }; + + let property_key = key.to_property_key(context)?; + let value = object.get(property_key, context)?; + + context.vm.push(key); + context.vm.push(value); + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/iteration/for_await.rs b/boa_engine/src/vm/opcode/iteration/for_await.rs new file mode 100644 index 0000000000..afa1253de5 --- /dev/null +++ b/boa_engine/src/vm/opcode/iteration/for_await.rs @@ -0,0 +1,78 @@ +use crate::{ + builtins::iterable::IteratorResult, + error::JsNativeError, + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, +}; + +/// `ForAwaitOfLoopIterate` implements the Opcode Operation for `Opcode::ForAwaitOfLoopIterator` +/// +/// Operation: +/// - Move to the next value in a for await..of loop. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct ForAwaitOfLoopIterate; + +impl Operation for ForAwaitOfLoopIterate { + const NAME: &'static str = "ForAwaitOfLoopIterate"; + const INSTRUCTION: &'static str = "INST - ForAwaitOfLoopIterate"; + + fn execute(context: &mut Context) -> JsResult { + let _done = context + .vm + .pop() + .as_boolean() + .expect("iterator [[Done]] was not a boolean"); + let next_method = context.vm.pop(); + let next_method_object = if let Some(object) = next_method.as_callable() { + object + } else { + return Err(JsNativeError::typ() + .with_message("iterable next method not a function") + .into()); + }; + let iterator = context.vm.pop(); + let next_result = next_method_object.call(&iterator, &[], context)?; + context.vm.push(iterator); + context.vm.push(next_method); + context.vm.push(next_result); + Ok(ShouldExit::False) + } +} + +/// `ForAwaitOfLoopNext` implements the Opcode Operation for `Opcode::ForAwaitOfLoopNext` +/// +/// Operation: +/// - Get the value from a for await..of loop next result. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct ForAwaitOfLoopNext; + +impl Operation for ForAwaitOfLoopNext { + const NAME: &'static str = "ForAwaitOfLoopNext"; + const INSTRUCTION: &'static str = "INST - ForAwaitOfLoopNext"; + + fn execute(context: &mut Context) -> JsResult { + let address = context.vm.read::(); + + let next_result = context.vm.pop(); + let next_result = if let Some(next_result) = next_result.as_object() { + IteratorResult::new(next_result.clone()) + } else { + return Err(JsNativeError::typ() + .with_message("next value should be an object") + .into()); + }; + + if next_result.complete(context)? { + context.vm.frame_mut().pc = address as usize; + context.vm.frame_mut().loop_env_stack_dec(); + context.vm.frame_mut().try_env_stack_dec(); + context.realm.environments.pop(); + context.vm.push(true); + } else { + context.vm.push(false); + let value = next_result.value(context)?; + context.vm.push(value); + } + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/iteration/for_in.rs b/boa_engine/src/vm/opcode/iteration/for_in.rs new file mode 100644 index 0000000000..b7c9582918 --- /dev/null +++ b/boa_engine/src/vm/opcode/iteration/for_in.rs @@ -0,0 +1,86 @@ +use crate::{ + builtins::{iterable::IteratorRecord, ForInIterator}, + error::JsNativeError, + property::PropertyDescriptor, + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, JsValue, +}; + +/// `ForInLoopInitIterator` implements the Opcode Operation for `Opcode::ForInLoopInitIterator` +/// +/// Operation: +/// - Initialize the iterator for a for..in loop or jump to after the loop if object is null or undefined. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct ForInLoopInitIterator; + +impl Operation for ForInLoopInitIterator { + const NAME: &'static str = "ForInLoopInitIterator"; + const INSTRUCTION: &'static str = "INST - ForInLoopInitIterator"; + + fn execute(context: &mut Context) -> JsResult { + let address = context.vm.read::(); + + let object = context.vm.pop(); + if object.is_null_or_undefined() { + context.vm.frame_mut().pc = address as usize; + return Ok(ShouldExit::False); + } + + let object = object.to_object(context)?; + let iterator = ForInIterator::create_for_in_iterator(JsValue::new(object), context); + let next_method = iterator + .get_property("next") + .as_ref() + .map(PropertyDescriptor::expect_value) + .cloned() + .ok_or_else(|| JsNativeError::typ().with_message("Could not find property `next`"))?; + + context.vm.push(iterator); + context.vm.push(next_method); + context.vm.push(false); + Ok(ShouldExit::False) + } +} + +/// `ForInLoopNext` implements the Opcode Operation for `Opcode::ForInLoopNext` +/// +/// Operation: +/// - Move to the next value in a for..in loop or jump to exit of the loop if done. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct ForInLoopNext; + +impl Operation for ForInLoopNext { + const NAME: &'static str = "ForInLoopInitIterator"; + const INSTRUCTION: &'static str = "INST - ForInLoopInitIterator"; + + fn execute(context: &mut Context) -> JsResult { + let address = context.vm.read::(); + + let done = context + .vm + .pop() + .as_boolean() + .expect("iterator [[Done]] was not a boolean"); + let next_method = context.vm.pop(); + let iterator = context.vm.pop(); + let iterator = iterator.as_object().expect("iterator was not an object"); + + let iterator_record = IteratorRecord::new(iterator.clone(), next_method.clone(), done); + if let Some(next) = iterator_record.step(context)? { + context.vm.push(iterator.clone()); + context.vm.push(next_method); + context.vm.push(done); + let value = next.value(context)?; + context.vm.push(value); + } else { + context.vm.frame_mut().pc = address as usize; + context.vm.frame_mut().loop_env_stack_dec(); + context.vm.frame_mut().try_env_stack_dec(); + context.realm.environments.pop(); + context.vm.push(iterator.clone()); + context.vm.push(next_method); + context.vm.push(done); + } + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/iteration/init.rs b/boa_engine/src/vm/opcode/iteration/init.rs new file mode 100644 index 0000000000..955ce79376 --- /dev/null +++ b/boa_engine/src/vm/opcode/iteration/init.rs @@ -0,0 +1,47 @@ +use crate::{ + builtins::iterable::IteratorHint, + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, +}; + +/// `InitIterator` implements the Opcode Operation for `Opcode::InitIterator` +/// +/// Operation: +/// - Initialize an iterator +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct InitIterator; + +impl Operation for InitIterator { + const NAME: &'static str = "InitIterator"; + const INSTRUCTION: &'static str = "INST - InitIterator"; + + fn execute(context: &mut Context) -> JsResult { + let object = context.vm.pop(); + let iterator = object.get_iterator(context, None, None)?; + context.vm.push(iterator.iterator().clone()); + context.vm.push(iterator.next_method().clone()); + context.vm.push(iterator.done()); + Ok(ShouldExit::False) + } +} + +/// `InitIteratorAsync` implements the Opcode Operation for `Opcode::InitIteratorAsync` +/// +/// Operation: +/// - Initialize an async iterator. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct InitIteratorAsync; + +impl Operation for InitIteratorAsync { + const NAME: &'static str = "InitIteratorAsync"; + const INSTRUCTION: &'static str = "INST - InitIteratorAsync"; + + fn execute(context: &mut Context) -> JsResult { + let object = context.vm.pop(); + let iterator = object.get_iterator(context, Some(IteratorHint::Async), None)?; + context.vm.push(iterator.iterator().clone()); + context.vm.push(iterator.next_method().clone()); + context.vm.push(iterator.done()); + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/iteration/iterator.rs b/boa_engine/src/vm/opcode/iteration/iterator.rs new file mode 100644 index 0000000000..38bf5e4d99 --- /dev/null +++ b/boa_engine/src/vm/opcode/iteration/iterator.rs @@ -0,0 +1,109 @@ +use crate::{ + builtins::{iterable::IteratorRecord, Array}, + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, JsValue, +}; + +/// `IteratorNext` implements the Opcode Operation for `Opcode::IteratorNext` +/// +/// Operation: +/// - Advance the iterator by one and put the value on the stack. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct IteratorNext; + +impl Operation for IteratorNext { + const NAME: &'static str = "IteratorNext"; + const INSTRUCTION: &'static str = "INST - IteratorNext"; + + fn execute(context: &mut Context) -> JsResult { + let done = context + .vm + .pop() + .as_boolean() + .expect("iterator [[Done]] was not a boolean"); + let next_method = context.vm.pop(); + let iterator = context.vm.pop(); + let iterator = iterator.as_object().expect("iterator was not an object"); + + let iterator_record = IteratorRecord::new(iterator.clone(), next_method.clone(), done); + let next = iterator_record.step(context)?; + + context.vm.push(iterator.clone()); + context.vm.push(next_method); + if let Some(next) = next { + let value = next.value(context)?; + context.vm.push(false); + context.vm.push(value); + } else { + context.vm.push(true); + context.vm.push(JsValue::undefined()); + } + Ok(ShouldExit::False) + } +} + +/// `IteratorClose` implements the Opcode Operation for `Opcode::IteratorClose` +/// +/// Operation: +/// - Close an iterator +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct IteratorClose; + +impl Operation for IteratorClose { + const NAME: &'static str = "IteratorClose"; + const INSTRUCTION: &'static str = "INST - IteratorClose"; + + fn execute(context: &mut Context) -> JsResult { + let done = context + .vm + .pop() + .as_boolean() + .expect("iterator [[Done]] was not a boolean"); + let next_method = context.vm.pop(); + let iterator = context.vm.pop(); + let iterator = iterator.as_object().expect("iterator was not an object"); + if !done { + let iterator_record = IteratorRecord::new(iterator.clone(), next_method, done); + iterator_record.close(Ok(JsValue::Null), context)?; + } + Ok(ShouldExit::False) + } +} + +/// `IteratorToArray` implements the Opcode Operation for `Opcode::IteratorToArray` +/// +/// Operation: +/// - Consume the iterator and construct and array with all the values. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct IteratorToArray; + +impl Operation for IteratorToArray { + const NAME: &'static str = "IteratorToArray"; + const INSTRUCTION: &'static str = "INST - IteratorToArray"; + + fn execute(context: &mut Context) -> JsResult { + let done = context + .vm + .pop() + .as_boolean() + .expect("iterator [[Done]] was not a boolean"); + let next_method = context.vm.pop(); + let iterator = context.vm.pop(); + let iterator = iterator.as_object().expect("iterator was not an object"); + + let iterator_record = IteratorRecord::new(iterator.clone(), next_method.clone(), done); + let mut values = Vec::new(); + + while let Some(result) = iterator_record.step(context)? { + values.push(result.value(context)?); + } + + let array = Array::create_array_from_list(values, context); + + context.vm.push(iterator.clone()); + context.vm.push(next_method); + context.vm.push(true); + context.vm.push(array); + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/iteration/loop_ops.rs b/boa_engine/src/vm/opcode/iteration/loop_ops.rs new file mode 100644 index 0000000000..e28a19c18e --- /dev/null +++ b/boa_engine/src/vm/opcode/iteration/loop_ops.rs @@ -0,0 +1,76 @@ +use crate::{ + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, +}; + +/// `LoopStart` implements the Opcode Operation for `Opcode::LoopStart` +/// +/// Operation: +/// - Push loop start marker. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct LoopStart; + +impl Operation for LoopStart { + const NAME: &'static str = "LoopStart"; + const INSTRUCTION: &'static str = "INST - LoopStart"; + + fn execute(context: &mut Context) -> JsResult { + context.vm.frame_mut().loop_env_stack.push(0); + context.vm.frame_mut().try_env_stack_loop_inc(); + Ok(ShouldExit::False) + } +} + +/// `LoopContinue` implements the Opcode Operation for `Opcode::LoopContinue` +/// +/// Operation: +/// - Clean up environments when a loop continues. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct LoopContinue; + +impl Operation for LoopContinue { + const NAME: &'static str = "LoopContinue"; + const INSTRUCTION: &'static str = "INST - LoopContinue"; + + fn execute(context: &mut Context) -> JsResult { + let env_num = context + .vm + .frame_mut() + .loop_env_stack + .last_mut() + .expect("loop env stack entry must exist"); + let env_num_copy = *env_num; + *env_num = 0; + for _ in 0..env_num_copy { + context.realm.environments.pop(); + } + Ok(ShouldExit::False) + } +} + +/// `LoopEnd` implements the Opcode Operation for `Opcode::LoopEnd` +/// +/// Operation: +/// - Clean up enviroments at the end of a lopp. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct LoopEnd; + +impl Operation for LoopEnd { + const NAME: &'static str = "LoopEnd"; + const INSTRUCTION: &'static str = "INST - LoopEnd"; + + fn execute(context: &mut Context) -> JsResult { + let env_num = context + .vm + .frame_mut() + .loop_env_stack + .pop() + .expect("loop env stack entry must exist"); + for _ in 0..env_num { + context.realm.environments.pop(); + context.vm.frame_mut().try_env_stack_dec(); + } + context.vm.frame_mut().try_env_stack_loop_dec(); + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/iteration/mod.rs b/boa_engine/src/vm/opcode/iteration/mod.rs new file mode 100644 index 0000000000..5e52baa2bd --- /dev/null +++ b/boa_engine/src/vm/opcode/iteration/mod.rs @@ -0,0 +1,11 @@ +pub(crate) mod for_await; +pub(crate) mod for_in; +pub(crate) mod init; +pub(crate) mod iterator; +pub(crate) mod loop_ops; + +pub(crate) use for_await::*; +pub(crate) use for_in::*; +pub(crate) use init::*; +pub(crate) use iterator::*; +pub(crate) use loop_ops::*; diff --git a/boa_engine/src/vm/opcode/jump/mod.rs b/boa_engine/src/vm/opcode/jump/mod.rs new file mode 100644 index 0000000000..f509a59c97 --- /dev/null +++ b/boa_engine/src/vm/opcode/jump/mod.rs @@ -0,0 +1,64 @@ +use crate::{ + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, +}; + +/// `Jump` implements the Opcode Operation for `Opcode::Jump` +/// +/// Operation: +/// - Unconditional jump to address. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct Jump; + +impl Operation for Jump { + const NAME: &'static str = "Jump"; + const INSTRUCTION: &'static str = "INST - Jump"; + + fn execute(context: &mut Context) -> JsResult { + let address = context.vm.read::(); + context.vm.frame_mut().pc = address as usize; + Ok(ShouldExit::False) + } +} + +/// `JumpIfFalse` implements the Opcode Operation for `Opcode::JumpIfFalse` +/// +/// Operation: +/// - Conditional jump to address. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct JumpIfFalse; + +impl Operation for JumpIfFalse { + const NAME: &'static str = "JumpIfFalse"; + const INSTRUCTION: &'static str = "INST - JumpIfFalse"; + + fn execute(context: &mut Context) -> JsResult { + let address = context.vm.read::(); + if !context.vm.pop().to_boolean() { + context.vm.frame_mut().pc = address as usize; + } + Ok(ShouldExit::False) + } +} + +/// `JumpIfNotUndefined` implements the Opcode Operation for `Opcode::JumpIfNotUndefined` +/// +/// Operation: +/// - Conditional jump to address. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct JumpIfNotUndefined; + +impl Operation for JumpIfNotUndefined { + const NAME: &'static str = "JumpIfNotUndefined"; + const INSTRUCTION: &'static str = "INST - JumpIfNotUndefined"; + + fn execute(context: &mut Context) -> JsResult { + let address = context.vm.read::(); + let value = context.vm.pop(); + if !value.is_undefined() { + context.vm.frame_mut().pc = address as usize; + context.vm.push(value); + } + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode.rs b/boa_engine/src/vm/opcode/mod.rs similarity index 56% rename from boa_engine/src/vm/opcode.rs rename to boa_engine/src/vm/opcode/mod.rs index ee5982b896..9fed4fe1b8 100644 --- a/boa_engine/src/vm/opcode.rs +++ b/boa_engine/src/vm/opcode/mod.rs @@ -1,4 +1,111 @@ /// The opcodes of the vm. +use crate::{vm::ShouldExit, Context, JsResult}; + +// Operation modules +mod await_stm; +mod binary_ops; +mod call; +mod concat; +mod copy; +mod define; +mod delete; +mod dup; +mod environment; +mod generator; +mod get; +mod iteration; +mod jump; +mod new; +mod nop; +mod pop; +mod promise; +mod push; +mod require; +mod rest_parameter; +mod return_stm; +mod set; +mod swap; +mod switch; +mod throw; +mod to; +mod try_catch; +mod unary_ops; +mod value; + +// Operation structs +#[doc(inline)] +pub(crate) use await_stm::*; +#[doc(inline)] +pub(crate) use binary_ops::*; +#[doc(inline)] +pub(crate) use call::*; +#[doc(inline)] +pub(crate) use concat::*; +#[doc(inline)] +pub(crate) use copy::*; +#[doc(inline)] +pub(crate) use define::*; +#[doc(inline)] +pub(crate) use delete::*; +#[doc(inline)] +pub(crate) use dup::*; +#[doc(inline)] +pub(crate) use environment::*; +#[doc(inline)] +pub(crate) use generator::*; +#[doc(inline)] +pub(crate) use get::*; +#[doc(inline)] +pub(crate) use iteration::*; +#[doc(inline)] +pub(crate) use jump::*; +#[doc(inline)] +pub(crate) use new::*; +#[doc(inline)] +pub(crate) use nop::*; +#[doc(inline)] +pub(crate) use pop::*; +#[doc(inline)] +pub(crate) use promise::*; +#[doc(inline)] +pub(crate) use push::*; +#[doc(inline)] +pub(crate) use require::*; +#[doc(inline)] +pub(crate) use rest_parameter::*; +#[doc(inline)] +pub(crate) use return_stm::*; +#[doc(inline)] +pub(crate) use set::*; +#[doc(inline)] +pub(crate) use swap::*; +#[doc(inline)] +pub(crate) use switch::*; +#[doc(inline)] +pub(crate) use throw::*; +#[doc(inline)] +pub(crate) use to::*; +#[doc(inline)] +pub(crate) use try_catch::*; +#[doc(inline)] +pub(crate) use unary_ops::*; +#[doc(inline)] +pub(crate) use value::*; + +/// The `Operation` trait implements the execution code along with the +/// identifying Name and Instruction value for an Boa Opcode +/// +/// +/// This trait should be implemented for a struct that corresponds with +/// any arm of the `OpCode` enum. +/// +pub(crate) trait Operation { + const NAME: &'static str; + const INSTRUCTION: &'static str; + + fn execute(context: &mut Context) -> JsResult; +} + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[repr(u8)] pub enum Opcode { @@ -1212,336 +1319,336 @@ impl Opcode { pub fn as_str(self) -> &'static str { match self { - Self::Pop => "Pop", - Self::PopIfThrown => "PopIfThrown", - Self::Dup => "Dup", - Self::Swap => "Swap", - Self::PushZero => "PushZero", - Self::PushOne => "PushOne", - Self::PushInt8 => "PushInt8", - Self::PushInt16 => "PushInt16", - Self::PushInt32 => "PushInt32", - Self::PushRational => "PushRational", - Self::PushNaN => "PushNaN", - Self::PushPositiveInfinity => "PushPositiveInfinity", - Self::PushNegativeInfinity => "PushNegativeInfinity", - Self::PushNull => "PushNull", - Self::PushTrue => "PushTrue", - Self::PushFalse => "PushFalse", - Self::PushUndefined => "PushUndefined", - Self::PushLiteral => "PushLiteral", - Self::PushEmptyObject => "PushEmptyObject", - Self::PushClassPrototype => "PushClassPrototype", - Self::SetClassPrototype => "SetClassPrototype", - Self::SetHomeObject => "SetHomeObject", - Self::PushNewArray => "PushNewArray", - Self::PushValueToArray => "PushValueToArray", - Self::PushElisionToArray => "PushElisionToArray", - Self::PushIteratorToArray => "PushIteratorToArray", - Self::Add => "Add", - Self::Sub => "Sub", - Self::Div => "Div", - Self::Mul => "Mul", - Self::Mod => "Mod", - Self::Pow => "Pow", - Self::ShiftRight => "ShiftRight", - Self::ShiftLeft => "ShiftLeft", - Self::UnsignedShiftRight => "UnsignedShiftRight", - Self::BitOr => "BitOr", - Self::BitAnd => "BitAnd", - Self::BitXor => "BitXor", - Self::BitNot => "BitNot", - Self::In => "In", - Self::Eq => "Eq", - Self::StrictEq => "StrictEq", - Self::NotEq => "NotEq", - Self::StrictNotEq => "StrictNotEq", - Self::GreaterThan => "GreaterThan", - Self::GreaterThanOrEq => "GreaterThanOrEq", - Self::LessThan => "LessThan", - Self::LessThanOrEq => "LessThanOrEq", - Self::InstanceOf => "InstanceOf", - Self::TypeOf => "TypeOf", - Self::Void => "Void", - Self::LogicalNot => "LogicalNot", - Self::LogicalAnd => "LogicalAnd", - Self::LogicalOr => "LogicalOr", - Self::Coalesce => "Coalesce", - Self::Pos => "Pos", - Self::Neg => "Neg", - Self::Inc => "Inc", - Self::IncPost => "IncPost", - Self::Dec => "Dec", - Self::DecPost => "DecPost", - Self::DefInitArg => "DefInitArg", - Self::DefVar => "DefVar", - Self::DefInitVar => "DefInitVar", - Self::DefLet => "DefLet", - Self::DefInitLet => "DefInitLet", - Self::DefInitConst => "DefInitConst", - Self::GetName => "GetName", - Self::GetNameOrUndefined => "GetNameOrUndefined", - Self::SetName => "SetName", - Self::GetPropertyByName => "GetPropertyByName", - Self::GetPropertyByValue => "GetPropertyByValue", - Self::GetPropertyByValuePush => "GetPropertyByValuePush", - Self::SetPropertyByName => "SetPropertyByName", - Self::DefineOwnPropertyByName => "DefineOwnPropertyByName", - Self::DefineClassMethodByName => "DefineClassMethodByName", - Self::SetPropertyByValue => "SetPropertyByValue", - Self::DefineOwnPropertyByValue => "DefineOwnPropertyByValue", - Self::DefineClassMethodByValue => "DefineClassMethodByValue", - Self::SetPropertyGetterByName => "SetPropertyGetterByName", - Self::DefineClassGetterByName => "DefineClassGetterByName", - Self::SetPropertyGetterByValue => "SetPropertyGetterByValue", - Self::DefineClassGetterByValue => "DefineClassGetterByValue", - Self::SetPropertySetterByName => "SetPropertySetterByName", - Self::DefineClassSetterByName => "DefineClassSetterByName", - Self::SetPropertySetterByValue => "SetPropertySetterByValue", - Self::DefineClassSetterByValue => "DefineClassSetterByValue", - Self::AssignPrivateField => "AssignPrivateField", - Self::SetPrivateField => "SetPrivateValue", - Self::SetPrivateMethod => "SetPrivateMethod", - Self::SetPrivateSetter => "SetPrivateSetter", - Self::SetPrivateGetter => "SetPrivateGetter", - Self::GetPrivateField => "GetPrivateField", - Self::PushClassField => "PushClassField", - Self::PushClassFieldPrivate => "PushClassFieldPrivate", - Self::PushClassPrivateGetter => "PushClassPrivateGetter", - Self::PushClassPrivateSetter => "PushClassPrivateSetter", - Self::PushClassPrivateMethod => "PushClassPrivateMethod", - Self::DeletePropertyByName => "DeletePropertyByName", - Self::DeletePropertyByValue => "DeletePropertyByValue", - Self::CopyDataProperties => "CopyDataProperties", - Self::ToPropertyKey => "ToPropertyKey", - Self::Jump => "Jump", - Self::JumpIfFalse => "JumpIfFalse", - Self::JumpIfNotUndefined => "JumpIfNotUndefined", - Self::Throw => "Throw", - Self::TryStart => "TryStart", - Self::TryEnd => "TryEnd", - Self::CatchStart => "CatchStart", - Self::CatchEnd => "CatchEnd", - Self::CatchEnd2 => "CatchEnd2", - Self::FinallyStart => "FinallyStart", - Self::FinallyEnd => "FinallyEnd", - Self::FinallySetJump => "FinallySetJump", - Self::ToBoolean => "ToBoolean", - Self::This => "This", - Self::Super => "Super", - Self::SuperCall => "SuperCall", - Self::SuperCallSpread => "SuperCallWithRest", - Self::SuperCallDerived => "SuperCallDerived", - Self::Case => "Case", - Self::Default => "Default", - Self::GetFunction => "GetFunction", - Self::GetFunctionAsync => "GetFunctionAsync", - Self::GetGenerator => "GetGenerator", - Self::GetGeneratorAsync => "GetGeneratorAsync", - Self::CallEval => "CallEval", - Self::CallEvalSpread => "CallEvalSpread", - Self::Call => "Call", - Self::CallSpread => "CallSpread", - Self::New => "New", - Self::NewSpread => "NewSpread", - Self::Return => "Return", - Self::PushDeclarativeEnvironment => "PushDeclarativeEnvironment", - Self::PushFunctionEnvironment => "PushFunctionEnvironment", - Self::PopEnvironment => "PopEnvironment", - Self::LoopStart => "LoopStart", - Self::LoopContinue => "LoopContinue", - Self::LoopEnd => "LoopEnd", - Self::ForInLoopInitIterator => "ForInLoopInitIterator", - Self::InitIterator => "InitIterator", - Self::InitIteratorAsync => "InitIteratorAsync", - Self::IteratorNext => "IteratorNext", - Self::IteratorClose => "IteratorClose", - Self::IteratorToArray => "IteratorToArray", - Self::ForInLoopNext => "ForInLoopNext", - Self::ForAwaitOfLoopNext => "ForAwaitOfLoopNext", - Self::ForAwaitOfLoopIterate => "ForAwaitOfLoopIterate", - Self::ConcatToString => "ConcatToString", - Self::RequireObjectCoercible => "RequireObjectCoercible", - Self::ValueNotNullOrUndefined => "ValueNotNullOrUndefined", - Self::RestParameterInit => "FunctionRestParameter", - Self::RestParameterPop => "RestParameterPop", - Self::PopOnReturnAdd => "PopOnReturnAdd", - Self::PopOnReturnSub => "PopOnReturnSub", - Self::Yield => "Yield", - Self::GeneratorNext => "GeneratorNext", - Self::AsyncGeneratorNext => "AsyncGeneratorNext", - Self::Await => "Await", - Self::PushNewTarget => "PushNewTarget", - Self::GeneratorNextDelegate => "GeneratorNextDelegate", - Self::Nop => "Nop", + Self::Pop => Pop::NAME, + Self::PopIfThrown => PopIfThrown::NAME, + Self::Dup => Dup::NAME, + Self::Swap => Swap::NAME, + Self::PushZero => PushZero::NAME, + Self::PushOne => PushOne::NAME, + Self::PushInt8 => PushInt8::NAME, + Self::PushInt16 => PushInt16::NAME, + Self::PushInt32 => PushInt32::NAME, + Self::PushRational => PushRational::NAME, + Self::PushNaN => PushNaN::NAME, + Self::PushPositiveInfinity => PushPositiveInfinity::NAME, + Self::PushNegativeInfinity => PushNegativeInfinity::NAME, + Self::PushNull => PushNull::NAME, + Self::PushTrue => PushTrue::NAME, + Self::PushFalse => PushFalse::NAME, + Self::PushUndefined => PushUndefined::NAME, + Self::PushLiteral => PushLiteral::NAME, + Self::PushEmptyObject => PushEmptyObject::NAME, + Self::PushClassPrototype => PushClassPrototype::NAME, + Self::SetClassPrototype => SetClassPrototype::NAME, + Self::SetHomeObject => SetHomeObject::NAME, + Self::PushNewArray => PushNewArray::NAME, + Self::PushValueToArray => PushValueToArray::NAME, + Self::PushElisionToArray => PushElisionToArray::NAME, + Self::PushIteratorToArray => PushIteratorToArray::NAME, + Self::Add => Add::NAME, + Self::Sub => Sub::NAME, + Self::Div => Div::NAME, + Self::Mul => Mul::NAME, + Self::Mod => Mod::NAME, + Self::Pow => Pow::NAME, + Self::ShiftRight => ShiftRight::NAME, + Self::ShiftLeft => ShiftLeft::NAME, + Self::UnsignedShiftRight => UnsignedShiftRight::NAME, + Self::BitOr => BitOr::NAME, + Self::BitAnd => BitAnd::NAME, + Self::BitXor => BitXor::NAME, + Self::BitNot => BitNot::NAME, + Self::In => In::NAME, + Self::Eq => Eq::NAME, + Self::StrictEq => StrictEq::NAME, + Self::NotEq => NotEq::NAME, + Self::StrictNotEq => StrictNotEq::NAME, + Self::GreaterThan => GreaterThan::NAME, + Self::GreaterThanOrEq => GreaterThanOrEq::NAME, + Self::LessThan => LessThan::NAME, + Self::LessThanOrEq => LessThanOrEq::NAME, + Self::InstanceOf => InstanceOf::NAME, + Self::TypeOf => TypeOf::NAME, + Self::Void => Void::NAME, + Self::LogicalNot => LogicalNot::NAME, + Self::LogicalAnd => LogicalAnd::NAME, + Self::LogicalOr => LogicalOr::NAME, + Self::Coalesce => Coalesce::NAME, + Self::Pos => Pos::NAME, + Self::Neg => Neg::NAME, + Self::Inc => Inc::NAME, + Self::IncPost => IncPost::NAME, + Self::Dec => Dec::NAME, + Self::DecPost => DecPost::NAME, + Self::DefInitArg => DefInitArg::NAME, + Self::DefVar => DefVar::NAME, + Self::DefInitVar => DefInitVar::NAME, + Self::DefLet => DefLet::NAME, + Self::DefInitLet => DefInitLet::NAME, + Self::DefInitConst => DefInitConst::NAME, + Self::GetName => GetName::NAME, + Self::GetNameOrUndefined => GetNameOrUndefined::NAME, + Self::SetName => SetName::NAME, + Self::GetPropertyByName => GetPropertyByName::NAME, + Self::GetPropertyByValue => GetPropertyByValue::NAME, + Self::GetPropertyByValuePush => GetPropertyByValuePush::NAME, + Self::SetPropertyByName => SetPropertyByName::NAME, + Self::DefineOwnPropertyByName => DefineOwnPropertyByName::NAME, + Self::DefineClassMethodByName => DefineClassMethodByName::NAME, + Self::SetPropertyByValue => SetPropertyByValue::NAME, + Self::DefineOwnPropertyByValue => DefineOwnPropertyByValue::NAME, + Self::DefineClassMethodByValue => DefineClassMethodByValue::NAME, + Self::SetPropertyGetterByName => SetPropertyGetterByName::NAME, + Self::DefineClassGetterByName => DefineClassGetterByName::NAME, + Self::SetPropertyGetterByValue => SetPropertyGetterByValue::NAME, + Self::DefineClassGetterByValue => DefineClassGetterByValue::NAME, + Self::SetPropertySetterByName => SetPropertySetterByName::NAME, + Self::DefineClassSetterByName => DefineClassSetterByName::NAME, + Self::SetPropertySetterByValue => SetPropertySetterByValue::NAME, + Self::DefineClassSetterByValue => DefineClassSetterByValue::NAME, + Self::AssignPrivateField => AssignPrivateField::NAME, + Self::SetPrivateField => SetPrivateField::NAME, + Self::SetPrivateMethod => SetPrivateMethod::NAME, + Self::SetPrivateSetter => SetPrivateSetter::NAME, + Self::SetPrivateGetter => SetPrivateGetter::NAME, + Self::GetPrivateField => GetPrivateField::NAME, + Self::PushClassField => PushClassField::NAME, + Self::PushClassFieldPrivate => PushClassFieldPrivate::NAME, + Self::PushClassPrivateGetter => PushClassPrivateGetter::NAME, + Self::PushClassPrivateSetter => PushClassPrivateSetter::NAME, + Self::PushClassPrivateMethod => PushClassPrivateMethod::NAME, + Self::DeletePropertyByName => DeletePropertyByName::NAME, + Self::DeletePropertyByValue => DeletePropertyByValue::NAME, + Self::CopyDataProperties => CopyDataProperties::NAME, + Self::ToPropertyKey => ToPropertyKey::NAME, + Self::Jump => Jump::NAME, + Self::JumpIfFalse => JumpIfFalse::NAME, + Self::JumpIfNotUndefined => JumpIfNotUndefined::NAME, + Self::Throw => Throw::NAME, + Self::TryStart => TryStart::NAME, + Self::TryEnd => TryEnd::NAME, + Self::CatchStart => CatchStart::NAME, + Self::CatchEnd => CatchEnd::NAME, + Self::CatchEnd2 => CatchEnd2::NAME, + Self::FinallyStart => FinallyStart::NAME, + Self::FinallyEnd => FinallyEnd::NAME, + Self::FinallySetJump => FinallySetJump::NAME, + Self::ToBoolean => ToBoolean::NAME, + Self::This => This::NAME, + Self::Super => Super::NAME, + Self::SuperCall => SuperCall::NAME, + Self::SuperCallSpread => SuperCallSpread::NAME, + Self::SuperCallDerived => SuperCallDerived::NAME, + Self::Case => Case::NAME, + Self::Default => Default::NAME, + Self::GetFunction => GetFunction::NAME, + Self::GetFunctionAsync => GetFunctionAsync::NAME, + Self::GetGenerator => GetGenerator::NAME, + Self::GetGeneratorAsync => GetGeneratorAsync::NAME, + Self::CallEval => CallEval::NAME, + Self::CallEvalSpread => CallEvalSpread::NAME, + Self::Call => Call::NAME, + Self::CallSpread => CallSpread::NAME, + Self::New => New::NAME, + Self::NewSpread => NewSpread::NAME, + Self::Return => Return::NAME, + Self::PushDeclarativeEnvironment => PushDeclarativeEnvironment::NAME, + Self::PushFunctionEnvironment => PushFunctionEnvironment::NAME, + Self::PopEnvironment => PopEnvironment::NAME, + Self::LoopStart => LoopStart::NAME, + Self::LoopContinue => LoopContinue::NAME, + Self::LoopEnd => LoopEnd::NAME, + Self::ForInLoopInitIterator => ForInLoopInitIterator::NAME, + Self::InitIterator => InitIterator::NAME, + Self::InitIteratorAsync => InitIteratorAsync::NAME, + Self::IteratorNext => IteratorNext::NAME, + Self::IteratorClose => IteratorClose::NAME, + Self::IteratorToArray => IteratorToArray::NAME, + Self::ForInLoopNext => ForInLoopNext::NAME, + Self::ForAwaitOfLoopNext => ForAwaitOfLoopNext::NAME, + Self::ForAwaitOfLoopIterate => ForAwaitOfLoopIterate::NAME, + Self::ConcatToString => ConcatToString::NAME, + Self::RequireObjectCoercible => RequireObjectCoercible::NAME, + Self::ValueNotNullOrUndefined => ValueNotNullOrUndefined::NAME, + Self::RestParameterInit => RestParameterInit::NAME, + Self::RestParameterPop => RestParameterPop::NAME, + Self::PopOnReturnAdd => PopOnReturnAdd::NAME, + Self::PopOnReturnSub => PopOnReturnSub::NAME, + Self::Yield => Yield::NAME, + Self::GeneratorNext => GeneratorNext::NAME, + Self::AsyncGeneratorNext => AsyncGeneratorNext::NAME, + Self::Await => Await::NAME, + Self::PushNewTarget => PushNewTarget::NAME, + Self::GeneratorNextDelegate => GeneratorNextDelegate::NAME, + Self::Nop => Nop::NAME, } } /// Name of the profiler event for this opcode pub fn as_instruction_str(self) -> &'static str { match self { - Self::Pop => "INST - Pop", - Self::PopIfThrown => "INST - PopIfThrown", - Self::Dup => "INST - Dup", - Self::Swap => "INST - Swap", - Self::PushZero => "INST - PushZero", - Self::PushOne => "INST - PushOne", - Self::PushInt8 => "INST - PushInt8", - Self::PushInt16 => "INST - PushInt16", - Self::PushInt32 => "INST - PushInt32", - Self::PushRational => "INST - PushRational", - Self::PushNaN => "INST - PushNaN", - Self::PushPositiveInfinity => "INST - PushPositiveInfinity", - Self::PushNegativeInfinity => "INST - PushNegativeInfinity", - Self::PushNull => "INST - PushNull", - Self::PushTrue => "INST - PushTrue", - Self::PushFalse => "INST - PushFalse", - Self::PushUndefined => "INST - PushUndefined", - Self::PushLiteral => "INST - PushLiteral", - Self::PushEmptyObject => "INST - PushEmptyObject", - Self::PushNewArray => "INST - PushNewArray", - Self::PushValueToArray => "INST - PushValueToArray", - Self::PushElisionToArray => "INST - PushElisionToArray", - Self::PushIteratorToArray => "INST - PushIteratorToArray", - Self::Add => "INST - Add", - Self::Sub => "INST - Sub", - Self::Div => "INST - Div", - Self::Mul => "INST - Mul", - Self::Mod => "INST - Mod", - Self::Pow => "INST - Pow", - Self::ShiftRight => "INST - ShiftRight", - Self::ShiftLeft => "INST - ShiftLeft", - Self::UnsignedShiftRight => "INST - UnsignedShiftRight", - Self::BitOr => "INST - BitOr", - Self::BitAnd => "INST - BitAnd", - Self::BitXor => "INST - BitXor", - Self::BitNot => "INST - BitNot", - Self::In => "INST - In", - Self::Eq => "INST - Eq", - Self::StrictEq => "INST - StrictEq", - Self::NotEq => "INST - NotEq", - Self::StrictNotEq => "INST - StrictNotEq", - Self::GreaterThan => "INST - GreaterThan", - Self::GreaterThanOrEq => "INST - GreaterThanOrEq", - Self::LessThan => "INST - LessThan", - Self::LessThanOrEq => "INST - LessThanOrEq", - Self::InstanceOf => "INST - InstanceOf", - Self::TypeOf => "INST - TypeOf", - Self::Void => "INST - Void", - Self::LogicalNot => "INST - LogicalNot", - Self::LogicalAnd => "INST - LogicalAnd", - Self::LogicalOr => "INST - LogicalOr", - Self::Coalesce => "INST - Coalesce", - Self::Pos => "INST - Pos", - Self::Neg => "INST - Neg", - Self::Inc => "INST - Inc", - Self::IncPost => "INST - IncPost", - Self::Dec => "INST - Dec", - Self::DecPost => "INST - DecPost", - Self::DefInitArg => "INST - DefInitArg", - Self::DefVar => "INST - DefVar", - Self::DefInitVar => "INST - DefInitVar", - Self::DefLet => "INST - DefLet", - Self::DefInitLet => "INST - DefInitLet", - Self::DefInitConst => "INST - DefInitConst", - Self::GetName => "INST - GetName", - Self::GetNameOrUndefined => "INST - GetNameOrUndefined", - Self::SetName => "INST - SetName", - Self::GetPropertyByName => "INST - GetPropertyByName", - Self::GetPropertyByValue => "INST - GetPropertyByValue", - Self::GetPropertyByValuePush => "INST - GetPropertyByValuePush", - Self::SetPropertyByName => "INST - SetPropertyByName", - Self::DefineOwnPropertyByName => "INST - DefineOwnPropertyByName", - Self::SetPropertyByValue => "INST - SetPropertyByValue", - Self::DefineOwnPropertyByValue => "INST - DefineOwnPropertyByValue", - Self::SetPropertyGetterByName => "INST - SetPropertyGetterByName", - Self::SetPropertyGetterByValue => "INST - SetPropertyGetterByValue", - Self::SetPropertySetterByName => "INST - SetPropertySetterByName", - Self::SetPropertySetterByValue => "INST - SetPropertySetterByValue", - Self::DeletePropertyByName => "INST - DeletePropertyByName", - Self::DeletePropertyByValue => "INST - DeletePropertyByValue", - Self::CopyDataProperties => "INST - CopyDataProperties", - Self::Jump => "INST - Jump", - Self::JumpIfFalse => "INST - JumpIfFalse", - Self::JumpIfNotUndefined => "INST - JumpIfNotUndefined", - Self::Throw => "INST - Throw", - Self::TryStart => "INST - TryStart", - Self::TryEnd => "INST - TryEnd", - Self::CatchStart => "INST - CatchStart", - Self::CatchEnd => "INST - CatchEnd", - Self::CatchEnd2 => "INST - CatchEnd2", - Self::FinallyStart => "INST - FinallyStart", - Self::FinallyEnd => "INST - FinallyEnd", - Self::FinallySetJump => "INST - FinallySetJump", - Self::ToBoolean => "INST - ToBoolean", - Self::This => "INST - This", - Self::Super => "INST - Super", - Self::SuperCall => "INST - SuperCall", - Self::SuperCallSpread => "INST - SuperCallWithRest", - Self::SuperCallDerived => "INST - SuperCallDerived", - Self::Case => "INST - Case", - Self::Default => "INST - Default", - Self::GetFunction => "INST - GetFunction", - Self::GetFunctionAsync => "INST - GetFunctionAsync", - Self::GetGenerator => "INST - GetGenerator", - Self::GetGeneratorAsync => "INST - GetGeneratorAsync", - Self::CallEval => "INST - CallEval", - Self::CallEvalSpread => "INST - CallEvalSpread", - Self::Call => "INST - Call", - Self::CallSpread => "INST - CallSpread", - Self::New => "INST - New", - Self::NewSpread => "INST - NewSpread", - Self::Return => "INST - Return", - Self::PushDeclarativeEnvironment => "INST - PushDeclarativeEnvironment", - Self::PushFunctionEnvironment => "INST - PushFunctionEnvironment", - Self::PopEnvironment => "INST - PopEnvironment", - Self::LoopStart => "INST - LoopStart", - Self::LoopContinue => "INST - LoopContinue", - Self::LoopEnd => "INST - LoopEnd", - Self::ForInLoopInitIterator => "INST - ForInLoopInitIterator", - Self::InitIterator => "INST - InitIterator", - Self::InitIteratorAsync => "INST - InitIteratorAsync", - Self::IteratorNext => "INST - IteratorNext", - Self::IteratorClose => "INST - IteratorClose", - Self::IteratorToArray => "INST - IteratorToArray", - Self::ForInLoopNext => "INST - ForInLoopNext", - Self::ForAwaitOfLoopIterate => "INST - ForAwaitOfLoopIterate", - Self::ForAwaitOfLoopNext => "INST - ForAwaitOfLoopNext", - Self::ConcatToString => "INST - ConcatToString", - Self::RequireObjectCoercible => "INST - RequireObjectCoercible", - Self::ValueNotNullOrUndefined => "INST - ValueNotNullOrUndefined", - Self::RestParameterInit => "INST - FunctionRestParameter", - Self::RestParameterPop => "INST - RestParameterPop", - Self::PopOnReturnAdd => "INST - PopOnReturnAdd", - Self::PopOnReturnSub => "INST - PopOnReturnSub", - Self::Yield => "INST - Yield", - Self::GeneratorNext => "INST - GeneratorNext", - Self::AsyncGeneratorNext => "INST - AsyncGeneratorNext", - Self::PushNewTarget => "INST - PushNewTarget", - Self::Await => "INST - Await", - Self::GeneratorNextDelegate => "INST - GeneratorNextDelegate", - Self::Nop => "INST - Nop", - Self::PushClassPrototype => "INST - PushClassPrototype", - Self::SetClassPrototype => "INST - SetClassPrototype", - Self::SetHomeObject => "INST - SetHomeObject", - Self::DefineClassMethodByName => "INST - DefineClassMethodByName", - Self::DefineClassMethodByValue => "INST - DefineClassMethodByValue", - Self::DefineClassGetterByName => "INST - DefineClassGetterByName", - Self::DefineClassGetterByValue => "INST - DefineClassGetterByValue", - Self::DefineClassSetterByName => "INST - DefineClassSetterByName", - Self::DefineClassSetterByValue => "INST - DefineClassSetterByValue", - Self::AssignPrivateField => "INST - AssignPrivateField", - Self::SetPrivateField => "INST - SetPrivateValue", - Self::SetPrivateMethod => "INST - SetPrivateMethod", - Self::SetPrivateSetter => "INST - SetPrivateSetter", - Self::SetPrivateGetter => "INST - SetPrivateGetter", - Self::GetPrivateField => "INST - GetPrivateField", - Self::PushClassField => "INST - PushClassField", - Self::PushClassFieldPrivate => "INST - PushClassFieldPrivate", - Self::PushClassPrivateGetter => "INST - PushClassPrivateGetter", - Self::PushClassPrivateSetter => "INST - PushClassPrivateSetter", - Self::PushClassPrivateMethod => "INST - PushClassPrivateMethod", - Self::ToPropertyKey => "INST - ToPropertyKey", + Self::Pop => Pop::INSTRUCTION, + Self::PopIfThrown => PopIfThrown::INSTRUCTION, + Self::Dup => Dup::INSTRUCTION, + Self::Swap => Swap::INSTRUCTION, + Self::PushZero => PushZero::INSTRUCTION, + Self::PushOne => PushOne::INSTRUCTION, + Self::PushInt8 => PushInt8::INSTRUCTION, + Self::PushInt16 => PushInt16::INSTRUCTION, + Self::PushInt32 => PushInt32::INSTRUCTION, + Self::PushRational => PushRational::INSTRUCTION, + Self::PushNaN => PushNaN::INSTRUCTION, + Self::PushPositiveInfinity => PushPositiveInfinity::INSTRUCTION, + Self::PushNegativeInfinity => PushNegativeInfinity::INSTRUCTION, + Self::PushNull => PushNull::INSTRUCTION, + Self::PushTrue => PushTrue::INSTRUCTION, + Self::PushFalse => PushFalse::INSTRUCTION, + Self::PushUndefined => PushUndefined::INSTRUCTION, + Self::PushLiteral => PushLiteral::INSTRUCTION, + Self::PushEmptyObject => PushEmptyObject::INSTRUCTION, + Self::PushNewArray => PushNewArray::INSTRUCTION, + Self::PushValueToArray => PushValueToArray::INSTRUCTION, + Self::PushElisionToArray => PushElisionToArray::INSTRUCTION, + Self::PushIteratorToArray => PushIteratorToArray::INSTRUCTION, + Self::Add => Add::INSTRUCTION, + Self::Sub => Sub::INSTRUCTION, + Self::Div => Div::INSTRUCTION, + Self::Mul => Mul::INSTRUCTION, + Self::Mod => Mod::INSTRUCTION, + Self::Pow => Pow::INSTRUCTION, + Self::ShiftRight => ShiftRight::INSTRUCTION, + Self::ShiftLeft => ShiftLeft::INSTRUCTION, + Self::UnsignedShiftRight => UnsignedShiftRight::INSTRUCTION, + Self::BitOr => BitOr::INSTRUCTION, + Self::BitAnd => BitAnd::INSTRUCTION, + Self::BitXor => BitXor::INSTRUCTION, + Self::BitNot => BitNot::INSTRUCTION, + Self::In => In::INSTRUCTION, + Self::Eq => Eq::INSTRUCTION, + Self::StrictEq => StrictEq::INSTRUCTION, + Self::NotEq => NotEq::INSTRUCTION, + Self::StrictNotEq => StrictNotEq::INSTRUCTION, + Self::GreaterThan => GreaterThan::INSTRUCTION, + Self::GreaterThanOrEq => GreaterThanOrEq::INSTRUCTION, + Self::LessThan => LessThan::INSTRUCTION, + Self::LessThanOrEq => LessThanOrEq::INSTRUCTION, + Self::InstanceOf => InstanceOf::INSTRUCTION, + Self::TypeOf => TypeOf::INSTRUCTION, + Self::Void => Void::INSTRUCTION, + Self::LogicalNot => LogicalNot::INSTRUCTION, + Self::LogicalAnd => LogicalAnd::INSTRUCTION, + Self::LogicalOr => LogicalOr::INSTRUCTION, + Self::Coalesce => Coalesce::INSTRUCTION, + Self::Pos => Pos::INSTRUCTION, + Self::Neg => Neg::INSTRUCTION, + Self::Inc => Inc::INSTRUCTION, + Self::IncPost => IncPost::INSTRUCTION, + Self::Dec => Dec::INSTRUCTION, + Self::DecPost => DecPost::INSTRUCTION, + Self::DefInitArg => DefInitArg::INSTRUCTION, + Self::DefVar => DefVar::INSTRUCTION, + Self::DefInitVar => DefInitVar::INSTRUCTION, + Self::DefLet => DefLet::INSTRUCTION, + Self::DefInitLet => DefInitLet::INSTRUCTION, + Self::DefInitConst => DefInitConst::INSTRUCTION, + Self::GetName => GetName::INSTRUCTION, + Self::GetNameOrUndefined => GetNameOrUndefined::INSTRUCTION, + Self::SetName => SetName::INSTRUCTION, + Self::GetPropertyByName => GetPropertyByName::INSTRUCTION, + Self::GetPropertyByValue => GetPropertyByValue::INSTRUCTION, + Self::GetPropertyByValuePush => GetPropertyByValuePush::INSTRUCTION, + Self::SetPropertyByName => SetPropertyByName::INSTRUCTION, + Self::DefineOwnPropertyByName => DefineOwnPropertyByName::INSTRUCTION, + Self::SetPropertyByValue => SetPropertyByValue::INSTRUCTION, + Self::DefineOwnPropertyByValue => DefineOwnPropertyByValue::INSTRUCTION, + Self::SetPropertyGetterByName => SetPropertyGetterByName::INSTRUCTION, + Self::SetPropertyGetterByValue => SetPropertyGetterByValue::INSTRUCTION, + Self::SetPropertySetterByName => SetPropertySetterByName::INSTRUCTION, + Self::SetPropertySetterByValue => SetPropertySetterByValue::INSTRUCTION, + Self::DeletePropertyByName => DeletePropertyByName::INSTRUCTION, + Self::DeletePropertyByValue => DeletePropertyByValue::INSTRUCTION, + Self::CopyDataProperties => CopyDataProperties::INSTRUCTION, + Self::Jump => Jump::INSTRUCTION, + Self::JumpIfFalse => JumpIfFalse::INSTRUCTION, + Self::JumpIfNotUndefined => JumpIfNotUndefined::INSTRUCTION, + Self::Throw => Throw::INSTRUCTION, + Self::TryStart => TryStart::INSTRUCTION, + Self::TryEnd => TryEnd::INSTRUCTION, + Self::CatchStart => CatchStart::INSTRUCTION, + Self::CatchEnd => CatchEnd::INSTRUCTION, + Self::CatchEnd2 => CatchEnd2::INSTRUCTION, + Self::FinallyStart => FinallyStart::INSTRUCTION, + Self::FinallyEnd => FinallyEnd::INSTRUCTION, + Self::FinallySetJump => FinallySetJump::INSTRUCTION, + Self::ToBoolean => ToBoolean::INSTRUCTION, + Self::This => This::INSTRUCTION, + Self::Super => Super::INSTRUCTION, + Self::SuperCall => SuperCall::INSTRUCTION, + Self::SuperCallSpread => SuperCallSpread::INSTRUCTION, + Self::SuperCallDerived => SuperCallDerived::INSTRUCTION, + Self::Case => Case::INSTRUCTION, + Self::Default => Default::INSTRUCTION, + Self::GetFunction => GetFunction::INSTRUCTION, + Self::GetFunctionAsync => GetFunctionAsync::INSTRUCTION, + Self::GetGenerator => GetGenerator::INSTRUCTION, + Self::GetGeneratorAsync => GetGeneratorAsync::INSTRUCTION, + Self::CallEval => CallEval::INSTRUCTION, + Self::CallEvalSpread => CallEvalSpread::INSTRUCTION, + Self::Call => Call::INSTRUCTION, + Self::CallSpread => CallSpread::INSTRUCTION, + Self::New => New::INSTRUCTION, + Self::NewSpread => NewSpread::INSTRUCTION, + Self::Return => Return::INSTRUCTION, + Self::PushDeclarativeEnvironment => PushDeclarativeEnvironment::INSTRUCTION, + Self::PushFunctionEnvironment => PushFunctionEnvironment::INSTRUCTION, + Self::PopEnvironment => PopEnvironment::INSTRUCTION, + Self::LoopStart => LoopStart::INSTRUCTION, + Self::LoopContinue => LoopContinue::INSTRUCTION, + Self::LoopEnd => LoopEnd::INSTRUCTION, + Self::ForInLoopInitIterator => ForInLoopInitIterator::INSTRUCTION, + Self::InitIterator => InitIterator::INSTRUCTION, + Self::InitIteratorAsync => InitIteratorAsync::INSTRUCTION, + Self::IteratorNext => IteratorNext::INSTRUCTION, + Self::IteratorClose => IteratorClose::INSTRUCTION, + Self::IteratorToArray => IteratorToArray::INSTRUCTION, + Self::ForInLoopNext => ForInLoopNext::INSTRUCTION, + Self::ForAwaitOfLoopIterate => ForAwaitOfLoopIterate::INSTRUCTION, + Self::ForAwaitOfLoopNext => ForAwaitOfLoopNext::INSTRUCTION, + Self::ConcatToString => ConcatToString::INSTRUCTION, + Self::RequireObjectCoercible => RequireObjectCoercible::INSTRUCTION, + Self::ValueNotNullOrUndefined => ValueNotNullOrUndefined::INSTRUCTION, + Self::RestParameterInit => RestParameterInit::INSTRUCTION, + Self::RestParameterPop => RestParameterPop::INSTRUCTION, + Self::PopOnReturnAdd => PopOnReturnAdd::INSTRUCTION, + Self::PopOnReturnSub => PopOnReturnSub::INSTRUCTION, + Self::Yield => Yield::INSTRUCTION, + Self::GeneratorNext => GeneratorNext::INSTRUCTION, + Self::AsyncGeneratorNext => AsyncGeneratorNext::INSTRUCTION, + Self::PushNewTarget => PushNewTarget::INSTRUCTION, + Self::Await => Await::INSTRUCTION, + Self::GeneratorNextDelegate => GeneratorNextDelegate::INSTRUCTION, + Self::Nop => Nop::INSTRUCTION, + Self::PushClassPrototype => PushClassPrototype::INSTRUCTION, + Self::SetClassPrototype => SetClassPrototype::INSTRUCTION, + Self::SetHomeObject => SetHomeObject::INSTRUCTION, + Self::DefineClassMethodByName => DefineClassMethodByName::INSTRUCTION, + Self::DefineClassMethodByValue => DefineClassMethodByValue::INSTRUCTION, + Self::DefineClassGetterByName => DefineClassGetterByName::INSTRUCTION, + Self::DefineClassGetterByValue => DefineClassGetterByValue::INSTRUCTION, + Self::DefineClassSetterByName => DefineClassSetterByName::INSTRUCTION, + Self::DefineClassSetterByValue => DefineClassSetterByValue::INSTRUCTION, + Self::AssignPrivateField => AssignPrivateField::INSTRUCTION, + Self::SetPrivateField => SetPrivateField::INSTRUCTION, + Self::SetPrivateMethod => SetPrivateMethod::INSTRUCTION, + Self::SetPrivateSetter => SetPrivateSetter::INSTRUCTION, + Self::SetPrivateGetter => SetPrivateGetter::INSTRUCTION, + Self::GetPrivateField => GetPrivateField::INSTRUCTION, + Self::PushClassField => PushClassField::INSTRUCTION, + Self::PushClassFieldPrivate => PushClassFieldPrivate::INSTRUCTION, + Self::PushClassPrivateGetter => PushClassPrivateGetter::INSTRUCTION, + Self::PushClassPrivateSetter => PushClassPrivateSetter::INSTRUCTION, + Self::PushClassPrivateMethod => PushClassPrivateMethod::INSTRUCTION, + Self::ToPropertyKey => ToPropertyKey::INSTRUCTION, } } } diff --git a/boa_engine/src/vm/opcode/new/mod.rs b/boa_engine/src/vm/opcode/new/mod.rs new file mode 100644 index 0000000000..65aa75ae6c --- /dev/null +++ b/boa_engine/src/vm/opcode/new/mod.rs @@ -0,0 +1,89 @@ +use crate::{ + error::JsNativeError, + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, +}; + +/// `New` implements the Opcode Operation for `Opcode::New` +/// +/// Operation: +/// - Call construct on a function. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct New; + +impl Operation for New { + const NAME: &'static str = "New"; + const INSTRUCTION: &'static str = "INST - New"; + + fn execute(context: &mut Context) -> JsResult { + if context.vm.stack_size_limit <= context.vm.stack.len() { + return Err(JsNativeError::range() + .with_message("Maximum call stack size exceeded") + .into()); + } + let argument_count = context.vm.read::(); + let mut arguments = Vec::with_capacity(argument_count as usize); + for _ in 0..argument_count { + arguments.push(context.vm.pop()); + } + arguments.reverse(); + let func = context.vm.pop(); + + let result = func + .as_constructor() + .ok_or_else(|| { + JsNativeError::typ() + .with_message("not a constructor") + .into() + }) + .and_then(|cons| cons.__construct__(&arguments, cons, context))?; + + context.vm.push(result); + Ok(ShouldExit::False) + } +} + +/// `NewSpread` implements the Opcode Operation for `Opcode::NewSpread` +/// +/// Operation: +/// - Call construct on a function where the arguments contain spreads. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct NewSpread; + +impl Operation for NewSpread { + const NAME: &'static str = "NewSpread"; + const INSTRUCTION: &'static str = "INST - NewSpread"; + + fn execute(context: &mut Context) -> JsResult { + if context.vm.stack_size_limit <= context.vm.stack.len() { + return Err(JsNativeError::range() + .with_message("Maximum call stack size exceeded") + .into()); + } + // Get the arguments that are stored as an array object on the stack. + let arguments_array = context.vm.pop(); + let arguments_array_object = arguments_array + .as_object() + .expect("arguments array in call spread function must be an object"); + let arguments = arguments_array_object + .borrow() + .properties() + .dense_indexed_properties() + .expect("arguments array in call spread function must be dense") + .clone(); + + let func = context.vm.pop(); + + let result = func + .as_constructor() + .ok_or_else(|| { + JsNativeError::typ() + .with_message("not a constructor") + .into() + }) + .and_then(|cons| cons.__construct__(&arguments, cons, context))?; + + context.vm.push(result); + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/nop/mod.rs b/boa_engine/src/vm/opcode/nop/mod.rs new file mode 100644 index 0000000000..0ed775f2c5 --- /dev/null +++ b/boa_engine/src/vm/opcode/nop/mod.rs @@ -0,0 +1,20 @@ +use crate::{ + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, +}; + +/// `Nop` implements the Opcode Operation for `Opcode::Nop` +/// +/// Operation: +/// - No-operation instruction, does nothing +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct Nop; + +impl Operation for Nop { + const NAME: &'static str = "Nop"; + const INSTRUCTION: &'static str = "INST - Nop"; + + fn execute(_context: &mut Context) -> JsResult { + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/pop/mod.rs b/boa_engine/src/vm/opcode/pop/mod.rs new file mode 100644 index 0000000000..ce27618898 --- /dev/null +++ b/boa_engine/src/vm/opcode/pop/mod.rs @@ -0,0 +1,95 @@ +use crate::{ + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, +}; + +/// `Pop` implements the Opcode Operation for `Opcode::Pop` +/// +/// Operation: +/// - Pop the top value from the stack. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct Pop; + +impl Operation for Pop { + const NAME: &'static str = "Pop"; + const INSTRUCTION: &'static str = "INST - Pop"; + + fn execute(context: &mut Context) -> JsResult { + let _val = context.vm.pop(); + Ok(ShouldExit::False) + } +} + +/// `PopIfThrown` implements the Opcode Operation for `Opcode::PopIfThrown` +/// +/// Operation: +/// - Pop the top value from the stack if the last try block has thrown a value. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct PopIfThrown; + +impl Operation for PopIfThrown { + const NAME: &'static str = "PopIfThrown"; + const INSTRUCTION: &'static str = "INST - PopIfThrown"; + + fn execute(context: &mut Context) -> JsResult { + let frame = context.vm.frame_mut(); + if frame.thrown { + frame.thrown = false; + context.vm.pop(); + } + Ok(ShouldExit::False) + } +} + +/// `PopEnvironment` implements the Opcode Operation for `Opcode::PopEnvironment` +/// +/// Operation: +/// - Pop the current environment. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct PopEnvironment; + +impl Operation for PopEnvironment { + const NAME: &'static str = "PopEnvironment"; + const INSTRUCTION: &'static str = "INST - PopEnvironment"; + + fn execute(context: &mut Context) -> JsResult { + context.realm.environments.pop(); + context.vm.frame_mut().loop_env_stack_dec(); + context.vm.frame_mut().try_env_stack_dec(); + Ok(ShouldExit::False) + } +} + +/// `PopReturnAdd` implements the Opcode Operation for `Opcode::PopReturnAdd` +/// +/// Operation: +/// - Add one to the pop on return count. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct PopOnReturnAdd; + +impl Operation for PopOnReturnAdd { + const NAME: &'static str = "PopOnReturnAdd"; + const INSTRUCTION: &'static str = "INST - PopOnReturnAdd"; + + fn execute(context: &mut Context) -> JsResult { + context.vm.frame_mut().pop_on_return += 1; + Ok(ShouldExit::False) + } +} + +/// `PopOnReturnSub` implements the Opcode Operation for `Opcode::PopOnReturnSub` +/// +/// Operation: +/// - Subtract one from the pop on return count. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct PopOnReturnSub; + +impl Operation for PopOnReturnSub { + const NAME: &'static str = "PopOnReturnSub"; + const INSTRUCTION: &'static str = "INST - PopOnReturnSub"; + + fn execute(context: &mut Context) -> JsResult { + context.vm.frame_mut().pop_on_return -= 1; + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/promise/mod.rs b/boa_engine/src/vm/opcode/promise/mod.rs new file mode 100644 index 0000000000..1f3f134266 --- /dev/null +++ b/boa_engine/src/vm/opcode/promise/mod.rs @@ -0,0 +1,80 @@ +use crate::{ + vm::{opcode::Operation, FinallyReturn, ShouldExit}, + Context, JsError, JsResult, +}; + +/// `FinallyStart` implements the Opcode Operation for `Opcode::FinallyStart` +/// +/// Operation: +/// - Start of a finally block. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct FinallyStart; + +impl Operation for FinallyStart { + const NAME: &'static str = "FinallyStart"; + const INSTRUCTION: &'static str = "INST - FinallyStart"; + + fn execute(context: &mut Context) -> JsResult { + *context + .vm + .frame_mut() + .finally_jump + .last_mut() + .expect("finally jump must exist here") = None; + Ok(ShouldExit::False) + } +} + +/// `FinallyEnd` implements the Opcode Operation for `Opcode::FinallyEnd` +/// +/// Operation: +/// - End of a finally block. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct FinallyEnd; + +impl Operation for FinallyEnd { + const NAME: &'static str = "FinallyEnd"; + const INSTRUCTION: &'static str = "INST - FinallyEnd"; + + fn execute(context: &mut Context) -> JsResult { + let address = context + .vm + .frame_mut() + .finally_jump + .pop() + .expect("finally jump must exist here"); + match context.vm.frame_mut().finally_return { + FinallyReturn::None => { + if let Some(address) = address { + context.vm.frame_mut().pc = address as usize; + } + Ok(ShouldExit::False) + } + FinallyReturn::Ok => Ok(ShouldExit::True), + FinallyReturn::Err => Err(JsError::from_opaque(context.vm.pop())), + } + } +} + +/// `FinallySetJump` implements the Opcode Operation for `Opcode::FinallySetJump` +/// +/// Operation: +/// - Set the address for a finally jump. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct FinallySetJump; + +impl Operation for FinallySetJump { + const NAME: &'static str = "FinallySetJump"; + const INSTRUCTION: &'static str = "INST - FinallySetJump"; + + fn execute(context: &mut Context) -> JsResult { + let address = context.vm.read::(); + *context + .vm + .frame_mut() + .finally_jump + .last_mut() + .expect("finally jump must exist here") = Some(address); + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/push/array.rs b/boa_engine/src/vm/opcode/push/array.rs new file mode 100644 index 0000000000..56e5065ff2 --- /dev/null +++ b/boa_engine/src/vm/opcode/push/array.rs @@ -0,0 +1,106 @@ +use crate::{ + builtins::{iterable::IteratorRecord, Array}, + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, +}; + +/// `PushNewArray` implements the Opcode Operation for `Opcode::PushNewArray` +/// +/// Operation: +/// - Push an empty array value on the stack. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct PushNewArray; + +impl Operation for PushNewArray { + const NAME: &'static str = "PushNewArray"; + const INSTRUCTION: &'static str = "INST - PushNewArray"; + + fn execute(context: &mut Context) -> JsResult { + let array = Array::array_create(0, None, context) + .expect("Array creation with 0 length should never fail"); + context.vm.push(array); + Ok(ShouldExit::False) + } +} + +/// `PushValueToArray` implements the Opcode Operation for `Opcode::PushValueToArray` +/// +/// Operation: +/// - Push a value to an array. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct PushValueToArray; + +impl Operation for PushValueToArray { + const NAME: &'static str = "PushValueToArray"; + const INSTRUCTION: &'static str = "INST - PushValueToArray"; + + fn execute(context: &mut Context) -> JsResult { + let value = context.vm.pop(); + let array = context.vm.pop(); + let o = array.as_object().expect("should be an object"); + let len = o + .length_of_array_like(context) + .expect("should have 'length' property"); + o.create_data_property_or_throw(len, value, context) + .expect("should be able to create new data property"); + context.vm.push(array); + Ok(ShouldExit::False) + } +} + +/// `PushEllisionToArray` implements the Opcode Operation for `Opcode::PushEllisionToArray` +/// +/// Operation: +/// - Push an empty element/hole to an array. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct PushElisionToArray; + +impl Operation for PushElisionToArray { + const NAME: &'static str = "PushElisionToArray"; + const INSTRUCTION: &'static str = "INST - PushElisionToArray"; + + fn execute(context: &mut Context) -> JsResult { + let array = context.vm.pop(); + let o = array.as_object().expect("should always be an object"); + + let len = o + .length_of_array_like(context) + .expect("arrays should always have a 'length' property"); + + o.set("length", len + 1, true, context)?; + context.vm.push(array); + Ok(ShouldExit::False) + } +} + +/// `PushIteratorToArray` implements the Opcode Operation for `Opcode::PushIteratorToArray` +/// +/// Operation: +/// - Push all iterator values to an array. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct PushIteratorToArray; + +impl Operation for PushIteratorToArray { + const NAME: &'static str = "PushIteratorToArray"; + const INSTRUCTION: &'static str = "INST - PushIteratorToArray"; + + fn execute(context: &mut Context) -> JsResult { + let done = context + .vm + .pop() + .as_boolean() + .expect("iterator [[Done]] was not a boolean"); + let next_method = context.vm.pop(); + let iterator = context.vm.pop(); + let iterator = iterator.as_object().expect("iterator was not an object"); + let array = context.vm.pop(); + + let iterator = IteratorRecord::new(iterator.clone(), next_method, done); + while let Some(next) = iterator.step(context)? { + Array::push(&array, &[next.value(context)?], context)?; + } + + context.vm.push(array); + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/push/class/field.rs b/boa_engine/src/vm/opcode/push/class/field.rs new file mode 100644 index 0000000000..210244dd9e --- /dev/null +++ b/boa_engine/src/vm/opcode/push/class/field.rs @@ -0,0 +1,85 @@ +use crate::{ + object::JsFunction, + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, +}; + +/// `PushClassField` implements the Opcode Operation for `Opcode::PushClassField` +/// +/// Operation: +/// - Push a field to a class. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct PushClassField; + +impl Operation for PushClassField { + const NAME: &'static str = "PushClassField"; + const INSTRUCTION: &'static str = "INST - PushClassField"; + + fn execute(context: &mut Context) -> JsResult { + let field_function_value = context.vm.pop(); + let field_name_value = context.vm.pop(); + let class_value = context.vm.pop(); + + let field_name_key = field_name_value.to_property_key(context)?; + let field_function_object = field_function_value + .as_object() + .expect("field value must be function object"); + let mut field_function_object_borrow = field_function_object.borrow_mut(); + let field_function = field_function_object_borrow + .as_function_mut() + .expect("field value must be function object"); + let class_object = class_value + .as_object() + .expect("class must be function object"); + field_function.set_home_object(class_object.clone()); + class_object + .borrow_mut() + .as_function_mut() + .expect("class must be function object") + .push_field( + field_name_key, + JsFunction::from_object_unchecked(field_function_object.clone()), + ); + Ok(ShouldExit::False) + } +} + +/// `PushClassFieldPrivate` implements the Opcode Operation for `Opcode::PushClassFieldPrivate` +/// +/// Operation: +/// - Push a private field to the class. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct PushClassFieldPrivate; + +impl Operation for PushClassFieldPrivate { + const NAME: &'static str = "PushClassFieldPrivate"; + const INSTRUCTION: &'static str = "INST - PushClassFieldPrivate"; + + fn execute(context: &mut Context) -> JsResult { + let index = context.vm.read::(); + let name = context.vm.frame().code.names[index as usize]; + let field_function_value = context.vm.pop(); + let class_value = context.vm.pop(); + + let field_function_object = field_function_value + .as_object() + .expect("field value must be function object"); + let mut field_function_object_borrow = field_function_object.borrow_mut(); + let field_function = field_function_object_borrow + .as_function_mut() + .expect("field value must be function object"); + let class_object = class_value + .as_object() + .expect("class must be function object"); + field_function.set_home_object(class_object.clone()); + class_object + .borrow_mut() + .as_function_mut() + .expect("class must be function object") + .push_field_private( + name.sym(), + JsFunction::from_object_unchecked(field_function_object.clone()), + ); + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/push/class/mod.rs b/boa_engine/src/vm/opcode/push/class/mod.rs new file mode 100644 index 0000000000..e76cbef32c --- /dev/null +++ b/boa_engine/src/vm/opcode/push/class/mod.rs @@ -0,0 +1,65 @@ +use crate::{ + builtins::function::{ConstructorKind, Function}, + error::JsNativeError, + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, JsValue, +}; + +pub(crate) mod field; +pub(crate) mod private; + +pub(crate) use field::*; +pub(crate) use private::*; + +/// `PushClassPrototype` implements the Opcode Operation for `Opcode::PushClassPrototype` +/// +/// Operation: +/// - Get the prototype of a superclass and push it on the stack. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct PushClassPrototype; + +impl Operation for PushClassPrototype { + const NAME: &'static str = "PushClassPrototype"; + const INSTRUCTION: &'static str = "INST - PushClassPrototype"; + + fn execute(context: &mut Context) -> JsResult { + let superclass = context.vm.pop(); + + if let Some(superclass) = superclass.as_constructor() { + let proto = superclass.get("prototype", context)?; + if !proto.is_object() && !proto.is_null() { + return Err(JsNativeError::typ() + .with_message("superclass prototype must be an object or null") + .into()); + } + + let class = context.vm.pop(); + { + let class_object = class.as_object().expect("class must be object"); + class_object.set_prototype(Some(superclass.clone())); + + let mut class_object_mut = class_object.borrow_mut(); + let class_function = class_object_mut + .as_function_mut() + .expect("class must be function object"); + if let Function::Ordinary { + constructor_kind, .. + } = class_function + { + *constructor_kind = ConstructorKind::Derived; + } + } + + context.vm.push(class); + context.vm.push(proto); + Ok(ShouldExit::False) + } else if superclass.is_null() { + context.vm.push(JsValue::Null); + Ok(ShouldExit::False) + } else { + Err(JsNativeError::typ() + .with_message("superclass must be a constructor") + .into()) + } + } +} diff --git a/boa_engine/src/vm/opcode/push/class/private.rs b/boa_engine/src/vm/opcode/push/class/private.rs new file mode 100644 index 0000000000..4d77b4e178 --- /dev/null +++ b/boa_engine/src/vm/opcode/push/class/private.rs @@ -0,0 +1,101 @@ +use crate::{ + object::PrivateElement, + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, +}; + +/// `PushClassPrivateMethod` implements the Opcode Operation for `Opcode::PushClassPrivateMethod` +/// +/// Operation: +/// - Push a private method to the class. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct PushClassPrivateMethod; + +impl Operation for PushClassPrivateMethod { + const NAME: &'static str = "PushClassPrivateMethod"; + const INSTRUCTION: &'static str = "INST - PushClassPrivateMethod"; + + fn execute(context: &mut Context) -> JsResult { + let index = context.vm.read::(); + let name = context.vm.frame().code.names[index as usize]; + let method = context.vm.pop(); + let method_object = method.as_callable().expect("method must be callable"); + let class = context.vm.pop(); + class + .as_object() + .expect("class must be function object") + .borrow_mut() + .as_function_mut() + .expect("class must be function object") + .push_private_method(name.sym(), PrivateElement::Method(method_object.clone())); + Ok(ShouldExit::False) + } +} + +/// `PushClassPrivateGetter` implements the Opcode Operation for `Opcode::PushClassPrivateGetter` +/// +/// Operation: +/// - Push a private getter to the class. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct PushClassPrivateGetter; + +impl Operation for PushClassPrivateGetter { + const NAME: &'static str = "PushClassPrivateGetter"; + const INSTRUCTION: &'static str = "INST - PushClassPrivateGetter"; + + fn execute(context: &mut Context) -> JsResult { + let index = context.vm.read::(); + let name = context.vm.frame().code.names[index as usize]; + let getter = context.vm.pop(); + let getter_object = getter.as_callable().expect("getter must be callable"); + let class = context.vm.pop(); + class + .as_object() + .expect("class must be function object") + .borrow_mut() + .as_function_mut() + .expect("class must be function object") + .push_private_method( + name.sym(), + PrivateElement::Accessor { + getter: Some(getter_object.clone()), + setter: None, + }, + ); + Ok(ShouldExit::False) + } +} + +/// `PushClassPrivateSetter` implements the Opcode Operation for `Opcode::PushClassPrivateSetter` +/// +/// Operation: +/// - Push a private setter to the class. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct PushClassPrivateSetter; + +impl Operation for PushClassPrivateSetter { + const NAME: &'static str = "PushClassPrivateSetter"; + const INSTRUCTION: &'static str = "INST - PushClassPrivateSetter"; + + fn execute(context: &mut Context) -> JsResult { + let index = context.vm.read::(); + let name = context.vm.frame().code.names[index as usize]; + let setter = context.vm.pop(); + let setter_object = setter.as_callable().expect("getter must be callable"); + let class = context.vm.pop(); + class + .as_object() + .expect("class must be function object") + .borrow_mut() + .as_function_mut() + .expect("class must be function object") + .push_private_method( + name.sym(), + PrivateElement::Accessor { + getter: None, + setter: Some(setter_object.clone()), + }, + ); + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/push/environment.rs b/boa_engine/src/vm/opcode/push/environment.rs new file mode 100644 index 0000000000..bc0c784094 --- /dev/null +++ b/boa_engine/src/vm/opcode/push/environment.rs @@ -0,0 +1,56 @@ +use crate::{ + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, +}; + +/// `PushDeclarativeEnvironment` implements the Opcode Operation for `Opcode::PushDeclarativeEnvironment` +/// +/// Operation: +/// - Push a declarative environment +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct PushDeclarativeEnvironment; + +impl Operation for PushDeclarativeEnvironment { + const NAME: &'static str = "PushDeclarativeEnvironment"; + const INSTRUCTION: &'static str = "INST - PushDeclarativeEnvironment"; + + fn execute(context: &mut Context) -> JsResult { + let num_bindings = context.vm.read::(); + let compile_environments_index = context.vm.read::(); + let compile_environment = context.vm.frame().code.compile_environments + [compile_environments_index as usize] + .clone(); + context + .realm + .environments + .push_declarative(num_bindings as usize, compile_environment); + context.vm.frame_mut().loop_env_stack_inc(); + context.vm.frame_mut().try_env_stack_inc(); + Ok(ShouldExit::False) + } +} + +/// `PushFunctionEnvironment` implements the Opcode Operation for `Opcode::PushFunctionEnvironment` +/// +/// Operation: +/// - Push a function environment. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct PushFunctionEnvironment; + +impl Operation for PushFunctionEnvironment { + const NAME: &'static str = "PushFunctionEnvironment"; + const INSTRUCTION: &'static str = "INST - PushFunctionEnvironment"; + + fn execute(context: &mut Context) -> JsResult { + let num_bindings = context.vm.read::(); + let compile_environments_index = context.vm.read::(); + let compile_environment = context.vm.frame().code.compile_environments + [compile_environments_index as usize] + .clone(); + context + .realm + .environments + .push_function_inherit(num_bindings as usize, compile_environment); + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/push/literal.rs b/boa_engine/src/vm/opcode/push/literal.rs new file mode 100644 index 0000000000..d0cc937564 --- /dev/null +++ b/boa_engine/src/vm/opcode/push/literal.rs @@ -0,0 +1,23 @@ +use crate::{ + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, +}; + +/// `PushLiteral` implements the Opcode Operation for `Opcode::PushLiteral` +/// +/// Operation: +/// - Push literal value on the stack. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct PushLiteral; + +impl Operation for PushLiteral { + const NAME: &'static str = "PushLiteral"; + const INSTRUCTION: &'static str = "INST - PushLiteral"; + + fn execute(context: &mut Context) -> JsResult { + let index = context.vm.read::() as usize; + let value = context.vm.frame().code.literals[index].clone(); + context.vm.push(value); + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/push/mod.rs b/boa_engine/src/vm/opcode/push/mod.rs new file mode 100644 index 0000000000..e029837735 --- /dev/null +++ b/boa_engine/src/vm/opcode/push/mod.rs @@ -0,0 +1,67 @@ +use crate::{ + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, JsValue, +}; + +pub(crate) mod array; +pub(crate) mod class; +pub(crate) mod environment; +pub(crate) mod literal; +pub(crate) mod new_target; +pub(crate) mod numbers; +pub(crate) mod object; + +pub(crate) use array::*; +pub(crate) use class::*; +pub(crate) use environment::*; +pub(crate) use literal::*; +pub(crate) use new_target::*; +pub(crate) use numbers::*; +pub(crate) use object::*; + +macro_rules! implement_push_generics { + ($name:ident, $push_value:expr, $doc_string:literal) => { + #[doc= concat!("`", stringify!($name), "` implements the OpCode Operation for `Opcode::", stringify!($name), "`\n")] + #[doc= "\n"] + #[doc="Operation:\n"] + #[doc= concat!(" - ", $doc_string)] + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub(crate) struct $name; + + impl Operation for $name { + const NAME: &'static str = stringify!($name); + const INSTRUCTION: &'static str = stringify!("INST - " + $name); + + fn execute(context: &mut Context) -> JsResult { + context.vm.push($push_value); + Ok(ShouldExit::False) + } + } + }; +} + +implement_push_generics!( + PushUndefined, + JsValue::undefined(), + "Push integer `undefined` on the stack." +); +implement_push_generics!( + PushNull, + JsValue::null(), + "Push integer `null` on the stack." +); +implement_push_generics!(PushTrue, true, "Push integer `true` on the stack."); +implement_push_generics!(PushFalse, false, "Push integer `false` on the stack."); +implement_push_generics!(PushZero, 0, "Push integer `0` on the stack."); +implement_push_generics!(PushOne, 1, "Push integer `1` on the stack."); +implement_push_generics!(PushNaN, JsValue::nan(), "Push integer `NaN` on the stack."); +implement_push_generics!( + PushPositiveInfinity, + JsValue::positive_infinity(), + "Push integer `Infinity` on the stack." +); +implement_push_generics!( + PushNegativeInfinity, + JsValue::negative_infinity(), + "Push integer `-Infinity` on the stack." +); diff --git a/boa_engine/src/vm/opcode/push/new_target.rs b/boa_engine/src/vm/opcode/push/new_target.rs new file mode 100644 index 0000000000..85ef0f8eaf --- /dev/null +++ b/boa_engine/src/vm/opcode/push/new_target.rs @@ -0,0 +1,34 @@ +use crate::{ + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, JsValue, +}; + +/// `PushNewTarget` implements the Opcode Operation for `Opcode::PushNewTarget` +/// +/// Operation: +/// - Push the current new target to the stack. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct PushNewTarget; + +impl Operation for PushNewTarget { + const NAME: &'static str = "PushNewTarget"; + const INSTRUCTION: &'static str = "INST - PushNewTarget"; + + fn execute(context: &mut Context) -> JsResult { + if let Some(env) = context + .realm + .environments + .get_this_environment() + .as_function_slots() + { + if let Some(new_target) = env.borrow().new_target() { + context.vm.push(new_target.clone()); + } else { + context.vm.push(JsValue::undefined()); + } + } else { + context.vm.push(JsValue::undefined()); + } + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/push/numbers.rs b/boa_engine/src/vm/opcode/push/numbers.rs new file mode 100644 index 0000000000..3f0b3a6a59 --- /dev/null +++ b/boa_engine/src/vm/opcode/push/numbers.rs @@ -0,0 +1,54 @@ +use crate::{ + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, +}; + +macro_rules! implement_push_numbers_with_conversion { + ($name:ident, $num_type:ty, $doc_string:literal) => { + #[doc= concat!("`", stringify!($name), "` implements the OpCode Operation for `Opcode::", stringify!($name), "`\n")] + #[doc= "\n"] + #[doc="Operation:\n"] + #[doc= concat!(" - ", $doc_string)] + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub(crate) struct $name; + + impl Operation for $name { + const NAME: &'static str = stringify!($name); + const INSTRUCTION: &'static str = stringify!("INST - " + $name); + + fn execute(context: &mut Context) -> JsResult { + let value = context.vm.read::<$num_type>(); + context.vm.push(i32::from(value)); + Ok(ShouldExit::False) + } + } + }; +} + +macro_rules! implement_push_numbers_no_conversion { + ($name:ident, $num_type:ty, $doc_string:literal) => { + #[doc= concat!("`", stringify!($name), "` implements the OpCode Operation for `Opcode::", stringify!($name), "`\n")] + #[doc= "\n"] + #[doc="Operation:\n"] + #[doc= concat!(" - ", $doc_string)] + #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] + pub(crate) struct $name; + + impl Operation for $name { + const NAME: &'static str = stringify!($name); + const INSTRUCTION: &'static str = stringify!("INST - " + $name); + + fn execute(context: &mut Context) -> JsResult { + let value = context.vm.read::<$num_type>(); + context.vm.push(value); + Ok(ShouldExit::False) + } + } + }; +} + +implement_push_numbers_with_conversion!(PushInt8, i8, "Push `i8` value on the stack"); +implement_push_numbers_with_conversion!(PushInt16, i16, "Push `i16` value on the stack"); + +implement_push_numbers_no_conversion!(PushInt32, i32, "Push `i32` value on the stack"); +implement_push_numbers_no_conversion!(PushRational, f64, "Push `f64` value on the stack"); diff --git a/boa_engine/src/vm/opcode/push/object.rs b/boa_engine/src/vm/opcode/push/object.rs new file mode 100644 index 0000000000..7819e97511 --- /dev/null +++ b/boa_engine/src/vm/opcode/push/object.rs @@ -0,0 +1,21 @@ +use crate::{ + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, +}; + +/// `PushEmptyObject` implements the Opcode Operation for `Opcode::PushEmptyObject` +/// +/// Operation: +/// - Push empty object `{}` value on the stack. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct PushEmptyObject; + +impl Operation for PushEmptyObject { + const NAME: &'static str = "PushEmptyObject"; + const INSTRUCTION: &'static str = "INST - PushEmptyObject"; + + fn execute(context: &mut Context) -> JsResult { + context.vm.push(context.construct_object()); + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/require/mod.rs b/boa_engine/src/vm/opcode/require/mod.rs new file mode 100644 index 0000000000..308591ea90 --- /dev/null +++ b/boa_engine/src/vm/opcode/require/mod.rs @@ -0,0 +1,23 @@ +use crate::{ + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, +}; + +/// `RequireObjectCoercible` implements the Opcode Operation for `Opcode::RequireObjectCoercible` +/// +/// Operation: +/// - Call `RequireObjectCoercible` on the stack value. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct RequireObjectCoercible; + +impl Operation for RequireObjectCoercible { + const NAME: &'static str = "RequireObjectCoercible"; + const INSTRUCTION: &'static str = "INST - RequireObjectCoercible"; + + fn execute(context: &mut Context) -> JsResult { + let value = context.vm.pop(); + let value = value.require_object_coercible()?; + context.vm.push(value); + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/rest_parameter/mod.rs b/boa_engine/src/vm/opcode/rest_parameter/mod.rs new file mode 100644 index 0000000000..18d950a224 --- /dev/null +++ b/boa_engine/src/vm/opcode/rest_parameter/mod.rs @@ -0,0 +1,62 @@ +use crate::{ + builtins::Array, + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, +}; + +/// `RestParameterInit` implements the Opcode Operation for `Opcode::RestParameterInit` +/// +/// Operation: +/// - Initialize the rest parameter value of a function from the remaining arguments. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct RestParameterInit; + +impl Operation for RestParameterInit { + const NAME: &'static str = "FunctionRestParameter"; + const INSTRUCTION: &'static str = "INST - FunctionRestParameter"; + + fn execute(context: &mut Context) -> JsResult { + let arg_count = context.vm.frame().arg_count; + let param_count = context.vm.frame().param_count; + if arg_count >= param_count { + let rest_count = arg_count - param_count + 1; + let mut args = Vec::with_capacity(rest_count); + for _ in 0..rest_count { + args.push(context.vm.pop()); + } + let array: _ = Array::create_array_from_list(args, context); + + context.vm.push(array); + } else { + context.vm.pop(); + + let array = + Array::array_create(0, None, context).expect("could not create an empty array"); + context.vm.push(array); + } + Ok(ShouldExit::False) + } +} + +/// `RestParameterPop` implements the Opcode Operation for `Opcode::RestParameterPop` +/// +/// Operation: +/// - Pop the remaining arguments of a function. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct RestParameterPop; + +impl Operation for RestParameterPop { + const NAME: &'static str = "RestParameterPop"; + const INSTRUCTION: &'static str = "INST - RestParameterPop"; + + fn execute(context: &mut Context) -> JsResult { + let arg_count = context.vm.frame().arg_count; + let param_count = context.vm.frame().param_count; + if arg_count > param_count { + for _ in 0..(arg_count - param_count) { + context.vm.pop(); + } + } + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/return_stm/mod.rs b/boa_engine/src/vm/opcode/return_stm/mod.rs new file mode 100644 index 0000000000..ee08121de6 --- /dev/null +++ b/boa_engine/src/vm/opcode/return_stm/mod.rs @@ -0,0 +1,52 @@ +use crate::{ + vm::{opcode::Operation, FinallyReturn, ShouldExit}, + Context, JsResult, +}; + +/// `Return` implements the Opcode Operation for `Opcode::Return` +/// +/// Operation: +/// - Return from a function. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct Return; + +impl Operation for Return { + const NAME: &'static str = "Return"; + const INSTRUCTION: &'static str = "INST - Return"; + + fn execute(context: &mut Context) -> JsResult { + if let Some(finally_address) = context.vm.frame().catch.last().and_then(|c| c.finally) { + let frame = context.vm.frame_mut(); + frame.pc = finally_address as usize; + frame.finally_return = FinallyReturn::Ok; + frame.catch.pop(); + let try_stack_entry = context + .vm + .frame_mut() + .try_env_stack + .pop() + .expect("must exist"); + for _ in 0..try_stack_entry.num_env { + context.realm.environments.pop(); + } + let mut num_env = try_stack_entry.num_env; + for _ in 0..try_stack_entry.num_loop_stack_entries { + num_env -= context + .vm + .frame_mut() + .loop_env_stack + .pop() + .expect("must exist"); + } + *context + .vm + .frame_mut() + .loop_env_stack + .last_mut() + .expect("must exist") -= num_env; + } else { + return Ok(ShouldExit::True); + } + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/set/class_prototype.rs b/boa_engine/src/vm/opcode/set/class_prototype.rs new file mode 100644 index 0000000000..b47c58ec7a --- /dev/null +++ b/boa_engine/src/vm/opcode/set/class_prototype.rs @@ -0,0 +1,74 @@ +use crate::{ + object::{JsObject, ObjectData}, + property::PropertyDescriptorBuilder, + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, JsValue, +}; + +/// `SetClassProtoType` implements the Opcode Operation for `Opcode::SetClassPrototype` +/// +/// Operation: +/// - Set the prototype of a class object. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct SetClassPrototype; + +impl Operation for SetClassPrototype { + const NAME: &'static str = "SetClassPrototype"; + const INSTRUCTION: &'static str = "INST - SetClassPrototype"; + + fn execute(context: &mut Context) -> JsResult { + let prototype_value = context.vm.pop(); + let prototype = match &prototype_value { + JsValue::Object(proto) => Some(proto.clone()), + JsValue::Null => None, + JsValue::Undefined => Some( + context + .intrinsics() + .constructors() + .object() + .prototype + .clone(), + ), + _ => unreachable!(), + }; + + let proto = JsObject::from_proto_and_data(prototype, ObjectData::ordinary()); + let class = context.vm.pop(); + + { + let class_object = class.as_object().expect("class must be object"); + class_object + .define_property_or_throw( + "prototype", + PropertyDescriptorBuilder::new() + .value(proto.clone()) + .writable(false) + .enumerable(false) + .configurable(false), + context, + ) + .expect("cannot fail per spec"); + let mut class_object_mut = class_object.borrow_mut(); + let class_function = class_object_mut + .as_function_mut() + .expect("class must be function object"); + class_function.set_home_object(proto.clone()); + } + + proto + .__define_own_property__( + "constructor".into(), + PropertyDescriptorBuilder::new() + .value(class) + .writable(true) + .enumerable(false) + .configurable(true) + .build(), + context, + ) + .expect("cannot fail per spec"); + + context.vm.push(proto); + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/set/home_object.rs b/boa_engine/src/vm/opcode/set/home_object.rs new file mode 100644 index 0000000000..a587f90ec0 --- /dev/null +++ b/boa_engine/src/vm/opcode/set/home_object.rs @@ -0,0 +1,33 @@ +use crate::{ + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, +}; + +/// `SetHomeObject` implements the Opcode Operation for `Opcode::SetHomeObject` +/// +/// Operation: +/// - Set home object internal slot of a function object. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct SetHomeObject; + +impl Operation for SetHomeObject { + const NAME: &'static str = "SetHomeObject"; + const INSTRUCTION: &'static str = "INST - SetHomeObject"; + + fn execute(context: &mut Context) -> JsResult { + let function = context.vm.pop(); + let function_object = function.as_object().expect("must be object"); + let home = context.vm.pop(); + let home_object = home.as_object().expect("must be object"); + + function_object + .borrow_mut() + .as_function_mut() + .expect("must be function object") + .set_home_object(home_object.clone()); + + context.vm.push(home); + context.vm.push(function); + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/set/mod.rs b/boa_engine/src/vm/opcode/set/mod.rs new file mode 100644 index 0000000000..8e7b8edcda --- /dev/null +++ b/boa_engine/src/vm/opcode/set/mod.rs @@ -0,0 +1,11 @@ +pub(crate) mod class_prototype; +pub(crate) mod home_object; +pub(crate) mod name; +pub(crate) mod private; +pub(crate) mod property; + +pub(crate) use class_prototype::*; +pub(crate) use home_object::*; +pub(crate) use name::*; +pub(crate) use private::*; +pub(crate) use property::*; diff --git a/boa_engine/src/vm/opcode/set/name.rs b/boa_engine/src/vm/opcode/set/name.rs new file mode 100644 index 0000000000..d23313162f --- /dev/null +++ b/boa_engine/src/vm/opcode/set/name.rs @@ -0,0 +1,77 @@ +use crate::{ + error::JsNativeError, + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, JsString, +}; + +/// `SetName` implements the Opcode Operation for `Opcode::SetName` +/// +/// Operation: +/// - Find a binding on the environment chain and assign its value. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct SetName; + +impl Operation for SetName { + const NAME: &'static str = "SetName"; + const INSTRUCTION: &'static str = "INST - SetName"; + + fn execute(context: &mut Context) -> JsResult { + let index = context.vm.read::(); + let binding_locator = context.vm.frame().code.bindings[index as usize]; + let value = context.vm.pop(); + binding_locator.throw_mutate_immutable(context)?; + + if binding_locator.is_global() { + if !context + .realm + .environments + .put_value_global_poisoned(binding_locator.name(), &value) + { + let key: JsString = context + .interner() + .resolve_expect(binding_locator.name().sym()) + .into_common(false); + let exists = context.global_bindings_mut().contains_key(&key); + + if !exists && context.vm.frame().code.strict { + return Err(JsNativeError::reference() + .with_message(format!( + "assignment to undeclared variable {}", + key.to_std_string_escaped() + )) + .into()); + } + + let success = crate::object::internal_methods::global::global_set_no_receiver( + &key.clone().into(), + value, + context, + )?; + + if !success && context.vm.frame().code.strict { + return Err(JsNativeError::typ() + .with_message(format!( + "cannot set non-writable property: {}", + key.to_std_string_escaped() + )) + .into()); + } + } + } else if !context.realm.environments.put_value_if_initialized( + binding_locator.environment_index(), + binding_locator.binding_index(), + binding_locator.name(), + value, + ) { + return Err(JsNativeError::reference() + .with_message(format!( + "cannot access '{}' before initialization", + context + .interner() + .resolve_expect(binding_locator.name().sym()) + )) + .into()); + } + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/set/private.rs b/boa_engine/src/vm/opcode/set/private.rs new file mode 100644 index 0000000000..5c46de0e4f --- /dev/null +++ b/boa_engine/src/vm/opcode/set/private.rs @@ -0,0 +1,187 @@ +use crate::{ + error::JsNativeError, + object::PrivateElement, + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, +}; + +/// `AssignPrivateField` implements the Opcode Operation for `Opcode::AssignPrivateField` +/// +/// Operation: +/// - Assign the value of a private property of an object by it's name. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct AssignPrivateField; + +impl Operation for AssignPrivateField { + const NAME: &'static str = "AssignPrivateField"; + const INSTRUCTION: &'static str = "INST - AssignPrivateField"; + + fn execute(context: &mut Context) -> JsResult { + let index = context.vm.read::(); + let name = context.vm.frame().code.names[index as usize]; + let value = context.vm.pop(); + let object = context.vm.pop(); + if let Some(object) = object.as_object() { + let mut object_borrow_mut = object.borrow_mut(); + match object_borrow_mut.get_private_element(name.sym()) { + Some(PrivateElement::Field(_)) => { + object_borrow_mut.set_private_element(name.sym(), PrivateElement::Field(value)); + } + Some(PrivateElement::Method(_)) => { + return Err(JsNativeError::typ() + .with_message("private method is not writable") + .into()); + } + Some(PrivateElement::Accessor { + setter: Some(setter), + .. + }) => { + let setter = setter.clone(); + drop(object_borrow_mut); + setter.call(&object.clone().into(), &[value], context)?; + } + None => { + return Err(JsNativeError::typ() + .with_message("private field not defined") + .into()); + } + _ => { + return Err(JsNativeError::typ() + .with_message("private field defined without a setter") + .into()); + } + } + } else { + return Err(JsNativeError::typ() + .with_message("cannot set private property on non-object") + .into()); + } + Ok(ShouldExit::False) + } +} + +/// `SetPrivateField` implements the Opcode Operation for `Opcode::SetPrivateField` +/// +/// Operation: +/// - Set a private property of a class constructor by it's name. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct SetPrivateField; + +impl Operation for SetPrivateField { + const NAME: &'static str = "SetPrivateValue"; + const INSTRUCTION: &'static str = "INST - SetPrivateValue"; + + fn execute(context: &mut Context) -> JsResult { + let index = context.vm.read::(); + let name = context.vm.frame().code.names[index as usize]; + let value = context.vm.pop(); + let object = context.vm.pop(); + if let Some(object) = object.as_object() { + let mut object_borrow_mut = object.borrow_mut(); + if let Some(PrivateElement::Accessor { + getter: _, + setter: Some(setter), + }) = object_borrow_mut.get_private_element(name.sym()) + { + let setter = setter.clone(); + drop(object_borrow_mut); + setter.call(&object.clone().into(), &[value], context)?; + } else { + object_borrow_mut.set_private_element(name.sym(), PrivateElement::Field(value)); + } + } else { + return Err(JsNativeError::typ() + .with_message("cannot set private property on non-object") + .into()); + } + Ok(ShouldExit::False) + } +} + +/// `SetPrivateMethod` implements the Opcode Operation for `Opcode::SetPrivateMethod` +/// +/// Operation: +/// - Set a private method of a class constructor by it's name. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct SetPrivateMethod; + +impl Operation for SetPrivateMethod { + const NAME: &'static str = "SetPrivateMethod"; + const INSTRUCTION: &'static str = "INST - SetPrivateMethod"; + + fn execute(context: &mut Context) -> JsResult { + let index = context.vm.read::(); + let name = context.vm.frame().code.names[index as usize]; + let value = context.vm.pop(); + let value = value.as_callable().expect("method must be callable"); + let object = context.vm.pop(); + if let Some(object) = object.as_object() { + let mut object_borrow_mut = object.borrow_mut(); + object_borrow_mut + .set_private_element(name.sym(), PrivateElement::Method(value.clone())); + } else { + return Err(JsNativeError::typ() + .with_message("cannot set private setter on non-object") + .into()); + } + Ok(ShouldExit::False) + } +} + +/// `SetPrivateSetter` implements the Opcode Operation for `Opcode::SetPrivateSetter` +/// +/// Operation: +/// - Set a private setter property of a class constructor by it's name. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct SetPrivateSetter; + +impl Operation for SetPrivateSetter { + const NAME: &'static str = "SetPrivateSetter"; + const INSTRUCTION: &'static str = "INST - SetPrivateSetter"; + + fn execute(context: &mut Context) -> JsResult { + let index = context.vm.read::(); + let name = context.vm.frame().code.names[index as usize]; + let value = context.vm.pop(); + let value = value.as_callable().expect("setter must be callable"); + let object = context.vm.pop(); + if let Some(object) = object.as_object() { + let mut object_borrow_mut = object.borrow_mut(); + object_borrow_mut.set_private_element_setter(name.sym(), value.clone()); + } else { + return Err(JsNativeError::typ() + .with_message("cannot set private setter on non-object") + .into()); + } + Ok(ShouldExit::False) + } +} + +/// `SetPrivateGetter` implements the Opcode Operation for `Opcode::SetPrivateGetter` +/// +/// Operation: +/// - Set a private getter property of a class constructor by it's name. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct SetPrivateGetter; + +impl Operation for SetPrivateGetter { + const NAME: &'static str = "SetPrivateGetter"; + const INSTRUCTION: &'static str = "INST - SetPrivateGetter"; + + fn execute(context: &mut Context) -> JsResult { + let index = context.vm.read::(); + let name = context.vm.frame().code.names[index as usize]; + let value = context.vm.pop(); + let value = value.as_callable().expect("getter must be callable"); + let object = context.vm.pop(); + if let Some(object) = object.as_object() { + let mut object_borrow_mut = object.borrow_mut(); + object_borrow_mut.set_private_element_getter(name.sym(), value.clone()); + } else { + return Err(JsNativeError::typ() + .with_message("cannot set private getter on non-object") + .into()); + } + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/set/property.rs b/boa_engine/src/vm/opcode/set/property.rs new file mode 100644 index 0000000000..f1f8855184 --- /dev/null +++ b/boa_engine/src/vm/opcode/set/property.rs @@ -0,0 +1,220 @@ +use crate::{ + property::{PropertyDescriptor, PropertyKey}, + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, JsString, +}; + +/// `SetPropertyByName` implements the Opcode Operation for `Opcode::SetPropertyByName` +/// +/// Operation: +/// - Sets a property by name of an object. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct SetPropertyByName; + +impl Operation for SetPropertyByName { + const NAME: &'static str = "SetPropertyByName"; + const INSTRUCTION: &'static str = "INST - SetPropertyByName"; + + fn execute(context: &mut Context) -> JsResult { + let index = context.vm.read::(); + + let object = context.vm.pop(); + let value = context.vm.pop(); + let object = if let Some(object) = object.as_object() { + object.clone() + } else { + object.to_object(context)? + }; + + let name = context.vm.frame().code.names[index as usize]; + let name: PropertyKey = context + .interner() + .resolve_expect(name.sym()) + .into_common::(false) + .into(); + + object.set(name, value, context.vm.frame().code.strict, context)?; + Ok(ShouldExit::False) + } +} + +/// `SetPropertyByValue` implements the Opcode Operation for `Opcode::SetPropertyByValue` +/// +/// Operation: +/// - Sets a property by value of an object. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct SetPropertyByValue; + +impl Operation for SetPropertyByValue { + const NAME: &'static str = "SetPropertyByValue"; + const INSTRUCTION: &'static str = "INST - SetPropertyByValue"; + + fn execute(context: &mut Context) -> JsResult { + let object = context.vm.pop(); + let key = context.vm.pop(); + let value = context.vm.pop(); + let object = if let Some(object) = object.as_object() { + object.clone() + } else { + object.to_object(context)? + }; + + let key = key.to_property_key(context)?; + object.set(key, value, context.vm.frame().code.strict, context)?; + Ok(ShouldExit::False) + } +} + +/// `SetPropertyGetterByName` implements the Opcode Operation for `Opcode::SetPropertyGetterByName` +/// +/// Operation: +/// - Sets a getter property by name of an object. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct SetPropertyGetterByName; + +impl Operation for SetPropertyGetterByName { + const NAME: &'static str = "SetPropertyGetterByName"; + const INSTRUCTION: &'static str = "INST - SetPropertyGetterByName"; + + fn execute(context: &mut Context) -> JsResult { + let index = context.vm.read::(); + let object = context.vm.pop(); + let value = context.vm.pop(); + let object = object.to_object(context)?; + let name = context.vm.frame().code.names[index as usize]; + let name = context + .interner() + .resolve_expect(name.sym()) + .into_common::(false) + .into(); + let set = object + .__get_own_property__(&name, context)? + .as_ref() + .and_then(PropertyDescriptor::set) + .cloned(); + object.__define_own_property__( + name, + PropertyDescriptor::builder() + .maybe_get(Some(value)) + .maybe_set(set) + .enumerable(true) + .configurable(true) + .build(), + context, + )?; + Ok(ShouldExit::False) + } +} + +/// `SetPropertyGetterByValue` implements the Opcode Operation for `Opcode::SetPropertyGetterByValue` +/// +/// Operation: +/// - Sets a getter property by value of an object. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct SetPropertyGetterByValue; + +impl Operation for SetPropertyGetterByValue { + const NAME: &'static str = "SetPropertyGetterByValue"; + const INSTRUCTION: &'static str = "INST - SetPropertyGetterByValue"; + + fn execute(context: &mut Context) -> JsResult { + let value = context.vm.pop(); + let key = context.vm.pop(); + let object = context.vm.pop(); + let object = object.to_object(context)?; + let name = key.to_property_key(context)?; + let set = object + .__get_own_property__(&name, context)? + .as_ref() + .and_then(PropertyDescriptor::set) + .cloned(); + object.__define_own_property__( + name, + PropertyDescriptor::builder() + .maybe_get(Some(value)) + .maybe_set(set) + .enumerable(true) + .configurable(true) + .build(), + context, + )?; + Ok(ShouldExit::False) + } +} + +/// `SetPropertySetterByName` implements the Opcode Operation for `Opcode::SetPropertySetterByName` +/// +/// Operation: +/// - Sets a setter property by name of an object. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct SetPropertySetterByName; + +impl Operation for SetPropertySetterByName { + const NAME: &'static str = "SetPropertySetterByName"; + const INSTRUCTION: &'static str = "INST - SetPropertySetterByName"; + + fn execute(context: &mut Context) -> JsResult { + let index = context.vm.read::(); + let object = context.vm.pop(); + let value = context.vm.pop(); + let object = object.to_object(context)?; + let name = context.vm.frame().code.names[index as usize]; + let name = context + .interner() + .resolve_expect(name.sym()) + .into_common::(false) + .into(); + let get = object + .__get_own_property__(&name, context)? + .as_ref() + .and_then(PropertyDescriptor::get) + .cloned(); + object.__define_own_property__( + name, + PropertyDescriptor::builder() + .maybe_set(Some(value)) + .maybe_get(get) + .enumerable(true) + .configurable(true) + .build(), + context, + )?; + Ok(ShouldExit::False) + } +} + +/// `SetPropertySetterByValue` implements the Opcode Operation for `Opcode::SetPropertySetterByValue` +/// +/// Operation: +/// - Sets a setter property by value of an object. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct SetPropertySetterByValue; + +impl Operation for SetPropertySetterByValue { + const NAME: &'static str = "SetPropertySetterByValue"; + const INSTRUCTION: &'static str = "INST - SetPropertySetterByValue"; + + fn execute(context: &mut Context) -> JsResult { + let value = context.vm.pop(); + let key = context.vm.pop(); + let object = context.vm.pop(); + let object = object.to_object(context)?; + let name = key.to_property_key(context)?; + let get = object + .__get_own_property__(&name, context)? + .as_ref() + .and_then(PropertyDescriptor::get) + .cloned(); + object.__define_own_property__( + name, + PropertyDescriptor::builder() + .maybe_set(Some(value)) + .maybe_get(get) + .enumerable(true) + .configurable(true) + .build(), + context, + )?; + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/swap/mod.rs b/boa_engine/src/vm/opcode/swap/mod.rs new file mode 100644 index 0000000000..fa74ca5ede --- /dev/null +++ b/boa_engine/src/vm/opcode/swap/mod.rs @@ -0,0 +1,25 @@ +use crate::{ + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, +}; + +/// `Swap` implements the Opcode Operation for `Opcode::Swap` +/// +/// Operation: +/// - Swap the top two values on the stack. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct Swap; + +impl Operation for Swap { + const NAME: &'static str = "Swap"; + const INSTRUCTION: &'static str = "INST - Swap"; + + fn execute(context: &mut Context) -> JsResult { + let first = context.vm.pop(); + let second = context.vm.pop(); + + context.vm.push(first); + context.vm.push(second); + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/switch/mod.rs b/boa_engine/src/vm/opcode/switch/mod.rs new file mode 100644 index 0000000000..385cd9c58c --- /dev/null +++ b/boa_engine/src/vm/opcode/switch/mod.rs @@ -0,0 +1,49 @@ +use crate::{ + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, +}; + +/// `Case` implements the Opcode Operation for `Opcode::Case` +/// +/// Operation: +/// - Pop the two values of the stack, strict equal compares the two values, +/// if true jumps to address, otherwise push the second pop'ed value. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct Case; + +impl Operation for Case { + const NAME: &'static str = "Case"; + const INSTRUCTION: &'static str = "INST - Case"; + + fn execute(context: &mut Context) -> JsResult { + let address = context.vm.read::(); + let cond = context.vm.pop(); + let value = context.vm.pop(); + + if value.strict_equals(&cond) { + context.vm.frame_mut().pc = address as usize; + } else { + context.vm.push(value); + } + Ok(ShouldExit::False) + } +} + +/// `Default` implements the Opcode Operation for `Opcode::Default` +/// +/// Operation: +/// - Pops the top of stack and jump to address. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct Default; + +impl Operation for Default { + const NAME: &'static str = "Default"; + const INSTRUCTION: &'static str = "INST - Default"; + + fn execute(context: &mut Context) -> JsResult { + let exit = context.vm.read::(); + let _val = context.vm.pop(); + context.vm.frame_mut().pc = exit as usize; + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/throw/mod.rs b/boa_engine/src/vm/opcode/throw/mod.rs new file mode 100644 index 0000000000..efa05ea375 --- /dev/null +++ b/boa_engine/src/vm/opcode/throw/mod.rs @@ -0,0 +1,21 @@ +use crate::{ + vm::{opcode::Operation, ShouldExit}, + Context, JsError, JsResult, +}; + +/// `Throw` implements the Opcode Operation for `Opcode::Throw` +/// +/// Operation: +/// - Throw exception. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct Throw; + +impl Operation for Throw { + const NAME: &'static str = "Throw"; + const INSTRUCTION: &'static str = "INST - Throw"; + + fn execute(context: &mut Context) -> JsResult { + let value = context.vm.pop(); + Err(JsError::from_opaque(value)) + } +} diff --git a/boa_engine/src/vm/opcode/to/mod.rs b/boa_engine/src/vm/opcode/to/mod.rs new file mode 100644 index 0000000000..20b7d6883d --- /dev/null +++ b/boa_engine/src/vm/opcode/to/mod.rs @@ -0,0 +1,41 @@ +use crate::{ + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, +}; + +/// `ToBoolean` implements the Opcode Operation for `Opcode::ToBoolean` +/// +/// Operation: +/// - Pops value converts it to boolean and pushes it back. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct ToBoolean; + +impl Operation for ToBoolean { + const NAME: &'static str = "ToBoolean"; + const INSTRUCTION: &'static str = "INST - ToBoolean"; + + fn execute(context: &mut Context) -> JsResult { + let value = context.vm.pop(); + context.vm.push(value.to_boolean()); + Ok(ShouldExit::False) + } +} + +/// `ToPropertyKey` implements the Opcode Operation for `Opcode::ToPropertyKey` +/// +/// Operation: +/// - Call `ToPropertyKey` on the value on the stack. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct ToPropertyKey; + +impl Operation for ToPropertyKey { + const NAME: &'static str = "ToPropertyKey"; + const INSTRUCTION: &'static str = "INST - ToPropertyKey"; + + fn execute(context: &mut Context) -> JsResult { + let value = context.vm.pop(); + let key = value.to_property_key(context)?; + context.vm.push(key); + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/try_catch/mod.rs b/boa_engine/src/vm/opcode/try_catch/mod.rs new file mode 100644 index 0000000000..c19fd2b75d --- /dev/null +++ b/boa_engine/src/vm/opcode/try_catch/mod.rs @@ -0,0 +1,164 @@ +use crate::{ + vm::{opcode::Operation, CatchAddresses, FinallyReturn, ShouldExit, TryStackEntry}, + Context, JsResult, +}; + +/// `TryStart` implements the Opcode Operation for `Opcode::TryStart` +/// +/// Operation: +/// - Start of a try block. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct TryStart; + +impl Operation for TryStart { + const NAME: &'static str = "TryStart"; + const INSTRUCTION: &'static str = "INST - TryStart"; + + fn execute(context: &mut Context) -> JsResult { + let next = context.vm.read::(); + let finally = context.vm.read::(); + let finally = if finally == 0 { None } else { Some(finally) }; + context + .vm + .frame_mut() + .catch + .push(CatchAddresses { next, finally }); + context.vm.frame_mut().finally_jump.push(None); + context.vm.frame_mut().finally_return = FinallyReturn::None; + context.vm.frame_mut().try_env_stack.push(TryStackEntry { + num_env: 0, + num_loop_stack_entries: 0, + }); + Ok(ShouldExit::False) + } +} + +/// `TryEnd` implements the Opcode Operation for `Opcode::TryEnd` +/// +/// Operation: +/// - End of a try block +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct TryEnd; + +impl Operation for TryEnd { + const NAME: &'static str = "TryEnd"; + const INSTRUCTION: &'static str = "INST - TryEnd"; + + fn execute(context: &mut Context) -> JsResult { + context.vm.frame_mut().catch.pop(); + let try_stack_entry = context + .vm + .frame_mut() + .try_env_stack + .pop() + .expect("must exist"); + for _ in 0..try_stack_entry.num_env { + context.realm.environments.pop(); + } + let mut num_env = try_stack_entry.num_env; + for _ in 0..try_stack_entry.num_loop_stack_entries { + num_env -= context + .vm + .frame_mut() + .loop_env_stack + .pop() + .expect("must exist"); + } + *context + .vm + .frame_mut() + .loop_env_stack + .last_mut() + .expect("must exist") -= num_env; + context.vm.frame_mut().finally_return = FinallyReturn::None; + Ok(ShouldExit::False) + } +} + +/// `CatchStart` implements the Opcode Operation for `Opcode::CatchStart` +/// +/// Operation: +/// - Start of a catch block. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct CatchStart; + +impl Operation for CatchStart { + const NAME: &'static str = "CatchStart"; + const INSTRUCTION: &'static str = "INST - CatchStart"; + + fn execute(context: &mut Context) -> JsResult { + let finally = context.vm.read::(); + context.vm.frame_mut().catch.push(CatchAddresses { + next: finally, + finally: Some(finally), + }); + context.vm.frame_mut().try_env_stack.push(TryStackEntry { + num_env: 0, + num_loop_stack_entries: 0, + }); + context.vm.frame_mut().thrown = false; + Ok(ShouldExit::False) + } +} + +/// `CatchEnd` implements the Opcode Operation for `Opcode::CatchEnd` +/// +/// Operation: +/// - End of a catch block. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct CatchEnd; + +impl Operation for CatchEnd { + const NAME: &'static str = "CatchEnd"; + const INSTRUCTION: &'static str = "INST - CatchEnd"; + + fn execute(context: &mut Context) -> JsResult { + context.vm.frame_mut().catch.pop(); + let try_stack_entry = context + .vm + .frame_mut() + .try_env_stack + .pop() + .expect("must exist"); + for _ in 0..try_stack_entry.num_env { + context.realm.environments.pop(); + } + let mut num_env = try_stack_entry.num_env; + for _ in 0..try_stack_entry.num_loop_stack_entries { + num_env -= context + .vm + .frame_mut() + .loop_env_stack + .pop() + .expect("must exist"); + } + *context + .vm + .frame_mut() + .loop_env_stack + .last_mut() + .expect("must exist") -= num_env; + context.vm.frame_mut().finally_return = FinallyReturn::None; + Ok(ShouldExit::False) + } +} + +/// `CatchEnd2` implements the Opcode Operation for `Opcode::CatchEnd2` +/// +/// Operation: +/// - End of a catch block +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct CatchEnd2; + +impl Operation for CatchEnd2 { + const NAME: &'static str = "CatchEnd2"; + const INSTRUCTION: &'static str = "INST - CatchEnd2"; + + fn execute(context: &mut Context) -> JsResult { + let frame = context.vm.frame_mut(); + if frame.finally_return == FinallyReturn::Err { + frame.finally_return = FinallyReturn::None; + } + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/unary_ops/decrement.rs b/boa_engine/src/vm/opcode/unary_ops/decrement.rs new file mode 100644 index 0000000000..a09396eb09 --- /dev/null +++ b/boa_engine/src/vm/opcode/unary_ops/decrement.rs @@ -0,0 +1,53 @@ +use crate::{ + value::Numeric, + vm::{opcode::Operation, ShouldExit}, + Context, JsBigInt, JsResult, +}; + +/// `Dec` implements the Opcode Operation for `Opcode::Dec` +/// +/// Operation: +/// - Unary `--` operator. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct Dec; + +impl Operation for Dec { + const NAME: &'static str = "Dec"; + const INSTRUCTION: &'static str = "INST - Dec"; + + fn execute(context: &mut Context) -> JsResult { + let value = context.vm.pop(); + match value.to_numeric(context)? { + Numeric::Number(number) => context.vm.push(number - 1f64), + Numeric::BigInt(bigint) => { + context.vm.push(JsBigInt::sub(&bigint, &JsBigInt::one())); + } + } + Ok(ShouldExit::False) + } +} + +/// `DecPost` implements the Opcode Operation for `Opcode::DecPost` +/// +/// Operation: +/// - Unary postfix `--` operator. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct DecPost; + +impl Operation for DecPost { + const NAME: &'static str = "DecPost"; + const INSTRUCTION: &'static str = "INST - DecPost"; + + fn execute(context: &mut Context) -> JsResult { + let value = context.vm.pop(); + let value = value.to_numeric(context)?; + context.vm.push(value.clone()); + match value { + Numeric::Number(number) => context.vm.push(number - 1f64), + Numeric::BigInt(bigint) => { + context.vm.push(JsBigInt::sub(&bigint, &JsBigInt::one())); + } + } + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/unary_ops/increment.rs b/boa_engine/src/vm/opcode/unary_ops/increment.rs new file mode 100644 index 0000000000..f977120691 --- /dev/null +++ b/boa_engine/src/vm/opcode/unary_ops/increment.rs @@ -0,0 +1,53 @@ +use crate::{ + value::Numeric, + vm::{opcode::Operation, ShouldExit}, + Context, JsBigInt, JsResult, +}; + +/// `Inc` implements the Opcode Operation for `Opcode::Inc` +/// +/// Operation: +/// - Unary `++` operator. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct Inc; + +impl Operation for Inc { + const NAME: &'static str = "Inc"; + const INSTRUCTION: &'static str = "INST - Inc"; + + fn execute(context: &mut Context) -> JsResult { + let value = context.vm.pop(); + match value.to_numeric(context)? { + Numeric::Number(number) => context.vm.push(number + 1f64), + Numeric::BigInt(bigint) => { + context.vm.push(JsBigInt::add(&bigint, &JsBigInt::one())); + } + } + Ok(ShouldExit::False) + } +} + +/// `Inc` implements the Opcode Operation for `Opcode::Inc` +/// +/// Operation: +/// - Unary postfix `++` operator. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct IncPost; + +impl Operation for IncPost { + const NAME: &'static str = "IncPost"; + const INSTRUCTION: &'static str = "INST - IncPost"; + + fn execute(context: &mut Context) -> JsResult { + let value = context.vm.pop(); + let value = value.to_numeric(context)?; + context.vm.push(value.clone()); + match value { + Numeric::Number(number) => context.vm.push(number + 1f64), + Numeric::BigInt(bigint) => { + context.vm.push(JsBigInt::add(&bigint, &JsBigInt::one())); + } + } + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/unary_ops/logical.rs b/boa_engine/src/vm/opcode/unary_ops/logical.rs new file mode 100644 index 0000000000..363a8dd444 --- /dev/null +++ b/boa_engine/src/vm/opcode/unary_ops/logical.rs @@ -0,0 +1,22 @@ +use crate::{ + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, +}; + +/// `LogicalNot` implements the Opcode Operation for `Opcode::LogicalNot` +/// +/// Operation: +/// - Unary logical `!` operator. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct LogicalNot; + +impl Operation for LogicalNot { + const NAME: &'static str = "LogicalNot"; + const INSTRUCTION: &'static str = "INST - LogicalNot"; + + fn execute(context: &mut Context) -> JsResult { + let value = context.vm.pop(); + context.vm.push(!value.to_boolean()); + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/unary_ops/mod.rs b/boa_engine/src/vm/opcode/unary_ops/mod.rs new file mode 100644 index 0000000000..daea12d340 --- /dev/null +++ b/boa_engine/src/vm/opcode/unary_ops/mod.rs @@ -0,0 +1,96 @@ +use crate::{ + builtins::Number, + value::Numeric, + vm::{opcode::Operation, ShouldExit}, + Context, JsBigInt, JsResult, +}; +use std::ops::Neg as StdNeg; + +pub(crate) mod decrement; +pub(crate) mod increment; +pub(crate) mod logical; +pub(crate) mod void; + +pub(crate) use decrement::*; +pub(crate) use increment::*; +pub(crate) use logical::*; +pub(crate) use void::*; + +/// `TypeOf` implements the Opcode Operation for `Opcode::TypeOf` +/// +/// Operation: +/// - Unary `typeof` operator. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct TypeOf; + +impl Operation for TypeOf { + const NAME: &'static str = "TypeOf"; + const INSTRUCTION: &'static str = "INST - TypeOf"; + + fn execute(context: &mut Context) -> JsResult { + let value = context.vm.pop(); + context.vm.push(value.type_of()); + Ok(ShouldExit::False) + } +} + +/// `Pos` implements the Opcode Operation for `Opcode::Pos` +/// +/// Operation: +/// - Unary `+` operator. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct Pos; + +impl Operation for Pos { + const NAME: &'static str = "Pos"; + const INSTRUCTION: &'static str = "INST - Pos"; + + fn execute(context: &mut Context) -> JsResult { + let value = context.vm.pop(); + let value = value.to_number(context)?; + context.vm.push(value); + Ok(ShouldExit::False) + } +} + +/// `Neg` implements the Opcode Operation for `Opcode::Neg` +/// +/// Operation: +/// - Unary `-` operator. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct Neg; + +impl Operation for Neg { + const NAME: &'static str = "Neg"; + const INSTRUCTION: &'static str = "INST - Neg"; + + fn execute(context: &mut Context) -> JsResult { + let value = context.vm.pop(); + match value.to_numeric(context)? { + Numeric::Number(number) => context.vm.push(number.neg()), + Numeric::BigInt(bigint) => context.vm.push(JsBigInt::neg(&bigint)), + } + Ok(ShouldExit::False) + } +} + +/// `BitNot` implements the Opcode Operation for `Opcode::BitNot` +/// +/// Operation: +/// - Unary bitwise `~` operator. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct BitNot; + +impl Operation for BitNot { + const NAME: &'static str = "BitNot"; + const INSTRUCTION: &'static str = "INST - BitNot"; + + fn execute(context: &mut Context) -> JsResult { + let value = context.vm.pop(); + match value.to_numeric(context)? { + Numeric::Number(number) => context.vm.push(Number::not(number)), + Numeric::BigInt(bigint) => context.vm.push(JsBigInt::not(&bigint)), + } + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/unary_ops/void.rs b/boa_engine/src/vm/opcode/unary_ops/void.rs new file mode 100644 index 0000000000..19da829fdd --- /dev/null +++ b/boa_engine/src/vm/opcode/unary_ops/void.rs @@ -0,0 +1,22 @@ +use crate::{ + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, JsValue, +}; + +/// `Void` implements the Opcode Operation for `Opcode::Void` +/// +/// Operation: +/// - Unary `void` operator. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct Void; + +impl Operation for Void { + const NAME: &'static str = "Void"; + const INSTRUCTION: &'static str = "INST - Void"; + + fn execute(context: &mut Context) -> JsResult { + let _old = context.vm.pop(); + context.vm.push(JsValue::undefined()); + Ok(ShouldExit::False) + } +} diff --git a/boa_engine/src/vm/opcode/value/mod.rs b/boa_engine/src/vm/opcode/value/mod.rs new file mode 100644 index 0000000000..e9813f645d --- /dev/null +++ b/boa_engine/src/vm/opcode/value/mod.rs @@ -0,0 +1,33 @@ +use crate::{ + error::JsNativeError, + vm::{opcode::Operation, ShouldExit}, + Context, JsResult, +}; + +/// `ValueNotNullOrUndefined` implements the Opcode Operation for `Opcode::ValueNotNullOrUndefined` +/// +/// Operation: +/// - Require the stack value to be neither null nor undefined. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub(crate) struct ValueNotNullOrUndefined; + +impl Operation for ValueNotNullOrUndefined { + const NAME: &'static str = "ValueNotNullOrUndefined"; + const INSTRUCTION: &'static str = "INST - ValueNotNullOrUndefined"; + + fn execute(context: &mut Context) -> JsResult { + let value = context.vm.pop(); + if value.is_null() { + return Err(JsNativeError::typ() + .with_message("Cannot destructure 'null' value") + .into()); + } + if value.is_undefined() { + return Err(JsNativeError::typ() + .with_message("Cannot destructure 'undefined' value") + .into()); + } + context.vm.push(value); + Ok(ShouldExit::False) + } +}