Browse Source

Refactor ordinary VM calling (#3295)

* Refactor ordinary VM calling

- Prevent recursing in `__call__` and `__construct__` internal methods

* Apply review

* Refactor `Context::run()`

* Fix typo

* Apply review
pull/3398/head
Haled Odat 1 year ago committed by GitHub
parent
commit
1d66836a32
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 15
      boa_engine/src/builtins/eval/mod.rs
  2. 24
      boa_engine/src/builtins/generator/mod.rs
  3. 15
      boa_engine/src/builtins/json/mod.rs
  4. 2
      boa_engine/src/bytecompiler/class.rs
  5. 7
      boa_engine/src/bytecompiler/declarations.rs
  6. 1
      boa_engine/src/bytecompiler/expression/mod.rs
  7. 7
      boa_engine/src/bytecompiler/jump_control.rs
  8. 3
      boa_engine/src/bytecompiler/mod.rs
  9. 34
      boa_engine/src/context/mod.rs
  10. 32
      boa_engine/src/error.rs
  11. 66
      boa_engine/src/module/source.rs
  12. 26
      boa_engine/src/module/synthetic.rs
  13. 49
      boa_engine/src/object/internal_methods/bound_function.rs
  14. 301
      boa_engine/src/object/internal_methods/function.rs
  15. 91
      boa_engine/src/object/internal_methods/mod.rs
  16. 50
      boa_engine/src/object/internal_methods/proxy.rs
  17. 48
      boa_engine/src/object/operations.rs
  18. 19
      boa_engine/src/script.rs
  19. 87
      boa_engine/src/vm/call_frame/mod.rs
  20. 340
      boa_engine/src/vm/code_block.rs
  21. 10
      boa_engine/src/vm/flowgraph/mod.rs
  22. 221
      boa_engine/src/vm/mod.rs
  23. 18
      boa_engine/src/vm/opcode/await/mod.rs
  24. 55
      boa_engine/src/vm/opcode/call/mod.rs
  25. 53
      boa_engine/src/vm/opcode/control_flow/return.rs
  26. 123
      boa_engine/src/vm/opcode/environment/mod.rs
  27. 46
      boa_engine/src/vm/opcode/generator/mod.rs
  28. 14
      boa_engine/src/vm/opcode/generator/yield_stm.rs
  29. 47
      boa_engine/src/vm/opcode/get/argument.rs
  30. 2
      boa_engine/src/vm/opcode/get/mod.rs
  31. 50
      boa_engine/src/vm/opcode/mod.rs
  32. 43
      boa_engine/src/vm/opcode/new/mod.rs
  33. 41
      boa_engine/src/vm/opcode/rest_parameter/mod.rs

15
boa_engine/src/builtins/eval/mod.rs

@ -18,7 +18,7 @@ use crate::{
object::JsObject,
realm::Realm,
string::common::StaticJsStrings,
vm::{CallFrame, Opcode},
vm::{CallFrame, CallFrameFlags, Opcode},
Context, JsArgs, JsResult, JsString, JsValue,
};
use boa_ast::operations::{contains, contains_arguments, ContainsSymbol};
@ -256,9 +256,16 @@ impl Eval {
}
let env_fp = context.vm.environments.len() as u32;
context
.vm
.push_frame(CallFrame::new(code_block, None, None).with_env_fp(env_fp));
let environments = context.vm.environments.clone();
let realm = context.realm().clone();
context.vm.push_frame_with_stack(
CallFrame::new(code_block, None, environments, realm)
.with_env_fp(env_fp)
.with_flags(CallFrameFlags::EXIT_EARLY),
JsValue::undefined(),
JsValue::null(),
);
context.realm().resize_global_env();
let record = context.run();

24
boa_engine/src/builtins/generator/mod.rs

@ -12,7 +12,6 @@
use crate::{
builtins::iterable::create_iter_result_object,
context::intrinsics::Intrinsics,
environments::EnvironmentStack,
error::JsNativeError,
js_string,
object::{JsObject, CONSTRUCTOR},
@ -60,36 +59,28 @@ unsafe impl Trace for GeneratorState {
/// context/vm before the generator execution starts/resumes and after it has ended/yielded.
#[derive(Debug, Clone, Trace, Finalize)]
pub(crate) struct GeneratorContext {
pub(crate) environments: EnvironmentStack,
pub(crate) stack: Vec<JsValue>,
pub(crate) call_frame: Option<CallFrame>,
pub(crate) realm: Realm,
}
impl GeneratorContext {
/// Creates a new `GeneratorContext` from the raw `Context` state components.
pub(crate) fn new(
environments: EnvironmentStack,
stack: Vec<JsValue>,
call_frame: CallFrame,
realm: Realm,
) -> Self {
pub(crate) fn new(stack: Vec<JsValue>, call_frame: CallFrame) -> Self {
Self {
environments,
stack,
call_frame: Some(call_frame),
realm,
}
}
/// Creates a new `GeneratorContext` from the current `Context` state.
pub(crate) fn from_current(context: &mut Context<'_>) -> Self {
let mut frame = context.vm.frame().clone();
frame.environments = context.vm.environments.clone();
frame.realm = context.realm().clone();
let fp = context.vm.frame().fp as usize;
let this = Self {
environments: context.vm.environments.clone(),
call_frame: Some(context.vm.frame().clone()),
call_frame: Some(frame),
stack: context.vm.stack[fp..].to_vec(),
realm: context.realm().clone(),
};
context.vm.stack.truncate(fp);
@ -104,14 +95,13 @@ impl GeneratorContext {
resume_kind: GeneratorResumeKind,
context: &mut Context<'_>,
) -> CompletionRecord {
std::mem::swap(&mut context.vm.environments, &mut self.environments);
std::mem::swap(&mut context.vm.stack, &mut self.stack);
context.swap_realm(&mut self.realm);
context
.vm
.push_frame(self.call_frame.take().expect("should have a call frame"));
context.vm.frame_mut().fp = 0;
context.vm.frame_mut().set_exit_early(true);
if let Some(value) = value {
context.vm.push(value);
@ -120,9 +110,7 @@ impl GeneratorContext {
let result = context.run();
std::mem::swap(&mut context.vm.environments, &mut self.environments);
std::mem::swap(&mut context.vm.stack, &mut self.stack);
context.swap_realm(&mut self.realm);
self.call_frame = context.vm.pop_frame();
assert!(self.call_frame.is_some());
result

15
boa_engine/src/builtins/json/mod.rs

@ -29,7 +29,7 @@ use crate::{
string::{common::StaticJsStrings, utf16, CodePoint},
symbol::JsSymbol,
value::IntegerOrInfinity,
vm::CallFrame,
vm::{CallFrame, CallFrameFlags},
Context, JsArgs, JsResult, JsString, JsValue,
};
use boa_gc::Gc;
@ -129,10 +129,17 @@ impl Json {
Gc::new(compiler.finish())
};
let realm = context.realm().clone();
let env_fp = context.vm.environments.len() as u32;
context
.vm
.push_frame(CallFrame::new(code_block, None, None).with_env_fp(env_fp));
context.vm.push_frame_with_stack(
CallFrame::new(code_block, None, context.vm.environments.clone(), realm)
.with_env_fp(env_fp)
.with_flags(CallFrameFlags::EXIT_EARLY),
JsValue::undefined(),
JsValue::null(),
);
context.realm().resize_global_env();
let record = context.run();
context.vm.pop_frame();

2
boa_engine/src/bytecompiler/class.rs

@ -74,8 +74,8 @@ impl ByteCompiler<'_, '_> {
compiler.emit_opcode(Opcode::PushUndefined);
} else if class.super_ref().is_some() {
compiler.emit_opcode(Opcode::SuperCallDerived);
compiler.emit_opcode(Opcode::BindThisValue);
} else {
compiler.emit_opcode(Opcode::RestParameterPop);
compiler.emit_opcode(Opcode::PushUndefined);
}
compiler.emit_opcode(Opcode::SetReturnValue);

7
boa_engine/src/bytecompiler/declarations.rs

@ -1007,9 +1007,11 @@ impl ByteCompiler<'_, '_> {
// a. Perform ? IteratorBindingInitialization of formals with arguments iteratorRecord and undefined.
// 26. Else,
// a. Perform ? IteratorBindingInitialization of formals with arguments iteratorRecord and env.
for parameter in formals.as_ref() {
for (i, parameter) in formals.as_ref().iter().enumerate() {
if parameter.is_rest_param() {
self.emit_opcode(Opcode::RestParameterInit);
} else {
self.emit_with_varying_operand(Opcode::GetArgument, i as u32);
}
match parameter.variable().binding() {
Binding::Identifier(ident) => {
@ -1030,9 +1032,6 @@ impl ByteCompiler<'_, '_> {
}
}
}
if !formals.has_rest_parameter() {
self.emit_opcode(Opcode::RestParameterPop);
}
if generator {
self.emit(Opcode::Generator, &[Operand::U8(self.in_async().into())]);

1
boa_engine/src/bytecompiler/expression/mod.rs

@ -326,6 +326,7 @@ impl ByteCompiler<'_, '_> {
super_call.arguments().len() as u32,
);
}
self.emit_opcode(Opcode::BindThisValue);
if !use_expr {
self.emit_opcode(Opcode::Pop);

7
boa_engine/src/bytecompiler/jump_control.rs

@ -141,7 +141,12 @@ impl JumpRecord {
//
// Note: If there is promise capability resolve or reject it based on pending exception.
(true, false) => compiler.emit_opcode(Opcode::CompletePromiseCapability),
(_, _) => {}
(false, false) => {
// TODO: We can omit checking for return, when constructing for functions,
// that cannot be constructed, like arrow functions.
compiler.emit_opcode(Opcode::CheckReturn);
}
(false, true) => {}
}
compiler.emit_opcode(Opcode::Return);

3
boa_engine/src/bytecompiler/mod.rs

@ -324,7 +324,8 @@ impl<'ctx, 'host> ByteCompiler<'ctx, 'host> {
compile_environments: Vec::default(),
current_open_environments_count: 0,
current_stack_value_count: 0,
// This starts at two because the first value is the `this` value, then function object.
current_stack_value_count: 2,
code_block_flags,
handlers: ThinVec::default(),

34
boa_engine/src/context/mod.rs

@ -82,9 +82,6 @@ use self::intrinsics::StandardConstructor;
/// assert_eq!(value.as_number(), Some(12.0))
/// ```
pub struct Context<'host> {
/// realm holds both the global object and the environment
realm: Realm,
/// String interner in the context.
interner: Interner,
@ -121,7 +118,7 @@ impl std::fmt::Debug for Context<'_> {
let mut debug = f.debug_struct("Context");
debug
.field("realm", &self.realm)
.field("realm", &self.vm.realm)
.field("interner", &self.interner)
.field("vm", &self.vm)
.field("strict", &self.strict)
@ -268,7 +265,7 @@ impl<'host> Context<'host> {
length: usize,
body: NativeFunction,
) -> JsResult<()> {
let function = FunctionObjectBuilder::new(&self.realm, body)
let function = FunctionObjectBuilder::new(self.realm(), body)
.name(name.clone())
.length(length)
.constructor(true)
@ -301,7 +298,7 @@ impl<'host> Context<'host> {
length: usize,
body: NativeFunction,
) -> JsResult<()> {
let function = FunctionObjectBuilder::new(&self.realm, body)
let function = FunctionObjectBuilder::new(self.realm(), body)
.name(name.clone())
.length(length)
.constructor(false)
@ -335,7 +332,7 @@ impl<'host> Context<'host> {
/// context.register_global_class::<MyClass>()?;
/// ```
pub fn register_global_class<C: Class>(&mut self) -> JsResult<()> {
if self.realm.has_class::<C>() {
if self.realm().has_class::<C>() {
return Err(JsNativeError::typ()
.with_message("cannot register a class twice")
.into());
@ -353,7 +350,7 @@ impl<'host> Context<'host> {
self.global_object()
.define_property_or_throw(js_string!(C::NAME), property, self)?;
self.realm.register_class::<C>(class);
self.realm().register_class::<C>(class);
Ok(())
}
@ -384,20 +381,20 @@ impl<'host> Context<'host> {
pub fn unregister_global_class<C: Class>(&mut self) -> JsResult<Option<StandardConstructor>> {
self.global_object()
.delete_property_or_throw(js_string!(C::NAME), self)?;
Ok(self.realm.unregister_class::<C>())
Ok(self.realm().unregister_class::<C>())
}
/// Checks if the currently active realm has the global class `C` registered.
#[must_use]
pub fn has_global_class<C: Class>(&self) -> bool {
self.realm.has_class::<C>()
self.realm().has_class::<C>()
}
/// Gets the constructor and prototype of the global class `C` if the currently active realm has
/// that class registered.
#[must_use]
pub fn get_global_class<C: Class>(&self) -> Option<StandardConstructor> {
self.realm.get_class::<C>()
self.realm().get_class::<C>()
}
/// Gets the string interner.
@ -417,21 +414,21 @@ impl<'host> Context<'host> {
#[inline]
#[must_use]
pub fn global_object(&self) -> JsObject {
self.realm.global_object().clone()
self.vm.realm.global_object().clone()
}
/// Returns the currently active intrinsic constructors and objects.
#[inline]
#[must_use]
pub fn intrinsics(&self) -> &Intrinsics {
self.realm.intrinsics()
self.vm.realm.intrinsics()
}
/// Returns the currently active realm.
#[inline]
#[must_use]
pub const fn realm(&self) -> &Realm {
&self.realm
&self.vm.realm
}
/// Set the value of trace on the context
@ -510,7 +507,7 @@ impl<'host> Context<'host> {
self.vm
.environments
.replace_global(realm.environment().clone());
std::mem::replace(&mut self.realm, realm)
std::mem::replace(&mut self.vm.realm, realm)
}
/// Create a new Realm with the default global bindings.
@ -576,7 +573,7 @@ impl<'host> Context<'host> {
impl Context<'_> {
/// Swaps the currently active realm with `realm`.
pub(crate) fn swap_realm(&mut self, realm: &mut Realm) {
std::mem::swap(&mut self.realm, realm);
std::mem::swap(&mut self.vm.realm, realm);
}
/// Increment and get the parser identifier.
@ -805,7 +802,7 @@ impl Context<'_> {
}
if let Some(frame) = self.vm.frames.last() {
return frame.active_function.clone();
return frame.function(&self.vm);
}
None
@ -998,7 +995,7 @@ impl<'icu, 'hooks, 'queue, 'module> ContextBuilder<'icu, 'hooks, 'queue, 'module
hooks.into()
});
let realm = Realm::create(&*host_hooks, &root_shape);
let vm = Vm::new(realm.environment().clone());
let vm = Vm::new(realm);
let module_loader = if let Some(loader) = self.module_loader {
loader
@ -1016,7 +1013,6 @@ impl<'icu, 'hooks, 'queue, 'module> ContextBuilder<'icu, 'hooks, 'queue, 'module
};
let mut context = Context {
realm,
interner: self.interner.unwrap_or_default(),
vm,
strict: false,

32
boa_engine/src/error.rs

@ -379,6 +379,12 @@ impl JsError {
}
self
}
/// Is the [`JsError`] catchable in JavaScript.
#[inline]
pub(crate) fn is_catchable(&self) -> bool {
self.as_native().map_or(true, JsNativeError::is_catchable)
}
}
impl From<boa_parser::Error> for JsError {
@ -873,6 +879,12 @@ impl JsNativeError {
self.realm = Some(realm);
self
}
/// Is the [`JsNativeError`] catchable in JavaScript.
#[inline]
pub(crate) fn is_catchable(&self) -> bool {
self.kind.is_catchable()
}
}
impl From<boa_parser::Error> for JsNativeError {
@ -978,6 +990,26 @@ pub enum JsNativeErrorKind {
RuntimeLimit,
}
impl JsNativeErrorKind {
/// Is the [`JsNativeErrorKind`] catchable in JavaScript.
#[inline]
pub(crate) fn is_catchable(&self) -> bool {
match self {
Self::Aggregate(_)
| Self::Error
| Self::Eval
| Self::Range
| Self::Reference
| Self::Syntax
| Self::Type
| Self::Uri => true,
Self::RuntimeLimit => false,
#[cfg(feature = "fuzz")]
Self::NoInstructionsRemain => false,
}
}
}
impl PartialEq<ErrorKind> for JsNativeErrorKind {
fn eq(&self, other: &ErrorKind) -> bool {
matches!(

66
boa_engine/src/module/source.rs

@ -29,7 +29,7 @@ use crate::{
realm::Realm,
vm::{
create_function_object_fast, create_generator_function_object, ActiveRunnable, CallFrame,
CodeBlock, CompletionRecord, Opcode,
CallFrameFlags, CodeBlock, CompletionRecord, Opcode,
},
Context, JsArgs, JsError, JsNativeError, JsObject, JsResult, JsString, JsValue, NativeFunction,
};
@ -1402,7 +1402,7 @@ impl SourceTextModule {
// 2. Assert: All named exports from module are resolvable.
// 3. Let realm be module.[[Realm]].
// 4. Assert: realm is not undefined.
let mut realm = parent.realm().clone();
let realm = parent.realm().clone();
// 5. Let env be NewModuleEnvironment(realm.[[GlobalEnv]]).
// 6. Set module.[[Environment]] to env.
@ -1600,22 +1600,22 @@ impl SourceTextModule {
envs.push_module(env);
// 9. Set the Function of moduleContext to null.
// 10. Assert: module.[[Realm]] is not undefined.
// 11. Set the Realm of moduleContext to module.[[Realm]].
// 12. Set the ScriptOrModule of moduleContext to module.
// 13. Set the VariableEnvironment of moduleContext to module.[[Environment]].
// 14. Set the LexicalEnvironment of moduleContext to module.[[Environment]].
// 15. Set the PrivateEnvironment of moduleContext to null.
let call_frame = CallFrame::new(
codeblock.clone(),
Some(ActiveRunnable::Module(parent.clone())),
None,
envs,
realm.clone(),
);
context.vm.push_frame(call_frame);
// 13. Set the VariableEnvironment of moduleContext to module.[[Environment]].
// 14. Set the LexicalEnvironment of moduleContext to module.[[Environment]].
// 15. Set the PrivateEnvironment of moduleContext to null.
std::mem::swap(&mut context.vm.environments, &mut envs);
context
.vm
.push_frame_with_stack(call_frame, JsValue::undefined(), JsValue::null());
// 10. Assert: module.[[Realm]] is not undefined.
// 11. Set the Realm of moduleContext to module.[[Realm]].
context.swap_realm(&mut realm);
// 17. Push moduleContext onto the execution context stack; moduleContext is now the running execution context.
// deferred initialization of import bindings
@ -1673,15 +1673,14 @@ impl SourceTextModule {
}
// 25. Remove moduleContext from the execution context stack.
context
let frame = context
.vm
.pop_frame()
.expect("There should be a call frame");
std::mem::swap(&mut context.vm.environments, &mut envs);
context.swap_realm(&mut realm);
debug_assert!(envs.current().as_declarative().is_some());
*parent.inner.environment.borrow_mut() = envs.current().as_declarative().cloned();
debug_assert!(frame.environments.current().as_declarative().is_some());
*parent.inner.environment.borrow_mut() =
frame.environments.current().as_declarative().cloned();
// 16. Set module.[[Context]] to moduleContext.
self.inner
@ -1692,7 +1691,7 @@ impl SourceTextModule {
info,
context: SourceTextContext {
codeblock,
environments: envs,
environments: frame.environments.clone(),
realm,
},
},
@ -1716,8 +1715,8 @@ impl SourceTextModule {
// 1. Let moduleContext be a new ECMAScript code execution context.
let SourceTextContext {
codeblock,
mut environments,
mut realm,
environments,
realm,
} = match &*self.inner.status.borrow() {
Status::Evaluating { context, .. } | Status::EvaluatingAsync { context, .. } => {
context.clone()
@ -1726,21 +1725,26 @@ impl SourceTextModule {
};
// 2. Set the Function of moduleContext to null.
// 3. Set the Realm of moduleContext to module.[[Realm]].
// 4. Set the ScriptOrModule of moduleContext to module.
let env_fp = environments.len() as u32;
let mut callframe =
CallFrame::new(codeblock, Some(ActiveRunnable::Module(self.parent())), None)
.with_env_fp(env_fp);
callframe.promise_capability = capability;
// 5. Assert: module has been linked and declarations in its module environment have been instantiated.
// 6. Set the VariableEnvironment of moduleContext to module.[[Environment]].
// 7. Set the LexicalEnvironment of moduleContext to module.[[Environment]].
std::mem::swap(&mut context.vm.environments, &mut environments);
// 3. Set the Realm of moduleContext to module.[[Realm]].
context.swap_realm(&mut realm);
let env_fp = environments.len() as u32;
let mut callframe = CallFrame::new(
codeblock,
Some(ActiveRunnable::Module(self.parent())),
environments,
realm,
)
.with_env_fp(env_fp)
.with_flags(CallFrameFlags::EXIT_EARLY);
callframe.promise_capability = capability;
// 8. Suspend the running execution context.
context.vm.push_frame(callframe);
context
.vm
.push_frame_with_stack(callframe, JsValue::undefined(), JsValue::null());
// 9. If module.[[HasTLA]] is false, then
// a. Assert: capability is not present.
@ -1753,8 +1757,6 @@ impl SourceTextModule {
// b. Perform AsyncBlockStart(capability, module.[[ECMAScriptCode]], moduleContext).
let result = context.run();
std::mem::swap(&mut context.vm.environments, &mut environments);
context.swap_realm(&mut realm);
context.vm.pop_frame();
// f. If result is an abrupt completion, then

26
boa_engine/src/module/synthetic.rs

@ -292,8 +292,8 @@ impl SyntheticModule {
// 1. Let moduleContext be a new ECMAScript code execution context.
let parent = self.parent();
let mut realm = parent.realm().clone();
let (mut environments, codeblock) = self
let realm = parent.realm().clone();
let (environments, codeblock) = self
.inner
.eval_context
.borrow()
@ -305,19 +305,20 @@ impl SyntheticModule {
codeblock,
// 4. Set the ScriptOrModule of moduleContext to module.
Some(ActiveRunnable::Module(parent)),
// 2. Set the Function of moduleContext to null.
None,
)
.with_env_fp(env_fp);
// 5. Set the VariableEnvironment of moduleContext to module.[[Environment]].
// 6. Set the LexicalEnvironment of moduleContext to module.[[Environment]].
std::mem::swap(&mut context.vm.environments, &mut environments);
environments,
// 3. Set the Realm of moduleContext to module.[[Realm]].
context.swap_realm(&mut realm);
realm,
)
.with_env_fp(env_fp);
// 2. Set the Function of moduleContext to null.
// 7. Suspend the currently running execution context.
// 8. Push moduleContext on to the execution context stack; moduleContext is now the running execution context.
context.vm.push_frame(callframe);
context
.vm
.push_frame_with_stack(callframe, JsValue::undefined(), JsValue::null());
// 9. Let steps be module.[[EvaluationSteps]].
// 10. Let result be Completion(steps(module)).
@ -325,9 +326,8 @@ impl SyntheticModule {
// 11. Suspend moduleContext and remove it from the execution context stack.
// 12. Resume the context that is now on the top of the execution context stack as the running execution context.
std::mem::swap(&mut context.vm.environments, &mut environments);
context.swap_realm(&mut realm);
context.vm.pop_frame();
let frame = context.vm.pop_frame().expect("there should be a frame");
context.vm.stack.truncate(frame.fp as usize);
// 13. Let pc be ! NewPromiseCapability(%Promise%).
let (promise, ResolvingFunctions { resolve, reject }) = JsPromise::new_pending(context);

49
boa_engine/src/object/internal_methods/bound_function.rs

@ -1,6 +1,6 @@
use crate::{object::JsObject, Context, JsResult, JsValue};
use super::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
use super::{CallValue, InternalObjectMethods, ORDINARY_INTERNAL_METHODS};
/// Definitions of the internal object methods for function objects.
///
@ -27,33 +27,37 @@ pub(crate) static BOUND_CONSTRUCTOR_EXOTIC_INTERNAL_METHODS: InternalObjectMetho
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-bound-function-exotic-objects-call-thisargument-argumentslist
#[track_caller]
#[allow(clippy::unnecessary_wraps)]
fn bound_function_exotic_call(
obj: &JsObject,
_: &JsValue,
arguments_list: &[JsValue],
argument_count: usize,
context: &mut Context<'_>,
) -> JsResult<JsValue> {
) -> JsResult<CallValue> {
let obj = obj.borrow();
let bound_function = obj
.as_bound_function()
.expect("bound function exotic method should only be callable from bound function objects");
let arguments_start_index = context.vm.stack.len() - argument_count;
// 1. Let target be F.[[BoundTargetFunction]].
let target = bound_function.target_function();
context.vm.stack[arguments_start_index - 1] = target.clone().into();
// 2. Let boundThis be F.[[BoundThis]].
let bound_this = bound_function.this();
context.vm.stack[arguments_start_index - 2] = bound_this.clone();
// 3. Let boundArgs be F.[[BoundArguments]].
let bound_args = bound_function.args();
// 4. Let args be the list-concatenation of boundArgs and argumentsList.
let mut args = bound_args.to_vec();
args.extend_from_slice(arguments_list);
context
.vm
.insert_values_at(bound_args, arguments_start_index);
// 5. Return ? Call(target, boundThis, args).
target.call(bound_this, &args, context)
Ok(target.__call__(bound_args.len() + argument_count))
}
/// Internal method `[[Construct]]` for Bound Function Exotic Objects
@ -62,14 +66,17 @@ fn bound_function_exotic_call(
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-bound-function-exotic-objects-construct-argumentslist-newtarget
#[track_caller]
#[allow(clippy::unnecessary_wraps)]
fn bound_function_exotic_construct(
obj: &JsObject,
arguments_list: &[JsValue],
new_target: &JsObject,
function_object: &JsObject,
argument_count: usize,
context: &mut Context<'_>,
) -> JsResult<JsObject> {
let object = obj.borrow();
) -> JsResult<CallValue> {
let new_target = context.vm.pop();
debug_assert!(new_target.is_object(), "new.target should be an object");
let object = function_object.borrow();
let bound_function = object
.as_bound_function()
.expect("bound function exotic method should only be callable from bound function objects");
@ -83,16 +90,20 @@ fn bound_function_exotic_construct(
let bound_args = bound_function.args();
// 4. Let args be the list-concatenation of boundArgs and argumentsList.
let mut args = bound_args.to_vec();
args.extend_from_slice(arguments_list);
let arguments_start_index = context.vm.stack.len() - argument_count;
context
.vm
.insert_values_at(bound_args, arguments_start_index);
// 5. If SameValue(F, newTarget) is true, set newTarget to target.
let new_target = if JsObject::equals(obj, new_target) {
target
let function_object: JsValue = function_object.clone().into();
let new_target = if JsValue::same_value(&function_object, &new_target) {
target.clone().into()
} else {
new_target
};
// 6. Return ? Construct(target, args, newTarget).
target.construct(&args, Some(new_target), context)
context.vm.push(new_target);
Ok(target.__construct__(bound_args.len() + argument_count))
}

301
boa_engine/src/object/internal_methods/function.rs

@ -1,13 +1,16 @@
use crate::{
builtins::function::{arguments::Arguments, FunctionKind, ThisMode},
context::intrinsics::StandardConstructors,
environments::{FunctionSlots, ThisBindingStatus},
object::{
internal_methods::{InternalObjectMethods, ORDINARY_INTERNAL_METHODS},
JsObject, ObjectData, ObjectKind,
},
vm::{CallFrame, CallFrameFlags},
Context, JsNativeError, JsResult, JsValue,
};
use super::get_prototype_from_constructor;
use super::{get_prototype_from_constructor, CallValue};
/// Definitions of the internal object methods for function objects.
///
@ -33,13 +36,122 @@ pub(crate) static CONSTRUCTOR_INTERNAL_METHODS: InternalObjectMethods = Internal
/// Panics if the object is currently mutably borrowed.
// <https://tc39.es/ecma262/#sec-prepareforordinarycall>
// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist>
fn function_call(
obj: &JsObject,
this: &JsValue,
args: &[JsValue],
pub(crate) fn function_call(
function_object: &JsObject,
argument_count: usize,
context: &mut Context<'_>,
) -> JsResult<JsValue> {
obj.call_internal(this, args, context)
) -> JsResult<CallValue> {
context.check_runtime_limits()?;
let object = function_object.borrow();
let function = object.as_function().expect("not a function");
let realm = function.realm().clone();
if let FunctionKind::Ordinary { .. } = function.kind() {
if function.code.is_class_constructor() {
return Err(JsNativeError::typ()
.with_message("class constructor cannot be invoked without 'new'")
.with_realm(realm)
.into());
}
}
let code = function.code.clone();
let environments = function.environments.clone();
let script_or_module = function.script_or_module.clone();
drop(object);
let env_fp = environments.len() as u32;
let frame = CallFrame::new(code.clone(), script_or_module, environments, realm)
.with_argument_count(argument_count as u32)
.with_env_fp(env_fp);
context.vm.push_frame(frame);
let fp = context.vm.stack.len() - argument_count - CallFrame::FUNCTION_PROLOGUE;
context.vm.frame_mut().fp = fp as u32;
let this = context.vm.stack[fp + CallFrame::THIS_POSITION].clone();
let lexical_this_mode = code.this_mode == ThisMode::Lexical;
let this = if lexical_this_mode {
ThisBindingStatus::Lexical
} else if code.strict() {
ThisBindingStatus::Initialized(this.clone())
} else if this.is_null_or_undefined() {
ThisBindingStatus::Initialized(context.realm().global_this().clone().into())
} else {
ThisBindingStatus::Initialized(
this.to_object(context)
.expect("conversion cannot fail")
.into(),
)
};
let mut last_env = 0;
if code.has_binding_identifier() {
let index = context
.vm
.environments
.push_lexical(code.compile_environments[last_env].clone());
context
.vm
.environments
.put_lexical_value(index, 0, function_object.clone().into());
last_env += 1;
}
context.vm.environments.push_function(
code.compile_environments[last_env].clone(),
FunctionSlots::new(this, function_object.clone(), None),
);
if code.has_parameters_env_bindings() {
last_env += 1;
context
.vm
.environments
.push_lexical(code.compile_environments[last_env].clone());
}
// Taken from: `FunctionDeclarationInstantiation` abstract function.
//
// Spec: https://tc39.es/ecma262/#sec-functiondeclarationinstantiation
//
// 22. If argumentsObjectNeeded is true, then
if code.needs_arguments_object() {
// a. If strict is true or simpleParameterList is false, then
// i. Let ao be CreateUnmappedArgumentsObject(argumentsList).
// b. Else,
// i. NOTE: A mapped argument object is only provided for non-strict functions
// that don't have a rest parameter, any parameter
// default value initializers, or any destructured parameters.
// ii. Let ao be CreateMappedArgumentsObject(func, formals, argumentsList, env).
let args = context.vm.stack[(fp + CallFrame::FIRST_ARGUMENT_POSITION)..].to_vec();
let arguments_obj = if code.strict() || !code.params.is_simple() {
Arguments::create_unmapped_arguments_object(&args, context)
} else {
let env = context.vm.environments.current();
Arguments::create_mapped_arguments_object(
function_object,
&code.params,
&args,
env.declarative_expect(),
context,
)
};
let env_index = context.vm.environments.len() as u32 - 1;
context
.vm
.environments
.put_lexical_value(env_index, 0, arguments_obj.into());
}
Ok(CallValue::Ready)
}
/// Construct an instance of this object with the specified arguments.
@ -49,12 +161,142 @@ fn function_call(
/// Panics if the object is currently mutably borrowed.
// <https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget>
fn function_construct(
obj: &JsObject,
args: &[JsValue],
new_target: &JsObject,
this_function_object: &JsObject,
argument_count: usize,
context: &mut Context<'_>,
) -> JsResult<JsObject> {
obj.construct_internal(args, &new_target.clone().into(), context)
) -> JsResult<CallValue> {
context.check_runtime_limits()?;
let object = this_function_object.borrow();
let function = object.as_function().expect("not a function");
let realm = function.realm().clone();
let FunctionKind::Ordinary {
constructor_kind, ..
} = function.kind()
else {
unreachable!("not a constructor")
};
let code = function.code.clone();
let environments = function.environments.clone();
let script_or_module = function.script_or_module.clone();
let constructor_kind = *constructor_kind;
drop(object);
let env_fp = environments.len() as u32;
let new_target = context.vm.pop();
let at = context.vm.stack.len() - argument_count;
let this = if constructor_kind.is_base() {
// If the prototype of the constructor is not an object, then use the default object
// prototype as prototype for the new object
// see <https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor>
// see <https://tc39.es/ecma262/#sec-getprototypefromconstructor>
let prototype =
get_prototype_from_constructor(&new_target, StandardConstructors::object, context)?;
let this = JsObject::from_proto_and_data_with_shared_shape(
context.root_shape(),
prototype,
ObjectData::ordinary(),
);
this.initialize_instance_elements(this_function_object, context)?;
Some(this)
} else {
None
};
let frame = CallFrame::new(code.clone(), script_or_module, environments, realm)
.with_argument_count(argument_count as u32)
.with_env_fp(env_fp)
.with_flags(CallFrameFlags::CONSTRUCT);
context.vm.push_frame(frame);
context.vm.frame_mut().fp = at as u32 - 1;
let mut last_env = 0;
if code.has_binding_identifier() {
let index = context
.vm
.environments
.push_lexical(code.compile_environments[last_env].clone());
context
.vm
.environments
.put_lexical_value(index, 0, this_function_object.clone().into());
last_env += 1;
}
context.vm.environments.push_function(
code.compile_environments[last_env].clone(),
FunctionSlots::new(
this.clone().map_or(ThisBindingStatus::Uninitialized, |o| {
ThisBindingStatus::Initialized(o.into())
}),
this_function_object.clone(),
Some(
new_target
.as_object()
.expect("new.target should be an object")
.clone(),
),
),
);
if code.has_parameters_env_bindings() {
last_env += 1;
context
.vm
.environments
.push_lexical(code.compile_environments[last_env].clone());
}
// Taken from: `FunctionDeclarationInstantiation` abstract function.
//
// Spec: https://tc39.es/ecma262/#sec-functiondeclarationinstantiation
//
// 22. If argumentsObjectNeeded is true, then
if code.needs_arguments_object() {
// a. If strict is true or simpleParameterList is false, then
// i. Let ao be CreateUnmappedArgumentsObject(argumentsList).
// b. Else,
// i. NOTE: A mapped argument object is only provided for non-strict functions
// that don't have a rest parameter, any parameter
// default value initializers, or any destructured parameters.
// ii. Let ao be CreateMappedArgumentsObject(func, formals, argumentsList, env).
let args = context.vm.stack[at..].to_vec();
let arguments_obj = if code.strict() || !code.params.is_simple() {
Arguments::create_unmapped_arguments_object(&args, context)
} else {
let env = context.vm.environments.current();
Arguments::create_mapped_arguments_object(
this_function_object,
&code.params,
&args,
env.declarative_expect(),
context,
)
};
let env_index = context.vm.environments.len() as u32 - 1;
context
.vm
.environments
.put_lexical_value(env_index, 0, arguments_obj.into());
}
// Insert `this` value
context
.vm
.stack
.insert(at - 1, this.map(JsValue::new).unwrap_or_default());
Ok(CallValue::Ready)
}
/// Definitions of the internal object methods for native function objects.
@ -81,13 +323,15 @@ pub(crate) static NATIVE_CONSTRUCTOR_INTERNAL_METHODS: InternalObjectMethods =
///
/// Panics if the object is currently mutably borrowed.
// <https://tc39.es/ecma262/#sec-built-in-function-objects-call-thisargument-argumentslist>
#[track_caller]
pub(crate) fn native_function_call(
obj: &JsObject,
this: &JsValue,
args: &[JsValue],
argument_count: usize,
context: &mut Context<'_>,
) -> JsResult<JsValue> {
) -> JsResult<CallValue> {
let args = context.vm.pop_n_values(argument_count);
let _func = context.vm.pop();
let this = context.vm.pop();
// We technically don't need this since native functions don't push any new frames to the
// vm, but we'll eventually have to combine the native stack with the vm stack.
context.check_runtime_limits()?;
@ -112,16 +356,18 @@ pub(crate) fn native_function_call(
context.vm.native_active_function = Some(this_function_object);
let result = if constructor.is_some() {
function.call(&JsValue::undefined(), args, context)
function.call(&JsValue::undefined(), &args, context)
} else {
function.call(this, args, context)
function.call(&this, &args, context)
}
.map_err(|err| err.inject_realm(context.realm().clone()));
context.vm.native_active_function = None;
context.swap_realm(&mut realm);
result
context.vm.push(result?);
Ok(CallValue::Complete)
}
/// Construct an instance of this object with the specified arguments.
@ -130,13 +376,11 @@ pub(crate) fn native_function_call(
///
/// Panics if the object is currently mutably borrowed.
// <https://tc39.es/ecma262/#sec-built-in-function-objects-construct-argumentslist-newtarget>
#[track_caller]
fn native_function_construct(
obj: &JsObject,
args: &[JsValue],
new_target: &JsObject,
argument_count: usize,
context: &mut Context<'_>,
) -> JsResult<JsObject> {
) -> JsResult<CallValue> {
// We technically don't need this since native functions don't push any new frames to the
// vm, but we'll eventually have to combine the native stack with the vm stack.
context.check_runtime_limits()?;
@ -160,9 +404,12 @@ fn native_function_construct(
context.swap_realm(&mut realm);
context.vm.native_active_function = Some(this_function_object);
let new_target = new_target.clone().into();
let new_target = context.vm.pop();
let args = context.vm.pop_n_values(argument_count);
let _func = context.vm.pop();
let result = function
.call(&new_target, args, context)
.call(&new_target, &args, context)
.map_err(|err| err.inject_realm(context.realm().clone()))
.and_then(|v| match v {
JsValue::Object(ref o) => Ok(o.clone()),
@ -189,5 +436,7 @@ fn native_function_construct(
context.vm.native_active_function = None;
context.swap_realm(&mut realm);
result
context.vm.push(result?);
Ok(CallValue::Complete)
}

91
boa_engine/src/object/internal_methods/mod.rs

@ -211,40 +211,38 @@ impl JsObject {
/// Internal method `[[Call]]`
///
/// Call this object if it has a `[[Call]]` internal method.
/// The caller must ensure that the following values are pushed on the stack.
///
/// Stack: `this, function, arg0, arg1, ..., argN`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist
#[track_caller]
pub(crate) fn __call__(
&self,
this: &JsValue,
args: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<JsValue> {
let _timer = Profiler::global().start_event("Object::__call__", "object");
(self.vtable().__call__)(self, this, args, context)
pub(crate) fn __call__(&self, argument_count: usize) -> CallValue {
CallValue::Pending {
func: self.vtable().__call__,
object: self.clone(),
argument_count,
}
}
/// Internal method `[[Construct]]`
///
/// Construct a new instance of this object if this object has a `[[Construct]]` internal method.
/// The caller must ensure that the following values are pushed on the stack.
///
/// Stack: `function, arg0, arg1, ..., argN, new.target`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget
#[track_caller]
pub(crate) fn __construct__(
&self,
args: &[JsValue],
new_target: &Self,
context: &mut Context<'_>,
) -> JsResult<Self> {
let _timer = Profiler::global().start_event("Object::__construct__", "object");
(self.vtable().__construct__)(self, args, new_target, context)
pub(crate) fn __construct__(&self, argument_count: usize) -> CallValue {
CallValue::Pending {
func: self.vtable().__construct__,
object: self.clone(),
argument_count,
}
}
}
@ -299,9 +297,46 @@ pub(crate) struct InternalObjectMethods {
fn(&JsObject, PropertyKey, JsValue, JsValue, &mut Context<'_>) -> JsResult<bool>,
pub(crate) __delete__: fn(&JsObject, &PropertyKey, &mut Context<'_>) -> JsResult<bool>,
pub(crate) __own_property_keys__: fn(&JsObject, &mut Context<'_>) -> JsResult<Vec<PropertyKey>>,
pub(crate) __call__: fn(&JsObject, &JsValue, &[JsValue], &mut Context<'_>) -> JsResult<JsValue>,
pub(crate) __call__:
fn(&JsObject, argument_count: usize, &mut Context<'_>) -> JsResult<CallValue>,
pub(crate) __construct__:
fn(&JsObject, &[JsValue], &JsObject, &mut Context<'_>) -> JsResult<JsObject>,
fn(&JsObject, argument_count: usize, &mut Context<'_>) -> JsResult<CallValue>,
}
/// The return value of an internal method (`[[Call]]` or `[[Construct]]`).
///
/// This is done to avoid recursion.
pub(crate) enum CallValue {
/// Calling is ready, the frames have been setup.
///
/// Requires calling [`Context::run()`].
Ready,
/// Further processing is needed.
Pending {
func: fn(&JsObject, argument_count: usize, &mut Context<'_>) -> JsResult<CallValue>,
object: JsObject,
argument_count: usize,
},
/// The value has been computed and is the first element on the stack.
Complete,
}
impl CallValue {
/// Resolves the [`CallValue`], and return if the value is complete.
pub(crate) fn resolve(mut self, context: &mut Context<'_>) -> JsResult<bool> {
while let Self::Pending {
func,
object,
argument_count,
} = self
{
self = func(&object, argument_count, context)?;
}
Ok(matches!(self, Self::Complete))
}
}
/// Abstract operation `OrdinaryGetPrototypeOf`.
@ -913,10 +948,9 @@ where
pub(crate) fn non_existant_call(
_obj: &JsObject,
_this: &JsValue,
_args: &[JsValue],
_argument_count: usize,
context: &mut Context<'_>,
) -> JsResult<JsValue> {
) -> JsResult<CallValue> {
Err(JsNativeError::typ()
.with_message("not a callable function")
.with_realm(context.realm().clone())
@ -925,12 +959,11 @@ pub(crate) fn non_existant_call(
pub(crate) fn non_existant_construct(
_obj: &JsObject,
_args: &[JsValue],
_new_target: &JsObject,
_argument_count: usize,
context: &mut Context<'_>,
) -> JsResult<JsObject> {
) -> JsResult<CallValue> {
Err(JsNativeError::typ()
.with_message("object is not constructable")
.with_message("not a constructor")
.with_realm(context.realm().clone())
.into())
}

50
boa_engine/src/object/internal_methods/proxy.rs

@ -9,7 +9,7 @@ use crate::{
};
use rustc_hash::FxHashSet;
use super::ORDINARY_INTERNAL_METHODS;
use super::{CallValue, ORDINARY_INTERNAL_METHODS};
/// Definitions of the internal object methods for array exotic objects.
///
@ -922,10 +922,9 @@ pub(crate) fn proxy_exotic_own_property_keys(
/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-call-thisargument-argumentslist
fn proxy_exotic_call(
obj: &JsObject,
this: &JsValue,
args: &[JsValue],
argument_count: usize,
context: &mut Context<'_>,
) -> JsResult<JsValue> {
) -> JsResult<CallValue> {
// 1. Let handler be O.[[ProxyHandler]].
// 2. If handler is null, throw a TypeError exception.
// 3. Assert: Type(handler) is Object.
@ -940,18 +939,25 @@ fn proxy_exotic_call(
let Some(trap) = handler.get_method(utf16!("apply"), context)? else {
// 6. If trap is undefined, then
// a. Return ? Call(target, thisArgument, argumentsList).
return target.call(this, args, context);
return Ok(target.__call__(argument_count));
};
let args = context.vm.pop_n_values(argument_count);
// 7. Let argArray be ! CreateArrayFromList(argumentsList).
let arg_array = array::Array::create_array_from_list(args.to_vec(), context);
let arg_array = array::Array::create_array_from_list(args, context);
// 8. Return ? Call(trap, handler, « target, thisArgument, argArray »).
trap.call(
&handler.into(),
&[target.clone().into(), this.clone(), arg_array.into()],
context,
)
let _func = context.vm.pop();
let this = context.vm.pop();
context.vm.push(handler); // This
context.vm.push(trap.clone()); // Function
context.vm.push(target);
context.vm.push(this);
context.vm.push(arg_array);
Ok(trap.__call__(3))
}
/// `[[Construct]] ( argumentsList, newTarget )`
@ -962,10 +968,9 @@ fn proxy_exotic_call(
/// [spec]: https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-construct-argumentslist-newtarget
fn proxy_exotic_construct(
obj: &JsObject,
args: &[JsValue],
new_target: &JsObject,
argument_count: usize,
context: &mut Context<'_>,
) -> JsResult<JsObject> {
) -> JsResult<CallValue> {
// 1. Let handler be O.[[ProxyHandler]].
// 2. If handler is null, throw a TypeError exception.
// 3. Assert: Type(handler) is Object.
@ -983,20 +988,20 @@ fn proxy_exotic_construct(
let Some(trap) = handler.get_method(utf16!("construct"), context)? else {
// 7. If trap is undefined, then
// a. Return ? Construct(target, argumentsList, newTarget).
return target.construct(args, Some(new_target), context);
return Ok(target.__construct__(argument_count));
};
let new_target = context.vm.pop();
let args = context.vm.pop_n_values(argument_count);
let _func = context.vm.pop();
// 8. Let argArray be ! CreateArrayFromList(argumentsList).
let arg_array = array::Array::create_array_from_list(args.to_vec(), context);
let arg_array = array::Array::create_array_from_list(args, context);
// 9. Let newObj be ? Call(trap, handler, « target, argArray, newTarget »).
let new_obj = trap.call(
&handler.into(),
&[
target.clone().into(),
arg_array.into(),
new_target.clone().into(),
],
&[target.into(), arg_array.into(), new_target],
context,
)?;
@ -1006,5 +1011,6 @@ fn proxy_exotic_construct(
})?;
// 11. Return newObj.
Ok(new_obj)
context.vm.push(new_obj);
Ok(CallValue::Complete)
}

48
boa_engine/src/object/operations.rs

@ -319,12 +319,29 @@ impl JsObject {
) -> JsResult<JsValue> {
// SKIP: 1. If argumentsList is not present, set argumentsList to a new empty List.
// SKIP: 2. If IsCallable(F) is false, throw a TypeError exception.
// NOTE(HalidOdat): For object's that are not callable we implement a special __call__ internal method
// that throws on call.
context.vm.push(this.clone()); // this
context.vm.push(self.clone()); // func
let argument_count = args.len();
context.vm.push_values(args);
// 3. Return ? F.[[Call]](V, argumentsList).
self.__call__(this, args, context)
let frame_index = context.vm.frames.len();
let is_complete = self.__call__(argument_count).resolve(context)?;
if is_complete {
return Ok(context.vm.pop());
}
context.vm.frames[frame_index].set_exit_early(true);
let result = context.run().consume();
context.vm.pop_frame().expect("frame must exist");
result
}
/// `Construct ( F [ , argumentsList [ , newTarget ] ] )`
@ -349,9 +366,32 @@ impl JsObject {
) -> JsResult<Self> {
// 1. If newTarget is not present, set newTarget to F.
let new_target = new_target.unwrap_or(self);
context.vm.push(self.clone()); // func
let argument_count = args.len();
context.vm.push_values(args);
context.vm.push(new_target.clone());
// 2. If argumentsList is not present, set argumentsList to a new empty List.
// 3. Return ? F.[[Construct]](argumentsList, newTarget).
self.__construct__(args, new_target, context)
let frame_index = context.vm.frames.len();
let is_complete = self.__construct__(argument_count).resolve(context)?;
if is_complete {
let result = context.vm.pop();
return Ok(result
.as_object()
.expect("construct value should be an object")
.clone());
}
context.vm.frames[frame_index].set_exit_early(true);
let result = context.run().consume();
context.vm.pop_frame().expect("frame must exist");
Ok(result?.as_object().expect("should be an object").clone())
}
/// Make the object [`sealed`][IntegrityLevel::Sealed] or [`frozen`][IntegrityLevel::Frozen].
@ -1168,7 +1208,7 @@ impl JsValue {
.into());
};
object.__call__(this, args, context)
object.call(this, args, context)
}
/// Abstract operation `( V, P [ , argumentsList ] )`

19
boa_engine/src/script.rs

@ -19,7 +19,7 @@ use rustc_hash::FxHashMap;
use crate::{
bytecompiler::ByteCompiler,
realm::Realm,
vm::{ActiveRunnable, CallFrame, CodeBlock},
vm::{ActiveRunnable, CallFrame, CallFrameFlags, CodeBlock},
Context, HostDefined, JsResult, JsString, JsValue, Module,
};
@ -148,11 +148,18 @@ impl Script {
let codeblock = self.codeblock(context)?;
let old_realm = context.enter_realm(self.inner.realm.clone());
let env_fp = context.vm.environments.len() as u32;
context.vm.push_frame(
CallFrame::new(codeblock, Some(ActiveRunnable::Script(self.clone())), None)
.with_env_fp(env_fp),
context.vm.push_frame_with_stack(
CallFrame::new(
codeblock,
Some(ActiveRunnable::Script(self.clone())),
context.vm.environments.clone(),
self.inner.realm.clone(),
)
.with_env_fp(env_fp)
.with_flags(CallFrameFlags::EXIT_EARLY),
JsValue::undefined(),
JsValue::null(),
);
// TODO: Here should be https://tc39.es/ecma262/#sec-globaldeclarationinstantiation
@ -161,8 +168,6 @@ impl Script {
let record = context.run();
context.vm.pop_frame();
context.enter_realm(old_realm);
context.clear_kept_objects();
record.consume()

87
boa_engine/src/vm/call_frame/mod.rs

@ -4,15 +4,29 @@
use crate::{
builtins::{iterable::IteratorRecord, promise::PromiseCapability},
environments::BindingLocator,
environments::{BindingLocator, EnvironmentStack},
object::JsObject,
realm::Realm,
vm::CodeBlock,
JsValue,
};
use boa_gc::{Finalize, Gc, Trace};
use thin_vec::ThinVec;
use super::ActiveRunnable;
use super::{ActiveRunnable, Vm};
bitflags::bitflags! {
/// Flags associated with a [`CallFrame`].
#[derive(Debug, Default, Clone, Copy)]
pub(crate) struct CallFrameFlags: u8 {
/// When we return from this [`CallFrame`] to stop execution and
/// return from [`crate::Context::run()`], and leave the remaining [`CallFrame`]s unchanged.
const EXIT_EARLY = 0b0000_0001;
/// Was this [`CallFrame`] created from the `__construct__()` internal object method?
const CONSTRUCT = 0b0000_0010;
}
}
/// A `CallFrame` holds the state of a function call.
#[derive(Clone, Debug, Finalize, Trace)]
@ -42,7 +56,15 @@ pub struct CallFrame {
/// \[\[ScriptOrModule\]\]
pub(crate) active_runnable: Option<ActiveRunnable>,
pub(crate) active_function: Option<JsObject>,
/// \[\[Environment\]\]
pub(crate) environments: EnvironmentStack,
/// \[\[Realm\]\]
pub(crate) realm: Realm,
// SAFETY: Nothing in `CallFrameFlags` requires tracing, so this is safe.
#[unsafe_ignore_trace]
pub(crate) flags: CallFrameFlags,
}
/// ---- `CallFrame` public API ----
@ -57,11 +79,31 @@ impl CallFrame {
/// ---- `CallFrame` creation methods ----
impl CallFrame {
/// This is the size of the function prologue.
///
/// The position of the elements are relative to the [`CallFrame::fp`].
///
/// ```text
/// --- frame pointer arguments
/// / __________________________/
/// / / \
/// | 0: this | 1: func | 2: arg1 | ... | (2 + N): argN |
/// \ /
/// ------------
/// |
/// function prolugue
/// ```
pub(crate) const FUNCTION_PROLOGUE: usize = 2;
pub(crate) const THIS_POSITION: usize = 0;
pub(crate) const FUNCTION_POSITION: usize = 1;
pub(crate) const FIRST_ARGUMENT_POSITION: usize = 2;
/// Creates a new `CallFrame` with the provided `CodeBlock`.
pub(crate) fn new(
code_block: Gc<CodeBlock>,
active_runnable: Option<ActiveRunnable>,
active_function: Option<JsObject>,
environments: EnvironmentStack,
realm: Realm,
) -> Self {
Self {
code_block,
@ -75,7 +117,9 @@ impl CallFrame {
binding_stack: Vec::new(),
loop_iteration_count: 0,
active_runnable,
active_function,
environments,
realm,
flags: CallFrameFlags::empty(),
}
}
@ -90,6 +134,39 @@ impl CallFrame {
self.env_fp = env_fp;
self
}
/// Updates a `CallFrame`'s `flags` field with the value provided.
pub(crate) fn with_flags(mut self, flags: CallFrameFlags) -> Self {
self.flags = flags;
self
}
pub(crate) fn this(&self, vm: &Vm) -> JsValue {
let this_index = self.fp as usize + Self::THIS_POSITION;
vm.stack[this_index].clone()
}
pub(crate) fn function(&self, vm: &Vm) -> Option<JsObject> {
let function_index = self.fp as usize + Self::FUNCTION_POSITION;
if let Some(object) = vm.stack[function_index].as_object() {
return Some(object.clone());
}
None
}
/// Does this have the [`CallFrameFlags::EXIT_EARLY`] flag.
pub(crate) fn exit_early(&self) -> bool {
self.flags.contains(CallFrameFlags::EXIT_EARLY)
}
/// Set the [`CallFrameFlags::EXIT_EARLY`] flag.
pub(crate) fn set_exit_early(&mut self, early_exit: bool) {
self.flags.set(CallFrameFlags::EXIT_EARLY, early_exit);
}
/// Does this have the [`CallFrameFlags::CONSTRUCT`] flag.
pub(crate) fn construct(&self) -> bool {
self.flags.contains(CallFrameFlags::CONSTRUCT)
}
}
/// ---- `CallFrame` stack methods ----

340
boa_engine/src/vm/code_block.rs

@ -3,17 +3,12 @@
//! This module is for the `CodeBlock` which implements a function representation in the VM
use crate::{
builtins::function::{
arguments::Arguments, ConstructorKind, FunctionKind, OrdinaryFunction, ThisMode,
},
context::intrinsics::StandardConstructors,
environments::{BindingLocator, CompileTimeEnvironment, FunctionSlots, ThisBindingStatus},
error::JsNativeError,
object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData, PROTOTYPE},
builtins::function::{ConstructorKind, FunctionKind, OrdinaryFunction, ThisMode},
environments::{BindingLocator, CompileTimeEnvironment},
object::{JsObject, ObjectData, PROTOTYPE},
property::PropertyDescriptor,
string::utf16,
vm::CallFrame,
Context, JsError, JsResult, JsString, JsValue,
Context, JsString, JsValue,
};
use bitflags::bitflags;
use boa_ast::function::FormalParameterList;
@ -360,7 +355,8 @@ impl CodeBlock {
| Instruction::SuperCall {
argument_count: value,
}
| Instruction::ConcatToString { value_count: value } => value.value().to_string(),
| Instruction::ConcatToString { value_count: value }
| Instruction::GetArgument { index: value } => value.value().to_string(),
Instruction::PushDeclarativeEnvironment {
compile_environments_index,
} => compile_environments_index.value().to_string(),
@ -533,6 +529,7 @@ impl CodeBlock {
| Instruction::MaybeException
| Instruction::This
| Instruction::Super
| Instruction::CheckReturn
| Instruction::Return
| Instruction::AsyncGeneratorClose
| Instruction::CreatePromiseCapability
@ -556,7 +553,6 @@ impl CodeBlock {
| Instruction::RequireObjectCoercible
| Instruction::ValueNotNullOrUndefined
| Instruction::RestParameterInit
| Instruction::RestParameterPop
| Instruction::PushValueToArray
| Instruction::PushElisionToArray
| Instruction::PushIteratorToArray
@ -582,6 +578,7 @@ impl CodeBlock {
| Instruction::ImportCall
| Instruction::GetReturnValue
| Instruction::SetReturnValue
| Instruction::BindThisValue
| Instruction::Nop => String::new(),
Instruction::U16Operands
@ -641,9 +638,7 @@ impl CodeBlock {
| Instruction::Reserved53
| Instruction::Reserved54
| Instruction::Reserved55
| Instruction::Reserved56
| Instruction::Reserved57
| Instruction::Reserved58 => unreachable!("Reserved opcodes are unrechable"),
| Instruction::Reserved56 => unreachable!("Reserved opcodes are unrechable"),
}
}
}
@ -1006,320 +1001,3 @@ pub(crate) fn create_generator_function_object(
constructor
}
impl JsObject {
pub(crate) fn call_internal(
&self,
this: &JsValue,
args: &[JsValue],
context: &mut Context<'_>,
) -> JsResult<JsValue> {
context.check_runtime_limits()?;
let old_realm = context.realm().clone();
let context = &mut context.guard(move |ctx| {
ctx.enter_realm(old_realm);
});
let this_function_object = self.clone();
let object = self.borrow();
let function = object.as_function().expect("not a function");
let realm = function.realm().clone();
if let FunctionKind::Ordinary { .. } = function.kind() {
if function.code.is_class_constructor() {
return Err(JsNativeError::typ()
.with_message("class constructor cannot be invoked without 'new'")
.with_realm(realm)
.into());
}
}
context.enter_realm(realm);
let code = function.code.clone();
let mut environments = function.environments.clone();
let script_or_module = function.script_or_module.clone();
drop(object);
std::mem::swap(&mut environments, &mut context.vm.environments);
let lexical_this_mode = code.this_mode == ThisMode::Lexical;
let this = if lexical_this_mode {
ThisBindingStatus::Lexical
} else if code.strict() {
ThisBindingStatus::Initialized(this.clone())
} else if this.is_null_or_undefined() {
ThisBindingStatus::Initialized(context.realm().global_this().clone().into())
} else {
ThisBindingStatus::Initialized(
this.to_object(context)
.expect("conversion cannot fail")
.into(),
)
};
let env_fp = context.vm.environments.len() as u32;
let mut last_env = 0;
if code.has_binding_identifier() {
let index = context
.vm
.environments
.push_lexical(code.compile_environments[last_env].clone());
context
.vm
.environments
.put_lexical_value(index, 0, self.clone().into());
last_env += 1;
}
context.vm.environments.push_function(
code.compile_environments[last_env].clone(),
FunctionSlots::new(this, self.clone(), None),
);
if code.has_parameters_env_bindings() {
last_env += 1;
context
.vm
.environments
.push_lexical(code.compile_environments[last_env].clone());
}
// Taken from: `FunctionDeclarationInstantiation` abstract function.
//
// Spec: https://tc39.es/ecma262/#sec-functiondeclarationinstantiation
//
// 22. If argumentsObjectNeeded is true, then
if code.needs_arguments_object() {
// a. If strict is true or simpleParameterList is false, then
// i. Let ao be CreateUnmappedArgumentsObject(argumentsList).
// b. Else,
// i. NOTE: A mapped argument object is only provided for non-strict functions
// that don't have a rest parameter, any parameter
// default value initializers, or any destructured parameters.
// ii. Let ao be CreateMappedArgumentsObject(func, formals, argumentsList, env).
let arguments_obj = if code.strict() || !code.params.is_simple() {
Arguments::create_unmapped_arguments_object(args, context)
} else {
let env = context.vm.environments.current();
Arguments::create_mapped_arguments_object(
&this_function_object,
&code.params,
args,
env.declarative_expect(),
context,
)
};
let env_index = context.vm.environments.len() as u32 - 1;
context
.vm
.environments
.put_lexical_value(env_index, 0, arguments_obj.into());
}
let argument_count = args.len();
let parameters_count = code.params.as_ref().len();
let frame = CallFrame::new(code, script_or_module, Some(self.clone()))
.with_argument_count(argument_count as u32)
.with_env_fp(env_fp);
context.vm.push_frame(frame);
// Push function arguments to the stack.
for _ in argument_count..parameters_count {
context.vm.push(JsValue::undefined());
}
context.vm.stack.extend(args.iter().rev().cloned());
let result = context
.run()
.consume()
.map_err(|err| err.inject_realm(context.realm().clone()));
context.vm.pop_frame().expect("frame must exist");
std::mem::swap(&mut environments, &mut context.vm.environments);
result
}
pub(crate) fn construct_internal(
&self,
args: &[JsValue],
this_target: &JsValue,
context: &mut Context<'_>,
) -> JsResult<Self> {
context.check_runtime_limits()?;
let old_realm = context.realm().clone();
let context = &mut context.guard(move |ctx| {
ctx.enter_realm(old_realm);
});
let this_function_object = self.clone();
let object = self.borrow();
let function = object.as_function().expect("not a function");
let realm = function.realm().clone();
context.enter_realm(realm);
let FunctionKind::Ordinary {
constructor_kind, ..
} = function.kind()
else {
unreachable!("not a constructor")
};
let code = function.code.clone();
let mut environments = function.environments.clone();
let script_or_module = function.script_or_module.clone();
let constructor_kind = *constructor_kind;
drop(object);
let this = if constructor_kind.is_base() {
// If the prototype of the constructor is not an object, then use the default object
// prototype as prototype for the new object
// see <https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor>
// see <https://tc39.es/ecma262/#sec-getprototypefromconstructor>
let prototype =
get_prototype_from_constructor(this_target, StandardConstructors::object, context)?;
let this = Self::from_proto_and_data_with_shared_shape(
context.root_shape(),
prototype,
ObjectData::ordinary(),
);
this.initialize_instance_elements(self, context)?;
Some(this)
} else {
None
};
let environments_len = environments.len();
std::mem::swap(&mut environments, &mut context.vm.environments);
let new_target = this_target.as_object().expect("must be object");
let mut last_env = 0;
if code.has_binding_identifier() {
let index = context
.vm
.environments
.push_lexical(code.compile_environments[last_env].clone());
context
.vm
.environments
.put_lexical_value(index, 0, self.clone().into());
last_env += 1;
}
context.vm.environments.push_function(
code.compile_environments[last_env].clone(),
FunctionSlots::new(
this.clone().map_or(ThisBindingStatus::Uninitialized, |o| {
ThisBindingStatus::Initialized(o.into())
}),
self.clone(),
Some(new_target.clone()),
),
);
let environment = context.vm.environments.current();
if code.has_parameters_env_bindings() {
last_env += 1;
context
.vm
.environments
.push_lexical(code.compile_environments[last_env].clone());
}
// Taken from: `FunctionDeclarationInstantiation` abstract function.
//
// Spec: https://tc39.es/ecma262/#sec-functiondeclarationinstantiation
//
// 22. If argumentsObjectNeeded is true, then
if code.needs_arguments_object() {
// a. If strict is true or simpleParameterList is false, then
// i. Let ao be CreateUnmappedArgumentsObject(argumentsList).
// b. Else,
// i. NOTE: A mapped argument object is only provided for non-strict functions
// that don't have a rest parameter, any parameter
// default value initializers, or any destructured parameters.
// ii. Let ao be CreateMappedArgumentsObject(func, formals, argumentsList, env).
let arguments_obj = if code.strict() || !code.params.is_simple() {
Arguments::create_unmapped_arguments_object(args, context)
} else {
let env = context.vm.environments.current();
Arguments::create_mapped_arguments_object(
&this_function_object,
&code.params,
args,
env.declarative_expect(),
context,
)
};
let env_index = context.vm.environments.len() as u32 - 1;
context
.vm
.environments
.put_lexical_value(env_index, 0, arguments_obj.into());
}
let argument_count = args.len();
let parameters_count = code.params.as_ref().len();
context.vm.push_frame(
CallFrame::new(code, script_or_module, Some(self.clone()))
.with_argument_count(argument_count as u32)
.with_env_fp(environments_len as u32),
);
// Push function arguments to the stack.
for _ in argument_count..parameters_count {
context.vm.push(JsValue::undefined());
}
context.vm.stack.extend(args.iter().rev().cloned());
let record = context.run();
context.vm.pop_frame();
std::mem::swap(&mut environments, &mut context.vm.environments);
let result = record
.consume()
.map_err(|err| err.inject_realm(context.realm().clone()))?;
if let Some(result) = result.as_object() {
Ok(result.clone())
} else if let Some(this) = this {
Ok(this)
} else if !result.is_undefined() {
Err(JsNativeError::typ()
.with_message("derived constructor can only return an Object or undefined")
.into())
} else {
let function_env = environment
.declarative_expect()
.kind()
.as_function()
.expect("must be function environment");
function_env
.get_this_binding()
.map(|v| {
v.expect("constructors cannot be arrow functions")
.as_object()
.expect("this binding must be object")
.clone()
})
.map_err(JsError::from)
}
}
}

10
boa_engine/src/vm/flowgraph/mod.rs

@ -207,7 +207,8 @@ impl CodeBlock {
| Instruction::Call { .. }
| Instruction::New { .. }
| Instruction::SuperCall { .. }
| Instruction::ConcatToString { .. } => {
| Instruction::ConcatToString { .. }
| Instruction::GetArgument { .. } => {
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
}
@ -425,7 +426,6 @@ impl CodeBlock {
| Instruction::RequireObjectCoercible
| Instruction::ValueNotNullOrUndefined
| Instruction::RestParameterInit
| Instruction::RestParameterPop
| Instruction::PushValueToArray
| Instruction::PushElisionToArray
| Instruction::PushIteratorToArray
@ -456,6 +456,8 @@ impl CodeBlock {
| Instruction::SetReturnValue
| Instruction::Exception
| Instruction::MaybeException
| Instruction::CheckReturn
| Instruction::BindThisValue
| Instruction::Nop => {
graph.add_node(previous_pc, NodeShape::None, label.into(), Color::None);
graph.add_edge(previous_pc, pc, None, Color::None, EdgeStyle::Line);
@ -520,9 +522,7 @@ impl CodeBlock {
| Instruction::Reserved53
| Instruction::Reserved54
| Instruction::Reserved55
| Instruction::Reserved56
| Instruction::Reserved57
| Instruction::Reserved58 => unreachable!("Reserved opcodes are unrechable"),
| Instruction::Reserved56 => unreachable!("Reserved opcodes are unrechable"),
}
}

221
boa_engine/src/vm/mod.rs

@ -5,13 +5,11 @@
//! plus an interpreter to execute those instructions
use crate::{
environments::{DeclarativeEnvironment, EnvironmentStack},
script::Script,
vm::code_block::Readable,
Context, JsError, JsNativeError, JsNativeErrorKind, JsObject, JsResult, JsValue, Module,
environments::EnvironmentStack, realm::Realm, script::Script, vm::code_block::Readable,
Context, JsError, JsNativeError, JsObject, JsResult, JsValue, Module,
};
use boa_gc::{custom_trace, Finalize, Gc, Trace};
use boa_gc::{custom_trace, Finalize, Trace};
use boa_profiler::Profiler;
use std::mem::size_of;
@ -40,6 +38,7 @@ pub use {
};
pub(crate) use {
call_frame::CallFrameFlags,
code_block::{
create_function_object, create_function_object_fast, create_generator_function_object,
CodeBlockFlags, Handler,
@ -74,6 +73,9 @@ pub struct Vm {
/// because we don't push a frame for them.
pub(crate) native_active_function: Option<JsObject>,
/// realm holds both the global object and the environment
pub(crate) realm: Realm,
#[cfg(feature = "trace")]
pub(crate) trace: bool,
}
@ -96,15 +98,16 @@ unsafe impl Trace for ActiveRunnable {
impl Vm {
/// Creates a new virtual machine.
pub(crate) fn new(global: Gc<DeclarativeEnvironment>) -> Self {
pub(crate) fn new(realm: Realm) -> Self {
Self {
frames: Vec::with_capacity(16),
stack: Vec::with_capacity(1024),
return_value: JsValue::undefined(),
environments: EnvironmentStack::new(global),
environments: EnvironmentStack::new(realm.environment().clone()),
pending_exception: None,
runtime_limits: RuntimeLimits::default(),
native_active_function: None,
realm,
#[cfg(feature = "trace")]
trace: false,
}
@ -158,11 +161,38 @@ impl Vm {
pub(crate) fn push_frame(&mut self, mut frame: CallFrame) {
let current_stack_length = self.stack.len();
frame.set_frame_pointer(current_stack_length as u32);
std::mem::swap(&mut self.environments, &mut frame.environments);
std::mem::swap(&mut self.realm, &mut frame.realm);
self.frames.push(frame);
}
pub(crate) fn push_frame_with_stack(
&mut self,
mut frame: CallFrame,
this: JsValue,
function: JsValue,
) {
let current_stack_length = self.stack.len();
frame.set_frame_pointer(current_stack_length as u32);
self.push(this);
self.push(function);
std::mem::swap(&mut self.environments, &mut frame.environments);
std::mem::swap(&mut self.realm, &mut frame.realm);
self.frames.push(frame);
}
pub(crate) fn pop_frame(&mut self) -> Option<CallFrame> {
self.frames.pop()
let mut frame = self.frames.pop();
if let Some(frame) = &mut frame {
std::mem::swap(&mut self.environments, &mut frame.environments);
std::mem::swap(&mut self.realm, &mut frame.realm);
}
frame
}
/// Handles an exception thrown at position `pc`.
@ -195,6 +225,23 @@ impl Vm {
pub(crate) fn set_return_value(&mut self, value: JsValue) {
self.return_value = value;
}
pub(crate) fn take_return_value(&mut self) -> JsValue {
std::mem::take(&mut self.return_value)
}
pub(crate) fn pop_n_values(&mut self, n: usize) -> Vec<JsValue> {
let at = self.stack.len() - n;
self.stack.split_off(at)
}
pub(crate) fn push_values(&mut self, values: &[JsValue]) {
self.stack.extend_from_slice(values);
}
pub(crate) fn insert_values_at(&mut self, values: &[JsValue], at: usize) {
self.stack.splice(at..at, values.iter().cloned());
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
@ -213,7 +260,7 @@ impl Context<'_> {
const OPERAND_COLUMN_WIDTH: usize = Self::COLUMN_WIDTH;
const NUMBER_OF_COLUMNS: usize = 4;
fn trace_call_frame(&self) {
pub(crate) fn trace_call_frame(&self) {
let msg = if self.vm.frames.last().is_some() {
format!(
" Call Frame -- {} ",
@ -257,21 +304,42 @@ impl Context<'_> {
.code_block
.instruction_operands(&instruction, self.interner());
let opcode = instruction.opcode();
match opcode {
Opcode::Call
| Opcode::CallSpread
| Opcode::CallEval
| Opcode::CallEvalSpread
| Opcode::New
| Opcode::NewSpread
| Opcode::Return
| Opcode::SuperCall
| Opcode::SuperCallSpread
| Opcode::SuperCallDerived => {
println!();
}
_ => {}
}
let instant = Instant::now();
let result = self.execute_instruction();
let duration = instant.elapsed();
let fp = self.vm.frames.last().map(|frame| frame.fp as usize);
let stack = {
let mut stack = String::from("[ ");
for (i, value) in self.vm.stack.iter().rev().enumerate() {
for (i, (j, value)) in self.vm.stack.iter().enumerate().rev().enumerate() {
match value {
value if value.is_callable() => stack.push_str("[function]"),
value if value.is_object() => stack.push_str("[object]"),
value => stack.push_str(&value.display().to_string()),
}
if i + 1 != self.vm.stack.len() {
if fp == Some(j) {
let frame_index = self.vm.frames.len() - 1;
stack.push_str(&format!(" |{frame_index}|"));
} else if i + 1 != self.vm.stack.len() {
stack.push(',');
}
@ -289,10 +357,9 @@ impl Context<'_> {
};
println!(
"{:<TIME_COLUMN_WIDTH$} {}{:<OPCODE_COLUMN_WIDTH$} {operands:<OPERAND_COLUMN_WIDTH$} {stack}",
"{:<TIME_COLUMN_WIDTH$} {:<OPCODE_COLUMN_WIDTH$} {operands:<OPERAND_COLUMN_WIDTH$} {stack}",
format!("{}μs", duration.as_micros()),
instruction.opcode().as_str(),
varying_operand_kind,
format!("{}{varying_operand_kind}", opcode.as_str()),
TIME_COLUMN_WIDTH = Self::TIME_COLUMN_WIDTH,
OPCODE_COLUMN_WIDTH = Self::OPCODE_COLUMN_WIDTH,
OPERAND_COLUMN_WIDTH = Self::OPERAND_COLUMN_WIDTH,
@ -328,7 +395,7 @@ impl Context<'_> {
self.trace_call_frame();
}
loop {
'instruction: loop {
#[cfg(feature = "fuzz")]
{
if self.instructions_remaining == 0 {
@ -349,15 +416,62 @@ impl Context<'_> {
#[cfg(not(feature = "trace"))]
let result = self.execute_instruction();
let result = match result {
Ok(result) => result,
Err(err) => {
// If we hit the execution step limit, bubble up the error to the
// (Rust) caller instead of trying to handle as an exception.
if !err.is_catchable() {
let mut fp = self.vm.stack.len();
let mut env_fp = self.vm.environments.len();
while let Some(frame) = self.vm.frames.last() {
if frame.exit_early() {
break;
}
fp = frame.fp as usize;
env_fp = frame.env_fp as usize;
self.vm.pop_frame();
}
self.vm.environments.truncate(env_fp);
self.vm.stack.truncate(fp);
return CompletionRecord::Throw(err);
}
// Note: -1 because we increment after fetching the opcode.
let pc = self.vm.frame().pc.saturating_sub(1);
if self.vm.handle_exception_at(pc) {
self.vm.pending_exception = Some(err);
continue;
}
// Inject realm before crossing the function boundry
let err = err.inject_realm(self.realm().clone());
self.vm.pending_exception = Some(err);
CompletionType::Throw
}
};
match result {
Ok(CompletionType::Normal) => {}
Ok(CompletionType::Return) => {
CompletionType::Normal => {}
CompletionType::Return => {
self.vm.stack.truncate(self.vm.frame().fp as usize);
let execution_result = std::mem::take(&mut self.vm.return_value);
return CompletionRecord::Normal(execution_result);
let result = self.vm.take_return_value();
if self.vm.frame().exit_early() {
return CompletionRecord::Normal(result);
}
Ok(CompletionType::Throw) => {
self.vm.stack.truncate(self.vm.frame().fp as usize);
self.vm.push(result);
self.vm.pop_frame();
}
CompletionType::Throw => {
let mut fp = self.vm.frame().fp;
let mut env_fp = self.vm.frame().env_fp;
if self.vm.frame().exit_early() {
self.vm.environments.truncate(env_fp as usize);
self.vm.stack.truncate(fp as usize);
return CompletionRecord::Throw(
self.vm
.pending_exception
@ -365,47 +479,42 @@ impl Context<'_> {
.expect("Err must exist for a CompletionType::Throw"),
);
}
// Early return immediately.
Ok(CompletionType::Yield) => {
let result = self.vm.pop();
return CompletionRecord::Return(result);
}
Err(err) => {
if let Some(native_error) = err.as_native() {
// If we hit the execution step limit, bubble up the error to the
// (Rust) caller instead of trying to handle as an exception.
match native_error.kind {
#[cfg(feature = "fuzz")]
JsNativeErrorKind::NoInstructionsRemain => {
self.vm
.environments
.truncate(self.vm.frame().env_fp as usize);
self.vm.stack.truncate(self.vm.frame().fp as usize);
return CompletionRecord::Throw(err);
self.vm.pop_frame();
while let Some(frame) = self.vm.frames.last_mut() {
fp = frame.fp;
env_fp = frame.fp;
let pc = frame.pc;
let exit_early = frame.exit_early();
if self.vm.handle_exception_at(pc) {
continue 'instruction;
}
JsNativeErrorKind::RuntimeLimit => {
if exit_early {
return CompletionRecord::Throw(
self.vm
.environments
.truncate(self.vm.frame().env_fp as usize);
self.vm.stack.truncate(self.vm.frame().fp as usize);
return CompletionRecord::Throw(err);
.pending_exception
.take()
.expect("Err must exist for a CompletionType::Throw"),
);
}
_ => {}
self.vm.pop_frame();
}
self.vm.environments.truncate(env_fp as usize);
self.vm.stack.truncate(fp as usize);
}
// Note: -1 because we increment after fetching the opcode.
let pc = self.vm.frame().pc.saturating_sub(1);
if self.vm.handle_exception_at(pc) {
self.vm.pending_exception = Some(err);
continue;
// Early return immediately.
CompletionType::Yield => {
let result = self.vm.take_return_value();
if self.vm.frame().exit_early() {
return CompletionRecord::Return(result);
}
self.vm
.environments
.truncate(self.vm.frame().env_fp as usize);
self.vm.stack.truncate(self.vm.frame().fp as usize);
return CompletionRecord::Throw(err);
self.vm.push(result);
self.vm.pop_frame();
}
}
}

18
boa_engine/src/vm/opcode/await/mod.rs

@ -1,7 +1,7 @@
use boa_gc::{Gc, GcRefCell};
use crate::{
builtins::{generator::GeneratorContext, Promise},
builtins::{generator::GeneratorContext, promise::PromiseCapability, Promise},
native_function::NativeFunction,
object::FunctionObjectBuilder,
vm::{opcode::Operation, CompletionType, GeneratorResumeKind},
@ -125,11 +125,17 @@ impl Operation for Await {
context,
);
if let Some(promise_capabality) = context.vm.frame().promise_capability.clone() {
context.vm.push(promise_capabality.promise().clone());
} else {
context.vm.push(JsValue::undefined());
}
let return_value = context
.vm
.frame()
.promise_capability
.as_ref()
.map(PromiseCapability::promise)
.cloned()
.map(JsValue::from)
.unwrap_or_default();
context.vm.set_return_value(return_value);
Ok(CompletionType::Yield)
}
}

55
boa_engine/src/vm/opcode/call/mod.rs

@ -16,14 +16,8 @@ pub(crate) struct CallEval;
impl CallEval {
fn operation(context: &mut Context<'_>, argument_count: usize) -> JsResult<CompletionType> {
let mut arguments = Vec::with_capacity(argument_count);
for _ in 0..argument_count {
arguments.push(context.vm.pop());
}
arguments.reverse();
let func = context.vm.pop();
let this = context.vm.pop();
let at = context.vm.stack.len() - argument_count;
let func = &context.vm.stack[at - 1];
let Some(object) = func.as_object() else {
return Err(JsNativeError::typ()
@ -40,6 +34,9 @@ impl CallEval {
// a. If SameValue(func, %eval%) is true, then
let eval = context.intrinsics().objects().eval();
if JsObject::equals(object, &eval) {
let arguments = context.vm.pop_n_values(argument_count);
let _func = context.vm.pop();
let _this = context.vm.pop();
if let Some(x) = arguments.get(0) {
// i. Let argList be ? ArgumentListEvaluation of arguments.
// ii. If argList has no elements, return undefined.
@ -54,11 +51,11 @@ impl CallEval {
// NOTE: This is a deviation from the spec, to optimize the case when we dont pass anything to `eval`.
context.vm.push(JsValue::Undefined);
}
return Ok(CompletionType::Normal);
}
let result = object.__call__(&this, &arguments, context)?;
context.vm.push(result);
object.__call__(argument_count).resolve(context)?;
Ok(CompletionType::Normal)
}
}
@ -107,8 +104,8 @@ impl Operation for CallEvalSpread {
.expect("arguments array in call spread function must be dense")
.clone();
let func = context.vm.pop();
let this = context.vm.pop();
let at = context.vm.stack.len();
let func = context.vm.stack[at - 1].clone();
let Some(object) = func.as_object() else {
return Err(JsNativeError::typ()
@ -125,6 +122,8 @@ impl Operation for CallEvalSpread {
// a. If SameValue(func, %eval%) is true, then
let eval = context.intrinsics().objects().eval();
if JsObject::equals(object, &eval) {
let _func = context.vm.pop();
let _this = context.vm.pop();
if let Some(x) = arguments.get(0) {
// i. Let argList be ? ArgumentListEvaluation of arguments.
// ii. If argList has no elements, return undefined.
@ -139,11 +138,14 @@ impl Operation for CallEvalSpread {
// NOTE: This is a deviation from the spec, to optimize the case when we dont pass anything to `eval`.
context.vm.push(JsValue::Undefined);
}
return Ok(CompletionType::Normal);
}
let result = object.__call__(&this, &arguments, context)?;
context.vm.push(result);
let argument_count = arguments.len();
context.vm.push_values(&arguments);
object.__call__(argument_count).resolve(context)?;
Ok(CompletionType::Normal)
}
}
@ -157,14 +159,8 @@ pub(crate) struct Call;
impl Call {
fn operation(context: &mut Context<'_>, argument_count: usize) -> JsResult<CompletionType> {
let mut arguments = Vec::with_capacity(argument_count);
for _ in 0..argument_count {
arguments.push(context.vm.pop());
}
arguments.reverse();
let func = context.vm.pop();
let this = context.vm.pop();
let at = context.vm.stack.len() - argument_count;
let func = &context.vm.stack[at - 1];
let Some(object) = func.as_object() else {
return Err(JsNativeError::typ()
@ -172,9 +168,7 @@ impl Call {
.into());
};
let result = object.__call__(&this, &arguments, context)?;
context.vm.push(result);
object.__call__(argument_count).resolve(context)?;
Ok(CompletionType::Normal)
}
}
@ -219,8 +213,11 @@ impl Operation for CallSpread {
.expect("arguments array in call spread function must be dense")
.clone();
let func = context.vm.pop();
let this = context.vm.pop();
let argument_count = arguments.len();
context.vm.push_values(&arguments);
let at = context.vm.stack.len() - argument_count;
let func = &context.vm.stack[at - 1];
let Some(object) = func.as_object() else {
return Err(JsNativeError::typ()
@ -228,9 +225,7 @@ impl Operation for CallSpread {
.into());
};
let result = object.__call__(&this, &arguments, context)?;
context.vm.push(result);
object.__call__(argument_count).resolve(context)?;
Ok(CompletionType::Normal)
}
}

53
boa_engine/src/vm/opcode/control_flow/return.rs

@ -1,6 +1,6 @@
use crate::{
vm::{opcode::Operation, CompletionType},
Context, JsResult,
Context, JsNativeError, JsResult,
};
/// `Return` implements the Opcode Operation for `Opcode::Return`
@ -19,6 +19,57 @@ impl Operation for Return {
}
}
/// `CheckReturn` implements the Opcode Operation for `Opcode::CheckReturn`
///
/// Operation:
/// - Check return from a function.
#[derive(Debug, Clone, Copy)]
pub(crate) struct CheckReturn;
impl Operation for CheckReturn {
const NAME: &'static str = "CheckReturn";
const INSTRUCTION: &'static str = "INST - CheckReturn";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
if !context.vm.frame().construct() {
return Ok(CompletionType::Normal);
}
let frame = context.vm.frame();
let this = frame.this(&context.vm);
let result = context.vm.take_return_value();
let result = if result.is_object() {
result
} else if !this.is_undefined() {
this
} else if !result.is_undefined() {
let realm = context.vm.frame().realm.clone();
context.vm.pending_exception = Some(
JsNativeError::typ()
.with_message("derived constructor can only return an Object or undefined")
.with_realm(realm)
.into(),
);
return Ok(CompletionType::Throw);
} else {
let realm = context.vm.frame().realm.clone();
let this = context.vm.environments.get_this_binding();
match this {
Err(err) => {
let err = err.inject_realm(realm);
context.vm.pending_exception = Some(err);
return Ok(CompletionType::Throw);
}
Ok(this) => this,
}
};
context.vm.set_return_value(result);
Ok(CompletionType::Normal)
}
}
/// `GetReturnValue` implements the Opcode Operation for `Opcode::GetReturnValue`
///
/// Operation:

123
boa_engine/src/vm/opcode/environment/mod.rs

@ -80,23 +80,14 @@ impl Operation for SuperCallPrepare {
.get_this_environment()
.as_function()
.expect("super call must be in function environment");
let new_target = this_env
.slots()
.new_target()
.expect("must have new target")
.clone();
let active_function = this_env.slots().function_object().clone();
let super_constructor = active_function
.__get_prototype_of__(context)
.expect("function object must have prototype");
if let Some(constructor) = super_constructor {
context.vm.push(constructor);
} else {
context.vm.push(JsValue::Null);
}
context.vm.push(new_target);
context
.vm
.push(super_constructor.map_or_else(JsValue::null, JsValue::from));
Ok(CompletionType::Normal)
}
}
@ -110,27 +101,14 @@ pub(crate) struct SuperCall;
impl SuperCall {
fn operation(context: &mut Context<'_>, argument_count: usize) -> JsResult<CompletionType> {
let mut arguments = Vec::with_capacity(argument_count);
for _ in 0..argument_count {
arguments.push(context.vm.pop());
}
arguments.reverse();
let new_target_value = context.vm.pop();
let super_constructor = context.vm.pop();
let new_target = new_target_value
.as_object()
.expect("new target must be object");
let super_constructor_index = context.vm.stack.len() - argument_count - 1;
let super_constructor = context.vm.stack[super_constructor_index].clone();
let Some(super_constructor) = super_constructor.as_constructor() else {
return Err(JsNativeError::typ()
.with_message("super constructor object must be constructor")
.into());
};
let result = super_constructor.__construct__(&arguments, new_target, context)?;
let this_env = context
.vm
.environments
@ -138,12 +116,17 @@ impl SuperCall {
.as_function()
.expect("super call must be in function environment");
this_env.bind_this_value(result.clone())?;
let function_object = this_env.slots().function_object().clone();
let new_target = this_env
.slots()
.new_target()
.expect("must have new.target")
.clone();
result.initialize_instance_elements(&function_object, context)?;
context.vm.push(new_target);
context.vm.push(result);
super_constructor
.__construct__(argument_count)
.resolve(context)?;
Ok(CompletionType::Normal)
}
}
@ -176,8 +159,8 @@ impl Operation for SuperCall {
pub(crate) struct SuperCallSpread;
impl Operation for SuperCallSpread {
const NAME: &'static str = "SuperCallWithRest";
const INSTRUCTION: &'static str = "INST - SuperCallWithRest";
const NAME: &'static str = "SuperCallSpread";
const INSTRUCTION: &'static str = "INST - SuperCallSpread";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
// Get the arguments that are stored as an array object on the stack.
@ -192,20 +175,17 @@ impl Operation for SuperCallSpread {
.expect("arguments array in call spread function must be dense")
.clone();
let new_target_value = context.vm.pop();
let super_constructor = context.vm.pop();
let new_target = new_target_value
.as_object()
.expect("new target must be object");
let Some(super_constructor) = super_constructor.as_constructor() else {
return Err(JsNativeError::typ()
.with_message("super constructor object must be constructor")
.into());
};
let result = super_constructor.__construct__(&arguments, new_target, context)?;
context.vm.push(super_constructor.clone());
context.vm.push_values(&arguments);
let this_env = context
.vm
@ -214,12 +194,17 @@ impl Operation for SuperCallSpread {
.as_function()
.expect("super call must be in function environment");
this_env.bind_this_value(result.clone())?;
let function_object = this_env.slots().function_object().clone();
let new_target = this_env
.slots()
.new_target()
.expect("must have new.target")
.clone();
result.initialize_instance_elements(&function_object, context)?;
context.vm.push(new_target);
context.vm.push(result);
super_constructor
.__construct__(arguments.len())
.resolve(context)?;
Ok(CompletionType::Normal)
}
}
@ -236,11 +221,7 @@ impl Operation for SuperCallDerived {
const INSTRUCTION: &'static str = "INST - SuperCallDerived";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let argument_count = context.vm.frame().argument_count;
let mut arguments = Vec::with_capacity(argument_count as usize);
for _ in 0..argument_count {
arguments.push(context.vm.pop());
}
let argument_count = context.vm.frame().argument_count as usize;
let this_env = context
.vm
@ -265,8 +246,45 @@ impl Operation for SuperCallDerived {
.into());
}
let result = super_constructor.__construct__(&arguments, &new_target, context)?;
let arguments_start_index = context.vm.stack.len() - argument_count;
context
.vm
.stack
.insert(arguments_start_index, super_constructor.clone().into());
context.vm.push(new_target);
super_constructor
.__construct__(argument_count)
.resolve(context)?;
Ok(CompletionType::Normal)
}
}
/// `BindThisValue` implements the Opcode Operation for `Opcode::BindThisValue`
///
/// Operation:
/// - Binds `this` value and initializes the instance elements.
#[derive(Debug, Clone, Copy)]
pub(crate) struct BindThisValue;
impl Operation for BindThisValue {
const NAME: &'static str = "BindThisValue";
const INSTRUCTION: &'static str = "INST - BindThisValue";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
// Taken from `SuperCall : super Arguments` steps 7-12.
//
// <https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation>
let result = context
.vm
.pop()
.as_object()
.expect("construct result should be an object")
.clone();
// 7. Let thisER be GetThisEnvironment().
let this_env = context
.vm
.environments
@ -274,10 +292,17 @@ impl Operation for SuperCallDerived {
.as_function()
.expect("super call must be in function environment");
// 8. Perform ? thisER.BindThisValue(result).
this_env.bind_this_value(result.clone())?;
// 9. Let F be thisER.[[FunctionObject]].
// SKIP: 10. Assert: F is an ECMAScript function object.
let active_function = this_env.slots().function_object().clone();
// 11. Perform ? InitializeInstanceElements(result, F).
result.initialize_instance_elements(&active_function, context)?;
// 12. Return result.
context.vm.push(result);
Ok(CompletionType::Normal)
}

46
boa_engine/src/vm/opcode/generator/mod.rs

@ -7,7 +7,6 @@ use crate::{
async_generator::{AsyncGenerator, AsyncGeneratorState},
generator::{GeneratorContext, GeneratorState},
},
environments::EnvironmentStack,
error::JsNativeError,
object::{ObjectData, PROTOTYPE},
string::utf16,
@ -37,15 +36,26 @@ impl Operation for Generator {
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let r#async = context.vm.read::<u8>() != 0;
let code_block = context.vm.frame().code_block().clone();
let active_runnable = context.vm.frame().active_runnable.clone();
let active_function = context.vm.frame().active_function.clone();
let pc = context.vm.frame().pc;
let mut dummy_call_frame =
CallFrame::new(code_block, active_runnable, active_function.clone());
let frame = context.vm.frame();
let code_block = frame.code_block().clone();
let active_runnable = frame.active_runnable.clone();
let active_function = frame.function(&context.vm);
let environments = frame.environments.clone();
let realm = frame.realm.clone();
let pc = frame.pc;
let mut dummy_call_frame = CallFrame::new(code_block, active_runnable, environments, realm);
dummy_call_frame.pc = pc;
let mut call_frame = std::mem::replace(context.vm.frame_mut(), dummy_call_frame);
context
.vm
.frame_mut()
.set_exit_early(call_frame.exit_early());
call_frame.environments = context.vm.environments.clone();
call_frame.realm = context.realm().clone();
let fp = call_frame.fp as usize;
let stack = context.vm.stack[fp..].to_vec();
@ -71,32 +81,16 @@ impl Operation for Generator {
Clone::clone,
);
let global_environement = context.vm.environments.global();
let environments = std::mem::replace(
&mut context.vm.environments,
EnvironmentStack::new(global_environement),
);
let data = if r#async {
ObjectData::async_generator(AsyncGenerator {
state: AsyncGeneratorState::SuspendedStart,
context: Some(GeneratorContext::new(
environments,
stack,
call_frame,
context.realm().clone(),
)),
context: Some(GeneratorContext::new(stack, call_frame)),
queue: VecDeque::new(),
})
} else {
ObjectData::generator(crate::builtins::generator::Generator {
state: GeneratorState::SuspendedStart {
context: GeneratorContext::new(
environments,
stack,
call_frame,
context.realm().clone(),
),
context: GeneratorContext::new(stack, call_frame),
},
})
};
@ -119,7 +113,7 @@ impl Operation for Generator {
.async_generator = Some(gen_clone);
}
context.vm.push(generator);
context.vm.set_return_value(generator.into());
Ok(CompletionType::Yield)
}
}

14
boa_engine/src/vm/opcode/generator/yield_stm.rs

@ -15,7 +15,9 @@ impl Operation for GeneratorYield {
const NAME: &'static str = "GeneratorYield";
const INSTRUCTION: &'static str = "INST - GeneratorYield";
fn execute(_context: &mut Context<'_>) -> JsResult<CompletionType> {
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let value = context.vm.pop();
context.vm.set_return_value(value);
Ok(CompletionType::Yield)
}
}
@ -76,11 +78,11 @@ impl Operation for AsyncGeneratorYield {
context.vm.push(resume_kind);
Ok(CompletionType::Normal)
} else {
gen.state = AsyncGeneratorState::SuspendedYield;
context.vm.push(JsValue::undefined());
GeneratorYield::execute(context)
return Ok(CompletionType::Normal);
}
gen.state = AsyncGeneratorState::SuspendedYield;
context.vm.set_return_value(JsValue::undefined());
Ok(CompletionType::Yield)
}
}

47
boa_engine/src/vm/opcode/get/argument.rs

@ -0,0 +1,47 @@
use crate::{
vm::{opcode::Operation, CompletionType},
Context, JsResult,
};
/// `GetArgument` implements the Opcode Operation for `Opcode::GetArgument`
///
/// Operation:
/// - Get i-th argument of the current frame.
#[derive(Debug, Clone, Copy)]
pub(crate) struct GetArgument;
impl GetArgument {
#[allow(clippy::unnecessary_wraps)]
fn operation(context: &mut Context<'_>, index: usize) -> JsResult<CompletionType> {
let fp = context.vm.frame().fp as usize;
let argument_index = fp + 2;
let argument_count = context.vm.frame().argument_count as usize;
let value = context.vm.stack[argument_index..(argument_index + argument_count)]
.get(index)
.cloned()
.unwrap_or_default();
context.vm.push(value);
Ok(CompletionType::Normal)
}
}
impl Operation for GetArgument {
const NAME: &'static str = "GetArgument";
const INSTRUCTION: &'static str = "INST - GetArgument";
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let index = context.vm.read::<u8>() as usize;
Self::operation(context, index)
}
fn execute_with_u16_operands(context: &mut Context<'_>) -> JsResult<CompletionType> {
let index = context.vm.read::<u16>() as usize;
Self::operation(context, index)
}
fn execute_with_u32_operands(context: &mut Context<'_>) -> JsResult<CompletionType> {
let index = context.vm.read::<u32>() as usize;
Self::operation(context, index)
}
}

2
boa_engine/src/vm/opcode/get/mod.rs

@ -1,9 +1,11 @@
pub(crate) mod argument;
pub(crate) mod function;
pub(crate) mod generator;
pub(crate) mod name;
pub(crate) mod private;
pub(crate) mod property;
pub(crate) use argument::*;
pub(crate) use function::*;
pub(crate) use generator::*;
pub(crate) use name::*;

50
boa_engine/src/vm/opcode/mod.rs

@ -536,14 +536,14 @@ pub(crate) trait Operation {
/// Execute opcode with [`VaryingOperandKind::U16`] sized [`VaryingOperand`]s.
///
/// By default if not implemented will call [`Reserved::u16_execute()`] which panics.
/// By default if not implemented will call [`Reserved::execute_with_u16_operands()`] which panics.
fn execute_with_u16_operands(context: &mut Context<'_>) -> JsResult<CompletionType> {
Reserved::execute_with_u16_operands(context)
}
/// Execute opcode with [`VaryingOperandKind::U32`] sized [`VaryingOperand`]s.
///
/// By default if not implemented will call [`Reserved::u32_execute()`] which panics.
/// By default if not implemented will call [`Reserved::execute_with_u32_operands()`] which panics.
fn execute_with_u32_operands(context: &mut Context<'_>) -> JsResult<CompletionType> {
Reserved::execute_with_u32_operands(context)
}
@ -1058,6 +1058,15 @@ generate_opcodes! {
/// Stack: **=>**
ThrowMutateImmutable { index: VaryingOperand },
/// Get i-th argument of the current frame.
///
/// Returns `undefined` if `arguments.len()` < `index`.
///
/// Operands: index: `u32`
///
/// Stack: **=>** value
GetArgument { index: VaryingOperand },
/// Find a binding on the environment chain and push its value.
///
/// Operands: index: `u32`
@ -1571,21 +1580,21 @@ generate_opcodes! {
///
/// Operands:
///
/// Stack: **=>** super_constructor, new_target
/// Stack: **=>** super_constructor
SuperCallPrepare,
/// Execute the `super()` method.
///
/// Operands: argument_count: `u32`
///
/// Stack: super_constructor, new_target, argument_1, ... argument_n **=>**
/// Stack: super_constructor, argument_1, ... argument_n **=>**
SuperCall { argument_count: VaryingOperand },
/// Execute the `super()` method where the arguments contain spreads.
///
/// Operands:
///
/// Stack: super_constructor, new_target, arguments_array **=>**
/// Stack: super_constructor, arguments_array **=>**
SuperCallSpread,
/// Execute the `super()` method when no constructor of the class is defined.
@ -1595,6 +1604,17 @@ generate_opcodes! {
/// Stack: argument_n, ... argument_1 **=>**
SuperCallDerived,
/// Binds `this` value and initializes the instance elements.
///
/// Performs steps 7-12 of [`SuperCall: super Arguments`][spec]
///
/// Operands:
///
/// Stack: result **=>** result
///
/// [spec]: https://tc39.es/ecma262/#sec-super-keyword-runtime-semantics-evaluation
BindThisValue,
/// Dynamically import a module.
///
/// Operands:
@ -1701,6 +1721,13 @@ generate_opcodes! {
/// Stack: arguments_array, func **=>** result
NewSpread,
/// Check return from a function.
///
/// Operands:
///
/// Stack: **=>**
CheckReturn,
/// Return from a function.
///
/// Operands:
@ -1912,18 +1939,11 @@ generate_opcodes! {
/// Stack: `argument_1` .. `argument_n` **=>** `array`
RestParameterInit,
/// Pop the remaining arguments of a function.
///
/// Operands:
///
/// Stack: `argument_1` .. `argument_n` **=>**
RestParameterPop,
/// Yields from the current generator execution.
///
/// Operands:
///
/// Stack: **=>** resume_kind, received
/// Stack: value **=>** resume_kind, received
GeneratorYield,
/// Resumes the current generator function.
@ -2169,10 +2189,6 @@ generate_opcodes! {
Reserved55 => Reserved,
/// Reserved [`Opcode`].
Reserved56 => Reserved,
/// Reserved [`Opcode`].
Reserved57 => Reserved,
/// Reserved [`Opcode`].
Reserved58 => Reserved,
}
/// Specific opcodes for bindings.

43
boa_engine/src/vm/opcode/new/mod.rs

@ -13,23 +13,17 @@ pub(crate) struct New;
impl New {
fn operation(context: &mut Context<'_>, argument_count: usize) -> JsResult<CompletionType> {
let mut arguments = Vec::with_capacity(argument_count);
for _ in 0..argument_count {
arguments.push(context.vm.pop());
}
arguments.reverse();
let func = context.vm.pop();
let at = context.vm.stack.len() - argument_count;
let func = &context.vm.stack[at - 1];
let cons = func
.as_object()
.ok_or_else(|| JsNativeError::typ().with_message("not a constructor"))?
.clone();
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(cons.clone()); // Push new.target
context.vm.push(result);
cons.__construct__(argument_count).resolve(context)?;
Ok(CompletionType::Normal)
}
}
@ -80,16 +74,17 @@ impl Operation for NewSpread {
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))?;
let cons = func
.as_object()
.ok_or_else(|| JsNativeError::typ().with_message("not a constructor"))?
.clone();
let argument_count = arguments.len();
context.vm.push(func);
context.vm.push_values(&arguments);
context.vm.push(cons.clone()); // Push new.target
context.vm.push(result);
cons.__construct__(argument_count).resolve(context)?;
Ok(CompletionType::Normal)
}
}

41
boa_engine/src/vm/opcode/rest_parameter/mod.rs

@ -18,45 +18,16 @@ impl Operation for RestParameterInit {
fn execute(context: &mut Context<'_>) -> JsResult<CompletionType> {
let arg_count = context.vm.frame().argument_count as usize;
let param_count = context.vm.frame().code_block().params.as_ref().len();
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);
let array = if arg_count >= param_count {
let rest_count = arg_count - param_count + 1;
let args = context.vm.pop_n_values(rest_count);
Array::create_array_from_list(args, context)
} else {
context.vm.pop();
Array::array_create(0, None, context).expect("could not create an empty array")
};
let array =
Array::array_create(0, None, context).expect("could not create an empty array");
context.vm.push(array);
}
Ok(CompletionType::Normal)
}
}
/// `RestParameterPop` implements the Opcode Operation for `Opcode::RestParameterPop`
///
/// Operation:
/// - Pop the remaining arguments of a function.
#[derive(Debug, Clone, Copy)]
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<CompletionType> {
let arg_count = context.vm.frame().argument_count;
let param_count = context.vm.frame().code_block().params.as_ref().len() as u32;
if arg_count > param_count {
for _ in 0..(arg_count - param_count) {
context.vm.pop();
}
}
Ok(CompletionType::Normal)
}
}

Loading…
Cancel
Save