Browse Source

Split vm/opcode into modules (#2343)

<!---
Thank you for contributing to Boa! Please fill out the template below, and remove or add any
information as you feel necessary.
--->
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>
pull/2370/head
Kevin 2 years ago
parent
commit
9a05b1ef81
  1. 2700
      boa_engine/src/vm/mod.rs
  2. 119
      boa_engine/src/vm/opcode/await_stm/mod.rs
  3. 70
      boa_engine/src/vm/opcode/binary_ops/logical.rs
  4. 46
      boa_engine/src/vm/opcode/binary_ops/macro_defined.rs
  5. 120
      boa_engine/src/vm/opcode/binary_ops/mod.rs
  6. 212
      boa_engine/src/vm/opcode/call/mod.rs
  7. 33
      boa_engine/src/vm/opcode/concat/mod.rs
  8. 42
      boa_engine/src/vm/opcode/copy/mod.rs
  9. 96
      boa_engine/src/vm/opcode/define/class/getter.rs
  10. 90
      boa_engine/src/vm/opcode/define/class/method.rs
  11. 7
      boa_engine/src/vm/opcode/define/class/mod.rs
  12. 96
      boa_engine/src/vm/opcode/define/class/setter.rs
  13. 140
      boa_engine/src/vm/opcode/define/mod.rs
  14. 79
      boa_engine/src/vm/opcode/define/own_property.rs
  15. 63
      boa_engine/src/vm/opcode/delete/mod.rs
  16. 23
      boa_engine/src/vm/opcode/dup/mod.rs
  17. 275
      boa_engine/src/vm/opcode/environment/mod.rs
  18. 226
      boa_engine/src/vm/opcode/generator/mod.rs
  19. 20
      boa_engine/src/vm/opcode/generator/yield_stm.rs
  20. 44
      boa_engine/src/vm/opcode/get/function.rs
  21. 44
      boa_engine/src/vm/opcode/get/generator.rs
  22. 11
      boa_engine/src/vm/opcode/get/mod.rs
  23. 136
      boa_engine/src/vm/opcode/get/name.rs
  24. 54
      boa_engine/src/vm/opcode/get/private.rs
  25. 96
      boa_engine/src/vm/opcode/get/property.rs
  26. 78
      boa_engine/src/vm/opcode/iteration/for_await.rs
  27. 86
      boa_engine/src/vm/opcode/iteration/for_in.rs
  28. 47
      boa_engine/src/vm/opcode/iteration/init.rs
  29. 109
      boa_engine/src/vm/opcode/iteration/iterator.rs
  30. 76
      boa_engine/src/vm/opcode/iteration/loop_ops.rs
  31. 11
      boa_engine/src/vm/opcode/iteration/mod.rs
  32. 64
      boa_engine/src/vm/opcode/jump/mod.rs
  33. 755
      boa_engine/src/vm/opcode/mod.rs
  34. 89
      boa_engine/src/vm/opcode/new/mod.rs
  35. 20
      boa_engine/src/vm/opcode/nop/mod.rs
  36. 95
      boa_engine/src/vm/opcode/pop/mod.rs
  37. 80
      boa_engine/src/vm/opcode/promise/mod.rs
  38. 106
      boa_engine/src/vm/opcode/push/array.rs
  39. 85
      boa_engine/src/vm/opcode/push/class/field.rs
  40. 65
      boa_engine/src/vm/opcode/push/class/mod.rs
  41. 101
      boa_engine/src/vm/opcode/push/class/private.rs
  42. 56
      boa_engine/src/vm/opcode/push/environment.rs
  43. 23
      boa_engine/src/vm/opcode/push/literal.rs
  44. 67
      boa_engine/src/vm/opcode/push/mod.rs
  45. 34
      boa_engine/src/vm/opcode/push/new_target.rs
  46. 54
      boa_engine/src/vm/opcode/push/numbers.rs
  47. 21
      boa_engine/src/vm/opcode/push/object.rs
  48. 23
      boa_engine/src/vm/opcode/require/mod.rs
  49. 62
      boa_engine/src/vm/opcode/rest_parameter/mod.rs
  50. 52
      boa_engine/src/vm/opcode/return_stm/mod.rs
  51. 74
      boa_engine/src/vm/opcode/set/class_prototype.rs
  52. 33
      boa_engine/src/vm/opcode/set/home_object.rs
  53. 11
      boa_engine/src/vm/opcode/set/mod.rs
  54. 77
      boa_engine/src/vm/opcode/set/name.rs
  55. 187
      boa_engine/src/vm/opcode/set/private.rs
  56. 220
      boa_engine/src/vm/opcode/set/property.rs
  57. 25
      boa_engine/src/vm/opcode/swap/mod.rs
  58. 49
      boa_engine/src/vm/opcode/switch/mod.rs
  59. 21
      boa_engine/src/vm/opcode/throw/mod.rs
  60. 41
      boa_engine/src/vm/opcode/to/mod.rs
  61. 164
      boa_engine/src/vm/opcode/try_catch/mod.rs
  62. 53
      boa_engine/src/vm/opcode/unary_ops/decrement.rs
  63. 53
      boa_engine/src/vm/opcode/unary_ops/increment.rs
  64. 22
      boa_engine/src/vm/opcode/unary_ops/logical.rs
  65. 96
      boa_engine/src/vm/opcode/unary_ops/mod.rs
  66. 22
      boa_engine/src/vm/opcode/unary_ops/void.rs
  67. 33
      boa_engine/src/vm/opcode/value/mod.rs

2700
boa_engine/src/vm/mod.rs

File diff suppressed because it is too large Load Diff

119
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<ShouldExit> {
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)
}
}

