Skip to content

Commit

Permalink
Separate declarative environment kinds (#2921)
Browse files Browse the repository at this point in the history
* Separate declarative environment kinds

* Fix typos
  • Loading branch information
jedel1043 authored May 10, 2023
1 parent c827313 commit 71ea4d2
Show file tree
Hide file tree
Showing 16 changed files with 823 additions and 482 deletions.
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

0 comments on commit 71ea4d2

Please sign in to comment.