Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Separate declarative environment kinds #2921

Merged
merged 2 commits into from
May 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions boa_engine/src/builtins/eval/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
10 changes: 4 additions & 6 deletions boa_engine/src/builtins/function/arguments.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,8 @@ impl ParameterMap {
///
/// [spec]: https://tc39.es/ecma262/#sec-makearggetter
pub(crate) fn get(&self, index: usize) -> Option<JsValue> {
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.
Expand All @@ -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());
}
}
}
Expand Down
10 changes: 5 additions & 5 deletions boa_engine/src/builtins/function/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -160,7 +160,7 @@ pub(crate) enum FunctionKind {
code: Gc<CodeBlock>,

/// The `[[Environment]]` internal slot.
environments: DeclarativeEnvironmentStack,
environments: EnvironmentStack,

/// The `[[ConstructorKind]]` internal slot.
constructor_kind: ConstructorKind,
Expand All @@ -184,7 +184,7 @@ pub(crate) enum FunctionKind {
code: Gc<CodeBlock>,

/// The `[[Environment]]` internal slot.
environments: DeclarativeEnvironmentStack,
environments: EnvironmentStack,

/// The `[[HomeObject]]` internal slot.
home_object: Option<JsObject>,
Expand All @@ -199,7 +199,7 @@ pub(crate) enum FunctionKind {
code: Gc<CodeBlock>,

/// The `[[Environment]]` internal slot.
environments: DeclarativeEnvironmentStack,
environments: EnvironmentStack,

/// The `[[HomeObject]]` internal slot.
home_object: Option<JsObject>,
Expand All @@ -214,7 +214,7 @@ pub(crate) enum FunctionKind {
code: Gc<CodeBlock>,

/// The `[[Environment]]` internal slot.
environments: DeclarativeEnvironmentStack,
environments: EnvironmentStack,

/// The `[[HomeObject]]` internal slot.
home_object: Option<JsObject>,
Expand Down
6 changes: 3 additions & 3 deletions boa_engine/src/builtins/generator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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<JsValue>,
pub(crate) active_function: Option<JsObject>,
pub(crate) call_frame: Option<CallFrame>,
Expand All @@ -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<JsValue>,
active_function: Option<JsObject>,
call_frame: CallFrame,
Expand Down
4 changes: 2 additions & 2 deletions boa_engine/src/environments/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ mod runtime;
pub(crate) use {
compile::CompileTimeEnvironment,
runtime::{
BindingLocator, DeclarativeEnvironment, DeclarativeEnvironmentStack, Environment,
EnvironmentSlots,
BindingLocator, DeclarativeEnvironment, Environment, EnvironmentStack, FunctionSlots,
ThisBindingStatus,
},
};

Expand Down
208 changes: 208 additions & 0 deletions boa_engine/src/environments/runtime/declarative/function.rs
Original file line number Diff line number Diff line change
@@ -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<JsValue> {
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<Option<JsValue>> {
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<ThisBindingStatus>,

/// The `[[FunctionObject]]` internal slot.
function_object: JsObject,

/// The `[[NewTarget]]` internal slot.
new_target: Option<JsObject>,
}

impl FunctionSlots {
/// Creates a new `FunctionSluts`.
pub(crate) fn new(
this: ThisBindingStatus,
function_object: JsObject,
new_target: Option<JsObject>,
) -> 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()
}
}
58 changes: 58 additions & 0 deletions boa_engine/src/environments/runtime/declarative/global.rs
Original file line number Diff line number Diff line change
@@ -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<JsValue> {
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()
}
}
Loading