70
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<ShouldExit> {
let exit = context.vm.read::<u32>();
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<ShouldExit> {
let exit = context.vm.read::<u32>();
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<ShouldExit> {
let exit = context.vm.read::<u32>();
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)
}
}

46
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<ShouldExit> {
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.");

120
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<ShouldExit> {
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<ShouldExit> {
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<ShouldExit> {
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<ShouldExit> {
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<ShouldExit> {
let target = context.vm.pop();
let v = context.vm.pop();
let value = v.instance_of(&target, context)?;
context.vm.push(value);
Ok(ShouldExit::False)
}
}

212
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<ShouldExit> {
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::<u32>();
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<ShouldExit> {
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<ShouldExit> {
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::<u32>();
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<ShouldExit> {
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)
}
}

33
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<ShouldExit> {
let value_count = context.vm.read::<u32>();
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::<Vec<&[u16]>>(),
);
context.vm.push(s);
Ok(ShouldExit::False)
}
}

42
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<ShouldExit> {
let excluded_key_count = context.vm.read::<u32>();
let excluded_key_count_computed = context.vm.read::<u32>();
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)
}
}

96
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<ShouldExit> {
let index = context.vm.read::<u32>();
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::<JsString>(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<ShouldExit> {
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)
}
}

90
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<ShouldExit> {
let index = context.vm.read::<u32>();
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::<JsString>(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<ShouldExit> {
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)
}
}

7
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::*;

96
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<ShouldExit> {
let index = context.vm.read::<u32>();
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::<JsString>(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<ShouldExit> {
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)
}
}

140
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<ShouldExit> {
let index = context.vm.read::<u32>();
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<ShouldExit> {
let index = context.vm.read::<u32>();
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::<JsString>(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<ShouldExit> {
let index = context.vm.read::<u32>();
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<ShouldExit> {
let index = context.vm.read::<u32>();
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");

79
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<ShouldExit> {
let index = context.vm.read::<u32>();
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::<JsString>(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<ShouldExit> {
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)
}
}

63
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<ShouldExit> {
let index = context.vm.read::<u32>();
let key = context.vm.frame().code.names[index as usize];
let key = context
.interner()
.resolve_expect(key.sym())
.into_common::<JsString>(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<ShouldExit> {
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)
}
}

23
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<ShouldExit> {
let value = context.vm.pop();
context.vm.push(value.clone());
context.vm.push(value);
Ok(ShouldExit::False)
}
}

275
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<ShouldExit> {
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<ShouldExit> {
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<ShouldExit> {
let argument_count = context.vm.read::<u32>();
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<ShouldExit> {
// 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<ShouldExit> {
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)
}
}

226
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<ShouldExit> {
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<ShouldExit> {
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<ShouldExit> {
let done_address = context.vm.read::<u32>();
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)
}
}
}
}

20
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<ShouldExit> {
Ok(ShouldExit::Yield)
}
}

44
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<ShouldExit> {
let index = context.vm.read::<u32>();
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<ShouldExit> {
let index = context.vm.read::<u32>();
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)
}
}

44
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<ShouldExit> {
let index = context.vm.read::<u32>();
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<ShouldExit> {
let index = context.vm.read::<u32>();
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)
}
}

11
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::*;

136
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<ShouldExit> {
let index = context.vm.read::<u32>();
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<ShouldExit> {
let index = context.vm.read::<u32>();
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)
}
}

