Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
2519499
Implement Promise.all
komyg Jul 22, 2025
3dba3e7
Remove unecessary code and comments
komyg Jul 22, 2025
3699e5d
Fix build
komyg Jul 28, 2025
e90ffb0
Added closure callback
komyg Jul 29, 2025
8f93139
Add PromiseAll reaction handler
komyg Jul 31, 2025
5488192
Decrease promise count
komyg Jul 31, 2025
4d8bdc2
Add Promise All Heap Data
komyg Aug 4, 2025
7d5530c
Update reactions
komyg Aug 6, 2025
24ab2a7
Fix merge conflicts
komyg Aug 6, 2025
e2e2656
Fix naming
komyg Aug 8, 2025
106d503
Implemented heap data
komyg Aug 8, 2025
be86077
Minor changes
komyg Aug 8, 2025
cab7195
Mutate heap data
komyg Aug 8, 2025
960b9ed
Fix warns
komyg Aug 11, 2025
ddb27f4
Updated GC
komyg Aug 12, 2025
80a5ba2
Remove useless code in `on_promise_fulfilled`
komyg Aug 12, 2025
33924a6
Re-add gc.reborrow()
komyg Aug 12, 2025
1259899
unbind fix
komyg Aug 14, 2025
50a3b84
Rename structs
komyg Aug 14, 2025
4adfb07
Fix mark and sweep
komyg Aug 14, 2025
8d7c640
Replace index for get
komyg Aug 19, 2025
d8eccea
Implemented on promise fulfilled
komyg Aug 19, 2025
fe562a1
Fix on promise fulfilled
komyg Aug 19, 2025
0dfa893
Fix promise jobs
komyg Aug 19, 2025
79a2fa0
Fix GC
komyg Aug 19, 2025
fc72d59
Combine imports
komyg Aug 19, 2025
49f9aed
Fix heap bits
komyg Aug 19, 2025
b5c1189
Fix nitpick
komyg Aug 21, 2025
3282bd7
Added result_array helper
komyg Aug 22, 2025
1a2fc7a
Use bindable_handle
komyg Aug 22, 2025
785c68c
Remove debug code from PromiseConstructor
komyg Aug 22, 2025
2c4c9e5
Minor improvements
komyg Aug 22, 2025
0976b82
Fix typo
komyg Aug 22, 2025
c26cefe
Fix warn
komyg Aug 22, 2025
977ee2c
Updated If
komyg Aug 22, 2025
302c459
Updated metrics
komyg Aug 25, 2025
3fe4d5c
Minor fixes
komyg Aug 27, 2025
aad261a
Create array initialized as None
komyg Aug 29, 2025
fcb8e47
Turn values into promises
komyg Aug 29, 2025
18a0f51
Replace `if` with `match`
komyg Aug 29, 2025
f7e966e
Improved code
komyg Aug 29, 2025
7a77664
Handle errors
komyg Sep 1, 2025
6eb1b15
Updated metrics
komyg Sep 1, 2025
95e3dad
Updated expectations
komyg Sep 1, 2025
c064ec1
Solve nitpicks
komyg Sep 3, 2025
7864cce
Add comment
komyg Sep 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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_finally_functions;
pub(crate) mod promise_jobs;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
// 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 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, bindable_handle},
heap::{
CompactionLists, CreateHeapData, Heap, HeapMarkAndSweep, WorkQueues, indexes::BaseIndex,
},
};

#[derive(Debug, Clone, Copy)]
pub struct PromiseAllRecord<'a> {
pub(crate) remaining_unresolved_promise_count: u32,
pub(crate) result_array: Array<'a>,
pub(crate) promise: Promise<'a>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct PromiseAll<'a>(BaseIndex<'a, PromiseAllRecord<'static>>);

