From e0f35de769bf6b6f66ea94dc72a488d9e5ea4b4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Juli=C3=A1n=20Espina?= Date: Fri, 2 Feb 2024 22:44:58 +0000 Subject: [PATCH] Remove double indirection in module types (#3640) * Remove double indirection in module types * Fix tests * cargo fmt --- core/engine/src/error.rs | 23 +- core/engine/src/module/mod.rs | 81 +-- core/engine/src/module/source.rs | 832 ++++++++++++++-------------- core/engine/src/module/synthetic.rs | 210 ++++--- core/gc/src/trace.rs | 12 +- 5 files changed, 599 insertions(+), 559 deletions(-) diff --git a/core/engine/src/error.rs b/core/engine/src/error.rs index fa5e2969d3..d56b20e68c 100644 --- a/core/engine/src/error.rs +++ b/core/engine/src/error.rs @@ -46,16 +46,12 @@ use thiserror::Error; /// let kind = &native_error.as_native().unwrap().kind; /// assert!(matches!(kind, JsNativeErrorKind::Type)); /// ``` -#[derive(Debug, Clone, Finalize, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Trace, Finalize)] +#[boa_gc(unsafe_no_drop)] pub struct JsError { inner: Repr, } -// SAFETY: just mirroring the default derive to allow destructuring. -unsafe impl Trace for JsError { - custom_trace!(this, mark, mark(&this.inner)); -} - /// Internal representation of a [`JsError`]. /// /// `JsError` is represented by an opaque enum because it restricts @@ -66,24 +62,13 @@ unsafe impl Trace for JsError { /// This should never be used outside of this module. If that's not the case, /// you should add methods to either `JsError` or `JsNativeError` to /// represent that special use case. -#[derive(Debug, Clone, Finalize, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Trace, Finalize)] +#[boa_gc(unsafe_no_drop)] enum Repr { Native(JsNativeError), Opaque(JsValue), } -// SAFETY: just mirroring the default derive to allow destructuring. -unsafe impl Trace for Repr { - custom_trace!( - this, - mark, - match &this { - Self::Native(err) => mark(err), - Self::Opaque(val) => mark(val), - } - ); -} - /// The error type returned by the [`JsError::try_native`] method. #[derive(Debug, Clone, Error)] pub enum TryNativeError { diff --git a/core/engine/src/module/mod.rs b/core/engine/src/module/mod.rs index bcf07e99f2..83f3e3b479 100644 --- a/core/engine/src/module/mod.rs +++ b/core/engine/src/module/mod.rs @@ -63,7 +63,6 @@ impl std::fmt::Debug for Module { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Module") .field("realm", &self.inner.realm.addr()) - .field("environment", &self.inner.environment) .field("namespace", &self.inner.namespace) .field("kind", &self.inner.kind) .finish() @@ -73,7 +72,6 @@ impl std::fmt::Debug for Module { #[derive(Trace, Finalize)] struct ModuleRepr { realm: Realm, - environment: GcRefCell>>, namespace: GcRefCell>, kind: ModuleKind, host_defined: HostDefined, @@ -88,6 +86,16 @@ pub(crate) enum ModuleKind { Synthetic(SyntheticModule), } +impl ModuleKind { + /// Returns the inner `SourceTextModule`. + pub(crate) fn as_source_text(&self) -> Option<&SourceTextModule> { + match self { + ModuleKind::SourceText(src) => Some(src), + ModuleKind::Synthetic(_) => None, + } + } +} + /// Return value of the [`Module::resolve_export`] operation. /// /// Indicates how to access a specific export in a module. @@ -126,7 +134,7 @@ struct GraphLoadingState { capability: PromiseCapability, loading: Cell, pending_modules: Cell, - visited: RefCell>, + visited: RefCell>, } #[derive(Debug, Clone, Copy)] @@ -151,19 +159,16 @@ impl Module { parser.set_identifier(context.next_parser_identifier()); let module = parser.parse_module(context.interner_mut())?; - let inner = Gc::new_cyclic(|weak| { - let src = SourceTextModule::new(module, weak.clone(), context.interner()); + let src = SourceTextModule::new(module, context.interner()); - ModuleRepr { + Ok(Self { + inner: Gc::new(ModuleRepr { realm: realm.unwrap_or_else(|| context.realm().clone()), - environment: GcRefCell::default(), namespace: GcRefCell::default(), kind: ModuleKind::SourceText(src), host_defined: HostDefined::default(), - } - }); - - Ok(Self { inner }) + }), + }) } /// Abstract operation [`CreateSyntheticModule ( exportNames, evaluationSteps, realm )`][spec]. @@ -181,19 +186,16 @@ impl Module { ) -> Self { let names = export_names.iter().cloned().collect(); let realm = realm.unwrap_or_else(|| context.realm().clone()); - let inner = Gc::new_cyclic(|weak| { - let synth = SyntheticModule::new(names, evaluation_steps, weak.clone()); + let synth = SyntheticModule::new(names, evaluation_steps); - ModuleRepr { + Self { + inner: Gc::new(ModuleRepr { realm, - environment: GcRefCell::default(), namespace: GcRefCell::default(), kind: ModuleKind::Synthetic(synth), host_defined: HostDefined::default(), - } - }); - - Self { inner } + }), + } } /// Gets the realm of this `Module`. @@ -217,9 +219,12 @@ impl Module { &self.inner.kind } - /// Gets the environment of this `Module`. + /// Gets the declarative environment of this `Module`. pub(crate) fn environment(&self) -> Option> { - self.inner.environment.borrow().clone() + match self.kind() { + ModuleKind::SourceText(src) => src.environment(), + ModuleKind::Synthetic(syn) => syn.environment(), + } } /// Abstract method [`LoadRequestedModules ( [ hostDefined ] )`][spec]. @@ -279,7 +284,7 @@ impl Module { if let ModuleKind::SourceText(src) = self.kind() { // continues on `inner_load - src.inner_load(state, context); + src.inner_load(self, state, context); if !state.loading.get() { return; } @@ -320,11 +325,11 @@ impl Module { /// [spec]: https://tc39.es/ecma262/#table-abstract-methods-of-module-records fn get_exported_names( &self, - export_star_set: &mut Vec, + export_star_set: &mut Vec, interner: &Interner, ) -> FxHashSet { match self.kind() { - ModuleKind::SourceText(src) => src.get_exported_names(export_star_set, interner), + ModuleKind::SourceText(src) => src.get_exported_names(self, export_star_set, interner), ModuleKind::Synthetic(synth) => synth.get_exported_names(), } } @@ -348,8 +353,10 @@ impl Module { interner: &Interner, ) -> Result { match self.kind() { - ModuleKind::SourceText(src) => src.resolve_export(&export_name, resolve_set, interner), - ModuleKind::Synthetic(synth) => synth.resolve_export(export_name), + ModuleKind::SourceText(src) => { + src.resolve_export(self, &export_name, resolve_set, interner) + } + ModuleKind::Synthetic(synth) => synth.resolve_export(self, export_name), } } @@ -367,9 +374,9 @@ impl Module { #[inline] pub fn link(&self, context: &mut Context) -> JsResult<()> { match self.kind() { - ModuleKind::SourceText(src) => src.link(context), + ModuleKind::SourceText(src) => src.link(self, context), ModuleKind::Synthetic(synth) => { - synth.link(context); + synth.link(self, context); Ok(()) } } @@ -380,16 +387,16 @@ impl Module { /// [spec]: https://tc39.es/ecma262/#sec-InnerModuleLinking fn inner_link( &self, - stack: &mut Vec, + stack: &mut Vec, index: usize, context: &mut Context, ) -> JsResult { match self.kind() { - ModuleKind::SourceText(src) => src.inner_link(stack, index, context), + ModuleKind::SourceText(src) => src.inner_link(self, stack, index, context), // If module is not a Cyclic Module Record, then ModuleKind::Synthetic(synth) => { // a. Perform ? module.Link(). - synth.link(context); + synth.link(self, context); // b. Return index. Ok(index) } @@ -411,8 +418,8 @@ impl Module { #[inline] pub fn evaluate(&self, context: &mut Context) -> JsPromise { match self.kind() { - ModuleKind::SourceText(src) => src.evaluate(context), - ModuleKind::Synthetic(synth) => synth.evaluate(context), + ModuleKind::SourceText(src) => src.evaluate(self, context), + ModuleKind::Synthetic(synth) => synth.evaluate(self, context), } } @@ -421,16 +428,16 @@ impl Module { /// [spec]: https://tc39.es/ecma262/#sec-InnerModuleLinking fn inner_evaluate( &self, - stack: &mut Vec, + stack: &mut Vec, index: usize, context: &mut Context, ) -> JsResult { match self.kind() { - ModuleKind::SourceText(src) => src.inner_evaluate(stack, index, None, context), + ModuleKind::SourceText(src) => src.inner_evaluate(self, stack, index, None, context), // 1. If module is not a Cyclic Module Record, then ModuleKind::Synthetic(synth) => { // a. Let promise be ! module.Evaluate(). - let promise: JsPromise = synth.evaluate(context); + let promise: JsPromise = synth.evaluate(self, context); let state = promise.state(); match state { PromiseState::Pending => { @@ -562,7 +569,7 @@ impl Module { impl PartialEq for Module { #[inline] fn eq(&self, other: &Self) -> bool { - std::ptr::eq(self.inner.as_ref(), other.inner.as_ref()) + Gc::ptr_eq(&self.inner, &other.inner) } } diff --git a/core/engine/src/module/source.rs b/core/engine/src/module/source.rs index c6545cd690..263962ffd7 100644 --- a/core/engine/src/module/source.rs +++ b/core/engine/src/module/source.rs @@ -1,9 +1,4 @@ -use std::{ - cell::Cell, - collections::HashSet, - hash::{BuildHasherDefault, Hash}, - rc::Rc, -}; +use std::{cell::Cell, collections::HashSet, hash::BuildHasherDefault, rc::Rc}; use boa_ast::{ declaration::{ @@ -15,7 +10,7 @@ use boa_ast::{ ContainsSymbol, LexicallyScopedDeclaration, }, }; -use boa_gc::{custom_trace, empty_trace, Finalize, Gc, GcRefCell, Trace, WeakGc}; +use boa_gc::{Finalize, Gc, GcRefCell, Trace}; use boa_interner::Interner; use boa_macros::utf16; use indexmap::IndexSet; @@ -24,10 +19,12 @@ use rustc_hash::{FxHashMap, FxHashSet, FxHasher}; use crate::{ builtins::{promise::PromiseCapability, Promise}, bytecompiler::{ByteCompiler, FunctionSpec, ToJsString}, - environments::{BindingLocator, CompileTimeEnvironment, EnvironmentStack}, + environments::{ + BindingLocator, CompileTimeEnvironment, DeclarativeEnvironment, EnvironmentStack, + }, js_string, module::ModuleKind, - object::{FunctionObjectBuilder, JsPromise, RecursionLimiter}, + object::{FunctionObjectBuilder, JsPromise}, realm::Realm, vm::{ create_function_object_fast, ActiveRunnable, CallFrame, CallFrameFlags, CodeBlock, @@ -37,111 +34,76 @@ use crate::{ }; use super::{ - BindingName, GraphLoadingState, Module, ModuleRepr, Referrer, ResolveExportError, - ResolvedBinding, + BindingName, GraphLoadingState, Module, Referrer, ResolveExportError, ResolvedBinding, }; /// Information for the [**Depth-first search**] algorithm used in the /// [`Module::link`] and [`Module::evaluate`] methods. -#[derive(Clone, Copy, Debug, Finalize)] +#[derive(Clone, Copy, Debug, Trace, Finalize)] +#[boa_gc(empty_trace)] pub(super) struct DfsInfo { dfs_index: usize, dfs_ancestor_index: usize, } -// SAFETY: `DfsInfo` only contains primitive types, making this safe. -unsafe impl Trace for DfsInfo { - empty_trace!(); -} - /// Current status of a [`SourceTextModule`]. /// /// Roughly corresponds to the `[[Status]]` field of [**Cyclic Module Records**][cyclic], /// but with a state machine-like design for better correctness. /// /// [cyclic]: https://tc39.es/ecma262/#table-cyclic-module-fields -#[derive(Debug, Finalize, Default)] -enum Status { +#[derive(Debug, Trace, Finalize, Default)] +#[boa_gc(unsafe_no_drop)] +enum ModuleStatus { #[default] Unlinked, Linking { info: DfsInfo, }, PreLinked { + environment: Gc, context: SourceTextContext, info: DfsInfo, }, Linked { + environment: Gc, context: SourceTextContext, info: DfsInfo, }, Evaluating { + environment: Gc, context: SourceTextContext, top_level_capability: Option, - cycle_root: SourceTextModule, + cycle_root: Module, info: DfsInfo, async_eval_index: Option, }, EvaluatingAsync { + environment: Gc, context: SourceTextContext, top_level_capability: Option, - cycle_root: SourceTextModule, + cycle_root: Module, async_eval_index: usize, pending_async_dependencies: usize, }, Evaluated { + environment: Gc, top_level_capability: Option, - cycle_root: SourceTextModule, + cycle_root: Module, error: Option, }, } -// SAFETY: This must be synced with `Status` to mark any new data added that needs to be traced. -// `Status` doesn't implement `Drop`, making this manual implementation safe. -// -// The `Trace` macro adds an empty `Drop` implementation to encourage using `Finalize` instead. -// However, this has the downside of disallowing destructuring, which is pretty -// useful to have for state machines like `Status`. This is solved by manually implementing -// `Trace`. -unsafe impl Trace for Status { - custom_trace!(this, mark, { - match this { - Self::Unlinked | Self::Linking { info: _ } => {} - Self::PreLinked { context, info: _ } | Self::Linked { context, info: _ } => { - mark(context); - } - Self::Evaluating { - top_level_capability, - cycle_root, - context, - info: _, - async_eval_index: _, - } - | Self::EvaluatingAsync { - top_level_capability, - cycle_root, - context, - pending_async_dependencies: _, - async_eval_index: _, - } => { - mark(top_level_capability); - mark(cycle_root); - mark(context); - } - Self::Evaluated { - top_level_capability, - cycle_root, - error, - } => { - mark(top_level_capability); - mark(cycle_root); - mark(error); - } - } - }); -} +impl ModuleStatus { + /// Transition from one state to another, taking the current state by value to move data + /// between states. + fn transition(&mut self, f: F) + where + F: FnOnce(Self) -> Self, + { + *self = f(std::mem::take(self)); + } -impl Status { /// Gets the current index info of the module within the dependency graph, or `None` if the /// module is not in a state executing the dfs algorithm. const fn dfs_info(&self) -> Option<&DfsInfo> { @@ -198,7 +160,7 @@ impl Status { } /// If this module is in the evaluating state, gets its cycle root. - const fn cycle_root(&self) -> Option<&SourceTextModule> { + const fn cycle_root(&self) -> Option<&Module> { match &self { Self::Evaluating { cycle_root, .. } | Self::EvaluatingAsync { cycle_root, .. } @@ -207,13 +169,16 @@ impl Status { } } - /// Transition from one state to another, taking the current state by value to move data - /// between states. - fn transition(&mut self, f: F) - where - F: FnOnce(Self) -> Self, - { - *self = f(std::mem::take(self)); + /// Gets the declarative environment from the module status. + fn environment(&self) -> Option> { + match self { + ModuleStatus::Unlinked | ModuleStatus::Linking { .. } => None, + ModuleStatus::PreLinked { environment, .. } + | ModuleStatus::Linked { environment, .. } + | ModuleStatus::Evaluating { environment, .. } + | ModuleStatus::EvaluatingAsync { environment, .. } + | ModuleStatus::Evaluated { environment, .. } => Some(environment.clone()), + } } } @@ -221,7 +186,8 @@ impl Status { /// /// Stores the required context data that needs to be in place before executing the /// inner code of the module. -#[derive(Clone, Finalize)] +#[derive(Clone, Trace, Finalize)] +#[boa_gc(unsafe_no_drop)] struct SourceTextContext { codeblock: Gc, environments: EnvironmentStack, @@ -238,50 +204,30 @@ impl std::fmt::Debug for SourceTextContext { } } -unsafe impl Trace for SourceTextContext { - custom_trace!(this, mark, { - mark(&this.codeblock); - mark(&this.environments); - mark(&this.realm); - }); -} - /// ECMAScript's [**Source Text Module Records**][spec]. /// /// [spec]: https://tc39.es/ecma262/#sec-source-text-module-records -#[derive(Clone, Trace, Finalize)] +#[derive(Trace, Finalize)] pub(crate) struct SourceTextModule { - inner: Gc, + status: GcRefCell, + loaded_modules: GcRefCell>, + async_parent_modules: GcRefCell>, + import_meta: GcRefCell>, + #[unsafe_ignore_trace] + code: ModuleCode, } impl std::fmt::Debug for SourceTextModule { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let limiter = RecursionLimiter::new(&*self.inner); - - if !limiter.visited && !limiter.live { - f.debug_struct("SourceTextModule") - .field("status", &self.inner.status) - .field("loaded_modules", &self.inner.loaded_modules) - .field("async_parent_modules", &self.inner.async_parent_modules) - .field("import_meta", &self.inner.import_meta) - .finish_non_exhaustive() - } else { - f.write_str("{ ... }") - } + f.debug_struct("SourceTextModule") + .field("status", &self.status) + .field("loaded_modules", &self.loaded_modules) + .field("async_parent_modules", &self.async_parent_modules) + .field("import_meta", &self.import_meta) + .finish_non_exhaustive() } } -#[derive(Trace, Finalize)] -struct Inner { - parent: WeakGc, - status: GcRefCell, - loaded_modules: GcRefCell>, - async_parent_modules: GcRefCell>, - import_meta: GcRefCell>, - #[unsafe_ignore_trace] - code: ModuleCode, -} - #[derive(Debug)] struct ModuleCode { has_tla: bool, @@ -294,27 +240,12 @@ struct ModuleCode { } impl SourceTextModule { - /// Gets the parent module of this source module. - fn parent(&self) -> Module { - Module { - inner: self - .inner - .parent - .upgrade() - .expect("parent module must be live"), - } - } - /// Creates a new `SourceTextModule` from a parsed `ModuleSource`. /// /// Contains part of the abstract operation [`ParseModule`][parse]. /// /// [parse]: https://tc39.es/ecma262/#sec-parsemodule - pub(super) fn new( - code: boa_ast::Module, - parent: WeakGc, - interner: &Interner, - ) -> Self { + pub(super) fn new(code: boa_ast::Module, interner: &Interner) -> Self { // 3. Let requestedModules be the ModuleRequests of body. let requested_modules = code .items() @@ -399,37 +330,39 @@ impl SourceTextModule { // }. // Most of this can be ignored, since `Status` takes care of the remaining state. Self { - inner: Gc::new(Inner { - parent, - status: GcRefCell::default(), - loaded_modules: GcRefCell::default(), - async_parent_modules: GcRefCell::default(), - import_meta: GcRefCell::default(), - code: ModuleCode { - source: code, - requested_modules, - has_tla, - import_entries, - local_export_entries, - indirect_export_entries, - star_export_entries, - }, - }), + status: GcRefCell::default(), + loaded_modules: GcRefCell::default(), + async_parent_modules: GcRefCell::default(), + import_meta: GcRefCell::default(), + code: ModuleCode { + source: code, + requested_modules, + has_tla, + import_entries, + local_export_entries, + indirect_export_entries, + star_export_entries, + }, } } /// Abstract operation [`InnerModuleLoading`][spec]. /// /// [spec]: https://tc39.es/ecma262/#sec-InnerModuleLoading - pub(super) fn inner_load(&self, state: &Rc, context: &mut Context) { + pub(super) fn inner_load( + &self, + module_self: &Module, + state: &Rc, + context: &mut Context, + ) { // 2. If module is a Cyclic Module Record, module.[[Status]] is new, and state.[[Visited]] does not contain // module, then // a. Append module to state.[[Visited]]. - if matches!(&*self.inner.status.borrow(), Status::Unlinked) - && state.visited.borrow_mut().insert(self.clone()) + if matches!(&*self.status.borrow(), ModuleStatus::Unlinked) + && state.visited.borrow_mut().insert(module_self.clone()) { // b. Let requestedModulesCount be the number of elements in module.[[RequestedModules]]. - let requested = &self.inner.code.requested_modules; + let requested = &self.code.requested_modules; // c. Set state.[[PendingModulesCount]] to state.[[PendingModulesCount]] + requestedModulesCount. state .pending_modules @@ -437,7 +370,7 @@ impl SourceTextModule { // d. For each String required of module.[[RequestedModules]], do for required in requested.iter().cloned() { // i. If module.[[LoadedModules]] contains a Record whose [[Specifier]] is required, then - let loaded = self.inner.loaded_modules.borrow().get(&required).cloned(); + let loaded = self.loaded_modules.borrow().get(&required).cloned(); if let Some(loaded) = loaded { // 1. Let record be that Record. // 2. Perform InnerModuleLoading(state, record.[[Module]]). @@ -448,10 +381,10 @@ impl SourceTextModule { // 2. NOTE: HostLoadImportedModule will call FinishLoadingImportedModule, which re-enters // the graph loading process through ContinueModuleLoading. let name_specifier = required.clone(); - let src = self.clone(); + let src = module_self.clone(); let state = state.clone(); context.module_loader().load_imported_module( - Referrer::Module(self.parent()), + Referrer::Module(module_self.clone()), name_specifier, Box::new(move |completion, context| { // FinishLoadingImportedModule ( referrer, specifier, payload, result ) @@ -459,16 +392,19 @@ impl SourceTextModule { // 1. If result is a normal completion, then if let Ok(loaded) = &completion { + let ModuleKind::SourceText(src) = src.kind() else { + unreachable!("captured src must be a source text module"); + }; // a. If referrer.[[LoadedModules]] contains a Record whose [[Specifier]] is specifier, then // b. Else, // i. Append the Record { [[Specifier]]: specifier, [[Module]]: result.[[Value]] } to referrer.[[LoadedModules]]. - let mut loaded_modules = src.inner.loaded_modules.borrow_mut(); + let mut loaded_modules = src.loaded_modules.borrow_mut(); let entry = loaded_modules .entry(required) .or_insert_with(|| loaded.clone()); // i. Assert: That Record's [[Module]] is result.[[Value]]. - debug_assert_eq!(entry, loaded); + assert_eq!(entry, loaded); } // 2. If payload is a GraphLoadingState Record, then @@ -525,27 +461,28 @@ impl SourceTextModule { /// [spec]: https://tc39.es/ecma262/#sec-getexportednames pub(super) fn get_exported_names( &self, - export_star_set: &mut Vec, + module_self: &Module, + export_star_set: &mut Vec, interner: &Interner, ) -> FxHashSet { // 1. Assert: module.[[Status]] is not new. // 2. If exportStarSet is not present, set exportStarSet to a new empty List. // 3. If exportStarSet contains module, then - if export_star_set.contains(self) { + if export_star_set.contains(module_self) { // a. Assert: We've reached the starting point of an export * circularity. // b. Return a new empty List. return FxHashSet::default(); } // 4. Append module to exportStarSet. - export_star_set.push(self.clone()); + export_star_set.push(module_self.clone()); // 5. Let exportedNames be a new empty List. let mut exported_names = FxHashSet::default(); // 6. For each ExportEntry Record e of module.[[LocalExportEntries]], do - for e in &self.inner.code.local_export_entries { + for e in &self.code.local_export_entries { // a. Assert: module provides the direct binding for this export. // b. Append e.[[ExportName]] to exportedNames. let name = e.export_name().to_js_string(interner); @@ -553,7 +490,7 @@ impl SourceTextModule { } // 7. For each ExportEntry Record e of module.[[IndirectExportEntries]], do - for e in &self.inner.code.indirect_export_entries { + for e in &self.code.indirect_export_entries { // a. Assert: module imports a specific binding for this export. // b. Append e.[[ExportName]] to exportedNames. let name = e.export_name().to_js_string(interner); @@ -561,9 +498,9 @@ impl SourceTextModule { } // 8. For each ExportEntry Record e of module.[[StarExportEntries]], do - for e in &self.inner.code.star_export_entries { + for e in &self.code.star_export_entries { // a. Let requestedModule be GetImportedModule(module, e.[[ModuleRequest]]). - let requested_module = self.inner.loaded_modules.borrow()[e].clone(); + let requested_module = self.loaded_modules.borrow()[e].clone(); // b. Let starNames be requestedModule.GetExportedNames(exportStarSet). // c. For each element n of starNames, do @@ -587,16 +524,16 @@ impl SourceTextModule { #[allow(clippy::mutable_key_type)] pub(super) fn resolve_export( &self, + module_self: &Module, export_name: &JsString, resolve_set: &mut FxHashSet<(Module, JsString)>, interner: &Interner, ) -> Result { - let parent = self.parent(); // 1. Assert: module.[[Status]] is not new. // 2. If resolveSet is not present, set resolveSet to a new empty List. // 3. For each Record { [[Module]], [[ExportName]] } r of resolveSet, do // a. If module and r.[[Module]] are the same Module Record and SameValue(exportName, r.[[ExportName]]) is true, then - let value = (parent.clone(), export_name.clone()); + let value = (module_self.clone(), export_name.clone()); if resolve_set.contains(&value) { // i. Assert: This is a circular import request. // ii. Return null. @@ -607,25 +544,25 @@ impl SourceTextModule { resolve_set.insert(value); // 5. For each ExportEntry Record e of module.[[LocalExportEntries]], do - for e in &self.inner.code.local_export_entries { + for e in &self.code.local_export_entries { // a. If SameValue(exportName, e.[[ExportName]]) is true, then if export_name == &e.export_name().to_js_string(interner) { // i. Assert: module provides the direct binding for this export. // ii. Return ResolvedBinding Record { [[Module]]: module, [[BindingName]]: e.[[LocalName]] }. return Ok(ResolvedBinding { - module: parent, + module: module_self.clone(), binding_name: BindingName::Name(e.local_name().to_js_string(interner)), }); } } // 6. For each ExportEntry Record e of module.[[IndirectExportEntries]], do - for e in &self.inner.code.indirect_export_entries { + for e in &self.code.indirect_export_entries { // a. If SameValue(exportName, e.[[ExportName]]) is true, then if export_name == &e.export_name().to_js_string(interner) { // i. Let importedModule be GetImportedModule(module, e.[[ModuleRequest]]). let module_request = e.module_request().to_js_string(interner); - let imported_module = self.inner.loaded_modules.borrow()[&module_request].clone(); + let imported_module = self.loaded_modules.borrow()[&module_request].clone(); return match e.import_name() { // ii. If e.[[ImportName]] is all, then // 1. Assert: module does not provide the direct binding for this export. @@ -657,9 +594,9 @@ impl SourceTextModule { let mut star_resolution: Option = None; // 9. For each ExportEntry Record e of module.[[StarExportEntries]], do - for e in &self.inner.code.star_export_entries { + for e in &self.code.star_export_entries { // a. Let importedModule be GetImportedModule(module, e.[[ModuleRequest]]). - let imported_module = self.inner.loaded_modules.borrow()[e].clone(); + let imported_module = self.loaded_modules.borrow()[e].clone(); // b. Let resolution be importedModule.ResolveExport(exportName, resolveSet). let resolution = match imported_module.resolve_export(export_name.clone(), resolve_set, interner) { @@ -712,14 +649,14 @@ impl SourceTextModule { /// Concrete method [`Link ( )`][spec]. /// /// [spec]: https://tc39.es/ecma262/#sec-moduledeclarationlinking - pub(super) fn link(&self, context: &mut Context) -> JsResult<()> { + pub(super) fn link(&self, module_self: &Module, context: &mut Context) -> JsResult<()> { // 1. Assert: module.[[Status]] is one of unlinked, linked, evaluating-async, or evaluated. debug_assert!(matches!( - &*self.inner.status.borrow(), - Status::Unlinked - | Status::Linked { .. } - | Status::EvaluatingAsync { .. } - | Status::Evaluated { .. } + &*self.status.borrow(), + ModuleStatus::Unlinked + | ModuleStatus::Linked { .. } + | ModuleStatus::EvaluatingAsync { .. } + | ModuleStatus::Evaluated { .. } )); // 2. Let stack be a new empty List. @@ -727,24 +664,26 @@ impl SourceTextModule { // 3. Let result be Completion(InnerModuleLinking(module, stack, 0)). // 4. If result is an abrupt completion, then - if let Err(err) = self.inner_link(&mut stack, 0, context) { + if let Err(err) = self.inner_link(module_self, &mut stack, 0, context) { // a. For each Cyclic Module Record m of stack, do - for m in stack { + for m in stack.iter().filter_map(|cmr| cmr.kind().as_source_text()) { // i. Assert: m.[[Status]] is linking. - debug_assert!(matches!(&*m.inner.status.borrow(), Status::Linking { .. })); + debug_assert!(matches!(&*m.status.borrow(), ModuleStatus::Linking { .. })); // ii. Set m.[[Status]] to unlinked. - *m.inner.status.borrow_mut() = Status::Unlinked; + *m.status.borrow_mut() = ModuleStatus::Unlinked; } // b. Assert: module.[[Status]] is unlinked. - assert!(matches!(&*self.inner.status.borrow(), Status::Unlinked)); + debug_assert!(matches!(&*self.status.borrow(), ModuleStatus::Unlinked)); // c. Return ? result. return Err(err); } // 5. Assert: module.[[Status]] is one of linked, evaluating-async, or evaluated. debug_assert!(matches!( - &*self.inner.status.borrow(), - Status::Linked { .. } | Status::EvaluatingAsync { .. } | Status::Evaluated { .. } + &*self.status.borrow(), + ModuleStatus::Linked { .. } + | ModuleStatus::EvaluatingAsync { .. } + | ModuleStatus::Evaluated { .. } )); // 6. Assert: stack is empty. assert!(stack.is_empty()); @@ -758,30 +697,31 @@ impl SourceTextModule { /// [spec]: https://tc39.es/ecma262/#sec-InnerModuleLinking pub(super) fn inner_link( &self, - stack: &mut Vec, + module_self: &Module, + stack: &mut Vec, mut index: usize, context: &mut Context, ) -> JsResult { // 2. If module.[[Status]] is one of linking, linked, evaluating-async, or evaluated, then if matches!( - &*self.inner.status.borrow(), - Status::Linking { .. } - | Status::PreLinked { .. } - | Status::Linked { .. } - | Status::EvaluatingAsync { .. } - | Status::Evaluated { .. } + &*self.status.borrow(), + ModuleStatus::Linking { .. } + | ModuleStatus::PreLinked { .. } + | ModuleStatus::Linked { .. } + | ModuleStatus::EvaluatingAsync { .. } + | ModuleStatus::Evaluated { .. } ) { // a. Return index. return Ok(index); } // 3. Assert: module.[[Status]] is unlinked. - debug_assert!(matches!(&*self.inner.status.borrow(), Status::Unlinked)); + debug_assert!(matches!(&*self.status.borrow(), ModuleStatus::Unlinked)); // 4. Set module.[[Status]] to linking. // 5. Set module.[[DFSIndex]] to index. // 6. Set module.[[DFSAncestorIndex]] to index. - *self.inner.status.borrow_mut() = Status::Linking { + *self.status.borrow_mut() = ModuleStatus::Linking { info: DfsInfo { dfs_index: index, dfs_ancestor_index: index, @@ -792,36 +732,36 @@ impl SourceTextModule { index += 1; // 8. Append module to stack. - stack.push(self.clone()); + stack.push(module_self.clone()); // 9. For each String required of module.[[RequestedModules]], do - for required in &self.inner.code.requested_modules { + for required in &self.code.requested_modules { // a. Let requiredModule be GetImportedModule(module, required). - let required_module = self.inner.loaded_modules.borrow()[required].clone(); + let required_module = self.loaded_modules.borrow()[required].clone(); // b. Set index to ? InnerModuleLinking(requiredModule, stack, index). index = required_module.inner_link(stack, index, context)?; // c. If requiredModule is a Cyclic Module Record, then - if let ModuleKind::SourceText(required_module) = required_module.kind() { + if let ModuleKind::SourceText(required_module_src) = required_module.kind() { // i. Assert: requiredModule.[[Status]] is one of linking, linked, evaluating-async, or evaluated. // ii. Assert: requiredModule.[[Status]] is linking if and only if stack contains requiredModule. - debug_assert!(match &*required_module.inner.status.borrow() { - Status::PreLinked { .. } - | Status::Linked { .. } - | Status::EvaluatingAsync { .. } - | Status::Evaluated { .. } => true, - Status::Linking { .. } if stack.contains(required_module) => true, + debug_assert!(match &*required_module_src.status.borrow() { + ModuleStatus::PreLinked { .. } + | ModuleStatus::Linked { .. } + | ModuleStatus::EvaluatingAsync { .. } + | ModuleStatus::Evaluated { .. } => true, + ModuleStatus::Linking { .. } if stack.contains(&required_module) => true, _ => false, }); // iii. If requiredModule.[[Status]] is linking, then - let required_index = if let Status::Linking { + let required_index = if let ModuleStatus::Linking { info: DfsInfo { dfs_ancestor_index, .. }, - } = &*required_module.inner.status.borrow() + } = &*required_module_src.status.borrow() { // 1. Set module.[[DFSAncestorIndex]] to min(module.[[DFSAncestorIndex]], // requiredModule.[[DFSAncestorIndex]]). @@ -832,7 +772,7 @@ impl SourceTextModule { }; if let Some(required_index) = required_index { - let mut status = self.inner.status.borrow_mut(); + let mut status = self.status.borrow_mut(); let DfsInfo { dfs_ancestor_index, .. @@ -845,17 +785,19 @@ impl SourceTextModule { } // 10. Perform ? module.InitializeEnvironment(). - self.initialize_environment(context)?; + self.initialize_environment(module_self, context)?; // 11. Assert: module occurs exactly once in stack. - debug_assert_eq!(stack.iter().filter(|module| *module == self).count(), 1); + debug_assert_eq!( + stack.iter().filter(|module| *module == module_self).count(), + 1 + ); // 12. Assert: module.[[DFSAncestorIndex]] ≤ module.[[DFSIndex]]. debug_assert!({ let DfsInfo { dfs_ancestor_index, dfs_index, } = self - .inner .status .borrow() .dfs_info() @@ -864,7 +806,7 @@ impl SourceTextModule { dfs_ancestor_index <= dfs_index }); - let info = self.inner.status.borrow().dfs_info().copied(); + let info = self.status.borrow().dfs_info().copied(); match info { // 13. If module.[[DFSAncestorIndex]] = module.[[DFSIndex]], then @@ -873,14 +815,25 @@ impl SourceTextModule { Some(info) if info.dfs_ancestor_index == info.dfs_index => loop { // i. Let requiredModule be the last element of stack. // ii. Remove the last element of stack. - // iii. Assert: requiredModule is a Cyclic Module Record. let last = stack.pop().expect("should have at least one element"); + let ModuleKind::SourceText(last_src) = last.kind() else { + unreachable!("iii. Assert: requiredModule is a Cyclic Module Record.") + }; + // iv. Set requiredModule.[[Status]] to linked. - last.inner + last_src .status .borrow_mut() .transition(|current| match current { - Status::PreLinked { info, context } => Status::Linked { info, context }, + ModuleStatus::PreLinked { + info, + context, + environment, + } => ModuleStatus::Linked { + info, + context, + environment, + }, _ => { unreachable!( "can only transition to `Linked` from the `PreLinked` state" @@ -889,7 +842,7 @@ impl SourceTextModule { }); // v. If requiredModule and module are the same Module Record, set done to true. - if &last == self { + if &last == module_self { break; } }, @@ -903,24 +856,24 @@ impl SourceTextModule { /// Concrete method [`Evaluate ( )`][spec]. /// /// [spec]: https://tc39.es/ecma262/#sec-moduleevaluation - pub(super) fn evaluate(&self, context: &mut Context) -> JsPromise { + pub(super) fn evaluate(&self, module_self: &Module, context: &mut Context) -> JsPromise { // 1. Assert: This call to Evaluate is not happening at the same time as another call to Evaluate within the surrounding agent. let (module, promise) = { - match &*self.inner.status.borrow() { - Status::Unlinked - | Status::Linking { .. } - | Status::PreLinked { .. } - | Status::Evaluating { .. } => { + match &*self.status.borrow() { + ModuleStatus::Unlinked + | ModuleStatus::Linking { .. } + | ModuleStatus::PreLinked { .. } + | ModuleStatus::Evaluating { .. } => { unreachable!("2. Assert: module.[[Status]] is one of linked, evaluating-async, or evaluated.") } - Status::Linked { .. } => (self.clone(), None), + ModuleStatus::Linked { .. } => (module_self.clone(), None), // 3. If module.[[Status]] is either evaluating-async or evaluated, set module to module.[[CycleRoot]]. - Status::EvaluatingAsync { + ModuleStatus::EvaluatingAsync { cycle_root, top_level_capability, .. } - | Status::Evaluated { + | ModuleStatus::Evaluated { cycle_root, top_level_capability, .. @@ -952,21 +905,25 @@ impl SourceTextModule { .expect("capability creation must always succeed when using the `%Promise%` intrinsic"); // 8. Let result be Completion(InnerModuleEvaluation(module, stack, 0)). - let result = module.inner_evaluate(&mut stack, 0, Some(capability.clone()), context); + let ModuleKind::SourceText(module_src) = module.kind() else { + unreachable!("module must be a source text module"); + }; + let result = + module_src.inner_evaluate(&module, &mut stack, 0, Some(capability.clone()), context); match result { Ok(_) => { // 10. Else, // a. Assert: module.[[Status]] is either evaluating-async or evaluated. - assert!(match &*module.inner.status.borrow() { - Status::EvaluatingAsync { .. } => true, + assert!(match &*module_src.status.borrow() { + ModuleStatus::EvaluatingAsync { .. } => true, // b. Assert: module.[[EvaluationError]] is empty. - Status::Evaluated { error, .. } if error.is_none() => true, + ModuleStatus::Evaluated { error, .. } if error.is_none() => true, _ => false, }); // c. If module.[[AsyncEvaluation]] is false, then - if matches!(&*module.inner.status.borrow(), Status::Evaluated { .. }) { + if matches!(&*module_src.status.borrow(), ModuleStatus::Evaluated { .. }) { // i. Assert: module.[[Status]] is evaluated. // ii. Perform ! Call(capability.[[Resolve]], undefined, « undefined »). capability @@ -981,21 +938,24 @@ impl SourceTextModule { // 9. If result is an abrupt completion, then Err(err) => { // a. For each Cyclic Module Record m of stack, do - for m in stack { - m.inner.status.borrow_mut().transition(|current| match current { + for m in stack.iter().filter_map(|cmr| cmr.kind().as_source_text()) { + m.status.borrow_mut().transition(|current| match current { // i. Assert: m.[[Status]] is evaluating. - Status::Evaluating { + ModuleStatus::Evaluating { + environment, top_level_capability, cycle_root, .. - } | Status::EvaluatingAsync { + } | ModuleStatus::EvaluatingAsync { + environment, top_level_capability, cycle_root, .. } => { // ii. Set m.[[Status]] to evaluated. // iii. Set m.[[EvaluationError]] to result. - Status::Evaluated { + ModuleStatus::Evaluated { + environment, top_level_capability, cycle_root, error: Some(err.clone()), @@ -1009,7 +969,7 @@ impl SourceTextModule { // b. Assert: module.[[Status]] is evaluated. // c. Assert: module.[[EvaluationError]] is result. assert!( - matches!(&*module.inner.status.borrow(), Status::Evaluated { error, .. } if error.is_some()) + matches!(&*module_src.status.borrow(), ModuleStatus::Evaluated { error, .. } if error.is_some()) ); // d. Perform ! Call(capability.[[Reject]], undefined, « result.[[Value]] »). @@ -1030,7 +990,8 @@ impl SourceTextModule { /// [spec]: https://tc39.es/ecma262/#sec-innermoduleevaluation pub(super) fn inner_evaluate( &self, - stack: &mut Vec, + module_self: &Module, + stack: &mut Vec, mut index: usize, capability: Option, context: &mut Context, @@ -1056,13 +1017,15 @@ impl SourceTextModule { } // 2. If module.[[Status]] is either evaluating-async or evaluated, then - match &*self.inner.status.borrow() { + match &*self.status.borrow() { // 3. If module.[[Status]] is evaluating, return index. - Status::Evaluating { .. } | Status::EvaluatingAsync { .. } => return Ok(index), + ModuleStatus::Evaluating { .. } | ModuleStatus::EvaluatingAsync { .. } => { + return Ok(index) + } // a. If module.[[EvaluationError]] is empty, return index. // b. Otherwise, return ? module.[[EvaluationError]]. - Status::Evaluated { error, .. } => return error.clone().map_or(Ok(index), Err), - Status::Linked { .. } => { + ModuleStatus::Evaluated { error, .. } => return error.clone().map_or(Ok(index), Err), + ModuleStatus::Linked { .. } => { // 4. Assert: module.[[Status]] is linked. // evaluate a linked module } @@ -1071,55 +1034,56 @@ impl SourceTextModule { ), } - let this = self.clone(); // 5. Set module.[[Status]] to evaluating. // 6. Set module.[[DFSIndex]] to index. // 7. Set module.[[DFSAncestorIndex]] to index. // 8. Set module.[[PendingAsyncDependencies]] to 0. - self.inner - .status - .borrow_mut() - .transition(|status| match status { - Status::Linked { context, .. } => Status::Evaluating { - context, - top_level_capability: capability, - cycle_root: this, - info: DfsInfo { - dfs_index: index, - dfs_ancestor_index: index, - }, - async_eval_index: None, + self.status.borrow_mut().transition(|status| match status { + ModuleStatus::Linked { + environment, + context, + .. + } => ModuleStatus::Evaluating { + environment, + context, + top_level_capability: capability, + cycle_root: module_self.clone(), + info: DfsInfo { + dfs_index: index, + dfs_ancestor_index: index, }, - _ => unreachable!("already asserted that this state is `Linked`. "), - }); + async_eval_index: None, + }, + _ => unreachable!("already asserted that this state is `Linked`. "), + }); // 9. Set index to index + 1. index += 1; let mut pending_async_dependencies = 0; // 10. Append module to stack. - stack.push(self.clone()); + stack.push(module_self.clone()); // 11. For each String required of module.[[RequestedModules]], do - for required in &self.inner.code.requested_modules { + for required in &self.code.requested_modules { // a. Let requiredModule be GetImportedModule(module, required). - let required_module = self.inner.loaded_modules.borrow()[required].clone(); + let required_module = self.loaded_modules.borrow()[required].clone(); // b. Set index to ? InnerModuleEvaluation(requiredModule, stack, index). index = required_module.inner_evaluate(stack, index, context)?; // c. If requiredModule is a Cyclic Module Record, then - if let ModuleKind::SourceText(required_module) = required_module.kind() { + if let ModuleKind::SourceText(required_module_src) = required_module.kind() { // i. Assert: requiredModule.[[Status]] is one of evaluating, evaluating-async, or evaluated. // ii. Assert: requiredModule.[[Status]] is evaluating if and only if stack contains requiredModule. - debug_assert!(match &*required_module.inner.status.borrow() { - Status::EvaluatingAsync { .. } | Status::Evaluated { .. } => true, - Status::Evaluating { .. } if stack.contains(required_module) => true, + debug_assert!(match &*required_module_src.status.borrow() { + ModuleStatus::EvaluatingAsync { .. } | ModuleStatus::Evaluated { .. } => true, + ModuleStatus::Evaluating { .. } if stack.contains(&required_module) => true, _ => false, }); - let (required_module, async_eval, req_info) = match &*required_module.inner.status.borrow() { + let (required_module, async_eval, req_info) = match &*required_module_src.status.borrow() { // iii. If requiredModule.[[Status]] is evaluating, then - Status::Evaluating { + ModuleStatus::Evaluating { info, async_eval_index, .. @@ -1128,23 +1092,31 @@ impl SourceTextModule { (required_module.clone(), async_eval_index.is_some(), Some(*info)) } // iv. Else, - Status::EvaluatingAsync { cycle_root, .. } - | Status::Evaluated { cycle_root, .. } => { + ModuleStatus::EvaluatingAsync { cycle_root, .. } + | ModuleStatus::Evaluated { cycle_root, .. } => { // 1. Set requiredModule to requiredModule.[[CycleRoot]]. + let ModuleKind::SourceText(cycle_root_src) = cycle_root.kind() else { + unreachable!("cycle_root must be a source text module"); + }; + // 2. Assert: requiredModule.[[Status]] is either evaluating-async or evaluated. - match &*cycle_root.inner.status.borrow() { - Status::EvaluatingAsync { .. } => (cycle_root.clone(), true, None), + match &*cycle_root_src.status.borrow() { + ModuleStatus::EvaluatingAsync { .. } => (cycle_root.clone(), true, None), // 3. If requiredModule.[[EvaluationError]] is not empty, return ? requiredModule.[[EvaluationError]]. - Status::Evaluated { error: Some(error), .. } => return Err(error.clone()), - Status::Evaluated { .. } => (cycle_root.clone(), false, None), + ModuleStatus::Evaluated { error: Some(error), .. } => return Err(error.clone()), + ModuleStatus::Evaluated { .. } => (cycle_root.clone(), false, None), _ => unreachable!("2. Assert: requiredModule.[[Status]] is either evaluating-async or evaluated."), } } _ => unreachable!("i. Assert: requiredModule.[[Status]] is one of evaluating, evaluating-async, or evaluated."), }; + let ModuleKind::SourceText(required_module) = required_module.kind() else { + unreachable!("required_module must be a source text module"); + }; + if let Some(req_info) = req_info { - let mut status = self.inner.status.borrow_mut(); + let mut status = self.status.borrow_mut(); let info = status .dfs_info_mut() .expect("self should still be in the evaluating state"); @@ -1158,21 +1130,20 @@ impl SourceTextModule { pending_async_dependencies += 1; // 2. Append module to requiredModule.[[AsyncParentModules]]. required_module - .inner .async_parent_modules .borrow_mut() - .push(self.clone()); + .push(module_self.clone()); } } } // 12. If module.[[PendingAsyncDependencies]] > 0 or module.[[HasTLA]] is true, then - if pending_async_dependencies > 0 || self.inner.code.has_tla { + if pending_async_dependencies > 0 || self.code.has_tla { // a. Assert: module.[[AsyncEvaluation]] is false and was never previously set to true. { - let Status::Evaluating { + let ModuleStatus::Evaluating { async_eval_index, .. - } = &mut *self.inner.status.borrow_mut() + } = &mut *self.status.borrow_mut() else { unreachable!("self should still be in the evaluating state") }; @@ -1184,20 +1155,20 @@ impl SourceTextModule { // d. If module.[[PendingAsyncDependencies]] = 0, perform ExecuteAsyncModule(module). if pending_async_dependencies == 0 { - self.execute_async(context); + self.execute_async(module_self, context); } } else { // 13. Else, // a. Perform ? module.ExecuteModule(). - self.execute(None, context)?; + self.execute(module_self, None, context)?; } - let dfs_info = self.inner.status.borrow().dfs_info().copied().expect( + let dfs_info = self.status.borrow().dfs_info().copied().expect( "haven't transitioned from the `Evaluating` state, so it should have its dfs info", ); // 14. Assert: module occurs exactly once in stack. - debug_assert_eq!(stack.iter().filter(|m| *m == self).count(), 1); + debug_assert_eq!(stack.iter().filter(|m| *m == module_self).count(), 1); // 15. Assert: module.[[DFSAncestorIndex]] ≤ module.[[DFSIndex]]. assert!(dfs_info.dfs_ancestor_index <= dfs_info.dfs_index); @@ -1211,11 +1182,14 @@ impl SourceTextModule { let required_module = stack .pop() .expect("should at least have `self` in the stack"); - let is_self = self == &required_module; + let is_self = module_self == &required_module; - // iii. Assert: requiredModule is a Cyclic Module Record. - required_module.inner.status.borrow_mut().transition(|current| match current { - Status::Evaluating { + let ModuleKind::SourceText(required_module_src) = required_module.kind() else { + unreachable!("iii. Assert: requiredModule is a Cyclic Module Record."); + }; + required_module_src.status.borrow_mut().transition(|current| match current { + ModuleStatus::Evaluating { + environment, top_level_capability, cycle_root, async_eval_index, @@ -1223,13 +1197,14 @@ impl SourceTextModule { .. } => if let Some(async_eval_index) = async_eval_index { // v. Otherwise, set requiredModule.[[Status]] to evaluating-async. - Status::EvaluatingAsync { + ModuleStatus::EvaluatingAsync { + environment, top_level_capability, // vii. Set requiredModule.[[CycleRoot]] to module. cycle_root: if is_self { cycle_root } else { - self.clone() + module_self.clone() }, async_eval_index, pending_async_dependencies, @@ -1237,12 +1212,13 @@ impl SourceTextModule { } } else { // iv. If requiredModule.[[AsyncEvaluation]] is false, set requiredModule.[[Status]] to evaluated. - Status::Evaluated { + ModuleStatus::Evaluated { + environment, top_level_capability, cycle_root: if is_self { cycle_root } else { - self.clone() + module_self.clone() }, error: None, } @@ -1267,14 +1243,14 @@ impl SourceTextModule { /// Abstract operation [`ExecuteAsyncModule ( module )`][spec]. /// /// [spec]: https://tc39.es/ecma262/#sec-execute-async-module - fn execute_async(&self, context: &mut Context) { + fn execute_async(&self, module_self: &Module, context: &mut Context) { // 1. Assert: module.[[Status]] is either evaluating or evaluating-async. debug_assert!(matches!( - &*self.inner.status.borrow(), - Status::Evaluating { .. } | Status::EvaluatingAsync { .. } + &*self.status.borrow(), + ModuleStatus::Evaluating { .. } | ModuleStatus::EvaluatingAsync { .. } )); // 2. Assert: module.[[HasTLA]] is true. - debug_assert!(self.inner.code.has_tla); + debug_assert!(self.code.has_tla); // 3. Let capability be ! NewPromiseCapability(%Promise%). let capability = PromiseCapability::new( @@ -1294,7 +1270,7 @@ impl SourceTextModule { // b. Return undefined. Ok(JsValue::undefined()) }, - self.clone(), + module_self.clone(), ), ) .build(); @@ -1311,7 +1287,7 @@ impl SourceTextModule { // b. Return undefined. Ok(JsValue::undefined()) }, - self.clone(), + module_self.clone(), ), ) .build(); @@ -1327,7 +1303,7 @@ impl SourceTextModule { // 9. Perform ! module.ExecuteModule(capability). // 10. Return unused. - self.execute(Some(&capability), context) + self.execute(module_self, Some(&capability), context) .expect("async modules cannot directly throw"); } @@ -1335,42 +1311,59 @@ impl SourceTextModule { /// /// [spec]: https://tc39.es/ecma262/#sec-gather-available-ancestors #[allow(clippy::mutable_key_type)] - fn gather_available_ancestors(&self, exec_list: &mut FxHashSet) { + fn gather_available_ancestors(&self, exec_list: &mut FxHashSet) { // 1. For each Cyclic Module Record m of module.[[AsyncParentModules]], do - for m in &*self.inner.async_parent_modules.borrow() { - // a. If execList does not contain m and m.[[CycleRoot]].[[EvaluationError]] is empty, then - if !exec_list.contains(m) - && m.inner.status.borrow().cycle_root().map_or(false, |cr| { - cr.inner.status.borrow().evaluation_error().is_none() - }) + for m in &*self.async_parent_modules.borrow() { + let ModuleKind::SourceText(m_src) = m.kind() else { + continue; + }; + + if exec_list.contains(m) { + continue; + } + { - let (deps, has_tla) = { - // i. Assert: m.[[Status]] is evaluating-async. - // ii. Assert: m.[[EvaluationError]] is empty. - // iii. Assert: m.[[AsyncEvaluation]] is true. - let Status::EvaluatingAsync { - pending_async_dependencies, - .. - } = &mut *m.inner.status.borrow_mut() - else { - unreachable!("i. Assert: m.[[Status]] is evaluating-async."); - }; - // iv. Assert: m.[[PendingAsyncDependencies]] > 0. - assert!(*pending_async_dependencies > 0); - - // v. Set m.[[PendingAsyncDependencies]] to m.[[PendingAsyncDependencies]] - 1. - *pending_async_dependencies -= 1; - (*pending_async_dependencies, m.inner.code.has_tla) + let m_status = m_src.status.borrow(); + let Some(cycle_root) = m_status + .cycle_root() + .and_then(|root| root.kind().as_source_text()) + else { + continue; }; - // vi. If m.[[PendingAsyncDependencies]] = 0, then - if deps == 0 { - // 1. Append m to execList. - exec_list.insert(m.clone()); - // 2. If m.[[HasTLA]] is false, perform GatherAvailableAncestors(m, execList). - if !has_tla { - m.gather_available_ancestors(exec_list); - } + if cycle_root.status.borrow().evaluation_error().is_some() { + continue; + } + } + + // a. If execList does not contain m and m.[[CycleRoot]].[[EvaluationError]] is empty, then + + let (deps, has_tla) = { + // i. Assert: m.[[Status]] is evaluating-async. + // ii. Assert: m.[[EvaluationError]] is empty. + // iii. Assert: m.[[AsyncEvaluation]] is true. + let ModuleStatus::EvaluatingAsync { + pending_async_dependencies, + .. + } = &mut *m_src.status.borrow_mut() + else { + unreachable!("i. Assert: m.[[Status]] is evaluating-async."); + }; + // iv. Assert: m.[[PendingAsyncDependencies]] > 0. + assert!(*pending_async_dependencies > 0); + + // v. Set m.[[PendingAsyncDependencies]] to m.[[PendingAsyncDependencies]] - 1. + *pending_async_dependencies -= 1; + (*pending_async_dependencies, m_src.code.has_tla) + }; + + // vi. If m.[[PendingAsyncDependencies]] = 0, then + if deps == 0 { + // 1. Append m to execList. + exec_list.insert(m.clone()); + // 2. If m.[[HasTLA]] is false, perform GatherAvailableAncestors(m, execList). + if !has_tla { + m_src.gather_available_ancestors(exec_list); } } } @@ -1380,7 +1373,7 @@ impl SourceTextModule { /// Abstract operation [`InitializeEnvironment ( )`][spec]. /// /// [spec]: https://tc39.es/ecma262/#sec-source-text-module-record-initialize-environment - fn initialize_environment(&self, context: &mut Context) -> JsResult<()> { + fn initialize_environment(&self, module_self: &Module, context: &mut Context) -> JsResult<()> { #[derive(Debug)] enum ImportBinding { Namespace { @@ -1393,13 +1386,11 @@ impl SourceTextModule { }, } - let parent = self.parent(); - { // 1. For each ExportEntry Record e of module.[[IndirectExportEntries]], do - for e in &self.inner.code.indirect_export_entries { + for e in &self.code.indirect_export_entries { // a. Let resolution be module.ResolveExport(e.[[ExportName]]). - parent + module_self .resolve_export( e.export_name().to_js_string(context.interner()), &mut HashSet::default(), @@ -1427,7 +1418,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 realm = parent.realm().clone(); + let realm = module_self.realm().clone(); // 5. Let env be NewModuleEnvironment(realm.[[GlobalEnv]]). // 6. Set module.[[Environment]] to env. @@ -1451,10 +1442,10 @@ impl SourceTextModule { let (codeblock, functions) = { // 7. For each ImportEntry Record in of module.[[ImportEntries]], do - for entry in &self.inner.code.import_entries { + for entry in &self.code.import_entries { // a. Let importedModule be GetImportedModule(module, in.[[ModuleRequest]]). let module_request = entry.module_request().to_js_string(compiler.interner()); - let imported_module = self.inner.loaded_modules.borrow()[&module_request].clone(); + let imported_module = self.loaded_modules.borrow()[&module_request].clone(); if let ImportName::Name(name) = entry.import_name() { let name = name.to_js_string(compiler.interner()); @@ -1516,7 +1507,7 @@ impl SourceTextModule { // 18. Let code be module.[[ECMAScriptCode]]. // 19. Let varDeclarations be the VarScopedDeclarations of code. - let var_declarations = var_scoped_declarations(&self.inner.code.source); + let var_declarations = var_scoped_declarations(&self.code.source); // 20. Let declaredVarNames be a new empty List. let mut declared_var_names = Vec::new(); // 21. For each element d of varDeclarations, do @@ -1542,7 +1533,7 @@ impl SourceTextModule { // 22. Let lexDeclarations be the LexicallyScopedDeclarations of code. // 23. Let privateEnv be null. - let lex_declarations = lexically_scoped_declarations(&self.inner.code.source); + let lex_declarations = lexically_scoped_declarations(&self.code.source); let mut functions = Vec::new(); // 24. For each element d of lexDeclarations, do for declaration in lex_declarations { @@ -1626,7 +1617,7 @@ impl SourceTextModule { .map(|(spec, locator)| (compiler.function(spec), locator)) .collect::>(); - compiler.compile_module_item_list(self.inner.code.source.items()); + compiler.compile_module_item_list(self.code.source.items()); (Gc::new(compiler.finish()), functions) }; @@ -1644,7 +1635,7 @@ impl SourceTextModule { // 15. Set the PrivateEnvironment of moduleContext to null. let call_frame = CallFrame::new( codeblock.clone(), - Some(ActiveRunnable::Module(parent.clone())), + Some(ActiveRunnable::Module(module_self.clone())), envs, realm.clone(), ); @@ -1710,27 +1701,28 @@ impl SourceTextModule { .pop_frame() .expect("There should be a call frame"); - debug_assert!(frame.environments.current().as_declarative().is_some()); - *parent.inner.environment.borrow_mut() = - frame.environments.current().as_declarative().cloned(); + let env = frame + .environments + .current() + .as_declarative() + .cloned() + .expect("frame must have a declarative environment"); // 16. Set module.[[Context]] to moduleContext. - self.inner - .status - .borrow_mut() - .transition(|state| match state { - Status::Linking { info } => Status::PreLinked { - info, - context: SourceTextContext { - codeblock, - environments: frame.environments.clone(), - realm, - }, + self.status.borrow_mut().transition(|state| match state { + ModuleStatus::Linking { info } => ModuleStatus::PreLinked { + environment: env, + info, + context: SourceTextContext { + codeblock, + environments: frame.environments.clone(), + realm, }, - _ => unreachable!( - "should only transition to the `PreLinked` state from the `Linking` state" - ), - }); + }, + _ => unreachable!( + "should only transition to the `PreLinked` state from the `Linking` state" + ), + }); // 26. Return unused. Ok(()) @@ -1741,6 +1733,7 @@ impl SourceTextModule { /// [spec]: https://tc39.es/ecma262/#sec-source-text-module-record-execute-module fn execute( &self, + module_self: &Module, capability: Option<&PromiseCapability>, context: &mut Context, ) -> JsResult<()> { @@ -1749,10 +1742,9 @@ impl SourceTextModule { codeblock, environments, realm, - } = match &*self.inner.status.borrow() { - Status::Evaluating { context, .. } | Status::EvaluatingAsync { context, .. } => { - context.clone() - } + } = match &*self.status.borrow() { + ModuleStatus::Evaluating { context, .. } + | ModuleStatus::EvaluatingAsync { context, .. } => context.clone(), _ => unreachable!("`execute` should only be called for evaluating modules."), }; @@ -1765,7 +1757,7 @@ impl SourceTextModule { let env_fp = environments.len() as u32; let callframe = CallFrame::new( codeblock, - Some(ActiveRunnable::Module(self.parent())), + Some(ActiveRunnable::Module(module_self.clone())), environments, realm, ) @@ -1809,12 +1801,18 @@ impl SourceTextModule { /// Gets the loaded modules of this module. pub(crate) fn loaded_modules(&self) -> &GcRefCell> { - &self.inner.loaded_modules + &self.loaded_modules } - /// Gets the import meta object of this module. + /// Gets the import meta object of this module, or initializes + /// it using the provided callback. pub(crate) fn import_meta(&self) -> &GcRefCell> { - &self.inner.import_meta + &self.import_meta + } + + /// Gets the declarative environment of this module. + pub(crate) fn environment(&self) -> Option> { + self.status.borrow().environment() } } @@ -1822,9 +1820,13 @@ impl SourceTextModule { /// /// [spec]: https://tc39.es/ecma262/#sec-async-module-execution-fulfilled #[allow(clippy::mutable_key_type)] -fn async_module_execution_fulfilled(module: &SourceTextModule, context: &mut Context) { +fn async_module_execution_fulfilled(module: &Module, context: &mut Context) { + let ModuleKind::SourceText(module_src) = module.kind() else { + unreachable!("async executed module must be a source text module"); + }; + // 1. If module.[[Status]] is evaluated, then - if let Status::Evaluated { error, .. } = &*module.inner.status.borrow() { + if let ModuleStatus::Evaluated { error, .. } = &*module_src.status.borrow() { // a. Assert: module.[[EvaluationError]] is not empty. assert!(error.is_some()); // b. Return unused. @@ -1836,16 +1838,17 @@ fn async_module_execution_fulfilled(module: &SourceTextModule, context: &mut Con // 4. Assert: module.[[EvaluationError]] is empty. // 5. Set module.[[AsyncEvaluation]] to false. // 6. Set module.[[Status]] to evaluated. - module - .inner + module_src .status .borrow_mut() .transition(|status| match status { - Status::EvaluatingAsync { + ModuleStatus::EvaluatingAsync { + environment, top_level_capability, cycle_root, .. - } => Status::Evaluated { + } => ModuleStatus::Evaluated { + environment, top_level_capability, cycle_root, error: None, @@ -1854,9 +1857,9 @@ fn async_module_execution_fulfilled(module: &SourceTextModule, context: &mut Con }); // 7. If module.[[TopLevelCapability]] is not empty, then - if let Some(cap) = module.inner.status.borrow().top_level_capability() { + if let Some(cap) = module_src.status.borrow().top_level_capability() { // a. Assert: module.[[CycleRoot]] is module. - debug_assert_eq!(module.inner.status.borrow().cycle_root(), Some(module)); + debug_assert_eq!(module_src.status.borrow().cycle_root(), Some(module)); // b. Perform ! Call(module.[[TopLevelCapability]].[[Resolve]], undefined, « undefined »). cap.resolve() @@ -1868,16 +1871,20 @@ fn async_module_execution_fulfilled(module: &SourceTextModule, context: &mut Con let mut ancestors = FxHashSet::default(); // 9. Perform GatherAvailableAncestors(module, execList). - module.gather_available_ancestors(&mut ancestors); + module_src.gather_available_ancestors(&mut ancestors); // 11. Assert: All elements of sortedExecList have their [[AsyncEvaluation]] field set to true, [[PendingAsyncDependencies]] field set to 0, and [[EvaluationError]] field set to empty. let mut ancestors = ancestors.into_iter().collect::>(); // 10. Let sortedExecList be a List whose elements are the elements of execList, in the order in which they had their [[AsyncEvaluation]] fields set to true in InnerModuleEvaluation. ancestors.sort_by_cached_key(|m| { - let Status::EvaluatingAsync { + let ModuleKind::SourceText(m_src) = m.kind() else { + unreachable!("ancestors must only be source text modules"); + }; + + let ModuleStatus::EvaluatingAsync { async_eval_index, .. - } = &*m.inner.status.borrow() + } = &*m_src.status.borrow() else { unreachable!("GatherAvailableAncestors: i. Assert: m.[[Status]] is evaluating-async."); }; @@ -1887,22 +1894,26 @@ fn async_module_execution_fulfilled(module: &SourceTextModule, context: &mut Con // 12. For each Cyclic Module Record m of sortedExecList, do for m in ancestors { + let ModuleKind::SourceText(m_src) = m.kind() else { + continue; + }; + // a. If m.[[Status]] is evaluated, then - if let Status::Evaluated { error, .. } = &*m.inner.status.borrow() { + if let ModuleStatus::Evaluated { error, .. } = &*m_src.status.borrow() { // i. Assert: m.[[EvaluationError]] is not empty. assert!(error.is_some()); continue; } // b. Else if m.[[HasTLA]] is true, then - let has_tla = m.inner.code.has_tla; + let has_tla = m_src.code.has_tla; if has_tla { // i. Perform ExecuteAsyncModule(m). - m.execute_async(context); + m_src.execute_async(&m, context); } else { // c. Else, // i. Let result be m.ExecuteModule(). - let result = m.execute(None, context); + let result = m_src.execute(&m, None, context); // ii. If result is an abrupt completion, then if let Err(e) = result { @@ -1911,27 +1922,26 @@ fn async_module_execution_fulfilled(module: &SourceTextModule, context: &mut Con } else { // iii. Else, // 1. Set m.[[Status]] to evaluated. - m.inner - .status - .borrow_mut() - .transition(|status| match status { - Status::EvaluatingAsync { - top_level_capability, - cycle_root, - .. - } => Status::Evaluated { - top_level_capability, - cycle_root, - error: None, - }, - _ => unreachable!(), - }); + m_src.status.borrow_mut().transition(|status| match status { + ModuleStatus::EvaluatingAsync { + environment, + top_level_capability, + cycle_root, + .. + } => ModuleStatus::Evaluated { + environment, + top_level_capability, + cycle_root, + error: None, + }, + _ => unreachable!(), + }); - let status = m.inner.status.borrow(); + let status = m_src.status.borrow(); // 2. If m.[[TopLevelCapability]] is not empty, then if let Some(cap) = status.top_level_capability() { // a. Assert: m.[[CycleRoot]] is m. - debug_assert_eq!(status.cycle_root(), Some(&m)); + assert_eq!(status.cycle_root(), Some(&m)); // b. Perform ! Call(m.[[TopLevelCapability]].[[Resolve]], undefined, « undefined »). cap.resolve() @@ -1947,13 +1957,12 @@ fn async_module_execution_fulfilled(module: &SourceTextModule, context: &mut Con /// Abstract operation [`AsyncModuleExecutionRejected ( module, error )`][spec]. /// /// [spec]: https://tc39.es/ecma262/#sec-async-module-execution-rejected -fn async_module_execution_rejected( - module: &SourceTextModule, - error: &JsError, - context: &mut Context, -) { +fn async_module_execution_rejected(module: &Module, error: &JsError, context: &mut Context) { + let ModuleKind::SourceText(module_src) = module.kind() else { + unreachable!("async executed module must be a source text module"); + }; // 1. If module.[[Status]] is evaluated, then - if let Status::Evaluated { error, .. } = &*module.inner.status.borrow() { + if let ModuleStatus::Evaluated { error, .. } = &*module_src.status.borrow() { // a. Assert: module.[[EvaluationError]] is not empty. assert!(error.is_some()); // b. Return unused. @@ -1965,16 +1974,17 @@ fn async_module_execution_rejected( // 4. Assert: module.[[EvaluationError]] is empty. // 5. Set module.[[EvaluationError]] to ThrowCompletion(error). // 6. Set module.[[Status]] to evaluated. - module - .inner + module_src .status .borrow_mut() .transition(|status| match status { - Status::EvaluatingAsync { + ModuleStatus::EvaluatingAsync { + environment, top_level_capability, cycle_root, .. - } => Status::Evaluated { + } => ModuleStatus::Evaluated { + environment, top_level_capability, cycle_root, error: Some(error.clone()), @@ -1983,16 +1993,16 @@ fn async_module_execution_rejected( }); // 7. For each Cyclic Module Record m of module.[[AsyncParentModules]], do - for m in &*module.inner.async_parent_modules.borrow() { + for m in &*module_src.async_parent_modules.borrow() { // a. Perform AsyncModuleExecutionRejected(m, error). async_module_execution_rejected(m, error, context); } - let status = module.inner.status.borrow(); + let status = module_src.status.borrow(); // 8. If module.[[TopLevelCapability]] is not empty, then if let Some(cap) = status.top_level_capability() { // a. Assert: module.[[CycleRoot]] is module. - debug_assert_eq!(status.cycle_root(), Some(module)); + assert_eq!(status.cycle_root(), Some(module)); // b. Perform ! Call(module.[[TopLevelCapability]].[[Reject]], undefined, « error »). cap.reject() @@ -2001,17 +2011,3 @@ fn async_module_execution_rejected( } // 9. Return unused. } - -impl PartialEq for SourceTextModule { - fn eq(&self, other: &Self) -> bool { - std::ptr::eq(self.inner.as_ref(), other.inner.as_ref()) - } -} - -impl Eq for SourceTextModule {} - -impl Hash for SourceTextModule { - fn hash(&self, state: &mut H) { - std::ptr::hash(self.inner.as_ref(), state); - } -} diff --git a/core/engine/src/module/synthetic.rs b/core/engine/src/module/synthetic.rs index 2b993131dc..fa151d468f 100644 --- a/core/engine/src/module/synthetic.rs +++ b/core/engine/src/module/synthetic.rs @@ -1,19 +1,19 @@ use std::rc::Rc; -use boa_gc::{Finalize, Gc, GcRefCell, Trace, WeakGc}; +use boa_gc::{Finalize, Gc, GcRefCell, Trace}; use rustc_hash::FxHashSet; use crate::{ builtins::promise::ResolvingFunctions, bytecompiler::ByteCompiler, - environments::{CompileTimeEnvironment, EnvironmentStack}, + environments::{CompileTimeEnvironment, DeclarativeEnvironment, EnvironmentStack}, js_string, object::JsPromise, vm::{ActiveRunnable, CallFrame, CodeBlock}, Context, JsNativeError, JsResult, JsString, JsValue, Module, }; -use super::{BindingName, ModuleRepr, ResolveExportError, ResolvedBinding}; +use super::{BindingName, ResolveExportError, ResolvedBinding}; trait TraceableCallback: Trace { fn call(&self, module: &SyntheticModule, context: &mut Context) -> JsResult<()>; @@ -135,62 +135,93 @@ impl SyntheticModuleInitializer { /// Calls this `SyntheticModuleInitializer`, forwarding the arguments to the corresponding function. #[inline] - pub fn call(&self, module: &SyntheticModule, context: &mut Context) -> JsResult<()> { + pub(crate) fn call(&self, module: &SyntheticModule, context: &mut Context) -> JsResult<()> { self.inner.call(module, context) } } +/// Current status of a [`SyntheticModule`]. +#[derive(Debug, Trace, Finalize, Default)] +#[boa_gc(unsafe_no_drop)] +enum ModuleStatus { + #[default] + Unlinked, + Linked { + environment: Gc, + eval_context: (EnvironmentStack, Gc), + }, + Evaluated { + environment: Gc, + promise: JsPromise, + }, +} + +impl ModuleStatus { + /// Transition from one state to another, taking the current state by value to move data + /// between states. + fn transition(&mut self, f: F) + where + F: FnOnce(Self) -> Self, + { + *self = f(std::mem::take(self)); + } +} + /// ECMAScript's [**Synthetic Module Records**][spec]. /// /// [spec]: https://tc39.es/proposal-json-modules/#sec-synthetic-module-records -#[derive(Clone, Trace, Finalize)] +#[derive(Trace, Finalize)] pub struct SyntheticModule { - inner: Gc, + #[unsafe_ignore_trace] + export_names: FxHashSet, + eval_steps: SyntheticModuleInitializer, + state: GcRefCell, } impl std::fmt::Debug for SyntheticModule { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("SyntheticModule") - .field("export_names", &self.inner.export_names) - .field("eval_steps", &self.inner.eval_steps) + .field("export_names", &self.export_names) + .field("eval_steps", &self.eval_steps) .finish_non_exhaustive() } } -#[derive(Trace, Finalize)] -struct Inner { - parent: WeakGc, - #[unsafe_ignore_trace] - export_names: FxHashSet, - eval_context: GcRefCell)>>, - eval_steps: SyntheticModuleInitializer, -} - impl SyntheticModule { - /// Gets the parent module of this source module. - fn parent(&self) -> Module { - Module { - inner: self - .inner - .parent - .upgrade() - .expect("parent module must be live"), - } + /// Abstract operation [`SetSyntheticModuleExport ( module, exportName, exportValue )`][spec]. + /// + /// Sets or changes the exported value for `exportName` in the synthetic module. + /// + /// # Note + /// + /// The default export corresponds to the name `"default"`, but note that it needs to + /// be passed to the list of exported names in [`Module::synthetic`] beforehand. + /// + /// [spec]: https://tc39.es/proposal-json-modules/#sec-createsyntheticmodule + pub fn set_export(&self, export_name: &JsString, export_value: JsValue) -> JsResult<()> { + let env = self.environment().ok_or_else(|| { + JsNativeError::typ().with_message(format!( + "cannot set name `{}` in an unlinked synthetic module", + export_name.to_std_string_escaped() + )) + })?; + let locator = env.compile_env().get_binding(export_name).ok_or_else(|| { + JsNativeError::reference().with_message(format!( + "cannot set name `{}` which was not included in the list of exports", + export_name.to_std_string_escaped() + )) + })?; + env.set(locator.binding_index(), export_value); + + Ok(()) } /// Creates a new synthetic module. - pub(super) fn new( - names: FxHashSet, - eval_steps: SyntheticModuleInitializer, - parent: WeakGc, - ) -> Self { + pub(super) fn new(names: FxHashSet, eval_steps: SyntheticModuleInitializer) -> Self { Self { - inner: Gc::new(Inner { - parent, - export_names: names, - eval_steps, - eval_context: GcRefCell::default(), - }), + export_names: names, + eval_steps, + state: GcRefCell::default(), } } @@ -207,7 +238,7 @@ impl SyntheticModule { /// [spec]: https://tc39.es/proposal-json-modules/#sec-smr-getexportednames pub(super) fn get_exported_names(&self) -> FxHashSet { // 1. Return module.[[ExportNames]]. - self.inner.export_names.clone() + self.export_names.clone() } /// Concrete method [`ResolveExport ( exportName )`][spec] @@ -216,12 +247,13 @@ impl SyntheticModule { #[allow(clippy::mutable_key_type)] pub(super) fn resolve_export( &self, + module_self: &Module, export_name: JsString, ) -> Result { - if self.inner.export_names.contains(&export_name) { + if self.export_names.contains(&export_name) { // 2. Return ResolvedBinding Record { [[Module]]: module, [[BindingName]]: exportName }. Ok(ResolvedBinding { - module: self.parent(), + module: module_self.clone(), binding_name: BindingName::Name(export_name), }) } else { @@ -233,12 +265,16 @@ impl SyntheticModule { /// Concrete method [`Link ( )`][spec]. /// /// [spec]: https://tc39.es/ecma262/#sec-moduledeclarationlinking - pub(super) fn link(&self, context: &mut Context) { - let parent = self.parent(); + pub(super) fn link(&self, module_self: &Module, context: &mut Context) { + if !matches!(&*self.state.borrow(), ModuleStatus::Unlinked) { + // Already linked and/or evaluated. + return; + } + // 1. Let realm be module.[[Realm]]. // 2. Let env be NewModuleEnvironment(realm.[[GlobalEnv]]). // 3. Set module.[[Environment]] to env. - let global_env = parent.realm().environment().clone(); + let global_env = module_self.realm().environment().clone(); let global_compile_env = global_env.compile_env(); let module_compile_env = Rc::new(CompileTimeEnvironment::new(global_compile_env, true)); @@ -255,7 +291,6 @@ impl SyntheticModule { // 4. For each String exportName in module.[[ExportNames]], do let exports = self - .inner .export_names .iter() .map(|name| { @@ -278,9 +313,18 @@ impl SyntheticModule { ); } - *parent.inner.environment.borrow_mut() = envs.current().as_declarative().cloned(); + let env = envs + .current() + .as_declarative() + .cloned() + .expect("should have the module environment"); - *self.inner.eval_context.borrow_mut() = Some((envs, cb)); + self.state + .borrow_mut() + .transition(|_| ModuleStatus::Linked { + environment: env, + eval_context: (envs, cb), + }); // 5. Return unused. } @@ -288,23 +332,34 @@ impl SyntheticModule { /// Concrete method [`Evaluate ( )`][spec]. /// /// [spec]: https://tc39.es/proposal-json-modules/#sec-smr-Evaluate - pub(super) fn evaluate(&self, context: &mut Context) -> JsPromise { + pub(super) fn evaluate(&self, module_self: &Module, context: &mut Context) -> JsPromise { + let (environments, codeblock) = match &*self.state.borrow() { + ModuleStatus::Unlinked => { + let (promise, ResolvingFunctions { reject, .. }) = JsPromise::new_pending(context); + reject + .call( + &JsValue::undefined(), + &[JsNativeError::typ() + .with_message("cannot evaluate unlinked synthetic module") + .to_opaque(context) + .into()], + context, + ) + .expect("native resolving functions cannot throw"); + return promise; + } + ModuleStatus::Linked { eval_context, .. } => eval_context.clone(), + ModuleStatus::Evaluated { promise, .. } => return promise.clone(), + }; // 1. Let moduleContext be a new ECMAScript code execution context. - let parent = self.parent(); - let realm = parent.realm().clone(); - let (environments, codeblock) = self - .inner - .eval_context - .borrow() - .clone() - .expect("should have been initialized on `link`"); + let realm = module_self.realm().clone(); let env_fp = environments.len() as u32; let callframe = CallFrame::new( codeblock, // 4. Set the ScriptOrModule of moduleContext to module. - Some(ActiveRunnable::Module(parent)), + Some(ActiveRunnable::Module(module_self.clone())), // 5. Set the VariableEnvironment of moduleContext to module.[[Environment]]. // 6. Set the LexicalEnvironment of moduleContext to module.[[Environment]]. environments, @@ -322,7 +377,7 @@ impl SyntheticModule { // 9. Let steps be module.[[EvaluationSteps]]. // 10. Let result be Completion(steps(module)). - let result = self.inner.eval_steps.call(self, context); + let result = self.eval_steps.call(self, context); // 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. @@ -340,36 +395,23 @@ impl SyntheticModule { } .expect("default resolving functions cannot throw"); + self.state.borrow_mut().transition(|state| match state { + ModuleStatus::Linked { environment, .. } => ModuleStatus::Evaluated { + environment, + promise: promise.clone(), + }, + _ => unreachable!("checks above ensure the module is linked"), + }); + // 16. Return pc.[[Promise]]. promise } - /// Abstract operation [`SetSyntheticModuleExport ( module, exportName, exportValue )`][spec]. - /// - /// Sets or changes the exported value for `exportName` in the synthetic module. - /// - /// # Note - /// - /// The default export corresponds to the name `"default"`, but note that it needs to - /// be passed to the list of exported names in [`Module::synthetic`] beforehand. - /// - /// [spec]: https://tc39.es/proposal-json-modules/#sec-createsyntheticmodule - pub fn set_export(&self, export_name: &JsString, export_value: JsValue) -> JsResult<()> { - let environment = self - .parent() - .environment() - .expect("this must be initialized before evaluating"); - let locator = environment - .compile_env() - .get_binding(export_name) - .ok_or_else(|| { - JsNativeError::reference().with_message(format!( - "cannot set name `{}` which was not included in the list of exports", - export_name.to_std_string_escaped() - )) - })?; - environment.set(locator.binding_index(), export_value); - - Ok(()) + pub(crate) fn environment(&self) -> Option> { + match &*self.state.borrow() { + ModuleStatus::Unlinked => None, + ModuleStatus::Linked { environment, .. } + | ModuleStatus::Evaluated { environment, .. } => Some(environment.clone()), + } } } diff --git a/core/gc/src/trace.rs b/core/gc/src/trace.rs index ba784511f7..b28d976e8d 100644 --- a/core/gc/src/trace.rs +++ b/core/gc/src/trace.rs @@ -1,7 +1,7 @@ use std::{ any::TypeId, borrow::{Cow, ToOwned}, - cell::Cell, + cell::{Cell, OnceCell}, collections::{BTreeMap, BTreeSet, BinaryHeap, HashMap, HashSet, LinkedList, VecDeque}, hash::{BuildHasher, Hash}, marker::PhantomData, @@ -491,6 +491,16 @@ unsafe impl Trace for Cell> { }); } +impl Finalize for OnceCell {} +// SAFETY: We only trace the inner cell if the cell has a value. +unsafe impl Trace for OnceCell { + custom_trace!(this, mark, { + if let Some(v) = this.get() { + mark(v); + } + }); +} + #[cfg(feature = "icu")] mod icu { use icu_locid::{LanguageIdentifier, Locale};