54
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<ShouldExit> {
let index = context.vm.read::<u32>();
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)
}
}

96
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<ShouldExit> {
let index = context.vm.read::<u32>();
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::<JsString>(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<ShouldExit> {
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<ShouldExit> {
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)
}
}

78
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<ShouldExit> {
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<ShouldExit> {
let address = context.vm.read::<u32>();
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)
}
}

86
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<ShouldExit> {
let address = context.vm.read::<u32>();
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<ShouldExit> {
let address = context.vm.read::<u32>();
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)
}
}

47
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<ShouldExit> {
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<ShouldExit> {
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)
}
}

109
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<ShouldExit> {
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<ShouldExit> {
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<ShouldExit> {
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)
}
}

76
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<ShouldExit> {
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<ShouldExit> {
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<ShouldExit> {
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)
}
}

11
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::*;

64
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<ShouldExit> {
let address = context.vm.read::<u32>();
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<ShouldExit> {
let address = context.vm.read::<u32>();
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<ShouldExit> {
let address = context.vm.read::<u32>();
let value = context.vm.pop();
if !value.is_undefined() {
context.vm.frame_mut().pc = address as usize;
context.vm.push(value);
}
Ok(ShouldExit::False)
}
}

755
boa_engine/src/vm/opcode.rs → 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<ShouldExit>;
}
#[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,
}
}
}

89
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<ShouldExit> {
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::<u32>();
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<ShouldExit> {
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)
}
}

20
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<ShouldExit> {
Ok(ShouldExit::False)
}
}

95
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<ShouldExit> {
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<ShouldExit> {
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<ShouldExit> {
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<ShouldExit> {
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<ShouldExit> {
context.vm.frame_mut().pop_on_return -= 1;
Ok(ShouldExit::False)
}
}

80
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<ShouldExit> {
*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<ShouldExit> {
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<ShouldExit> {
let address = context.vm.read::<u32>();
*context
.vm
.frame_mut()
.finally_jump
.last_mut()
.expect("finally jump must exist here") = Some(address);
Ok(ShouldExit::False)
}
}

106
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<ShouldExit> {
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<ShouldExit> {
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<ShouldExit> {
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<ShouldExit> {
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)
}
}

85
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<ShouldExit> {
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<ShouldExit> {
let index = context.vm.read::<u32>();
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)
}
}

65
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<ShouldExit> {
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())
}
}
}

101
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<ShouldExit> {
let index = context.vm.read::<u32>();
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<ShouldExit> {
let index = context.vm.read::<u32>();
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<ShouldExit> {
let index = context.vm.read::<u32>();
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)
}
}

56
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<ShouldExit> {
let num_bindings = context.vm.read::<u32>();
let compile_environments_index = context.vm.read::<u32>();
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<ShouldExit> {
let num_bindings = context.vm.read::<u32>();
let compile_environments_index = context.vm.read::<u32>();
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)
}
}