impl<'a> PromiseAll<'a> {
pub(crate) fn on_promise_fulfilled(
self,
agent: &mut Agent,
index: u32,
value: Value<'a>,
gc: GcScope<'a, '_>,
) {
let promise_all = self.bind(gc.nogc());
let value = value.bind(gc.nogc());

let result_array = self.get_result_array(agent, gc.nogc());

let elements = result_array.as_mut_slice(agent);
elements[index as usize] = Some(value.unbind());

let data = promise_all.get_mut(agent);
data.remaining_unresolved_promise_count =
data.remaining_unresolved_promise_count.saturating_sub(1);
if data.remaining_unresolved_promise_count == 0 {
let capability = PromiseCapability::from_promise(data.promise, true);
capability.resolve(agent, result_array.into_value().unbind(), gc);
}
}

pub(crate) fn on_promise_rejected(
self,
agent: &mut Agent,
value: Value<'a>,
gc: NoGcScope<'a, '_>,
) {
let value = value.bind(gc);
let promise_all = self.bind(gc);
let data = promise_all.get_mut(agent);

let capability = PromiseCapability::from_promise(data.promise, true);
capability.reject(agent, value.unbind(), gc);
}

pub(crate) fn get_result_array(self, agent: &Agent, gc: NoGcScope<'a, '_>) -> Array<'a> {
let data = self.get(agent);
data.result_array.bind(gc).unbind()
}

pub(crate) const fn get_index(self) -> usize {
self.0.into_index()
}

fn get(self, agent: &Agent) -> &PromiseAllRecord<'a> {
agent
.heap
.promise_all_records
.get(self.get_index())
.expect("PromiseAllRecord not found")
}

fn get_mut(self, agent: &mut Agent) -> &mut PromiseAllRecord<'static> {
agent
.heap
.promise_all_records
.get_mut(self.get_index())
.expect("PromiseAllRecord not found")
}

pub(crate) const fn _def() -> Self {
Self(BaseIndex::from_u32_index(0))
}
}

impl AsRef<[PromiseAllRecord<'static>]> for Agent {
fn as_ref(&self) -> &[PromiseAllRecord<'static>] {
&self.heap.promise_all_records
}
}

impl AsMut<[PromiseAllRecord<'static>]> for Agent {
fn as_mut(&mut self) -> &mut [PromiseAllRecord<'static>] {
&mut self.heap.promise_all_records
}
}

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);
}
}

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);
}
}

bindable_handle!(PromiseAllRecord);
bindable_handle!(PromiseAll);

impl<'a> CreateHeapData<PromiseAllRecord<'a>, PromiseAll<'a>> for Heap {
fn create(&mut self, data: PromiseAllRecord<'a>) -> PromiseAll<'a> {
self.promise_all_records.push(data.unbind());
self.alloc_counter += core::mem::size_of::<PromiseAllRecord<'static>>();
PromiseAll(BaseIndex::last_t(&self.promise_all_records))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down Expand Up @@ -278,12 +278,31 @@ impl PromiseReactionJob {
);
return Ok(());
}
PromiseReactionType::Reject => (
Err(JsError::new(argument)),
PromiseCapability::from_promise(promise, true),
),
}
}
PromiseReactionHandler::PromiseAll { promise_all, index } => {
let reaction_type = agent[reaction].reaction_type;
let capability = agent[reaction].capability.clone().unwrap();
let scoped_arg = argument.scope(agent, gc.nogc());
match reaction_type {
PromiseReactionType::Fulfill => {
promise_all.on_promise_fulfilled(
agent,
index,
scoped_arg.get(agent),
gc.reborrow(),
);
(Ok(scoped_arg.get(agent)), capability.bind(gc.nogc()))
}
PromiseReactionType::Reject => {
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « reason »).
// b. Return unused.
promise_all.on_promise_rejected(agent, scoped_arg.get(agent), gc.nogc());
(
Err(JsError::new(argument)),
PromiseCapability::from_promise(promise, true),
Err(JsError::new(scoped_arg.get(agent))),
capability.bind(gc.nogc()),
)
}
}
Expand Down Expand Up @@ -348,7 +367,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 }.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -68,6 +69,10 @@ pub(crate) enum PromiseReactionHandler<'a> {
promise: Promise<'a>,
module: AbstractModule<'a>,
},
PromiseAll {
index: u32,
promise_all: PromiseAll<'a>,
},
Empty,
}

