From 9e2b671085dc85eeeb25e9c2939d3bf0846cef1f Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sat, 27 Apr 2024 09:31:56 +0300 Subject: [PATCH 1/8] feat(ecmascript): Begin SourceTextModuleRecord and module loading implementation --- nova_vm/src/ecmascript/builtins/module.rs | 6 +- .../module/abstract_module_records.rs | 58 + .../builtins/module/cyclic_module_records.rs | 1187 +++++++++++++++++ .../src/ecmascript/builtins/module/data.rs | 73 +- .../ecmascript/builtins/module/semantics.rs | 519 +++++++ .../module/source_text_module_records.rs | 617 +++++++++ nova_vm/src/ecmascript/execution/agent.rs | 13 +- 7 files changed, 2404 insertions(+), 69 deletions(-) create mode 100644 nova_vm/src/ecmascript/builtins/module/abstract_module_records.rs create mode 100644 nova_vm/src/ecmascript/builtins/module/cyclic_module_records.rs create mode 100644 nova_vm/src/ecmascript/builtins/module/semantics.rs create mode 100644 nova_vm/src/ecmascript/builtins/module/source_text_module_records.rs diff --git a/nova_vm/src/ecmascript/builtins/module.rs b/nova_vm/src/ecmascript/builtins/module.rs index e15801175..9a7c338fd 100644 --- a/nova_vm/src/ecmascript/builtins/module.rs +++ b/nova_vm/src/ecmascript/builtins/module.rs @@ -21,7 +21,11 @@ use super::ordinary::{ ordinary_own_property_keys, set_immutable_prototype, }; +pub(crate) mod abstract_module_records; +pub(crate) mod cyclic_module_records; pub mod data; +pub(crate) mod semantics; +pub(crate) mod source_text_module_records; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct Module(pub(crate) ModuleIdentifier); @@ -307,7 +311,7 @@ impl InternalMethods for Module { Ok(Value::Undefined) } else { // 4. Let m be O.[[Module]]. - let m = &agent[self].module; + let m = &agent[self].cyclic; // 5. Let binding be m.ResolveExport(P). let binding = m.resolve_export(property_key); // 6. Assert: binding is a ResolvedBinding Record. diff --git a/nova_vm/src/ecmascript/builtins/module/abstract_module_records.rs b/nova_vm/src/ecmascript/builtins/module/abstract_module_records.rs new file mode 100644 index 000000000..b21d4a26b --- /dev/null +++ b/nova_vm/src/ecmascript/builtins/module/abstract_module_records.rs @@ -0,0 +1,58 @@ +use super::Module; +use crate::{ + ecmascript::{ + execution::{ModuleEnvironmentIndex, RealmIdentifier}, + scripts_and_modules::{module::ModuleIdentifier, script::HostDefined}, + }, + heap::indexes::StringIndex, +}; +use small_string::SmallString; + +#[derive(Debug, Clone, Copy)] +pub(crate) struct NotLinkedErr; +#[derive(Debug, Clone, Copy)] +pub(crate) struct NotLoadedErr; + +#[derive(Debug)] +pub(crate) struct ModuleRecord { + /// \[\[Realm]] + /// + /// The Realm within which this module was created. + pub(super) realm: RealmIdentifier, + /// \[\[Environment]] + /// + /// The Environment Record containing the top level bindings for this + /// module. This field is set when the module is linked. + pub(super) environment: Option, + /// \[\[Namespace]] + /// + /// The Module Namespace Object (28.3) if one has been created for this + /// module. + pub(super) namespace: Option, + /// \[\[HostDefined]] + /// + /// Field reserved for use by host environments that need to associate + /// additional information with a module. + pub(super) host_defined: Option, +} + +#[derive(Debug, Clone, Copy)] +pub(crate) enum ResolvedBindingName { + String(StringIndex), + SmallString(SmallString), + Namespace, +} + +#[derive(Debug, Clone, Copy)] +pub(crate) struct ResolvedBinding { + /// \[\[Module]] + pub(super) module: Option, + /// \[\[BindingName]] + pub(super) binding_name: ResolvedBindingName, +} + +#[derive(Debug, Clone, Copy)] +pub(crate) enum ResolveExportResult { + Ambiguous, + Resolved(ResolvedBinding), +} diff --git a/nova_vm/src/ecmascript/builtins/module/cyclic_module_records.rs b/nova_vm/src/ecmascript/builtins/module/cyclic_module_records.rs new file mode 100644 index 000000000..ef5448c35 --- /dev/null +++ b/nova_vm/src/ecmascript/builtins/module/cyclic_module_records.rs @@ -0,0 +1,1187 @@ +use std::any::Any; + +use oxc_span::Atom; + +use crate::ecmascript::{ + abstract_operations::operations_on_objects::call_function, + builtins::{create_builtin_function, error::Error, module::source_text_module_records::get_imported_module, promise::Promise, ArgumentsList}, + execution::{agent::JsError, Agent, JsResult}, + types::{String, Value}, +}; + +use super::{ + abstract_module_records::{ModuleRecord, NotLoadedErr, ResolvedBinding}, + Module, +}; + +/// ### [CyclicModuleRecord] \[\[EvaluationError\]\] +/// +/// A throw completion representing the exception that occurred during +/// evaluation. undefined if no exception occurred or if \[\[Status\]\] is not +/// evaluated. +#[repr(transparent)] +#[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) struct EvaluationError(JsError); + +/// ### [CyclicModuleRecord] \[\[DFSIndex\]\] +/// +/// Auxiliary field used during Link and Evaluate only. If \[\[Status\]\] is +/// either linking or evaluating, this non-negative number records the point at +/// which the module was first visited during the depth-first traversal of the +/// dependency graph. +#[repr(transparent)] +#[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) struct DFSIndex(u16); + +impl DFSIndex { + const fn new(index: u16) -> Self { + Self(index) + } + + const fn value(&self) -> u16 { + self.0 + } +} + +/// ### [CyclicModuleRecord] \[\[DFSAncestorIndex\]\] +/// +/// Auxiliary field used during Link and Evaluate only. If \[\[Status\]\] is +/// either linking or evaluating, this is either the module's own +/// [\[\[DFSIndex\]\]](DFSIndex) or that of an "earlier" module in the same +/// strongly connected component. +#[repr(transparent)] +#[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) struct DFSAncestorIndex(DFSIndex); + +impl DFSAncestorIndex { + const fn new(index: u16) -> Self { + Self(DFSIndex::new(index)) + } + + const fn value(&self) -> u16 { + self.0.value() + } +} + +/// ### [CyclicModuleRecord] \[\[Status\]\] +/// +/// Initially new. Transitions to unlinked, linking, linked, evaluating, +/// possibly evaluating-async, evaluated (in that order) as the module +/// progresses throughout its lifecycle. evaluating-async indicates this module +/// is queued to execute on completion of its asynchronous dependencies or it +/// is a module whose \[\[HasTLA\]\] field is true that has been executed and is +/// pending top-level completion. +#[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) enum CyclicModuleRecordStatus { + New, + Unlinked, + Linking(DFSIndex, DFSAncestorIndex), + Linked, + Evaluating(DFSIndex, DFSAncestorIndex), + EvaluatingAsync, + Evaluated(Option), +} + +#[derive(Debug, Clone)] +pub(crate) struct LoadedModuleRecord { + /// \[\[Specifier\]\] + pub(super) specifier: Atom<'static>, + pub(super) module: Module, +} + +#[derive(Debug, Clone)] +pub(crate) struct CyclicModuleRecord { + /// [\[\[Status\]\]](CyclicModuleRecordStatus) + pub(super) status: CyclicModuleRecordStatus, + /// \[\[RequestedModules\]\] + /// + /// A List of all the ModuleSpecifier strings used by the module + /// represented by this record to request the importation of a module. The + /// List is in source text occurrence order. + pub(super) requested_modules: Box<[Atom<'static>]>, + /// \[\[LoadedModules\]\] + /// + /// A map from the specifier strings used by the module represented by this + /// record to request the importation of a module to the resolved Module + /// Record. The list does not contain two different Records with the same + /// \[\[Specifier\]\]. + pub(super) loaded_modules: Box<[LoadedModuleRecord]>, + /// \[\[CycleRoot\]\] + /// + /// The first visited module of the cycle, the root DFS ancestor of the + /// strongly connected component. For a module not in a cycle, this would + /// be the module itself. Once Evaluate has completed, a module's + /// \[\[DFSAncestorIndex\]\] is the \[\[DFSIndex\]\] of its + /// \[\[CycleRoot\]\]. + pub(super) cycle_root: Option, + /// \[\[HasTLA\]\] + /// + /// Whether this module is individually asynchronous (for example, if it's + /// a Source Text Module Record containing a top-level await). Having an + /// asynchronous dependency does not mean this field is true. This field + /// must not change after the module is parsed. + pub(super) has_top_level_await: bool, + /// \[\[AsyncEvaluation\]\] + /// + /// Whether this module is either itself asynchronous or has an + /// asynchronous dependency. Note: The order in which this field is set is + /// used to order queued executions, see 16.2.1.5.3.4. + pub(super) async_evaluation: bool, + /// \[\[TopLevelCapability\]\] + /// + /// TODO: a PromiseCapability Record or empty + /// + /// If this module is the \[\[CycleRoot\]\] of some cycle, and Evaluate() + /// was called on some module in that cycle, this field contains the + /// PromiseCapability Record for that entire evaluation. It is used to + /// settle the Promise object that is returned from the Evaluate() abstract + /// method. This field will be empty for any dependencies of that module, + /// unless a top-level Evaluate() has been initiated for some of those + /// dependencies. + pub(super) top_level_capability: Option<()>, + /// \[\[AsyncParentModules\]\] + /// + /// a List of Cyclic Module Records + /// + /// If this module or a dependency has \[\[HasTLA\]\] true, and execution + /// is in progress, this tracks the parent importers of this module for the + /// top-level execution job. These parent modules will not start executing + /// before this module has successfully completed execution. + pub(super) async_parent_modules: Vec, + /// \[\[PendingAsyncDependencies\]\] + /// + /// If this module has any asynchronous dependencies, this tracks the + /// number of asynchronous dependency modules remaining to execute for this + /// module. A module with asynchronous dependencies will be executed when + /// this field reaches 0 and there are no execution errors. + pub(super) pending_async_dependencies: Option, +} + +impl CyclicModuleRecord { + pub(crate) fn initialize_environment() { + todo!(); + } + + pub(crate) fn execute_module(_promise_capability: Option<()>) { + todo!(); + } +} + +pub(crate) struct GraphLoadingStateRecord { + /// \[\[PromiseCapability\]\] + /// + /// TODO: a PromiseCapability Record + /// + /// The promise to resolve when the loading process finishes. + promise_capability: (), + /// \[\[IsLoading\]\] + /// + /// It is true if the loading process has not finished yet, neither + /// successfully nor with an error. + is_loading: bool, + /// \[\[PendingModulesCount\]\] + /// + /// a non-negative integer + /// + /// It tracks the number of pending HostLoadImportedModule calls. + pending_modules_count: u16, + /// \[\[Visited\]\] + /// + /// a List of Cyclic Module Records + /// + /// It is a list of the Cyclic Module Records that have been already + /// loaded by the current loading process, to avoid infinite loops with + /// circular dependencies. + visited: Vec, + /// \[\[HostDefined\]\] + /// + /// anything (default value is empty) + /// + /// It contains host-defined data to pass from the LoadRequestedModules + /// caller to HostLoadImportedModule. + host_defined: Option>, +} + +impl Module { + fn is_cyclic_module_record(self) -> bool { + true + } + + /// ### [16.2.1.5.1 LoadRequestedModules ( \[ hostDefined \] )](https://tc39.es/ecma262/#sec-InnerModuleLoading) + /// + /// The LoadRequestedModules concrete method of a Cyclic Module Record + /// module takes optional argument hostDefined (anything) and returns a + /// Promise. It populates the \[\[LoadedModules\]\] of all the Module + /// Records in the dependency graph of module (most of the work is done by + /// the auxiliary function InnerModuleLoading). It takes an optional + /// hostDefined parameter that is passed to the HostLoadImportedModule + /// hook. + fn load_requested_modules( + self, + agent: &mut Agent, + host_defined: Option>, + ) -> Promise { + // 1. If hostDefined is not present, let hostDefined be empty. + // TODO: 2. Let pc be ! NewPromiseCapability(%Promise%). + let pc = (); + // 3. Let state be the GraphLoadingState Record { + let mut state = GraphLoadingStateRecord { + // [[PromiseCapability]]: pc, + promise_capability: pc, + // [[IsLoading]]: true, + is_loading: true, + // [[PendingModulesCount]]: 1, + pending_modules_count: 1, + // [[Visited]]: « », + visited: vec![], + // [[HostDefined]]: hostDefined + host_defined, + }; + // }. + // 4. Perform InnerModuleLoading(state, module). + inner_module_loading(agent, &mut state, self); + // 5. Return pc.[[Promise]]. + + // Note + // The hostDefined parameter can be used to pass additional information + // necessary to fetch the imported modules. It is used, for example, by + // HTML to set the correct fetch destination for + // `` tags. `import()` expressions never + // set the hostDefined parameter. + todo!(); + } + + pub(crate) fn get_exported_names( + self, + agent: &mut Agent, + export_start_set: Option<()>, + ) -> Box<[String]> { + todo!() + } + + fn resolve_export( + self, + agent: &mut Agent, + export_name: String, + resolve_set: Option<()>, + ) -> Option { + todo!() + } + + /// ### [16.2.1.5.2 Link ( )](https://tc39.es/ecma262/#sec-moduledeclarationlinking) + /// + /// The Link concrete method of a Cyclic Module Record module takes no + /// arguments and returns either a normal completion containing unused or a + /// throw completion. On success, Link transitions this module's \[\[Status\]\] + /// from unlinked to linked. On failure, an exception is thrown and this + /// module's \[\[Status\]\] remains unlinked. (Most of the work is done by the + /// auxiliary function InnerModuleLinking.) + fn link(self, agent: &mut Agent) -> JsResult<()> { + link(agent, self) + } + + fn evaluate( + self, + agent: &mut Agent, + ) -> Result { + Ok(evaluate(agent, self)) + } + + fn initialize_environment(self, agent: &mut Agent) {} + + fn execute_module(self, agent: &mut Agent, promise_capability: Option<()>) {} +} + +/// ### [16.2.1.5.1.1 InnerModuleLoading ( state, module )](https://tc39.es/ecma262/#sec-InnerModuleLoading) +/// +/// The abstract operation InnerModuleLoading takes arguments state (a GraphLoadingState Record) and module (a Module Record) and returns unused. It is used by LoadRequestedModules to recursively perform the actual loading process for module's dependency graph. It performs the following steps when called: +fn inner_module_loading(agent: &mut Agent, state: &mut GraphLoadingStateRecord, module: Module) { + // 1. Assert: state.[[IsLoading]] is true. + assert!(state.is_loading); + // 2. If module is a Cyclic Module Record, module.[[Status]] is new, and + // state.[[Visited]] does not contain module, then + if matches!(agent[module].cyclic.status, CyclicModuleRecordStatus::New) + && !state.visited.contains(&module) + { + // a. Append module to state.[[Visited]]. + state.visited.push(module); + // b. Let requestedModulesCount be the number of elements in module.[[RequestedModules]]. + let requested_modules_count = agent[module].cyclic.requested_modules.len(); + // c. Set state.[[PendingModulesCount]] to state.[[PendingModulesCount]] + requestedModulesCount. + state.pending_modules_count += requested_modules_count as u16; + // d. For each String required of module.[[RequestedModules]], do + for required in agent[module].cyclic.requested_modules.iter() { + // i. If module.[[LoadedModules]] contains a Record whose [[Specifier]] is required, then + let record = agent[module] + .cyclic + .loaded_modules + .iter() + .find(|record| record.specifier == required); + // 1. Let record be that Record. + if let Some(record) = record { + // 2. Perform InnerModuleLoading(state, record.[[Module]]). + inner_module_loading(agent, state, record.module); + } else { + // ii. Else, + // 1. Perform HostLoadImportedModule(module, required, state.[[HostDefined]], state). + agent.host_hooks.host_load_imported_module( + // agent, + (), // module, + &required, + state.host_defined, + (), // state + ); + // 2. NOTE: HostLoadImportedModule will call FinishLoadingImportedModule, + // which re-enters the graph loading process through ContinueModuleLoading. + } + // iii. If state.[[IsLoading]] is false, return unused. + if !state.is_loading { + return; + } + } + } + // 3. Assert: state.[[PendingModulesCount]] ≥ 1. + assert!(state.pending_modules_count >= 1); + // 4. Set state.[[PendingModulesCount]] to state.[[PendingModulesCount]] - 1. + state.pending_modules_count -= 1; + // 5. If state.[[PendingModulesCount]] = 0, then + if state.pending_modules_count == 0 { + // a. Set state.[[IsLoading]] to false. + state.is_loading = false; + // b. For each Cyclic Module Record loaded of state.[[Visited]], do + for _loaded in state.visited { + // TODO: i. If loaded.[[Status]] is new, set loaded.[[Status]] to unlinked. + } + // c. Perform ! Call(state.[[PromiseCapability]].[[Resolve]], undefined, « undefined »). + // call_function(agent, state.promise_capability.resolve, Value::Undefined, Some(ArgumentsList(&[Value::Undefined]))); + } + // 6. Return unused. +} + +/// ### [16.2.1.5.1.2 ContinueModuleLoading ( state, moduleCompletion )](https://tc39.es/ecma262/#sec-ContinueModuleLoading) +/// +/// The abstract operation ContinueModuleLoading takes arguments state (a +/// GraphLoadingState Record) and moduleCompletion (either a normal completion +/// containing a Module Record or a throw completion) and returns unused. It is +/// used to re-enter the loading process after a call to +/// HostLoadImportedModule. +fn continue_module_loading( + agent: &mut Agent, + state: &mut GraphLoadingStateRecord, + module_completion: JsResult, +) { + // 1. If state.[[IsLoading]] is false, return unused. + if !state.is_loading { + return; + } + match module_completion { + // 2. If moduleCompletion is a normal completion, then + // a. Perform InnerModuleLoading(state, moduleCompletion.[[Value]]). + Ok(module) => inner_module_loading(agent, state, module), + // 3. Else, + Err(thrown_value) => { + // a. Set state.[[IsLoading]] to false. + state.is_loading = false; + // b. Perform ! Call(state.[[PromiseCapability]].[[Reject]], undefined, « moduleCompletion.[[Value]] »). + // call_function(state.promise_capability.reject, Value::Undefined, Some(ArgumentsList(&[thrown_value]))); + } + } + // 4. Return unused. +} + +/// ### [16.2.1.5.2 Link ( )](https://tc39.es/ecma262/#sec-moduledeclarationlinking) +/// +/// The Link concrete method of a Cyclic Module Record module takes no +/// arguments and returns either a normal completion containing unused or a +/// throw completion. On success, Link transitions this module's \[\[Status\]\] +/// from unlinked to linked. On failure, an exception is thrown and this +/// module's \[\[Status\]\] remains unlinked. (Most of the work is done by the +/// auxiliary function InnerModuleLinking.) +fn link(agent: &mut Agent, module: Module) -> JsResult<()> { + // 1. Assert: module.[[Status]] is one of unlinked, linked, evaluating-async, or evaluated. + assert!(matches!( + agent[module].cyclic.status, + CyclicModuleRecordStatus::Linked + | CyclicModuleRecordStatus::EvaluatingAsync + | CyclicModuleRecordStatus::Evaluated(_) + )); + // 2. Let stack be a new empty List. + let mut stack = vec![]; + // 3. Let result be Completion(InnerModuleLinking(module, stack, 0)). + let result = inner_module_linking(agent, module, &mut stack, 0); + match result { + // 4. If result is an abrupt completion, then + Err(result) => { + // a. For each Cyclic Module Record m of stack, do + for m in stack { + // i. Assert: m.[[Status]] is linking. + assert!(matches!( + agent[m].cyclic.status, + CyclicModuleRecordStatus::Linking(_, _) + )); + // ii. Set m.[[Status]] to unlinked. + agent[m].cyclic.status = CyclicModuleRecordStatus::Unlinked; + } + // b. Assert: module.[[Status]] is unlinked. + assert_eq!( + agent[module].cyclic.status, + CyclicModuleRecordStatus::Unlinked + ); + // c. Return ? result. + return Err(result); + } + Ok(_) => {} + } + // 5. Assert: module.[[Status]] is one of linked, evaluating-async, or evaluated. + assert!(matches!( + agent[module].cyclic.status, + CyclicModuleRecordStatus::Linked + | CyclicModuleRecordStatus::EvaluatingAsync + | CyclicModuleRecordStatus::Evaluated(_) + )); + // 6. Assert: stack is empty. + assert!(stack.is_empty()); + // 7. Return unused. + Ok(()) +} + +/// ### [16.2.1.5.2.1 InnerModuleLinking ( module, stack, index )](https://tc39.es/ecma262/#sec-InnerModuleLinking) +/// +/// The abstract operation InnerModuleLinking takes arguments module (a Module +/// Record), stack (a List of Cyclic Module Records), and index (a non-negative +/// integer) and returns either a normal completion containing a non-negative +/// integer or a throw completion. It is used by Link to perform the actual +/// linking process for module, as well as recursively on all other modules in +/// the dependency graph. The stack and index parameters, as well as a module's +/// \[\[DFSIndex\]\] and \[\[DFSAncestorIndex\]\] fields, keep track of the +/// depth-first search (DFS) traversal. In particular, \[\[DFSAncestorIndex\]\] +/// is used to discover strongly connected components (SCCs), such that all +/// modules in an SCC transition to linked together. +fn inner_module_linking( + agent: &mut Agent, + module: Module, + stack: &mut Vec, + index: u16, +) -> JsResult { + // 1. If module is not a Cyclic Module Record, then + if !module.is_cyclic_module_record() { + // a. Perform ? module.Link(). + module.link(agent)?; + // b. Return index. + return Ok(index); + } + // 2. If module.[[Status]] is one of linking, linked, evaluating-async, or + // evaluated, then + match agent[module].cyclic.status { + CyclicModuleRecordStatus::Linking(_, _) + | CyclicModuleRecordStatus::Linked + | CyclicModuleRecordStatus::EvaluatingAsync => { + // a. Return index. + return Ok(index); + } + _ => {} + } + // 3. Assert: module.[[Status]] is unlinked. + assert_eq!( + agent[module].cyclic.status, + CyclicModuleRecordStatus::Unlinked + ); + // 4. Set module.[[Status]] to linking. + // 5. Set module.[[DFSIndex]] to index. + // 6. Set module.[[DFSAncestorIndex]] to index. + agent[module].cyclic.status = + CyclicModuleRecordStatus::Linking(DFSIndex::new(index), DFSAncestorIndex::new(index)); + // 7. Set index to index + 1. + let mut index = index + 1; + // 8. Append module to stack. + stack.push(module); + // 9. For each String required of module.[[RequestedModules]], do + for required in agent[module].cyclic.requested_modules.iter() { + // a. Let requiredModule be GetImportedModule(module, required). + let required_module = get_imported_module(agent, module, *required); + // b. Set index to ? InnerModuleLinking(requiredModule, stack, index). + index = inner_module_linking(agent, required_module, stack, index)?; + // c. If requiredModule is a Cyclic Module Record, then + if required_module.is_cyclic_module_record() { + // i. Assert: requiredModule.[[Status]] is one of linking, linked, evaluating-async, or evaluated. + assert!(matches!( + agent[required_module].cyclic.status, + CyclicModuleRecordStatus::Linked + | CyclicModuleRecordStatus::EvaluatingAsync + | CyclicModuleRecordStatus::Evaluated(_) + )); + // ii. Assert: requiredModule.[[Status]] is linking if and only if stack contains requiredModule. + assert_eq!( + matches!( + agent[required_module].cyclic.status, + CyclicModuleRecordStatus::Linking(_, _) + ), + stack.contains(&required_module) + ); + // iii. If requiredModule.[[Status]] is linking, then + if let CyclicModuleRecordStatus::Linking(_, ancestor_index) = + agent[required_module].cyclic.status + { + assert!(matches!( + agent[module].cyclic.status, + CyclicModuleRecordStatus::Evaluating(_, _) + )); + // 1. Set module.[[DFSAncestorIndex]] to min(module.[[DFSAncestorIndex]], requiredModule.[[DFSAncestorIndex]]). + match &mut agent[module].cyclic.status { + CyclicModuleRecordStatus::Evaluating(_, module_ancestor_index) => { + let min = module_ancestor_index.value().min(ancestor_index.value()); + *module_ancestor_index = DFSAncestorIndex::new(min); + } + _ => unreachable!(), + } + } + } + } + // 10. Perform ? module.InitializeEnvironment(). + module.initialize_environment(agent); + // 11. Assert: module occurs exactly once in stack. + assert_eq!( + stack + .iter() + .filter(|entry| **entry == module) + .count(), + 1 + ); + // 12. Assert: module.[[DFSAncestorIndex]] ≤ module.[[DFSIndex]]. + match &mut agent[module].cyclic.status { + CyclicModuleRecordStatus::Evaluating(index, ancestor_index) => { + assert!(ancestor_index.value() <= index.value()); + // 13. If module.[[DFSAncestorIndex]] = module.[[DFSIndex]], then + if ancestor_index.value() == index.value() { + // a. Let done be false. + let mut done = false; + // b. Repeat, while done is false, + while !done { + // i. Let requiredModule be the last element of stack. + // ii. Remove the last element of stack. + let required_module = stack.pop().unwrap(); + // iii. Assert: requiredModule is a Cyclic Module Record. + assert!(required_module.is_cyclic_module_record()); + // iv. Set requiredModule.[[Status]] to linked. + agent[required_module].cyclic.status = CyclicModuleRecordStatus::Linked; + // v. If requiredModule and module are the same Module Record, set done to true. + if required_module == module { + done = true; + } + } + } + } + _ => unreachable!(), + } + // 14. Return index. + Ok(index) +} + +/// ### [16.2.1.5.3 Evaluate ( )]() +/// +/// The Evaluate concrete method of a Cyclic Module Record module takes no +/// arguments and returns a Promise. Evaluate transitions this module's +/// \[\[Status\]\] from linked to either evaluating-async or evaluated. The +/// first time it is called on a module in a given strongly connected +/// component, Evaluate creates and returns a Promise which resolves when the +/// module has finished evaluating. This Promise is stored in the +/// \[\[TopLevelCapability\]\] field of the \[\[CycleRoot\]\] for the +/// component. Future invocations of Evaluate on any module in the component +/// return the same Promise. (Most of the work is done by the auxiliary +/// function InnerModuleEvaluation.) +pub(crate) fn evaluate(agent: &mut Agent, mut module: Module) -> Promise { + // 1. Assert: This call to Evaluate is not happening at the same time as another call to Evaluate within the surrounding agent. + // TODO: How to figure this one out? + // 2. Assert: module.[[Status]] is one of linked, evaluating-async, or evaluated. + assert!(matches!( + agent[module].cyclic.status, + CyclicModuleRecordStatus::Linked + | CyclicModuleRecordStatus::EvaluatingAsync + | CyclicModuleRecordStatus::Evaluated(_) + )); + match agent[module].cyclic.status { + CyclicModuleRecordStatus::Linked => {} + // 3. If module.[[Status]] is either evaluating-async or evaluated, set module to module.[[CycleRoot]]. + CyclicModuleRecordStatus::EvaluatingAsync | CyclicModuleRecordStatus::Evaluated(_) => { + module = agent[module].cyclic.cycle_root.unwrap(); + } + _ => unreachable!(), + } + // 4. If module.[[TopLevelCapability]] is not empty, then + if let Some(tlc) = agent[module].cyclic.top_level_capability { + // a. Return module.[[TopLevelCapability]].[[Promise]]. + todo!(); + } + // 5. Let stack be a new empty List. + let mut stack = vec![]; + // 6. Let capability be ! NewPromiseCapability(%Promise%). + let capability = (); // new_promise_capability(); + // 7. Set module.[[TopLevelCapability]] to capability. + agent[module].cyclic.top_level_capability = Some(capability); + // 8. Let result be Completion(InnerModuleEvaluation(module, stack, 0)). + let result = inner_module_evaluation(agent, module, &mut stack, 0); + // 9. If result is an abrupt completion, then + if result.is_err() { + let result_value = result.err().unwrap(); + // a. For each Cyclic Module Record m of stack, do + for m in stack { + // i. Assert: m.[[Status]] is evaluating. + assert!(matches!( + agent[m].cyclic.status, + CyclicModuleRecordStatus::Evaluating(_, _) + )); + // ii. Set m.[[Status]] to evaluated. + // iii. Set m.[[EvaluationError]] to result. + agent[m].cyclic.status = + CyclicModuleRecordStatus::Evaluated(Some(EvaluationError(result_value))); + } + // b. Assert: module.[[Status]] is evaluated. + // c. Assert: module.[[EvaluationError]] and result are the same Completion Record. + assert_eq!( + agent[module].cyclic.status, + CyclicModuleRecordStatus::Evaluated(Some(EvaluationError(result_value))) + ); + // d. Perform ! Call(capability.[[Reject]], undefined, « result.[[Value]] »). + // call_function(agent, capability.reject, Value::Undefined, Some(ArgumentsList(&[result_value.0]))).unwrap(); + } else { + // 10. Else, + // a. Assert: module.[[Status]] is either evaluating-async or evaluated. + // b. Assert: module.[[EvaluationError]] is empty. + assert!(matches!( + agent[module].cyclic.status, + CyclicModuleRecordStatus::EvaluatingAsync | CyclicModuleRecordStatus::Evaluated(None) + )); + // c. If module.[[AsyncEvaluation]] is false, then + if !agent[module].cyclic.async_evaluation { + // i. Assert: module.[[Status]] is evaluated. + assert_eq!( + agent[module].cyclic.status, + CyclicModuleRecordStatus::Evaluated(None) + ); + } + // ii. Perform ! Call(capability.[[Resolve]], undefined, « undefined »). + // call_function(agent, capability.resolve, Value::Undefined, Some(ArgumentsList(&[Value::Undefined]))); + // d. Assert: stack is empty. + assert!(stack.is_empty()); + } + // 11. Return capability.[[Promise]]. + todo!(); +} + +/// ### [16.2.1.5.3.1 InnerModuleEvaluation ( module, stack, index )]() +/// +/// The abstract operation InnerModuleEvaluation takes arguments module (a +/// Module Record), stack (a List of Cyclic Module Records), and index (a +/// non-negative integer) and returns either a normal completion containing a +/// non-negative integer or a throw completion. It is used by Evaluate to +/// perform the actual evaluation process for module, as well as recursively on +/// all other modules in the dependency graph. The stack and index parameters, +/// as well as module's \[\[DFSIndex\]\] and \[\[DFSAncestorIndex\]\] fields, +/// are used the same way as in InnerModuleLinking. +pub(crate) fn inner_module_evaluation( + agent: &mut Agent, + module: Module, + stack: &mut Vec, + index: u16, +) -> JsResult { + // 1. If module is not a Cyclic Module Record, then + if module.is_cyclic_module_record() { + // a. Let promise be ! module.Evaluate(). + let promise = module.evaluate(agent).unwrap(); + // b. Assert: promise.[[PromiseState]] is not pending. + // c. If promise.[[PromiseState]] is rejected, then + let is_rejected = false; + if is_rejected { + // i. Return ThrowCompletion(promise.[[PromiseResult]]). + return Err(JsError(Value::Undefined)); + } + // d. Return index. + return Ok(index); + } + let module_borrow = &agent[module]; + // 2. If module.[[Status]] is either evaluating-async or evaluated, then + match module_borrow.cyclic.status { + // a. If module.[[EvaluationError]] is empty, return index. + // b. Otherwise, return ? module.[[EvaluationError]]. + CyclicModuleRecordStatus::EvaluatingAsync => { + return Ok(index); + } + CyclicModuleRecordStatus::Evaluated(maybe_evaluation_error) => match maybe_evaluation_error + { + Some(error) => Err(error.0), + None => { + return Ok(index); + } + }, + CyclicModuleRecordStatus::Evaluating(_, _) => { + // 3. If module.[[Status]] is evaluating, return index. + Ok(index) + } + CyclicModuleRecordStatus::Linked => { + // 5. Set module.[[Status]] to evaluating. + // 6. Set module.[[DFSIndex]] to index. + // 7. Set module.[[DFSAncestorIndex]] to index. + module_borrow.cyclic.status = CyclicModuleRecordStatus::Evaluating( + DFSIndex::new(index), + DFSAncestorIndex::new(index), + ); + // 8. Set module.[[PendingAsyncDependencies]] to 0. + module_borrow.cyclic.pending_async_dependencies = Some(0); + // 9. Set index to index + 1. + let mut index = index + 1; + // 10. Append module to stack. + stack.push(module); + // 11. For each String required of module.[[RequestedModules]], do + for required in module_borrow.cyclic.requested_modules.iter() { + // a. Let requiredModule be GetImportedModule(module, required). + let mut required_module = get_imported_module(agent, module, *required); + // b. Set index to ? InnerModuleEvaluation(requiredModule, stack, index). + index = inner_module_evaluation(agent, required_module, stack, index)?; + // c. If requiredModule is a Cyclic Module Record, then + if required_module.is_cyclic_module_record() { + let required_module_borrow = &agent[required_module]; + let stack_contains_required_module = stack.contains(&required_module); + match required_module_borrow.cyclic.status { + CyclicModuleRecordStatus::Evaluating(_, required_module_ancestor_index) => { + // ii. Assert: requiredModule.[[Status]] is evaluating if and only if stack contains requiredModule. + assert!(stack_contains_required_module); + // iii. If requiredModule.[[Status]] is evaluating, then + // 1. Set module.[[DFSAncestorIndex]] to min(module.[[DFSAncestorIndex]], requiredModule.[[DFSAncestorIndex]]). + let module_borrow = &mut agent[module]; + match &mut module_borrow.cyclic.status { + CyclicModuleRecordStatus::Evaluating(_, ancestor_index) => { + *ancestor_index = DFSAncestorIndex::new( + ancestor_index + .value() + .min(required_module_ancestor_index.value()), + ); + } + _ => unreachable!(), + } + } + // iv. Else, + CyclicModuleRecordStatus::EvaluatingAsync => { + assert!(!stack_contains_required_module); + // 1. Set requiredModule to requiredModule.[[CycleRoot]]. + required_module = required_module_borrow.cyclic.cycle_root.unwrap(); + let required_module_borrow = &agent[required_module]; + match required_module_borrow.cyclic.status { + CyclicModuleRecordStatus::EvaluatingAsync => { + // v. If requiredModule.[[AsyncEvaluation]] is true, then + if required_module_borrow.cyclic.async_evaluation { + // 1. Set module.[[PendingAsyncDependencies]] to module.[[PendingAsyncDependencies]] + 1. + agent[module] + .cyclic + .pending_async_dependencies + .as_mut() + .map(|val| *val += 1); + // 2. Append module to requiredModule.[[AsyncParentModules]]. + required_module_borrow + .cyclic + .async_parent_modules + .push(module); + } + } + CyclicModuleRecordStatus::Evaluated(maybe_evaluation_error) => { + // 3. If requiredModule.[[EvaluationError]] is not empty, + if let Some(evaluation_error) = maybe_evaluation_error { + // return ? requiredModule.[[EvaluationError]]. + return Err(evaluation_error.0); + } + // v. If requiredModule.[[AsyncEvaluation]] is true, then + if required_module_borrow.cyclic.async_evaluation { + // 1. Set module.[[PendingAsyncDependencies]] to module.[[PendingAsyncDependencies]] + 1. + agent[module] + .cyclic + .pending_async_dependencies + .as_mut() + .map(|val| *val += 1); + // 2. Append module to requiredModule.[[AsyncParentModules]]. + required_module_borrow + .cyclic + .async_parent_modules + .push(module); + } + } + // 2. Assert: requiredModule.[[Status]] is either evaluating-async or evaluated. + _ => unreachable!(), + } + } + CyclicModuleRecordStatus::Evaluated(_) => { + assert!(!stack_contains_required_module); + // 1. Set requiredModule to requiredModule.[[CycleRoot]]. + required_module = required_module_borrow.cyclic.cycle_root.unwrap(); + let required_module_borrow = &agent[required_module]; + match required_module_borrow.cyclic.status { + CyclicModuleRecordStatus::EvaluatingAsync => { + // v. If requiredModule.[[AsyncEvaluation]] is true, then + if required_module_borrow.cyclic.async_evaluation { + // 1. Set module.[[PendingAsyncDependencies]] to module.[[PendingAsyncDependencies]] + 1. + agent[module] + .cyclic + .pending_async_dependencies + .as_mut() + .map(|val| *val += 1); + // 2. Append module to requiredModule.[[AsyncParentModules]]. + required_module_borrow + .cyclic + .async_parent_modules + .push(module); + } + } + CyclicModuleRecordStatus::Evaluated(maybe_evaluation_error) => { + // 3. If requiredModule.[[EvaluationError]] is not empty, + if let Some(evaluation_error) = maybe_evaluation_error { + // return ? requiredModule.[[EvaluationError]]. + return Err(evaluation_error.0); + } + // v. If requiredModule.[[AsyncEvaluation]] is true, then + if required_module_borrow.cyclic.async_evaluation { + // 1. Set module.[[PendingAsyncDependencies]] to module.[[PendingAsyncDependencies]] + 1. + agent[module] + .cyclic + .pending_async_dependencies + .as_mut() + .map(|val| *val += 1); + // 2. Append module to requiredModule.[[AsyncParentModules]]. + required_module_borrow + .cyclic + .async_parent_modules + .push(module); + } + } + // 2. Assert: requiredModule.[[Status]] is either evaluating-async or evaluated. + _ => unreachable!(), + } + } + // i. Assert: requiredModule.[[Status]] is one of evaluating, evaluating-async, or evaluated. + _ => unreachable!(), + } + } + } + // 12. If module.[[PendingAsyncDependencies]] > 0 or module.[[HasTLA]] is true, then + if agent[module].cyclic.pending_async_dependencies.unwrap() > 0 + || agent[module].cyclic.has_top_level_await + { + // a. Assert: module.[[AsyncEvaluation]] is false and was never previously set to true. + assert!(agent[module].cyclic.async_evaluation); + // b. Set module.[[AsyncEvaluation]] to true. + agent[module].cyclic.async_evaluation = true; + // c. NOTE: The order in which module records have their + // [[AsyncEvaluation]] fields transition to true is + // significant. (See 16.2.1.5.3.4.) + // d. If module.[[PendingAsyncDependencies]] = 0, + if agent[module].cyclic.pending_async_dependencies == Some(0) { + // perform ExecuteAsyncModule(module). + execute_async_module(agent, module); + } + } else { + // 13. Else, + // a. Perform ? module.ExecuteModule(). + module.execute_module(agent, None); + } + // 14. Assert: module occurs exactly once in stack. + assert_eq!( + stack + .iter() + .filter(|entry| **entry == module) + .count(), + 1 + ); + let module_borrow = &agent[module]; + match module_borrow.cyclic.status { + CyclicModuleRecordStatus::Evaluating(index, ancestor_index) => { + // 15. Assert: module.[[DFSAncestorIndex]] ≤ module.[[DFSIndex]]. + assert!(ancestor_index.value() <= index.value()); + // 16. If module.[[DFSAncestorIndex]] = module.[[DFSIndex]], then + if ancestor_index.value() == index.value() { + // a. Let done be false. + let mut done = false; + while !done { + // b. Repeat, while done is false, + // i. Let requiredModule be the last element of stack. + // ii. Remove the last element of stack. + let required_module = stack.pop().unwrap(); + // iii. Assert: requiredModule is a Cyclic Module Record. + assert!(required_module.is_cyclic_module_record()); + // iv. If requiredModule.[[AsyncEvaluation]] is false, set requiredModule.[[Status]] to evaluated. + if !agent[required_module].cyclic.async_evaluation { + agent[required_module].cyclic.status = + CyclicModuleRecordStatus::Evaluated(None); + } else { + // v. Otherwise, set requiredModule.[[Status]] to evaluating-async. + agent[required_module].cyclic.status = + CyclicModuleRecordStatus::EvaluatingAsync; + } + // vi. If requiredModule and module are the same Module Record, set done to true. + if required_module == module { + done = true; + } + // vii. Set requiredModule.[[CycleRoot]] to module. + agent[required_module].cyclic.cycle_root = Some(module); + } + } + } + _ => unreachable!(), + } + // 17. Return index. + Ok(index) + } + // 4. Assert: module.[[Status]] is linked. + _ => unreachable!(), + } + + // Note 1 + + // A module is evaluating while it is being traversed by + // InnerModuleEvaluation. A module is evaluated on execution completion or + // evaluating-async during execution if its [[HasTLA]] field is true or if + // it has asynchronous dependencies. + // Note 2 + + // Any modules depending on a module of an asynchronous cycle when that + // cycle is not evaluating will instead depend on the execution of the root + // of the cycle via [[CycleRoot]]. This ensures that the cycle state can be + // treated as a single strongly connected component through its root module + // state. +} + +/// ### [16.2.1.5.3.2 ExecuteAsyncModule ( module )]() +/// +/// The abstract operation ExecuteAsyncModule takes argument module (a Cyclic +/// Module Record) and returns unused. +pub(crate) fn execute_async_module(agent: &mut Agent, module: Module) { + let module_borrow = &agent[module]; + // 1. Assert: module.[[Status]] is either evaluating or evaluating-async. + assert!(matches!( + module_borrow.cyclic.status, + CyclicModuleRecordStatus::Evaluating(_, _) | CyclicModuleRecordStatus::EvaluatingAsync + )); + // 2. Assert: module.[[HasTLA]] is true. + assert!(module_borrow.cyclic.has_top_level_await); + // 3. Let capability be ! NewPromiseCapability(%Promise%). + let capability = (); // new_promise_capability(agent, ProtoIntrinsics::Promise); + // 4. Let fulfilledClosure be a new Abstract Closure with no parameters that captures module and performs the following steps when called: + // a. Perform AsyncModuleExecutionFulfilled(module). + // b. Return undefined. + // 5. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 0, "", « »). + let on_fulfilled = (); + // 6. Let rejectedClosure be a new Abstract Closure with parameters (error) that captures module and performs the following steps when called: + // a. Perform AsyncModuleExecutionRejected(module, error). + // b. Return undefined. + // 7. Let onRejected be CreateBuiltinFunction(rejectedClosure, 0, "", « »). + let on_rejected = (); + // 8. Perform PerformPromiseThen(capability.[[Promise]], onFulfilled, onRejected). + perform_promise_then(capability.promise, on_fulfilled, on_rejected); + // 9. Perform ! module.ExecuteModule(capability). + module.execute_module(agent, Some(capability)); + // 10. Return unused. +} + +fn fulfilled_closure( + agent: &mut Agent, + this_value: Value, + arguments: Option, + module: Module, +) -> JsResult { + // a. Perform AsyncModuleExecutionFulfilled(module). + async_module_execution_fulfilled(agent, module); + // b. Return undefined. + Ok(Value::Undefined) +} + +fn rejected_closure( + agent: &mut Agent, + this_value: Value, + arguments: Option, + module: Module, +) -> JsResult { + async_module_execution_rejected(agent, module, arguments.unwrap().get(0)); + Ok(Value::Undefined) +} + +/// ### [16.2.1.5.3.3 GatherAvailableAncestors ( module, execList )]() +/// +/// The abstract operation GatherAvailableAncestors takes arguments module (a +/// Cyclic Module Record) and execList (a List of Cyclic Module Records) and +/// returns unused. +pub(crate) fn gather_available_ancestors( + agent: &mut Agent, + module: Module, + exec_list: &mut Vec, +) { + // 1. For each Cyclic Module Record m of module.[[AsyncParentModules]], do + for m in agent[module].cyclic.async_parent_modules { + // a. If execList does not contain m and m.[[CycleRoot]].[[EvaluationError]] is empty, then + if !exec_list.contains(&m) + && !matches!( + agent[agent[m].cyclic.cycle_root.unwrap()].cyclic.status, + CyclicModuleRecordStatus::Evaluated(Some(_)) + ) + { + // i. Assert: m.[[Status]] is evaluating-async. + // ii. Assert: m.[[EvaluationError]] is empty. + assert!(matches!( + agent[m].cyclic.status, + CyclicModuleRecordStatus::EvaluatingAsync + )); + // iii. Assert: m.[[AsyncEvaluation]] is true. + assert!(agent[m].cyclic.async_evaluation); + // iv. Assert: m.[[PendingAsyncDependencies]] > 0. + assert!(agent[m].cyclic.pending_async_dependencies.unwrap() > 0); + // v. Set m.[[PendingAsyncDependencies]] to m.[[PendingAsyncDependencies]] - 1. + agent[m] + .cyclic + .pending_async_dependencies + .as_mut() + .map(|val| *val -= 1); + // vi. If m.[[PendingAsyncDependencies]] = 0, then + if agent[m].cyclic.pending_async_dependencies == Some(0) { + // 1. Append m to execList. + exec_list.push(m); + } + } + // 2. If m.[[HasTLA]] is false, perform GatherAvailableAncestors(m, execList). + if !agent[m].cyclic.has_top_level_await { + gather_available_ancestors(agent, m, exec_list); + } + } + // 2. Return unused. + + // Note + + // When an asynchronous execution for a root module is fulfilled, this + // function determines the list of modules which are able to synchronously + // execute together on this completion, populating them in execList. +} + +/// ### [16.2.1.5.3.4 AsyncModuleExecutionFulfilled ( module )]() +/// +/// The abstract operation AsyncModuleExecutionFulfilled takes argument module +/// // (a Cyclic Module Record) and returns unused. +pub(crate) fn async_module_execution_fulfilled(agent: &mut Agent, module: Module) { + let module_borrow = &agent[module].cyclic; + // 1. If module.[[Status]] is evaluated, then + match module_borrow.status { + CyclicModuleRecordStatus::Evaluated(maybe_evaluation_error) => { + // a. Assert: module.[[EvaluationError]] is not empty. + assert!(maybe_evaluation_error.is_none()); + // b. Return unused. + return; + } + // 2. Assert: module.[[Status]] is evaluating-async. + CyclicModuleRecordStatus::EvaluatingAsync => {} + _ => unreachable!(), + } + // 3. Assert: module.[[AsyncEvaluation]] is true. + assert!(module_borrow.async_evaluation); + // 4. Assert: module.[[EvaluationError]] is empty. + // 5. Set module.[[AsyncEvaluation]] to false. + module_borrow.async_evaluation = false; + // 6. Set module.[[Status]] to evaluated. + module_borrow.status = CyclicModuleRecordStatus::Evaluated(None); + // 7. If module.[[TopLevelCapability]] is not empty, then + if module_borrow.top_level_capability.is_some() { + // a. Assert: module.[[CycleRoot]] and module are the same Module Record. + assert_eq!(module_borrow.cycle_root.unwrap(), module); + // b. Perform ! Call(module.[[TopLevelCapability]].[[Resolve]], undefined, « undefined »). + // call_function(agent, module_borrow.top_level_capability.unwrap().resolve, Value::Undefined, Some(ArgumentsList(&[Value::Undefined]))).unwrap(); + } + // 8. Let execList be a new empty List. + let mut exec_list = Vec::with_capacity(module_borrow.async_parent_modules.len()); + // 9. Perform GatherAvailableAncestors(module, execList). + gather_available_ancestors(agent, module, &mut exec_list); + // 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. + // TODO: exec_list.sort(); + // 11. Assert: All elements of sortedExecList have their + // [[AsyncEvaluation]] field set to true, [[PendingAsyncDependencies]] + // field set to 0, and [[EvaluationError]] field set to empty. + for element in exec_list { + assert!(agent[element].cyclic.async_evaluation); + assert_eq!(agent[element].cyclic.pending_async_dependencies, Some(0)); + assert!(!matches!( + agent[element].cyclic.status, + CyclicModuleRecordStatus::Evaluated(Some(_)) + )); + } + // 12. For each Cyclic Module Record m of sortedExecList, do + for m in exec_list { + // a. If m.[[Status]] is evaluated, then + if let CyclicModuleRecordStatus::Evaluated(maybe_evaluation_error) = agent[m].cyclic.status + { + // i. Assert: m.[[EvaluationError]] is not empty. + assert!(maybe_evaluation_error.is_none()); + } else if agent[m].cyclic.has_top_level_await { + // b. Else if m.[[HasTLA]] is true, then + // i. Perform ExecuteAsyncModule(m). + execute_async_module(agent, m); + } else { + // c. Else, + // i. Let result be m.ExecuteModule(). + let result = m.execute_module(agent, None); + match result { + // ii. If result is an abrupt completion, then + Err(error) => { + // 1. Perform AsyncModuleExecutionRejected(m, result.[[Value]]). + async_module_execution_rejected(agent, m, error); + } + // iii. Else, + Ok(_) => { + // 1. Set m.[[Status]] to evaluated. + agent[m].cyclic.status = CyclicModuleRecordStatus::Evaluated(None); + // 2. If m.[[TopLevelCapability]] is not empty, then + if agent[m].cyclic.top_level_capability.is_some() { + // a. Assert: m.[[CycleRoot]] and m are the same Module Record. + assert_eq!(agent[m].cyclic.cycle_root.unwrap(), m); + // b. Perform ! Call(m.[[TopLevelCapability]].[[Resolve]], undefined, « undefined »). + // call_function(agent[m].module.top_level_capability.unwrap().resolve, Value::Undefined, Some(ArgumentsList(&[Value::Undefined]))); + } + } + } + } + } + // 13. Return unused. +} + +/// ### [16.2.1.5.3.5 AsyncModuleExecutionRejected ( module, error )]() +/// +/// The abstract operation AsyncModuleExecutionRejected takes arguments module +/// (a Cyclic Module Record) and error (an ECMAScript language value) and +/// returns unused. +pub(crate) fn async_module_execution_rejected(agent: &mut Agent, module: Module, error: Value) { + // 1. If module.[[Status]] is evaluated, then + if let CyclicModuleRecordStatus::Evaluated(maybe_evaluation_error) = agent[module].cyclic.status + { + // a. Assert: module.[[EvaluationError]] is not empty. + assert!(maybe_evaluation_error.is_some()); + // b. Return unused. + return; + } + // 2. Assert: module.[[Status]] is evaluating-async. + // 4. Assert: module.[[EvaluationError]] is empty. + assert_eq!( + agent[module].cyclic.status, + CyclicModuleRecordStatus::EvaluatingAsync + ); + // 3. Assert: module.[[AsyncEvaluation]] is true. + assert!(agent[module].cyclic.async_evaluation); + // 5. Set module.[[EvaluationError]] to ThrowCompletion(error). + // 6. Set module.[[Status]] to evaluated. + agent[module].cyclic.status = + CyclicModuleRecordStatus::Evaluated(Some(EvaluationError(JsError(error)))); + // 7. For each Cyclic Module Record m of module.[[AsyncParentModules]], do + for m in agent[module].cyclic.async_parent_modules { + // a. Perform AsyncModuleExecutionRejected(m, error). + async_module_execution_rejected(agent, m, error); + } + // 8. If module.[[TopLevelCapability]] is not empty, then + if agent[module].cyclic.top_level_capability.is_some() { + // a. Assert: module.[[CycleRoot]] and module are the same Module Record. + assert_eq!(agent[module].cyclic.cycle_root.unwrap(), module); + // b. Perform ! Call(module.[[TopLevelCapability]].[[Reject]], undefined, « error »). + // call_function(agent, agent[module].module.top_level_capability.unwrap().reject, Value::Undefined, Some(ArgumentsList(&[Value::Undefined]))); + } + // 9. Return unused. +} diff --git a/nova_vm/src/ecmascript/builtins/module/data.rs b/nova_vm/src/ecmascript/builtins/module/data.rs index 172f42c08..0941748ef 100644 --- a/nova_vm/src/ecmascript/builtins/module/data.rs +++ b/nova_vm/src/ecmascript/builtins/module/data.rs @@ -9,73 +9,16 @@ use crate::{ heap::indexes::{ObjectIndex, StringIndex}, }; -use super::Module; +use super::{ + abstract_module_records::ModuleRecord, cyclic_module_records::CyclicModuleRecord, + source_text_module_records::SourceTextModuleRecord, Module, +}; -#[derive(Debug, Clone)] +#[derive(Debug)] pub struct ModuleHeapData { pub(crate) object_index: Option, - pub(crate) module: ModuleRecord, + pub(crate) r#abstract: ModuleRecord, + pub(crate) cyclic: CyclicModuleRecord, + pub(crate) source_text: SourceTextModuleRecord, pub(crate) exports: Box<[String]>, } - -#[derive(Debug, Clone, Copy)] -pub(crate) struct ModuleRecord { - /// \[\[Realm]] - /// - /// The Realm within which this module was created. - realm: RealmIdentifier, - /// \[\[Environment]] - /// - /// The Environment Record containing the top level bindings for this - /// module. This field is set when the module is linked. - pub(super) environment: Option, - /// \[\[Namespace]] - /// - /// The Module Namespace Object (28.3) if one has been created for this - /// module. - namespace: Option, - /// \[\[HostDefined]] - /// - /// Field reserved for use by host environments that need to associate - /// additional information with a module. - host_defined: (), -} - -#[derive(Debug, Clone, Copy)] -pub(crate) enum ResolvedBindingName { - String(StringIndex), - SmallString(SmallString), - Namespace, -} - -#[derive(Debug, Clone, Copy)] -pub(crate) struct ResolvedBinding { - /// \[\[Module]] - pub(super) module: Option, - /// \[\[BindingName]] - pub(super) binding_name: ResolvedBindingName, -} - -#[derive(Debug, Clone, Copy)] -pub(crate) enum ResolveExportResult { - Ambiguous, - Resolved(ResolvedBinding), -} - -impl ModuleRecord { - /// Return the binding of a name exported by this module. Bindings are - /// represented by a ResolvedBinding Record, of the form { \[\[Module]]: - /// Module Record, \[\[BindingName]]: String | NAMESPACE }. If the export - /// is a Module Namespace Object without a direct binding in any module, - /// \[\[BindingName]] will be set to NAMESPACE. Return null if the name - /// cannot be resolved, or AMBIGUOUS if multiple bindings were found. - /// - /// Each time this operation is called with a specific exportName, - /// resolveSet pair as arguments it must return the same result. - /// - /// LoadRequestedModules must have completed successfully prior to - /// invoking this method. - pub(crate) fn resolve_export(&self, _property_key: PropertyKey) -> Option { - todo!() - } -} diff --git a/nova_vm/src/ecmascript/builtins/module/semantics.rs b/nova_vm/src/ecmascript/builtins/module/semantics.rs new file mode 100644 index 000000000..f7dc7c5a9 --- /dev/null +++ b/nova_vm/src/ecmascript/builtins/module/semantics.rs @@ -0,0 +1,519 @@ +use std::ops::Deref; + +use oxc_ast::ast::{ImportDeclarationSpecifier, Program}; +use oxc_span::Atom; + +use crate::ecmascript::{ + execution::Agent, + types::{String, BUILTIN_STRING_MEMORY}, +}; + +use super::source_text_module_records::{ExportEntryRecord, ImportEntryRecord, ImportName}; + +/// ###[16.2.1.2 Static Semantics: ImportedLocalNames ( importEntries )](https://tc39.es/ecma262/#sec-importedlocalnames) +/// +/// The abstract operation ImportedLocalNames takes argument importEntries (a +/// List of ImportEntry Records) and returns a List of Strings. It creates a +/// List of all of the local name bindings defined by importEntries. +pub(crate) fn imported_local_names(import_entries: &[ImportEntryRecord]) -> Box<[String]> { + // 1. Let localNames be a new empty List. + // 2. For each ImportEntry Record i of importEntries, do + import_entries + .iter() + .map(|i| { + // a. Append i.[[LocalName]] to localNames. + i.local_name + }) + .collect() + // 3. Return localNames. +} + +/// ###[16.2.1.3 Static Semantics: ModuleRequests](https://tc39.es/ecma262/#sec-static-semantics-modulerequests) +/// +/// The syntax-directed operation ModuleRequests takes no arguments and returns a List of Strings. +pub(crate) fn module_requests(agent: &mut Agent, program: &Program<'_>) -> Box<[String]> { + let mut strings = vec![]; + // Module : [empty] + // 1. Return a new empty List. + for statement in program.body { + // ModuleItemList : ModuleItem + + // 1. Return ModuleRequests of ModuleItem. + // ModuleItemList : ModuleItemList ModuleItem + + // 1. Let moduleNames be ModuleRequests of ModuleItemList. + // 2. Let additionalNames be ModuleRequests of ModuleItem. + // 3. For each String name of additionalNames, do + // a. If moduleNames does not contain name, then + // i. Append name to moduleNames. + // 4. Return moduleNames. + match statement { + oxc_ast::ast::Statement::ModuleDeclaration(decl) => match decl.deref() { + // ImportDeclaration : import ImportClause FromClause ; + oxc_ast::ast::ModuleDeclaration::ImportDeclaration(decl) => { + // 1. Return ModuleRequests of FromClause. + // ModuleSpecifier : StringLiteral + // 1. Return a List whose sole element is the SV of StringLiteral. + strings.push(String::from_str(agent, &decl.source.value)); + } + oxc_ast::ast::ModuleDeclaration::ExportAllDeclaration(decl) => { + strings.push(String::from_str(agent, &decl.source.value)); + } + oxc_ast::ast::ModuleDeclaration::ExportNamedDeclaration(decl) => { + // ExportDeclaration : export ExportFromClause FromClause ; + if let Some(source) = &decl.source { + // 1. Return the ModuleRequests of FromClause. + strings.push(String::from_str(agent, &source.value)); + } + } + + // ExportDeclaration : + // export NamedExports ; + // export VariableStatement + // export Declaration + // export default HoistableDeclaration + // export default ClassDeclaration + // export default AssignmentExpression ; + // 1. Return a new empty List. + _ => {} + }, + // ModuleItem : StatementListItem + // 1. Return a new empty List. + _ => {} + } + } + + strings.into_boxed_slice() +} + +/// 16.2.2.2 Static Semantics: ImportEntries +/// +/// The syntax-directed operation ImportEntries takes no arguments and returns a List of ImportEntry Records. +pub fn import_entries(agent: &mut Agent, program: &Program<'_>) -> Box<[ImportEntryRecord]> { + let mut entries = vec![]; + + // Module : [empty] + // 1. Return a new empty List. + + // ModuleItemList : ModuleItemList ModuleItem + // 1. Let entries1 be ImportEntries of ModuleItemList. + // 2. Let entries2 be ImportEntries of ModuleItem. + // 3. Return the list-concatenation of entries1 and entries2. + for statement in program.body { + match statement { + oxc_ast::ast::Statement::ModuleDeclaration(decl) => { + match decl.deref() { + oxc_ast::ast::ModuleDeclaration::ImportDeclaration(decl) => { + // ImportDeclaration : import ModuleSpecifier ; + let Some(specifiers) = decl.specifiers else { + // 1. Return a new empty List. + continue; + }; + + // ImportDeclaration : import ImportClause FromClause ; + + // 1. Let module be the sole element of ModuleRequests of FromClause. + // SAFETY: The Atom refers to the Program which will be moved to the + // Heap and will be owned by the ModuleHeapData that also owns the + // ImportEntryRecords. The Program's internal data is not moved, so the + // Atom references are "safe". + let module = unsafe { + std::mem::transmute::, Atom<'static>>(decl.source.value) + }; + // 2. Return ImportEntriesForModule of ImportClause with argument module. + import_entries_for_module(agent, &specifiers, module, &mut |entry| { + entries.push(entry) + }); + } + // ModuleItem : + // ExportDeclaration + // StatementListItem + // 1. Return a new empty List. + _ => {} + } + } + _ => {} + } + } + + entries.into_boxed_slice() +} + +/// 16.2.2.3 Static Semantics: ImportEntriesForModule +/// +/// The syntax-directed operation ImportEntriesForModule takes argument module (a String) and returns a List of ImportEntry Records. +fn import_entries_for_module( + agent: &mut Agent, + specifiers: &[ImportDeclarationSpecifier<'_>], + module: Atom<'static>, + f: &mut impl FnMut(ImportEntryRecord), +) { + // ImportClause : ImportedDefaultBinding , NameSpaceImport + + // 1. Let entries1 be ImportEntriesForModule of ImportedDefaultBinding with argument module. + // 2. Let entries2 be ImportEntriesForModule of NameSpaceImport with argument module. + // 3. Return the list-concatenation of entries1 and entries2. + + // ImportClause : ImportedDefaultBinding , NamedImports + + // 1. Let entries1 be ImportEntriesForModule of ImportedDefaultBinding with argument module. + // 2. Let entries2 be ImportEntriesForModule of NamedImports with argument module. + // 3. Return the list-concatenation of entries1 and entries2. + + // ImportsList : ImportsList , ImportSpecifier + + // 1. Let specs1 be the ImportEntriesForModule of ImportsList with argument module. + // 2. Let specs2 be the ImportEntriesForModule of ImportSpecifier with argument module. + // 3. Return the list-concatenation of specs1 and specs2. + + for specifier in specifiers { + // NamedImports : { } + // 1. Return a new empty List. + match specifier { + ImportDeclarationSpecifier::ImportSpecifier(specifier) => { + // ImportSpecifier : ImportedBinding + // ImportSpecifier : ModuleExportName as ImportedBinding + + // 1. Let localName be the sole element of BoundNames of ImportedBinding. + // 1. Let importName be the StringValue of ModuleExportName. + // 2. Let localName be the StringValue of ImportedBinding. + let local_name = String::from_str(agent, &specifier.local.name); + + // 2. Let entry be the ImportEntry Record { + let entry = ImportEntryRecord { + // [[ModuleRequest]]: module, + module_request: specifier.local.name, + // [[ImportName]]: localName / importName, + import_name: BUILTIN_STRING_MEMORY.default.into(), + // [[LocalName]]: localName + local_name, + }; + // 4. Return « entry ». + f(entry); + } + ImportDeclarationSpecifier::ImportDefaultSpecifier(specifier) => { + // ImportedDefaultBinding : ImportedBinding + + // 1. Let localName be the sole element of BoundNames of ImportedBinding. + let local_name = String::from_str(agent, &specifier.local.name); + // 2. Let defaultEntry be the ImportEntry Record { + let default_entry = ImportEntryRecord { + // [[ModuleRequest]]: module, + module_request: specifier.local.name, + // [[ImportName]]: "default", + import_name: BUILTIN_STRING_MEMORY.default.into(), + // [[LocalName]]: localName + local_name, + }; + // }. + // 3. Return « defaultEntry ». + f(default_entry); + } + ImportDeclarationSpecifier::ImportNamespaceSpecifier(specifier) => { + // NameSpaceImport : * as ImportedBinding + + // 1. Let localName be the StringValue of ImportedBinding. + let local_name = String::from_str(agent, &specifier.local.name); + // 2. Let entry be the ImportEntry Record { + let entry = ImportEntryRecord { + // [[ModuleRequest]]: module, + module_request: specifier.local.name, + // [[ImportName]]: namespace-object, + import_name: ImportName::NamespaceObject, + // [[LocalName]]: localName + local_name, + }; + // 3. Return « entry ». + f(entry); + } + } + } +} + +/// ### [16.2.3.2 Static Semantics: ExportedBindings]() +/// +/// The syntax-directed operation ExportedBindings takes no arguments and returns a List of Strings. +pub(crate) fn exported_bindings() -> Box<[String]> { + let entries = vec![]; + // Note + + // ExportedBindings are the locally bound names that are explicitly associated with a Module's ExportedNames. + + // It is defined piecewise over the following productions: + // ModuleItemList : ModuleItemList ModuleItem + + // 1. Let names1 be ExportedBindings of ModuleItemList. + // 2. Let names2 be ExportedBindings of ModuleItem. + // 3. Return the list-concatenation of names1 and names2. + + // ModuleItem : + // ImportDeclaration + // StatementListItem + + // 1. Return a new empty List. + + // ExportDeclaration : + // export ExportFromClause FromClause ; + + // 1. Return a new empty List. + + // ExportDeclaration : export NamedExports ; + + // 1. Return the ExportedBindings of NamedExports. + + // ExportDeclaration : export VariableStatement + + // 1. Return the BoundNames of VariableStatement. + + // ExportDeclaration : export Declaration + + // 1. Return the BoundNames of Declaration. + + // ExportDeclaration : + // export default HoistableDeclaration + // export default ClassDeclaration + // export default AssignmentExpression ; + + // 1. Return the BoundNames of this ExportDeclaration. + + // NamedExports : { } + + // 1. Return a new empty List. + + // ExportsList : ExportsList , ExportSpecifier + + // 1. Let names1 be the ExportedBindings of ExportsList. + // 2. Let names2 be the ExportedBindings of ExportSpecifier. + // 3. Return the list-concatenation of names1 and names2. + + // ExportSpecifier : ModuleExportName + + // 1. Return a List whose sole element is the StringValue of ModuleExportName. + + // ExportSpecifier : ModuleExportName as ModuleExportName + + // 1. Return a List whose sole element is the StringValue of the first ModuleExportName. + entries.into_boxed_slice() +} + +/// ### [16.2.3.3 Static Semantics: ExportedNames]() +/// +/// The syntax-directed operation ExportedNames takes no arguments and returns a List of Strings. +pub(crate) fn exported_names() -> Box<[String]> { + let entries = vec![]; + // Note + + // ExportedNames are the externally visible names that a Module explicitly maps to one of its local name bindings. + + // It is defined piecewise over the following productions: + // ModuleItemList : ModuleItemList ModuleItem + + // 1. Let names1 be ExportedNames of ModuleItemList. + // 2. Let names2 be ExportedNames of ModuleItem. + // 3. Return the list-concatenation of names1 and names2. + + // ModuleItem : ExportDeclaration + + // 1. Return the ExportedNames of ExportDeclaration. + + // ModuleItem : + // ImportDeclaration + // StatementListItem + + // 1. Return a new empty List. + + // ExportDeclaration : export ExportFromClause FromClause ; + + // 1. Return the ExportedNames of ExportFromClause. + + // ExportFromClause : * + + // 1. Return a new empty List. + + // ExportFromClause : * as ModuleExportName + + // 1. Return a List whose sole element is the StringValue of ModuleExportName. + + // ExportFromClause : NamedExports + + // 1. Return the ExportedNames of NamedExports. + + // ExportDeclaration : export VariableStatement + + // 1. Return the BoundNames of VariableStatement. + + // ExportDeclaration : export Declaration + + // 1. Return the BoundNames of Declaration. + + // ExportDeclaration : + // export default HoistableDeclaration + // export default ClassDeclaration + // export default AssignmentExpression ; + + // 1. Return « "default" ». + + // NamedExports : { } + + // 1. Return a new empty List. + + // ExportsList : ExportsList , ExportSpecifier + + // 1. Let names1 be the ExportedNames of ExportsList. + // 2. Let names2 be the ExportedNames of ExportSpecifier. + // 3. Return the list-concatenation of names1 and names2. + + // ExportSpecifier : ModuleExportName + + // 1. Return a List whose sole element is the StringValue of ModuleExportName. + + // ExportSpecifier : ModuleExportName as ModuleExportName + + // 1. Return a List whose sole element is the StringValue of the second ModuleExportName. + entries.into_boxed_slice() +} + +/// ### [16.2.3.4 Static Semantics: ExportEntries]() +/// +/// The syntax-directed operation ExportEntries takes no arguments and returns a List of ExportEntry Records. It is defined piecewise over the following productions: +pub(crate) fn export_entries() -> Vec { + let entries = vec![]; + // Module : [empty] + + // 1. Return a new empty List. + + // ModuleItemList : ModuleItemList ModuleItem + + // 1. Let entries1 be ExportEntries of ModuleItemList. + // 2. Let entries2 be ExportEntries of ModuleItem. + // 3. Return the list-concatenation of entries1 and entries2. + + // ModuleItem : + // ImportDeclaration + // StatementListItem + + // 1. Return a new empty List. + + // ExportDeclaration : export ExportFromClause FromClause ; + + // 1. Let module be the sole element of ModuleRequests of FromClause. + // 2. Return ExportEntriesForModule of ExportFromClause with argument module. + + // ExportDeclaration : export NamedExports ; + + // 1. Return ExportEntriesForModule of NamedExports with argument null. + + // ExportDeclaration : export VariableStatement + + // 1. Let entries be a new empty List. + // 2. Let names be the BoundNames of VariableStatement. + // 3. For each element name of names, do + // a. Append the ExportEntry Record { [[ModuleRequest]]: null, [[ImportName]]: null, [[LocalName]]: name, [[ExportName]]: name } to entries. + // 4. Return entries. + + // ExportDeclaration : export Declaration + + // 1. Let entries be a new empty List. + // 2. Let names be the BoundNames of Declaration. + // 3. For each element name of names, do + // a. Append the ExportEntry Record { [[ModuleRequest]]: null, [[ImportName]]: null, [[LocalName]]: name, [[ExportName]]: name } to entries. + // 4. Return entries. + + // ExportDeclaration : export default HoistableDeclaration + + // 1. Let names be BoundNames of HoistableDeclaration. + // 2. Let localName be the sole element of names. + // 3. Return a List whose sole element is a new ExportEntry Record { [[ModuleRequest]]: null, [[ImportName]]: null, [[LocalName]]: localName, [[ExportName]]: "default" }. + + // ExportDeclaration : export default ClassDeclaration + + // 1. Let names be BoundNames of ClassDeclaration. + // 2. Let localName be the sole element of names. + // 3. Return a List whose sole element is a new ExportEntry Record { [[ModuleRequest]]: null, [[ImportName]]: null, [[LocalName]]: localName, [[ExportName]]: "default" }. + + // ExportDeclaration : export default AssignmentExpression ; + + // 1. Let entry be the ExportEntry Record { [[ModuleRequest]]: null, [[ImportName]]: null, [[LocalName]]: "*default*", [[ExportName]]: "default" }. + // 2. Return « entry ». + + // Note + + // "*default*" is used within this specification as a synthetic name for anonymous default export values. See this note for more details. + entries +} + +/// ### [16.2.3.5 Static Semantics: ExportEntriesForModule]() +/// +/// The syntax-directed operation ExportEntriesForModule takes argument module (a String or null) and returns a List of ExportEntry Records. It is defined piecewise over the following productions: +pub(crate) fn export_entries_for_module() { + // ExportFromClause : * + + // 1. Let entry be the ExportEntry Record { [[ModuleRequest]]: module, [[ImportName]]: all-but-default, [[LocalName]]: null, [[ExportName]]: null }. + // 2. Return « entry ». + + // ExportFromClause : * as ModuleExportName + + // 1. Let exportName be the StringValue of ModuleExportName. + // 2. Let entry be the ExportEntry Record { [[ModuleRequest]]: module, [[ImportName]]: all, [[LocalName]]: null, [[ExportName]]: exportName }. + // 3. Return « entry ». + + // NamedExports : { } + + // 1. Return a new empty List. + + // ExportsList : ExportsList , ExportSpecifier + + // 1. Let specs1 be the ExportEntriesForModule of ExportsList with argument module. + // 2. Let specs2 be the ExportEntriesForModule of ExportSpecifier with argument module. + // 3. Return the list-concatenation of specs1 and specs2. + + // ExportSpecifier : ModuleExportName + + // 1. Let sourceName be the StringValue of ModuleExportName. + // 2. If module is null, then + // a. Let localName be sourceName. + // b. Let importName be null. + // 3. Else, + // a. Let localName be null. + // b. Let importName be sourceName. + // 4. Return a List whose sole element is a new ExportEntry Record { [[ModuleRequest]]: module, [[ImportName]]: importName, [[LocalName]]: localName, [[ExportName]]: sourceName }. + + // ExportSpecifier : ModuleExportName as ModuleExportName + + // 1. Let sourceName be the StringValue of the first ModuleExportName. + // 2. Let exportName be the StringValue of the second ModuleExportName. + // 3. If module is null, then + // a. Let localName be sourceName. + // b. Let importName be null. + // 4. Else, + // a. Let localName be null. + // b. Let importName be sourceName. + // 5. Return a List whose sole element is a new ExportEntry Record { [[ModuleRequest]]: module, [[ImportName]]: importName, [[LocalName]]: localName, [[ExportName]]: exportName }. +} + +/// ### [16.2.3.6 Static Semantics: ReferencedBindings]() +/// +/// The syntax-directed operation ReferencedBindings takes no arguments and returns a List of Parse Nodes. It is defined piecewise over the following productions: +pub(crate) fn referenced_bindings() { + // NamedExports : { } + + // 1. Return a new empty List. + + // ExportsList : ExportsList , ExportSpecifier + + // 1. Let names1 be the ReferencedBindings of ExportsList. + // 2. Let names2 be the ReferencedBindings of ExportSpecifier. + // 3. Return the list-concatenation of names1 and names2. + + // ExportSpecifier : ModuleExportName as ModuleExportName + + // 1. Return the ReferencedBindings of the first ModuleExportName. + + // ModuleExportName : IdentifierName + + // 1. Return a List whose sole element is the IdentifierName. + + // ModuleExportName : StringLiteral + + // 1. Return a List whose sole element is the StringLiteral. +} diff --git a/nova_vm/src/ecmascript/builtins/module/source_text_module_records.rs b/nova_vm/src/ecmascript/builtins/module/source_text_module_records.rs new file mode 100644 index 000000000..961a60ed1 --- /dev/null +++ b/nova_vm/src/ecmascript/builtins/module/source_text_module_records.rs @@ -0,0 +1,617 @@ +use oxc_allocator::Allocator; +use oxc_ast::ast::Program; +use oxc_parser::{Parser, ParserReturn}; +use oxc_span::{Atom, SourceType}; +use oxc_syntax::module_record::RequestedModule; +use small_string::SmallString; + +use super::{abstract_module_records::{ModuleRecord, ResolveExportResult}, cyclic_module_records::{CyclicModuleRecord, CyclicModuleRecordStatus}, data::ModuleHeapData, Module}; +use crate::{ + ecmascript::{ + builtins::module::semantics::{self, module_requests}, + execution::{Agent, ExecutionContext, JsResult, RealmIdentifier}, + scripts_and_modules::script::HostDefined, + types::{ + Object, String, BUILTIN_STRING_MEMORY, SMALL_STRING_DISCRIMINANT, STRING_DISCRIMINANT, + }, + }, + heap::indexes::StringIndex, +}; + +/// a String or NAMESPACE-OBJECT +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) enum ImportName { + String(StringIndex) = STRING_DISCRIMINANT, + SmallString(SmallString) = SMALL_STRING_DISCRIMINANT, + NamespaceObject, +} + +impl Into for String { + fn into(self) -> ImportName { + match self { + String::String(data) => ImportName::String(data), + String::SmallString(data) => ImportName::SmallString(data), + } + } +} + +#[derive(Debug, Clone)] +pub(crate) struct ImportEntryRecord { + /// \[\[ModuleRequest\]\] + /// + /// a String + /// + /// String value of the ModuleSpecifier of the ImportDeclaration. + /// + /// SAFETY: The Atom refers to the Program that is owned by the + /// ModuleHeapData that this ImportEntryRecord also is owned by. + /// This is thus a self-referential struct and "safe". + pub(crate) module_request: Atom<'static>, + /// \[\[ImportName\]\] + /// + /// a String or NAMESPACE-OBJECT + /// + /// The name under which the desired binding is exported by the module + /// identified by \[\[ModuleRequest\]\]. The value NAMESPACE-OBJECT indicates + /// that the import request is for the target module's namespace object. + pub(crate) import_name: ImportName, + /// \[\[LocalName\]\] + /// + /// a String + /// + /// The name that is used to locally access the imported value from within the + /// importing module. + pub(crate) local_name: String, +} + +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq)] +/// a String, all, or all-but-default +pub(crate) enum ExportImportName { + String(StringIndex) = STRING_DISCRIMINANT, + SmallString(SmallString) = SMALL_STRING_DISCRIMINANT, + All, + AllButDefault, +} + +impl Into for String { + fn into(self) -> ExportImportName { + match self { + String::String(data) => ExportImportName::String(data), + String::SmallString(data) => ExportImportName::SmallString(data), + } + } +} + +#[derive(Debug, Clone)] +pub(crate) struct ExportEntryRecord { + /// \[\[ExportName\]\] + /// + /// a String or null + /// + /// The name used to export this binding by this module. + pub(crate) export_name: Option, + /// \[\[ModuleRequest\]\] + /// + /// a String or null + /// + /// The String value of the ModuleSpecifier of the ExportDeclaration. null + /// if the ExportDeclaration does not have a ModuleSpecifier. + pub(crate) module_request: Option>, + /// \[\[ImportName\]\] + /// + /// a String, null, all, or all-but-default + /// + /// The name under which the desired binding is exported by the module + /// identified by \[\[ModuleRequest\]\]. null if the ExportDeclaration does + /// not have a ModuleSpecifier. all is used for export * as ns from "mod" + /// declarations. all-but-default is used for export * from "mod" + /// declarations. + pub(crate) import_name: Option, + /// \[\[LocalName\]\] + /// + /// a String or null + /// + /// The name that is used to locally access the exported value from within + /// the importing module. null if the exported value is not locally + /// accessible from within the module. + pub(crate) local_name: Option, +} + +#[derive(Debug)] +pub(crate) struct SourceTextModuleRecord { + /// ### \[\[ECMAScriptCode\]\] + /// + /// a Parse Node + /// + /// The result of parsing the source text of this module using Module as + /// the goal symbol. + pub(crate) ecmascript_code: Program<'static>, + /// ### \[\[Context\]\] + /// + /// an ECMAScript code execution context or empty + /// + /// The execution context associated with this module. It is empty until + /// the module's environment has been initialized. + context: Option, + /// ### \[\[ImportMeta\]\] + /// + /// an Object or empty + /// + /// An object exposed through the import.meta meta property. It is empty + /// until it is accessed by ECMAScript code. + import_meta: Option, + /// ### \[\[ImportEntries\]\] + /// + /// a List of ImportEntry Records + /// + /// A List of ImportEntry records derived from the code of this module. + import_entries: Box<[ImportEntryRecord]>, + /// ### \[\[LocalExportEntries\]\] + /// + /// a List of ExportEntry Records + /// + /// A List of ExportEntry records derived from the code of this module that + /// correspond to declarations that occur within the module. + local_export_entries: Box<[ExportEntryRecord]>, + /// ### \[\[IndirectExportEntries\]\] + /// + /// a List of ExportEntry Records + /// + /// A List of ExportEntry records derived from the code of this module that + /// correspond to reexported imports that occur within the module or + /// exports from export * as namespace declarations. + indirect_export_entries: Box<[ExportEntryRecord]>, + /// ### \[\[StarExportEntries\]\] + /// + /// a List of ExportEntry Records + /// + /// A List of ExportEntry records derived from the code of this module that + /// correspond to export * declarations that occur within the module, not + /// including export * as namespace declarations. + star_export_entries: Box<[ExportEntryRecord]>, +} + +pub type ModuleOrErrors = Result>; + +/// ### [16.2.1.6.1 ParseModule ( sourceText, realm, hostDefined )]() +/// +/// The abstract operation ParseModule takes arguments sourceText (ECMAScript +/// source text), realm (a Realm Record), and hostDefined (anything) and +/// returns a Source Text Module Record or a non-empty List of SyntaxError +/// objects. It creates a Source Text Module Record based upon the result of +/// parsing sourceText as a Module. +pub(crate) fn parse_module( + agent: &mut Agent, + allocator: &Allocator, + module: Module, + source_text: Box, + realm: RealmIdentifier, + host_defined: Option, +) -> ModuleOrErrors { + // 1. Let body be ParseText(sourceText, Module). + let parser = Parser::new( + allocator, + &source_text, + SourceType::default().with_module(true), + ); + let ParserReturn { + errors, program, .. + } = parser.parse(); + // 2. If body is a List of errors, return body. + if !errors.is_empty() { + return Err(errors); + } + // 3. Let requestedModules be the ModuleRequests of body. + let requested_modules = module_requests(agent, &program); + // 4. Let importEntries be ImportEntries of body. + let import_entries = semantics::import_entries(agent, &program); + // 5. Let importedBoundNames be ImportedLocalNames(importEntries). + let imported_bound_names = semantics::imported_local_names(&import_entries); + // 6. Let indirectExportEntries be a new empty List. + let mut indirect_export_entries = vec![]; + // 7. Let localExportEntries be a new empty List. + let mut local_export_entries = vec![]; + // 8. Let starExportEntries be a new empty List. + let mut star_export_entries = vec![]; + // 9. Let exportEntries be ExportEntries of body. + let mut export_entries = semantics::export_entries(); + // 10. For each ExportEntry Record ee of exportEntries, do + for ee in export_entries.drain(..) { + // a. If ee.[[ModuleRequest]] is null, then + if ee.module_request.is_none() { + let local_name = ee.local_name.unwrap(); + // i. If importedBoundNames does not contain ee.[[LocalName]], then + if !imported_bound_names.contains(&local_name) { + // 1. Append ee to localExportEntries. + local_export_entries.push(ee); + } else { + // ii. Else, + // 1. Let ie be the element of importEntries whose [[LocalName]] is ee.[[LocalName]]. + let ie = import_entries + .iter() + .find(|entry| entry.local_name == local_name) + .unwrap(); + // 2. If ie.[[ImportName]] is NAMESPACE-OBJECT, then + if ie.import_name == ImportName::NamespaceObject { + // a. NOTE: This is a re-export of an imported module namespace object. + // b. Append ee to localExportEntries. + local_export_entries.push(ee); + } else { + let import_name = match ie.import_name { + ImportName::String(data) => String::from(data), + ImportName::SmallString(data) => String::from(data), + _ => unreachable!(), + }; + // 3. Else, + // a. NOTE: This is a re-export of a single name. + // b. Append the ExportEntry Record { + indirect_export_entries.push(ExportEntryRecord { + // [[ModuleRequest]]: ie.[[ModuleRequest]], + module_request: Some(ie.module_request), + // [[ImportName]]: ie.[[ImportName]], + import_name: Some(import_name.into()), + // [[LocalName]]: null, + local_name: None, + // [[ExportName]]: ee.[[ExportName]] + export_name: ee.export_name, + }); + // } to indirectExportEntries. + } + } + } else if ee.import_name != Some(BUILTIN_STRING_MEMORY.default.into()) { + // b. Else if ee.[[ImportName]] is all-but-default, then + // i. Assert: ee.[[ExportName]] is null. + assert!(ee.export_name.is_none()); + // ii. Append ee to starExportEntries. + star_export_entries.push(ee); + } else { + // c. Else, + // i. Append ee to indirectExportEntries. + indirect_export_entries.push(ee); + } + } + // 11. Let async be body Contains await. + let r#async = program + .body + .iter() + .find(|statement| match statement { + oxc_ast::ast::Statement::ExpressionStatement(expression) => match expression.expression + { + oxc_ast::ast::Expression::AwaitExpression(_) => true, + _ => false, + }, + _ => false, + }) + .is_some(); + // 12. Return Source Text Module Record { + Ok(ModuleHeapData { + + object_index: None, + r#abstract: ModuleRecord { + // [[Realm]]: realm, + realm, + // [[Environment]]: empty, + environment: None, + // [[Namespace]]: empty, + namespace: None, + // [[HostDefined]]: hostDefined, + host_defined: host_defined, + }, + cyclic: CyclicModuleRecord { + // [[CycleRoot]]: empty, + cycle_root: None, + // [[HasTLA]]: async, + has_top_level_await: r#async, + // [[AsyncEvaluation]]: false, + async_evaluation: false, + // [[TopLevelCapability]]: empty, + top_level_capability: None, + // [[AsyncParentModules]]: « », + async_parent_modules: vec![], + // [[PendingAsyncDependencies]]: empty, + pending_async_dependencies: None, + // [[Status]]: new, + // [[EvaluationError]]: empty, + // [[DFSIndex]]: empty, + // [[DFSAncestorIndex]]: empty + status: CyclicModuleRecordStatus::New, + // [[RequestedModules]]: requestedModules, + requested_modules, + // [[LoadedModules]]: « », + loaded_modules: vec![], + }, + source_text: SourceTextModuleRecord { + // [[ECMAScriptCode]]: body, + ecmascript_code: program, + // [[Context]]: empty, + context: None, + // [[ImportMeta]]: empty, + import_meta: None, + // [[ImportEntries]]: importEntries, + import_entries, + // [[LocalExportEntries]]: localExportEntries, + local_export_entries: local_export_entries.into_boxed_slice(), + // [[IndirectExportEntries]]: indirectExportEntries, + indirect_export_entries: indirect_export_entries.into_boxed_slice(), + // [[StarExportEntries]]: starExportEntries, + star_export_entries: star_export_entries.into_boxed_slice(), + }, + exports: todo!(), + }) + // }. + // Note + + // An implementation may parse module source text and analyse it for Early Error conditions prior to the evaluation of ParseModule for that module source text. However, the reporting of any errors must be deferred until the point where this specification actually performs ParseModule upon that source text. +} + +/// ### [16.2.1.6.2 GetExportedNames ( [ exportStarSet ] )]() +/// +/// The GetExportedNames concrete method of a Source Text Module Record module +/// takes optional argument exportStarSet (a List of Source Text Module +/// Records) and returns a List of Strings. +pub(crate) fn get_exported_names( + agent: &mut Agent, + module: Module, + export_start_set: Option>, +) -> Vec { + // 1. Assert: module.[[Status]] is not new. + assert_ne!(agent[module].cyclic.status, CyclicModuleRecordStatus::New); + // 2. If exportStarSet is not present, set exportStarSet to a new empty List. + let mut export_start_set = export_start_set.unwrap_or(vec![]); + // 3. If exportStarSet contains module, then + if export_start_set.contains(&module) { + // a. Assert: We've reached the starting point of an export * circularity. + // b. Return a new empty List. + return vec![]; + } + // 4. Append module to exportStarSet. + export_start_set.push(module); + // 5. Let exportedNames be a new empty List. + let mut exported_names = Vec::with_capacity(export_start_set.len()); + // 6. For each ExportEntry Record e of module.[[LocalExportEntries]], do + for e in agent[module].source_text.local_export_entries.iter() { + // a. Assert: module provides the direct binding for this export. + // TODO: How to do this? Probably checking the environment? + // b. Assert: e.[[ExportName]] is not null. + // c. Append e.[[ExportName]] to exportedNames. + exported_names.push(e.export_name.unwrap()); + } + // 7. For each ExportEntry Record e of module.[[IndirectExportEntries]], do + for e in agent[module].source_text.indirect_export_entries.iter() { + // a. Assert: module imports a specific binding for this export. + // TODO: How? + // b. Assert: e.[[ExportName]] is not null. + // c. Append e.[[ExportName]] to exportedNames. + exported_names.push(e.export_name.unwrap()); + } + // 8. For each ExportEntry Record e of module.[[StarExportEntries]], do + for e in agent[module].source_text.star_export_entries.iter() { + // a. Assert: e.[[ModuleRequest]] is not null. + let module_request = e.module_request.unwrap(); + // b. Let requestedModule be GetImportedModule(module, e.[[ModuleRequest]]). + let requested_module = get_imported_module(agent, module, module_request); + // c. Let starNames be requestedModule.GetExportedNames(exportStarSet). + let star_names = requested_module.get_exported_names(agent, export_start_set); + // d. For each element n of starNames, do + for n in star_names.iter() { + // i. If n is not "default", then + if n != BUILTIN_STRING_MEMORY.default && + // 1. If exportedNames does not contain n, then + !exported_names.contains(n) { + // a. Append n to exportedNames. + exported_names.push(n); + } + } + } + // 9. Return exportedNames. + exported_names + + // Note + + // GetExportedNames does not filter out or throw an exception for names + // that have ambiguous star export bindings. +} + +/// ### [16.2.1.6.3 ResolveExport ( exportName [ , resolveSet ] )]() +/// +/// The ResolveExport concrete method of a Source Text Module Record module +/// takes argument exportName (a String) and optional argument resolveSet (a +/// List of Records with fields \[\[Module\]\] (a Module Record) and +/// ### \[\[ExportName\]\] (a String)) and returns a ResolvedBinding Record, null, +/// +/// or ambiguous. +/// +/// +/// ResolveExport attempts to resolve an imported binding to the actual +/// defining module and local binding name. The defining module may be the +/// module represented by the Module Record this method was invoked on or some +/// other module that is imported by that module. The parameter resolveSet is +/// used to detect unresolved circular import/export paths. If a pair +/// consisting of specific Module Record and exportName is reached that is +/// already in resolveSet, an import circularity has been encountered. Before +/// recursively calling ResolveExport, a pair consisting of module and +/// exportName is added to resolveSet. +/// +/// If a defining module is found, a ResolvedBinding Record { \[\[Module\]\], +/// ### \[\[BindingName\]\] } is returned. This record identifies the resolved +/// +/// binding of the originally requested export, unless this is the export of a +/// +/// namespace with no local binding. In this case, \[\[BindingName\]\] will be +/// set to namespace. If no definition was found or the request is found to be +/// circular, null is returned. If the request is found to be ambiguous, +/// ambiguous is returned. +pub(crate) fn resolve_export( + agent: &mut Agent, + module: Module, + export_name: String, + resolve_set: Option<()>, +) -> ResolveExportResult { + // 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 exportName is r.[[ExportName]], then + // i. Assert: This is a circular import request. + // ii. Return null. + // 4. Append the Record { [[Module]]: module, [[ExportName]]: exportName } to resolveSet. + // 5. For each ExportEntry Record e of module.[[LocalExportEntries]], do + // a. If e.[[ExportName]] is exportName, then + // i. Assert: module provides the direct binding for this export. + // ii. Return ResolvedBinding Record { [[Module]]: module, [[BindingName]]: e.[[LocalName]] }. + // 6. For each ExportEntry Record e of module.[[IndirectExportEntries]], do + // a. If e.[[ExportName]] is exportName, then + // i. Assert: e.[[ModuleRequest]] is not null. + // ii. Let importedModule be GetImportedModule(module, e.[[ModuleRequest]]). + // iii. If e.[[ImportName]] is all, then + // 1. Assert: module does not provide the direct binding for this export. + // 2. Return ResolvedBinding Record { [[Module]]: importedModule, [[BindingName]]: namespace }. + // iv. Else, + // 1. Assert: module imports a specific binding for this export. + // 2. Return importedModule.ResolveExport(e.[[ImportName]], resolveSet). + // 7. If exportName is "default", then + // a. Assert: A default export was not explicitly defined by this module. + // b. Return null. + // c. NOTE: A default export cannot be provided by an export * from "mod" declaration. + // 8. Let starResolution be null. + // 9. For each ExportEntry Record e of module.[[StarExportEntries]], do + // a. Assert: e.[[ModuleRequest]] is not null. + // b. Let importedModule be GetImportedModule(module, e.[[ModuleRequest]]). + // c. Let resolution be importedModule.ResolveExport(exportName, resolveSet). + // d. If resolution is ambiguous, return ambiguous. + // e. If resolution is not null, then + // i. Assert: resolution is a ResolvedBinding Record. + // ii. If starResolution is null, then + // 1. Set starResolution to resolution. + // iii. Else, + // 1. Assert: There is more than one * import that includes the requested name. + // 2. If resolution.[[Module]] and starResolution.[[Module]] are not the same Module Record, return ambiguous. + // 3. If resolution.[[BindingName]] is not starResolution.[[BindingName]] and either resolution.[[BindingName]] or starResolution.[[BindingName]] is namespace, return ambiguous. + // 4. If resolution.[[BindingName]] is a String, starResolution.[[BindingName]] is a String, and resolution.[[BindingName]] is not starResolution.[[BindingName]], return ambiguous. + // 10. Return starResolution. +} + +/// ### [16.2.1.6.4 InitializeEnvironment ( )]() +/// +/// The InitializeEnvironment concrete method of a Source Text Module Record +/// module takes no arguments and returns either a normal completion containing +/// unused or a throw completion. +pub(crate) fn initialize_environment(agent: &mut Agent, module: Module) -> JsResult<()> { + // 1. For each ExportEntry Record e of module.[[IndirectExportEntries]], do + // a. Assert: e.[[ExportName]] is not null. + // b. Let resolution be module.ResolveExport(e.[[ExportName]]). + // c. If resolution is either null or ambiguous, throw a SyntaxError exception. + // d. Assert: resolution is a ResolvedBinding Record. + // 2. Assert: All named exports from module are resolvable. + // 3. Let realm be module.[[Realm]]. + // 4. Assert: realm is not undefined. + // 5. Let env be NewModuleEnvironment(realm.[[GlobalEnv]]). + // 6. Set module.[[Environment]] to env. + // 7. For each ImportEntry Record in of module.[[ImportEntries]], do + // a. Let importedModule be GetImportedModule(module, in.[[ModuleRequest]]). + // b. If in.[[ImportName]] is NAMESPACE-OBJECT, then + // i. Let namespace be GetModuleNamespace(importedModule). + // ii. Perform ! env.CreateImmutableBinding(in.[[LocalName]], true). + // iii. Perform ! env.InitializeBinding(in.[[LocalName]], namespace). + // c. Else, + // i. Let resolution be importedModule.ResolveExport(in.[[ImportName]]). + // ii. If resolution is either null or ambiguous, throw a SyntaxError exception. + // iii. If resolution.[[BindingName]] is namespace, then + // 1. Let namespace be GetModuleNamespace(resolution.[[Module]]). + // 2. Perform ! env.CreateImmutableBinding(in.[[LocalName]], true). + // 3. Perform ! env.InitializeBinding(in.[[LocalName]], namespace). + // iv. Else, + // 1. Perform env.CreateImportBinding(in.[[LocalName]], resolution.[[Module]], resolution.[[BindingName]]). + // 8. Let moduleContext be a new ECMAScript code execution context. + // 9. Set the Function of moduleContext to null. + // 10. Assert: module.[[Realm]] is not undefined. + // 11. Set the Realm of moduleContext to module.[[Realm]]. + // 12. Set the ScriptOrModule of moduleContext to module. + // 13. Set the VariableEnvironment of moduleContext to module.[[Environment]]. + // 14. Set the LexicalEnvironment of moduleContext to module.[[Environment]]. + // 15. Set the PrivateEnvironment of moduleContext to null. + // 16. Set module.[[Context]] to moduleContext. + // 17. Push moduleContext onto the execution context stack; moduleContext is now the running execution context. + // 18. Let code be module.[[ECMAScriptCode]]. + // 19. Let varDeclarations be the VarScopedDeclarations of code. + // 20. Let declaredVarNames be a new empty List. + // 21. For each element d of varDeclarations, do + // a. For each element dn of the BoundNames of d, do + // i. If declaredVarNames does not contain dn, then + // 1. Perform ! env.CreateMutableBinding(dn, false). + // 2. Perform ! env.InitializeBinding(dn, undefined). + // 3. Append dn to declaredVarNames. + // 22. Let lexDeclarations be the LexicallyScopedDeclarations of code. + // 23. Let privateEnv be null. + // 24. For each element d of lexDeclarations, do + // a. For each element dn of the BoundNames of d, do + // i. If IsConstantDeclaration of d is true, then + // 1. Perform ! env.CreateImmutableBinding(dn, true). + // ii. Else, + // 1. Perform ! env.CreateMutableBinding(dn, false). + // iii. If d is either a FunctionDeclaration, a GeneratorDeclaration, an AsyncFunctionDeclaration, or an AsyncGeneratorDeclaration, then + // 1. Let fo be InstantiateFunctionObject of d with arguments env and privateEnv. + // 2. Perform ! env.InitializeBinding(dn, fo). + // 25. Remove moduleContext from the execution context stack. + // 26. Return unused. + Ok(()) +} + +/// ### [16.2.1.6.5 ExecuteModule ( \[ capability \] )](https://tc39.es/ecma262/#sec-source-text-module-record-execute-module) +/// +/// The ExecuteModule concrete method of a Source Text Module Record module +/// takes optional argument capability (a PromiseCapability Record) and returns +/// either a normal completion containing unused or a throw completion. +pub(crate) fn execute_module( + agent: &mut Agent, + module: Module, + capability: Option<()>, +) -> JsResult<()> { + // 1. Let moduleContext be a new ECMAScript code execution context. + // 2. Set the Function of moduleContext to null. + // 3. Set the Realm of moduleContext to module.[[Realm]]. + // 4. Set the ScriptOrModule of moduleContext to module. + // 5. Assert: module has been linked and declarations in its module environment have been instantiated. + // 6. Set the VariableEnvironment of moduleContext to module.[[Environment]]. + // 7. Set the LexicalEnvironment of moduleContext to module.[[Environment]]. + // 8. Suspend the running execution context. + // 9. If module.[[HasTLA]] is false, then + // a. Assert: capability is not present. + // b. Push moduleContext onto the execution context stack; moduleContext is now the running execution context. + // c. Let result be Completion(Evaluation of module.[[ECMAScriptCode]]). + // d. Suspend moduleContext and remove it from the execution context stack. + // e. Resume the context that is now on the top of the execution context stack as the running execution context. + // f. If result is an abrupt completion, then + // i. Return ? result. + // 10. Else, + // a. Assert: capability is a PromiseCapability Record. + // b. Perform AsyncBlockStart(capability, module.[[ECMAScriptCode]], moduleContext). + // 11. Return unused. + Ok(()) +} + + +/// 16.2.1.7 GetImportedModule ( referrer, specifier ) +/// +/// The abstract operation GetImportedModule takes arguments referrer (a Cyclic Module Record) and specifier (a String) and returns a Module Record. It performs the following steps when called: +pub(super) fn get_imported_module(agent: &mut Agent, referrer: Module, specifier: Atom<'static>) -> Module { + // 1. Assert: Exactly one element of referrer.[[LoadedModules]] is a Record + // whose [[Specifier]] is specifier, since LoadRequestedModules has + // completed successfully on referrer prior to invoking this abstract + // operation. + // 2. Let record be the Record in referrer.[[LoadedModules]] whose [[Specifier]] is specifier. + let mut record: Option = None; + assert_eq!(agent[referrer].cyclic.loaded_modules.iter().filter(|loaded_module| { + if loaded_module.specifier == specifier { + record = Some(loaded_module.module); + true + } else { + false + } + }).count(), 1); + // 3. Return record.[[Module]]. + record.unwrap() +} + + diff --git a/nova_vm/src/ecmascript/execution/agent.rs b/nova_vm/src/ecmascript/execution/agent.rs index f292a8b91..3f727cbd7 100644 --- a/nova_vm/src/ecmascript/execution/agent.rs +++ b/nova_vm/src/ecmascript/execution/agent.rs @@ -17,7 +17,7 @@ use crate::{ heap::indexes::ErrorIndex, Heap, }; -use std::collections::HashMap; +use std::{any::Any, collections::HashMap}; #[derive(Debug, Default)] pub struct Options { @@ -28,8 +28,8 @@ pub struct Options { pub type JsResult = std::result::Result; -#[derive(Debug, Default, Clone, Copy)] -pub struct JsError(Value); +#[derive(Debug, Default, Clone, Copy, PartialEq)] +pub struct JsError(pub(crate) Value); impl JsError { pub(crate) fn new(value: Value) -> Self { @@ -51,6 +51,13 @@ impl JsError { pub trait HostHooks: std::fmt::Debug { fn host_ensure_can_compile_strings(&self, callee_realm: &mut Realm) -> JsResult<()>; fn host_has_source_text_available(&self, func: Function) -> bool; + fn host_load_imported_module( + &self, + referrer: (), + specifier: &str, + host_defined: Option>, + payload: (), + ); } /// ### [9.7 Agents](https://tc39.es/ecma262/#sec-agents) From 63ecfee59174001fc6419e8ce6c53a4682266753 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sat, 4 May 2024 11:06:32 +0300 Subject: [PATCH 2/8] Abstract closures, Promises, and such work --- .../promise_objects.rs | 1 + .../promise_abstract_operations.rs | 280 ++++++++++++++++++ .../promise_capability_records.rs | 97 ++++++ .../promise_reaction_records.rs | 89 ++++++ .../builtins/module/cyclic_module_records.rs | 10 +- .../module/source_text_module_records.rs | 4 +- nova_vm/src/ecmascript/execution/agent.rs | 83 +++++- .../execution/default_host_hooks.rs | 19 +- .../src/ecmascript/types/language/function.rs | 28 +- .../src/ecmascript/types/language/object.rs | 9 +- .../src/ecmascript/types/language/value.rs | 8 +- nova_vm/src/ecmascript/types/spec.rs | 2 + .../ecmascript/types/spec/abstract_closure.rs | 23 ++ nova_vm/src/heap.rs | 29 +- nova_vm/src/heap/heap_gc.rs | 8 + 15 files changed, 637 insertions(+), 53 deletions(-) create mode 100644 nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations.rs create mode 100644 nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_capability_records.rs create mode 100644 nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_reaction_records.rs create mode 100644 nova_vm/src/ecmascript/types/spec/abstract_closure.rs diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects.rs index a7714a92b..37941bd5a 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects.rs @@ -1,2 +1,3 @@ +pub(crate) mod promise_abstract_operations; pub(crate) mod promise_constructor; pub(crate) mod promise_prototype; diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations.rs new file mode 100644 index 000000000..e2b9903d8 --- /dev/null +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations.rs @@ -0,0 +1,280 @@ +use std::ops::{Index, IndexMut}; + +use crate::{ecmascript::{abstract_operations::testing_and_comparison::is_constructor, builtins::{promise::Promise, ArgumentsList}, execution::{agent::ExceptionType, Agent, JsResult}, types::{AbstractClosureHeapData, Function, IntoValue, Object, String, Value}}, heap::{indexes::{BaseIndex, BoundFunctionIndex}, Heap}}; + +use self::{promise_capability_records::PromiseCapability, promise_reaction_records::PromiseReaction}; + +pub(crate) mod promise_capability_records; +pub(crate) mod promise_reaction_records; + +pub(crate) struct PromiseResolvingFunctions { + pub(crate) resolve: Function, + pub(crate) reject: BuiltinPromiseRejectFunction, +} + +/// ### [27.2.1.3 CreateResolvingFunctions ( promise )]() +/// +/// The abstract operation CreateResolvingFunctions takes argument promise (a +/// Promise) and returns a Record with fields \[\[Resolve\]\] (a function +/// object) and \[\[Reject\]\] (a function object). +pub(crate) fn create_resolving_functions(agent: &mut Agent, promise: Promise) -> PromiseResolvingFunctions { + // 1. Let alreadyResolved be the Record { [[Value]]: false }. + let already_resolved = false; + // 2. Let stepsResolve be the algorithm steps defined in Promise Resolve Functions. + // 3. Let lengthResolve be the number of non-optional parameters of the function definition in Promise Resolve Functions. + // 4. Let resolve be CreateBuiltinFunction(stepsResolve, lengthResolve, "", « [[Promise]], [[AlreadyResolved]] »). + // TODO + let resolve = Function::BoundFunction(BoundFunctionIndex::from_u32_index(0)); + // 5. Set resolve.[[Promise]] to promise. + // 6. Set resolve.[[AlreadyResolved]] to alreadyResolved. + // 7. Let stepsReject be the algorithm steps defined in Promise Reject Functions. + // 8. Let lengthReject be the number of non-optional parameters of the function definition in Promise Reject Functions. + // 9. Let reject be CreateBuiltinFunction(stepsReject, lengthReject, "", « [[Promise]], [[AlreadyResolved]] »). + let reject = PromiseRejectFunctionHeapData { + promise, + already_resolved, + object_index: None, + }; + // 10. Set reject.[[Promise]] to promise. + // 11. Set reject.[[AlreadyResolved]] to alreadyResolved. + agent.heap.promise_reject_functions.push(Some(reject)); + let reject = BuiltinPromiseRejectFunction(BuiltinPromiseRejectFunctionIndex::last(&agent.heap.promise_reject_functions)); + // 12. Return the Record { [[Resolve]]: resolve, [[Reject]]: reject }. + PromiseResolvingFunctions { resolve, reject } +} + +/// ### [27.2.1.3.1 Promise Reject Functions]() +/// +/// A promise reject function is an anonymous built-in function that has +/// \[\[Promise\]\] and \[\[AlreadyResolved\]\] internal slots. +/// +/// The "length" property of a promise reject function is 1𝔽. +#[derive(Debug, Clone, Copy)] +pub(crate) struct PromiseRejectFunctionHeapData { + /// \[\[Promise\]\] + pub(crate) promise: Promise, + /// \[\[AlreadyResolved\]\] + pub(crate) already_resolved: bool, + pub(crate) object_index: Option, +} + +pub(crate) type BuiltinPromiseRejectFunctionIndex = BaseIndex; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) struct BuiltinPromiseRejectFunction(pub(crate) BuiltinPromiseRejectFunctionIndex); + +impl Index for Agent { + type Output = PromiseRejectFunctionHeapData; + + fn index(&self, index: BuiltinPromiseRejectFunction) -> &Self::Output { + &self.heap[index] + } +} + +impl IndexMut for Agent { + fn index_mut(&mut self, index: BuiltinPromiseRejectFunction) -> &mut Self::Output { + &mut self.heap[index] + } +} + +impl Index for Heap { + type Output = PromiseRejectFunctionHeapData; + + fn index(&self, index: BuiltinPromiseRejectFunction) -> &Self::Output { + self.promise_reject_functions + .get(index.0.into_index()) + .expect("BuiltinPromiseRejectFunction out of bounds") + .as_ref() + .expect("BuiltinPromiseRejectFunction slot empty") + } +} + +impl IndexMut for Heap { + fn index_mut(&mut self, index: BuiltinPromiseRejectFunction) -> &mut Self::Output { + self.promise_reject_functions + .get_mut(index.0.into_index()) + .expect("BuiltinPromiseRejectFunction out of bounds") + .as_mut() + .expect("BuiltinPromiseRejectFunction slot empty") + } +} + +impl PromiseRejectFunctionHeapData { + /// When a promise reject function is called with argument reason, the + /// following steps are taken: + pub(crate) fn call(agent: &mut Agent, reason: Value) { + // 1. Let F be the active function object. + let f = agent.running_execution_context().function.unwrap(); + // 2. Assert: F has a [[Promise]] internal slot whose value is an Object. + let Function::BuiltinPromiseRejectFunction(f) = f else { + unreachable!(); + }; + // 3. Let promise be F.[[Promise]]. + // 4. Let alreadyResolved be F.[[AlreadyResolved]]. + let PromiseRejectFunctionHeapData { + promise, + already_resolved, + .. + } = agent[BuiltinPromiseRejectFunction(f)]; + // 5. If alreadyResolved.[[Value]] is true, return undefined. + if !already_resolved { + // 6. Set alreadyResolved.[[Value]] to true. + agent[BuiltinPromiseRejectFunction(f)].already_resolved = true; + // 7. Perform RejectPromise(promise, reason). + reject_promise(agent, promise, reason); + // 8. Return undefined. + } + } +} + + +/// ### [27.2.1.3.2 Promise Resolve Functions]() +/// +/// A promise resolve function is an anonymous built-in function that has [[Promise]] and [[AlreadyResolved]] internal slots. + +// When a promise resolve function is called with argument resolution, the following steps are taken: + +// 1. Let F be the active function object. +// 2. Assert: F has a [[Promise]] internal slot whose value is an Object. +// 3. Let promise be F.[[Promise]]. +// 4. Let alreadyResolved be F.[[AlreadyResolved]]. +// 5. If alreadyResolved.[[Value]] is true, return undefined. +// 6. Set alreadyResolved.[[Value]] to true. +// 7. If SameValue(resolution, promise) is true, then +// a. Let selfResolutionError be a newly created TypeError object. +// b. Perform RejectPromise(promise, selfResolutionError). +// c. Return undefined. +// 8. If resolution is not an Object, then +// a. Perform FulfillPromise(promise, resolution). +// b. Return undefined. +// 9. Let then be Completion(Get(resolution, "then")). +// 10. If then is an abrupt completion, then +// a. Perform RejectPromise(promise, then.[[Value]]). +// b. Return undefined. +// 11. Let thenAction be then.[[Value]]. +// 12. If IsCallable(thenAction) is false, then +// a. Perform FulfillPromise(promise, resolution). +// b. Return undefined. +// 13. Let thenJobCallback be HostMakeJobCallback(thenAction). +// 14. Let job be NewPromiseResolveThenableJob(promise, resolution, thenJobCallback). +// 15. Perform HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]). +// 16. Return undefined. + +// The "length" property of a promise resolve function is 1𝔽. +/// ### [27.2.1.4 FulfillPromise ( promise, value )]() +/// +/// The abstract operation FulfillPromise takes arguments promise (a Promise) +/// and value (an ECMAScript language value) and returns unused. +pub(crate) fn fulfill_promise(agent: &mut Agent, promise: Promise, value: Value) { + // 1. Assert: The value of promise.[[PromiseState]] is pending. + // 2. Let reactions be promise.[[PromiseFulfillReactions]]. + // 3. Set promise.[[PromiseResult]] to value. + // 4. Set promise.[[PromiseFulfillReactions]] to undefined. + // 5. Set promise.[[PromiseRejectReactions]] to undefined. + // 6. Set promise.[[PromiseState]] to fulfilled. + // 7. Perform TriggerPromiseReactions(reactions, value). + // 8. Return unused. +} + +/// ### [27.2.1.5 NewPromiseCapability ( C )]() +/// +/// The abstract operation NewPromiseCapability takes argument C (an ECMAScript +/// language value) and returns either a normal completion containing a +/// PromiseCapability Record or a throw completion. It attempts to use C as a +/// constructor in the fashion of the built-in Promise constructor to create a +/// promise and extract its resolve and reject functions. The promise plus the +/// resolve and reject functions are used to initialize a new PromiseCapability +/// Record. +pub(crate) fn new_promise_capability(agent: &mut Agent, c: Value) -> JsResult { + // 1. If IsConstructor(C) is false, throw a TypeError exception. + if !is_constructor(agent, c) { + return Err(agent.throw_exception(ExceptionType::TypeError, "Not a constructor")); + } + // 2. NOTE: C is assumed to be a constructor function that supports the parameter conventions of the Promise constructor (see 27.2.3.1). + if c == agent.current_realm().intrinsics().promise().into_value() { + todo!("PromiseConstructor quick-route") + } + + // 3. Let resolvingFunctions be the Record { [[Resolve]]: undefined, [[Reject]]: undefined }. + struct SettableResolvingFunction { + resolve: Option, + reject: Option, + } + let resolving_functions = SettableResolvingFunction { + resolve: None, + reject: None, + }; + + // 4. Let executorClosure be a new Abstract Closure with parameters (resolve, reject) that captures resolvingFunctions and performs the following steps when called: + agent.heap.abstract_closures.push(Some(AbstractClosureHeapData { + object_index: None, + length: 2, + realm: agent.current_realm_id(), + initial_name: Some(String::EMPTY_STRING), + behaviour: Box::new(|agent: &mut Agent, this_value: Value, arguments: Option| { + // a. If resolvingFunctions.[[Resolve]] is not undefined, throw a TypeError exception. + + // b. If resolvingFunctions.[[Reject]] is not undefined, throw a TypeError exception. + // c. Set resolvingFunctions.[[Resolve]] to resolve. + // d. Set resolvingFunctions.[[Reject]] to reject. + // e. Return undefined. + Ok(Value::Undefined) + }), + })); + // 5. Let executor be CreateBuiltinFunction(executorClosure, 2, "", « »). + // 6. Let promise be ? Construct(C, « executor »). + // 7. If IsCallable(resolvingFunctions.[[Resolve]]) is false, throw a TypeError exception. + // 8. If IsCallable(resolvingFunctions.[[Reject]]) is false, throw a TypeError exception. + // 9. Return the PromiseCapability Record { [[Promise]]: promise, [[Resolve]]: resolvingFunctions.[[Resolve]], [[Reject]]: resolvingFunctions.[[Reject]] }. + todo!(); + // Note + + // This abstract operation supports Promise subclassing, as it is generic + // on any constructor that calls a passed executor function argument in the + // same way as the Promise constructor. It is used to generalize static + // methods of the Promise constructor to any subclass. +} + +/// ### [27.2.1.6 IsPromise ( x )]() +/// +/// The abstract operation IsPromise takes argument x (an ECMAScript language +/// value) and returns a Boolean. It checks for the promise brand on an object. +pub(crate) fn is_promise(agent: &mut Agent, x: Value) -> bool { + // 1. If x is not an Object, return false. + // 2. If x does not have a [[PromiseState]] internal slot, return false. + // 3. Return true. + matches!(x, Value::Promise(_)) +} + +/// ### [27.2.1.7 RejectPromise ( promise, reason )]() +/// +/// The abstract operation RejectPromise takes arguments promise (a Promise) +/// and reason (an ECMAScript language value) and returns unused. +pub(crate) fn reject_promise(agent: &mut Agent, promise: Promise, reason: Value) { + // 1. Assert: The value of promise.[[PromiseState]] is pending. + let promise = &mut agent[promise]; + // 2. Let reactions be promise.[[PromiseRejectReactions]]. + // 3. Set promise.[[PromiseResult]] to reason. + // 4. Set promise.[[PromiseFulfillReactions]] to undefined. + // 5. Set promise.[[PromiseRejectReactions]] to undefined. + // 6. Set promise.[[PromiseState]] to rejected. + // 7. If promise.[[PromiseIsHandled]] is false, perform HostPromiseRejectionTracker(promise, "reject"). + // 8. Perform TriggerPromiseReactions(reactions, reason). + // 9. Return unused. +} + +/// ### [27.2.1.8 TriggerPromiseReactions ( reactions, argument )]() +/// +/// The abstract operation TriggerPromiseReactions takes arguments reactions (a +/// List of PromiseReaction Records) and argument (an ECMAScript language +/// value) and returns unused. It enqueues a new Job for each record in +/// reactions. Each such Job processes the \[\[Type\]\] and \[\[Handler\]\] of +/// the PromiseReaction Record, and if the \[\[Handler\]\] is not empty, calls +/// it passing the given argument. If the \[\[Handler\]\] is empty, the +/// behaviour is determined by the \[\[Type\]\]. +pub(crate) fn trigger_promise_reactions(agent: &mut Agent, reactions: &[PromiseReaction], argument: Value) { + // 1. For each element reaction of reactions, do + // a. Let job be NewPromiseReactionJob(reaction, argument). + // b. Perform HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]). + // 2. Return unused. +} diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_capability_records.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_capability_records.rs new file mode 100644 index 000000000..c3e876681 --- /dev/null +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_capability_records.rs @@ -0,0 +1,97 @@ +//! ## [27.2.1.1 PromiseCapability Records]() + +use std::ops::{Index, IndexMut}; + +use crate::{ecmascript::{abstract_operations::operations_on_objects::{call, call_function}, builtins::ArgumentsList, execution::{agent::JsError, Agent, JsResult}, types::{Function, IntoValue, Object, Value}}, heap::{indexes::BaseIndex, Heap}}; + + +#[derive(Debug, Clone, Copy)] +pub(crate) struct PromiseCapabilityRecord { + /// \[\[Promise\]\] + /// + /// an Object + /// + /// An object that is usable as a promise. + pub(crate) promise: Object, + /// \[\[Resolve\]\] + /// + /// a function object + /// + /// The function that is used to resolve the given promise. + pub(crate) resolve: Function, + /// \[\[Reject\]\] + /// + /// a function object + /// + /// The function that is used to reject the given promise. + pub(crate) reject: Function, +} + +pub(crate) type PromiseCapability = BaseIndex; + +impl Index for Agent { + type Output = PromiseCapabilityRecord; + + fn index(&self, index: PromiseCapability) -> &Self::Output { + &self.heap[index] + } +} + +impl IndexMut for Agent { + fn index_mut(&mut self, index: PromiseCapability) -> &mut Self::Output { + &mut self.heap[index] + } +} + +impl Index for Heap { + type Output = PromiseCapabilityRecord; + + fn index(&self, index: PromiseCapability) -> &Self::Output { + self.promise_capability_records + .get(index.into_index()) + .expect("PromiseCapability out of bounds") + .as_ref() + .expect("PromiseCapability slot empty") + } +} + +impl IndexMut for Heap { + fn index_mut(&mut self, index: PromiseCapability) -> &mut Self::Output { + self.promise_capability_records + .get_mut(index.into_index()) + .expect("PromiseCapability out of bounds") + .as_mut() + .expect("PromiseCapability slot empty") + } +} + +/// ### [27.2.1.1.1 IfAbruptRejectPromise ( value, capability )](https://tc39.es/ecma262/#sec-ifabruptrejectpromise) +/// +/// IfAbruptRejectPromise is a shorthand for a sequence of algorithm steps that +/// use a PromiseCapability Record. An algorithm step of the form: +/// +/// ``` +/// 1. IfAbruptRejectPromise(value, capability). +/// ``` +/// +/// means the same thing as: +/// ``` +/// 1. Assert: value is a Completion Record. +/// 2. If value is an abrupt completion, then +/// a. Perform ? Call(capability.[[Reject]], undefined, « value.[[Value]] »). +/// b. Return capability.[[Promise]]. +/// 3. Else, +/// a. Set value to ! value. +/// ``` +#[inline(always)] +pub(crate) fn if_abrupt_reject_promise(agent: &mut Agent, value: JsResult, capability: PromiseCapability) -> JsResult { + value.or_else(|err| { + // If abrupt completion, call reject and make caller return the + // capability promise + let PromiseCapabilityRecord { promise, reject, .. } = agent[capability]; + call_function(agent, reject, Value::Undefined, Some(ArgumentsList(&[err.0])))?; + // Note: We return an error here so that caller gets to call this + // function with the ? operator + Err(JsError(promise.into_value())) + }) +} \ No newline at end of file diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_reaction_records.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_reaction_records.rs new file mode 100644 index 000000000..1f0c8e846 --- /dev/null +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_reaction_records.rs @@ -0,0 +1,89 @@ +use std::ops::{Index, IndexMut}; + +use crate::{ecmascript::execution::Agent, heap::{indexes::BaseIndex, Heap}}; + +use super::promise_capability_records::PromiseCapability; + +/// \[\[Type\]\] +/// +/// fulfill or reject +/// +/// The \[\[Type\]\] is used when \[\[Handler\]\] is empty to allow for +/// behaviour specific to the settlement type. +#[derive(Debug, Clone, Copy)] +pub(crate) enum PromiseReactionType { + Fulfill, + Reject, +} + +/// \[\[Handler\]\] +/// +/// a JobCallback Record or empty +/// +/// The function that should be applied to the incoming value, and whose +/// return value will govern what happens to the derived promise. If +/// \[\[Handler\]\] is empty, a function that depends on the value of +/// \[\[Type\]\] will be used instead. +#[derive(Debug, Clone, Copy)] +pub(crate) enum PromiseReactionHandler { + Empty(PromiseReactionType), + JobCallback(()), +} + +#[derive(Debug, Clone, Copy)] +pub(crate) struct PromiseReactionRecord { + /// \[\[Capability\]\] + /// + /// a PromiseCapability Record or undefined + /// + /// The capabilities of the promise for which this record provides a + /// reaction handler. + pub(crate) capability: Option, + /// \[\[Handler\]\] + /// + /// a JobCallback Record or empty + /// + /// The function that should be applied to the incoming value, and whose + /// return value will govern what happens to the derived promise. If + /// \[\[Handler\]\] is empty, a function that depends on the value of + /// \[\[Type\]\] will be used instead. + pub(crate) handler: PromiseReactionHandler, +} + +pub type PromiseReaction = BaseIndex; + +impl Index for Agent { + type Output = PromiseReactionRecord; + + fn index(&self, index: PromiseReaction) -> &Self::Output { + &self.heap[index] + } +} + +impl IndexMut for Agent { + fn index_mut(&mut self, index: PromiseReaction) -> &mut Self::Output { + &mut self.heap[index] + } +} + +impl Index for Heap { + type Output = PromiseReactionRecord; + + fn index(&self, index: PromiseReaction) -> &Self::Output { + self.promise_reaction_records + .get(index.into_index()) + .expect("PromiseReaction out of bounds") + .as_ref() + .expect("PromiseReaction slot empty") + } +} + +impl IndexMut for Heap { + fn index_mut(&mut self, index: PromiseReaction) -> &mut Self::Output { + self.promise_reaction_records + .get_mut(index.into_index()) + .expect("PromiseReaction out of bounds") + .as_mut() + .expect("PromiseReaction slot empty") + } +} \ No newline at end of file diff --git a/nova_vm/src/ecmascript/builtins/module/cyclic_module_records.rs b/nova_vm/src/ecmascript/builtins/module/cyclic_module_records.rs index ef5448c35..dfdd54974 100644 --- a/nova_vm/src/ecmascript/builtins/module/cyclic_module_records.rs +++ b/nova_vm/src/ecmascript/builtins/module/cyclic_module_records.rs @@ -4,7 +4,7 @@ use oxc_span::Atom; use crate::ecmascript::{ abstract_operations::operations_on_objects::call_function, - builtins::{create_builtin_function, error::Error, module::source_text_module_records::get_imported_module, promise::Promise, ArgumentsList}, + builtins::{control_abstraction_objects::promise_objects::promise_abstract_operations::promise_capability_records::PromiseCapability, create_builtin_function, error::Error, module::source_text_module_records::get_imported_module, promise::Promise, ArgumentsList}, execution::{agent::JsError, Agent, JsResult}, types::{String, Value}, }; @@ -129,7 +129,7 @@ pub(crate) struct CyclicModuleRecord { pub(super) async_evaluation: bool, /// \[\[TopLevelCapability\]\] /// - /// TODO: a PromiseCapability Record or empty + /// a PromiseCapability Record or empty /// /// If this module is the \[\[CycleRoot\]\] of some cycle, and Evaluate() /// was called on some module in that cycle, this field contains the @@ -138,7 +138,7 @@ pub(crate) struct CyclicModuleRecord { /// method. This field will be empty for any dependencies of that module, /// unless a top-level Evaluate() has been initiated for some of those /// dependencies. - pub(super) top_level_capability: Option<()>, + pub(super) top_level_capability: Option, /// \[\[AsyncParentModules\]\] /// /// a List of Cyclic Module Records @@ -170,10 +170,10 @@ impl CyclicModuleRecord { pub(crate) struct GraphLoadingStateRecord { /// \[\[PromiseCapability\]\] /// - /// TODO: a PromiseCapability Record + /// a PromiseCapability Record /// /// The promise to resolve when the loading process finishes. - promise_capability: (), + promise_capability: PromiseCapability, /// \[\[IsLoading\]\] /// /// It is true if the loading process has not finished yet, neither diff --git a/nova_vm/src/ecmascript/builtins/module/source_text_module_records.rs b/nova_vm/src/ecmascript/builtins/module/source_text_module_records.rs index 961a60ed1..5231c5949 100644 --- a/nova_vm/src/ecmascript/builtins/module/source_text_module_records.rs +++ b/nova_vm/src/ecmascript/builtins/module/source_text_module_records.rs @@ -8,7 +8,7 @@ use small_string::SmallString; use super::{abstract_module_records::{ModuleRecord, ResolveExportResult}, cyclic_module_records::{CyclicModuleRecord, CyclicModuleRecordStatus}, data::ModuleHeapData, Module}; use crate::{ ecmascript::{ - builtins::module::semantics::{self, module_requests}, + builtins::{control_abstraction_objects::promise_objects::promise_abstract_operations::promise_capability_records::PromiseCapability, module::semantics::{self, module_requests}}, execution::{Agent, ExecutionContext, JsResult, RealmIdentifier}, scripts_and_modules::script::HostDefined, types::{ @@ -566,7 +566,7 @@ pub(crate) fn initialize_environment(agent: &mut Agent, module: Module) -> JsRes pub(crate) fn execute_module( agent: &mut Agent, module: Module, - capability: Option<()>, + capability: Option, ) -> JsResult<()> { // 1. Let moduleContext be a new ECMAScript code execution context. // 2. Set the Function of moduleContext to null. diff --git a/nova_vm/src/ecmascript/execution/agent.rs b/nova_vm/src/ecmascript/execution/agent.rs index 3f727cbd7..cb37db26f 100644 --- a/nova_vm/src/ecmascript/execution/agent.rs +++ b/nova_vm/src/ecmascript/execution/agent.rs @@ -10,7 +10,7 @@ use super::{ use crate::{ ecmascript::{ abstract_operations::type_conversion::to_string, - builtins::error::ErrorHeapData, + builtins::{error::ErrorHeapData, promise::Promise}, scripts_and_modules::ScriptOrModule, types::{Function, Reference, String, Symbol, Value}, }, @@ -48,9 +48,59 @@ impl JsError { // #[derive(Debug)] // pub struct PreAllocated; +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum PromiseRejectionOperation { + Reject, + Handle, +} + pub trait HostHooks: std::fmt::Debug { fn host_ensure_can_compile_strings(&self, callee_realm: &mut Realm) -> JsResult<()>; fn host_has_source_text_available(&self, func: Function) -> bool; + /// ### [16.2.1.8 HostLoadImportedModule ( referrer, specifier, hostDefined, payload )](https://tc39.es/ecma262/#sec-HostLoadImportedModule) + /// + /// The host-defined abstract operation HostLoadImportedModule takes + /// arguments referrer (a Script Record, a Cyclic Module Record, or a Realm + /// Record), specifier (a String), hostDefined (anything), and payload (a + /// GraphLoadingState Record or a PromiseCapability Record) and returns + /// unused. + /// + /// #### Note + /// + /// An example of when referrer can be a Realm Record is in a web browser + /// host. There, if a user clicks on a control given by + /// ```html + /// + /// ``` + /// there will be no active script or module at the time the `import()` + /// expression runs. More generally, this can happen in any situation where + /// the host pushes execution contexts with null ScriptOrModule components + /// onto the execution context stack. + /// + /// An implementation of HostLoadImportedModule must conform to the + /// following requirements: + /// + /// * The host environment must perform + /// `FinishLoadingImportedModule(referrer, specifier, payload, result)`, + /// where result is either a normal completion containing the loaded Module + /// Record or a throw completion, either synchronously or asynchronously. + /// * If this operation is called multiple times with the same (referrer, + /// specifier) pair and it performs + /// `FinishLoadingImportedModule(referrer, specifier, payload, result)` + /// where result is a normal completion, then it must perform + /// `FinishLoadingImportedModule(referrer, specifier, payload, result)` + /// with the same result each time. + /// * The operation must treat payload as an opaque value to be passed + /// through to FinishLoadingImportedModule. + /// + /// The actual process performed is host-defined, but typically consists of + /// performing whatever I/O operations are necessary to load the + /// appropriate Module Record. Multiple different (referrer, specifier) + /// pairs may map to the same Module Record instance. The actual mapping + /// semantics is host-defined but typically a normalization process is + /// applied to specifier as part of the mapping process. A typical + /// normalization process would include actions such as expansion of + /// relative and abbreviated path specifiers. fn host_load_imported_module( &self, referrer: (), @@ -58,6 +108,37 @@ pub trait HostHooks: std::fmt::Debug { host_defined: Option>, payload: (), ); + /// ### [27.2.1.9 HostPromiseRejectionTracker ( promise, operation )](https://tc39.es/ecma262/#sec-host-promise-rejection-tracker) + /// + /// The host-defined abstract operation HostPromiseRejectionTracker takes + /// arguments promise (a Promise) and operation ("reject" or "handle") and + /// returns unused. It allows host environments to track promise rejections. + /// + /// The default implementation of HostPromiseRejectionTracker is to return + /// unused. + /// + /// #### Note 1 + /// + /// HostPromiseRejectionTracker is called in two scenarios: + /// + /// When a promise is rejected without any handlers, it is called with its + /// operation argument set to "reject". + /// When a handler is added to a rejected promise for the first time, it is + /// called with its operation argument set to "handle". + /// + /// A typical implementation of HostPromiseRejectionTracker might try to + /// notify developers of unhandled rejections, while also being careful to + /// notify them if such previous notifications are later invalidated by new + /// handlers being attached. + /// + /// #### Note 2 + /// + /// If operation is "handle", an implementation should not hold a reference + /// to promise in a way that would interfere with garbage collection. An + /// implementation may hold a reference to promise if operation is "reject", + /// since it is expected that rejections will be rare and not on hot code + /// paths. + fn host_promise_rejection_tracker(&self, promise: Promise, operation: PromiseRejectionOperation); } /// ### [9.7 Agents](https://tc39.es/ecma262/#sec-agents) diff --git a/nova_vm/src/ecmascript/execution/default_host_hooks.rs b/nova_vm/src/ecmascript/execution/default_host_hooks.rs index 786cd1edf..562230ceb 100644 --- a/nova_vm/src/ecmascript/execution/default_host_hooks.rs +++ b/nova_vm/src/ecmascript/execution/default_host_hooks.rs @@ -1,5 +1,5 @@ -use super::{agent::HostHooks, JsResult, Realm}; -use crate::ecmascript::types::Function; +use super::{agent::{HostHooks, PromiseRejectionOperation}, JsResult, Realm}; +use crate::ecmascript::{builtins::promise::Promise, types::Function}; #[derive(Debug)] pub struct DefaultHostHooks; @@ -15,4 +15,19 @@ impl HostHooks for DefaultHostHooks { // The default implementation of HostHasSourceTextAvailable is to return true. true } + + fn host_load_imported_module( + &self, + referrer: (), + specifier: &str, + host_defined: Option>, + payload: (), + ) { + unreachable!("HostLoadImportedModule does not have a default implementation"); + } + + fn host_promise_rejection_tracker(&self, promise: Promise, operation: PromiseRejectionOperation) { + } + + } diff --git a/nova_vm/src/ecmascript/types/language/function.rs b/nova_vm/src/ecmascript/types/language/function.rs index ea0f4aada..46cc9d254 100644 --- a/nova_vm/src/ecmascript/types/language/function.rs +++ b/nova_vm/src/ecmascript/types/language/function.rs @@ -17,7 +17,7 @@ use super::{ }; use crate::{ ecmascript::{ - builtins::{ArgumentsList, BuiltinFunction, ECMAScriptFunction}, + builtins::{control_abstraction_objects::promise_objects::promise_abstract_operations::BuiltinPromiseRejectFunctionIndex, ArgumentsList, BuiltinFunction, ECMAScriptFunction}, execution::{Agent, JsResult}, types::PropertyDescriptor, }, @@ -37,7 +37,7 @@ pub enum Function { BuiltinGeneratorFunction = BUILTIN_GENERATOR_FUNCTION_DISCRIMINANT, BuiltinConstructorFunction = BUILTIN_CONSTRUCTOR_FUNCTION_DISCRIMINANT, BuiltinPromiseResolveFunction = BUILTIN_PROMISE_RESOLVE_FUNCTION_DISCRIMINANT, - BuiltinPromiseRejectFunction = BUILTIN_PROMISE_REJECT_FUNCTION_DISCRIMINANT, + BuiltinPromiseRejectFunction(BuiltinPromiseRejectFunctionIndex) = BUILTIN_PROMISE_REJECT_FUNCTION_DISCRIMINANT, BuiltinPromiseCollectorFunction = BUILTIN_PROMISE_COLLECTOR_FUNCTION_DISCRIMINANT, BuiltinProxyRevokerFunction = BUILTIN_PROXY_REVOKER_FUNCTION, ECMAScriptAsyncFunction = ECMASCRIPT_ASYNC_FUNCTION_DISCRIMINANT, @@ -55,7 +55,7 @@ impl std::fmt::Debug for Function { Function::BuiltinGeneratorFunction => todo!(), Function::BuiltinConstructorFunction => todo!(), Function::BuiltinPromiseResolveFunction => todo!(), - Function::BuiltinPromiseRejectFunction => todo!(), + Function::BuiltinPromiseRejectFunction(BuiltinPromiseRejectFunction) => write!(f, "BuiltinPromiseRejectFunction({:?})", d), Function::BuiltinPromiseCollectorFunction => todo!(), Function::BuiltinProxyRevokerFunction => todo!(), Function::ECMAScriptAsyncFunction => todo!(), @@ -131,8 +131,8 @@ impl TryFrom for Function { Value::ECMAScriptFunction(d) => Ok(Function::from(d)), Value::BuiltinGeneratorFunction => Ok(Function::BuiltinGeneratorFunction), Value::BuiltinConstructorFunction => Ok(Function::BuiltinConstructorFunction), - Value::BuiltinPromiseResolveFunction => Ok(Function::BuiltinPromiseResolveFunction), - Value::BuiltinPromiseRejectFunction => Ok(Function::BuiltinPromiseRejectFunction), + Value::BuiltinPromiseResolveFunction(d) => Ok(Function::BuiltinPromiseResolveFunction(d)), + Value::BuiltinPromiseRejectFunction(d) => Ok(Function::BuiltinPromiseRejectFunction(d)), Value::BuiltinPromiseCollectorFunction => Ok(Function::BuiltinPromiseCollectorFunction), Value::BuiltinProxyRevokerFunction => Ok(Function::BuiltinProxyRevokerFunction), Value::ECMAScriptAsyncFunction => Ok(Function::ECMAScriptAsyncFunction), @@ -155,7 +155,7 @@ impl From for Object { Function::BuiltinGeneratorFunction => Object::BuiltinGeneratorFunction, Function::BuiltinConstructorFunction => Object::BuiltinConstructorFunction, Function::BuiltinPromiseResolveFunction => Object::BuiltinPromiseResolveFunction, - Function::BuiltinPromiseRejectFunction => Object::BuiltinPromiseRejectFunction, + Function::BuiltinPromiseRejectFunction(d) => Object::BuiltinPromiseRejectFunction(d), Function::BuiltinPromiseCollectorFunction => Object::BuiltinPromiseCollectorFunction, Function::BuiltinProxyRevokerFunction => Object::BuiltinProxyRevokerFunction, Function::ECMAScriptAsyncFunction => Object::ECMAScriptAsyncFunction, @@ -175,7 +175,7 @@ impl From for Value { Function::BuiltinGeneratorFunction => Value::BuiltinGeneratorFunction, Function::BuiltinConstructorFunction => Value::BuiltinConstructorFunction, Function::BuiltinPromiseResolveFunction => Value::BuiltinPromiseResolveFunction, - Function::BuiltinPromiseRejectFunction => Value::BuiltinPromiseRejectFunction, + Function::BuiltinPromiseRejectFunction(d) => Value::BuiltinPromiseRejectFunction(d), Function::BuiltinPromiseCollectorFunction => Value::BuiltinPromiseCollectorFunction, Function::BuiltinProxyRevokerFunction => Value::BuiltinProxyRevokerFunction, Function::ECMAScriptAsyncFunction => Value::ECMAScriptAsyncFunction, @@ -201,7 +201,7 @@ impl OrdinaryObjectInternalSlots for Function { Function::BuiltinGeneratorFunction => todo!(), Function::BuiltinConstructorFunction => todo!(), Function::BuiltinPromiseResolveFunction => todo!(), - Function::BuiltinPromiseRejectFunction => todo!(), + Function::BuiltinPromiseRejectFunction(d) => agent[d].object_index, Function::BuiltinPromiseCollectorFunction => todo!(), Function::BuiltinProxyRevokerFunction => todo!(), Function::ECMAScriptAsyncFunction => todo!(), @@ -223,7 +223,7 @@ impl OrdinaryObjectInternalSlots for Function { Function::BuiltinGeneratorFunction => todo!(), Function::BuiltinConstructorFunction => todo!(), Function::BuiltinPromiseResolveFunction => todo!(), - Function::BuiltinPromiseRejectFunction => todo!(), + Function::BuiltinPromiseRejectFunction(d) => agent[d].object_index, Function::BuiltinPromiseCollectorFunction => todo!(), Function::BuiltinProxyRevokerFunction => todo!(), Function::ECMAScriptAsyncFunction => todo!(), @@ -246,7 +246,7 @@ impl OrdinaryObjectInternalSlots for Function { Function::BuiltinGeneratorFunction => todo!(), Function::BuiltinConstructorFunction => todo!(), Function::BuiltinPromiseResolveFunction => todo!(), - Function::BuiltinPromiseRejectFunction => todo!(), + Function::BuiltinPromiseRejectFunction(d) => agent[d].object_index, Function::BuiltinPromiseCollectorFunction => todo!(), Function::BuiltinProxyRevokerFunction => todo!(), Function::ECMAScriptAsyncFunction => todo!(), @@ -274,7 +274,7 @@ impl OrdinaryObjectInternalSlots for Function { Function::BuiltinGeneratorFunction => todo!(), Function::BuiltinConstructorFunction => todo!(), Function::BuiltinPromiseResolveFunction => todo!(), - Function::BuiltinPromiseRejectFunction => todo!(), + Function::BuiltinPromiseRejectFunction(d) => agent[d].object_index, Function::BuiltinPromiseCollectorFunction => todo!(), Function::BuiltinProxyRevokerFunction => todo!(), Function::ECMAScriptAsyncFunction => todo!(), @@ -482,7 +482,7 @@ impl InternalMethods for Function { Function::BuiltinGeneratorFunction => todo!(), Function::BuiltinConstructorFunction => todo!(), Function::BuiltinPromiseResolveFunction => todo!(), - Function::BuiltinPromiseRejectFunction => todo!(), + Function::BuiltinPromiseRejectFunction(d) => agent[d].object_index, Function::BuiltinPromiseCollectorFunction => todo!(), Function::BuiltinProxyRevokerFunction => todo!(), Function::ECMAScriptAsyncFunction => todo!(), @@ -581,7 +581,7 @@ impl InternalMethods for Function { Function::BuiltinGeneratorFunction => todo!(), Function::BuiltinConstructorFunction => todo!(), Function::BuiltinPromiseResolveFunction => todo!(), - Function::BuiltinPromiseRejectFunction => todo!(), + Function::BuiltinPromiseRejectFunction(d) => agent[d].object_index, Function::BuiltinPromiseCollectorFunction => todo!(), Function::BuiltinProxyRevokerFunction => todo!(), Function::ECMAScriptAsyncFunction => todo!(), @@ -608,7 +608,7 @@ impl InternalMethods for Function { Function::BuiltinGeneratorFunction => todo!(), Function::BuiltinConstructorFunction => todo!(), Function::BuiltinPromiseResolveFunction => todo!(), - Function::BuiltinPromiseRejectFunction => todo!(), + Function::BuiltinPromiseRejectFunction(d) => agent[d].object_index, Function::BuiltinPromiseCollectorFunction => todo!(), Function::BuiltinProxyRevokerFunction => todo!(), Function::ECMAScriptAsyncFunction => todo!(), diff --git a/nova_vm/src/ecmascript/types/language/object.rs b/nova_vm/src/ecmascript/types/language/object.rs index 20a11f84e..5515c6366 100644 --- a/nova_vm/src/ecmascript/types/language/object.rs +++ b/nova_vm/src/ecmascript/types/language/object.rs @@ -33,8 +33,7 @@ use super::{ use crate::{ ecmascript::{ builtins::{ - date::Date, error::Error, map::Map, set::Set, ArgumentsList, Array, ArrayBuffer, - BuiltinFunction, ECMAScriptFunction, + control_abstraction_objects::promise_objects::promise_abstract_operations::BuiltinPromiseRejectFunctionIndex, date::Date, error::Error, map::Map, set::Set, ArgumentsList, Array, ArrayBuffer, BuiltinFunction, ECMAScriptFunction }, execution::{Agent, JsResult}, scripts_and_modules::module::ModuleIdentifier, @@ -69,7 +68,7 @@ pub enum Object { BuiltinGeneratorFunction = BUILTIN_GENERATOR_FUNCTION_DISCRIMINANT, BuiltinConstructorFunction = BUILTIN_CONSTRUCTOR_FUNCTION_DISCRIMINANT, BuiltinPromiseResolveFunction = BUILTIN_PROMISE_RESOLVE_FUNCTION_DISCRIMINANT, - BuiltinPromiseRejectFunction = BUILTIN_PROMISE_REJECT_FUNCTION_DISCRIMINANT, + BuiltinPromiseRejectFunction(BuiltinPromiseRejectFunctionIndex) = BUILTIN_PROMISE_REJECT_FUNCTION_DISCRIMINANT, BuiltinPromiseCollectorFunction = BUILTIN_PROMISE_COLLECTOR_FUNCTION_DISCRIMINANT, BuiltinProxyRevokerFunction = BUILTIN_PROXY_REVOKER_FUNCTION, ECMAScriptAsyncFunction = ECMASCRIPT_ASYNC_FUNCTION_DISCRIMINANT, @@ -244,7 +243,7 @@ impl From for Value { Object::BuiltinGeneratorFunction => Value::BuiltinGeneratorFunction, Object::BuiltinConstructorFunction => Value::BuiltinConstructorFunction, Object::BuiltinPromiseResolveFunction => Value::BuiltinPromiseResolveFunction, - Object::BuiltinPromiseRejectFunction => Value::BuiltinPromiseRejectFunction, + Object::BuiltinPromiseRejectFunction => Value::BuiltinPromiseRejectFunction(todo!()), Object::BuiltinPromiseCollectorFunction => Value::BuiltinPromiseCollectorFunction, Object::BuiltinProxyRevokerFunction => Value::BuiltinProxyRevokerFunction, Object::ECMAScriptAsyncFunction => Value::ECMAScriptAsyncFunction, @@ -313,7 +312,7 @@ impl TryFrom for Object { Value::BuiltinGeneratorFunction => Ok(Object::BuiltinGeneratorFunction), Value::BuiltinConstructorFunction => Ok(Object::BuiltinConstructorFunction), Value::BuiltinPromiseResolveFunction => Ok(Object::BuiltinPromiseResolveFunction), - Value::BuiltinPromiseRejectFunction => Ok(Object::BuiltinPromiseRejectFunction), + Value::BuiltinPromiseRejectFunction(d) => Ok(Object::BuiltinPromiseRejectFunction(d)), Value::BuiltinPromiseCollectorFunction => Ok(Object::BuiltinPromiseCollectorFunction), Value::BuiltinProxyRevokerFunction => Ok(Object::BuiltinProxyRevokerFunction), Value::ECMAScriptAsyncFunction => Ok(Object::ECMAScriptAsyncFunction), diff --git a/nova_vm/src/ecmascript/types/language/value.rs b/nova_vm/src/ecmascript/types/language/value.rs index 71f806b7b..381dd450f 100644 --- a/nova_vm/src/ecmascript/types/language/value.rs +++ b/nova_vm/src/ecmascript/types/language/value.rs @@ -4,9 +4,7 @@ use crate::{ ecmascript::{ abstract_operations::type_conversion::{ to_big_int, to_int32, to_number, to_numeric, to_uint32, - }, - execution::{Agent, JsResult}, - scripts_and_modules::module::ModuleIdentifier, + }, builtins::control_abstraction_objects::promise_objects::promise_abstract_operations::BuiltinPromiseRejectFunctionIndex, execution::{Agent, JsResult}, scripts_and_modules::module::ModuleIdentifier }, heap::indexes::{ ArrayBufferIndex, ArrayIndex, BigIntIndex, BoundFunctionIndex, BuiltinFunctionIndex, @@ -62,7 +60,7 @@ pub enum Value { BuiltinGeneratorFunction, BuiltinConstructorFunction, BuiltinPromiseResolveFunction, - BuiltinPromiseRejectFunction, + BuiltinPromiseRejectFunction(BuiltinPromiseRejectFunctionIndex), BuiltinPromiseCollectorFunction, BuiltinProxyRevokerFunction, ECMAScriptAsyncFunction, @@ -186,7 +184,7 @@ pub(crate) const BUILTIN_CONSTRUCTOR_FUNCTION_DISCRIMINANT: u8 = pub(crate) const BUILTIN_PROMISE_RESOLVE_FUNCTION_DISCRIMINANT: u8 = value_discriminant(Value::BuiltinPromiseResolveFunction); pub(crate) const BUILTIN_PROMISE_REJECT_FUNCTION_DISCRIMINANT: u8 = - value_discriminant(Value::BuiltinPromiseRejectFunction); + value_discriminant(Value::BuiltinPromiseRejectFunction(BuiltinPromiseRejectFunctionIndex::from_u32_index(0))); pub(crate) const BUILTIN_PROMISE_COLLECTOR_FUNCTION_DISCRIMINANT: u8 = value_discriminant(Value::BuiltinPromiseCollectorFunction); pub(crate) const BUILTIN_PROXY_REVOKER_FUNCTION: u8 = diff --git a/nova_vm/src/ecmascript/types/spec.rs b/nova_vm/src/ecmascript/types/spec.rs index 250892bdf..c279dba83 100644 --- a/nova_vm/src/ecmascript/types/spec.rs +++ b/nova_vm/src/ecmascript/types/spec.rs @@ -1,8 +1,10 @@ mod data_block; mod property_descriptor; mod reference; +mod abstract_closure; pub(crate) use data_block::DataBlock; pub use property_descriptor::PropertyDescriptor; pub use reference::ReferencedName; pub(crate) use reference::*; +pub(crate) use abstract_closure::AbstractClosureHeapData; diff --git a/nova_vm/src/ecmascript/types/spec/abstract_closure.rs b/nova_vm/src/ecmascript/types/spec/abstract_closure.rs new file mode 100644 index 000000000..a2ca0e41e --- /dev/null +++ b/nova_vm/src/ecmascript/types/spec/abstract_closure.rs @@ -0,0 +1,23 @@ +use std::fmt::Debug; + +use crate::{ecmascript::{builtins::ArgumentsList, execution::{Agent, JsResult, RealmIdentifier}, types::Value}, heap::indexes::ObjectIndex}; + +pub struct AbstractClosureHeapData { + pub(crate) object_index: Option, + pub(crate) length: u8, + /// #### \[\[Realm]] + /// A Realm Record that represents the realm in which the function was + /// created. + pub(crate) realm: RealmIdentifier, + /// #### \[\[InitialName]] + /// A String that is the initial name of the function. It is used by + /// 20.2.3.5 (`Function.prototype.toString()`). + pub(crate) initial_name: Option, + pub(crate) behaviour: Box) -> JsResult>, +} + +impl Debug for AbstractClosureHeapData { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("AbstractClosureHeapData").field("object_index", &self.object_index).field("length", &self.length).field("realm", &self.realm).field("initial_name", &self.initial_name).field("behaviour", &"some closure").finish() + } +} \ No newline at end of file diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index efe2c7219..166ceeb4e 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -33,26 +33,9 @@ use self::{ }; use crate::ecmascript::{ builtins::{ - data_view::{data::DataViewHeapData, DataView}, - date::{data::DateHeapData, Date}, - embedder_object::data::EmbedderObjectHeapData, - error::{Error, ErrorHeapData}, - finalization_registry::{data::FinalizationRegistryHeapData, FinalizationRegistry}, - map::{data::MapHeapData, Map}, - module::data::ModuleHeapData, - primitive_objects::PrimitiveObjectHeapData, - promise::data::PromiseHeapData, - proxy::data::ProxyHeapData, - regexp::RegExpHeapData, - set::{data::SetHeapData, Set}, - shared_array_buffer::{data::SharedArrayBufferHeapData, SharedArrayBuffer}, - typed_array::{data::TypedArrayHeapData, TypedArray}, - weak_map::{data::WeakMapHeapData, WeakMap}, - weak_ref::{data::WeakRefHeapData, WeakRef}, - weak_set::{data::WeakSetHeapData, WeakSet}, - Array, ArrayBuffer, + control_abstraction_objects::promise_objects::promise_abstract_operations::{promise_capability_records::PromiseCapabilityRecord, promise_reaction_records::PromiseReactionRecord, PromiseRejectFunctionHeapData}, data_view::{data::DataViewHeapData, DataView}, date::{data::DateHeapData, Date}, embedder_object::data::EmbedderObjectHeapData, error::{Error, ErrorHeapData}, finalization_registry::{data::FinalizationRegistryHeapData, FinalizationRegistry}, map::{data::MapHeapData, Map}, module::data::ModuleHeapData, primitive_objects::PrimitiveObjectHeapData, promise::data::PromiseHeapData, proxy::data::ProxyHeapData, regexp::RegExpHeapData, set::{data::SetHeapData, Set}, shared_array_buffer::{data::SharedArrayBufferHeapData, SharedArrayBuffer}, typed_array::{data::TypedArrayHeapData, TypedArray}, weak_map::{data::WeakMapHeapData, WeakMap}, weak_ref::{data::WeakRefHeapData, WeakRef}, weak_set::{data::WeakSetHeapData, WeakSet}, Array, ArrayBuffer }, - types::BUILTIN_STRINGS_LIST, + types::{AbstractClosureHeapData, BUILTIN_STRINGS_LIST}, }; use crate::ecmascript::{ builtins::{ArrayBufferHeapData, ArrayHeapData, BuiltinFunction}, @@ -74,6 +57,7 @@ pub struct Heap { pub arrays: Vec>, pub bigints: Vec>, pub bound_functions: Vec>, + pub abstract_closures: Vec>, pub builtin_functions: Vec>, pub data_views: Vec>, pub dates: Vec>, @@ -92,6 +76,9 @@ pub struct Heap { pub numbers: Vec>, pub objects: Vec>, pub primitive_objects: Vec>, + pub promise_capability_records: Vec>, + pub promise_reaction_records: Vec>, + pub promise_reject_functions: Vec>, pub promises: Vec>, pub proxys: Vec>, pub realms: Vec>, @@ -307,6 +294,7 @@ impl CreateHeapData for Heap { impl Heap { pub fn new() -> Heap { let mut heap = Heap { + abstract_closures: Vec::with_capacity(0), array_buffers: Vec::with_capacity(1024), arrays: Vec::with_capacity(1024), bigints: Vec::with_capacity(1024), @@ -335,6 +323,9 @@ impl Heap { numbers: Vec::with_capacity(1024), objects: Vec::with_capacity(1024), primitive_objects: Vec::with_capacity(0), + promise_capability_records: Vec::with_capacity(0), + promise_reaction_records: Vec::with_capacity(0), + promise_reject_functions: Vec::with_capacity(0), promises: Vec::with_capacity(0), proxys: Vec::with_capacity(0), realms: Vec::with_capacity(1), diff --git a/nova_vm/src/heap/heap_gc.rs b/nova_vm/src/heap/heap_gc.rs index b9da96742..6368e8968 100644 --- a/nova_vm/src/heap/heap_gc.rs +++ b/nova_vm/src/heap/heap_gc.rs @@ -44,6 +44,7 @@ pub fn heap_gc(heap: &mut Heap) { while !queues.is_empty() { let Heap { + abstract_closures, array_buffers, arrays, bigints, @@ -63,6 +64,9 @@ pub fn heap_gc(heap: &mut Heap) { numbers, objects, primitive_objects, + promise_capability_records, + promise_reaction_records, + promise_reject_functions, promises, proxys, realms, @@ -680,6 +684,7 @@ fn sweep(heap: &mut Heap, bits: &HeapBits) { let compactions = CompactionLists::create_from_bits(bits); let Heap { + abstract_closures, array_buffers, arrays, bigints, @@ -699,6 +704,9 @@ fn sweep(heap: &mut Heap, bits: &HeapBits) { numbers, objects, primitive_objects, + promise_capability_records, + promise_reaction_records, + promise_reject_functions, promises, proxys, realms, From d02fe6fbd7092142fe853a7662d0444586ddc81a Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Mon, 20 May 2024 22:36:10 +0300 Subject: [PATCH 3/8] fix errors in cyclic_module_records and fmt --- .../promise_abstract_operations.rs | 89 +++++--- .../promise_capability_records.rs | 56 +++-- .../promise_reaction_records.rs | 43 ++-- .../builtins/module/cyclic_module_records.rs | 192 +++++++++++------- .../ecmascript/builtins/module/semantics.rs | 18 +- .../module/source_text_module_records.rs | 45 ++-- nova_vm/src/ecmascript/execution/agent.rs | 12 +- .../execution/default_host_hooks.rs | 17 +- .../src/ecmascript/types/language/function.rs | 16 +- .../src/ecmascript/types/language/object.rs | 7 +- .../src/ecmascript/types/language/value.rs | 10 +- nova_vm/src/ecmascript/types/spec.rs | 4 +- .../ecmascript/types/spec/abstract_closure.rs | 19 +- nova_vm/src/heap.rs | 23 ++- 14 files changed, 361 insertions(+), 190 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations.rs index e2b9903d8..d97f78824 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations.rs @@ -1,8 +1,21 @@ use std::ops::{Index, IndexMut}; -use crate::{ecmascript::{abstract_operations::testing_and_comparison::is_constructor, builtins::{promise::Promise, ArgumentsList}, execution::{agent::ExceptionType, Agent, JsResult}, types::{AbstractClosureHeapData, Function, IntoValue, Object, String, Value}}, heap::{indexes::{BaseIndex, BoundFunctionIndex}, Heap}}; +use crate::{ + ecmascript::{ + abstract_operations::testing_and_comparison::is_constructor, + builtins::{promise::Promise, ArgumentsList}, + execution::{agent::ExceptionType, Agent, JsResult}, + types::{AbstractClosureHeapData, Function, IntoValue, Object, String, Value}, + }, + heap::{ + indexes::{BaseIndex, BoundFunctionIndex}, + Heap, + }, +}; -use self::{promise_capability_records::PromiseCapability, promise_reaction_records::PromiseReaction}; +use self::{ + promise_capability_records::PromiseCapability, promise_reaction_records::PromiseReaction, +}; pub(crate) mod promise_capability_records; pub(crate) mod promise_reaction_records; @@ -17,7 +30,10 @@ pub(crate) struct PromiseResolvingFunctions { /// The abstract operation CreateResolvingFunctions takes argument promise (a /// Promise) and returns a Record with fields \[\[Resolve\]\] (a function /// object) and \[\[Reject\]\] (a function object). -pub(crate) fn create_resolving_functions(agent: &mut Agent, promise: Promise) -> PromiseResolvingFunctions { +pub(crate) fn create_resolving_functions( + agent: &mut Agent, + promise: Promise, +) -> PromiseResolvingFunctions { // 1. Let alreadyResolved be the Record { [[Value]]: false }. let already_resolved = false; // 2. Let stepsResolve be the algorithm steps defined in Promise Resolve Functions. @@ -38,7 +54,9 @@ pub(crate) fn create_resolving_functions(agent: &mut Agent, promise: Promise) -> // 10. Set reject.[[Promise]] to promise. // 11. Set reject.[[AlreadyResolved]] to alreadyResolved. agent.heap.promise_reject_functions.push(Some(reject)); - let reject = BuiltinPromiseRejectFunction(BuiltinPromiseRejectFunctionIndex::last(&agent.heap.promise_reject_functions)); + let reject = BuiltinPromiseRejectFunction(BuiltinPromiseRejectFunctionIndex::last( + &agent.heap.promise_reject_functions, + )); // 12. Return the Record { [[Resolve]]: resolve, [[Reject]]: reject }. PromiseResolvingFunctions { resolve, reject } } @@ -47,7 +65,7 @@ pub(crate) fn create_resolving_functions(agent: &mut Agent, promise: Promise) -> /// /// A promise reject function is an anonymous built-in function that has /// \[\[Promise\]\] and \[\[AlreadyResolved\]\] internal slots. -/// +/// /// The "length" property of a promise reject function is 1𝔽. #[derive(Debug, Clone, Copy)] pub(crate) struct PromiseRejectFunctionHeapData { @@ -127,7 +145,6 @@ impl PromiseRejectFunctionHeapData { } } - /// ### [27.2.1.3.2 Promise Resolve Functions]() /// /// A promise resolve function is an anonymous built-in function that has [[Promise]] and [[AlreadyResolved]] internal slots. @@ -185,16 +202,23 @@ pub(crate) fn fulfill_promise(agent: &mut Agent, promise: Promise, value: Value) /// promise and extract its resolve and reject functions. The promise plus the /// resolve and reject functions are used to initialize a new PromiseCapability /// Record. -pub(crate) fn new_promise_capability(agent: &mut Agent, c: Value) -> JsResult { +/// +/// NOTE: The argument `c` can take None to signify that the current realm's +/// %Promise% intrinsic should be used as the constructor. +pub(crate) fn new_promise_capability( + agent: &mut Agent, + c: Option, +) -> JsResult { + // 2. NOTE: C is assumed to be a constructor function that supports the parameter conventions of the Promise constructor (see 27.2.3.1). + let Some(c) = c else { + todo!("PromiseConstructor quick-route") + }; + // 1. If IsConstructor(C) is false, throw a TypeError exception. if !is_constructor(agent, c) { return Err(agent.throw_exception(ExceptionType::TypeError, "Not a constructor")); } - // 2. NOTE: C is assumed to be a constructor function that supports the parameter conventions of the Promise constructor (see 27.2.3.1). - if c == agent.current_realm().intrinsics().promise().into_value() { - todo!("PromiseConstructor quick-route") - } - + // 3. Let resolvingFunctions be the Record { [[Resolve]]: undefined, [[Reject]]: undefined }. struct SettableResolvingFunction { resolve: Option, @@ -206,21 +230,26 @@ pub(crate) fn new_promise_capability(agent: &mut Agent, c: Value) -> JsResult| { - // a. If resolvingFunctions.[[Resolve]] is not undefined, throw a TypeError exception. + agent + .heap + .abstract_closures + .push(Some(AbstractClosureHeapData { + object_index: None, + length: 2, + realm: agent.current_realm_id(), + initial_name: Some(String::EMPTY_STRING), + behaviour: Box::new( + |agent: &mut Agent, this_value: Value, arguments: Option| { + // a. If resolvingFunctions.[[Resolve]] is not undefined, throw a TypeError exception. - // b. If resolvingFunctions.[[Reject]] is not undefined, throw a TypeError exception. - // c. Set resolvingFunctions.[[Resolve]] to resolve. - // d. Set resolvingFunctions.[[Reject]] to reject. - // e. Return undefined. - Ok(Value::Undefined) - }), - })); + // b. If resolvingFunctions.[[Reject]] is not undefined, throw a TypeError exception. + // c. Set resolvingFunctions.[[Resolve]] to resolve. + // d. Set resolvingFunctions.[[Reject]] to reject. + // e. Return undefined. + Ok(Value::Undefined) + }, + ), + })); // 5. Let executor be CreateBuiltinFunction(executorClosure, 2, "", « »). // 6. Let promise be ? Construct(C, « executor »). // 7. If IsCallable(resolvingFunctions.[[Resolve]]) is false, throw a TypeError exception. @@ -228,7 +257,7 @@ pub(crate) fn new_promise_capability(agent: &mut Agent, c: Value) -> JsResult for Heap { /// a. Set value to ! value. /// ``` #[inline(always)] -pub(crate) fn if_abrupt_reject_promise(agent: &mut Agent, value: JsResult, capability: PromiseCapability) -> JsResult { +pub(crate) fn if_abrupt_reject_promise( + agent: &mut Agent, + value: JsResult, + capability: PromiseCapability, +) -> JsResult { value.or_else(|err| { // If abrupt completion, call reject and make caller return the // capability promise - let PromiseCapabilityRecord { promise, reject, .. } = agent[capability]; - call_function(agent, reject, Value::Undefined, Some(ArgumentsList(&[err.0])))?; + let PromiseCapabilityRecord { + promise, reject, .. + } = agent[capability]; + call_function( + agent, + reject, + Value::Undefined, + Some(ArgumentsList(&[err.0])), + )?; // Note: We return an error here so that caller gets to call this // function with the ? operator Err(JsError(promise.into_value())) }) -} \ No newline at end of file +} diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_reaction_records.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_reaction_records.rs index 1f0c8e846..eae96447e 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_reaction_records.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_reaction_records.rs @@ -1,13 +1,16 @@ use std::ops::{Index, IndexMut}; -use crate::{ecmascript::execution::Agent, heap::{indexes::BaseIndex, Heap}}; +use crate::{ + ecmascript::execution::Agent, + heap::{indexes::BaseIndex, Heap}, +}; use super::promise_capability_records::PromiseCapability; -/// \[\[Type\]\] -/// -/// fulfill or reject -/// +/// \[\[Type\]\] +/// +/// fulfill or reject +/// /// The \[\[Type\]\] is used when \[\[Handler\]\] is empty to allow for /// behaviour specific to the settlement type. #[derive(Debug, Clone, Copy)] @@ -16,14 +19,14 @@ pub(crate) enum PromiseReactionType { Reject, } -/// \[\[Handler\]\] -/// -/// a JobCallback Record or empty -/// +/// \[\[Handler\]\] +/// +/// a JobCallback Record or empty +/// /// The function that should be applied to the incoming value, and whose /// return value will govern what happens to the derived promise. If /// \[\[Handler\]\] is empty, a function that depends on the value of -/// \[\[Type\]\] will be used instead. +/// \[\[Type\]\] will be used instead. #[derive(Debug, Clone, Copy)] pub(crate) enum PromiseReactionHandler { Empty(PromiseReactionType), @@ -32,21 +35,21 @@ pub(crate) enum PromiseReactionHandler { #[derive(Debug, Clone, Copy)] pub(crate) struct PromiseReactionRecord { - /// \[\[Capability\]\] - /// - /// a PromiseCapability Record or undefined - /// + /// \[\[Capability\]\] + /// + /// a PromiseCapability Record or undefined + /// /// The capabilities of the promise for which this record provides a /// reaction handler. pub(crate) capability: Option, - /// \[\[Handler\]\] - /// - /// a JobCallback Record or empty - /// + /// \[\[Handler\]\] + /// + /// a JobCallback Record or empty + /// /// The function that should be applied to the incoming value, and whose /// return value will govern what happens to the derived promise. If /// \[\[Handler\]\] is empty, a function that depends on the value of - /// \[\[Type\]\] will be used instead. + /// \[\[Type\]\] will be used instead. pub(crate) handler: PromiseReactionHandler, } @@ -86,4 +89,4 @@ impl IndexMut for Heap { .as_mut() .expect("PromiseReaction slot empty") } -} \ No newline at end of file +} diff --git a/nova_vm/src/ecmascript/builtins/module/cyclic_module_records.rs b/nova_vm/src/ecmascript/builtins/module/cyclic_module_records.rs index dfdd54974..b879e23dc 100644 --- a/nova_vm/src/ecmascript/builtins/module/cyclic_module_records.rs +++ b/nova_vm/src/ecmascript/builtins/module/cyclic_module_records.rs @@ -1,10 +1,19 @@ -use std::any::Any; +use std::{any::Any, sync::atomic::AtomicU16}; use oxc_span::Atom; use crate::ecmascript::{ abstract_operations::operations_on_objects::call_function, - builtins::{control_abstraction_objects::promise_objects::promise_abstract_operations::promise_capability_records::PromiseCapability, create_builtin_function, error::Error, module::source_text_module_records::get_imported_module, promise::Promise, ArgumentsList}, + builtins::{ + control_abstraction_objects::promise_objects::promise_abstract_operations::{ + new_promise_capability, promise_capability_records::PromiseCapability, + }, + create_builtin_function, + error::Error, + module::source_text_module_records::get_imported_module, + promise::Promise, + ArgumentsList, + }, execution::{agent::JsError, Agent, JsResult}, types::{String, Value}, }; @@ -89,7 +98,7 @@ pub(crate) struct LoadedModuleRecord { pub(super) module: Module, } -#[derive(Debug, Clone)] +#[derive(Debug)] pub(crate) struct CyclicModuleRecord { /// [\[\[Status\]\]](CyclicModuleRecordStatus) pub(super) status: CyclicModuleRecordStatus, @@ -154,7 +163,7 @@ pub(crate) struct CyclicModuleRecord { /// number of asynchronous dependency modules remaining to execute for this /// module. A module with asynchronous dependencies will be executed when /// this field reaches 0 and there are no execution errors. - pub(super) pending_async_dependencies: Option, + pub(super) pending_async_dependencies: Option, } impl CyclicModuleRecord { @@ -223,7 +232,7 @@ impl Module { ) -> Promise { // 1. If hostDefined is not present, let hostDefined be empty. // TODO: 2. Let pc be ! NewPromiseCapability(%Promise%). - let pc = (); + let pc = new_promise_capability(agent, None).unwrap(); // 3. Let state be the GraphLoadingState Record { let mut state = GraphLoadingStateRecord { // [[PromiseCapability]]: pc, @@ -289,7 +298,9 @@ impl Module { fn initialize_environment(self, agent: &mut Agent) {} - fn execute_module(self, agent: &mut Agent, promise_capability: Option<()>) {} + fn execute_module(self, agent: &mut Agent, promise_capability: Option<()>) -> JsResult<()> { + todo!() + } } /// ### [16.2.1.5.1.1 InnerModuleLoading ( state, module )](https://tc39.es/ecma262/#sec-InnerModuleLoading) @@ -310,7 +321,7 @@ fn inner_module_loading(agent: &mut Agent, state: &mut GraphLoadingStateRecord, // c. Set state.[[PendingModulesCount]] to state.[[PendingModulesCount]] + requestedModulesCount. state.pending_modules_count += requested_modules_count as u16; // d. For each String required of module.[[RequestedModules]], do - for required in agent[module].cyclic.requested_modules.iter() { + for required in agent[module].cyclic.requested_modules.clone().iter() { // i. If module.[[LoadedModules]] contains a Record whose [[Specifier]] is required, then let record = agent[module] .cyclic @@ -328,7 +339,7 @@ fn inner_module_loading(agent: &mut Agent, state: &mut GraphLoadingStateRecord, // agent, (), // module, &required, - state.host_defined, + state.host_defined.as_ref().map(|boxed| boxed.as_ref()), (), // state ); // 2. NOTE: HostLoadImportedModule will call FinishLoadingImportedModule, @@ -349,7 +360,7 @@ fn inner_module_loading(agent: &mut Agent, state: &mut GraphLoadingStateRecord, // a. Set state.[[IsLoading]] to false. state.is_loading = false; // b. For each Cyclic Module Record loaded of state.[[Visited]], do - for _loaded in state.visited { + for _loaded in &state.visited { // TODO: i. If loaded.[[Status]] is new, set loaded.[[Status]] to unlinked. } // c. Perform ! Call(state.[[PromiseCapability]].[[Resolve]], undefined, « undefined »). @@ -496,9 +507,9 @@ fn inner_module_linking( // 8. Append module to stack. stack.push(module); // 9. For each String required of module.[[RequestedModules]], do - for required in agent[module].cyclic.requested_modules.iter() { + for required in agent[module].cyclic.requested_modules.clone().iter() { // a. Let requiredModule be GetImportedModule(module, required). - let required_module = get_imported_module(agent, module, *required); + let required_module = get_imported_module(agent, module, required.clone()); // b. Set index to ? InnerModuleLinking(requiredModule, stack, index). index = inner_module_linking(agent, required_module, stack, index)?; // c. If requiredModule is a Cyclic Module Record, then @@ -540,13 +551,7 @@ fn inner_module_linking( // 10. Perform ? module.InitializeEnvironment(). module.initialize_environment(agent); // 11. Assert: module occurs exactly once in stack. - assert_eq!( - stack - .iter() - .filter(|entry| **entry == module) - .count(), - 1 - ); + assert_eq!(stack.iter().filter(|entry| **entry == module).count(), 1); // 12. Assert: module.[[DFSAncestorIndex]] ≤ module.[[DFSIndex]]. match &mut agent[module].cyclic.status { CyclicModuleRecordStatus::Evaluating(index, ancestor_index) => { @@ -615,8 +620,8 @@ pub(crate) fn evaluate(agent: &mut Agent, mut module: Module) -> Promise { // 5. Let stack be a new empty List. let mut stack = vec![]; // 6. Let capability be ! NewPromiseCapability(%Promise%). - let capability = (); // new_promise_capability(); - // 7. Set module.[[TopLevelCapability]] to capability. + let capability = new_promise_capability(agent, None).unwrap(); + // 7. Set module.[[TopLevelCapability]] to capability. agent[module].cyclic.top_level_capability = Some(capability); // 8. Let result be Completion(InnerModuleEvaluation(module, stack, 0)). let result = inner_module_evaluation(agent, module, &mut stack, 0); @@ -698,9 +703,8 @@ pub(crate) fn inner_module_evaluation( // d. Return index. return Ok(index); } - let module_borrow = &agent[module]; // 2. If module.[[Status]] is either evaluating-async or evaluated, then - match module_borrow.cyclic.status { + match agent[module].cyclic.status { // a. If module.[[EvaluationError]] is empty, return index. // b. Otherwise, return ? module.[[EvaluationError]]. CyclicModuleRecordStatus::EvaluatingAsync => { @@ -718,23 +722,26 @@ pub(crate) fn inner_module_evaluation( Ok(index) } CyclicModuleRecordStatus::Linked => { - // 5. Set module.[[Status]] to evaluating. - // 6. Set module.[[DFSIndex]] to index. - // 7. Set module.[[DFSAncestorIndex]] to index. - module_borrow.cyclic.status = CyclicModuleRecordStatus::Evaluating( - DFSIndex::new(index), - DFSAncestorIndex::new(index), - ); - // 8. Set module.[[PendingAsyncDependencies]] to 0. - module_borrow.cyclic.pending_async_dependencies = Some(0); + { + let module_borrow = &mut agent[module]; + // 5. Set module.[[Status]] to evaluating. + // 6. Set module.[[DFSIndex]] to index. + // 7. Set module.[[DFSAncestorIndex]] to index. + module_borrow.cyclic.status = CyclicModuleRecordStatus::Evaluating( + DFSIndex::new(index), + DFSAncestorIndex::new(index), + ); + // 8. Set module.[[PendingAsyncDependencies]] to 0. + module_borrow.cyclic.pending_async_dependencies = Some(AtomicU16::new(0)); + } // 9. Set index to index + 1. let mut index = index + 1; // 10. Append module to stack. stack.push(module); // 11. For each String required of module.[[RequestedModules]], do - for required in module_borrow.cyclic.requested_modules.iter() { + for required in agent[module].cyclic.requested_modules.clone().into_iter() { // a. Let requiredModule be GetImportedModule(module, required). - let mut required_module = get_imported_module(agent, module, *required); + let mut required_module = get_imported_module(agent, module, required.clone()); // b. Set index to ? InnerModuleEvaluation(requiredModule, stack, index). index = inner_module_evaluation(agent, required_module, stack, index)?; // c. If requiredModule is a Cyclic Module Record, then @@ -770,13 +777,14 @@ pub(crate) fn inner_module_evaluation( // v. If requiredModule.[[AsyncEvaluation]] is true, then if required_module_borrow.cyclic.async_evaluation { // 1. Set module.[[PendingAsyncDependencies]] to module.[[PendingAsyncDependencies]] + 1. - agent[module] + *agent[module] .cyclic .pending_async_dependencies .as_mut() - .map(|val| *val += 1); + .unwrap() + .get_mut() += 1; // 2. Append module to requiredModule.[[AsyncParentModules]]. - required_module_borrow + agent[required_module] .cyclic .async_parent_modules .push(module); @@ -791,13 +799,14 @@ pub(crate) fn inner_module_evaluation( // v. If requiredModule.[[AsyncEvaluation]] is true, then if required_module_borrow.cyclic.async_evaluation { // 1. Set module.[[PendingAsyncDependencies]] to module.[[PendingAsyncDependencies]] + 1. - agent[module] + *agent[module] .cyclic .pending_async_dependencies .as_mut() - .map(|val| *val += 1); + .unwrap() + .get_mut() += 1; // 2. Append module to requiredModule.[[AsyncParentModules]]. - required_module_borrow + agent[required_module] .cyclic .async_parent_modules .push(module); @@ -817,13 +826,14 @@ pub(crate) fn inner_module_evaluation( // v. If requiredModule.[[AsyncEvaluation]] is true, then if required_module_borrow.cyclic.async_evaluation { // 1. Set module.[[PendingAsyncDependencies]] to module.[[PendingAsyncDependencies]] + 1. - agent[module] + *agent[module] .cyclic .pending_async_dependencies .as_mut() - .map(|val| *val += 1); + .unwrap() + .get_mut() += 1; // 2. Append module to requiredModule.[[AsyncParentModules]]. - required_module_borrow + agent[required_module] .cyclic .async_parent_modules .push(module); @@ -838,13 +848,14 @@ pub(crate) fn inner_module_evaluation( // v. If requiredModule.[[AsyncEvaluation]] is true, then if required_module_borrow.cyclic.async_evaluation { // 1. Set module.[[PendingAsyncDependencies]] to module.[[PendingAsyncDependencies]] + 1. - agent[module] + *agent[module] .cyclic .pending_async_dependencies .as_mut() - .map(|val| *val += 1); + .unwrap() + .get_mut() += 1; // 2. Append module to requiredModule.[[AsyncParentModules]]. - required_module_borrow + agent[required_module] .cyclic .async_parent_modules .push(module); @@ -860,7 +871,13 @@ pub(crate) fn inner_module_evaluation( } } // 12. If module.[[PendingAsyncDependencies]] > 0 or module.[[HasTLA]] is true, then - if agent[module].cyclic.pending_async_dependencies.unwrap() > 0 + if *agent[module] + .cyclic + .pending_async_dependencies + .as_mut() + .unwrap() + .get_mut() + > 0 || agent[module].cyclic.has_top_level_await { // a. Assert: module.[[AsyncEvaluation]] is false and was never previously set to true. @@ -871,7 +888,14 @@ pub(crate) fn inner_module_evaluation( // [[AsyncEvaluation]] fields transition to true is // significant. (See 16.2.1.5.3.4.) // d. If module.[[PendingAsyncDependencies]] = 0, - if agent[module].cyclic.pending_async_dependencies == Some(0) { + if *agent[module] + .cyclic + .pending_async_dependencies + .as_mut() + .unwrap() + .get_mut() + == 0 + { // perform ExecuteAsyncModule(module). execute_async_module(agent, module); } @@ -881,13 +905,7 @@ pub(crate) fn inner_module_evaluation( module.execute_module(agent, None); } // 14. Assert: module occurs exactly once in stack. - assert_eq!( - stack - .iter() - .filter(|entry| **entry == module) - .count(), - 1 - ); + assert_eq!(stack.iter().filter(|entry| **entry == module).count(), 1); let module_borrow = &agent[module]; match module_borrow.cyclic.status { CyclicModuleRecordStatus::Evaluating(index, ancestor_index) => { @@ -980,8 +998,8 @@ pub(crate) fn execute_async_module(agent: &mut Agent, module: Module) { fn fulfilled_closure( agent: &mut Agent, - this_value: Value, - arguments: Option, + _this_value: Value, + _arguments: Option, module: Module, ) -> JsResult { // a. Perform AsyncModuleExecutionFulfilled(module). @@ -992,7 +1010,7 @@ fn fulfilled_closure( fn rejected_closure( agent: &mut Agent, - this_value: Value, + _this_value: Value, arguments: Option, module: Module, ) -> JsResult { @@ -1006,12 +1024,12 @@ fn rejected_closure( /// Cyclic Module Record) and execList (a List of Cyclic Module Records) and /// returns unused. pub(crate) fn gather_available_ancestors( - agent: &mut Agent, + agent: &Agent, module: Module, exec_list: &mut Vec, ) { // 1. For each Cyclic Module Record m of module.[[AsyncParentModules]], do - for m in agent[module].cyclic.async_parent_modules { + for &m in &agent[module].cyclic.async_parent_modules { // a. If execList does not contain m and m.[[CycleRoot]].[[EvaluationError]] is empty, then if !exec_list.contains(&m) && !matches!( @@ -1028,15 +1046,29 @@ pub(crate) fn gather_available_ancestors( // iii. Assert: m.[[AsyncEvaluation]] is true. assert!(agent[m].cyclic.async_evaluation); // iv. Assert: m.[[PendingAsyncDependencies]] > 0. - assert!(agent[m].cyclic.pending_async_dependencies.unwrap() > 0); + + // NOTE: Load is done separately on purpose. A relaxed fetch_sub + // still requires a lock, which we do not need here. + let pending_async_dependencies = agent[m] + .cyclic + .pending_async_dependencies + .as_ref() + .unwrap() + .load(std::sync::atomic::Ordering::Relaxed); + assert!(pending_async_dependencies > 0); // v. Set m.[[PendingAsyncDependencies]] to m.[[PendingAsyncDependencies]] - 1. + let pending_async_dependencies = pending_async_dependencies - 1; agent[m] .cyclic .pending_async_dependencies - .as_mut() - .map(|val| *val -= 1); + .as_ref() + .unwrap() + .store( + pending_async_dependencies, + std::sync::atomic::Ordering::Relaxed, + ); // vi. If m.[[PendingAsyncDependencies]] = 0, then - if agent[m].cyclic.pending_async_dependencies == Some(0) { + if pending_async_dependencies == 0 { // 1. Append m to execList. exec_list.push(m); } @@ -1060,7 +1092,7 @@ pub(crate) fn gather_available_ancestors( /// The abstract operation AsyncModuleExecutionFulfilled takes argument module /// // (a Cyclic Module Record) and returns unused. pub(crate) fn async_module_execution_fulfilled(agent: &mut Agent, module: Module) { - let module_borrow = &agent[module].cyclic; + let module_borrow = &mut agent[module].cyclic; // 1. If module.[[Status]] is evaluated, then match module_borrow.status { CyclicModuleRecordStatus::Evaluated(maybe_evaluation_error) => { @@ -1098,16 +1130,24 @@ pub(crate) fn async_module_execution_fulfilled(agent: &mut Agent, module: Module // 11. Assert: All elements of sortedExecList have their // [[AsyncEvaluation]] field set to true, [[PendingAsyncDependencies]] // field set to 0, and [[EvaluationError]] field set to empty. - for element in exec_list { + for &element in &exec_list { assert!(agent[element].cyclic.async_evaluation); - assert_eq!(agent[element].cyclic.pending_async_dependencies, Some(0)); + assert_eq!( + *agent[element] + .cyclic + .pending_async_dependencies + .as_mut() + .unwrap() + .get_mut(), + 0 + ); assert!(!matches!( agent[element].cyclic.status, CyclicModuleRecordStatus::Evaluated(Some(_)) )); } // 12. For each Cyclic Module Record m of sortedExecList, do - for m in exec_list { + for &m in &exec_list { // a. If m.[[Status]] is evaluated, then if let CyclicModuleRecordStatus::Evaluated(maybe_evaluation_error) = agent[m].cyclic.status { @@ -1125,7 +1165,7 @@ pub(crate) fn async_module_execution_fulfilled(agent: &mut Agent, module: Module // ii. If result is an abrupt completion, then Err(error) => { // 1. Perform AsyncModuleExecutionRejected(m, result.[[Value]]). - async_module_execution_rejected(agent, m, error); + async_module_execution_rejected(agent, m, error.value()); } // iii. Else, Ok(_) => { @@ -1172,9 +1212,19 @@ pub(crate) fn async_module_execution_rejected(agent: &mut Agent, module: Module, agent[module].cyclic.status = CyclicModuleRecordStatus::Evaluated(Some(EvaluationError(JsError(error)))); // 7. For each Cyclic Module Record m of module.[[AsyncParentModules]], do - for m in agent[module].cyclic.async_parent_modules { - // a. Perform AsyncModuleExecutionRejected(m, error). - async_module_execution_rejected(agent, m, error); + { + // SAFETY: Calling into async_module_execution_rejected with parent + // modules will not move nor take a mutable borrow on + // async_parent_modules. + let async_parent_modules = unsafe { + std::mem::transmute::<&[Module], &'static [Module]>( + &agent[module].cyclic.async_parent_modules, + ) + }; + for m in async_parent_modules { + // a. Perform AsyncModuleExecutionRejected(m, error). + async_module_execution_rejected(agent, *m, error); + } } // 8. If module.[[TopLevelCapability]] is not empty, then if agent[module].cyclic.top_level_capability.is_some() { diff --git a/nova_vm/src/ecmascript/builtins/module/semantics.rs b/nova_vm/src/ecmascript/builtins/module/semantics.rs index f7dc7c5a9..ba8bd4ece 100644 --- a/nova_vm/src/ecmascript/builtins/module/semantics.rs +++ b/nova_vm/src/ecmascript/builtins/module/semantics.rs @@ -35,7 +35,7 @@ pub(crate) fn module_requests(agent: &mut Agent, program: &Program<'_>) -> Box<[ let mut strings = vec![]; // Module : [empty] // 1. Return a new empty List. - for statement in program.body { + for statement in &program.body { // ModuleItemList : ModuleItem // 1. Return ModuleRequests of ModuleItem. @@ -99,13 +99,13 @@ pub fn import_entries(agent: &mut Agent, program: &Program<'_>) -> Box<[ImportEn // 1. Let entries1 be ImportEntries of ModuleItemList. // 2. Let entries2 be ImportEntries of ModuleItem. // 3. Return the list-concatenation of entries1 and entries2. - for statement in program.body { + for statement in &program.body { match statement { oxc_ast::ast::Statement::ModuleDeclaration(decl) => { match decl.deref() { oxc_ast::ast::ModuleDeclaration::ImportDeclaration(decl) => { // ImportDeclaration : import ModuleSpecifier ; - let Some(specifiers) = decl.specifiers else { + let Some(specifiers) = &decl.specifiers else { // 1. Return a new empty List. continue; }; @@ -118,10 +118,12 @@ pub fn import_entries(agent: &mut Agent, program: &Program<'_>) -> Box<[ImportEn // ImportEntryRecords. The Program's internal data is not moved, so the // Atom references are "safe". let module = unsafe { - std::mem::transmute::, Atom<'static>>(decl.source.value) + std::mem::transmute::, Atom<'static>>( + decl.source.value.clone(), + ) }; // 2. Return ImportEntriesForModule of ImportClause with argument module. - import_entries_for_module(agent, &specifiers, module, &mut |entry| { + import_entries_for_module(agent, specifiers, module, &mut |entry| { entries.push(entry) }); } @@ -182,7 +184,7 @@ fn import_entries_for_module( // 2. Let entry be the ImportEntry Record { let entry = ImportEntryRecord { // [[ModuleRequest]]: module, - module_request: specifier.local.name, + module_request: module.clone(), // [[ImportName]]: localName / importName, import_name: BUILTIN_STRING_MEMORY.default.into(), // [[LocalName]]: localName @@ -199,7 +201,7 @@ fn import_entries_for_module( // 2. Let defaultEntry be the ImportEntry Record { let default_entry = ImportEntryRecord { // [[ModuleRequest]]: module, - module_request: specifier.local.name, + module_request: module.clone(), // [[ImportName]]: "default", import_name: BUILTIN_STRING_MEMORY.default.into(), // [[LocalName]]: localName @@ -217,7 +219,7 @@ fn import_entries_for_module( // 2. Let entry be the ImportEntry Record { let entry = ImportEntryRecord { // [[ModuleRequest]]: module, - module_request: specifier.local.name, + module_request: module.clone(), // [[ImportName]]: namespace-object, import_name: ImportName::NamespaceObject, // [[LocalName]]: localName diff --git a/nova_vm/src/ecmascript/builtins/module/source_text_module_records.rs b/nova_vm/src/ecmascript/builtins/module/source_text_module_records.rs index 5231c5949..e0369704c 100644 --- a/nova_vm/src/ecmascript/builtins/module/source_text_module_records.rs +++ b/nova_vm/src/ecmascript/builtins/module/source_text_module_records.rs @@ -287,7 +287,6 @@ pub(crate) fn parse_module( .is_some(); // 12. Return Source Text Module Record { Ok(ModuleHeapData { - object_index: None, r#abstract: ModuleRecord { // [[Realm]]: realm, @@ -395,14 +394,13 @@ pub(crate) fn get_exported_names( // c. Let starNames be requestedModule.GetExportedNames(exportStarSet). let star_names = requested_module.get_exported_names(agent, export_start_set); // d. For each element n of starNames, do - for n in star_names.iter() { + for &n in star_names.iter() { // i. If n is not "default", then - if n != BUILTIN_STRING_MEMORY.default && // 1. If exportedNames does not contain n, then - !exported_names.contains(n) { - // a. Append n to exportedNames. - exported_names.push(n); - } + if n != BUILTIN_STRING_MEMORY.default && !exported_names.contains(&n) { + // a. Append n to exportedNames. + exported_names.push(n); + } } } // 9. Return exportedNames. @@ -591,27 +589,36 @@ pub(crate) fn execute_module( Ok(()) } - /// 16.2.1.7 GetImportedModule ( referrer, specifier ) /// /// The abstract operation GetImportedModule takes arguments referrer (a Cyclic Module Record) and specifier (a String) and returns a Module Record. It performs the following steps when called: -pub(super) fn get_imported_module(agent: &mut Agent, referrer: Module, specifier: Atom<'static>) -> Module { +pub(super) fn get_imported_module( + agent: &Agent, + referrer: Module, + specifier: Atom<'static>, +) -> Module { // 1. Assert: Exactly one element of referrer.[[LoadedModules]] is a Record // whose [[Specifier]] is specifier, since LoadRequestedModules has // completed successfully on referrer prior to invoking this abstract // operation. // 2. Let record be the Record in referrer.[[LoadedModules]] whose [[Specifier]] is specifier. let mut record: Option = None; - assert_eq!(agent[referrer].cyclic.loaded_modules.iter().filter(|loaded_module| { - if loaded_module.specifier == specifier { - record = Some(loaded_module.module); - true - } else { - false - } - }).count(), 1); + assert_eq!( + agent[referrer] + .cyclic + .loaded_modules + .iter() + .filter(|loaded_module| { + if loaded_module.specifier == specifier { + record = Some(loaded_module.module); + true + } else { + false + } + }) + .count(), + 1 + ); // 3. Return record.[[Module]]. record.unwrap() } - - diff --git a/nova_vm/src/ecmascript/execution/agent.rs b/nova_vm/src/ecmascript/execution/agent.rs index cb37db26f..fd2d6050b 100644 --- a/nova_vm/src/ecmascript/execution/agent.rs +++ b/nova_vm/src/ecmascript/execution/agent.rs @@ -64,7 +64,7 @@ pub trait HostHooks: std::fmt::Debug { /// Record), specifier (a String), hostDefined (anything), and payload (a /// GraphLoadingState Record or a PromiseCapability Record) and returns /// unused. - /// + /// /// #### Note /// /// An example of when referrer can be a Realm Record is in a web browser @@ -105,7 +105,7 @@ pub trait HostHooks: std::fmt::Debug { &self, referrer: (), specifier: &str, - host_defined: Option>, + host_defined: Option<&dyn Any>, payload: (), ); /// ### [27.2.1.9 HostPromiseRejectionTracker ( promise, operation )](https://tc39.es/ecma262/#sec-host-promise-rejection-tracker) @@ -113,7 +113,7 @@ pub trait HostHooks: std::fmt::Debug { /// The host-defined abstract operation HostPromiseRejectionTracker takes /// arguments promise (a Promise) and operation ("reject" or "handle") and /// returns unused. It allows host environments to track promise rejections. - /// + /// /// The default implementation of HostPromiseRejectionTracker is to return /// unused. /// @@ -138,7 +138,11 @@ pub trait HostHooks: std::fmt::Debug { /// implementation may hold a reference to promise if operation is "reject", /// since it is expected that rejections will be rare and not on hot code /// paths. - fn host_promise_rejection_tracker(&self, promise: Promise, operation: PromiseRejectionOperation); + fn host_promise_rejection_tracker( + &self, + promise: Promise, + operation: PromiseRejectionOperation, + ); } /// ### [9.7 Agents](https://tc39.es/ecma262/#sec-agents) diff --git a/nova_vm/src/ecmascript/execution/default_host_hooks.rs b/nova_vm/src/ecmascript/execution/default_host_hooks.rs index 562230ceb..b2b952866 100644 --- a/nova_vm/src/ecmascript/execution/default_host_hooks.rs +++ b/nova_vm/src/ecmascript/execution/default_host_hooks.rs @@ -1,4 +1,7 @@ -use super::{agent::{HostHooks, PromiseRejectionOperation}, JsResult, Realm}; +use super::{ + agent::{HostHooks, PromiseRejectionOperation}, + JsResult, Realm, +}; use crate::ecmascript::{builtins::promise::Promise, types::Function}; #[derive(Debug)] @@ -15,7 +18,7 @@ impl HostHooks for DefaultHostHooks { // The default implementation of HostHasSourceTextAvailable is to return true. true } - + fn host_load_imported_module( &self, referrer: (), @@ -25,9 +28,11 @@ impl HostHooks for DefaultHostHooks { ) { unreachable!("HostLoadImportedModule does not have a default implementation"); } - - fn host_promise_rejection_tracker(&self, promise: Promise, operation: PromiseRejectionOperation) { - } - + fn host_promise_rejection_tracker( + &self, + promise: Promise, + operation: PromiseRejectionOperation, + ) { + } } diff --git a/nova_vm/src/ecmascript/types/language/function.rs b/nova_vm/src/ecmascript/types/language/function.rs index 46cc9d254..ed6de28a7 100644 --- a/nova_vm/src/ecmascript/types/language/function.rs +++ b/nova_vm/src/ecmascript/types/language/function.rs @@ -17,7 +17,10 @@ use super::{ }; use crate::{ ecmascript::{ - builtins::{control_abstraction_objects::promise_objects::promise_abstract_operations::BuiltinPromiseRejectFunctionIndex, ArgumentsList, BuiltinFunction, ECMAScriptFunction}, + builtins::{ + control_abstraction_objects::promise_objects::promise_abstract_operations::BuiltinPromiseRejectFunctionIndex, + ArgumentsList, BuiltinFunction, ECMAScriptFunction, + }, execution::{Agent, JsResult}, types::PropertyDescriptor, }, @@ -37,7 +40,8 @@ pub enum Function { BuiltinGeneratorFunction = BUILTIN_GENERATOR_FUNCTION_DISCRIMINANT, BuiltinConstructorFunction = BUILTIN_CONSTRUCTOR_FUNCTION_DISCRIMINANT, BuiltinPromiseResolveFunction = BUILTIN_PROMISE_RESOLVE_FUNCTION_DISCRIMINANT, - BuiltinPromiseRejectFunction(BuiltinPromiseRejectFunctionIndex) = BUILTIN_PROMISE_REJECT_FUNCTION_DISCRIMINANT, + BuiltinPromiseRejectFunction(BuiltinPromiseRejectFunctionIndex) = + BUILTIN_PROMISE_REJECT_FUNCTION_DISCRIMINANT, BuiltinPromiseCollectorFunction = BUILTIN_PROMISE_COLLECTOR_FUNCTION_DISCRIMINANT, BuiltinProxyRevokerFunction = BUILTIN_PROXY_REVOKER_FUNCTION, ECMAScriptAsyncFunction = ECMASCRIPT_ASYNC_FUNCTION_DISCRIMINANT, @@ -55,7 +59,9 @@ impl std::fmt::Debug for Function { Function::BuiltinGeneratorFunction => todo!(), Function::BuiltinConstructorFunction => todo!(), Function::BuiltinPromiseResolveFunction => todo!(), - Function::BuiltinPromiseRejectFunction(BuiltinPromiseRejectFunction) => write!(f, "BuiltinPromiseRejectFunction({:?})", d), + Function::BuiltinPromiseRejectFunction(BuiltinPromiseRejectFunction) => { + write!(f, "BuiltinPromiseRejectFunction({:?})", d) + } Function::BuiltinPromiseCollectorFunction => todo!(), Function::BuiltinProxyRevokerFunction => todo!(), Function::ECMAScriptAsyncFunction => todo!(), @@ -131,7 +137,9 @@ impl TryFrom for Function { Value::ECMAScriptFunction(d) => Ok(Function::from(d)), Value::BuiltinGeneratorFunction => Ok(Function::BuiltinGeneratorFunction), Value::BuiltinConstructorFunction => Ok(Function::BuiltinConstructorFunction), - Value::BuiltinPromiseResolveFunction(d) => Ok(Function::BuiltinPromiseResolveFunction(d)), + Value::BuiltinPromiseResolveFunction(d) => { + Ok(Function::BuiltinPromiseResolveFunction(d)) + } Value::BuiltinPromiseRejectFunction(d) => Ok(Function::BuiltinPromiseRejectFunction(d)), Value::BuiltinPromiseCollectorFunction => Ok(Function::BuiltinPromiseCollectorFunction), Value::BuiltinProxyRevokerFunction => Ok(Function::BuiltinProxyRevokerFunction), diff --git a/nova_vm/src/ecmascript/types/language/object.rs b/nova_vm/src/ecmascript/types/language/object.rs index 5515c6366..d07c99ea8 100644 --- a/nova_vm/src/ecmascript/types/language/object.rs +++ b/nova_vm/src/ecmascript/types/language/object.rs @@ -33,7 +33,9 @@ use super::{ use crate::{ ecmascript::{ builtins::{ - control_abstraction_objects::promise_objects::promise_abstract_operations::BuiltinPromiseRejectFunctionIndex, date::Date, error::Error, map::Map, set::Set, ArgumentsList, Array, ArrayBuffer, BuiltinFunction, ECMAScriptFunction + control_abstraction_objects::promise_objects::promise_abstract_operations::BuiltinPromiseRejectFunctionIndex, + date::Date, error::Error, map::Map, set::Set, ArgumentsList, Array, ArrayBuffer, + BuiltinFunction, ECMAScriptFunction, }, execution::{Agent, JsResult}, scripts_and_modules::module::ModuleIdentifier, @@ -68,7 +70,8 @@ pub enum Object { BuiltinGeneratorFunction = BUILTIN_GENERATOR_FUNCTION_DISCRIMINANT, BuiltinConstructorFunction = BUILTIN_CONSTRUCTOR_FUNCTION_DISCRIMINANT, BuiltinPromiseResolveFunction = BUILTIN_PROMISE_RESOLVE_FUNCTION_DISCRIMINANT, - BuiltinPromiseRejectFunction(BuiltinPromiseRejectFunctionIndex) = BUILTIN_PROMISE_REJECT_FUNCTION_DISCRIMINANT, + BuiltinPromiseRejectFunction(BuiltinPromiseRejectFunctionIndex) = + BUILTIN_PROMISE_REJECT_FUNCTION_DISCRIMINANT, BuiltinPromiseCollectorFunction = BUILTIN_PROMISE_COLLECTOR_FUNCTION_DISCRIMINANT, BuiltinProxyRevokerFunction = BUILTIN_PROXY_REVOKER_FUNCTION, ECMAScriptAsyncFunction = ECMASCRIPT_ASYNC_FUNCTION_DISCRIMINANT, diff --git a/nova_vm/src/ecmascript/types/language/value.rs b/nova_vm/src/ecmascript/types/language/value.rs index 381dd450f..d6043681d 100644 --- a/nova_vm/src/ecmascript/types/language/value.rs +++ b/nova_vm/src/ecmascript/types/language/value.rs @@ -4,7 +4,10 @@ use crate::{ ecmascript::{ abstract_operations::type_conversion::{ to_big_int, to_int32, to_number, to_numeric, to_uint32, - }, builtins::control_abstraction_objects::promise_objects::promise_abstract_operations::BuiltinPromiseRejectFunctionIndex, execution::{Agent, JsResult}, scripts_and_modules::module::ModuleIdentifier + }, + builtins::control_abstraction_objects::promise_objects::promise_abstract_operations::BuiltinPromiseRejectFunctionIndex, + execution::{Agent, JsResult}, + scripts_and_modules::module::ModuleIdentifier, }, heap::indexes::{ ArrayBufferIndex, ArrayIndex, BigIntIndex, BoundFunctionIndex, BuiltinFunctionIndex, @@ -183,8 +186,9 @@ pub(crate) const BUILTIN_CONSTRUCTOR_FUNCTION_DISCRIMINANT: u8 = value_discriminant(Value::BuiltinConstructorFunction); pub(crate) const BUILTIN_PROMISE_RESOLVE_FUNCTION_DISCRIMINANT: u8 = value_discriminant(Value::BuiltinPromiseResolveFunction); -pub(crate) const BUILTIN_PROMISE_REJECT_FUNCTION_DISCRIMINANT: u8 = - value_discriminant(Value::BuiltinPromiseRejectFunction(BuiltinPromiseRejectFunctionIndex::from_u32_index(0))); +pub(crate) const BUILTIN_PROMISE_REJECT_FUNCTION_DISCRIMINANT: u8 = value_discriminant( + Value::BuiltinPromiseRejectFunction(BuiltinPromiseRejectFunctionIndex::from_u32_index(0)), +); pub(crate) const BUILTIN_PROMISE_COLLECTOR_FUNCTION_DISCRIMINANT: u8 = value_discriminant(Value::BuiltinPromiseCollectorFunction); pub(crate) const BUILTIN_PROXY_REVOKER_FUNCTION: u8 = diff --git a/nova_vm/src/ecmascript/types/spec.rs b/nova_vm/src/ecmascript/types/spec.rs index c279dba83..767d922e6 100644 --- a/nova_vm/src/ecmascript/types/spec.rs +++ b/nova_vm/src/ecmascript/types/spec.rs @@ -1,10 +1,10 @@ +mod abstract_closure; mod data_block; mod property_descriptor; mod reference; -mod abstract_closure; +pub(crate) use abstract_closure::AbstractClosureHeapData; pub(crate) use data_block::DataBlock; pub use property_descriptor::PropertyDescriptor; pub use reference::ReferencedName; pub(crate) use reference::*; -pub(crate) use abstract_closure::AbstractClosureHeapData; diff --git a/nova_vm/src/ecmascript/types/spec/abstract_closure.rs b/nova_vm/src/ecmascript/types/spec/abstract_closure.rs index a2ca0e41e..2de666df2 100644 --- a/nova_vm/src/ecmascript/types/spec/abstract_closure.rs +++ b/nova_vm/src/ecmascript/types/spec/abstract_closure.rs @@ -1,6 +1,13 @@ use std::fmt::Debug; -use crate::{ecmascript::{builtins::ArgumentsList, execution::{Agent, JsResult, RealmIdentifier}, types::Value}, heap::indexes::ObjectIndex}; +use crate::{ + ecmascript::{ + builtins::ArgumentsList, + execution::{Agent, JsResult, RealmIdentifier}, + types::{String, Value}, + }, + heap::indexes::ObjectIndex, +}; pub struct AbstractClosureHeapData { pub(crate) object_index: Option, @@ -18,6 +25,12 @@ pub struct AbstractClosureHeapData { impl Debug for AbstractClosureHeapData { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("AbstractClosureHeapData").field("object_index", &self.object_index).field("length", &self.length).field("realm", &self.realm).field("initial_name", &self.initial_name).field("behaviour", &"some closure").finish() + f.debug_struct("AbstractClosureHeapData") + .field("object_index", &self.object_index) + .field("length", &self.length) + .field("realm", &self.realm) + .field("initial_name", &self.initial_name) + .field("behaviour", &"some closure") + .finish() } -} \ No newline at end of file +} diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index 166ceeb4e..028e0f786 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -33,7 +33,28 @@ use self::{ }; use crate::ecmascript::{ builtins::{ - control_abstraction_objects::promise_objects::promise_abstract_operations::{promise_capability_records::PromiseCapabilityRecord, promise_reaction_records::PromiseReactionRecord, PromiseRejectFunctionHeapData}, data_view::{data::DataViewHeapData, DataView}, date::{data::DateHeapData, Date}, embedder_object::data::EmbedderObjectHeapData, error::{Error, ErrorHeapData}, finalization_registry::{data::FinalizationRegistryHeapData, FinalizationRegistry}, map::{data::MapHeapData, Map}, module::data::ModuleHeapData, primitive_objects::PrimitiveObjectHeapData, promise::data::PromiseHeapData, proxy::data::ProxyHeapData, regexp::RegExpHeapData, set::{data::SetHeapData, Set}, shared_array_buffer::{data::SharedArrayBufferHeapData, SharedArrayBuffer}, typed_array::{data::TypedArrayHeapData, TypedArray}, weak_map::{data::WeakMapHeapData, WeakMap}, weak_ref::{data::WeakRefHeapData, WeakRef}, weak_set::{data::WeakSetHeapData, WeakSet}, Array, ArrayBuffer + control_abstraction_objects::promise_objects::promise_abstract_operations::{ + promise_capability_records::PromiseCapabilityRecord, + promise_reaction_records::PromiseReactionRecord, PromiseRejectFunctionHeapData, + }, + data_view::{data::DataViewHeapData, DataView}, + date::{data::DateHeapData, Date}, + embedder_object::data::EmbedderObjectHeapData, + error::{Error, ErrorHeapData}, + finalization_registry::{data::FinalizationRegistryHeapData, FinalizationRegistry}, + map::{data::MapHeapData, Map}, + module::data::ModuleHeapData, + primitive_objects::PrimitiveObjectHeapData, + promise::data::PromiseHeapData, + proxy::data::ProxyHeapData, + regexp::RegExpHeapData, + set::{data::SetHeapData, Set}, + shared_array_buffer::{data::SharedArrayBufferHeapData, SharedArrayBuffer}, + typed_array::{data::TypedArrayHeapData, TypedArray}, + weak_map::{data::WeakMapHeapData, WeakMap}, + weak_ref::{data::WeakRefHeapData, WeakRef}, + weak_set::{data::WeakSetHeapData, WeakSet}, + Array, ArrayBuffer, }, types::{AbstractClosureHeapData, BUILTIN_STRINGS_LIST}, }; From f0cfbbfb8da3103db91e29b799654776dd663098 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Tue, 21 May 2024 23:51:11 +0300 Subject: [PATCH 4/8] ResolveExport and mostly fix ParseModule --- .../module/abstract_module_records.rs | 26 +- .../builtins/module/cyclic_module_records.rs | 6 +- .../src/ecmascript/builtins/module/data.rs | 2 +- .../ecmascript/builtins/module/semantics.rs | 20 +- .../module/source_text_module_records.rs | 226 ++++++++++++++---- 5 files changed, 219 insertions(+), 61 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/module/abstract_module_records.rs b/nova_vm/src/ecmascript/builtins/module/abstract_module_records.rs index b21d4a26b..644183ebd 100644 --- a/nova_vm/src/ecmascript/builtins/module/abstract_module_records.rs +++ b/nova_vm/src/ecmascript/builtins/module/abstract_module_records.rs @@ -3,6 +3,7 @@ use crate::{ ecmascript::{ execution::{ModuleEnvironmentIndex, RealmIdentifier}, scripts_and_modules::{module::ModuleIdentifier, script::HostDefined}, + types::String, }, heap::indexes::StringIndex, }; @@ -36,17 +37,36 @@ pub(crate) struct ModuleRecord { pub(super) host_defined: Option, } -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, PartialEq)] pub(crate) enum ResolvedBindingName { String(StringIndex), SmallString(SmallString), Namespace, } -#[derive(Debug, Clone, Copy)] +impl Into for String { + fn into(self) -> ResolvedBindingName { + match self { + String::String(d) => ResolvedBindingName::String(d), + String::SmallString(d) => ResolvedBindingName::SmallString(d), + } + } +} + +impl ResolvedBindingName { + pub(crate) fn is_string(&self) -> bool { + match self { + ResolvedBindingName::String(_) => true, + ResolvedBindingName::SmallString(_) => true, + ResolvedBindingName::Namespace => false, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] pub(crate) struct ResolvedBinding { /// \[\[Module]] - pub(super) module: Option, + pub(super) module: Option, /// \[\[BindingName]] pub(super) binding_name: ResolvedBindingName, } diff --git a/nova_vm/src/ecmascript/builtins/module/cyclic_module_records.rs b/nova_vm/src/ecmascript/builtins/module/cyclic_module_records.rs index b879e23dc..417d93433 100644 --- a/nova_vm/src/ecmascript/builtins/module/cyclic_module_records.rs +++ b/nova_vm/src/ecmascript/builtins/module/cyclic_module_records.rs @@ -114,7 +114,7 @@ pub(crate) struct CyclicModuleRecord { /// record to request the importation of a module to the resolved Module /// Record. The list does not contain two different Records with the same /// \[\[Specifier\]\]. - pub(super) loaded_modules: Box<[LoadedModuleRecord]>, + pub(super) loaded_modules: Vec, /// \[\[CycleRoot\]\] /// /// The first visited module of the cycle, the root DFS ancestor of the @@ -263,7 +263,7 @@ impl Module { pub(crate) fn get_exported_names( self, agent: &mut Agent, - export_start_set: Option<()>, + export_start_set: &mut Vec, ) -> Box<[String]> { todo!() } @@ -272,7 +272,7 @@ impl Module { self, agent: &mut Agent, export_name: String, - resolve_set: Option<()>, + resolve_set: Option>, ) -> Option { todo!() } diff --git a/nova_vm/src/ecmascript/builtins/module/data.rs b/nova_vm/src/ecmascript/builtins/module/data.rs index 0941748ef..8c6e1e37e 100644 --- a/nova_vm/src/ecmascript/builtins/module/data.rs +++ b/nova_vm/src/ecmascript/builtins/module/data.rs @@ -20,5 +20,5 @@ pub struct ModuleHeapData { pub(crate) r#abstract: ModuleRecord, pub(crate) cyclic: CyclicModuleRecord, pub(crate) source_text: SourceTextModuleRecord, - pub(crate) exports: Box<[String]>, + pub(crate) exports: Vec, } diff --git a/nova_vm/src/ecmascript/builtins/module/semantics.rs b/nova_vm/src/ecmascript/builtins/module/semantics.rs index ba8bd4ece..9cc0c06e4 100644 --- a/nova_vm/src/ecmascript/builtins/module/semantics.rs +++ b/nova_vm/src/ecmascript/builtins/module/semantics.rs @@ -31,7 +31,7 @@ pub(crate) fn imported_local_names(import_entries: &[ImportEntryRecord]) -> Box< /// ###[16.2.1.3 Static Semantics: ModuleRequests](https://tc39.es/ecma262/#sec-static-semantics-modulerequests) /// /// The syntax-directed operation ModuleRequests takes no arguments and returns a List of Strings. -pub(crate) fn module_requests(agent: &mut Agent, program: &Program<'_>) -> Box<[String]> { +pub(crate) fn module_requests(agent: &mut Agent, program: &Program<'_>) -> Box<[Atom<'static>]> { let mut strings = vec![]; // Module : [empty] // 1. Return a new empty List. @@ -54,16 +54,28 @@ pub(crate) fn module_requests(agent: &mut Agent, program: &Program<'_>) -> Box<[ // 1. Return ModuleRequests of FromClause. // ModuleSpecifier : StringLiteral // 1. Return a List whose sole element is the SV of StringLiteral. - strings.push(String::from_str(agent, &decl.source.value)); + // SAFETY: This is a self-referential reference. The + // strings are kept in the same object as Program is. + strings.push(unsafe { + std::mem::transmute::, Atom<'static>>(decl.source.value.clone()) + }); } oxc_ast::ast::ModuleDeclaration::ExportAllDeclaration(decl) => { - strings.push(String::from_str(agent, &decl.source.value)); + // SAFETY: This is a self-referential reference. The + // strings are kept in the same object as Program is. + strings.push(unsafe { + std::mem::transmute::, Atom<'static>>(decl.source.value.clone()) + }); } oxc_ast::ast::ModuleDeclaration::ExportNamedDeclaration(decl) => { // ExportDeclaration : export ExportFromClause FromClause ; if let Some(source) = &decl.source { // 1. Return the ModuleRequests of FromClause. - strings.push(String::from_str(agent, &source.value)); + // SAFETY: This is a self-referential reference. The + // strings are kept in the same object as Program is. + strings.push(unsafe { + std::mem::transmute::, Atom<'static>>(source.value.clone()) + }); } } diff --git a/nova_vm/src/ecmascript/builtins/module/source_text_module_records.rs b/nova_vm/src/ecmascript/builtins/module/source_text_module_records.rs index e0369704c..4602d94ea 100644 --- a/nova_vm/src/ecmascript/builtins/module/source_text_module_records.rs +++ b/nova_vm/src/ecmascript/builtins/module/source_text_module_records.rs @@ -2,14 +2,13 @@ use oxc_allocator::Allocator; use oxc_ast::ast::Program; use oxc_parser::{Parser, ParserReturn}; use oxc_span::{Atom, SourceType}; -use oxc_syntax::module_record::RequestedModule; use small_string::SmallString; use super::{abstract_module_records::{ModuleRecord, ResolveExportResult}, cyclic_module_records::{CyclicModuleRecord, CyclicModuleRecordStatus}, data::ModuleHeapData, Module}; use crate::{ ecmascript::{ - builtins::{control_abstraction_objects::promise_objects::promise_abstract_operations::promise_capability_records::PromiseCapability, module::semantics::{self, module_requests}}, - execution::{Agent, ExecutionContext, JsResult, RealmIdentifier}, + builtins::{control_abstraction_objects::promise_objects::promise_abstract_operations::promise_capability_records::PromiseCapability, module::{abstract_module_records::{ResolvedBinding, ResolvedBindingName}, semantics::{self, module_requests}}}, + execution::{agent, Agent, DeclarativeEnvironmentIndex, ExecutionContext, JsResult, RealmIdentifier}, scripts_and_modules::script::HostDefined, types::{ Object, String, BUILTIN_STRING_MEMORY, SMALL_STRING_DISCRIMINANT, STRING_DISCRIMINANT, @@ -128,6 +127,7 @@ pub(crate) struct SourceTextModuleRecord { /// The result of parsing the source text of this module using Module as /// the goal symbol. pub(crate) ecmascript_code: Program<'static>, + source_text: Box, /// ### \[\[Context\]\] /// /// an ECMAScript code execution context or empty @@ -182,10 +182,9 @@ pub type ModuleOrErrors = Result>; /// returns a Source Text Module Record or a non-empty List of SyntaxError /// objects. It creates a Source Text Module Record based upon the result of /// parsing sourceText as a Module. -pub(crate) fn parse_module( +pub(crate) fn parse_module<'a>( agent: &mut Agent, - allocator: &Allocator, - module: Module, + allocator: &'static Allocator, source_text: Box, realm: RealmIdentifier, host_defined: Option, @@ -193,7 +192,9 @@ pub(crate) fn parse_module( // 1. Let body be ParseText(sourceText, Module). let parser = Parser::new( allocator, - &source_text, + // SAFETY: We're moving the Parser result into the same heap object as + // source_text. + unsafe { std::mem::transmute::<&str, &'static str>(&source_text) }, SourceType::default().with_module(true), ); let ParserReturn { @@ -249,7 +250,7 @@ pub(crate) fn parse_module( // b. Append the ExportEntry Record { indirect_export_entries.push(ExportEntryRecord { // [[ModuleRequest]]: ie.[[ModuleRequest]], - module_request: Some(ie.module_request), + module_request: Some(ie.module_request.clone()), // [[ImportName]]: ie.[[ImportName]], import_name: Some(import_name.into()), // [[LocalName]]: null, @@ -285,6 +286,7 @@ pub(crate) fn parse_module( _ => false, }) .is_some(); + let requested_module_count = requested_modules.len(); // 12. Return Source Text Module Record { Ok(ModuleHeapData { object_index: None, @@ -319,11 +321,12 @@ pub(crate) fn parse_module( // [[RequestedModules]]: requestedModules, requested_modules, // [[LoadedModules]]: « », - loaded_modules: vec![], + loaded_modules: Vec::with_capacity(requested_module_count), }, source_text: SourceTextModuleRecord { // [[ECMAScriptCode]]: body, ecmascript_code: program, + source_text, // [[Context]]: empty, context: None, // [[ImportMeta]]: empty, @@ -337,12 +340,16 @@ pub(crate) fn parse_module( // [[StarExportEntries]]: starExportEntries, star_export_entries: star_export_entries.into_boxed_slice(), }, - exports: todo!(), + exports: vec![], }) // }. // Note - // An implementation may parse module source text and analyse it for Early Error conditions prior to the evaluation of ParseModule for that module source text. However, the reporting of any errors must be deferred until the point where this specification actually performs ParseModule upon that source text. + // An implementation may parse module source text and analyse it for Early + // Error conditions prior to the evaluation of ParseModule for that module + // source text. However, the reporting of any errors must be deferred until + // the point where this specification actually performs ParseModule upon + // that source text. } /// ### [16.2.1.6.2 GetExportedNames ( [ exportStarSet ] )]() @@ -386,13 +393,13 @@ pub(crate) fn get_exported_names( exported_names.push(e.export_name.unwrap()); } // 8. For each ExportEntry Record e of module.[[StarExportEntries]], do - for e in agent[module].source_text.star_export_entries.iter() { + for e in agent[module].source_text.star_export_entries.clone().iter() { // a. Assert: e.[[ModuleRequest]] is not null. - let module_request = e.module_request.unwrap(); + let module_request = e.module_request.clone().unwrap(); // b. Let requestedModule be GetImportedModule(module, e.[[ModuleRequest]]). let requested_module = get_imported_module(agent, module, module_request); // c. Let starNames be requestedModule.GetExportedNames(exportStarSet). - let star_names = requested_module.get_exported_names(agent, export_start_set); + let star_names = requested_module.get_exported_names(agent, &mut export_start_set); // d. For each element n of starNames, do for &n in star_names.iter() { // i. If n is not "default", then @@ -412,14 +419,21 @@ pub(crate) fn get_exported_names( // that have ambiguous star export bindings. } +#[derive(Debug, Clone)] +struct ResolveSetEntry { + /// \[\[Module\]\] + module: Module, + /// \[\[ExportName\]\] + export_name: String, +} + /// ### [16.2.1.6.3 ResolveExport ( exportName [ , resolveSet ] )]() /// /// The ResolveExport concrete method of a Source Text Module Record module /// takes argument exportName (a String) and optional argument resolveSet (a /// List of Records with fields \[\[Module\]\] (a Module Record) and -/// ### \[\[ExportName\]\] (a String)) and returns a ResolvedBinding Record, null, -/// -/// or ambiguous. +/// ### \[\[ExportName\]\] (a String)) and returns a ResolvedBinding Record, +/// null, or ambiguous. /// /// /// ResolveExport attempts to resolve an imported binding to the actual @@ -445,49 +459,161 @@ pub(crate) fn resolve_export( agent: &mut Agent, module: Module, export_name: String, - resolve_set: Option<()>, -) -> ResolveExportResult { + resolve_set: Option>, +) -> Option { // 1. Assert: module.[[Status]] is not new. + assert!(agent[module].cyclic.status != CyclicModuleRecordStatus::New); // 2. If resolveSet is not present, set resolveSet to a new empty List. + let mut resolve_set = resolve_set.unwrap_or(vec![]); // 3. For each Record { [[Module]], [[ExportName]] } r of resolveSet, do - // a. If module and r.[[Module]] are the same Module Record and exportName is r.[[ExportName]], then - // i. Assert: This is a circular import request. - // ii. Return null. + for r in &resolve_set { + // a. If module and r.[[Module]] are the same Module Record and exportName is r.[[ExportName]], then + if module == r.module && export_name == r.export_name { + // i. Assert: This is a circular import request. + // TODO: debug_assert!(module.requires_module(module)); + // ii. Return null. + return None; + } + } // 4. Append the Record { [[Module]]: module, [[ExportName]]: exportName } to resolveSet. + resolve_set.push(ResolveSetEntry { + module, + export_name, + }); // 5. For each ExportEntry Record e of module.[[LocalExportEntries]], do - // a. If e.[[ExportName]] is exportName, then - // i. Assert: module provides the direct binding for this export. - // ii. Return ResolvedBinding Record { [[Module]]: module, [[BindingName]]: e.[[LocalName]] }. + for e in agent[module].source_text.local_export_entries.iter() { + // a. If e.[[ExportName]] is exportName, then + if e.export_name == Some(export_name) { + // i. Assert: module provides the direct binding for this export. + let module_declarative_index = agent[module].r#abstract.environment.unwrap(); + let module_declarative_index = + DeclarativeEnvironmentIndex::from_u32(module_declarative_index.into_u32()); + debug_assert!(agent + .heap + .environments + .get_declarative_environment(module_declarative_index) + .bindings + .contains_key(&export_name)); + // ii. Return ResolvedBinding Record { [[Module]]: module, [[BindingName]]: e.[[LocalName]] }. + return Some(ResolveExportResult::Resolved(ResolvedBinding { + module: Some(module), + binding_name: e.local_name.unwrap().into(), + })); + } + } // 6. For each ExportEntry Record e of module.[[IndirectExportEntries]], do - // a. If e.[[ExportName]] is exportName, then - // i. Assert: e.[[ModuleRequest]] is not null. - // ii. Let importedModule be GetImportedModule(module, e.[[ModuleRequest]]). - // iii. If e.[[ImportName]] is all, then - // 1. Assert: module does not provide the direct binding for this export. - // 2. Return ResolvedBinding Record { [[Module]]: importedModule, [[BindingName]]: namespace }. - // iv. Else, - // 1. Assert: module imports a specific binding for this export. - // 2. Return importedModule.ResolveExport(e.[[ImportName]], resolveSet). + for e in agent[module].source_text.indirect_export_entries.iter() { + // a. If e.[[ExportName]] is exportName, then + if e.export_name == Some(export_name) { + // i. Assert: e.[[ModuleRequest]] is not null. + let module_request = e.module_request.as_ref().unwrap().clone(); + // ii. Let importedModule be GetImportedModule(module, e.[[ModuleRequest]]). + let imported_module = get_imported_module(agent, module, module_request); + // iii. If e.[[ImportName]] is all, then + if e.import_name == Some(ExportImportName::All) { + // 1. Assert: module does not provide the direct binding for this export. + let module_declarative_index = agent[module].r#abstract.environment.unwrap(); + let module_declarative_index = + DeclarativeEnvironmentIndex::from_u32(module_declarative_index.into_u32()); + debug_assert!(!agent + .heap + .environments + .get_declarative_environment(module_declarative_index) + .bindings + .contains_key(&export_name)); + // 2. Return ResolvedBinding Record { [[Module]]: importedModule, [[BindingName]]: namespace }. + return Some(ResolveExportResult::Resolved(ResolvedBinding { + module: Some(imported_module), + binding_name: ResolvedBindingName::Namespace, + })); + } else { + // iv. Else, + // 1. Assert: module imports a specific binding for this export. + // 2. Return importedModule.ResolveExport(e.[[ImportName]], resolveSet). + let import_name = match e.import_name.unwrap() { + ExportImportName::String(d) => String::from(d), + ExportImportName::SmallString(d) => String::from(d), + _ => unreachable!(), + }; + return resolve_export(agent, imported_module, import_name, Some(resolve_set)); + } + } + } // 7. If exportName is "default", then - // a. Assert: A default export was not explicitly defined by this module. - // b. Return null. - // c. NOTE: A default export cannot be provided by an export * from "mod" declaration. + if export_name == BUILTIN_STRING_MEMORY.default { + // a. Assert: A default export was not explicitly defined by this module. + // TODO: Figure out what this meant again + // b. Return null. + return None; + // c. NOTE: A default export cannot be provided by an export * from "mod" declaration. + } // 8. Let starResolution be null. + let mut star_resolution = None; // 9. For each ExportEntry Record e of module.[[StarExportEntries]], do - // a. Assert: e.[[ModuleRequest]] is not null. - // b. Let importedModule be GetImportedModule(module, e.[[ModuleRequest]]). - // c. Let resolution be importedModule.ResolveExport(exportName, resolveSet). - // d. If resolution is ambiguous, return ambiguous. - // e. If resolution is not null, then - // i. Assert: resolution is a ResolvedBinding Record. - // ii. If starResolution is null, then - // 1. Set starResolution to resolution. - // iii. Else, - // 1. Assert: There is more than one * import that includes the requested name. - // 2. If resolution.[[Module]] and starResolution.[[Module]] are not the same Module Record, return ambiguous. - // 3. If resolution.[[BindingName]] is not starResolution.[[BindingName]] and either resolution.[[BindingName]] or starResolution.[[BindingName]] is namespace, return ambiguous. - // 4. If resolution.[[BindingName]] is a String, starResolution.[[BindingName]] is a String, and resolution.[[BindingName]] is not starResolution.[[BindingName]], return ambiguous. + for e in agent[module].source_text.star_export_entries.clone().iter() { + // a. Assert: e.[[ModuleRequest]] is not null. + let module_request = e.module_request.as_ref().unwrap().clone(); + // b. Let importedModule be GetImportedModule(module, e.[[ModuleRequest]]). + let imported_module = get_imported_module(agent, module, module_request); + // c. Let resolution be importedModule.ResolveExport(exportName, resolveSet). + let resolution = resolve_export( + agent, + imported_module, + export_name, + Some(resolve_set.clone()), + ); + match resolution { + Some(resolution) => { + match resolution { + ResolveExportResult::Ambiguous => { + // d. If resolution is ambiguous, return ambiguous. + return Some(ResolveExportResult::Ambiguous); + } + ResolveExportResult::Resolved(resolution) => { + // e. If resolution is not null, then + // i. Assert: resolution is a ResolvedBinding Record. + // ii. If starResolution is null, then + if star_resolution.is_none() { + // 1. Set starResolution to resolution. + star_resolution = Some(resolution); + } else { + let star_resolution = star_resolution.unwrap(); + // iii. Else, + // 1. Assert: There is more than one * import that + // includes the requested name. + // 2. If resolution.[[Module]] and starResolution.[[Module]] + // are not the same Module Record, return ambiguous. + if resolution.module != star_resolution.module { + return Some(ResolveExportResult::Ambiguous); + } + // 3. If resolution.[[BindingName]] is not starResolution.[[BindingName]] + // and either resolution.[[BindingName]] or starResolution.[[BindingName]] + // is namespace, return ambiguous. + if resolution.binding_name != star_resolution.binding_name + && (resolution.binding_name == ResolvedBindingName::Namespace + || star_resolution.binding_name + == ResolvedBindingName::Namespace) + { + return Some(ResolveExportResult::Ambiguous); + } + // 4. If resolution.[[BindingName]] is a String, starResolution.[[BindingName]] + // is a String, and resolution.[[BindingName]] is not starResolution.[[BindingName]], + // return ambiguous. + if resolution.binding_name.is_string() + && star_resolution.binding_name.is_string() + && resolution.binding_name != star_resolution.binding_name + { + return Some(ResolveExportResult::Ambiguous); + } + } + } + } + } + None => {} + } + } // 10. Return starResolution. + star_resolution.map(|resolved_binding| ResolveExportResult::Resolved(resolved_binding)) } /// ### [16.2.1.6.4 InitializeEnvironment ( )]() From e1340408587f0cd94ff6d8e476ad5afa41025ef5 Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Thu, 23 May 2024 22:19:05 +0300 Subject: [PATCH 5/8] More work around Promises, module environments --- .../promise_abstract_operations.rs | 128 +++++++++++++++--- .../promise_objects/promise_constructor.rs | 111 ++++++++++++++- .../builtins/ecmascript_function.rs | 4 +- .../function_objects/function_prototype.rs | 2 +- nova_vm/src/ecmascript/builtins/module.rs | 22 +-- .../builtins/module/cyclic_module_records.rs | 7 +- nova_vm/src/ecmascript/builtins/promise.rs | 22 +++ .../src/ecmascript/builtins/promise/data.rs | 37 ++++- .../execution/default_host_hooks.rs | 2 +- .../src/ecmascript/execution/environments.rs | 30 +--- .../environments/function_environment.rs | 4 +- .../environments/module_environment.rs | 86 +++++++++++- .../src/ecmascript/types/language/function.rs | 34 +++-- .../src/ecmascript/types/language/object.rs | 32 ++--- nova_vm/src/engine/bytecode/vm.rs | 2 +- nova_vm/src/heap.rs | 12 +- 16 files changed, 424 insertions(+), 111 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations.rs index d97f78824..946433dde 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations.rs @@ -3,18 +3,25 @@ use std::ops::{Index, IndexMut}; use crate::{ ecmascript::{ abstract_operations::testing_and_comparison::is_constructor, - builtins::{promise::Promise, ArgumentsList}, + builtins::{ + promise::{data::PromiseHeapData, Promise}, + ArgumentsList, + }, execution::{agent::ExceptionType, Agent, JsResult}, - types::{AbstractClosureHeapData, Function, IntoValue, Object, String, Value}, + types::{ + AbstractClosureHeapData, Function, IntoFunction, IntoObject, IntoValue, Object, String, + Value, + }, }, heap::{ - indexes::{BaseIndex, BoundFunctionIndex}, - Heap, + indexes::{BaseIndex, BoundFunctionIndex, ObjectIndex}, + CreateHeapData, Heap, }, }; use self::{ - promise_capability_records::PromiseCapability, promise_reaction_records::PromiseReaction, + promise_capability_records::{PromiseCapability, PromiseCapabilityRecord}, + promise_reaction_records::PromiseReaction, }; pub(crate) mod promise_capability_records; @@ -22,7 +29,7 @@ pub(crate) mod promise_reaction_records; pub(crate) struct PromiseResolvingFunctions { pub(crate) resolve: Function, - pub(crate) reject: BuiltinPromiseRejectFunction, + pub(crate) reject: Function, } /// ### [27.2.1.3 CreateResolvingFunctions ( promise )]() @@ -56,7 +63,8 @@ pub(crate) fn create_resolving_functions( agent.heap.promise_reject_functions.push(Some(reject)); let reject = BuiltinPromiseRejectFunction(BuiltinPromiseRejectFunctionIndex::last( &agent.heap.promise_reject_functions, - )); + )) + .into_function(); // 12. Return the Record { [[Resolve]]: resolve, [[Reject]]: reject }. PromiseResolvingFunctions { resolve, reject } } @@ -73,7 +81,7 @@ pub(crate) struct PromiseRejectFunctionHeapData { pub(crate) promise: Promise, /// \[\[AlreadyResolved\]\] pub(crate) already_resolved: bool, - pub(crate) object_index: Option, + pub(crate) object_index: Option, } pub(crate) type BuiltinPromiseRejectFunctionIndex = BaseIndex; @@ -95,22 +103,86 @@ impl IndexMut for Agent { } } +impl Index for Agent { + type Output = PromiseRejectFunctionHeapData; + + fn index(&self, index: BuiltinPromiseRejectFunctionIndex) -> &Self::Output { + &self.heap[index] + } +} + +impl IndexMut for Agent { + fn index_mut(&mut self, index: BuiltinPromiseRejectFunctionIndex) -> &mut Self::Output { + &mut self.heap[index] + } +} + +impl From for Function { + fn from(value: BuiltinPromiseRejectFunction) -> Self { + Self::BuiltinPromiseRejectFunction(value.0) + } +} + +impl IntoFunction for BuiltinPromiseRejectFunction { + fn into_function(self) -> Function { + self.into() + } +} + +impl From for Object { + fn from(value: BuiltinPromiseRejectFunction) -> Self { + Self::BuiltinPromiseRejectFunction(value.0) + } +} + +impl IntoObject for BuiltinPromiseRejectFunction { + fn into_object(self) -> Object { + self.into() + } +} + +impl From for Value { + fn from(value: BuiltinPromiseRejectFunction) -> Self { + Self::BuiltinPromiseRejectFunction(value.0) + } +} + +impl IntoValue for BuiltinPromiseRejectFunction { + fn into_value(self) -> Value { + self.into() + } +} + impl Index for Heap { type Output = PromiseRejectFunctionHeapData; fn index(&self, index: BuiltinPromiseRejectFunction) -> &Self::Output { + &self[index.0] + } +} + +impl IndexMut for Heap { + fn index_mut(&mut self, index: BuiltinPromiseRejectFunction) -> &mut Self::Output { + &mut self[index.0] + } +} + +impl Index for Heap { + type Output = PromiseRejectFunctionHeapData; + + fn index(&self, index: BuiltinPromiseRejectFunctionIndex) -> &Self::Output { self.promise_reject_functions - .get(index.0.into_index()) + .get(index.into_index()) .expect("BuiltinPromiseRejectFunction out of bounds") .as_ref() .expect("BuiltinPromiseRejectFunction slot empty") } } -impl IndexMut for Heap { - fn index_mut(&mut self, index: BuiltinPromiseRejectFunction) -> &mut Self::Output { +impl IndexMut for Heap { + fn index_mut(&mut self, index: BuiltinPromiseRejectFunctionIndex) -> &mut Self::Output { self.promise_reject_functions - .get_mut(index.0.into_index()) + .get_mut(index.into_index()) .expect("BuiltinPromiseRejectFunction out of bounds") .as_mut() .expect("BuiltinPromiseRejectFunction slot empty") @@ -193,6 +265,27 @@ pub(crate) fn fulfill_promise(agent: &mut Agent, promise: Promise, value: Value) // 8. Return unused. } +pub(crate) fn new_intrinsic_promise_capability(agent: &mut Agent) -> PromiseCapability { + // 5. Let executor be CreateBuiltinFunction(executorClosure, 2, "", « »). + // 6. Let promise be ? Construct(C, « executor »). + // From Promise Constructor: OrdinaryCreateFromConstructor + let promise = agent.heap.create(PromiseHeapData::default()); + // From Promise Constructor: Let resolvingFunctions be CreateResolvingFunctions(promise). + let resolving_functions = + create_resolving_functions(agent, Promise::try_from(promise).unwrap()); + // From Promise Constructor: Call(executor, undefined, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »). + + // 7. If IsCallable(resolvingFunctions.[[Resolve]]) is false, throw a TypeError exception. + // 8. If IsCallable(resolvingFunctions.[[Reject]]) is false, throw a TypeError exception. + // 9. Return the PromiseCapability Record { [[Promise]]: promise, [[Resolve]]: resolvingFunctions.[[Resolve]], [[Reject]]: resolvingFunctions.[[Reject]] }. + let record = PromiseCapabilityRecord { + promise, + resolve: resolving_functions.resolve, + reject: resolving_functions.reject, + }; + agent.heap.create(record) +} + /// ### [27.2.1.5 NewPromiseCapability ( C )]() /// /// The abstract operation NewPromiseCapability takes argument C (an ECMAScript @@ -205,14 +298,11 @@ pub(crate) fn fulfill_promise(agent: &mut Agent, promise: Promise, value: Value) /// /// NOTE: The argument `c` can take None to signify that the current realm's /// %Promise% intrinsic should be used as the constructor. -pub(crate) fn new_promise_capability( - agent: &mut Agent, - c: Option, -) -> JsResult { +pub(crate) fn new_promise_capability(agent: &mut Agent, c: Value) -> JsResult { // 2. NOTE: C is assumed to be a constructor function that supports the parameter conventions of the Promise constructor (see 27.2.3.1). - let Some(c) = c else { - todo!("PromiseConstructor quick-route") - }; + if c == agent.current_realm().intrinsics().promise().into_value() { + return Ok(new_intrinsic_promise_capability(agent)); + } // 1. If IsConstructor(C) is false, throw a TypeError exception. if !is_constructor(agent, c) { diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs index 1b4d28d59..c2a244436 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs @@ -1,13 +1,24 @@ use crate::{ ecmascript::{ + abstract_operations::{ + operations_on_objects::call_function, testing_and_comparison::is_callable, + }, builders::builtin_function_builder::BuiltinFunctionBuilder, - builtins::{ArgumentsList, Behaviour, Builtin, BuiltinGetter, BuiltinIntrinsicConstructor}, - execution::{Agent, JsResult, RealmIdentifier}, - types::{IntoObject, Object, PropertyKey, String, Value, BUILTIN_STRING_MEMORY}, + builtins::{ + ordinary::ordinary_create_from_constructor, promise::Promise, ArgumentsList, Behaviour, + Builtin, BuiltinGetter, BuiltinIntrinsicConstructor, + }, + execution::{agent::ExceptionType, Agent, JsResult, ProtoIntrinsics, RealmIdentifier}, + types::{ + Function, IntoObject, IntoValue, Object, PropertyKey, String, Value, + BUILTIN_STRING_MEMORY, + }, }, heap::{IntrinsicConstructorIndexes, WellKnownSymbolIndexes}, }; +use super::promise_abstract_operations::create_resolving_functions; + pub(crate) struct PromiseConstructor; impl Builtin for PromiseConstructor { const NAME: String = BUILTIN_STRING_MEMORY.Promise; @@ -73,12 +84,98 @@ impl BuiltinGetter for PromiseGetSpecies { impl PromiseConstructor { fn behaviour( - _agent: &mut Agent, + agent: &mut Agent, _this_value: Value, - _arguments: ArgumentsList, - _new_target: Option, + arguments: ArgumentsList, + new_target: Option, ) -> JsResult { - todo!() + // 1. If NewTarget is undefined, throw a TypeError exception. + let Some(new_target) = new_target else { + return Err(agent.throw_exception( + ExceptionType::TypeError, + "Promise constructor cannot be called as a function", + )); + }; + // 2. If IsCallable(executor) is false, throw a TypeError exception. + let executor = arguments.get(0); + if !is_callable(executor) { + return Err( + agent.throw_exception(ExceptionType::TypeError, "Executor is not a constructor") + ); + } + let executor = Function::try_from(executor).unwrap(); + let new_target = Function::try_from(new_target).unwrap(); + // 3. Let promise be ? OrdinaryCreateFromConstructor(NewTarget, "%Promise.prototype%", « [[PromiseState]], [[PromiseResult]], [[PromiseFulfillReactions]], [[PromiseRejectReactions]], [[PromiseIsHandled]] »). + let promise = Promise::try_from(ordinary_create_from_constructor( + agent, + new_target, + ProtoIntrinsics::Promise, + )?) + .unwrap(); + + // All of these steps are done by the heap data default builder. + // 4. Set promise.[[PromiseState]] to pending. + // 5. Set promise.[[PromiseFulfillReactions]] to a new empty List. + // 6. Set promise.[[PromiseRejectReactions]] to a new empty List. + // 7. Set promise.[[PromiseIsHandled]] to false. + + // 8. Let resolvingFunctions be CreateResolvingFunctions(promise). + let resolving_functions = create_resolving_functions(agent, promise); + // 9. Let completion be Completion(Call(executor, undefined, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »)). + let completion = call_function( + agent, + executor, + Value::Undefined, + Some(ArgumentsList(&[ + resolving_functions.resolve.into_value(), + resolving_functions.reject.into_value(), + ])), + ); + // 10. If completion is an abrupt completion, then + match completion { + Ok(_) => { + // 11. Return promise. + Ok(promise.into_value()) + } + Err(err) => { + // a. Perform ? Call(resolvingFunctions.[[Reject]], undefined, « completion.[[Value]] »). + call_function( + agent, + resolving_functions.reject, + Value::Undefined, + Some(ArgumentsList(&[err.value()])), + ) + } + } + + // Note + // The executor argument must be a function object. It is called for + // initiating and reporting completion of the possibly deferred action + // represented by this Promise. The executor is called with two + // arguments: resolve and reject. These are functions that may be used + // by the executor function to report eventual completion or failure of + // the deferred computation. Returning from the executor function does + // not mean that the deferred action has been completed but only that + // the request to eventually perform the deferred action has been + // accepted. + // The resolve function that is passed to an executor function accepts + // a single argument. The executor code may eventually call the resolve + // function to indicate that it wishes to resolve the associated + // Promise. The argument passed to the resolve function represents the + // eventual value of the deferred action and can be either the actual + // fulfillment value or another promise which will provide the value if + // it is fulfilled. + // The reject function that is passed to an executor function accepts a + // single argument. The executor code may eventually call the reject + // function to indicate that the associated Promise is rejected and + // will never be fulfilled. The argument passed to the reject function + // is used as the rejection value of the promise. Typically it will be + // an Error object. + // The resolve and reject functions passed to an executor function by + // the Promise constructor have the capability to actually resolve and + // reject the associated promise. Subclasses may have different + // constructor behaviour that passes in customized values for resolve + // and reject. } fn all(_agent: &mut Agent, _this_value: Value, _arguments: ArgumentsList) -> JsResult { diff --git a/nova_vm/src/ecmascript/builtins/ecmascript_function.rs b/nova_vm/src/ecmascript/builtins/ecmascript_function.rs index d3cf8401a..86f0a7475 100644 --- a/nova_vm/src/ecmascript/builtins/ecmascript_function.rs +++ b/nova_vm/src/ecmascript/builtins/ecmascript_function.rs @@ -852,7 +852,7 @@ pub(crate) fn make_constructor( Function::BuiltinGeneratorFunction => todo!(), Function::BuiltinConstructorFunction => todo!(), Function::BuiltinPromiseResolveFunction => todo!(), - Function::BuiltinPromiseRejectFunction => todo!(), + Function::BuiltinPromiseRejectFunction(_) => todo!(), Function::BuiltinPromiseCollectorFunction => todo!(), Function::BuiltinProxyRevokerFunction => todo!(), Function::ECMAScriptAsyncFunction => todo!(), @@ -950,7 +950,7 @@ pub(crate) fn set_function_name( Function::BuiltinGeneratorFunction => todo!(), Function::BuiltinConstructorFunction => todo!(), Function::BuiltinPromiseResolveFunction => todo!(), - Function::BuiltinPromiseRejectFunction => todo!(), + Function::BuiltinPromiseRejectFunction(_) => todo!(), Function::BuiltinPromiseCollectorFunction => todo!(), Function::BuiltinProxyRevokerFunction => todo!(), Function::ECMAScriptAsyncFunction => todo!(), diff --git a/nova_vm/src/ecmascript/builtins/fundamental_objects/function_objects/function_prototype.rs b/nova_vm/src/ecmascript/builtins/fundamental_objects/function_objects/function_prototype.rs index 12e9cbe04..a9d0fd9ed 100644 --- a/nova_vm/src/ecmascript/builtins/fundamental_objects/function_objects/function_prototype.rs +++ b/nova_vm/src/ecmascript/builtins/fundamental_objects/function_objects/function_prototype.rs @@ -182,7 +182,7 @@ impl FunctionPrototype { Function::BuiltinGeneratorFunction => todo!(), Function::BuiltinConstructorFunction => todo!(), Function::BuiltinPromiseResolveFunction => todo!(), - Function::BuiltinPromiseRejectFunction => todo!(), + Function::BuiltinPromiseRejectFunction(_) => todo!(), Function::BuiltinPromiseCollectorFunction => todo!(), Function::BuiltinProxyRevokerFunction => todo!(), Function::ECMAScriptAsyncFunction => todo!(), diff --git a/nova_vm/src/ecmascript/builtins/module.rs b/nova_vm/src/ecmascript/builtins/module.rs index 9a7c338fd..942f2437f 100644 --- a/nova_vm/src/ecmascript/builtins/module.rs +++ b/nova_vm/src/ecmascript/builtins/module.rs @@ -14,7 +14,11 @@ use crate::{ Heap, }; -use self::data::ModuleHeapData; +use self::{ + abstract_module_records::{ResolveExportResult, ResolvedBindingName}, + data::ModuleHeapData, + source_text_module_records::resolve_export, +}; use super::ordinary::{ ordinary_define_own_property, ordinary_delete, ordinary_get, ordinary_has_property, @@ -313,9 +317,9 @@ impl InternalMethods for Module { // 4. Let m be O.[[Module]]. let m = &agent[self].cyclic; // 5. Let binding be m.ResolveExport(P). - let binding = m.resolve_export(property_key); + let binding = resolve_export(agent, self, key, None); // 6. Assert: binding is a ResolvedBinding Record. - let Some(data::ResolveExportResult::Resolved(binding)) = binding else { + let Some(ResolveExportResult::Resolved(binding)) = binding else { unreachable!(); }; // 7. Let targetModule be binding.[[Module]]. @@ -323,24 +327,24 @@ impl InternalMethods for Module { let target_module = binding.module.unwrap(); // 9. If binding.[[BindingName]] is NAMESPACE, then let _binding_name = match binding.binding_name { - data::ResolvedBindingName::Namespace => { + ResolvedBindingName::Namespace => { // a. Return GetModuleNamespace(targetModule). todo!(); } - data::ResolvedBindingName::String(data) => String::String(data), - data::ResolvedBindingName::SmallString(data) => String::SmallString(data), + ResolvedBindingName::String(data) => String::String(data), + ResolvedBindingName::SmallString(data) => String::SmallString(data), }; // 10. Let targetEnv be targetModule.[[Environment]]. - let target_env = agent[target_module].module.environment; + let target_env = agent[target_module].r#abstract.environment; // 11. If targetEnv is EMPTY, throw a ReferenceError exception. match target_env { None => Err(agent.throw_exception( ExceptionType::ReferenceError, "Could not resolve module", )), - Some(_target_env) => { + Some(target_env) => { // 12. Return ? targetEnv.GetBindingValue(binding.[[BindingName]], true). - todo!() + target_env.get_ } } } diff --git a/nova_vm/src/ecmascript/builtins/module/cyclic_module_records.rs b/nova_vm/src/ecmascript/builtins/module/cyclic_module_records.rs index 417d93433..a9c200e32 100644 --- a/nova_vm/src/ecmascript/builtins/module/cyclic_module_records.rs +++ b/nova_vm/src/ecmascript/builtins/module/cyclic_module_records.rs @@ -6,7 +6,8 @@ use crate::ecmascript::{ abstract_operations::operations_on_objects::call_function, builtins::{ control_abstraction_objects::promise_objects::promise_abstract_operations::{ - new_promise_capability, promise_capability_records::PromiseCapability, + new_intrinsic_promise_capability, new_promise_capability, + promise_capability_records::PromiseCapability, }, create_builtin_function, error::Error, @@ -232,7 +233,7 @@ impl Module { ) -> Promise { // 1. If hostDefined is not present, let hostDefined be empty. // TODO: 2. Let pc be ! NewPromiseCapability(%Promise%). - let pc = new_promise_capability(agent, None).unwrap(); + let pc = new_intrinsic_promise_capability(agent); // 3. Let state be the GraphLoadingState Record { let mut state = GraphLoadingStateRecord { // [[PromiseCapability]]: pc, @@ -620,7 +621,7 @@ pub(crate) fn evaluate(agent: &mut Agent, mut module: Module) -> Promise { // 5. Let stack be a new empty List. let mut stack = vec![]; // 6. Let capability be ! NewPromiseCapability(%Promise%). - let capability = new_promise_capability(agent, None).unwrap(); + let capability = new_intrinsic_promise_capability(agent); // 7. Set module.[[TopLevelCapability]] to capability. agent[module].cyclic.top_level_capability = Some(capability); // 8. Let result be Completion(InnerModuleEvaluation(module, stack, 0)). diff --git a/nova_vm/src/ecmascript/builtins/promise.rs b/nova_vm/src/ecmascript/builtins/promise.rs index b7d62ad3d..67c95a592 100644 --- a/nova_vm/src/ecmascript/builtins/promise.rs +++ b/nova_vm/src/ecmascript/builtins/promise.rs @@ -56,6 +56,28 @@ impl From for Object { } } +impl TryFrom for Promise { + type Error = (); + + fn try_from(value: Value) -> Result { + match value { + Value::Promise(d) => Ok(Promise::from(d)), + _ => Err(()), + } + } +} + +impl TryFrom for Promise { + type Error = (); + + fn try_from(value: Object) -> Result { + match value { + Object::Promise(d) => Ok(Promise::from(d)), + _ => Err(()), + } + } +} + impl Index for Agent { type Output = PromiseHeapData; diff --git a/nova_vm/src/ecmascript/builtins/promise/data.rs b/nova_vm/src/ecmascript/builtins/promise/data.rs index 2f522e3cf..e9254018d 100644 --- a/nova_vm/src/ecmascript/builtins/promise/data.rs +++ b/nova_vm/src/ecmascript/builtins/promise/data.rs @@ -1,6 +1,41 @@ -use crate::heap::indexes::ObjectIndex; +use crate::{ecmascript::{builtins::control_abstraction_objects::promise_objects::promise_abstract_operations::promise_capability_records::PromiseCapability, types::Value}, heap::indexes::ObjectIndex}; #[derive(Debug, Clone, Default)] pub struct PromiseHeapData { pub(crate) object_index: Option, + pub(crate) promise_state: PromiseState, + pub(crate) promise_fulfill_reactions: Option, + pub(crate) promise_reject_reactions: Option, + pub(crate) promise_is_handled: bool, +} + +#[derive(Debug, Clone, Copy, Default)] +pub(crate) enum PromiseState { + #[default] + Pending, + Fulfilled { + promise_result: Value, + }, + Rejected { + promise_result: Value, + }, +} + +#[derive(Debug, Clone)] +pub(crate) enum PromiseReactions { + One(PromiseReactionRecord), + Many(Vec), +} + +#[derive(Debug, Clone, Copy)] +pub(crate) struct PromiseReactionRecord { + capability: PromiseCapability, + r#type: PromiseReactionType, + handler: (), +} + +#[derive(Debug, Clone, Copy)] +pub(crate) enum PromiseReactionType { + Fulfill, + Reject, } diff --git a/nova_vm/src/ecmascript/execution/default_host_hooks.rs b/nova_vm/src/ecmascript/execution/default_host_hooks.rs index b2b952866..bb11e9f79 100644 --- a/nova_vm/src/ecmascript/execution/default_host_hooks.rs +++ b/nova_vm/src/ecmascript/execution/default_host_hooks.rs @@ -23,7 +23,7 @@ impl HostHooks for DefaultHostHooks { &self, referrer: (), specifier: &str, - host_defined: Option>, + host_defined: Option<&dyn std::any::Any>, payload: (), ) { unreachable!("HostLoadImportedModule does not have a default implementation"); diff --git a/nova_vm/src/ecmascript/execution/environments.rs b/nova_vm/src/ecmascript/execution/environments.rs index 9cd9f6788..40ca997ef 100644 --- a/nova_vm/src/ecmascript/execution/environments.rs +++ b/nova_vm/src/ecmascript/execution/environments.rs @@ -34,6 +34,7 @@ pub(crate) use function_environment::{ new_function_environment, FunctionEnvironment, ThisBindingStatus, }; pub(crate) use global_environment::GlobalEnvironment; +pub(crate) use module_environment::{ModuleEnvironment, ModuleEnvironmentIndex}; pub(crate) use object_environment::ObjectEnvironment; pub(crate) use private_environment::PrivateEnvironment; @@ -99,33 +100,6 @@ create_environment_index!(GlobalEnvironment, GlobalEnvironmentIndex); create_environment_index!(ObjectEnvironment, ObjectEnvironmentIndex); create_environment_index!(PrivateEnvironment, PrivateEnvironmentIndex); -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub(crate) struct ModuleEnvironmentIndex(NonZeroU32, PhantomData); -impl ModuleEnvironmentIndex { - /// Creates a new index from a u32. - /// - /// ## Panics - /// - If the value is equal to 0. - pub(crate) const fn from_u32(value: u32) -> Self { - assert!(value != 0); - // SAFETY: Number is not 0 and will not overflow to zero. - // This check is done manually to allow const context. - Self(unsafe { NonZeroU32::new_unchecked(value) }, PhantomData) - } - - pub(crate) const fn into_index(self) -> usize { - self.0.get() as usize - 1 - } - - pub(crate) const fn into_u32(self) -> u32 { - self.0.get() - } - - pub(crate) fn last(vec: &[Option]) -> Self { - Self::from_u32(vec.len() as u32) - } -} - /// ### [9.1.1 The Environment Record Type Hierarchy](https://tc39.es/ecma262/#sec-the-environment-record-type-hierarchy) /// /// Environment Records can be thought of as existing in a simple @@ -141,7 +115,7 @@ pub(crate) enum EnvironmentIndex { Declarative(DeclarativeEnvironmentIndex) = 1, Function(FunctionEnvironmentIndex), Global(GlobalEnvironmentIndex), - // Module(ModuleEnvironmentIndex), + Module(ModuleEnvironmentIndex), Object(ObjectEnvironmentIndex), } diff --git a/nova_vm/src/ecmascript/execution/environments/function_environment.rs b/nova_vm/src/ecmascript/execution/environments/function_environment.rs index 66b6bd0c6..50a2cf828 100644 --- a/nova_vm/src/ecmascript/execution/environments/function_environment.rs +++ b/nova_vm/src/ecmascript/execution/environments/function_environment.rs @@ -331,7 +331,7 @@ impl FunctionEnvironmentIndex { Function::BuiltinGeneratorFunction => todo!(), Function::BuiltinConstructorFunction => todo!(), Function::BuiltinPromiseResolveFunction => todo!(), - Function::BuiltinPromiseRejectFunction => todo!(), + Function::BuiltinPromiseRejectFunction(_) => todo!(), Function::BuiltinPromiseCollectorFunction => todo!(), Function::BuiltinProxyRevokerFunction => todo!(), Function::ECMAScriptAsyncFunction => todo!(), @@ -366,7 +366,7 @@ impl FunctionEnvironmentIndex { Function::BuiltinGeneratorFunction => todo!(), Function::BuiltinConstructorFunction => todo!(), Function::BuiltinPromiseResolveFunction => todo!(), - Function::BuiltinPromiseRejectFunction => todo!(), + Function::BuiltinPromiseRejectFunction(_) => todo!(), Function::BuiltinPromiseCollectorFunction => todo!(), Function::BuiltinProxyRevokerFunction => todo!(), Function::ECMAScriptAsyncFunction => todo!(), diff --git a/nova_vm/src/ecmascript/execution/environments/module_environment.rs b/nova_vm/src/ecmascript/execution/environments/module_environment.rs index 9c4dab2b5..e6a587a70 100644 --- a/nova_vm/src/ecmascript/execution/environments/module_environment.rs +++ b/nova_vm/src/ecmascript/execution/environments/module_environment.rs @@ -1,4 +1,10 @@ -use super::DeclarativeEnvironment; +use crate::ecmascript::{ + builtins::module::Module, + execution::{agent::ExceptionType, Agent, JsResult}, + types::{String, Value}, +}; + +use super::{DeclarativeEnvironment, DeclarativeEnvironmentIndex}; /// ### [9.1.1.5 Module Environment Records](https://tc39.es/ecma262/#sec-module-environment-records) /// A Module Environment Record is a Declarative Environment Record that is @@ -17,3 +23,81 @@ use super::DeclarativeEnvironment; #[derive(Debug, Clone)] #[repr(transparent)] pub(crate) struct ModuleEnvironment(DeclarativeEnvironment); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +pub(crate) struct ModuleEnvironmentIndex(DeclarativeEnvironmentIndex); +impl ModuleEnvironmentIndex { + /// ### [9.1.1.5.1 GetBindingValue ( N, S )](https://tc39.es/ecma262/#sec-module-environment-records-getbindingvalue-n-s) + /// + /// The GetBindingValue concrete method of a Module Environment Record + /// envRec takes arguments N (a String) and S (a Boolean) and returns + /// either a normal completion containing an ECMAScript language value or + /// a throw completion. It returns the value of its bound identifier whose + /// name is N. However, if the binding is an indirect binding the value of + /// the target binding is returned. If the binding exists but is + /// uninitialized a ReferenceError is thrown. + pub(crate) fn get_binding_value( + self, + agent: &mut Agent, + name: String, + is_strict: bool, + ) -> JsResult { + // 1. Assert: S is true. + debug_assert!(is_strict); + // 2. Assert: envRec has a binding for N. + let binding = self.0.heap_data(agent).bindings.get(&name); + let binding = binding.unwrap(); + // 3. If the binding for N is an indirect binding, then + if false { + // a. Let M and N2 be the indirection values provided when this binding for N was created. + // b. Let targetEnv be M.[[Environment]]. + // c. If targetEnv is empty, throw a ReferenceError exception. + // d. Return ? targetEnv.GetBindingValue(N2, true). + todo!(); + } + // 4. If the binding for N in envRec is an uninitialized binding, throw a ReferenceError exception. + if binding.value.is_none() { + return Err(agent.throw_exception( + ExceptionType::ReferenceError, + "Accessed uninitialized binding", + )); + } + // 5. Return the value currently bound to N in envRec. + Ok(binding.value.unwrap()) + } + + pub(crate) fn has_this_binding(self) -> bool { + true + } + + pub(crate) fn get_this_binding(self) -> Option { + None + } + + /// ### [9.1.1.5.5 CreateImportBinding ( N, M, N2 )](https://tc39.es/ecma262/#sec-createimportbinding) + /// + /// The CreateImportBinding concrete method of a Module Environment Record + /// envRec takes arguments N (a String), M (a Module Record), and N2 (a + /// String) and returns unused. It creates a new initialized immutable + /// indirect binding for the name N. A binding must not already exist in + /// this Environment Record for N. N2 is the name of a binding that exists + /// in M's Module Environment Record. Accesses to the value of the new + /// binding will indirectly access the bound value of the target binding. + pub(crate) fn create_import_binding( + self, + agent: &mut Agent, + name: String, + module: Module, + name2: String, + ) { + // 1. Assert: envRec does not already have a binding for N. + debug_assert!(!self.0.has_binding(agent, name)); + // 2. Assert: When M.[[Environment]] is instantiated, it will have a + // direct binding for N2. + // 3. Create an immutable indirect binding in envRec for N that + // references M and N2 as its target binding and record that the + // binding is initialized. + // 4. Return unused. + todo!(); + } +} diff --git a/nova_vm/src/ecmascript/types/language/function.rs b/nova_vm/src/ecmascript/types/language/function.rs index ed6de28a7..8323d6f69 100644 --- a/nova_vm/src/ecmascript/types/language/function.rs +++ b/nova_vm/src/ecmascript/types/language/function.rs @@ -59,7 +59,7 @@ impl std::fmt::Debug for Function { Function::BuiltinGeneratorFunction => todo!(), Function::BuiltinConstructorFunction => todo!(), Function::BuiltinPromiseResolveFunction => todo!(), - Function::BuiltinPromiseRejectFunction(BuiltinPromiseRejectFunction) => { + Function::BuiltinPromiseRejectFunction(d) => { write!(f, "BuiltinPromiseRejectFunction({:?})", d) } Function::BuiltinPromiseCollectorFunction => todo!(), @@ -112,7 +112,7 @@ impl TryFrom for Function { Object::BuiltinGeneratorFunction => Ok(Function::BuiltinGeneratorFunction), Object::BuiltinConstructorFunction => Ok(Function::BuiltinConstructorFunction), Object::BuiltinPromiseResolveFunction => Ok(Function::BuiltinPromiseResolveFunction), - Object::BuiltinPromiseRejectFunction => Ok(Function::BuiltinPromiseResolveFunction), + Object::BuiltinPromiseRejectFunction(_) => Ok(Function::BuiltinPromiseResolveFunction), Object::BuiltinPromiseCollectorFunction => { Ok(Function::BuiltinPromiseCollectorFunction) } @@ -137,9 +137,7 @@ impl TryFrom for Function { Value::ECMAScriptFunction(d) => Ok(Function::from(d)), Value::BuiltinGeneratorFunction => Ok(Function::BuiltinGeneratorFunction), Value::BuiltinConstructorFunction => Ok(Function::BuiltinConstructorFunction), - Value::BuiltinPromiseResolveFunction(d) => { - Ok(Function::BuiltinPromiseResolveFunction(d)) - } + Value::BuiltinPromiseResolveFunction => Ok(Function::BuiltinPromiseResolveFunction), Value::BuiltinPromiseRejectFunction(d) => Ok(Function::BuiltinPromiseRejectFunction(d)), Value::BuiltinPromiseCollectorFunction => Ok(Function::BuiltinPromiseCollectorFunction), Value::BuiltinProxyRevokerFunction => Ok(Function::BuiltinProxyRevokerFunction), @@ -319,7 +317,7 @@ impl InternalMethods for Function { Function::BuiltinGeneratorFunction => todo!(), Function::BuiltinConstructorFunction => todo!(), Function::BuiltinPromiseResolveFunction => todo!(), - Function::BuiltinPromiseRejectFunction => todo!(), + Function::BuiltinPromiseRejectFunction(_) => todo!(), Function::BuiltinPromiseCollectorFunction => todo!(), Function::BuiltinProxyRevokerFunction => todo!(), Function::ECMAScriptAsyncFunction => todo!(), @@ -345,7 +343,7 @@ impl InternalMethods for Function { Function::BuiltinGeneratorFunction => todo!(), Function::BuiltinConstructorFunction => todo!(), Function::BuiltinPromiseResolveFunction => todo!(), - Function::BuiltinPromiseRejectFunction => todo!(), + Function::BuiltinPromiseRejectFunction(_) => todo!(), Function::BuiltinPromiseCollectorFunction => todo!(), Function::BuiltinProxyRevokerFunction => todo!(), Function::ECMAScriptAsyncFunction => todo!(), @@ -365,7 +363,7 @@ impl InternalMethods for Function { Function::BuiltinGeneratorFunction => todo!(), Function::BuiltinConstructorFunction => todo!(), Function::BuiltinPromiseResolveFunction => todo!(), - Function::BuiltinPromiseRejectFunction => todo!(), + Function::BuiltinPromiseRejectFunction(_) => todo!(), Function::BuiltinPromiseCollectorFunction => todo!(), Function::BuiltinProxyRevokerFunction => todo!(), Function::ECMAScriptAsyncFunction => todo!(), @@ -387,7 +385,7 @@ impl InternalMethods for Function { Function::BuiltinGeneratorFunction => todo!(), Function::BuiltinConstructorFunction => todo!(), Function::BuiltinPromiseResolveFunction => todo!(), - Function::BuiltinPromiseRejectFunction => todo!(), + Function::BuiltinPromiseRejectFunction(_) => todo!(), Function::BuiltinPromiseCollectorFunction => todo!(), Function::BuiltinProxyRevokerFunction => todo!(), Function::ECMAScriptAsyncFunction => todo!(), @@ -413,7 +411,7 @@ impl InternalMethods for Function { Function::BuiltinGeneratorFunction => todo!(), Function::BuiltinConstructorFunction => todo!(), Function::BuiltinPromiseResolveFunction => todo!(), - Function::BuiltinPromiseRejectFunction => todo!(), + Function::BuiltinPromiseRejectFunction(_) => todo!(), Function::BuiltinPromiseCollectorFunction => todo!(), Function::BuiltinProxyRevokerFunction => todo!(), Function::ECMAScriptAsyncFunction => todo!(), @@ -441,7 +439,7 @@ impl InternalMethods for Function { Function::BuiltinGeneratorFunction => todo!(), Function::BuiltinConstructorFunction => todo!(), Function::BuiltinPromiseResolveFunction => todo!(), - Function::BuiltinPromiseRejectFunction => todo!(), + Function::BuiltinPromiseRejectFunction(_) => todo!(), Function::BuiltinPromiseCollectorFunction => todo!(), Function::BuiltinProxyRevokerFunction => todo!(), Function::ECMAScriptAsyncFunction => todo!(), @@ -463,7 +461,7 @@ impl InternalMethods for Function { Function::BuiltinGeneratorFunction => todo!(), Function::BuiltinConstructorFunction => todo!(), Function::BuiltinPromiseResolveFunction => todo!(), - Function::BuiltinPromiseRejectFunction => todo!(), + Function::BuiltinPromiseRejectFunction(_) => todo!(), Function::BuiltinPromiseCollectorFunction => todo!(), Function::BuiltinProxyRevokerFunction => todo!(), Function::ECMAScriptAsyncFunction => todo!(), @@ -490,7 +488,7 @@ impl InternalMethods for Function { Function::BuiltinGeneratorFunction => todo!(), Function::BuiltinConstructorFunction => todo!(), Function::BuiltinPromiseResolveFunction => todo!(), - Function::BuiltinPromiseRejectFunction(d) => agent[d].object_index, + Function::BuiltinPromiseRejectFunction(d) => todo!(), Function::BuiltinPromiseCollectorFunction => todo!(), Function::BuiltinProxyRevokerFunction => todo!(), Function::ECMAScriptAsyncFunction => todo!(), @@ -518,7 +516,7 @@ impl InternalMethods for Function { Function::BuiltinGeneratorFunction => todo!(), Function::BuiltinConstructorFunction => todo!(), Function::BuiltinPromiseResolveFunction => todo!(), - Function::BuiltinPromiseRejectFunction => todo!(), + Function::BuiltinPromiseRejectFunction(_) => todo!(), Function::BuiltinPromiseCollectorFunction => todo!(), Function::BuiltinProxyRevokerFunction => todo!(), Function::ECMAScriptAsyncFunction => todo!(), @@ -540,7 +538,7 @@ impl InternalMethods for Function { Function::BuiltinGeneratorFunction => todo!(), Function::BuiltinConstructorFunction => todo!(), Function::BuiltinPromiseResolveFunction => todo!(), - Function::BuiltinPromiseRejectFunction => todo!(), + Function::BuiltinPromiseRejectFunction(_) => todo!(), Function::BuiltinPromiseCollectorFunction => todo!(), Function::BuiltinProxyRevokerFunction => todo!(), Function::ECMAScriptAsyncFunction => todo!(), @@ -562,7 +560,7 @@ impl InternalMethods for Function { Function::BuiltinGeneratorFunction => todo!(), Function::BuiltinConstructorFunction => todo!(), Function::BuiltinPromiseResolveFunction => todo!(), - Function::BuiltinPromiseRejectFunction => todo!(), + Function::BuiltinPromiseRejectFunction(_) => todo!(), Function::BuiltinPromiseCollectorFunction => todo!(), Function::BuiltinProxyRevokerFunction => todo!(), Function::ECMAScriptAsyncFunction => todo!(), @@ -589,7 +587,7 @@ impl InternalMethods for Function { Function::BuiltinGeneratorFunction => todo!(), Function::BuiltinConstructorFunction => todo!(), Function::BuiltinPromiseResolveFunction => todo!(), - Function::BuiltinPromiseRejectFunction(d) => agent[d].object_index, + Function::BuiltinPromiseRejectFunction(d) => todo!(), Function::BuiltinPromiseCollectorFunction => todo!(), Function::BuiltinProxyRevokerFunction => todo!(), Function::ECMAScriptAsyncFunction => todo!(), @@ -616,7 +614,7 @@ impl InternalMethods for Function { Function::BuiltinGeneratorFunction => todo!(), Function::BuiltinConstructorFunction => todo!(), Function::BuiltinPromiseResolveFunction => todo!(), - Function::BuiltinPromiseRejectFunction(d) => agent[d].object_index, + Function::BuiltinPromiseRejectFunction(d) => todo!(), Function::BuiltinPromiseCollectorFunction => todo!(), Function::BuiltinProxyRevokerFunction => todo!(), Function::ECMAScriptAsyncFunction => todo!(), diff --git a/nova_vm/src/ecmascript/types/language/object.rs b/nova_vm/src/ecmascript/types/language/object.rs index d07c99ea8..e77b0f6f4 100644 --- a/nova_vm/src/ecmascript/types/language/object.rs +++ b/nova_vm/src/ecmascript/types/language/object.rs @@ -246,7 +246,7 @@ impl From for Value { Object::BuiltinGeneratorFunction => Value::BuiltinGeneratorFunction, Object::BuiltinConstructorFunction => Value::BuiltinConstructorFunction, Object::BuiltinPromiseResolveFunction => Value::BuiltinPromiseResolveFunction, - Object::BuiltinPromiseRejectFunction => Value::BuiltinPromiseRejectFunction(todo!()), + Object::BuiltinPromiseRejectFunction(d) => Value::BuiltinPromiseRejectFunction(d), Object::BuiltinPromiseCollectorFunction => Value::BuiltinPromiseCollectorFunction, Object::BuiltinProxyRevokerFunction => Value::BuiltinProxyRevokerFunction, Object::ECMAScriptAsyncFunction => Value::ECMAScriptAsyncFunction, @@ -380,7 +380,7 @@ impl OrdinaryObjectInternalSlots for Object { Object::BuiltinGeneratorFunction => todo!(), Object::BuiltinConstructorFunction => todo!(), Object::BuiltinPromiseResolveFunction => todo!(), - Object::BuiltinPromiseRejectFunction => todo!(), + Object::BuiltinPromiseRejectFunction(_) => todo!(), Object::BuiltinPromiseCollectorFunction => todo!(), Object::BuiltinProxyRevokerFunction => todo!(), Object::ECMAScriptAsyncFunction => todo!(), @@ -438,7 +438,7 @@ impl OrdinaryObjectInternalSlots for Object { Object::BuiltinGeneratorFunction => todo!(), Object::BuiltinConstructorFunction => todo!(), Object::BuiltinPromiseResolveFunction => todo!(), - Object::BuiltinPromiseRejectFunction => todo!(), + Object::BuiltinPromiseRejectFunction(_) => todo!(), Object::BuiltinPromiseCollectorFunction => todo!(), Object::BuiltinProxyRevokerFunction => todo!(), Object::ECMAScriptAsyncFunction => todo!(), @@ -490,7 +490,7 @@ impl OrdinaryObjectInternalSlots for Object { Object::BuiltinGeneratorFunction => todo!(), Object::BuiltinConstructorFunction => todo!(), Object::BuiltinPromiseResolveFunction => todo!(), - Object::BuiltinPromiseRejectFunction => todo!(), + Object::BuiltinPromiseRejectFunction(_) => todo!(), Object::BuiltinPromiseCollectorFunction => todo!(), Object::BuiltinProxyRevokerFunction => todo!(), Object::ECMAScriptAsyncFunction => todo!(), @@ -552,7 +552,7 @@ impl OrdinaryObjectInternalSlots for Object { Object::BuiltinGeneratorFunction => todo!(), Object::BuiltinConstructorFunction => todo!(), Object::BuiltinPromiseResolveFunction => todo!(), - Object::BuiltinPromiseRejectFunction => todo!(), + Object::BuiltinPromiseRejectFunction(_) => todo!(), Object::BuiltinPromiseCollectorFunction => todo!(), Object::BuiltinProxyRevokerFunction => todo!(), Object::ECMAScriptAsyncFunction => todo!(), @@ -606,7 +606,7 @@ impl InternalMethods for Object { Object::BuiltinGeneratorFunction => todo!(), Object::BuiltinConstructorFunction => todo!(), Object::BuiltinPromiseResolveFunction => todo!(), - Object::BuiltinPromiseRejectFunction => todo!(), + Object::BuiltinPromiseRejectFunction(_) => todo!(), Object::BuiltinPromiseCollectorFunction => todo!(), Object::BuiltinProxyRevokerFunction => todo!(), Object::ECMAScriptAsyncFunction => todo!(), @@ -672,7 +672,7 @@ impl InternalMethods for Object { Object::BuiltinGeneratorFunction => todo!(), Object::BuiltinConstructorFunction => todo!(), Object::BuiltinPromiseResolveFunction => todo!(), - Object::BuiltinPromiseRejectFunction => todo!(), + Object::BuiltinPromiseRejectFunction(_) => todo!(), Object::BuiltinPromiseCollectorFunction => todo!(), Object::BuiltinProxyRevokerFunction => todo!(), Object::ECMAScriptAsyncFunction => todo!(), @@ -724,7 +724,7 @@ impl InternalMethods for Object { Object::BuiltinGeneratorFunction => todo!(), Object::BuiltinConstructorFunction => todo!(), Object::BuiltinPromiseResolveFunction => todo!(), - Object::BuiltinPromiseRejectFunction => todo!(), + Object::BuiltinPromiseRejectFunction(_) => todo!(), Object::BuiltinPromiseCollectorFunction => todo!(), Object::BuiltinProxyRevokerFunction => todo!(), Object::ECMAScriptAsyncFunction => todo!(), @@ -778,7 +778,7 @@ impl InternalMethods for Object { Object::BuiltinGeneratorFunction => todo!(), Object::BuiltinConstructorFunction => todo!(), Object::BuiltinPromiseResolveFunction => todo!(), - Object::BuiltinPromiseRejectFunction => todo!(), + Object::BuiltinPromiseRejectFunction(_) => todo!(), Object::BuiltinPromiseCollectorFunction => todo!(), Object::BuiltinProxyRevokerFunction => todo!(), Object::ECMAScriptAsyncFunction => todo!(), @@ -844,7 +844,7 @@ impl InternalMethods for Object { Object::BuiltinGeneratorFunction => todo!(), Object::BuiltinConstructorFunction => todo!(), Object::BuiltinPromiseResolveFunction => todo!(), - Object::BuiltinPromiseRejectFunction => todo!(), + Object::BuiltinPromiseRejectFunction(_) => todo!(), Object::BuiltinPromiseCollectorFunction => todo!(), Object::BuiltinProxyRevokerFunction => todo!(), Object::ECMAScriptAsyncFunction => todo!(), @@ -933,7 +933,7 @@ impl InternalMethods for Object { Object::BuiltinGeneratorFunction => todo!(), Object::BuiltinConstructorFunction => todo!(), Object::BuiltinPromiseResolveFunction => todo!(), - Object::BuiltinPromiseRejectFunction => todo!(), + Object::BuiltinPromiseRejectFunction(_) => todo!(), Object::BuiltinPromiseCollectorFunction => todo!(), Object::BuiltinProxyRevokerFunction => todo!(), Object::ECMAScriptAsyncFunction => todo!(), @@ -1003,7 +1003,7 @@ impl InternalMethods for Object { Object::BuiltinGeneratorFunction => todo!(), Object::BuiltinConstructorFunction => todo!(), Object::BuiltinPromiseResolveFunction => todo!(), - Object::BuiltinPromiseRejectFunction => todo!(), + Object::BuiltinPromiseRejectFunction(_) => todo!(), Object::BuiltinPromiseCollectorFunction => todo!(), Object::BuiltinProxyRevokerFunction => todo!(), Object::ECMAScriptAsyncFunction => todo!(), @@ -1070,7 +1070,7 @@ impl InternalMethods for Object { Object::BuiltinGeneratorFunction => todo!(), Object::BuiltinConstructorFunction => todo!(), Object::BuiltinPromiseResolveFunction => todo!(), - Object::BuiltinPromiseRejectFunction => todo!(), + Object::BuiltinPromiseRejectFunction(_) => todo!(), Object::BuiltinPromiseCollectorFunction => todo!(), Object::BuiltinProxyRevokerFunction => todo!(), Object::ECMAScriptAsyncFunction => todo!(), @@ -1142,7 +1142,7 @@ impl InternalMethods for Object { Object::BuiltinGeneratorFunction => todo!(), Object::BuiltinConstructorFunction => todo!(), Object::BuiltinPromiseResolveFunction => todo!(), - Object::BuiltinPromiseRejectFunction => todo!(), + Object::BuiltinPromiseRejectFunction(_) => todo!(), Object::BuiltinPromiseCollectorFunction => todo!(), Object::BuiltinProxyRevokerFunction => todo!(), Object::ECMAScriptAsyncFunction => todo!(), @@ -1198,7 +1198,7 @@ impl InternalMethods for Object { Object::BuiltinGeneratorFunction => todo!(), Object::BuiltinConstructorFunction => todo!(), Object::BuiltinPromiseResolveFunction => todo!(), - Object::BuiltinPromiseRejectFunction => todo!(), + Object::BuiltinPromiseRejectFunction(_) => todo!(), Object::BuiltinPromiseCollectorFunction => todo!(), Object::BuiltinProxyRevokerFunction => todo!(), Object::ECMAScriptAsyncFunction => todo!(), @@ -1252,7 +1252,7 @@ impl InternalMethods for Object { Object::BuiltinGeneratorFunction => todo!(), Object::BuiltinConstructorFunction => todo!(), Object::BuiltinPromiseResolveFunction => todo!(), - Object::BuiltinPromiseRejectFunction => todo!(), + Object::BuiltinPromiseRejectFunction(_) => todo!(), Object::BuiltinPromiseCollectorFunction => todo!(), Object::BuiltinProxyRevokerFunction => todo!(), Object::ECMAScriptAsyncFunction => todo!(), diff --git a/nova_vm/src/engine/bytecode/vm.rs b/nova_vm/src/engine/bytecode/vm.rs index c1e622eaf..676f5dfba 100644 --- a/nova_vm/src/engine/bytecode/vm.rs +++ b/nova_vm/src/engine/bytecode/vm.rs @@ -770,7 +770,7 @@ fn typeof_operator(_: &mut Agent, val: Value) -> String { Value::BuiltinGeneratorFunction | Value::BuiltinConstructorFunction | Value::BuiltinPromiseResolveFunction | - Value::BuiltinPromiseRejectFunction | + Value::BuiltinPromiseRejectFunction(_) | Value::BuiltinPromiseCollectorFunction | Value::BuiltinProxyRevokerFunction | Value::ECMAScriptAsyncFunction | diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index 028e0f786..3026df5df 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -34,8 +34,9 @@ use self::{ use crate::ecmascript::{ builtins::{ control_abstraction_objects::promise_objects::promise_abstract_operations::{ - promise_capability_records::PromiseCapabilityRecord, - promise_reaction_records::PromiseReactionRecord, PromiseRejectFunctionHeapData, + promise_capability_records::{PromiseCapability, PromiseCapabilityRecord}, + promise_reaction_records::PromiseReactionRecord, + PromiseRejectFunctionHeapData, }, data_view::{data::DataViewHeapData, DataView}, date::{data::DateHeapData, Date}, @@ -259,6 +260,13 @@ impl CreateHeapData for Heap { } } +impl CreateHeapData for Heap { + fn create(&mut self, data: PromiseCapabilityRecord) -> PromiseCapability { + self.promise_capability_records.push(Some(data)); + PromiseCapability::last(&self.promise_capability_records) + } +} + impl CreateHeapData for Heap { fn create(&mut self, data: RegExpHeapData) -> Object { self.regexps.push(Some(data)); From 0c31db122733c088f8b9b1baf032e6103c649e4a Mon Sep 17 00:00:00 2001 From: Aapo Alasuutari Date: Sun, 26 May 2024 09:56:57 +0300 Subject: [PATCH 6/8] ModuleEnvironments, quite a bit of other work --- .../promise_abstract_operations.rs | 80 +- .../promise_capability_records.rs | 4 +- .../promise_objects/promise_constructor.rs | 155 +- nova_vm/src/ecmascript/builtins/module.rs | 37 +- .../module/abstract_module_records.rs | 8 +- .../builtins/module/cyclic_module_records.rs | 67 +- .../src/ecmascript/builtins/module/data.rs | 15 +- .../module/source_text_module_records.rs | 194 ++- .../src/ecmascript/builtins/promise/data.rs | 15 +- nova_vm/src/ecmascript/execution.rs | 4 +- nova_vm/src/ecmascript/execution/agent.rs | 166 ++- .../src/ecmascript/execution/environments.rs | 50 +- .../environments/declarative_environment.rs | 1 + .../environments/module_environment.rs | 283 +++- nova_vm/src/ecmascript/execution/realm.rs | 4 +- nova_vm/src/ecmascript/scripts_and_modules.rs | 6 +- .../ecmascript/scripts_and_modules/script.rs | 2 +- .../ecmascript/types/spec/abstract_closure.rs | 13 +- nova_vm/src/engine/bytecode/executable.rs | 14 + nova_vm/src/engine/bytecode/vm.rs | 1 + nova_vm/src/heap.rs | 1 + nova_vm/src/heap/heap_bits.rs | 1264 +++++++++-------- nova_vm/src/heap/heap_gc.rs | 117 +- 23 files changed, 1716 insertions(+), 785 deletions(-) diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations.rs index 946433dde..de04575d2 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations.rs @@ -7,7 +7,10 @@ use crate::{ promise::{data::PromiseHeapData, Promise}, ArgumentsList, }, - execution::{agent::ExceptionType, Agent, JsResult}, + execution::{ + agent::{ExceptionType, JobCallbackRecord}, + Agent, JsResult, + }, types::{ AbstractClosureHeapData, Function, IntoFunction, IntoObject, IntoValue, Object, String, Value, @@ -401,3 +404,78 @@ pub(crate) fn trigger_promise_reactions( // b. Perform HostEnqueuePromiseJob(job.[[Job]], job.[[Realm]]). // 2. Return unused. } + +/// ### [27.2.2.1 NewPromiseReactionJob ( reaction, argument )](https://tc39.es/ecma262/#sec-newpromisereactionjob) +/// +/// The abstract operation NewPromiseReactionJob takes arguments reaction (a +/// PromiseReaction Record) and argument (an ECMAScript language value) and +/// returns a Record with fields \[\[Job\]\] (a Job Abstract Closure) and +/// \[\[Realm\]\] (a Realm Record or null). It returns a new Job Abstract +/// Closure that applies the appropriate handler to the incoming value, and +/// uses the handler's return value to resolve or reject the derived promise +/// associated with that handler. +pub(crate) fn new_promise_reaction_job( + agent: &mut Agent, + reaction: PromiseReaction, + argument: Value, +) { + // 1. Let job be a new Job Abstract Closure with no parameters that captures reaction and argument and performs the following steps when called: + // a. Let promiseCapability be reaction.[[Capability]]. + // b. Let type be reaction.[[Type]]. + // c. Let handler be reaction.[[Handler]]. + // d. If handler is empty, then + // i. If type is fulfill, then + // 1. Let handlerResult be NormalCompletion(argument). + // ii. Else, + // 1. Assert: type is reject. + // 2. Let handlerResult be ThrowCompletion(argument). + // e. Else, + // i. Let handlerResult be Completion(HostCallJobCallback(handler, undefined, « argument »)). + // f. If promiseCapability is undefined, then + // i. Assert: handlerResult is not an abrupt completion. + // ii. Return empty. + // g. Assert: promiseCapability is a PromiseCapability Record. + // h. If handlerResult is an abrupt completion, then + // i. Return ? Call(promiseCapability.[[Reject]], undefined, « handlerResult.[[Value]] »). + // i. Else, + // i. Return ? Call(promiseCapability.[[Resolve]], undefined, « handlerResult.[[Value]] »). + // 2. Let handlerRealm be null. + // 3. If reaction.[[Handler]] is not empty, then + // a. Let getHandlerRealmResult be Completion(GetFunctionRealm(reaction.[[Handler]].[[Callback]])). + // b. If getHandlerRealmResult is a normal completion, set handlerRealm to getHandlerRealmResult.[[Value]]. + // c. Else, set handlerRealm to the current Realm Record. + // d. NOTE: handlerRealm is never null unless the handler is undefined. When the handler is a revoked Proxy and no ECMAScript code runs, handlerRealm is used to create error objects. + // 4. Return the Record { [[Job]]: job, [[Realm]]: handlerRealm }. +} + +/// 27.2.2.2 NewPromiseResolveThenableJob ( promiseToResolve, thenable, then ) +/// +/// The abstract operation NewPromiseResolveThenableJob takes arguments +/// promiseToResolve (a Promise), thenable (an Object), and then (a JobCallback +/// Record) and returns a Record with fields \[\[Job\]\] (a Job Abstract +/// Closure) and \[\[Realm\]\] (a Realm Record). +/// +/// #### Note +/// +/// This Job uses the supplied thenable and its then method to resolve the +/// given promise. This process must take place as a Job to ensure that the +/// evaluation of the then method occurs after evaluation of any surrounding +/// code has completed. +pub(crate) fn new_promise_resolve_thenable_job( + agent: &mut Agent, + promise_to_resolve: Promise, + thenable: Object, + then: JobCallbackRecord, +) { + // 1. Let job be a new Job Abstract Closure with no parameters that captures promiseToResolve, thenable, and then and performs the following steps when called: + // a. Let resolvingFunctions be CreateResolvingFunctions(promiseToResolve). + // b. Let thenCallResult be Completion(HostCallJobCallback(then, thenable, « resolvingFunctions.[[Resolve]], resolvingFunctions.[[Reject]] »)). + // c. If thenCallResult is an abrupt completion, then + // i. Return ? Call(resolvingFunctions.[[Reject]], undefined, « thenCallResult.[[Value]] »). + // d. Return ? thenCallResult. + // 2. Let getThenRealmResult be Completion(GetFunctionRealm(then.[[Callback]])). + // 3. If getThenRealmResult is a normal completion, let thenRealm be getThenRealmResult.[[Value]]. + // 4. Else, let thenRealm be the current Realm Record. + // 5. NOTE: thenRealm is never null. When then.[[Callback]] is a revoked Proxy and no code runs, thenRealm is used to create error objects. + // 6. Return the Record { [[Job]]: job, [[Realm]]: thenRealm }. +} diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_capability_records.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_capability_records.rs index 9c0a7e914..9dcabe02b 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_capability_records.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_capability_records.rs @@ -4,10 +4,10 @@ use std::ops::{Index, IndexMut}; use crate::{ ecmascript::{ - abstract_operations::operations_on_objects::{call, call_function}, + abstract_operations::operations_on_objects::call_function, builtins::ArgumentsList, execution::{agent::JsError, Agent, JsResult}, - types::{Function, IntoValue, Object, Value}, + types::{Function, Object, Value}, }, heap::{indexes::BaseIndex, Heap}, }; diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs index c2a244436..394f5275d 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_constructor.rs @@ -5,10 +5,17 @@ use crate::{ }, builders::builtin_function_builder::BuiltinFunctionBuilder, builtins::{ - ordinary::ordinary_create_from_constructor, promise::Promise, ArgumentsList, Behaviour, - Builtin, BuiltinGetter, BuiltinIntrinsicConstructor, + ordinary::ordinary_create_from_constructor, + promise::{ + data::{PromiseReactions, PromiseState}, + Promise, + }, + ArgumentsList, Behaviour, Builtin, BuiltinGetter, BuiltinIntrinsicConstructor, + }, + execution::{ + agent::{ExceptionType, PromiseRejectionOperation}, + Agent, JsResult, ProtoIntrinsics, RealmIdentifier, }, - execution::{agent::ExceptionType, Agent, JsResult, ProtoIntrinsics, RealmIdentifier}, types::{ Function, IntoObject, IntoValue, Object, PropertyKey, String, Value, BUILTIN_STRING_MEMORY, @@ -17,7 +24,13 @@ use crate::{ heap::{IntrinsicConstructorIndexes, WellKnownSymbolIndexes}, }; -use super::promise_abstract_operations::create_resolving_functions; +use super::promise_abstract_operations::{ + create_resolving_functions, new_promise_reaction_job, + promise_capability_records::PromiseCapability, + promise_reaction_records::{ + PromiseReactionHandler, PromiseReactionRecord, PromiseReactionType, + }, +}; pub(crate) struct PromiseConstructor; impl Builtin for PromiseConstructor { @@ -243,3 +256,137 @@ impl PromiseConstructor { .build(); } } + +/// ### [27.2.5.4.1 PerformPromiseThen ( promise, onFulfilled, onRejected \[ , resultCapability \] )](https://tc39.es/ecma262/#sec-performpromisethen) +/// +/// The abstract operation PerformPromiseThen takes arguments promise (a +/// Promise), onFulfilled (an ECMAScript language value), and onRejected (an +/// ECMAScript language value) and optional argument resultCapability (a +/// PromiseCapability Record) and returns an ECMAScript language value. It +/// performs the “then” operation on promise using onFulfilled and onRejected +/// as its settlement actions. If resultCapability is passed, the result is +/// stored by updating resultCapability's promise. If it is not passed, then +/// PerformPromiseThen is being called by a specification-internal operation +/// where the result does not matter. +pub(crate) fn perform_promise_then( + agent: &mut Agent, + promise: Promise, + on_fulfilled: Value, + on_rejected: Value, + result_capability: Option, +) -> Option { + // 1. Assert: IsPromise(promise) is true. + // Already asserted by type. + + // 2. If resultCapability is not present, then + // a. Set resultCapability to undefined. + // 3. If IsCallable(onFulfilled) is false, then + let on_fulfilled_job_callback = if !is_callable(on_fulfilled) { + // a. Let onFulfilledJobCallback be empty. + None + } else { + // 4. Else, + // a. Let onFulfilledJobCallback be HostMakeJobCallback(onFulfilled). + // host_make_job_callback(on_fulfilled); + Some(()) + }; + let on_rejected_job_callback = if !is_callable(on_rejected) { + // 5. If IsCallable(onRejected) is false, then + // a. Let onRejectedJobCallback be empty. + None + } else { + // 6. Else, + // a. Let onRejectedJobCallback be HostMakeJobCallback(onRejected). + // host_make_job_callback(on_rejected); + Some(()) + }; + // 7. Let fulfillReaction be the PromiseReaction Record { [[Capability]]: resultCapability, [[Type]]: fulfill, [[Handler]]: onFulfilledJobCallback }. + let fulfill_reaction = PromiseReactionRecord { + capability: result_capability, + handler: on_fulfilled_job_callback.map_or_else( + || PromiseReactionHandler::Empty(PromiseReactionType::Fulfill), + |job| PromiseReactionHandler::JobCallback(job), + ), + }; + // 8. Let rejectReaction be the PromiseReaction Record { [[Capability]]: resultCapability, [[Type]]: reject, [[Handler]]: onRejectedJobCallback }. + let reject_reaction = PromiseReactionRecord { + capability: result_capability, + handler: on_rejected_job_callback.map_or_else( + || PromiseReactionHandler::Empty(PromiseReactionType::Reject), + |job| PromiseReactionHandler::JobCallback(job), + ), + }; + // 9. If promise.[[PromiseState]] is pending, then + match agent[promise].promise_state { + PromiseState::Pending => { + // a. Append fulfillReaction to promise.[[PromiseFulfillReactions]]. + let existing_fulfill_reactions = &mut agent[promise].promise_fulfill_reactions; + match existing_fulfill_reactions { + Some(existing) => match existing { + PromiseReactions::One(previous) => { + *existing = PromiseReactions::Many(vec![*previous, fulfill_reaction]); + } + PromiseReactions::Many(multiple) => { + multiple.push(fulfill_reaction); + } + }, + None => { + *existing_fulfill_reactions = Some(PromiseReactions::One(fulfill_reaction)); + } + } + // b. Append rejectReaction to promise.[[PromiseRejectReactions]]. + let existing_reject_reactions = &mut agent[promise].promise_reject_reactions; + match existing_reject_reactions { + Some(existing) => match existing { + PromiseReactions::One(previous) => { + *existing = PromiseReactions::Many(vec![*previous, reject_reaction]); + } + PromiseReactions::Many(multiple) => { + multiple.push(reject_reaction); + } + }, + None => { + *existing_reject_reactions = Some(PromiseReactions::One(reject_reaction)); + } + } + } + // 10. Else if promise.[[PromiseState]] is fulfilled, then + PromiseState::Fulfilled { + promise_result: value, + } => { + // a. Let value be promise.[[PromiseResult]]. + // b. Let fulfillJob be NewPromiseReactionJob(fulfillReaction, value). + let fulfill_job = new_promise_reaction_job(agent, fulfill_reaction, value); + // c. Perform HostEnqueuePromiseJob(fulfillJob.[[Job]], fulfillJob.[[Realm]]). + agent + .host_hooks + .host_enqueue_promise_job(fulfill_job.job, fulfill_job.realm); + } + // 11. Else, + PromiseState::Rejected { + promise_result: reason, + } => { + // a. Assert: The value of promise.[[PromiseState]] is rejected. + // b. Let reason be promise.[[PromiseResult]]. + // c. If promise.[[PromiseIsHandled]] is false, perform HostPromiseRejectionTracker(promise, "handle"). + if !agent[promise].promise_is_handled { + agent + .host_hooks + .host_promise_rejection_tracker(promise, PromiseRejectionOperation::Handle); + } + // d. Let rejectJob be NewPromiseReactionJob(rejectReaction, reason). + let reject_job = new_promise_reaction_job(agent, reject_reaction, reason); + // e. Perform HostEnqueuePromiseJob(rejectJob.[[Job]], rejectJob.[[Realm]]). + agent + .host_hooks + .host_enqueue_promise_job(reject_job.job, reject_job.realm); + } + } + // 12. Set promise.[[PromiseIsHandled]] to true. + agent[promise].promise_is_handled = true; + // 13. If resultCapability is undefined, then + // a. Return undefined. + // 14. Else, + // a. Return resultCapability.[[Promise]]. + result_capability.map(|result_capability| agent[result_capability].promise) +} diff --git a/nova_vm/src/ecmascript/builtins/module.rs b/nova_vm/src/ecmascript/builtins/module.rs index 942f2437f..0b6f642e1 100644 --- a/nova_vm/src/ecmascript/builtins/module.rs +++ b/nova_vm/src/ecmascript/builtins/module.rs @@ -17,7 +17,7 @@ use crate::{ use self::{ abstract_module_records::{ResolveExportResult, ResolvedBindingName}, data::ModuleHeapData, - source_text_module_records::resolve_export, + source_text_module_records::{get_module_namespace, resolve_export}, }; use super::ordinary::{ @@ -326,10 +326,10 @@ impl InternalMethods for Module { // 8. Assert: targetModule is not undefined. let target_module = binding.module.unwrap(); // 9. If binding.[[BindingName]] is NAMESPACE, then - let _binding_name = match binding.binding_name { + let binding_name = match binding.binding_name { ResolvedBindingName::Namespace => { // a. Return GetModuleNamespace(targetModule). - todo!(); + return Ok(get_module_namespace(agent, target_module).into_value()); } ResolvedBindingName::String(data) => String::String(data), ResolvedBindingName::SmallString(data) => String::SmallString(data), @@ -344,7 +344,7 @@ impl InternalMethods for Module { )), Some(target_env) => { // 12. Return ? targetEnv.GetBindingValue(binding.[[BindingName]], true). - target_env.get_ + target_env.get_binding_value(agent, binding_name, true) } } } @@ -415,3 +415,32 @@ impl InternalMethods for Module { Ok(own_property_keys) } } + +/// ### [10.4.6.12 ModuleNamespaceCreate ( module, exports )](https://tc39.es/ecma262/#sec-modulenamespacecreate) +/// +/// The abstract operation ModuleNamespaceCreate takes arguments module (a +/// Module Record) and exports (a List of Strings) and returns a module +/// namespace exotic object. It is used to specify the creation of new module +/// namespace exotic objects. +pub(crate) fn module_namespace_create( + agent: &mut Agent, + module: Module, + mut exports: Box<[String]>, +) { + // 1. Assert: module.[[Namespace]] is empty. + debug_assert!(!agent[module].r#abstract.namespace); + // NOTE: All of the following steps are already done by default. + // 2. Let internalSlotsList be the internal slots listed in Table 33. + // 3. Let M be MakeBasicObject(internalSlotsList). + // 4. Set M's essential internal methods to the definitions specified in 10.4.6. + // 5. Set M.[[Module]] to module. + // 6. Let sortedExports be a List whose elements are the elements of exports, sorted according to lexicographic code unit order. + exports.sort_by(|a, b| a.as_str(agent).cmp(&b.as_str(agent))); + // 7. Set M.[[Exports]] to sortedExports. + let module_data = &mut agent[module]; + module_data.exports = exports; + // 8. Create own properties of M corresponding to the definitions in 28.3. + // 9. Set module.[[Namespace]] to M. + module_data.r#abstract.namespace = true; + // 10. Return M. +} diff --git a/nova_vm/src/ecmascript/builtins/module/abstract_module_records.rs b/nova_vm/src/ecmascript/builtins/module/abstract_module_records.rs index 644183ebd..d2c673beb 100644 --- a/nova_vm/src/ecmascript/builtins/module/abstract_module_records.rs +++ b/nova_vm/src/ecmascript/builtins/module/abstract_module_records.rs @@ -24,12 +24,16 @@ pub(crate) struct ModuleRecord { /// /// The Environment Record containing the top level bindings for this /// module. This field is set when the module is linked. - pub(super) environment: Option, + pub(crate) environment: Option, /// \[\[Namespace]] /// /// The Module Namespace Object (28.3) if one has been created for this /// module. - pub(super) namespace: Option, + /// + /// NOTE: The ModuleRecord is contained in the Module Namespace Exotic + /// Object's heap data; this boolean then just tells if the object has + /// been created. + pub(super) namespace: bool, /// \[\[HostDefined]] /// /// Field reserved for use by host environments that need to associate diff --git a/nova_vm/src/ecmascript/builtins/module/cyclic_module_records.rs b/nova_vm/src/ecmascript/builtins/module/cyclic_module_records.rs index a9c200e32..a032997cc 100644 --- a/nova_vm/src/ecmascript/builtins/module/cyclic_module_records.rs +++ b/nova_vm/src/ecmascript/builtins/module/cyclic_module_records.rs @@ -5,23 +5,23 @@ use oxc_span::Atom; use crate::ecmascript::{ abstract_operations::operations_on_objects::call_function, builtins::{ - control_abstraction_objects::promise_objects::promise_abstract_operations::{ - new_intrinsic_promise_capability, new_promise_capability, - promise_capability_records::PromiseCapability, + control_abstraction_objects::promise_objects::{ + promise_abstract_operations::{ + new_intrinsic_promise_capability, promise_capability_records::PromiseCapability, + }, + promise_constructor::perform_promise_then, }, - create_builtin_function, - error::Error, module::source_text_module_records::get_imported_module, promise::Promise, ArgumentsList, }, execution::{agent::JsError, Agent, JsResult}, + scripts_and_modules::script::HostDefined, types::{String, Value}, }; use super::{ - abstract_module_records::{ModuleRecord, NotLoadedErr, ResolvedBinding}, - Module, + abstract_module_records::ResolvedBinding, source_text_module_records::execute_module, Module, }; /// ### [CyclicModuleRecord] \[\[EvaluationError\]\] @@ -209,7 +209,7 @@ pub(crate) struct GraphLoadingStateRecord { /// /// It contains host-defined data to pass from the LoadRequestedModules /// caller to HostLoadImportedModule. - host_defined: Option>, + host_defined: Option, } impl Module { @@ -229,7 +229,7 @@ impl Module { fn load_requested_modules( self, agent: &mut Agent, - host_defined: Option>, + host_defined: Option, ) -> Promise { // 1. If hostDefined is not present, let hostDefined be empty. // TODO: 2. Let pc be ! NewPromiseCapability(%Promise%). @@ -251,6 +251,7 @@ impl Module { // 4. Perform InnerModuleLoading(state, module). inner_module_loading(agent, &mut state, self); // 5. Return pc.[[Promise]]. + Promise::try_from(agent[pc].promise).unwrap() // Note // The hostDefined parameter can be used to pass additional information @@ -258,7 +259,6 @@ impl Module { // HTML to set the correct fetch destination for // `` tags. `import()` expressions never // set the hostDefined parameter. - todo!(); } pub(crate) fn get_exported_names( @@ -299,8 +299,12 @@ impl Module { fn initialize_environment(self, agent: &mut Agent) {} - fn execute_module(self, agent: &mut Agent, promise_capability: Option<()>) -> JsResult<()> { - todo!() + fn execute_module( + self, + agent: &mut Agent, + promise_capability: Option, + ) -> JsResult<()> { + execute_module(agent, self, promise_capability) } } @@ -339,9 +343,9 @@ fn inner_module_loading(agent: &mut Agent, state: &mut GraphLoadingStateRecord, agent.host_hooks.host_load_imported_module( // agent, (), // module, - &required, + required.clone(), state.host_defined.as_ref().map(|boxed| boxed.as_ref()), - (), // state + state, ); // 2. NOTE: HostLoadImportedModule will call FinishLoadingImportedModule, // which re-enters the graph loading process through ContinueModuleLoading. @@ -361,11 +365,20 @@ fn inner_module_loading(agent: &mut Agent, state: &mut GraphLoadingStateRecord, // a. Set state.[[IsLoading]] to false. state.is_loading = false; // b. For each Cyclic Module Record loaded of state.[[Visited]], do - for _loaded in &state.visited { - // TODO: i. If loaded.[[Status]] is new, set loaded.[[Status]] to unlinked. + for loaded in &state.visited { + // i. If loaded.[[Status]] is new, set loaded.[[Status]] to unlinked. + if agent[*loaded].cyclic.status == CyclicModuleRecordStatus::New { + agent[*loaded].cyclic.status = CyclicModuleRecordStatus::Unlinked; + } } // c. Perform ! Call(state.[[PromiseCapability]].[[Resolve]], undefined, « undefined »). - // call_function(agent, state.promise_capability.resolve, Value::Undefined, Some(ArgumentsList(&[Value::Undefined]))); + let resolve = agent[state.promise_capability].resolve; + call_function( + agent, + resolve, + Value::Undefined, + Some(ArgumentsList(&[Value::Undefined])), + ); } // 6. Return unused. } @@ -377,7 +390,7 @@ fn inner_module_loading(agent: &mut Agent, state: &mut GraphLoadingStateRecord, /// containing a Module Record or a throw completion) and returns unused. It is /// used to re-enter the loading process after a call to /// HostLoadImportedModule. -fn continue_module_loading( +pub(super) fn continue_module_loading( agent: &mut Agent, state: &mut GraphLoadingStateRecord, module_completion: JsResult, @@ -979,11 +992,11 @@ pub(crate) fn execute_async_module(agent: &mut Agent, module: Module) { // 2. Assert: module.[[HasTLA]] is true. assert!(module_borrow.cyclic.has_top_level_await); // 3. Let capability be ! NewPromiseCapability(%Promise%). - let capability = (); // new_promise_capability(agent, ProtoIntrinsics::Promise); - // 4. Let fulfilledClosure be a new Abstract Closure with no parameters that captures module and performs the following steps when called: - // a. Perform AsyncModuleExecutionFulfilled(module). - // b. Return undefined. - // 5. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 0, "", « »). + let capability = new_intrinsic_promise_capability(agent); + // 4. Let fulfilledClosure be a new Abstract Closure with no parameters that captures module and performs the following steps when called: + // a. Perform AsyncModuleExecutionFulfilled(module). + // b. Return undefined. + // 5. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 0, "", « »). let on_fulfilled = (); // 6. Let rejectedClosure be a new Abstract Closure with parameters (error) that captures module and performs the following steps when called: // a. Perform AsyncModuleExecutionRejected(module, error). @@ -991,7 +1004,13 @@ pub(crate) fn execute_async_module(agent: &mut Agent, module: Module) { // 7. Let onRejected be CreateBuiltinFunction(rejectedClosure, 0, "", « »). let on_rejected = (); // 8. Perform PerformPromiseThen(capability.[[Promise]], onFulfilled, onRejected). - perform_promise_then(capability.promise, on_fulfilled, on_rejected); + perform_promise_then( + agent, + Promise::try_from(agent[capability].promise).unwrap(), + on_fulfilled, + on_rejected, + None, + ); // 9. Perform ! module.ExecuteModule(capability). module.execute_module(agent, Some(capability)); // 10. Return unused. diff --git a/nova_vm/src/ecmascript/builtins/module/data.rs b/nova_vm/src/ecmascript/builtins/module/data.rs index 8c6e1e37e..e5997cb70 100644 --- a/nova_vm/src/ecmascript/builtins/module/data.rs +++ b/nova_vm/src/ecmascript/builtins/module/data.rs @@ -1,17 +1,8 @@ -use small_string::SmallString; - -use crate::{ - ecmascript::{ - execution::{ModuleEnvironmentIndex, RealmIdentifier}, - scripts_and_modules::module::ModuleIdentifier, - types::{PropertyKey, String}, - }, - heap::indexes::{ObjectIndex, StringIndex}, -}; +use crate::{ecmascript::types::String, heap::indexes::ObjectIndex}; use super::{ abstract_module_records::ModuleRecord, cyclic_module_records::CyclicModuleRecord, - source_text_module_records::SourceTextModuleRecord, Module, + source_text_module_records::SourceTextModuleRecord, }; #[derive(Debug)] @@ -20,5 +11,5 @@ pub struct ModuleHeapData { pub(crate) r#abstract: ModuleRecord, pub(crate) cyclic: CyclicModuleRecord, pub(crate) source_text: SourceTextModuleRecord, - pub(crate) exports: Vec, + pub(crate) exports: Box<[String]>, } diff --git a/nova_vm/src/ecmascript/builtins/module/source_text_module_records.rs b/nova_vm/src/ecmascript/builtins/module/source_text_module_records.rs index 4602d94ea..36bfc90eb 100644 --- a/nova_vm/src/ecmascript/builtins/module/source_text_module_records.rs +++ b/nova_vm/src/ecmascript/builtins/module/source_text_module_records.rs @@ -4,17 +4,16 @@ use oxc_parser::{Parser, ParserReturn}; use oxc_span::{Atom, SourceType}; use small_string::SmallString; -use super::{abstract_module_records::{ModuleRecord, ResolveExportResult}, cyclic_module_records::{CyclicModuleRecord, CyclicModuleRecordStatus}, data::ModuleHeapData, Module}; +use super::{abstract_module_records::{ModuleRecord, ResolveExportResult}, cyclic_module_records::{continue_module_loading, CyclicModuleRecord, CyclicModuleRecordStatus, LoadedModuleRecord}, data::ModuleHeapData, Module}; use crate::{ ecmascript::{ - builtins::{control_abstraction_objects::promise_objects::promise_abstract_operations::promise_capability_records::PromiseCapability, module::{abstract_module_records::{ResolvedBinding, ResolvedBindingName}, semantics::{self, module_requests}}}, - execution::{agent, Agent, DeclarativeEnvironmentIndex, ExecutionContext, JsResult, RealmIdentifier}, - scripts_and_modules::script::HostDefined, + builtins::{control_abstraction_objects::promise_objects::promise_abstract_operations::promise_capability_records::PromiseCapability, module::{abstract_module_records::{ResolvedBinding, ResolvedBindingName}, cyclic_module_records::evaluate, semantics::{self, module_requests}}}, + execution::{Agent, DeclarativeEnvironmentIndex, ECMAScriptCodeEvaluationState, EnvironmentIndex, ExecutionContext, JsResult, RealmIdentifier}, + scripts_and_modules::{script::{HostDefined, ScriptIdentifier}, ScriptOrModule}, types::{ Object, String, BUILTIN_STRING_MEMORY, SMALL_STRING_DISCRIMINANT, STRING_DISCRIMINANT, }, - }, - heap::indexes::StringIndex, + }, engine::{Executable, Vm}, heap::indexes::StringIndex }; /// a String or NAMESPACE-OBJECT @@ -173,6 +172,8 @@ pub(crate) struct SourceTextModuleRecord { star_export_entries: Box<[ExportEntryRecord]>, } +unsafe impl Send for SourceTextModuleRecord {} + pub type ModuleOrErrors = Result>; /// ### [16.2.1.6.1 ParseModule ( sourceText, realm, hostDefined )]() @@ -296,7 +297,7 @@ pub(crate) fn parse_module<'a>( // [[Environment]]: empty, environment: None, // [[Namespace]]: empty, - namespace: None, + namespace: false, // [[HostDefined]]: hostDefined, host_defined: host_defined, }, @@ -340,7 +341,7 @@ pub(crate) fn parse_module<'a>( // [[StarExportEntries]]: starExportEntries, star_export_entries: star_export_entries.into_boxed_slice(), }, - exports: vec![], + exports: Default::default(), }) // }. // Note @@ -432,7 +433,7 @@ struct ResolveSetEntry { /// The ResolveExport concrete method of a Source Text Module Record module /// takes argument exportName (a String) and optional argument resolveSet (a /// List of Records with fields \[\[Module\]\] (a Module Record) and -/// ### \[\[ExportName\]\] (a String)) and returns a ResolvedBinding Record, +/// \[\[ExportName\]\] (a String)) and returns a ResolvedBinding Record, /// null, or ambiguous. /// /// @@ -448,9 +449,7 @@ struct ResolveSetEntry { /// /// If a defining module is found, a ResolvedBinding Record { \[\[Module\]\], /// ### \[\[BindingName\]\] } is returned. This record identifies the resolved -/// /// binding of the originally requested export, unless this is the export of a -/// /// namespace with no local binding. In this case, \[\[BindingName\]\] will be /// set to namespace. If no definition was found or the request is found to be /// circular, null is returned. If the request is found to be ambiguous, @@ -693,24 +692,50 @@ pub(crate) fn execute_module( capability: Option, ) -> JsResult<()> { // 1. Let moduleContext be a new ECMAScript code execution context. - // 2. Set the Function of moduleContext to null. - // 3. Set the Realm of moduleContext to module.[[Realm]]. - // 4. Set the ScriptOrModule of moduleContext to module. // 5. Assert: module has been linked and declarations in its module environment have been instantiated. - // 6. Set the VariableEnvironment of moduleContext to module.[[Environment]]. - // 7. Set the LexicalEnvironment of moduleContext to module.[[Environment]]. + // TODO: What else does this need to assert? + let module_env = agent[module].r#abstract.environment.unwrap(); + let module_context = ExecutionContext { + ecmascript_code: Some(ECMAScriptCodeEvaluationState { + // 6. Set the VariableEnvironment of moduleContext to module.[[Environment]]. + // 7. Set the LexicalEnvironment of moduleContext to module.[[Environment]]. + lexical_environment: EnvironmentIndex::Module(module_env), + variable_environment: EnvironmentIndex::Module(module_env), + private_environment: None, + }), + // 2. Set the Function of moduleContext to null. + function: None, + // 3. Set the Realm of moduleContext to module.[[Realm]]. + realm: agent[module].r#abstract.realm, + // 4. Set the ScriptOrModule of moduleContext to module. + script_or_module: Some(ScriptOrModule::Module(module)), + }; // 8. Suspend the running execution context. + // TODO: What does suspending mean again? + // 9. If module.[[HasTLA]] is false, then - // a. Assert: capability is not present. - // b. Push moduleContext onto the execution context stack; moduleContext is now the running execution context. - // c. Let result be Completion(Evaluation of module.[[ECMAScriptCode]]). - // d. Suspend moduleContext and remove it from the execution context stack. - // e. Resume the context that is now on the top of the execution context stack as the running execution context. - // f. If result is an abrupt completion, then - // i. Return ? result. - // 10. Else, - // a. Assert: capability is a PromiseCapability Record. - // b. Perform AsyncBlockStart(capability, module.[[ECMAScriptCode]], moduleContext). + if !agent[module].cyclic.has_top_level_await { + // a. Assert: capability is not present. + debug_assert!(capability.is_none()); + // b. Push moduleContext onto the execution context stack; moduleContext is now the running execution context. + agent.execution_context_stack.push(module_context); + // c. Let result be Completion(Evaluation of module.[[ECMAScriptCode]]). + let exe = Executable::compile_module(agent, module); + let result = Vm::execute(agent, &exe); + // d. Suspend moduleContext and remove it from the execution context stack. + agent.execution_context_stack.pop(); + // e. Resume the context that is now on the top of the execution context stack as the running execution context. + // f. If result is an abrupt completion, then + // i. Return ? result. + result?; + } else { + // 10. Else, + // a. Assert: capability is a PromiseCapability Record. + let _capability = capability.unwrap(); + // b. Perform AsyncBlockStart(capability, module.[[ECMAScriptCode]], moduleContext). + // async_block_start(agent, capability, agent[module].source_text.ecmascript_code, module_context); + todo!("AsyncBlockStart"); + } // 11. Return unused. Ok(()) } @@ -748,3 +773,120 @@ pub(super) fn get_imported_module( // 3. Return record.[[Module]]. record.unwrap() } + +#[derive(Debug, Clone, Copy)] +pub(crate) enum ModuleReferrer { + Script(ScriptIdentifier), + Module(Module), + Realm(RealmIdentifier), +} + +#[derive(Debug, Clone, Copy)] +pub(crate) enum ModuleImportPayload { + GraphLoadingState(Module), + PromiseCapabilityRecord(PromiseCapability), +} + +/// ### [16.2.1.9 FinishLoadingImportedModule ( referrer, specifier, payload, result )](https://tc39.es/ecma262/#sec-FinishLoadingImportedModule) +/// +/// The abstract operation FinishLoadingImportedModule takes arguments referrer +/// (a Script Record, a Cyclic Module Record, or a Realm Record), specifier (a +/// String), payload (a GraphLoadingState Record or a PromiseCapability +/// Record), and result (either a normal completion containing a Module Record +/// or a throw completion) and returns unused. +pub(crate) fn finish_loading_imported_module( + agent: &mut Agent, + referrer: ModuleReferrer, + specifier: Atom<'static>, + payload: ModuleImportPayload, + result: JsResult, +) { + // 1. If result is a normal completion, then + if let Ok(result) = result { + let ModuleReferrer::Module(referrer) = referrer else { + unreachable!("The spec seems to suggest that referrer can only be a Module?"); + }; + // a. If referrer.[[LoadedModules]] contains a Record whose [[Specifier]] is specifier, then + if let Some(matching_module) = agent[referrer] + .cyclic + .loaded_modules + .iter() + .find(|loaded_module| loaded_module.specifier == Some(specifier)) + { + // i. Assert: That Record's [[Module]] is result.[[Value]]. + assert_eq!(matching_module.module, result); + } else { + // b. Else, + // i. Append the Record { [[Specifier]]: specifier, [[Module]]: result.[[Value]] } to referrer.[[LoadedModules]]. + agent[referrer] + .cyclic + .loaded_modules + .push(LoadedModuleRecord { + specifier, + module: result, + }); + } + } + // 2. If payload is a GraphLoadingState Record, then + match payload { + // a. Perform ContinueModuleLoading(payload, result). + ModuleImportPayload::GraphLoadingState(payload) => { + continue_module_loading(agent, payload, result) + } + // 3. Else, + ModuleImportPayload::PromiseCapabilityRecord(payload) => { + continue_dynamic_import(agent, payload, result) + } + } + // a. Perform ContinueDynamicImport(payload, result). + // 4. Return unused. +} + +/// ### [16.2.1.10 GetModuleNamespace ( module )](https://tc39.es/ecma262/#sec-getmodulenamespace) +/// +/// The abstract operation GetModuleNamespace takes argument module (an +/// instance of a concrete subclass of Module Record) and returns a Module +/// Namespace Object or empty. It retrieves the Module Namespace Object +/// representing module's exports, lazily creating it the first time it was +/// requested, and storing it in module.[[Namespace]] for future retrieval. +/// +/// #### Note +/// +/// GetModuleNamespace never throws. Instead, unresolvable names are simply +/// excluded from the namespace at this point. They will lead to a real +/// linking error later unless they are all ambiguous star exports that are +/// not explicitly requested anywhere. +pub(crate) fn get_module_namespace(agent: &mut Agent, module: Module) -> Module { + // 1. Assert: If module is a Cyclic Module Record, then module.[[Status]] is not new or unlinked. + debug_assert!({ + if true { + !matches!( + agent[module].cyclic.status, + CyclicModuleRecordStatus::New | CyclicModuleRecordStatus::Unlinked + ) + } else { + true + } + }); + // 2. Let namespace be module.[[Namespace]]. + let namespace = agent[module].r#abstract.namespace; + // 3. If namespace is empty, then + if !namespace { + // a. Let exportedNames be module.GetExportedNames(). + let exported_names = get_exported_names(agent, module, None); + // b. Let unambiguousNames be a new empty List. + let mut unamibigious_names = Vec::with_capacity(exported_names.len()); + // c. For each element name of exportedNames, do + for name in exported_names { + // i. Let resolution be module.ResolveExport(name). + let resolution = resolve_export(agent, module, name, None); + // ii. If resolution is a ResolvedBinding Record, append name to unambiguousNames. + if let Some(ResolveExportResult::Resolved(_)) = resolution { + unamibigious_names.push(name); + } + } + // d. Set namespace to ModuleNamespaceCreate(module, unambiguousNames). + } + // 4. Return namespace. + module +} diff --git a/nova_vm/src/ecmascript/builtins/promise/data.rs b/nova_vm/src/ecmascript/builtins/promise/data.rs index e9254018d..d6ab344cc 100644 --- a/nova_vm/src/ecmascript/builtins/promise/data.rs +++ b/nova_vm/src/ecmascript/builtins/promise/data.rs @@ -1,4 +1,4 @@ -use crate::{ecmascript::{builtins::control_abstraction_objects::promise_objects::promise_abstract_operations::promise_capability_records::PromiseCapability, types::Value}, heap::indexes::ObjectIndex}; +use crate::{ecmascript::{builtins::control_abstraction_objects::promise_objects::promise_abstract_operations::promise_reaction_records::PromiseReactionRecord, types::Value}, heap::indexes::ObjectIndex}; #[derive(Debug, Clone, Default)] pub struct PromiseHeapData { @@ -26,16 +26,3 @@ pub(crate) enum PromiseReactions { One(PromiseReactionRecord), Many(Vec), } - -#[derive(Debug, Clone, Copy)] -pub(crate) struct PromiseReactionRecord { - capability: PromiseCapability, - r#type: PromiseReactionType, - handler: (), -} - -#[derive(Debug, Clone, Copy)] -pub(crate) enum PromiseReactionType { - Fulfill, - Reject, -} diff --git a/nova_vm/src/ecmascript/execution.rs b/nova_vm/src/ecmascript/execution.rs index ce1babcb1..feb405abc 100644 --- a/nova_vm/src/ecmascript/execution.rs +++ b/nova_vm/src/ecmascript/execution.rs @@ -10,8 +10,8 @@ pub(crate) use environments::{ get_this_environment, new_declarative_environment, new_function_environment, DeclarativeEnvironment, DeclarativeEnvironmentIndex, EnvironmentIndex, Environments, FunctionEnvironment, FunctionEnvironmentIndex, GlobalEnvironment, GlobalEnvironmentIndex, - ModuleEnvironmentIndex, ObjectEnvironment, ObjectEnvironmentIndex, PrivateEnvironment, - PrivateEnvironmentIndex, ThisBindingStatus, + ModuleBinding, ModuleEnvironment, ModuleEnvironmentIndex, ObjectEnvironment, + ObjectEnvironmentIndex, PrivateEnvironment, PrivateEnvironmentIndex, ThisBindingStatus, }; pub(crate) use execution_context::*; pub use realm::{ diff --git a/nova_vm/src/ecmascript/execution/agent.rs b/nova_vm/src/ecmascript/execution/agent.rs index fd2d6050b..7d97ea4d2 100644 --- a/nova_vm/src/ecmascript/execution/agent.rs +++ b/nova_vm/src/ecmascript/execution/agent.rs @@ -3,16 +3,24 @@ //! - This is inspired by and/or copied from Kiesel engine: //! Copyright (c) 2023-2024 Linus Groh +use oxc_span::Atom; + use super::{ environments::get_identifier_reference, EnvironmentIndex, ExecutionContext, Realm, RealmIdentifier, }; use crate::{ ecmascript::{ - abstract_operations::type_conversion::to_string, - builtins::{error::ErrorHeapData, promise::Promise}, - scripts_and_modules::ScriptOrModule, - types::{Function, Reference, String, Symbol, Value}, + abstract_operations::{ + operations_on_objects::call_function, testing_and_comparison::is_callable, + type_conversion::to_string, + }, + builtins::{ + error::ErrorHeapData, module::cyclic_module_records::GraphLoadingStateRecord, + promise::Promise, ArgumentsList, + }, + scripts_and_modules::{script::HostDefined, ScriptOrModule}, + types::{Function, IntoValue, Reference, String, Symbol, Value}, }, heap::indexes::ErrorIndex, Heap, @@ -45,8 +53,12 @@ impl JsError { } } -// #[derive(Debug)] -// pub struct PreAllocated; +pub struct JobCallbackRecord { + /// \[\[Callback\]\] + callback: Function, + /// \[\[HostDefined\]\] + host_defined: Option, +} #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum PromiseRejectionOperation { @@ -57,6 +69,144 @@ pub enum PromiseRejectionOperation { pub trait HostHooks: std::fmt::Debug { fn host_ensure_can_compile_strings(&self, callee_realm: &mut Realm) -> JsResult<()>; fn host_has_source_text_available(&self, func: Function) -> bool; + + /// ### [9.5.2 HostMakeJobCallback ( callback )](https://tc39.es/ecma262/#sec-hostcalljobcallback) + /// + /// The host-defined abstract operation HostMakeJobCallback takes argument + /// callback (a function object) and returns a JobCallback Record. + /// + /// An implementation of HostMakeJobCallback must conform to the following + /// requirements: + /// + /// * It must return a JobCallback Record whose \[\[Callback\]\] field is + /// callback. + /// + /// The default implementation of HostMakeJobCallback performs the + /// following steps when called: + /// + /// 1. Return the `JobCallback Record { [[Callback]]: callback, + /// [[HostDefined]]: empty }`. + /// + /// ECMAScript hosts that are not web browsers must use the default + /// implementation of HostMakeJobCallback. + /// + /// #### Note + /// + /// This is called at the time that the callback is passed to the function + /// that is responsible for its being eventually scheduled and run. For + /// example, `promise.then(thenAction)` calls MakeJobCallback on + /// **thenAction** at the time of invoking `Promise.prototype.then`, not at + /// the time of scheduling the reaction Job. + fn host_make_job_callback(&self, callback: Function) -> JobCallbackRecord { + JobCallbackRecord { + callback, + host_defined: None, + } + } + /// ### [9.5.3 HostCallJobCallback ( jobCallback, V, argumentsList )](https://tc39.es/ecma262/#sec-hostcalljobcallback) + /// + /// The host-defined abstract operation HostCallJobCallback takes arguments + /// jobCallback (a JobCallback Record), V (an ECMAScript language value), + /// and argumentsList (a List of ECMAScript language values) and returns + /// either a normal completion containing an ECMAScript language value or a + /// throw completion. + /// + /// An implementation of HostCallJobCallback must conform to the following + /// requirements: + /// + /// * It must perform and return the result of + /// Call(jobCallback.\[\[Callback\]\], V, argumentsList). + /// + /// #### Note + /// + /// This requirement means that hosts cannot change the \[\[Call\]\] + /// behaviour of function objects defined in this specification. + /// + /// The default implementation of HostCallJobCallback performs the + /// following steps when called: + /// + /// 1. Assert: IsCallable(jobCallback.\[\[Callback\]\]) is true. + /// 2. Return ? Call(jobCallback.\[\[Callback\]\], V, argumentsList). + /// + /// ECMAScript hosts that are not web browsers must use the default + /// implementation of HostCallJobCallback. + fn host_call_job_callback( + &self, + agent: &mut Agent, + job_callback: JobCallbackRecord, + v: Value, + arguments_list: ArgumentsList, + ) -> JsResult { + debug_assert!(is_callable(job_callback.callback.into_value())); + call_function(agent, job_callback.callback, v, Some(arguments_list)) + } + + /// ### [9.5.4 HostEnqueueGenericJob ( job, realm )](https://tc39.es/ecma262/#sec-hostenqueuegenericjob) + /// + /// The host-defined abstract operation HostEnqueueGenericJob takes + /// arguments job (a Job Abstract Closure) and realm (a Realm Record) + /// and returns unused. It schedules job in the realm realm in the agent + /// signified by realm.[[AgentSignifier]] to be performed at some future + /// time. The Abstract Closures used with this algorithm are intended to be + /// scheduled without additional constraints, such as priority and + /// ordering. + /// + /// An implementation of HostEnqueueGenericJob must conform to the + /// requirements in 9.5. + fn host_enqueue_generic_job(&self, job: (), realm: Realm); + + /// ### [9.5.5 HostEnqueuePromiseJob ( job, realm )](https://tc39.es/ecma262/#sec-hostenqueuepromisejob) + /// + /// The host-defined abstract operation HostEnqueuePromiseJob takes + /// arguments job (a Job Abstract Closure) and realm (a Realm Record or + /// null) and returns unused. It schedules job to be performed at some + /// future time. The Abstract Closures used with this algorithm are + /// intended to be related to the handling of Promises, or otherwise, to be + /// scheduled with equal priority to Promise handling operations. + /// + /// An implementation of HostEnqueuePromiseJob must conform to the + /// requirements in 9.5 as well as the following: + /// + /// * If realm is not null, each time job is invoked the implementation + /// must perform implementation-defined steps such that execution is + /// prepared to evaluate ECMAScript code at the time of job's invocation. + /// * Let scriptOrModule be GetActiveScriptOrModule() at the time + /// HostEnqueuePromiseJob is invoked. If realm is not null, each time job + /// is invoked the implementation must perform implementation-defined steps + /// such that scriptOrModule is the active script or module at the time of + /// job's invocation. + /// * Jobs must run in the same order as the HostEnqueuePromiseJob + /// invocations that scheduled them. + /// + /// #### Note + /// + /// The realm for Jobs returned by NewPromiseResolveThenableJob is usually + /// the result of calling GetFunctionRealm on the then function object. The + /// realm for Jobs returned by NewPromiseReactionJob is usually the result + /// of calling GetFunctionRealm on the handler if the handler is not + /// undefined. If the handler is undefined, realm is null. For both kinds + /// of Jobs, when GetFunctionRealm completes abnormally (i.e. called on a + /// revoked Proxy), realm is the current Realm Record at the time of the + /// GetFunctionRealm call. When the realm is null, no user ECMAScript code + /// will be evaluated and no new ECMAScript objects (e.g. Error objects) + /// will be created. The WHATWG HTML specification + /// (https://html.spec.whatwg.org/), for example, uses realm to check for + /// the ability to run script and for the entry concept. + fn host_enqueue_promise_job(&self, job: (), realm: Realm); + + /// ### [9.5.6 HostEnqueueTimeoutJob ( timeoutJob, realm, milliseconds )](https://tc39.es/ecma262/#sec-hostenqueuetimeoutjob) + /// + /// The host-defined abstract operation HostEnqueueTimeoutJob takes + /// arguments timeoutJob (a Job Abstract Closure), realm (a Realm Record), + /// and milliseconds (a non-negative finite Number) and returns unused. It + /// schedules timeoutJob in the realm realm in the agent signified by + /// `realm.[[AgentSignifier]]` to be performed after at least milliseconds + /// milliseconds. + /// + /// An implementation of HostEnqueueTimeoutJob must conform to the + /// requirements in 9.5. + fn host_enqueue_timeout_job(&self, job: (), realm: Realm); + /// ### [16.2.1.8 HostLoadImportedModule ( referrer, specifier, hostDefined, payload )](https://tc39.es/ecma262/#sec-HostLoadImportedModule) /// /// The host-defined abstract operation HostLoadImportedModule takes @@ -104,9 +254,9 @@ pub trait HostHooks: std::fmt::Debug { fn host_load_imported_module( &self, referrer: (), - specifier: &str, + specifier: Atom<'static>, host_defined: Option<&dyn Any>, - payload: (), + payload: &mut GraphLoadingStateRecord, ); /// ### [27.2.1.9 HostPromiseRejectionTracker ( promise, operation )](https://tc39.es/ecma262/#sec-host-promise-rejection-tracker) /// diff --git a/nova_vm/src/ecmascript/execution/environments.rs b/nova_vm/src/ecmascript/execution/environments.rs index 40ca997ef..e6b5c221b 100644 --- a/nova_vm/src/ecmascript/execution/environments.rs +++ b/nova_vm/src/ecmascript/execution/environments.rs @@ -34,7 +34,7 @@ pub(crate) use function_environment::{ new_function_environment, FunctionEnvironment, ThisBindingStatus, }; pub(crate) use global_environment::GlobalEnvironment; -pub(crate) use module_environment::{ModuleEnvironment, ModuleEnvironmentIndex}; +pub(crate) use module_environment::{ModuleBinding, ModuleEnvironment}; pub(crate) use object_environment::ObjectEnvironment; pub(crate) use private_environment::PrivateEnvironment; @@ -97,6 +97,7 @@ macro_rules! create_environment_index { create_environment_index!(DeclarativeEnvironment, DeclarativeEnvironmentIndex); create_environment_index!(FunctionEnvironment, FunctionEnvironmentIndex); create_environment_index!(GlobalEnvironment, GlobalEnvironmentIndex); +create_environment_index!(ModuleEnvironment, ModuleEnvironmentIndex); create_environment_index!(ObjectEnvironment, ObjectEnvironmentIndex); create_environment_index!(PrivateEnvironment, PrivateEnvironmentIndex); @@ -131,6 +132,7 @@ impl EnvironmentIndex { .outer_env } EnvironmentIndex::Global(_) => None, + EnvironmentIndex::Module(_) => None, EnvironmentIndex::Object(index) => index.heap_data(agent).outer_env, } } @@ -144,6 +146,7 @@ impl EnvironmentIndex { EnvironmentIndex::Declarative(idx) => Ok(idx.has_binding(agent, name)), EnvironmentIndex::Function(idx) => Ok(idx.has_binding(agent, name)), EnvironmentIndex::Global(idx) => idx.has_binding(agent, name), + EnvironmentIndex::Module(idx) => Ok(idx.has_binding(agent, name)), EnvironmentIndex::Object(idx) => idx.has_binding(agent, name), } } @@ -169,6 +172,10 @@ impl EnvironmentIndex { Ok(()) } EnvironmentIndex::Global(idx) => idx.create_mutable_binding(agent, name, is_deletable), + EnvironmentIndex::Module(idx) => { + idx.create_mutable_binding(agent, name, is_deletable); + Ok(()) + } EnvironmentIndex::Object(idx) => idx.create_mutable_binding(agent, name, is_deletable), } } @@ -196,6 +203,10 @@ impl EnvironmentIndex { Ok(()) } EnvironmentIndex::Global(idx) => idx.create_immutable_binding(agent, name, is_strict), + EnvironmentIndex::Module(idx) => { + idx.create_immutable_binding(agent, name, is_strict); + Ok(()) + } EnvironmentIndex::Object(idx) => { idx.create_immutable_binding(agent, name, is_strict); Ok(()) @@ -225,6 +236,10 @@ impl EnvironmentIndex { Ok(()) } EnvironmentIndex::Global(idx) => idx.initialize_binding(agent, name, value), + EnvironmentIndex::Module(idx) => { + idx.initialize_binding(agent, name, value); + Ok(()) + } EnvironmentIndex::Object(idx) => idx.initialize_binding(agent, name, value), } } @@ -251,6 +266,10 @@ impl EnvironmentIndex { idx.set_mutable_binding(agent, name, value, is_strict) } EnvironmentIndex::Global(idx) => idx.set_mutable_binding(agent, name, value, is_strict), + EnvironmentIndex::Module(idx) => { + idx.initialize_binding(agent, name, value); + Ok(()) + } EnvironmentIndex::Object(idx) => idx.set_mutable_binding(agent, name, value, is_strict), } } @@ -274,6 +293,7 @@ impl EnvironmentIndex { EnvironmentIndex::Declarative(idx) => idx.get_binding_value(agent, name, is_strict), EnvironmentIndex::Function(idx) => idx.get_binding_value(agent, name, is_strict), EnvironmentIndex::Global(idx) => idx.get_binding_value(agent, name, is_strict), + EnvironmentIndex::Module(idx) => idx.get_binding_value(agent, name, is_strict), EnvironmentIndex::Object(idx) => idx.get_binding_value(agent, name, is_strict), } } @@ -289,6 +309,7 @@ impl EnvironmentIndex { EnvironmentIndex::Declarative(idx) => Ok(idx.delete_binding(agent, name)), EnvironmentIndex::Function(idx) => Ok(idx.delete_binding(agent, name)), EnvironmentIndex::Global(idx) => idx.delete_binding(agent, name), + EnvironmentIndex::Module(idx) => Ok(idx.delete_binding(agent, name)), EnvironmentIndex::Object(idx) => idx.delete_binding(agent, name), } } @@ -302,6 +323,7 @@ impl EnvironmentIndex { EnvironmentIndex::Declarative(idx) => idx.has_this_binding(), EnvironmentIndex::Function(idx) => idx.has_this_binding(agent), EnvironmentIndex::Global(idx) => idx.has_this_binding(), + EnvironmentIndex::Module(idx) => idx.has_this_binding(), EnvironmentIndex::Object(idx) => idx.has_this_binding(), } } @@ -315,6 +337,7 @@ impl EnvironmentIndex { EnvironmentIndex::Declarative(idx) => idx.has_super_binding(), EnvironmentIndex::Function(idx) => idx.has_super_binding(agent), EnvironmentIndex::Global(idx) => idx.has_super_binding(), + EnvironmentIndex::Module(idx) => idx.has_super_binding(), EnvironmentIndex::Object(idx) => idx.has_super_binding(), } } @@ -328,6 +351,7 @@ impl EnvironmentIndex { EnvironmentIndex::Declarative(idx) => idx.with_base_object(), EnvironmentIndex::Function(idx) => idx.with_base_object(), EnvironmentIndex::Global(idx) => idx.with_base_object(), + EnvironmentIndex::Module(idx) => idx.with_base_object(), EnvironmentIndex::Object(idx) => idx.with_base_object(agent), } } @@ -338,6 +362,7 @@ pub struct Environments { pub(crate) declarative: Vec>, pub(crate) function: Vec>, pub(crate) global: Vec>, + pub(crate) module: Vec>, pub(crate) object: Vec>, } @@ -347,6 +372,7 @@ impl Default for Environments { declarative: Vec::with_capacity(256), function: Vec::with_capacity(1024), global: Vec::with_capacity(1), + module: Vec::with_capacity(64), object: Vec::with_capacity(1024), } } @@ -507,6 +533,28 @@ impl Environments { .expect("GlobalEnvironmentIndex pointed to a None") } + pub(crate) fn get_module_environment( + &self, + index: ModuleEnvironmentIndex, + ) -> &ModuleEnvironment { + self.module + .get(index.into_index()) + .expect("ModuleEnvironmentIndex did not match to any vector index") + .as_ref() + .expect("ModuleEnvironmentIndex pointed to a None") + } + + pub(crate) fn get_module_environment_mut( + &mut self, + index: ModuleEnvironmentIndex, + ) -> &mut ModuleEnvironment { + self.module + .get_mut(index.into_index()) + .expect("ModuleEnvironmentIndex did not match to any vector index") + .as_mut() + .expect("ModuleEnvironmentIndex pointed to a None") + } + pub(crate) fn get_object_environment( &self, index: ObjectEnvironmentIndex, diff --git a/nova_vm/src/ecmascript/execution/environments/declarative_environment.rs b/nova_vm/src/ecmascript/execution/environments/declarative_environment.rs index 9511be73f..403adbc9d 100644 --- a/nova_vm/src/ecmascript/execution/environments/declarative_environment.rs +++ b/nova_vm/src/ecmascript/execution/environments/declarative_environment.rs @@ -98,6 +98,7 @@ impl DeclarativeEnvironment { pub(super) fn initialize_binding(&mut self, name: String, value: Value) { // 1. Assert: envRec must have an uninitialized binding for N. let binding = self.bindings.get_mut(&name).unwrap(); + debug_assert!(binding.value.is_none()); // 2. Set the bound value for N in envRec to V. // 3. Record that the binding for N in envRec has been initialized. diff --git a/nova_vm/src/ecmascript/execution/environments/module_environment.rs b/nova_vm/src/ecmascript/execution/environments/module_environment.rs index e6a587a70..795f4584f 100644 --- a/nova_vm/src/ecmascript/execution/environments/module_environment.rs +++ b/nova_vm/src/ecmascript/execution/environments/module_environment.rs @@ -1,10 +1,12 @@ +use std::collections::HashMap; + use crate::ecmascript::{ builtins::module::Module, execution::{agent::ExceptionType, Agent, JsResult}, - types::{String, Value}, + types::{Object, String, Value}, }; -use super::{DeclarativeEnvironment, DeclarativeEnvironmentIndex}; +use super::{declarative_environment::Binding, ModuleEnvironmentIndex}; /// ### [9.1.1.5 Module Environment Records](https://tc39.es/ecma262/#sec-module-environment-records) /// A Module Environment Record is a Declarative Environment Record that is @@ -20,13 +22,205 @@ use super::{DeclarativeEnvironment, DeclarativeEnvironmentIndex}; /// /// NOTE: There is no data-wise difference between a DeclarativeEnvironment and /// a ModuleEnvironment, so we treat them exactly the same way. +#[derive(Default, Debug, Clone)] +pub(crate) struct ModuleEnvironment { + /// The environment's bindings. + pub(crate) bindings: HashMap, +} + #[derive(Debug, Clone)] -#[repr(transparent)] -pub(crate) struct ModuleEnvironment(DeclarativeEnvironment); +pub(crate) enum ModuleBinding { + Lexical(Binding), + Indirect { + /// Module that this binding references + module: Module, + /// Name that this binding references + name: String, + }, +} + +impl ModuleBinding { + fn is_direct_binding(&self) -> bool { + matches!(self, ModuleBinding::Lexical(_)) + } +} + +impl ModuleEnvironment { + /// ### [9.1.1.1.1 HasBinding ( N )](https://tc39.es/ecma262/#sec-declarative-environment-records-hasbinding-n) + pub(super) fn has_binding(&self, name: String) -> bool { + // 1. If envRec has a binding for N, return true. + // 2. Return false. + self.bindings.contains_key(&name) + } + /// ### [9.1.1.1.2 CreateMutableBinding ( N, D )](https://tc39.es/ecma262/#sec-declarative-environment-records-createmutablebinding-n-d) + pub(super) fn create_mutable_binding(&mut self, name: String, is_deletable: bool) { + // 1. Assert: envRec does not already have a binding for N. + debug_assert!(!self.has_binding(name)); + + // 2. Create a mutable binding in envRec for N and record that it is + // uninitialized. If D is true, record that the newly created binding + // may be deleted by a subsequent DeleteBinding call. + self.bindings.insert( + name, + ModuleBinding::Lexical(Binding { + value: None, + // TODO: Figure out how/if we should propagate this. + strict: true, + mutable: true, + deletable: is_deletable, + }), + ); + + // 3. Return UNUSED. + } + /// ### [9.1.1.1.3 CreateImmutableBinding ( N, S )](https://tc39.es/ecma262/#sec-declarative-environment-records-createimmutablebinding-n-s) + pub(super) fn create_immutable_binding(&mut self, name: String, is_strict: bool) { + // 1. Assert: envRec does not already have a binding for N. + debug_assert!(!self.has_binding(name)); + + // 2. Create an immutable binding in envRec for N and record that it is + // uninitialized. If S is true, record that the newly created binding is + // a strict binding. + self.bindings.insert( + name, + ModuleBinding::Lexical(Binding { + value: None, + strict: is_strict, + mutable: false, + deletable: false, + }), + ); + + // 3. Return UNUSED. + } + /// ### [9.1.1.1.4 InitializeBinding ( N, V )](https://tc39.es/ecma262/#sec-declarative-environment-records-initializebinding-n-v) + pub(super) fn initialize_binding(&mut self, name: String, value: Value) { + // 1. Assert: envRec must have an uninitialized binding for N. + let binding = self.bindings.get_mut(&name).unwrap(); + + let binding = match binding { + ModuleBinding::Lexical(binding) => binding, + ModuleBinding::Indirect { .. } => { + unreachable!("Should never attempt to initialize indirect bindings") + } + }; + debug_assert!(binding.value.is_none()); + + // 2. Set the bound value for N in envRec to V. + // 3. Record that the binding for N in envRec has been initialized. + // Note: Initialization status of N is determined by the Some/None. + binding.value = Some(value); + + // 4. Return UNUSED. + } +} -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub(crate) struct ModuleEnvironmentIndex(DeclarativeEnvironmentIndex); impl ModuleEnvironmentIndex { + pub(super) fn heap_data(self, agent: &Agent) -> &ModuleEnvironment { + agent.heap.environments.get_module_environment(self) + } + + pub(super) fn heap_data_mut(self, agent: &mut Agent) -> &mut ModuleEnvironment { + agent.heap.environments.get_module_environment_mut(self) + } + + pub fn has_binding(self, agent: &Agent, name: String) -> bool { + let env_rec = self.heap_data(agent); + // Delegate to heap data record method. + env_rec.has_binding(name) + } + + fn has_direct_binding(self, agent: &Agent, name: String) -> bool { + let env_rec = self.heap_data(agent); + env_rec + .bindings + .get(&name) + .map_or(false, |binding| binding.is_direct_binding()) + } + + pub fn create_mutable_binding(self, agent: &mut Agent, name: String, is_deletable: bool) { + let env_rec = self.heap_data_mut(agent); + // Delegate to heap data record method. + env_rec.create_mutable_binding(name, is_deletable); + } + + pub(crate) fn create_immutable_binding(self, agent: &mut Agent, name: String, is_strict: bool) { + let env_rec = self.heap_data_mut(agent); + // Delegate to heap data record method. + env_rec.create_immutable_binding(name, is_strict); + } + + pub(crate) fn initialize_binding(self, agent: &mut Agent, name: String, value: Value) { + let env_rec = self.heap_data_mut(agent); + // Delegate to heap data record method. + env_rec.initialize_binding(name, value) + } + + pub(crate) fn set_mutable_binding( + self, + agent: &mut Agent, + name: String, + value: Value, + mut is_strict: bool, + ) -> JsResult<()> { + let env_rec = self.heap_data_mut(agent); + // 1. If envRec does not have a binding for N, then + let Some(binding) = env_rec.bindings.get_mut(&name) else { + // a. If S is true, throw a ReferenceError exception. + if is_strict { + return Err(agent + .throw_exception(ExceptionType::ReferenceError, "Identifier is not defined.")); + } + + // b. Perform ! envRec.CreateMutableBinding(N, true). + env_rec.create_mutable_binding(name, true); + + // c. Perform ! envRec.InitializeBinding(N, V). + env_rec.initialize_binding(name, value); + + // d. Return UNUSED. + return Ok(()); + }; + + let ModuleBinding::Lexical(binding) = binding else { + unreachable!("Cannot SetMutableBinding for indirect binding"); + }; + + // 2. If the binding for N in envRec is a strict binding, set S to true. + if binding.strict { + is_strict = true; + } + + // 3. If the binding for N in envRec has not yet been initialized, then + if binding.value.is_none() { + // a. Throw a ReferenceError exception. + return Err( + agent.throw_exception(ExceptionType::ReferenceError, "Identifier is not defined.") + ); + } + + // 4. Else if the binding for N in envRec is a mutable binding, then + if binding.mutable { + // a. Change its bound value to V. + binding.value = Some(value); + } + // 5. Else, + else { + // a. Assert: This is an attempt to change the value of an immutable binding. + debug_assert!(!binding.mutable); + + // b. If S is true, throw a TypeError exception. + if is_strict { + return Err( + agent.throw_exception(ExceptionType::TypeError, "Cannot assign to constant.") + ); + } + } + + // 6. Return UNUSED. + Ok(()) + } + /// ### [9.1.1.5.1 GetBindingValue ( N, S )](https://tc39.es/ecma262/#sec-module-environment-records-getbindingvalue-n-s) /// /// The GetBindingValue concrete method of a Module Environment Record @@ -45,32 +239,58 @@ impl ModuleEnvironmentIndex { // 1. Assert: S is true. debug_assert!(is_strict); // 2. Assert: envRec has a binding for N. - let binding = self.0.heap_data(agent).bindings.get(&name); + let binding = self.heap_data(agent).bindings.get(&name); let binding = binding.unwrap(); - // 3. If the binding for N is an indirect binding, then - if false { - // a. Let M and N2 be the indirection values provided when this binding for N was created. - // b. Let targetEnv be M.[[Environment]]. - // c. If targetEnv is empty, throw a ReferenceError exception. - // d. Return ? targetEnv.GetBindingValue(N2, true). - todo!(); + match binding { + // 3. If the binding for N is an indirect binding, then + ModuleBinding::Indirect { module, name } => { + // a. Let M and N2 be the indirection values provided when this binding for N was created. + // b. Let targetEnv be M.[[Environment]]. + let target_env = agent[*module].r#abstract.environment; + match target_env { + None => { + // c. If targetEnv is empty, throw a ReferenceError exception. + Err(agent + .throw_exception(ExceptionType::ReferenceError, "Cyclical reference")) + } + Some(target_env) => { + // d. Return ? targetEnv.GetBindingValue(N2, true). + target_env.get_binding_value(agent, *name, true) + } + } + } + ModuleBinding::Lexical(binding) => { + // 4. If the binding for N in envRec is an uninitialized binding, throw a ReferenceError exception. + if binding.value.is_none() { + return Err(agent.throw_exception( + ExceptionType::ReferenceError, + "Accessed uninitialized binding", + )); + } + // 5. Return the value currently bound to N in envRec. + Ok(binding.value.unwrap()) + } } - // 4. If the binding for N in envRec is an uninitialized binding, throw a ReferenceError exception. - if binding.value.is_none() { - return Err(agent.throw_exception( - ExceptionType::ReferenceError, - "Accessed uninitialized binding", - )); - } - // 5. Return the value currently bound to N in envRec. - Ok(binding.value.unwrap()) + } + + pub(crate) fn delete_binding(self, agent: &mut Agent, _: String) -> bool { + unreachable!("DeleteBinding should never get called on a Module Environment"); } pub(crate) fn has_this_binding(self) -> bool { true } - pub(crate) fn get_this_binding(self) -> Option { + #[inline(always)] + pub(crate) fn get_this_binding(self) -> Value { + Value::Undefined + } + + pub(crate) fn has_super_binding(self) -> bool { + false + } + + pub(crate) fn with_base_object(self) -> Option { None } @@ -91,13 +311,24 @@ impl ModuleEnvironmentIndex { name2: String, ) { // 1. Assert: envRec does not already have a binding for N. - debug_assert!(!self.0.has_binding(agent, name)); + debug_assert!(!self.has_binding(agent, name)); // 2. Assert: When M.[[Environment]] is instantiated, it will have a // direct binding for N2. + debug_assert!({ + let env = agent[module].r#abstract.environment.unwrap(); + env.has_direct_binding(agent, name2) + }); // 3. Create an immutable indirect binding in envRec for N that // references M and N2 as its target binding and record that the // binding is initialized. + let env_rec = self.heap_data_mut(agent); + env_rec.bindings.insert( + name, + ModuleBinding::Indirect { + module, + name: name2, + }, + ); // 4. Return unused. - todo!(); } } diff --git a/nova_vm/src/ecmascript/execution/realm.rs b/nova_vm/src/ecmascript/execution/realm.rs index 9f1e6b81f..3160f5e6d 100644 --- a/nova_vm/src/ecmascript/execution/realm.rs +++ b/nova_vm/src/ecmascript/execution/realm.rs @@ -6,6 +6,7 @@ use super::{ use crate::{ ecmascript::{ abstract_operations::operations_on_objects::define_property_or_throw, + scripts_and_modules::script::HostDefined, types::{ IntoValue, Number, Object, PropertyDescriptor, PropertyKey, Value, BUILTIN_STRING_MEMORY, @@ -16,7 +17,6 @@ use crate::{ pub(crate) use intrinsics::Intrinsics; pub(crate) use intrinsics::ProtoIntrinsics; use std::{ - any::Any, marker::PhantomData, ops::{Index, IndexMut}, }; @@ -139,7 +139,7 @@ pub struct Realm { /// /// Field reserved for use by hosts that need to associate additional /// information with a Realm Record. - pub(crate) host_defined: Option<&'static dyn Any>, + pub(crate) host_defined: Option, } unsafe impl Send for Realm {} diff --git a/nova_vm/src/ecmascript/scripts_and_modules.rs b/nova_vm/src/ecmascript/scripts_and_modules.rs index 1d3fb0cbf..ad93dc995 100644 --- a/nova_vm/src/ecmascript/scripts_and_modules.rs +++ b/nova_vm/src/ecmascript/scripts_and_modules.rs @@ -1,4 +1,6 @@ -use self::{module::ModuleIdentifier, script::ScriptIdentifier}; +use self::script::ScriptIdentifier; + +use super::builtins::module::Module; pub mod module; pub mod script; @@ -6,5 +8,5 @@ pub mod script; #[derive(Debug, Clone, Copy)] pub(crate) enum ScriptOrModule { Script(ScriptIdentifier), - Module(ModuleIdentifier), + Module(Module), } diff --git a/nova_vm/src/ecmascript/scripts_and_modules/script.rs b/nova_vm/src/ecmascript/scripts_and_modules/script.rs index 1d08bc9f8..25ab26c15 100644 --- a/nova_vm/src/ecmascript/scripts_and_modules/script.rs +++ b/nova_vm/src/ecmascript/scripts_and_modules/script.rs @@ -32,7 +32,7 @@ use std::{ ops::{Index, IndexMut}, }; -pub type HostDefined = &'static mut dyn Any; +pub type HostDefined = Box; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub(crate) struct ScriptIdentifier(u32, PhantomData