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 63e5d81e5..b99553506 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 @@ -2,6 +2,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +pub(crate) mod promise_all_record; pub mod promise_capability_records; pub(crate) mod promise_jobs; pub(crate) mod promise_reaction_records; diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_all_record.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_all_record.rs new file mode 100644 index 000000000..151d90cd2 --- /dev/null +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_all_record.rs @@ -0,0 +1,160 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +use core::ops::{Index, IndexMut}; + +use crate::{ + ecmascript::{ + builtins::{ + Array, promise::Promise, + promise_objects::promise_abstract_operations::promise_capability_records::PromiseCapability, + }, + execution::Agent, + types::{IntoValue, Value}, + }, + engine::context::{Bindable, GcScope, NoGcScope}, + heap::{ + CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, WorkQueues, indexes::BaseIndex, + }, +}; + +#[derive(Debug, Clone, Copy)] +pub struct PromiseAllRecord<'a> { + pub remaining_unresolved_promise_count: u32, + pub result_array: Array<'a>, + pub promise: Promise<'a>, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[repr(transparent)] +pub struct PromiseAll<'a>(pub(crate) BaseIndex<'a, PromiseAllRecord<'a>>); + +impl<'a> PromiseAllRecord<'a> { + pub(crate) fn on_promise_fufilled( + &mut self, + agent: &mut Agent, + index: u32, + value: Value<'a>, + mut gc: GcScope<'a, '_>, + ) { + value.bind(gc.nogc()); + let elements = self.result_array.as_mut_slice(agent); + elements[index as usize] = Some(value.unbind()); + + self.remaining_unresolved_promise_count -= 1; + if self.remaining_unresolved_promise_count == 0 { + eprintln!("Promise fulfilled: {:#?}", elements); + let capability = PromiseCapability::from_promise(self.promise.unbind(), true); + capability.resolve(agent, self.result_array.into_value().unbind(), gc); + } + } +} + +impl PromiseAll<'_> { + pub(crate) const fn get_index(self) -> usize { + self.0.into_index() + } +} + +impl Index> for Agent { + type Output = PromiseAllRecord<'static>; + + fn index(&self, index: PromiseAll) -> &Self::Output { + &self.heap.promise_all_records[index] + } +} + +impl IndexMut> for Agent { + fn index_mut(&mut self, index: PromiseAll) -> &mut Self::Output { + &mut self.heap.promise_all_records[index] + } +} + +impl Index> for Vec>> { + type Output = PromiseAllRecord<'static>; + + fn index(&self, index: PromiseAll) -> &Self::Output { + self.get(index.get_index()) + .expect("PromiseAllRecord out of bounds") + .as_ref() + .expect("PromiseAllRecord slot empty") + } +} + +impl IndexMut> for Vec>> { + fn index_mut(&mut self, index: PromiseAll) -> &mut Self::Output { + self.get_mut(index.get_index()) + .expect("PromiseAllRecord out of bounds") + .as_mut() + .expect("PromiseAllRecord slot empty") + } +} + +impl HeapMarkAndSweep for PromiseAllRecord<'static> { + fn mark_values(&self, queues: &mut WorkQueues) { + let Self { + remaining_unresolved_promise_count: _, + result_array, + promise, + } = self; + result_array.mark_values(queues); + promise.mark_values(queues); + } + + fn sweep_values(&mut self, compactions: &CompactionLists) { + let Self { + remaining_unresolved_promise_count: _, + result_array, + promise, + } = self; + result_array.sweep_values(compactions); + promise.sweep_values(compactions); + } +} + +unsafe impl Bindable for PromiseAllRecord<'_> { + type Of<'a> = PromiseAllRecord<'a>; + + #[inline(always)] + fn unbind(self) -> Self::Of<'static> { + unsafe { core::mem::transmute::>(self) } + } + + #[inline(always)] + fn bind<'a>(self, _gc: NoGcScope<'a, '_>) -> Self::Of<'a> { + unsafe { core::mem::transmute::>(self) } + } +} + +impl HeapMarkAndSweep for PromiseAll<'static> { + fn mark_values(&self, queues: &mut WorkQueues) { + queues.promise_all_records.push(*self); + } + + fn sweep_values(&mut self, compactions: &CompactionLists) { + compactions.promise_all_records.shift_index(&mut self.0); + } +} + +unsafe impl Bindable for PromiseAll<'_> { + type Of<'a> = PromiseAll<'a>; + + #[inline(always)] + fn unbind(self) -> Self::Of<'static> { + unsafe { core::mem::transmute::>(self) } + } + + #[inline(always)] + fn bind<'a>(self, _gc: NoGcScope<'a, '_>) -> Self::Of<'a> { + unsafe { core::mem::transmute::>(self) } + } +} + +impl<'a> CreateHeapData, PromiseAll<'a>> for Heap { + fn create(&mut self, data: PromiseAllRecord<'a>) -> PromiseAll<'a> { + self.promise_all_records.push(Some(data.unbind())); + self.alloc_counter += core::mem::size_of::>>(); + PromiseAll(BaseIndex::last(&self.promise_all_records)) + } +} diff --git a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_jobs.rs b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_jobs.rs index 409e95553..1fa48d203 100644 --- a/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_jobs.rs +++ b/nova_vm/src/ecmascript/builtins/control_abstraction_objects/promise_objects/promise_abstract_operations/promise_jobs.rs @@ -145,7 +145,7 @@ impl PromiseReactionJob { let Self { reaction, argument } = self; let reaction = reaction.take(agent).bind(gc.nogc()); let argument = argument.take(agent).bind(gc.nogc()); - // The following are substeps of point 1 in NewPromiseReactionJob. + let (handler_result, promise_capability) = match agent[reaction].handler { PromiseReactionHandler::Empty => { let capability = agent[reaction].capability.clone().unwrap().bind(gc.nogc()); @@ -288,6 +288,42 @@ impl PromiseReactionJob { } } } + PromiseReactionHandler::PromiseAll { promise_all, index } => { + let capability = agent[reaction].capability.clone().unwrap().bind(gc.nogc()); + match agent[reaction].reaction_type { + PromiseReactionType::Fulfill => { + // Take out the record to drop the vec borrow before we use `agent`/`gc` + let rec = { + let slot = agent + .heap + .promise_all_records + .get_mut(promise_all.get_index()) + .expect("PromiseAllRecord out of bounds"); + slot.take().expect("PromiseAllRecord slot empty") + }; + + // Bind to current scope and mutate + let mut rec_bound = rec.unbind().bind(gc.nogc()); + rec_bound.on_promise_fufilled( + agent, + index, + argument.clone().unbind(), + gc.reborrow(), + ); + + // Write back with 'static lifetime + agent + .heap + .promise_all_records + .get_mut(promise_all.get_index()) + .unwrap() + .replace(rec_bound.unbind()); + + (Ok(argument), capability) + } + PromiseReactionType::Reject => (Err(JsError::new(argument)), capability), + } + } }; // f. If promiseCapability is undefined, then @@ -348,7 +384,8 @@ pub(crate) fn new_promise_reaction_job( | PromiseReactionHandler::AsyncFromSyncIteratorClose(_) | PromiseReactionHandler::AsyncModule(_) | PromiseReactionHandler::DynamicImport { .. } - | PromiseReactionHandler::DynamicImportEvaluate { .. } => None, + | PromiseReactionHandler::DynamicImportEvaluate { .. } + | PromiseReactionHandler::PromiseAll { .. } => None, }; // 4. Return the Record { [[Job]]: job, [[Realm]]: handlerRealm }. 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 74d2a19e6..4e9aaa937 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 @@ -10,6 +10,7 @@ use crate::{ async_generator_objects::AsyncGenerator, control_abstraction_objects::async_function_objects::await_reaction::AwaitReaction, promise::Promise, + promise_objects::promise_abstract_operations::promise_all_record::PromiseAll, }, execution::Agent, scripts_and_modules::module::module_semantics::{ @@ -68,6 +69,10 @@ pub(crate) enum PromiseReactionHandler<'a> { promise: Promise<'a>, module: AbstractModule<'a>, }, + PromiseAll { + index: u32, + promise_all: PromiseAll<'a>, + }, Empty, } @@ -85,6 +90,10 @@ impl HeapMarkAndSweep for PromiseReactionHandler<'static> { promise.mark_values(queues); module.mark_values(queues); } + Self::PromiseAll { + index: _, + promise_all, + } => promise_all.mark_values(queues), Self::Empty => {} } } @@ -104,6 +113,12 @@ impl HeapMarkAndSweep for PromiseReactionHandler<'static> { promise.sweep_values(compactions); module.sweep_values(compactions); } + Self::PromiseAll { + index: _, + promise_all, + } => { + promise_all.sweep_values(compactions); + } Self::Empty => {} } } 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 985ff4afd..244a782dd 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 @@ -2,6 +2,10 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. +use crate::ecmascript::builtins::promise_objects::promise_abstract_operations::promise_all_record::PromiseAllRecord; +use crate::ecmascript::builtins::promise_objects::promise_abstract_operations::promise_reaction_records::PromiseReactionHandler; +use crate::ecmascript::builtins::promise_objects::promise_prototype::inner_promise_then; +use crate::ecmascript::builtins::Array; use crate::{ ecmascript::{ abstract_operations::{ @@ -212,11 +216,149 @@ impl PromiseConstructor { fn all<'gc>( agent: &mut Agent, - _this_value: Value, - _arguments: ArgumentsList, - gc: GcScope<'gc, '_>, + this_value: Value, + arguments: ArgumentsList, + mut gc: GcScope<'gc, '_>, ) -> JsResult<'gc, Value<'gc>> { - Err(agent.todo("Promise.all", gc.into_nogc())) + // 1. Let C be the this value. + if this_value + != agent + .current_realm_record() + .intrinsics() + .promise() + .into_value() + { + return Err(throw_promise_subclassing_not_supported( + agent, + gc.into_nogc(), + )); + } + + let first_arg = arguments.get(0).bind(gc.nogc()); + + let Ok(promise_array) = Array::try_from(first_arg.unbind()) else { + return Err(agent.throw_exception_with_static_message( + ExceptionType::TypeError, + "Expected an array of promises", + gc.into_nogc(), + )); + }; + + let array_slice = promise_array.as_slice(agent); + if array_slice.is_empty() { + return Err(agent.throw_exception_with_static_message( + ExceptionType::TypeError, + "Array is empty", + gc.into_nogc(), + )); + } + + let Some(first_element) = array_slice[0] else { + return Err(agent.throw_exception_with_static_message( + ExceptionType::TypeError, + "First element is None", + gc.into_nogc(), + )); + }; + + let Value::Promise(promise_to_await) = first_element.unbind() else { + return Err(agent.throw_exception_with_static_message( + ExceptionType::TypeError, + "First element is not a Promise", + gc.into_nogc(), + )); + }; + + let Some(second_element) = array_slice[1] else { + return Err(agent.throw_exception_with_static_message( + ExceptionType::TypeError, + "Second element is None", + gc.into_nogc(), + )); + }; + let Value::Promise(second_promise) = second_element.unbind() else { + return Err(agent.throw_exception_with_static_message( + ExceptionType::TypeError, + "Second element is not a Promise", + gc.into_nogc(), + )); + }; + + let result_capability = PromiseCapability::new(agent, gc.nogc()); + let second_capability = PromiseCapability::new(agent, gc.nogc()); + + let result_promise = PromiseCapability::new(agent, gc.nogc()) + .promise() + .unbind() + .bind(gc.nogc()); + + // let result_callback_closure: for<'a, 'b, 'c, 'd, 'e, '_gc> fn( + // &'a mut Agent, + // Value<'b>, + // ArgumentsList<'c, 'd>, + // GcScope<'_gc, 'e>, + // ) -> Result< + // Value<'_gc>, + // JsError<'_gc>, + // > = |_agent, _this_value, arguments, _gc| { + // let result_value = arguments.get(0); + // eprintln!("Promise fulfilled with result: {:?}", result_value); + // Ok(result_value.unbind()) + // }; + + // let result_callback = create_builtin_function( + // agent, + // Behaviour::Regular(result_callback_closure), + // BuiltinFunctionArgs::new(0, "Promise.all callback"), + // gc.nogc(), + // ); + + // let fulfill_handler = PromiseReactionHandler::JobCallback(result_callback.into()); + // let reject_handler = PromiseReactionHandler::Empty; + + // inner_promise_then( + // agent, + // promise_to_await, + // fulfill_handler, + // reject_handler, + // Some(result_capability), + // gc.nogc(), + // ); + + let undefined_values = (vec![Value::Undefined.bind(gc.nogc()); 2]).bind(gc.nogc()); + let result_array = + Array::from_slice(agent, &undefined_values.unbind(), gc.nogc()).bind(gc.nogc()); + let promise_all_record = agent.heap.create(PromiseAllRecord { + remaining_unresolved_promise_count: 2, + result_array: result_array.unbind(), + promise: result_promise.unbind(), + }); + + inner_promise_then( + agent, + promise_to_await.unbind(), + PromiseReactionHandler::PromiseAll { + index: 0, + promise_all: promise_all_record, + }, + PromiseReactionHandler::Empty, + Some(result_capability.unbind()), + gc.nogc(), + ); + + inner_promise_then( + agent, + second_promise.unbind(), + PromiseReactionHandler::PromiseAll { + index: 1, + promise_all: promise_all_record, + }, + PromiseReactionHandler::Empty, + Some(second_capability.unbind()), + gc.nogc(), + ); + + Ok(result_promise.unbind().into_value()) } fn all_settled<'gc>( diff --git a/nova_vm/src/heap.rs b/nova_vm/src/heap.rs index 77c966b42..686e65481 100644 --- a/nova_vm/src/heap.rs +++ b/nova_vm/src/heap.rs @@ -80,6 +80,7 @@ use crate::{ }, primitive_objects::PrimitiveObjectHeapData, promise::data::PromiseHeapData, + promise_objects::promise_abstract_operations::promise_all_record::PromiseAllRecord, proxy::data::ProxyHeapData, text_processing::string_objects::string_iterator_objects::StringIteratorHeapData, }, @@ -168,6 +169,7 @@ pub struct Heap { pub promise_reaction_records: Vec>>, pub promise_resolving_functions: Vec>>, pub promises: Vec>>, + pub promise_all_records: Vec>>, pub proxys: Vec>>, pub realms: Vec>>, #[cfg(feature = "regexp")] @@ -313,6 +315,7 @@ impl Heap { promise_reaction_records: Vec::with_capacity(0), promise_resolving_functions: Vec::with_capacity(0), promises: Vec::with_capacity(0), + promise_all_records: Vec::with_capacity(0), proxys: Vec::with_capacity(0), realms: Vec::with_capacity(1), #[cfg(feature = "regexp")] diff --git a/nova_vm/src/heap/heap_bits.rs b/nova_vm/src/heap/heap_bits.rs index 79cbb0d9a..f5b2f950d 100644 --- a/nova_vm/src/heap/heap_bits.rs +++ b/nova_vm/src/heap/heap_bits.rs @@ -50,6 +50,7 @@ use crate::ecmascript::{ ordinary::{caches::PropertyLookupCache, shape::ObjectShape}, primitive_objects::PrimitiveObject, promise::Promise, + promise_objects::promise_abstract_operations::promise_all_record::PromiseAll, proxy::Proxy, text_processing::string_objects::string_iterator_objects::StringIterator, }, @@ -126,6 +127,7 @@ pub struct HeapBits { pub promise_reaction_records: Box<[bool]>, pub promise_resolving_functions: Box<[bool]>, pub promises: Box<[bool]>, + pub promise_all_records: Box<[bool]>, pub proxys: Box<[bool]>, pub realms: Box<[bool]>, #[cfg(feature = "regexp")] @@ -208,6 +210,7 @@ pub(crate) struct WorkQueues { pub promises: Vec>, pub promise_reaction_records: Vec>, pub promise_resolving_functions: Vec>, + pub promise_all_records: Vec>, pub proxys: Vec>, pub realms: Vec>, #[cfg(feature = "regexp")] @@ -290,6 +293,7 @@ impl HeapBits { let promise_resolving_functions = vec![false; heap.promise_resolving_functions.len()]; let private_environments = vec![false; heap.environments.private.len()]; let promises = vec![false; heap.promises.len()]; + let promise_all_records = vec![false; heap.promise_all_records.len()]; let proxys = vec![false; heap.proxys.len()]; let realms = vec![false; heap.realms.len()]; #[cfg(feature = "regexp")] @@ -369,6 +373,7 @@ impl HeapBits { promise_resolving_functions: promise_resolving_functions.into_boxed_slice(), private_environments: private_environments.into_boxed_slice(), promises: promises.into_boxed_slice(), + promise_all_records: promise_all_records.into_boxed_slice(), proxys: proxys.into_boxed_slice(), realms: realms.into_boxed_slice(), #[cfg(feature = "regexp")] @@ -456,6 +461,7 @@ impl WorkQueues { heap.promise_resolving_functions.len() / 4, ), promises: Vec::with_capacity(heap.promises.len() / 4), + promise_all_records: Vec::with_capacity(heap.promise_all_records.len() / 4), proxys: Vec::with_capacity(heap.proxys.len() / 4), realms: Vec::with_capacity(heap.realms.len() / 4), #[cfg(feature = "regexp")] @@ -541,6 +547,7 @@ impl WorkQueues { promises, promise_reaction_records, promise_resolving_functions, + promise_all_records, proxys, realms, #[cfg(feature = "regexp")] @@ -985,6 +992,7 @@ pub(crate) struct CompactionLists { pub promise_reaction_records: CompactionList, pub promise_resolving_functions: CompactionList, pub promises: CompactionList, + pub promise_all_records: CompactionList, pub proxys: CompactionList, pub realms: CompactionList, #[cfg(feature = "regexp")] @@ -1081,6 +1089,7 @@ impl CompactionLists { &bits.promise_resolving_functions, ), promises: CompactionList::from_mark_bits(&bits.promises), + promise_all_records: CompactionList::from_mark_bits(&bits.promise_all_records), #[cfg(feature = "regexp")] regexps: CompactionList::from_mark_bits(&bits.regexps), #[cfg(feature = "set")] diff --git a/nova_vm/src/heap/heap_gc.rs b/nova_vm/src/heap/heap_gc.rs index d2edf82bd..734be6af4 100644 --- a/nova_vm/src/heap/heap_gc.rs +++ b/nova_vm/src/heap/heap_gc.rs @@ -159,6 +159,7 @@ pub fn heap_gc(agent: &mut Agent, root_realms: &mut [Option>], gc promise_reaction_records, promise_resolving_functions, promises, + promise_all_records, proxys, realms, #[cfg(feature = "regexp")] @@ -1290,6 +1291,7 @@ fn sweep( promise_reaction_records, promise_resolving_functions, promises, + promise_all_records, proxys, realms, #[cfg(feature = "regexp")] @@ -1719,6 +1721,15 @@ fn sweep( sweep_heap_vector_values(promises, &compactions, &bits.promises); }); } + if !promise_all_records.is_empty() { + s.spawn(|| { + sweep_heap_vector_values( + promise_all_records, + &compactions, + &bits.promise_all_records, + ); + }); + } if !proxys.is_empty() { s.spawn(|| { sweep_heap_vector_values(proxys, &compactions, &bits.proxys);