Expand All @@ -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 => {}
}
}
Expand All @@ -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 => {}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,20 @@ use crate::{
},
builders::builtin_function_builder::BuiltinFunctionBuilder,
builtins::{
ArgumentsList, Behaviour, Builtin, BuiltinGetter, BuiltinIntrinsicConstructor,
ArgumentsList, Array, Behaviour, Builtin, BuiltinGetter, BuiltinIntrinsicConstructor,
array_create,
ordinary::ordinary_create_from_constructor,
promise::{
Promise,
data::{PromiseHeapData, PromiseState},
},
promise_objects::{
promise_abstract_operations::{
promise_all_record::PromiseAllRecord,
promise_reaction_records::PromiseReactionHandler,
},
promise_prototype::inner_promise_then,
},
},
execution::{
Agent, JsResult, ProtoIntrinsics, Realm,
Expand Down Expand Up @@ -210,13 +218,90 @@ impl PromiseConstructor {
Ok(scoped_promise.get(agent).into_value())
}

/// ### [27.2.4.1 Promise.all ( iterable )](https://tc39.es/ecma262/#sec-promise.all)
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 arguments = arguments.bind(gc.nogc());

let first_arg = arguments.get(0);
let Ok(promise_array) = Array::try_from(first_arg) else {
return Err(agent.throw_exception_with_static_message(
ExceptionType::TypeError,
"Expected an array as first argument",
gc.into_nogc(),
));
};
let scoped_promise_array = promise_array.scope(agent, gc.nogc());

let array_len = promise_array.len(agent);
let result_capability = PromiseCapability::new(agent, gc.nogc());
let result_promise = result_capability.promise();
let scoped_result_promise = result_promise.scope(agent, gc.nogc());

let result_array = array_create(
agent,
array_len as usize,
array_len as usize,
None,
gc.nogc(),
)
.unbind()?
.bind(gc.nogc());

let promise_all_record = agent.heap.create(PromiseAllRecord {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: This PromiseAll value (which is effectively a reference to the heap) is unbound from the GC lifetime and is thus becomes use-after-free if Promise::resolve in the array loop triggers GC.

A further issue is that this method doesn't quite do what the spec says in general. The first_arg argument is supposed to be an iterable type (so eg. a Set or Map would be okay as well), with all the good and bad things that come with that. Supporting that whole all becomes a... whole thing :)

I'm wondering, would you want to pair-program this over the finish line? There's some busy-work to be done (around making the PromiseAll scopable, iterating iterators), and then the "real" work of translating the spec text. I assume that may be a bit hard to do without help, so pair programming seems like it might be a good solution.

remaining_unresolved_promise_count: array_len,
result_array: result_array.unbind(),
promise: scoped_result_promise.get(agent),
});

for index in 0..array_len {
let element = scoped_promise_array.get(agent).as_slice(agent)[index as usize];

let promise = match element {
Some(Value::Promise(promise)) => promise,
Some(value) => Promise::resolve(agent, value.unbind(), gc.reborrow())
.unbind()
.bind(gc.nogc()),
None => {
unreachable!()
}
};

let capability = PromiseCapability::new(agent, gc.nogc());
inner_promise_then(
agent,
promise.unbind(),
PromiseReactionHandler::PromiseAll {
index,
promise_all: promise_all_record,
},
PromiseReactionHandler::PromiseAll {
index,
promise_all: promise_all_record,
},
Some(capability.unbind()),
gc.nogc(),
);
}

Ok(scoped_result_promise.get(agent).into_value())
}

fn all_settled<'gc>(
Expand Down
Loading
Loading