diff --git a/boa_engine/src/builtins/eval/mod.rs b/boa_engine/src/builtins/eval/mod.rs index 89b838b7c3c..bb27226edc8 100644 --- a/boa_engine/src/builtins/eval/mod.rs +++ b/boa_engine/src/builtins/eval/mod.rs @@ -122,18 +122,12 @@ impl Eval { // 8. Let inDerivedConstructor be false. // 9. Let inClassFieldInitializer be false. // a. Let thisEnvRec be GetThisEnvironment(). - let flags = match context - .vm - .environments - .get_this_environment() - .as_function_slots() - { + let flags = match context.vm.environments.get_this_environment().as_function() { // 10. If direct is true, then // b. If thisEnvRec is a Function Environment Record, then Some(function_env) if direct => { - let function_env = function_env.borrow(); // i. Let F be thisEnvRec.[[FunctionObject]]. - let function_object = function_env.function_object().borrow(); + let function_object = function_env.slots().function_object().borrow(); // ii. Set inFunction to true. let mut flags = Flags::IN_FUNCTION; diff --git a/boa_engine/src/builtins/function/arguments.rs b/boa_engine/src/builtins/function/arguments.rs index 8b9f2e8490c..aafd5f98eaa 100644 --- a/boa_engine/src/builtins/function/arguments.rs +++ b/boa_engine/src/builtins/function/arguments.rs @@ -33,10 +33,8 @@ impl ParameterMap { /// /// [spec]: https://tc39.es/ecma262/#sec-makearggetter pub(crate) fn get(&self, index: usize) -> Option { - if let Some(Some(binding_index)) = self.binding_indices.get(index) { - return Some(self.environment.get(*binding_index)); - } - None + let binding_index = self.binding_indices.get(index).copied().flatten()?; + self.environment.get(binding_index) } /// Set the value of the binding at the given index in the function environment. @@ -48,8 +46,8 @@ impl ParameterMap { /// /// [spec]: https://tc39.es/ecma262/#sec-makeargsetter pub(crate) fn set(&self, index: usize, value: &JsValue) { - if let Some(Some(binding_index)) = self.binding_indices.get(index) { - self.environment.set(*binding_index, value.clone()); + if let Some(binding_index) = self.binding_indices.get(index).copied().flatten() { + self.environment.set(binding_index, value.clone()); } } } diff --git a/boa_engine/src/builtins/function/mod.rs b/boa_engine/src/builtins/function/mod.rs index d86ebc9df05..8343ff3ebc7 100644 --- a/boa_engine/src/builtins/function/mod.rs +++ b/boa_engine/src/builtins/function/mod.rs @@ -15,7 +15,7 @@ use crate::{ builtins::BuiltInObject, bytecompiler::FunctionCompiler, context::intrinsics::{Intrinsics, StandardConstructor, StandardConstructors}, - environments::DeclarativeEnvironmentStack, + environments::EnvironmentStack, error::JsNativeError, js_string, native_function::NativeFunction, @@ -160,7 +160,7 @@ pub(crate) enum FunctionKind { code: Gc, /// The `[[Environment]]` internal slot. - environments: DeclarativeEnvironmentStack, + environments: EnvironmentStack, /// The `[[ConstructorKind]]` internal slot. constructor_kind: ConstructorKind, @@ -184,7 +184,7 @@ pub(crate) enum FunctionKind { code: Gc, /// The `[[Environment]]` internal slot. - environments: DeclarativeEnvironmentStack, + environments: EnvironmentStack, /// The `[[HomeObject]]` internal slot. home_object: Option, @@ -199,7 +199,7 @@ pub(crate) enum FunctionKind { code: Gc, /// The `[[Environment]]` internal slot. - environments: DeclarativeEnvironmentStack, + environments: EnvironmentStack, /// The `[[HomeObject]]` internal slot. home_object: Option, @@ -214,7 +214,7 @@ pub(crate) enum FunctionKind { code: Gc, /// The `[[Environment]]` internal slot. - environments: DeclarativeEnvironmentStack, + environments: EnvironmentStack, /// The `[[HomeObject]]` internal slot. home_object: Option, diff --git a/boa_engine/src/builtins/generator/mod.rs b/boa_engine/src/builtins/generator/mod.rs index bd177be9937..bf4999afaf8 100644 --- a/boa_engine/src/builtins/generator/mod.rs +++ b/boa_engine/src/builtins/generator/mod.rs @@ -12,7 +12,7 @@ use crate::{ builtins::iterable::create_iter_result_object, context::intrinsics::Intrinsics, - environments::DeclarativeEnvironmentStack, + environments::EnvironmentStack, error::JsNativeError, object::{JsObject, CONSTRUCTOR}, property::Attribute, @@ -58,7 +58,7 @@ 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: DeclarativeEnvironmentStack, + pub(crate) environments: EnvironmentStack, pub(crate) stack: Vec, pub(crate) active_function: Option, pub(crate) call_frame: Option, @@ -68,7 +68,7 @@ pub(crate) struct GeneratorContext { impl GeneratorContext { /// Creates a new `GeneratorContext` from the raw `Context` state components. pub(crate) fn new( - environments: DeclarativeEnvironmentStack, + environments: EnvironmentStack, stack: Vec, active_function: Option, call_frame: CallFrame, diff --git a/boa_engine/src/environments/mod.rs b/boa_engine/src/environments/mod.rs index e9c7dc2761f..616b9e3c2dc 100644 --- a/boa_engine/src/environments/mod.rs +++ b/boa_engine/src/environments/mod.rs @@ -30,8 +30,8 @@ mod runtime; pub(crate) use { compile::CompileTimeEnvironment, runtime::{ - BindingLocator, DeclarativeEnvironment, DeclarativeEnvironmentStack, Environment, - EnvironmentSlots, + BindingLocator, DeclarativeEnvironment, Environment, EnvironmentStack, FunctionSlots, + ThisBindingStatus, }, }; diff --git a/boa_engine/src/environments/runtime/declarative/function.rs b/boa_engine/src/environments/runtime/declarative/function.rs new file mode 100644 index 00000000000..e9d6ac4cc27 --- /dev/null +++ b/boa_engine/src/environments/runtime/declarative/function.rs @@ -0,0 +1,208 @@ +use boa_gc::{custom_trace, Finalize, GcRefCell, Trace}; + +use crate::{JsNativeError, JsObject, JsResult, JsValue}; + +use super::PoisonableEnvironment; + +#[derive(Debug, Trace, Finalize)] +pub(crate) struct FunctionEnvironment { + inner: PoisonableEnvironment, + slots: FunctionSlots, +} + +impl FunctionEnvironment { + /// Creates a new `FunctionEnvironment`. + pub(crate) fn new(bindings: usize, poisoned: bool, with: bool, slots: FunctionSlots) -> Self { + Self { + inner: PoisonableEnvironment::new(bindings, poisoned, with), + slots, + } + } + + /// Gets the slots of this function environment. + pub(crate) const fn slots(&self) -> &FunctionSlots { + &self.slots + } + + /// Gets the `poisonable_environment` of this function environment. + pub(crate) const fn poisonable_environment(&self) -> &PoisonableEnvironment { + &self.inner + } + + /// Gets the binding value from the environment by it's index. + /// + /// # Panics + /// + /// Panics if the binding value is out of range or not initialized. + #[track_caller] + pub(crate) fn get(&self, index: usize) -> Option { + self.inner.get(index) + } + + /// Sets the binding value from the environment by index. + /// + /// # Panics + /// + /// Panics if the binding value is out of range. + #[track_caller] + pub(crate) fn set(&self, index: usize, value: JsValue) { + self.inner.set(index, value); + } + + /// `BindThisValue` + /// + /// Sets the given value as the `this` binding of the environment. + /// Returns `false` if the `this` binding has already been initialized. + /// + /// More information: + /// - [ECMAScript specification][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-bindthisvalue + pub(crate) fn bind_this_value(&self, value: JsObject) -> JsResult<()> { + let mut this = self.slots.this.borrow_mut(); + match &*this { + ThisBindingStatus::Lexical => { + unreachable!("1. Assert: envRec.[[ThisBindingStatus]] is not lexical.") + } + ThisBindingStatus::Initialized(_) => { + // 2. If envRec.[[ThisBindingStatus]] is initialized, throw a ReferenceError exception. + return Err(JsNativeError::reference() + .with_message("cannot reinitialize `this` binding") + .into()); + } + ThisBindingStatus::Uninitialized => { + // 3. Set envRec.[[ThisValue]] to V. + // 4. Set envRec.[[ThisBindingStatus]] to initialized. + *this = ThisBindingStatus::Initialized(value.into()); + } + } + + // 5. Return V. + Ok(()) + } + + /// `HasSuperBinding` + /// + /// Returns `true` if the environment has a `super` binding. + /// + /// More information: + /// - [ECMAScript specification][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-function-environment-records-hassuperbinding + /// + /// # Panics + /// + /// Panics if the function object of the environment is not a function. + pub(crate) fn has_super_binding(&self) -> bool { + // 1.If envRec.[[ThisBindingStatus]] is lexical, return false. + if matches!(&*self.slots.this.borrow(), ThisBindingStatus::Lexical) { + return false; + } + + // 2. If envRec.[[FunctionObject]].[[HomeObject]] is undefined, return false; otherwise, return true. + self.slots + .function_object + .borrow() + .as_function() + .expect("function object must be function") + .get_home_object() + .is_some() + } + + /// `HasThisBinding` + /// + /// Returns `true` if the environment has a `this` binding. + /// + /// More information: + /// - [ECMAScript specification][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-function-environment-records-hasthisbinding + pub(crate) fn has_this_binding(&self) -> bool { + // 1. If envRec.[[ThisBindingStatus]] is lexical, return false; otherwise, return true. + !matches!(&*self.slots.this.borrow(), ThisBindingStatus::Lexical) + } + + /// `GetThisBinding` + /// + /// Returns the `this` binding of the current environment. + /// + /// Differs slightly from the spec where lexical this (arrow functions) doesn't get asserted, + /// but instead is returned as `None`. + /// + /// More information: + /// - [ECMAScript specification][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-function-environment-records-getthisbinding + pub(crate) fn get_this_binding(&self) -> JsResult> { + match &*self.slots.this.borrow() { + ThisBindingStatus::Lexical => Ok(None), + // 2. If envRec.[[ThisBindingStatus]] is uninitialized, throw a ReferenceError exception. + ThisBindingStatus::Uninitialized => Err(JsNativeError::reference() + .with_message( + "Must call super constructor in derived \ + class before accessing 'this' or returning from derived constructor", + ) + .into()), + // 3. Return envRec.[[ThisValue]]. + ThisBindingStatus::Initialized(this) => Ok(Some(this.clone())), + } + } +} + +/// Describes the status of a `this` binding in function environments. +#[derive(Clone, Debug, Finalize)] +pub(crate) enum ThisBindingStatus { + /// Function doesn't have a `this` binding. (arrow functions and async arrow functions) + Lexical, + /// Function has a `this` binding, but is uninitialized. (derived constructors) + Uninitialized, + /// Funciton has an initialized `this` binding. (base constructors and most callable objects) + Initialized(JsValue), +} + +unsafe impl Trace for ThisBindingStatus { + custom_trace!(this, { + match this { + ThisBindingStatus::Initialized(obj) => mark(obj), + ThisBindingStatus::Lexical | ThisBindingStatus::Uninitialized => {} + } + }); +} + +/// Holds the internal slots of a function environment. +#[derive(Clone, Debug, Trace, Finalize)] +pub(crate) struct FunctionSlots { + /// The `[[ThisValue]]` and `[[ThisBindingStatus]]` internal slots. + this: GcRefCell, + + /// The `[[FunctionObject]]` internal slot. + function_object: JsObject, + + /// The `[[NewTarget]]` internal slot. + new_target: Option, +} + +impl FunctionSlots { + /// Creates a new `FunctionSluts`. + pub(crate) fn new( + this: ThisBindingStatus, + function_object: JsObject, + new_target: Option, + ) -> Self { + Self { + this: GcRefCell::new(this), + function_object, + new_target, + } + } + + /// Returns the value of the `[[FunctionObject]]` internal slot. + pub(crate) const fn function_object(&self) -> &JsObject { + &self.function_object + } + + /// Returns the value of the `[[NewTarget]]` internal slot. + pub(crate) const fn new_target(&self) -> Option<&JsObject> { + self.new_target.as_ref() + } +} diff --git a/boa_engine/src/environments/runtime/declarative/global.rs b/boa_engine/src/environments/runtime/declarative/global.rs new file mode 100644 index 00000000000..fbd75eac1f7 --- /dev/null +++ b/boa_engine/src/environments/runtime/declarative/global.rs @@ -0,0 +1,58 @@ +use boa_gc::{Finalize, Trace}; + +use crate::{JsObject, JsValue}; + +use super::PoisonableEnvironment; + +#[derive(Debug, Trace, Finalize)] +pub(crate) struct GlobalEnvironment { + inner: PoisonableEnvironment, + global_this: JsObject, +} + +impl GlobalEnvironment { + /// Creates a new `GlobalEnvironment`. + pub(crate) fn new(global_this: JsObject) -> Self { + Self { + inner: PoisonableEnvironment::new(0, false, false), + global_this, + } + } + + /// Gets the `poisonable_environment` of this global environment. + pub(crate) const fn poisonable_environment(&self) -> &PoisonableEnvironment { + &self.inner + } + + /// Gets the binding value from the environment by it's index. + /// + /// # Panics + /// + /// Panics if the binding value is out of range or not initialized. + #[track_caller] + pub(crate) fn get(&self, index: usize) -> Option { + self.inner.get(index) + } + + /// Sets the binding value from the environment by index. + /// + /// # Panics + /// + /// Panics if the binding value is out of range. + #[track_caller] + pub(crate) fn set(&self, index: usize, value: JsValue) { + self.inner.set(index, value); + } + + /// `GetThisBinding` + /// + /// Returns the `this` binding on the global environment. + /// + /// More information: + /// - [ECMAScript specification][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-function-environment-records-getthisbinding + pub(crate) fn get_this_binding(&self) -> JsObject { + self.global_this.clone() + } +} diff --git a/boa_engine/src/environments/runtime/declarative/lexical.rs b/boa_engine/src/environments/runtime/declarative/lexical.rs new file mode 100644 index 00000000000..ef39abe5b3e --- /dev/null +++ b/boa_engine/src/environments/runtime/declarative/lexical.rs @@ -0,0 +1,44 @@ +use boa_gc::{Finalize, Trace}; + +use crate::JsValue; + +use super::PoisonableEnvironment; + +#[derive(Debug, Trace, Finalize)] +pub(crate) struct LexicalEnvironment { + inner: PoisonableEnvironment, +} + +impl LexicalEnvironment { + /// Creates a new `LexicalEnvironment`. + pub(crate) fn new(bindings: usize, poisoned: bool, with: bool) -> Self { + Self { + inner: PoisonableEnvironment::new(bindings, poisoned, with), + } + } + + /// Gets the `poisonable_environment` of this lexical environment. + pub(crate) const fn poisonable_environment(&self) -> &PoisonableEnvironment { + &self.inner + } + + /// Gets the binding value from the environment by it's index. + /// + /// # Panics + /// + /// Panics if the binding value is out of range or not initialized. + #[track_caller] + pub(crate) fn get(&self, index: usize) -> Option { + self.inner.get(index) + } + + /// Sets the binding value from the environment by index. + /// + /// # Panics + /// + /// Panics if the binding value is out of range. + #[track_caller] + pub(crate) fn set(&self, index: usize, value: JsValue) { + self.inner.set(index, value); + } +} diff --git a/boa_engine/src/environments/runtime/declarative/mod.rs b/boa_engine/src/environments/runtime/declarative/mod.rs new file mode 100644 index 00000000000..6d38765ae51 --- /dev/null +++ b/boa_engine/src/environments/runtime/declarative/mod.rs @@ -0,0 +1,303 @@ +mod function; +mod global; +mod lexical; + +use std::cell::Cell; + +use boa_gc::{Finalize, Gc, GcRefCell, Trace}; +pub(crate) use function::{FunctionEnvironment, FunctionSlots, ThisBindingStatus}; +pub(crate) use global::GlobalEnvironment; +pub(crate) use lexical::LexicalEnvironment; + +use crate::{environments::CompileTimeEnvironment, JsObject, JsResult, JsValue}; + +/// A declarative environment holds binding values at runtime. +/// +/// Bindings are stored in a fixed size list of optional values. +/// If a binding is not initialized, the value is `None`. +/// +/// Optionally, an environment can hold a `this` value. +/// The `this` value is present only if the environment is a function environment. +/// +/// Code evaluation at runtime (e.g. the `eval` built-in function) can add +/// bindings to existing, compiled function environments. +/// This makes it impossible to determine the location of all bindings at compile time. +/// To dynamically check for added bindings at runtime, a reference to the +/// corresponding compile time environment is needed. +/// +/// Checking all environments for potential added bindings at runtime on every get/set +/// would offset the performance improvement of determining binding locations at compile time. +/// To minimize this, each environment holds a `poisoned` flag. +/// If bindings where added at runtime, the current environment and all inner environments +/// are marked as poisoned. +/// All poisoned environments have to be checked for added bindings. +#[derive(Debug, Trace, Finalize)] +pub(crate) struct DeclarativeEnvironment { + kind: DeclarativeEnvironmentKind, + compile: Gc>, +} + +impl DeclarativeEnvironment { + /// Creates a new global `DeclarativeEnvironment`. + pub(crate) fn global(global_this: JsObject) -> Self { + DeclarativeEnvironment { + kind: DeclarativeEnvironmentKind::Global(GlobalEnvironment::new(global_this)), + compile: Gc::new(GcRefCell::new(CompileTimeEnvironment::new_global())), + } + } + + /// Creates a new `DeclarativeEnvironment` from its kind and compile environment. + pub(crate) fn new( + kind: DeclarativeEnvironmentKind, + compile: Gc>, + ) -> Self { + Self { kind, compile } + } + + /// Gets the compile time environment of this environment. + pub(crate) fn compile_env(&self) -> Gc> { + self.compile.clone() + } + + /// Returns a reference to the the kind of the environment. + pub(crate) const fn kind(&self) -> &DeclarativeEnvironmentKind { + &self.kind + } + + /// Gets the binding value from the environment by index. + /// + /// # Panics + /// + /// Panics if the binding value is out of range or not initialized. + #[track_caller] + pub(crate) fn get(&self, index: usize) -> Option { + self.kind.get(index) + } + + /// Sets the binding value from the environment by index. + /// + /// # Panics + /// + /// Panics if the binding value is out of range. + #[track_caller] + pub(crate) fn set(&self, index: usize, value: JsValue) { + self.kind.set(index, value); + } + + /// `GetThisBinding` + /// + /// Returns the `this` binding of this environment. + /// + /// More information: + /// - [ECMAScript specification][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-function-environment-records-getthisbinding + pub(crate) fn get_this_binding(&self) -> JsResult> { + self.kind.get_this_binding() + } + + /// `HasThisBinding` + /// + /// Returns if the environment has a `this` binding. + /// + /// More information: + /// - [ECMAScript specification][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-function-environment-records-hasthisbinding + pub(crate) fn has_this_binding(&self) -> bool { + self.kind.has_this_binding() + } + + /// Returns `true` if this environment is poisoned. + pub(crate) fn poisoned(&self) -> bool { + self.kind.poisoned() + } + + /// Returns `true` if this environment is inside a `with` environment. + pub(crate) fn with(&self) -> bool { + self.kind.with() + } + + /// Poisons this environment for future binding searchs. + pub(crate) fn poison(&self) { + self.kind.poison(); + } +} + +/// The kind of the declarative environment. +#[derive(Debug, Trace, Finalize)] +pub(crate) enum DeclarativeEnvironmentKind { + /// Only stores lexical bindings. + Lexical(LexicalEnvironment), + /// Stores lexical bindings, global var bindings and the global this. + Global(GlobalEnvironment), + /// Stores lexical bindings, var bindings and the `FunctionSlots` of the function environment. + Function(FunctionEnvironment), +} + +impl DeclarativeEnvironmentKind { + /// Unwraps the inner function environment if possible. Returns `None` otherwise. + pub(crate) const fn as_function(&self) -> Option<&FunctionEnvironment> { + if let Self::Function(fun) = &self { + Some(fun) + } else { + None + } + } + + /// Unwraps the inner global environment if possible. Returns `None` otherwise. + pub(crate) const fn as_global(&self) -> Option<&GlobalEnvironment> { + if let Self::Global(fun) = &self { + Some(fun) + } else { + None + } + } + + /// Get the binding value from the environment by it's index. + /// + /// # Panics + /// + /// Panics if the binding value is out of range or not initialized. + #[track_caller] + pub(crate) fn get(&self, index: usize) -> Option { + match self { + DeclarativeEnvironmentKind::Lexical(inner) => inner.get(index), + DeclarativeEnvironmentKind::Global(inner) => inner.get(index), + DeclarativeEnvironmentKind::Function(inner) => inner.get(index), + } + } + + /// Sets the binding value from the environment by index. + /// + /// # Panics + /// + /// Panics if the binding value is out of range. + #[track_caller] + pub(crate) fn set(&self, index: usize, value: JsValue) { + match self { + DeclarativeEnvironmentKind::Lexical(inner) => inner.set(index, value), + DeclarativeEnvironmentKind::Global(inner) => inner.set(index, value), + DeclarativeEnvironmentKind::Function(inner) => inner.set(index, value), + } + } + + /// `GetThisBinding` + /// + /// Returns the `this` binding of this environment. + /// + /// More information: + /// - [ECMAScript specification][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-function-environment-records-getthisbinding + pub(crate) fn get_this_binding(&self) -> JsResult> { + match self { + DeclarativeEnvironmentKind::Lexical(_) => Ok(None), + DeclarativeEnvironmentKind::Global(g) => Ok(Some(g.get_this_binding().into())), + DeclarativeEnvironmentKind::Function(f) => f.get_this_binding(), + } + } + + /// `HasThisBinding` + /// + /// Returns if the environment has a `this` binding. + /// + /// More information: + /// - [ECMAScript specification][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-function-environment-records-hasthisbinding + pub(crate) fn has_this_binding(&self) -> bool { + match self { + DeclarativeEnvironmentKind::Lexical(_) => false, + DeclarativeEnvironmentKind::Global(_) => true, + DeclarativeEnvironmentKind::Function(f) => f.has_this_binding(), + } + } + + /// Returns `true` if this environment is poisoned. + pub(crate) fn poisoned(&self) -> bool { + match self { + DeclarativeEnvironmentKind::Lexical(lex) => lex.poisonable_environment().poisoned(), + DeclarativeEnvironmentKind::Global(g) => g.poisonable_environment().poisoned(), + DeclarativeEnvironmentKind::Function(f) => f.poisonable_environment().poisoned(), + } + } + + /// Returns `true` if this environment is inside a `with` environment. + pub(crate) fn with(&self) -> bool { + match self { + DeclarativeEnvironmentKind::Lexical(lex) => lex.poisonable_environment().with(), + DeclarativeEnvironmentKind::Global(g) => g.poisonable_environment().with(), + DeclarativeEnvironmentKind::Function(f) => f.poisonable_environment().with(), + } + } + + /// Poisons this environment for future binding searches. + pub(crate) fn poison(&self) { + match self { + DeclarativeEnvironmentKind::Lexical(lex) => lex.poisonable_environment().poison(), + DeclarativeEnvironmentKind::Global(g) => g.poisonable_environment().poison(), + DeclarativeEnvironmentKind::Function(f) => f.poisonable_environment().poison(), + } + } +} + +#[derive(Debug, Trace, Finalize)] +pub(crate) struct PoisonableEnvironment { + bindings: GcRefCell>>, + #[unsafe_ignore_trace] + poisoned: Cell, + #[unsafe_ignore_trace] + with: Cell, +} + +impl PoisonableEnvironment { + /// Creates a new `PoisonableEnvironment`. + pub(crate) fn new(bindings_count: usize, poisoned: bool, with: bool) -> Self { + PoisonableEnvironment { + bindings: GcRefCell::new(vec![None; bindings_count]), + poisoned: Cell::new(poisoned), + with: Cell::new(with), + } + } + + /// Gets the bindings of this poisonable environment. + pub(crate) const fn bindings(&self) -> &GcRefCell>> { + &self.bindings + } + + /// Gets the binding value from the environment by it's index. + /// + /// # Panics + /// + /// Panics if the binding value is out of range. + #[track_caller] + fn get(&self, index: usize) -> Option { + self.bindings.borrow()[index].clone() + } + + /// Sets the binding value from the environment by index. + /// + /// # Panics + /// + /// Panics if the binding value is out of range. + #[track_caller] + pub(crate) fn set(&self, index: usize, value: JsValue) { + self.bindings.borrow_mut()[index] = Some(value); + } + + /// Returns `true` if this environment is poisoned. + fn poisoned(&self) -> bool { + self.poisoned.get() + } + + /// Returns `true` if this environment is inside a `with` environment. + fn with(&self) -> bool { + self.with.get() + } + + /// Poisons this environment for future binding searches. + fn poison(&self) { + self.poisoned.set(true); + } +} diff --git a/boa_engine/src/environments/runtime.rs b/boa_engine/src/environments/runtime/mod.rs similarity index 61% rename from boa_engine/src/environments/runtime.rs rename to boa_engine/src/environments/runtime/mod.rs index 3557636a612..636fec61ce4 100644 --- a/boa_engine/src/environments/runtime.rs +++ b/boa_engine/src/environments/runtime/mod.rs @@ -5,247 +5,20 @@ use crate::{ use boa_ast::expression::Identifier; use boa_gc::{empty_trace, Finalize, Gc, GcRefCell, Trace}; use rustc_hash::FxHashSet; -use std::cell::Cell; -/// A declarative environment holds binding values at runtime. -/// -/// Bindings are stored in a fixed size list of optional values. -/// If a binding is not initialized, the value is `None`. -/// -/// Optionally, an environment can hold a `this` value. -/// The `this` value is present only if the environment is a function environment. -/// -/// Code evaluation at runtime (e.g. the `eval` built-in function) can add -/// bindings to existing, compiled function environments. -/// This makes it impossible to determine the location of all bindings at compile time. -/// To dynamically check for added bindings at runtime, a reference to the -/// corresponding compile time environment is needed. -/// -/// Checking all environments for potential added bindings at runtime on every get/set -/// would offset the performance improvement of determining binding locations at compile time. -/// To minimize this, each environment holds a `poisoned` flag. -/// If bindings where added at runtime, the current environment and all inner environments -/// are marked as poisoned. -/// All poisoned environments have to be checked for added bindings. -#[derive(Debug, Trace, Finalize)] -pub(crate) struct DeclarativeEnvironment { - bindings: GcRefCell>>, - compile: Gc>, - #[unsafe_ignore_trace] - poisoned: Cell, - #[unsafe_ignore_trace] - with: Cell, - slots: Option, -} - -impl DeclarativeEnvironment { - /// Creates a new, global `DeclarativeEnvironment`. - pub(crate) fn new_global() -> Self { - DeclarativeEnvironment { - bindings: GcRefCell::new(Vec::new()), - compile: Gc::new(GcRefCell::new(CompileTimeEnvironment::new_global())), - poisoned: Cell::new(false), - with: Cell::new(false), - slots: Some(EnvironmentSlots::Global), - } - } - - /// Gets the compile time environment of this environment. - pub(crate) fn compile_env(&self) -> Gc> { - self.compile.clone() - } - - /// Gets the bindings of this environment. - pub(crate) const fn bindings(&self) -> &GcRefCell>> { - &self.bindings - } -} - -/// Describes the different types of internal slot data that an environment can hold. -#[derive(Clone, Debug, Trace, Finalize)] -pub(crate) enum EnvironmentSlots { - Function(GcRefCell), - Global, -} - -impl EnvironmentSlots { - /// Return the slots if they are part of a function environment. - pub(crate) const fn as_function_slots(&self) -> Option<&GcRefCell> { - if let Self::Function(env) = &self { - Some(env) - } else { - None - } - } -} - -/// Holds the internal slots of a function environment. -#[derive(Clone, Debug, Trace, Finalize)] -pub(crate) struct FunctionSlots { - /// The `[[ThisValue]]` internal slot. - this: JsValue, - - /// The `[[ThisBindingStatus]]` internal slot. - #[unsafe_ignore_trace] - this_binding_status: ThisBindingStatus, - - /// The `[[FunctionObject]]` internal slot. - function_object: JsObject, - - /// The `[[NewTarget]]` internal slot. - new_target: Option, -} - -impl FunctionSlots { - /// Returns the value of the `[[FunctionObject]]` internal slot. - pub(crate) const fn function_object(&self) -> &JsObject { - &self.function_object - } - - /// Returns the value of the `[[NewTarget]]` internal slot. - pub(crate) const fn new_target(&self) -> Option<&JsObject> { - self.new_target.as_ref() - } - - /// `BindThisValue` - /// - /// Sets the given value as the `this` binding of the environment. - /// Returns `false` if the `this` binding has already been initialized. - /// - /// More information: - /// - [ECMAScript specification][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-bindthisvalue - pub(crate) fn bind_this_value(&mut self, this: &JsObject) -> bool { - // 1. Assert: envRec.[[ThisBindingStatus]] is not lexical. - debug_assert!(self.this_binding_status != ThisBindingStatus::Lexical); - - // 2. If envRec.[[ThisBindingStatus]] is initialized, throw a ReferenceError exception. - if self.this_binding_status == ThisBindingStatus::Initialized { - return false; - } - - // 3. Set envRec.[[ThisValue]] to V. - self.this = this.clone().into(); - - // 4. Set envRec.[[ThisBindingStatus]] to initialized. - self.this_binding_status = ThisBindingStatus::Initialized; - - // 5. Return V. - true - } - - /// `HasThisBinding` - /// - /// Returns if the environment has a `this` binding. - /// - /// More information: - /// - [ECMAScript specification][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-function-environment-records-hasthisbinding - pub(crate) fn has_this_binding(&self) -> bool { - // 1. If envRec.[[ThisBindingStatus]] is lexical, return false; otherwise, return true. - self.this_binding_status != ThisBindingStatus::Lexical - } - - /// `HasSuperBinding` - /// - /// Returns if the environment has a `super` binding. - /// - /// More information: - /// - [ECMAScript specification][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-function-environment-records-hassuperbinding - /// - /// # Panics - /// - /// Panics if the function object of the environment is not a function. - pub(crate) fn has_super_binding(&self) -> bool { - // 1.If envRec.[[ThisBindingStatus]] is lexical, return false. - if self.this_binding_status == ThisBindingStatus::Lexical { - return false; - } - - // 2. If envRec.[[FunctionObject]].[[HomeObject]] is undefined, return false; otherwise, return true. - self.function_object - .borrow() - .as_function() - .expect("function object must be function") - .get_home_object() - .is_some() - } - - /// `GetThisBinding` - /// - /// Returns the `this` binding on the function environment. - /// - /// More information: - /// - [ECMAScript specification][spec] - /// - /// [spec]: https://tc39.es/ecma262/#sec-function-environment-records-getthisbinding - pub(crate) fn get_this_binding(&self) -> Result<&JsValue, JsNativeError> { - // 1. Assert: envRec.[[ThisBindingStatus]] is not lexical. - debug_assert!(self.this_binding_status != ThisBindingStatus::Lexical); +mod declarative; - // 2. If envRec.[[ThisBindingStatus]] is uninitialized, throw a ReferenceError exception. - if self.this_binding_status == ThisBindingStatus::Uninitialized { - Err(JsNativeError::reference().with_message("Must call super constructor in derived class before accessing 'this' or returning from derived constructor")) - } else { - // 3. Return envRec.[[ThisValue]]. - Ok(&self.this) - } - } -} - -/// Describes the status of a `this` binding in function environments. -#[derive(Clone, Copy, Debug, PartialEq)] -enum ThisBindingStatus { - Lexical, - Initialized, - Uninitialized, -} - -impl DeclarativeEnvironment { - /// Returns the internal slot data of the current environment. - pub(crate) const fn slots(&self) -> Option<&EnvironmentSlots> { - self.slots.as_ref() - } - - /// Get the binding value from the environment by it's index. - /// - /// # Panics - /// - /// Panics if the binding value is out of range or not initialized. - pub(crate) fn get(&self, index: usize) -> JsValue { - self.bindings - .borrow() - .get(index) - .expect("binding index must be in range") - .clone() - .expect("binding must be initialized") - } - - /// Set the binding value at the specified index. - /// - /// # Panics - /// - /// Panics if the binding value is out of range or not initialized. - pub(crate) fn set(&self, index: usize, value: JsValue) { - let mut bindings = self.bindings.borrow_mut(); - let binding = bindings - .get_mut(index) - .expect("binding index must be in range"); - assert!(!binding.is_none(), "binding must be initialized"); - *binding = Some(value); - } -} +pub(crate) use self::declarative::{ + DeclarativeEnvironment, DeclarativeEnvironmentKind, FunctionEnvironment, FunctionSlots, + LexicalEnvironment, ThisBindingStatus, +}; /// The environment stack holds all environments at runtime. /// /// Environments themselves are garbage collected, /// because they must be preserved for function calls. #[derive(Clone, Debug, Trace, Finalize)] -pub(crate) struct DeclarativeEnvironmentStack { +pub(crate) struct EnvironmentStack { stack: Vec, } @@ -273,9 +46,13 @@ impl Environment { } } -impl DeclarativeEnvironmentStack { +impl EnvironmentStack { /// Create a new environment stack. pub(crate) fn new(global: Gc) -> Self { + assert!(matches!( + global.kind(), + DeclarativeEnvironmentKind::Global(_) + )); Self { stack: vec![Environment::Declarative(global)], } @@ -283,6 +60,10 @@ impl DeclarativeEnvironmentStack { /// Replaces the current global with a new global environment. pub(crate) fn replace_global(&mut self, global: Gc) { + assert!(matches!( + global.kind(), + DeclarativeEnvironmentKind::Global(_) + )); self.stack[0] = Environment::Declarative(global); } @@ -296,9 +77,9 @@ impl DeclarativeEnvironmentStack { .filter_map(Environment::as_declarative) .rev() { - if let Some(EnvironmentSlots::Function(_)) = env.slots { - let compile_bindings_number = env.compile.borrow().num_bindings(); - let mut bindings_mut = env.bindings.borrow_mut(); + if let DeclarativeEnvironmentKind::Function(fun) = &env.kind() { + let compile_bindings_number = env.compile_env().borrow().num_bindings(); + let mut bindings_mut = fun.poisonable_environment().bindings().borrow_mut(); if compile_bindings_number > bindings_mut.len() { let diff = compile_bindings_number - bindings_mut.len(); @@ -323,7 +104,8 @@ impl DeclarativeEnvironmentStack { .filter_map(Environment::as_declarative) .rev() { - let compile = env.compile.borrow(); + let compile = env.compile_env(); + let compile = compile.borrow(); for name in names { if compile.has_lex_binding(*name) { return Some(*name); @@ -344,7 +126,8 @@ impl DeclarativeEnvironmentStack { .rev() .filter_map(Environment::as_declarative) { - let compile = env.compile.borrow(); + let compile = env.compile_env(); + let compile = compile.borrow(); if compile.is_function() { return compile.outer().is_none(); } @@ -384,26 +167,34 @@ impl DeclarativeEnvironmentStack { /// # Panics /// /// Panics if no environment exists on the stack. - pub(crate) fn get_this_environment(&self) -> &EnvironmentSlots { - for env in self - .stack - .iter() - .filter_map(Environment::as_declarative) - .rev() - { - if let Some(slots) = &env.slots { - match slots { - EnvironmentSlots::Function(function_env) => { - if function_env.borrow().has_this_binding() { - return slots; - } - } - EnvironmentSlots::Global => return slots, + pub(crate) fn get_this_environment(&self) -> &DeclarativeEnvironmentKind { + for env in self.stack.iter().rev() { + if let Some(decl) = env.as_declarative().filter(|decl| decl.has_this_binding()) { + return decl.kind(); + } + } + + panic!("global environment must exist"); + } + + /// `GetThisBinding` + /// + /// Returns the current `this` binding of the environment. + /// + /// More information: + /// - [ECMAScript specification][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-function-environment-records-getthisbinding + pub(crate) fn get_this_binding(&self) -> JsResult { + for env in self.stack.iter().rev() { + if let Environment::Declarative(decl) = env { + if let Some(this) = decl.get_this_binding()? { + return Ok(this); } } } - panic!("global environment must exist") + panic!("global environment must exist"); } /// Push a new object environment on the environments stack and return it's index. @@ -413,13 +204,13 @@ impl DeclarativeEnvironmentStack { index } - /// Push a declarative environment on the environments stack and return it's index. + /// Push a lexical environment on the environments stack and return it's index. /// /// # Panics /// /// Panics if no environment exists on the stack. #[track_caller] - pub(crate) fn push_declarative( + pub(crate) fn push_lexical( &mut self, num_bindings: usize, compile_environment: Gc>, @@ -438,19 +229,21 @@ impl DeclarativeEnvironmentStack { .rev() .find_map(Environment::as_declarative) .expect("global environment must always exist"); - (environment.poisoned.get(), with || environment.with.get()) + (environment.poisoned(), with || environment.with()) }; let index = self.stack.len(); - self.stack - .push(Environment::Declarative(Gc::new(DeclarativeEnvironment { - bindings: GcRefCell::new(vec![None; num_bindings]), - compile: compile_environment, - poisoned: Cell::new(poisoned), - with: Cell::new(with), - slots: None, - }))); + self.stack.push(Environment::Declarative(Gc::new( + DeclarativeEnvironment::new( + DeclarativeEnvironmentKind::Lexical(LexicalEnvironment::new( + num_bindings, + poisoned, + with, + )), + compile_environment, + ), + ))); index } @@ -465,10 +258,7 @@ impl DeclarativeEnvironmentStack { &mut self, num_bindings: usize, compile_environment: Gc>, - this: Option, - function_object: JsObject, - new_target: Option, - lexical: bool, + function_slots: FunctionSlots, ) { let (poisoned, with) = { let with = self @@ -484,32 +274,20 @@ impl DeclarativeEnvironmentStack { .rev() .find_map(Environment::as_declarative) .expect("global environment must always exist"); - (environment.poisoned.get(), with || environment.with.get()) + (environment.poisoned(), with || environment.with()) }; - let this_binding_status = if lexical { - ThisBindingStatus::Lexical - } else if this.is_some() { - ThisBindingStatus::Initialized - } else { - ThisBindingStatus::Uninitialized - }; - - let this = this.unwrap_or(JsValue::Null); - - self.stack - .push(Environment::Declarative(Gc::new(DeclarativeEnvironment { - bindings: GcRefCell::new(vec![None; num_bindings]), - compile: compile_environment, - poisoned: Cell::new(poisoned), - with: Cell::new(with), - slots: Some(EnvironmentSlots::Function(GcRefCell::new(FunctionSlots { - this, - this_binding_status, - function_object, - new_target, - }))), - }))); + self.stack.push(Environment::Declarative(Gc::new( + DeclarativeEnvironment::new( + DeclarativeEnvironmentKind::Function(FunctionEnvironment::new( + num_bindings, + poisoned, + with, + function_slots, + )), + compile_environment, + ), + ))); } /// Push a function environment that inherits it's internal slots from the outer function @@ -533,31 +311,37 @@ impl DeclarativeEnvironmentStack { let with = self .stack .last() - .expect("global environment must always exist") + .expect("can only be called inside a function") .as_declarative() .is_none(); - let environment = self + let (environment, slots) = self .stack .iter() .rev() - .find_map(|env| env.as_declarative().filter(|decl| decl.slots().is_some())) - .expect("global environment must always exist"); - ( - environment.poisoned.get(), - with || environment.with.get(), - environment.slots.clone(), - ) + .find_map(|env| { + if let Some(env) = env.as_declarative() { + if let DeclarativeEnvironmentKind::Function(fun) = env.kind() { + return Some((env, fun.slots().clone())); + } + } + None + }) + .expect("can only be called inside a function"); + (environment.poisoned(), with || environment.with(), slots) }; - self.stack - .push(Environment::Declarative(Gc::new(DeclarativeEnvironment { - bindings: GcRefCell::new(vec![None; num_bindings]), - compile: compile_environment, - poisoned: Cell::new(poisoned), - with: Cell::new(with), - slots, - }))); + self.stack.push(Environment::Declarative(Gc::new( + DeclarativeEnvironment::new( + DeclarativeEnvironmentKind::Function(FunctionEnvironment::new( + num_bindings, + poisoned, + with, + slots, + )), + compile_environment, + ), + ))); } /// Pop environment from the environments stack. @@ -593,8 +377,7 @@ impl DeclarativeEnvironmentStack { .filter_map(Environment::as_declarative) .last() .expect("global environment must always exist") - .compile - .clone() + .compile_env() } /// Mark that there may be added bindings from the current environment to the next function @@ -606,7 +389,7 @@ impl DeclarativeEnvironmentStack { .rev() .filter_map(Environment::as_declarative) { - env.poisoned.set(true); + env.poison(); if env.compile_env().borrow().is_function() { return; } @@ -625,17 +408,12 @@ impl DeclarativeEnvironmentStack { binding_index: usize, value: JsValue, ) { - let mut bindings = self + let env = self .stack .get(environment_index) .expect("environment index must be in range") - .declarative_expect() - .bindings - .borrow_mut(); - let binding = bindings - .get_mut(binding_index) - .expect("binding index must be in range"); - *binding = Some(value); + .declarative_expect(); + env.set(binding_index, value); } /// Set the value of a binding if it is uninitialized. @@ -650,18 +428,13 @@ impl DeclarativeEnvironmentStack { binding_index: usize, value: JsValue, ) { - let mut bindings = self + let env = self .stack .get(environment_index) .expect("environment index must be in range") - .declarative_expect() - .bindings - .borrow_mut(); - let binding = bindings - .get_mut(binding_index) - .expect("binding index must be in range"); - if binding.is_none() { - *binding = Some(value); + .declarative_expect(); + if env.get(binding_index).is_none() { + env.set(binding_index, value); } } } @@ -791,7 +564,7 @@ impl Context<'_> { pub(crate) fn find_runtime_binding(&mut self, locator: &mut BindingLocator) -> JsResult<()> { let current = self.vm.environments.current(); if let Some(env) = current.as_declarative() { - if !env.with.get() && !env.poisoned.get() { + if !env.with() && !env.poisoned() { return Ok(()); } } @@ -799,8 +572,9 @@ impl Context<'_> { for env_index in (locator.environment_index..self.vm.environments.stack.len()).rev() { match self.environment_expect(env_index) { Environment::Declarative(env) => { - if env.poisoned.get() { - let compile = env.compile.borrow(); + if env.poisoned() { + let compile = env.compile_env(); + let compile = compile.borrow(); if compile.is_function() { if let Some(b) = compile.get_binding(locator.name) { locator.environment_index = b.environment_index; @@ -809,7 +583,7 @@ impl Context<'_> { break; } } - } else if !env.with.get() { + } else if !env.with() { break; } } @@ -851,9 +625,7 @@ impl Context<'_> { self.global_object().has_property(key, self) } else { match self.environment_expect(locator.environment_index) { - Environment::Declarative(env) => { - Ok(env.bindings.borrow()[locator.binding_index].is_some()) - } + Environment::Declarative(env) => Ok(env.get(locator.binding_index).is_some()), Environment::Object(obj) => { let key: JsString = self .interner() @@ -884,9 +656,7 @@ impl Context<'_> { } } else { match self.environment_expect(locator.environment_index) { - Environment::Declarative(env) => { - Ok(env.bindings.borrow()[locator.binding_index].clone()) - } + Environment::Declarative(env) => Ok(env.get(locator.binding_index)), Environment::Object(obj) => { let obj = obj.clone(); let key: JsString = self @@ -921,7 +691,7 @@ impl Context<'_> { } else { match self.environment_expect(locator.environment_index) { Environment::Declarative(decl) => { - decl.bindings.borrow_mut()[locator.binding_index] = Some(value); + decl.set(locator.binding_index, value); } Environment::Object(obj) => { let obj = obj.clone(); diff --git a/boa_engine/src/realm.rs b/boa_engine/src/realm.rs index 3967f32d38e..349d7de7668 100644 --- a/boa_engine/src/realm.rs +++ b/boa_engine/src/realm.rs @@ -62,11 +62,12 @@ impl Realm { let global_this = hooks .create_global_this(&intrinsics) .unwrap_or_else(|| global_object.clone()); + let environment = Gc::new(DeclarativeEnvironment::global(global_this.clone())); let realm = Self { inner: Gc::new(Inner { intrinsics, - environment: Gc::new(DeclarativeEnvironment::new_global()), + environment, global_object, global_this, }), @@ -97,8 +98,14 @@ impl Realm { /// Resizes the number of bindings on the global environment. pub(crate) fn resize_global_env(&self) { let binding_number = self.environment().compile_env().borrow().num_bindings(); + let env = self + .environment() + .kind() + .as_global() + .expect("Realm should only store global environments") + .poisonable_environment(); + let mut bindings = env.bindings().borrow_mut(); - let mut bindings = self.environment().bindings().borrow_mut(); if bindings.len() < binding_number { bindings.resize(binding_number, None); } diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index 1c1e6506e1c..6731a1285df 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -10,7 +10,7 @@ use crate::{ promise::PromiseCapability, }, context::intrinsics::StandardConstructors, - environments::{BindingLocator, CompileTimeEnvironment}, + environments::{BindingLocator, CompileTimeEnvironment, FunctionSlots, ThisBindingStatus}, error::JsNativeError, object::{internal_methods::get_prototype_from_constructor, JsObject, ObjectData, PROTOTYPE}, property::PropertyDescriptor, @@ -952,13 +952,13 @@ impl JsObject { let lexical_this_mode = code.this_mode == ThisMode::Lexical; let this = if lexical_this_mode { - None + ThisBindingStatus::Lexical } else if code.strict { - Some(this.clone()) + ThisBindingStatus::Initialized(this.clone()) } else if this.is_null_or_undefined() { - Some(context.global_object().into()) + ThisBindingStatus::Initialized(context.realm().global_this().clone().into()) } else { - Some( + ThisBindingStatus::Initialized( this.to_object(context) .expect("conversion cannot fail") .into(), @@ -971,7 +971,7 @@ impl JsObject { let index = context .vm .environments - .push_declarative(1, code.compile_environments[last_env].clone()); + .push_lexical(1, code.compile_environments[last_env].clone()); context .vm .environments @@ -983,7 +983,7 @@ impl JsObject { let index = context .vm .environments - .push_declarative(1, code.compile_environments[last_env].clone()); + .push_lexical(1, code.compile_environments[last_env].clone()); context .vm .environments @@ -994,10 +994,7 @@ impl JsObject { context.vm.environments.push_function( code.num_bindings, code.compile_environments[last_env].clone(), - this, - self.clone(), - None, - lexical_this_mode, + FunctionSlots::new(this, self.clone(), None), ); if let Some(bindings) = code.parameters_env_bindings { @@ -1005,7 +1002,7 @@ impl JsObject { context .vm .environments - .push_declarative(bindings, code.compile_environments[last_env].clone()); + .push_lexical(bindings, code.compile_environments[last_env].clone()); } if let Some(binding) = code.arguments_binding { @@ -1239,7 +1236,7 @@ impl JsObject { let index = context .vm .environments - .push_declarative(1, code.compile_environments[last_env].clone()); + .push_lexical(1, code.compile_environments[last_env].clone()); context .vm .environments @@ -1250,17 +1247,20 @@ impl JsObject { context.vm.environments.push_function( code.num_bindings, code.compile_environments[last_env].clone(), - this.clone().map(Into::into), - self.clone(), - Some(new_target.clone()), - false, + FunctionSlots::new( + this.clone().map_or(ThisBindingStatus::Uninitialized, |o| { + ThisBindingStatus::Initialized(o.into()) + }), + self.clone(), + Some(new_target.clone()), + ), ); if let Some(bindings) = code.parameters_env_bindings { context .vm .environments - .push_declarative(bindings, code.compile_environments[0].clone()); + .push_lexical(bindings, code.compile_environments[0].clone()); } if let Some(binding) = code.arguments_binding { @@ -1341,15 +1341,14 @@ impl JsObject { } else { let function_env = environment .declarative_expect() - .slots() - .expect("must be function environment") - .as_function_slots() + .kind() + .as_function() .expect("must be function environment"); function_env - .borrow() .get_this_binding() - .map(|this| { - this.as_object() + .map(|v| { + v.expect("constructors cannot be arrow functions") + .as_object() .expect("this binding must be object") .clone() }) diff --git a/boa_engine/src/vm/mod.rs b/boa_engine/src/vm/mod.rs index 239be0ec4d3..584a86f639c 100644 --- a/boa_engine/src/vm/mod.rs +++ b/boa_engine/src/vm/mod.rs @@ -8,7 +8,7 @@ use crate::JsNativeError; use crate::{ builtins::async_generator::{AsyncGenerator, AsyncGeneratorState}, - environments::{DeclarativeEnvironment, DeclarativeEnvironmentStack}, + environments::{DeclarativeEnvironment, EnvironmentStack}, vm::code_block::Readable, Context, JsError, JsObject, JsResult, JsValue, }; @@ -53,7 +53,7 @@ pub struct Vm { pub(crate) frames: Vec, pub(crate) stack: Vec, pub(crate) err: Option, - pub(crate) environments: DeclarativeEnvironmentStack, + pub(crate) environments: EnvironmentStack, #[cfg(feature = "trace")] pub(crate) trace: bool, pub(crate) runtime_limits: RuntimeLimits, @@ -66,7 +66,7 @@ impl Vm { Self { frames: Vec::with_capacity(16), stack: Vec::with_capacity(1024), - environments: DeclarativeEnvironmentStack::new(global), + environments: EnvironmentStack::new(global), err: None, #[cfg(feature = "trace")] trace: false, diff --git a/boa_engine/src/vm/opcode/environment/mod.rs b/boa_engine/src/vm/opcode/environment/mod.rs index 8cfffe8a484..36c15e8ad39 100644 --- a/boa_engine/src/vm/opcode/environment/mod.rs +++ b/boa_engine/src/vm/opcode/environment/mod.rs @@ -1,5 +1,4 @@ use crate::{ - environments::EnvironmentSlots, error::JsNativeError, vm::{opcode::Operation, CompletionType}, Context, JsResult, JsValue, @@ -17,21 +16,8 @@ impl Operation for This { const INSTRUCTION: &'static str = "INST - This"; fn execute(context: &mut Context<'_>) -> JsResult { - let env = context.vm.environments.get_this_environment(); - match env { - EnvironmentSlots::Function(env) => { - let binding_result = match env.borrow().get_this_binding() { - Ok(binding) => Ok(binding.clone()), - Err(e) => Err(e), - }; - let function_binding = binding_result?; - context.vm.push(function_binding); - } - EnvironmentSlots::Global => { - let this = context.realm().global_this(); - context.vm.push(this.clone()); - } - } + let this = context.vm.environments.get_this_binding()?; + context.vm.push(this); Ok(CompletionType::Normal) } } @@ -48,38 +34,30 @@ impl Operation for Super { const INSTRUCTION: &'static str = "INST - Super"; fn execute(context: &mut Context<'_>) -> JsResult { - let home_result = { + let home_object = { let env = context .vm .environments .get_this_environment() - .as_function_slots() + .as_function() .expect("super access must be in a function environment"); - let env = env.borrow(); - match env.get_this_binding() { - Ok(binding) => { - let function_object = env.function_object().borrow(); - let function = function_object - .as_function() - .expect("must be function object"); - - Ok(function.get_home_object().or(binding.as_object()).cloned()) - } - Err(e) => Err(e), - } + let this = env + .get_this_binding()? + .expect("`get_this_environment` ensures this returns `Some`"); + let function_object = env.slots().function_object().borrow(); + let function = function_object + .as_function() + .expect("must be function object"); + function.get_home_object().or(this.as_object()).cloned() }; - let home = home_result?; + let value = home_object + .map(|o| o.__get_prototype_of__(context)) + .transpose()? + .flatten() + .map_or_else(JsValue::null, JsValue::from); - if let Some(home) = home { - if let Some(proto) = home.__get_prototype_of__(context)? { - context.vm.push(JsValue::from(proto)); - } else { - context.vm.push(JsValue::Null); - } - } else { - context.vm.push(JsValue::Null); - }; + context.vm.push(value); Ok(CompletionType::Normal) } } @@ -100,15 +78,14 @@ impl Operation for SuperCallPrepare { .vm .environments .get_this_environment() - .as_function_slots() + .as_function() .expect("super call must be in function environment"); - let this_env_borrow = this_env.borrow(); - let new_target = this_env_borrow + let new_target = this_env + .slots() .new_target() .expect("must have new target") .clone(); - let active_function = this_env_borrow.function_object().clone(); - drop(this_env_borrow); + let active_function = this_env.slots().function_object().clone(); let super_constructor = active_function .__get_prototype_of__(context) .expect("function object must have prototype"); @@ -162,18 +139,13 @@ impl Operation for SuperCall { .vm .environments .get_this_environment() - .as_function_slots() + .as_function() .expect("super call must be in function environment"); - if !this_env.borrow_mut().bind_this_value(&result) { - return Err(JsNativeError::reference() - .with_message("this already initialized") - .into()); - } + this_env.bind_this_value(result.clone())?; + let function_object = this_env.slots().function_object().clone(); - let active_function = this_env.borrow().function_object().clone(); - - result.initialize_instance_elements(&active_function, context)?; + result.initialize_instance_elements(&function_object, context)?; context.vm.push(result); Ok(CompletionType::Normal) @@ -223,18 +195,13 @@ impl Operation for SuperCallSpread { .vm .environments .get_this_environment() - .as_function_slots() + .as_function() .expect("super call must be in function environment"); - if !this_env.borrow_mut().bind_this_value(&result) { - return Err(JsNativeError::reference() - .with_message("this already initialized") - .into()); - } - - let active_function = this_env.borrow().function_object().clone(); + this_env.bind_this_value(result.clone())?; + let function_object = this_env.slots().function_object().clone(); - result.initialize_instance_elements(&active_function, context)?; + result.initialize_instance_elements(&function_object, context)?; context.vm.push(result); Ok(CompletionType::Normal) @@ -260,21 +227,18 @@ impl Operation for SuperCallDerived { } arguments.reverse(); - let (new_target, active_function) = { - let this_env = context - .vm - .environments - .get_this_environment() - .as_function_slots() - .expect("super call must be in function environment"); - let this_env_borrow = this_env.borrow(); - let new_target = this_env_borrow - .new_target() - .expect("must have new target") - .clone(); - let active_function = this_env.borrow().function_object().clone(); - (new_target, active_function) - }; + let this_env = context + .vm + .environments + .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") @@ -292,14 +256,10 @@ impl Operation for SuperCallDerived { .vm .environments .get_this_environment() - .as_function_slots() + .as_function() .expect("super call must be in function environment"); - if !this_env.borrow_mut().bind_this_value(&result) { - return Err(JsNativeError::reference() - .with_message("this already initialized") - .into()); - } + this_env.bind_this_value(result.clone())?; result.initialize_instance_elements(&active_function, context)?; diff --git a/boa_engine/src/vm/opcode/push/environment.rs b/boa_engine/src/vm/opcode/push/environment.rs index 0313c01390f..a3408d44688 100644 --- a/boa_engine/src/vm/opcode/push/environment.rs +++ b/boa_engine/src/vm/opcode/push/environment.rs @@ -23,7 +23,7 @@ impl Operation for PushDeclarativeEnvironment { context .vm .environments - .push_declarative(num_bindings as usize, compile_environment); + .push_lexical(num_bindings as usize, compile_environment); context.vm.frame_mut().inc_frame_env_stack(); Ok(CompletionType::Normal) } diff --git a/boa_engine/src/vm/opcode/push/new_target.rs b/boa_engine/src/vm/opcode/push/new_target.rs index 540b3e65541..24cd3e23447 100644 --- a/boa_engine/src/vm/opcode/push/new_target.rs +++ b/boa_engine/src/vm/opcode/push/new_target.rs @@ -19,8 +19,8 @@ impl Operation for PushNewTarget { .vm .environments .get_this_environment() - .as_function_slots() - .and_then(|env| env.borrow().new_target().cloned()) + .as_function() + .and_then(|env| env.slots().new_target().cloned()) { new_target.into() } else {