Skip to content

Commit

Permalink
feat(gc): Promise lifetime (#533)
Browse files Browse the repository at this point in the history
  • Loading branch information
aapoalas authored Jan 14, 2025
1 parent 3fe843e commit 207511f
Show file tree
Hide file tree
Showing 9 changed files with 91 additions and 50 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ use super::promise_jobs::new_promise_resolve_thenable_job;
/// if it's Pending but `is_resolved` is set to true.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct PromiseCapability {
promise: Promise,
promise: Promise<'static>,
must_be_unresolved: bool,
}

Expand All @@ -54,12 +54,12 @@ impl PromiseCapability {

pub fn from_promise(promise: Promise, must_be_unresolved: bool) -> Self {
Self {
promise,
promise: promise.unbind(),
must_be_unresolved,
}
}

pub fn promise(&self) -> Promise {
pub fn promise(&self) -> Promise<'static> {
self.promise
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use super::{

#[derive(Debug, Clone, Copy)]
pub(crate) struct PromiseResolveThenableJob {
promise_to_resolve: Promise,
promise_to_resolve: Promise<'static>,
thenable: Object,
then: Function<'static>,
}
Expand Down Expand Up @@ -93,7 +93,7 @@ pub(crate) fn new_promise_resolve_thenable_job(
Job {
realm: Some(then_realm),
inner: InnerJob::PromiseResolveThenable(PromiseResolveThenableJob {
promise_to_resolve,
promise_to_resolve: promise_to_resolve.unbind(),
thenable,
then: then.unbind(),
}),
Expand Down
107 changes: 74 additions & 33 deletions nova_vm/src/ecmascript/builtins/promise.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

use std::ops::{Index, IndexMut};

use crate::engine::context::GcScope;
use crate::engine::context::{GcScope, NoGcScope};
use crate::engine::rootable::{HeapRootData, HeapRootRef, Rootable};
use crate::engine::Scoped;
use crate::{
ecmascript::{
execution::{Agent, ProtoIntrinsics},
Expand All @@ -26,9 +28,37 @@ pub mod data;

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct Promise(pub(crate) PromiseIndex);
pub struct Promise<'a>(pub(crate) PromiseIndex<'a>);

impl<'a> Promise<'a> {
/// Unbind this Promise from its current lifetime. This is necessary to use
/// the Promise as a parameter in a call that can perform garbage
/// collection.
pub fn unbind(self) -> Promise<'static> {
unsafe { std::mem::transmute::<Self, Promise<'static>>(self) }
}

// Bind this Promise to the garbage collection lifetime. This enables Rust's
// borrow checker to verify that your Promises cannot not be invalidated by
// garbage collection being performed.
//
// This function is best called with the form
// ```rs
// let promise = promise.bind(&gc);
// ```
// to make sure that the unbound Promise cannot be used after binding.
pub const fn bind<'gc>(self, _: NoGcScope<'gc, '_>) -> Promise<'gc> {
unsafe { std::mem::transmute::<Promise, Promise<'gc>>(self) }
}

pub fn scope<'scope>(
self,
agent: &mut Agent,
gc: NoGcScope<'_, 'scope>,
) -> Scoped<'scope, Promise<'static>> {
Scoped::new(agent, self.unbind(), gc)
}

impl Promise {
pub(crate) const fn _def() -> Self {
Self(BaseIndex::from_u32_index(0))
}
Expand All @@ -38,7 +68,7 @@ impl Promise {
}

/// [27.2.4.7.1 PromiseResolve ( C, x )](https://tc39.es/ecma262/#sec-promise-resolve)
pub fn resolve(agent: &mut Agent, x: Value, gc: GcScope<'_, '_>) -> Self {
pub fn resolve(agent: &mut Agent, x: Value, mut gc: GcScope<'a, '_>) -> Self {
// 1. If IsPromise(x) is true, then
if let Value::Promise(promise) = x {
// a. Let xConstructor be ? Get(x, "constructor").
Expand All @@ -49,50 +79,38 @@ impl Promise {
// 2. Let promiseCapability be ? NewPromiseCapability(C).
let promise_capability = PromiseCapability::new(agent);
// 3. Perform ? Call(promiseCapability.[[Resolve]], undefined, « x »).
promise_capability.resolve(agent, x, gc);
promise_capability.resolve(agent, x, gc.reborrow());
// 4. Return promiseCapability.[[Promise]].
promise_capability.promise()
promise_capability.promise().bind(gc.into_nogc())
}
}
}

impl From<Promise> for PromiseIndex {
fn from(val: Promise) -> Self {
val.0
}
}

impl From<PromiseIndex> for Promise {
fn from(value: PromiseIndex) -> Self {
Self(value)
}
}

impl IntoValue for Promise {
impl IntoValue for Promise<'_> {
fn into_value(self) -> Value {
self.into()
}
}

impl IntoObject for Promise {
impl IntoObject for Promise<'_> {
fn into_object(self) -> Object {
self.into()
}
}

impl From<Promise> for Value {
impl From<Promise<'_>> for Value {
fn from(val: Promise) -> Self {
Value::Promise(val)
Value::Promise(val.unbind())
}
}

impl From<Promise> for Object {
impl From<Promise<'_>> for Object {
fn from(val: Promise) -> Self {
Object::Promise(val)
Object::Promise(val.unbind())
}
}

impl InternalSlots for Promise {
impl InternalSlots for Promise<'_> {
const DEFAULT_PROTOTYPE: ProtoIntrinsics = ProtoIntrinsics::Promise;

#[inline(always)]
Expand All @@ -108,30 +126,30 @@ impl InternalSlots for Promise {
}
}

impl InternalMethods for Promise {}
impl InternalMethods for Promise<'_> {}

impl CreateHeapData<PromiseHeapData, Promise> for Heap {
fn create(&mut self, data: PromiseHeapData) -> Promise {
impl CreateHeapData<PromiseHeapData, Promise<'static>> for Heap {
fn create(&mut self, data: PromiseHeapData) -> Promise<'static> {
self.promises.push(Some(data));
Promise(PromiseIndex::last(&self.promises))
}
}

impl Index<Promise> for Agent {
impl Index<Promise<'_>> for Agent {
type Output = PromiseHeapData;

fn index(&self, index: Promise) -> &Self::Output {
&self.heap.promises[index]
}
}

impl IndexMut<Promise> for Agent {
impl IndexMut<Promise<'_>> for Agent {
fn index_mut(&mut self, index: Promise) -> &mut Self::Output {
&mut self.heap.promises[index]
}
}

impl Index<Promise> for Vec<Option<PromiseHeapData>> {
impl Index<Promise<'_>> for Vec<Option<PromiseHeapData>> {
type Output = PromiseHeapData;

fn index(&self, index: Promise) -> &Self::Output {
Expand All @@ -142,7 +160,7 @@ impl Index<Promise> for Vec<Option<PromiseHeapData>> {
}
}

impl IndexMut<Promise> for Vec<Option<PromiseHeapData>> {
impl IndexMut<Promise<'_>> for Vec<Option<PromiseHeapData>> {
fn index_mut(&mut self, index: Promise) -> &mut Self::Output {
self.get_mut(index.get_index())
.expect("Promise out of bounds")
Expand All @@ -151,7 +169,30 @@ impl IndexMut<Promise> for Vec<Option<PromiseHeapData>> {
}
}

impl HeapMarkAndSweep for Promise {
impl Rootable for Promise<'_> {
type RootRepr = HeapRootRef;

fn to_root_repr(value: Self) -> Result<Self::RootRepr, HeapRootData> {
Err(HeapRootData::Promise(value.unbind()))
}

fn from_root_repr(value: &Self::RootRepr) -> Result<Self, HeapRootRef> {
Err(*value)
}

fn from_heap_ref(heap_ref: HeapRootRef) -> Self::RootRepr {
heap_ref
}

fn from_heap_data(heap_data: HeapRootData) -> Option<Self> {
match heap_data {
HeapRootData::Promise(object) => Some(object),
_ => None,
}
}
}

impl HeapMarkAndSweep for Promise<'static> {
fn mark_values(&self, queues: &mut crate::heap::WorkQueues) {
queues.promises.push(*self);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -302,12 +302,12 @@ pub(crate) fn evaluate_function_body(
}

/// ### [15.8.4 Runtime Semantics: EvaluateAsyncFunctionBody](https://tc39.es/ecma262/#sec-runtime-semantics-evaluateasyncfunctionbody)
pub(crate) fn evaluate_async_function_body(
pub(crate) fn evaluate_async_function_body<'a>(
agent: &mut Agent,
function_object: ECMAScriptFunction,
arguments_list: ArgumentsList,
mut gc: GcScope<'_, '_>,
) -> Promise {
mut gc: GcScope<'a, '_>,
) -> Promise<'a> {
let function_object = function_object.bind(gc.nogc());
let scoped_function_object = function_object.scope(agent, gc.nogc());
// 1. Let promiseCapability be ! NewPromiseCapability(%Promise%).
Expand Down Expand Up @@ -338,7 +338,7 @@ pub(crate) fn evaluate_async_function_body(
// i. Perform ! Call(promiseCapability.[[Resolve]], undefined, « undefined »).
// f. Else if result is a return completion, then
// i. Perform ! Call(promiseCapability.[[Resolve]], undefined, « result.[[Value]] »).
promise_capability.resolve(agent, result, gc);
promise_capability.resolve(agent, result, gc.reborrow());
}
ExecutionResult::Throw(err) => {
// [27.7.5.2 AsyncBlockStart ( promiseCapability, asyncBody, asyncContext )](https://tc39.es/ecma262/#sec-asyncblockstart)
Expand All @@ -360,7 +360,7 @@ pub(crate) fn evaluate_async_function_body(
return_promise_capability: promise_capability,
}));
// 2. Let promise be ? PromiseResolve(%Promise%, value).
let promise = Promise::resolve(agent, awaited_value, gc);
let promise = Promise::resolve(agent, awaited_value, gc.reborrow());
// 7. Perform PerformPromiseThen(promise, onFulfilled, onRejected).
inner_promise_then(agent, promise, handler, handler, None);
}
Expand All @@ -369,7 +369,7 @@ pub(crate) fn evaluate_async_function_body(
//}

// 5. Return Completion Record { [[Type]]: return, [[Value]]: promiseCapability.[[Promise]], [[Target]]: empty }.
promise_capability.promise()
promise_capability.promise().bind(gc.into_nogc())
}

/// ### [15.5.2 Runtime Semantics: EvaluateGeneratorBody](https://tc39.es/ecma262/#sec-runtime-semantics-evaluategeneratorbody)
Expand Down
2 changes: 1 addition & 1 deletion nova_vm/src/ecmascript/types/language/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ pub enum Object {
Error(Error<'static>) = ERROR_DISCRIMINANT,
FinalizationRegistry(FinalizationRegistry<'static>) = FINALIZATION_REGISTRY_DISCRIMINANT,
Map(Map<'static>) = MAP_DISCRIMINANT,
Promise(Promise) = PROMISE_DISCRIMINANT,
Promise(Promise<'static>) = PROMISE_DISCRIMINANT,
Proxy(Proxy) = PROXY_DISCRIMINANT,
#[cfg(feature = "regexp")]
RegExp(RegExp) = REGEXP_DISCRIMINANT,
Expand Down
2 changes: 1 addition & 1 deletion nova_vm/src/ecmascript/types/language/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ pub enum Value {
Error(Error<'static>),
FinalizationRegistry(FinalizationRegistry<'static>),
Map(Map<'static>),
Promise(Promise),
Promise(Promise<'static>),
Proxy(Proxy),
#[cfg(feature = "regexp")]
RegExp(RegExp),
Expand Down
4 changes: 2 additions & 2 deletions nova_vm/src/engine/rootable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ mod private {
impl RootableSealed for OrdinaryObject<'_> {}
impl RootableSealed for Primitive<'_> {}
impl RootableSealed for PrimitiveObject<'_> {}
impl RootableSealed for Promise {}
impl RootableSealed for Promise<'_> {}
impl RootableSealed for PropertyKey<'_> {}
impl RootableSealed for Proxy {}
#[cfg(feature = "regexp")]
Expand Down Expand Up @@ -237,7 +237,7 @@ pub enum HeapRootData {
Error(Error<'static>) = ERROR_DISCRIMINANT,
FinalizationRegistry(FinalizationRegistry<'static>) = FINALIZATION_REGISTRY_DISCRIMINANT,
Map(Map<'static>) = MAP_DISCRIMINANT,
Promise(Promise) = PROMISE_DISCRIMINANT,
Promise(Promise<'static>) = PROMISE_DISCRIMINANT,
Proxy(Proxy) = PROXY_DISCRIMINANT,
#[cfg(feature = "regexp")]
RegExp(RegExp) = REGEXP_DISCRIMINANT,
Expand Down
2 changes: 1 addition & 1 deletion nova_vm/src/heap/heap_bits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ pub(crate) struct WorkQueues {
pub object_environments: Vec<ObjectEnvironmentIndex>,
pub objects: Vec<OrdinaryObject<'static>>,
pub primitive_objects: Vec<PrimitiveObject<'static>>,
pub promises: Vec<Promise>,
pub promises: Vec<Promise<'static>>,
pub promise_reaction_records: Vec<PromiseReaction>,
pub promise_resolving_functions: Vec<BuiltinPromiseResolvingFunction<'static>>,
pub proxys: Vec<Proxy>,
Expand Down
2 changes: 1 addition & 1 deletion nova_vm/src/heap/indexes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@ pub type MapIteratorIndex = BaseIndex<'static, MapIteratorHeapData>;
pub type NumberIndex<'a> = BaseIndex<'a, NumberHeapData>;
pub type ObjectIndex<'a> = BaseIndex<'a, ObjectHeapData>;
pub type PrimitiveObjectIndex<'a> = BaseIndex<'a, PrimitiveObjectHeapData>;
pub type PromiseIndex = BaseIndex<'static, PromiseHeapData>;
pub type PromiseIndex<'a> = BaseIndex<'a, PromiseHeapData>;
pub type ProxyIndex = BaseIndex<'static, ProxyHeapData>;
#[cfg(feature = "regexp")]
pub type RegExpIndex = BaseIndex<'static, RegExpHeapData>;
Expand Down

0 comments on commit 207511f

Please sign in to comment.