23
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<ShouldExit> {
let index = context.vm.read::<u32>() as usize;
let value = context.vm.frame().code.literals[index].clone();
context.vm.push(value);
Ok(ShouldExit::False)
}
}

67
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<ShouldExit> {
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."
);

34
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<ShouldExit> {
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)
}
}

54
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<ShouldExit> {
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<ShouldExit> {
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");

21
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<ShouldExit> {
context.vm.push(context.construct_object());
Ok(ShouldExit::False)
}
}

23
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<ShouldExit> {
let value = context.vm.pop();
let value = value.require_object_coercible()?;
context.vm.push(value);
Ok(ShouldExit::False)
}
}

62
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<ShouldExit> {
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<ShouldExit> {
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)
}
}

52
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<ShouldExit> {
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)
}
}

74
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<ShouldExit> {
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)
}
}

33
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<ShouldExit> {
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)
}
}

11
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::*;

77
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<ShouldExit> {
let index = context.vm.read::<u32>();
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)
}
}

187
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<ShouldExit> {
let index = context.vm.read::<u32>();
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<ShouldExit> {
let index = context.vm.read::<u32>();
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<ShouldExit> {
let index = context.vm.read::<u32>();
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<ShouldExit> {
let index = context.vm.read::<u32>();
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<ShouldExit> {
let index = context.vm.read::<u32>();
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)
}
}

220
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<ShouldExit> {
let index = context.vm.read::<u32>();
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::<JsString>(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<ShouldExit> {
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<ShouldExit> {
let index = context.vm.read::<u32>();
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::<JsString>(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<ShouldExit> {
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<ShouldExit> {
let index = context.vm.read::<u32>();
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::<JsString>(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<ShouldExit> {
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)
}
}

25
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<ShouldExit> {
let first = context.vm.pop();
let second = context.vm.pop();
context.vm.push(first);
context.vm.push(second);
Ok(ShouldExit::False)
}
}

49
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<ShouldExit> {
let address = context.vm.read::<u32>();
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<ShouldExit> {
let exit = context.vm.read::<u32>();
let _val = context.vm.pop();
context.vm.frame_mut().pc = exit as usize;
Ok(ShouldExit::False)
}
}

21
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<ShouldExit> {
let value = context.vm.pop();
Err(JsError::from_opaque(value))
}
}

41
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<ShouldExit> {
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<ShouldExit> {
let value = context.vm.pop();
let key = value.to_property_key(context)?;
context.vm.push(key);
Ok(ShouldExit::False)
}
}

164
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<ShouldExit> {
let next = context.vm.read::<u32>();
let finally = context.vm.read::<u32>();
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<ShouldExit> {
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<ShouldExit> {
let finally = context.vm.read::<u32>();
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<ShouldExit> {
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<ShouldExit> {
let frame = context.vm.frame_mut();
if frame.finally_return == FinallyReturn::Err {
frame.finally_return = FinallyReturn::None;
}
Ok(ShouldExit::False)
}
}

53
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<ShouldExit> {
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<ShouldExit> {
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)
}
}

53
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<ShouldExit> {
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<ShouldExit> {
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)
}
}

22
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<ShouldExit> {
let value = context.vm.pop();
context.vm.push(!value.to_boolean());
Ok(ShouldExit::False)
}
}

96
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<ShouldExit> {
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<ShouldExit> {
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<ShouldExit> {
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<ShouldExit> {
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)
}
}

22
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<ShouldExit> {
let _old = context.vm.pop();
context.vm.push(JsValue::undefined());
Ok(ShouldExit::False)
}
}

33
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<ShouldExit> {
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)
}
}
Loading…
Cancel
Save