Browse Source

Implement new spec changes for `AsyncGenerator` (#3950)

* Implement new spec changes for `AsyncGenerator`

* Add panic docs
pull/3956/head
José Julián Espina 3 months ago committed by GitHub
parent
commit
c22f39b026
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 421
      core/engine/src/builtins/async_generator/mod.rs
  2. 4
      core/engine/src/vm/completion_record.rs
  3. 11
      core/engine/src/vm/opcode/await/mod.rs
  4. 24
      core/engine/src/vm/opcode/generator/mod.rs
  5. 51
      core/engine/src/vm/opcode/generator/yield_stm.rs
  6. 14
      test262_config.toml
  7. 17
      tests/tester/src/edition.rs

421
core/engine/src/builtins/async_generator/mod.rs

@ -37,7 +37,7 @@ pub(crate) enum AsyncGeneratorState {
SuspendedStart, SuspendedStart,
SuspendedYield, SuspendedYield,
Executing, Executing,
AwaitingReturn, DrainingQueue,
Completed, Completed,
} }
@ -168,7 +168,15 @@ impl AsyncGenerator {
let completion = CompletionRecord::Normal(args.get_or_undefined(0).clone()); let completion = CompletionRecord::Normal(args.get_or_undefined(0).clone());
// 8. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability). // 8. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability).
Self::enqueue(&generator, completion, promise_capability.clone(), context); Self::enqueue(&generator, completion.clone(), promise_capability.clone());
// 9. If state is either suspendedStart or suspendedYield, then
if state == AsyncGeneratorState::SuspendedStart
|| state == AsyncGeneratorState::SuspendedYield
{
// a. Perform AsyncGeneratorResume(generator, completion).
Self::resume(&generator, completion, context);
}
// 11. Return promiseCapability.[[Promise]]. // 11. Return promiseCapability.[[Promise]].
Ok(promise_capability.promise().clone().into()) Ok(promise_capability.promise().clone().into())
@ -212,10 +220,29 @@ impl AsyncGenerator {
// 5. Let completion be Completion Record { [[Type]]: return, [[Value]]: value, [[Target]]: empty }. // 5. Let completion be Completion Record { [[Type]]: return, [[Value]]: value, [[Target]]: empty }.
let return_value = args.get_or_undefined(0).clone(); let return_value = args.get_or_undefined(0).clone();
let completion = CompletionRecord::Return(return_value); let completion = CompletionRecord::Return(return_value.clone());
// 6. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability). // 6. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability).
Self::enqueue(&generator, completion, promise_capability.clone(), context); Self::enqueue(&generator, completion.clone(), promise_capability.clone());
// 7. Let state be generator.[[AsyncGeneratorState]].
let state = generator.borrow().data.state;
// 8. If state is either suspended-start or completed, then
if state == AsyncGeneratorState::SuspendedStart || state == AsyncGeneratorState::Completed {
// a. Set generator.[[AsyncGeneratorState]] to draining-queue.
generator.borrow_mut().data.state = AsyncGeneratorState::DrainingQueue;
// b. Perform ! AsyncGeneratorAwaitReturn(generator).
Self::await_return(&generator, return_value, context);
}
// 9. Else if state is suspended-yield, then
else if state == AsyncGeneratorState::SuspendedYield {
// a. Perform AsyncGeneratorResume(generator, completion).
Self::resume(&generator, completion, context);
}
// 10. Else,
// a. Assert: state is either executing or draining-queue.
// 11. Return promiseCapability.[[Promise]]. // 11. Return promiseCapability.[[Promise]].
Ok(promise_capability.promise().clone().into()) Ok(promise_capability.promise().clone().into())
@ -294,12 +321,16 @@ impl AsyncGenerator {
CompletionRecord::Throw(JsError::from_opaque(args.get_or_undefined(0).clone())); CompletionRecord::Throw(JsError::from_opaque(args.get_or_undefined(0).clone()));
// 9. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability). // 9. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability).
Self::enqueue( Self::enqueue(&generator, completion.clone(), promise_capability.clone());
&generator,
completion.clone(), // 10. If state is suspended-yield, then
promise_capability.clone(), if state == AsyncGeneratorState::SuspendedYield {
context, // a. Perform AsyncGeneratorResume(generator, completion).
); Self::resume(&generator, completion, context);
}
// 11. Else,
// a. Assert: state is either executing or draining-queue.
// 12. Return promiseCapability.[[Promise]]. // 12. Return promiseCapability.[[Promise]].
Ok(promise_capability.promise().clone().into()) Ok(promise_capability.promise().clone().into())
@ -315,7 +346,6 @@ impl AsyncGenerator {
generator: &JsObject<AsyncGenerator>, generator: &JsObject<AsyncGenerator>,
completion: CompletionRecord, completion: CompletionRecord,
promise_capability: PromiseCapability, promise_capability: PromiseCapability,
context: &mut Context,
) { ) {
let mut gen = generator.borrow_mut(); let mut gen = generator.borrow_mut();
// 1. Let request be AsyncGeneratorRequest { [[Completion]]: completion, [[Capability]]: promiseCapability }. // 1. Let request be AsyncGeneratorRequest { [[Completion]]: completion, [[Capability]]: promiseCapability }.
@ -326,13 +356,6 @@ impl AsyncGenerator {
// 2. Append request to the end of generator.[[AsyncGeneratorQueue]]. // 2. Append request to the end of generator.[[AsyncGeneratorQueue]].
gen.data.queue.push_back(request); gen.data.queue.push_back(request);
// Patch that mirrors https://262.ecma-international.org/12.0/#sec-asyncgeneratorenqueue
// This resolves the return bug.
if gen.data.state != AsyncGeneratorState::Executing {
drop(gen);
AsyncGenerator::resume_next(generator, context);
}
} }
/// `AsyncGeneratorCompleteStep ( generator, completion, done [ , realm ] )` /// `AsyncGeneratorCompleteStep ( generator, completion, done [ , realm ] )`
@ -340,24 +363,34 @@ impl AsyncGenerator {
/// More information: /// More information:
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
/// ///
/// # Panics
///
/// Panics if the async generator request queue of `generator` is empty.
///
/// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorcompletestep /// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorcompletestep
pub(crate) fn complete_step( pub(crate) fn complete_step(
next: &AsyncGeneratorRequest, generator: &JsObject<AsyncGenerator>,
completion: JsResult<JsValue>, completion: JsResult<JsValue>,
done: bool, done: bool,
realm: Option<Realm>, realm: Option<Realm>,
context: &mut Context, context: &mut Context,
) { ) {
// 1. Let queue be generator.[[AsyncGeneratorQueue]]. // 1. Assert: generator.[[AsyncGeneratorQueue]] is not empty.
// 2. Assert: queue is not empty. // 2. Let next be the first element of generator.[[AsyncGeneratorQueue]].
// 3. Let next be the first element of queue. // 3. Remove the first element from generator.[[AsyncGeneratorQueue]].
// 4. Remove the first element from queue. let next = generator
// 5. Let promiseCapability be next.[[Capability]]. .borrow_mut()
.data
.queue
.pop_front()
.expect("1. Assert: generator.[[AsyncGeneratorQueue]] is not empty.");
// 4. Let promiseCapability be next.[[Capability]].
let promise_capability = &next.capability; let promise_capability = &next.capability;
// 6. Let value be completion.[[Value]]. // 5. Let value be completion.[[Value]].
match completion { match completion {
// 7. If completion.[[Type]] is throw, then // 6. If completion is a throw completion, then
Err(e) => { Err(e) => {
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « value »). // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « value »).
promise_capability promise_capability
@ -365,17 +398,17 @@ impl AsyncGenerator {
.call(&JsValue::undefined(), &[e.to_opaque(context)], context) .call(&JsValue::undefined(), &[e.to_opaque(context)], context)
.expect("cannot fail per spec"); .expect("cannot fail per spec");
} }
// 8. Else,
Ok(value) => {
// a. Assert: completion.[[Type]] is normal.
// 7. Else,
Ok(value) => {
// a. Assert: completion is a normal completion.
// b. If realm is present, then // b. If realm is present, then
let iterator_result = if let Some(realm) = realm { let iterator_result = if let Some(realm) = realm {
// i. Let oldRealm be the running execution context's Realm. // i. Let oldRealm be the running execution context's Realm.
// ii. Set the running execution context's Realm to realm. // ii. Set the running execution context's Realm to realm.
let old_realm = context.enter_realm(realm); let old_realm = context.enter_realm(realm);
// iii. Let iteratorResult be CreateIterResultObject(value, done). // iii. Let iteratorResult be CreateIteratorResultObject(value, done).
let iterator_result = create_iter_result_object(value, done, context); let iterator_result = create_iter_result_object(value, done, context);
// iv. Set the running execution context's Realm to oldRealm. // iv. Set the running execution context's Realm to oldRealm.
@ -384,7 +417,7 @@ impl AsyncGenerator {
iterator_result iterator_result
} else { } else {
// c. Else, // c. Else,
// i. Let iteratorResult be CreateIterResultObject(value, done). // i. Let iteratorResult be CreateIteratorResultObject(value, done).
create_iter_result_object(value, done, context) create_iter_result_object(value, done, context)
}; };
@ -395,6 +428,61 @@ impl AsyncGenerator {
.expect("cannot fail per spec"); .expect("cannot fail per spec");
} }
} }
// 8. Return unused.
}
/// `AsyncGeneratorResume ( generator, completion )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// # Panics
///
/// Panics if `generator` is neither in the `SuspendedStart` nor in the `SuspendedYield` states.
///
/// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorresume
pub(crate) fn resume(
generator: &JsObject<AsyncGenerator>,
completion: CompletionRecord,
context: &mut Context,
) {
// 1. Assert: generator.[[AsyncGeneratorState]] is either suspended-start or suspended-yield.
assert!(matches!(
generator.borrow().data.state,
AsyncGeneratorState::SuspendedStart | AsyncGeneratorState::SuspendedYield
));
// 2. Let genContext be generator.[[AsyncGeneratorContext]].
let mut generator_context = generator
.borrow_mut()
.data
.context
.take()
.expect("generator context cannot be empty here");
// 5. Set generator.[[AsyncGeneratorState]] to executing.
generator.borrow_mut().data.state = AsyncGeneratorState::Executing;
let (value, resume_kind) = match completion {
CompletionRecord::Normal(val) => (val, GeneratorResumeKind::Normal),
CompletionRecord::Return(val) => (val, GeneratorResumeKind::Return),
CompletionRecord::Throw(err) => (err.to_opaque(context), GeneratorResumeKind::Throw),
};
// 3. Let callerContext be the running execution context.
// 4. Suspend callerContext.
// 6. Push genContext onto the execution context stack; genContext is now the running execution context.
let result = generator_context.resume(Some(value), resume_kind, context);
// 7. Resume the suspended evaluation of genContext using completion as the result of the operation that suspended it. Let result be the Completion Record returned by the resumed computation.
generator.borrow_mut().data.context = Some(generator_context);
// 8. Assert: result is never an abrupt completion.
assert!(!result.is_throw_completion());
// 9. Assert: When we return here, genContext has already been removed from the execution context stack and
// callerContext is the currently running execution context.
// 10. Return unused.
} }
/// `AsyncGeneratorAwaitReturn ( generator )` /// `AsyncGeneratorAwaitReturn ( generator )`
@ -402,21 +490,29 @@ impl AsyncGenerator {
/// More information: /// More information:
/// - [ECMAScript reference][spec] /// - [ECMAScript reference][spec]
/// ///
/// # Panics
///
/// Panics if `generator` is not in the `DrainingQueue` state.
///
/// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorawaitreturn /// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorawaitreturn
pub(crate) fn await_return( pub(crate) fn await_return(
generator: JsObject<AsyncGenerator>, generator: &JsObject<AsyncGenerator>,
value: JsValue, value: JsValue,
context: &mut Context, context: &mut Context,
) { ) {
// 1. Let queue be generator.[[AsyncGeneratorQueue]]. // 1. Assert: generator.[[AsyncGeneratorState]] is draining-queue.
// 2. Assert: queue is not empty. assert_eq!(
// 3. Let next be the first element of queue. generator.borrow().data.state,
// 4. Let completion be Completion(next.[[Completion]]). AsyncGeneratorState::DrainingQueue
);
// Note: The spec is currently broken here. // 2. Let queue be generator.[[AsyncGeneratorQueue]].
// See: https://github.com/tc39/ecma262/pull/2683 // 3. Assert: queue is not empty.
// 4. Let next be the first element of queue.
// 5. Let completion be Completion(next.[[Completion]]).
// 6. Assert: completion is a return completion.
// 6. Let promise be ? PromiseResolve(%Promise%, completion.[[Value]]). // 7. Let promiseCompletion be Completion(PromiseResolve(%Promise%, completion.[[Value]])).
let promise_completion = Promise::promise_resolve( let promise_completion = Promise::promise_resolve(
&context.intrinsics().constructors().promise().constructor(), &context.intrinsics().constructors().promise().constructor(),
value, value,
@ -425,43 +521,39 @@ impl AsyncGenerator {
let promise = match promise_completion { let promise = match promise_completion {
Ok(value) => value, Ok(value) => value,
Err(value) => { // 8. If promiseCompletion is an abrupt completion, then
let next = { Err(e) => {
let mut gen = generator.borrow_mut(); // a. Perform AsyncGeneratorCompleteStep(generator, promiseCompletion, true).
gen.data.state = AsyncGeneratorState::Completed; Self::complete_step(generator, Err(e), true, None, context);
gen.data.context = None; // b. Perform AsyncGeneratorDrainQueue(generator).
gen.data.queue.pop_front().expect("queue must not be empty") Self::drain_queue(generator, context);
}; // c. Return unused.
Self::complete_step(&next, Err(value), true, None, context);
Self::resume_next(&generator, context);
return; return;
} }
}; };
// 7. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures generator and performs the following steps when called: // 9. Assert: promiseCompletion is a normal completion.
// 8. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »). // 10. Let promise be promiseCompletion.[[Value]].
// 11. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures generator and performs the following steps when called:
// 12. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »).
let on_fulfilled = FunctionObjectBuilder::new( let on_fulfilled = FunctionObjectBuilder::new(
context.realm(), context.realm(),
NativeFunction::from_copy_closure_with_captures( NativeFunction::from_copy_closure_with_captures(
|_this, args, generator, context| { |_this, args, generator, context| {
let next = { // a. Assert: generator.[[AsyncGeneratorState]] is draining-queue.
let mut gen = generator.borrow_mut(); assert_eq!(
generator.borrow().data.state,
// a. Set generator.[[AsyncGeneratorState]] to completed. AsyncGeneratorState::DrainingQueue
gen.data.state = AsyncGeneratorState::Completed; );
gen.data.context = None;
gen.data.queue.pop_front().expect("must have one entry")
};
// b. Let result be NormalCompletion(value). // b. Let result be NormalCompletion(value).
let result = Ok(args.get_or_undefined(0).clone()); let result = Ok(args.get_or_undefined(0).clone());
// c. Perform AsyncGeneratorCompleteStep(generator, result, true). // c. Perform AsyncGeneratorCompleteStep(generator, result, true).
Self::complete_step(&next, result, true, None, context); Self::complete_step(generator, result, true, None, context);
// d. Perform AsyncGeneratorDrainQueue(generator). // d. Perform AsyncGeneratorDrainQueue(generator).
Self::resume_next(generator, context); Self::drain_queue(generator, context);
// e. Return undefined. // e. Return undefined.
Ok(JsValue::undefined()) Ok(JsValue::undefined())
@ -473,42 +565,39 @@ impl AsyncGenerator {
.length(1) .length(1)
.build(); .build();
// 9. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures generator and performs the following steps when called: // 13. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures generator and performs the following steps when called:
// 10. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »). // 14. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »).
let on_rejected = FunctionObjectBuilder::new( let on_rejected = FunctionObjectBuilder::new(
context.realm(), context.realm(),
NativeFunction::from_copy_closure_with_captures( NativeFunction::from_copy_closure_with_captures(
|_this, args, generator, context| { |_this, args, generator, context| {
let next = { // a. Assert: generator.[[AsyncGeneratorState]] is draining-queue.
let mut gen = generator.borrow_mut(); assert_eq!(
generator.borrow().data.state,
// a. Set generator.[[AsyncGeneratorState]] to completed. AsyncGeneratorState::DrainingQueue
gen.data.state = AsyncGeneratorState::Completed; );
gen.data.context = None;
gen.data.queue.pop_front().expect("must have one entry")
};
// b. Let result be ThrowCompletion(reason). // b. Let result be ThrowCompletion(reason).
let result = Err(JsError::from_opaque(args.get_or_undefined(0).clone())); let result = Err(JsError::from_opaque(args.get_or_undefined(0).clone()));
// c. Perform AsyncGeneratorCompleteStep(generator, result, true). // c. Perform AsyncGeneratorCompleteStep(generator, result, true).
Self::complete_step(&next, result, true, None, context); Self::complete_step(generator, result, true, None, context);
// d. Perform AsyncGeneratorDrainQueue(generator). // d. Perform AsyncGeneratorDrainQueue(generator).
Self::resume_next(generator, context); Self::drain_queue(generator, context);
// e. Return undefined. // e. Return undefined.
Ok(JsValue::undefined()) Ok(JsValue::undefined())
}, },
generator, generator.clone(),
), ),
) )
.name(js_string!("")) .name(js_string!(""))
.length(1) .length(1)
.build(); .build();
// 11. Perform PerformPromiseThen(promise, onFulfilled, onRejected). // 15. Perform PerformPromiseThen(promise, onFulfilled, onRejected).
// 16. Return unused.
Promise::perform_promise_then( Promise::perform_promise_then(
&promise, &promise,
Some(on_fulfilled), Some(on_fulfilled),
@ -518,135 +607,77 @@ impl AsyncGenerator {
); );
} }
/// [`AsyncGeneratorResumeNext ( generator )`][spec] /// `AsyncGeneratorDrainQueue ( generator )`
/// ///
/// [spec]: https://262.ecma-international.org/12.0/#sec-asyncgeneratorresumenext /// More information:
pub(crate) fn resume_next(generator: &JsObject<AsyncGenerator>, context: &mut Context) { /// - [ECMAScript reference][spec]
// 1. Assert: generator is an AsyncGenerator instance. ///
let mut gen = generator.borrow_mut(); /// # Panics
// 2. Let state be generator.[[AsyncGeneratorState]]. ///
match gen.data.state { /// Panics if `generator` is not in the `DrainingQueue` state.
// 3. Assert: state is not executing. ///
AsyncGeneratorState::Executing => panic!("3. Assert: state is not executing."), /// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratordrainqueue
// 4. If state is awaiting-return, return undefined. pub(crate) fn drain_queue(generator: &JsObject<AsyncGenerator>, context: &mut Context) {
AsyncGeneratorState::AwaitingReturn => return, // 1. Assert: generator.[[AsyncGeneratorState]] is draining-queue.
_ => {} assert_eq!(
} generator.borrow().data.state,
AsyncGeneratorState::DrainingQueue
// 5. Let queue be generator.[[AsyncGeneratorQueue]].
// 6. If queue is an empty List, return undefined.
// 7. Let next be the value of the first element of queue.
// 8. Assert: next is an AsyncGeneratorRequest record.
let Some(next) = gen.data.queue.front() else {
return;
};
// 9. Let completion be next.[[Completion]].
let completion = &next.completion;
match (completion, gen.data.state) {
// 11. Else if state is completed, return ! AsyncGeneratorResolve(generator, undefined, true).
(CompletionRecord::Normal(_), s) => {
if s == AsyncGeneratorState::Completed {
let next = gen
.data
.queue
.pop_front()
.expect("already have a reference to the front");
drop(gen);
AsyncGenerator::complete_step(
&next,
Ok(JsValue::undefined()),
true,
None,
context,
); );
return AsyncGenerator::resume_next(generator, context);
}
}
// b. If state is completed, then
// i. If completion.[[Type]] is return, then
(
CompletionRecord::Return(val),
AsyncGeneratorState::SuspendedStart | AsyncGeneratorState::Completed,
) => {
let val = val.clone();
// 1. Set generator.[[AsyncGeneratorState]] to awaiting-return.
gen.data.state = AsyncGeneratorState::AwaitingReturn;
drop(gen);
// Steps 2-11 are superseeded by `AsyncGeneratorAwaitReturn`
AsyncGenerator::await_return(generator.clone(), val, context);
// 12. Return undefined. // 2. Let queue be generator.[[AsyncGeneratorQueue]].
// 3. If queue is empty, then
if generator.borrow().data.queue.is_empty() {
// a. Set generator.[[AsyncGeneratorState]] to completed.
generator.borrow_mut().data.state = AsyncGeneratorState::Completed;
generator.borrow_mut().data.context = None;
// b. Return unused.
return; return;
} }
// ii. Else,
(
CompletionRecord::Throw(e),
AsyncGeneratorState::SuspendedStart | AsyncGeneratorState::Completed,
) => {
let e = e.clone();
// 1. Assert: completion.[[Type]] is throw.
// 2. Perform ! AsyncGeneratorReject(generator, completion.[[Value]]).
gen.data.state = AsyncGeneratorState::Completed;
let next = gen // 4. Let done be false.
// 5. Repeat, while done is false,
loop {
// a. Let next be the first element of queue.
let next = generator
.borrow()
.data .data
.queue .queue
.pop_front() .front()
.expect("already have a reference to the front"); .expect("must have entry")
drop(gen); .completion
AsyncGenerator::complete_step(&next, Err(e), true, None, context); .clone();
// 3. Return undefined.
return AsyncGenerator::resume_next(generator, context); // b. Let completion be Completion(next.[[Completion]]).
match next {
// c. If completion is a return completion, then
CompletionRecord::Return(val) => {
// i. Perform AsyncGeneratorAwaitReturn(generator).
Self::await_return(generator, val, context);
// ii. Set done to true.
break;
}
// d. Else,
completion => {
// i. If completion is a normal completion, then
// 1. Set completion to NormalCompletion(undefined).
let completion = completion.consume().map(|_| JsValue::undefined());
// ii. Perform AsyncGeneratorCompleteStep(generator, completion, true).
Self::complete_step(generator, completion, true, None, context);
// iii. If queue is empty, then
if generator.borrow().data.queue.is_empty() {
// 1. Set generator.[[AsyncGeneratorState]] to completed.
generator.borrow_mut().data.state = AsyncGeneratorState::Completed;
generator.borrow_mut().data.context = None;
// 2. Set done to true.
break;
}
}
} }
_ => {}
} }
// 12. Assert: state is either suspendedStart or suspendedYield. // 6. Return unused.
assert!(matches!(
gen.data.state,
AsyncGeneratorState::SuspendedStart | AsyncGeneratorState::SuspendedYield
));
let completion = completion.clone();
// 16. Set generator.[[AsyncGeneratorState]] to executing.
gen.data.state = AsyncGeneratorState::Executing;
// 13. Let genContext be generator.[[AsyncGeneratorContext]].
let mut generator_context = gen
.data
.context
.take()
.expect("generator context cannot be empty here");
drop(gen);
let (value, resume_kind) = match completion {
CompletionRecord::Normal(val) => (val, GeneratorResumeKind::Normal),
CompletionRecord::Return(val) => (val, GeneratorResumeKind::Return),
CompletionRecord::Throw(err) => (err.to_opaque(context), GeneratorResumeKind::Throw),
};
// 14. Let callerContext be the running execution context.
// 15. Suspend callerContext.
// 17. Push genContext onto the execution context stack; genContext is now the running execution context.
// 18. Resume the suspended evaluation of genContext using completion as the result of the operation that suspended it.
// Let result be the completion record returned by the resumed computation.
let result = generator_context.resume(Some(value), resume_kind, context);
// 19. Assert: result is never an abrupt completion.
assert!(!matches!(result, CompletionRecord::Throw(_)));
generator
.borrow_mut()
.data
.context
.get_or_insert(generator_context);
// 20. Assert: When we return here, genContext has already been removed from the execution context stack and
// callerContext is the currently running execution context.
// 21. Return undefined.
} }
} }

4
core/engine/src/vm/completion_record.rs

@ -29,6 +29,10 @@ unsafe impl Trace for CompletionRecord {
// ---- `CompletionRecord` methods ---- // ---- `CompletionRecord` methods ----
impl CompletionRecord { impl CompletionRecord {
pub(crate) const fn is_throw_completion(&self) -> bool {
matches!(self, Self::Throw(_))
}
/// This function will consume the current `CompletionRecord` and return a `JsResult<JsValue>` /// This function will consume the current `CompletionRecord` and return a `JsResult<JsValue>`
// NOTE: rustc bug around evaluating destructors that prevents this from being a const function. // NOTE: rustc bug around evaluating destructors that prevents this from being a const function.
// Related issue(s): // Related issue(s):

11
core/engine/src/vm/opcode/await/mod.rs

@ -48,16 +48,6 @@ impl Operation for Await {
let gen = GeneratorContext::from_current(context); let gen = GeneratorContext::from_current(context);
// Even though it would be great to avoid cloning, we need to ensure
// the original async generator has a copy of the context in case it is resumed
// by a `return` or `throw` call instead of a continuation.
if let Some(async_generator) = gen.async_generator_object() {
async_generator
.downcast_mut::<AsyncGenerator>()
.expect("must be async generator")
.context = Some(gen.clone());
}
let captures = Gc::new(Cell::new(Some(gen))); let captures = Gc::new(Cell::new(Some(gen)));
// 3. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures asyncContext and performs the following steps when called: // 3. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures asyncContext and performs the following steps when called:
@ -111,7 +101,6 @@ impl Operation for Await {
// d. Resume the suspended evaluation of asyncContext using ThrowCompletion(reason) as the result of the operation that suspended it. // d. Resume the suspended evaluation of asyncContext using ThrowCompletion(reason) as the result of the operation that suspended it.
// e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context. // e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context.
// f. Return undefined. // f. Return undefined.
let mut gen = captures.take().expect("should only run once"); let mut gen = captures.take().expect("should only run once");
// NOTE: We need to get the object before resuming, since it could clear the stack. // NOTE: We need to get the object before resuming, since it could clear the stack.

24
core/engine/src/vm/opcode/generator/mod.rs

@ -15,7 +15,7 @@ use crate::{
opcode::{Operation, ReThrow}, opcode::{Operation, ReThrow},
CallFrame, CompletionType, CallFrame, CompletionType,
}, },
Context, JsError, JsObject, JsResult, JsValue, Context, JsError, JsObject, JsResult,
}; };
pub(crate) use yield_stm::*; pub(crate) use yield_stm::*;
@ -128,15 +128,17 @@ impl Operation for AsyncGeneratorClose {
let mut gen = generator.borrow_mut(); let mut gen = generator.borrow_mut();
gen.data.state = AsyncGeneratorState::Completed; // e. Assert: If we return here, the async generator either threw an exception or performed either an implicit or explicit return.
gen.data.context = None; // f. Remove acGenContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context.
let next = gen.data.queue.pop_front().expect("must have item in queue"); // g. Set acGenerator.[[AsyncGeneratorState]] to draining-queue.
gen.data.state = AsyncGeneratorState::DrainingQueue;
let return_value = context.vm.get_return_value(); // h. If result is a normal completion, set result to NormalCompletion(undefined).
context.vm.set_return_value(JsValue::undefined()); // i. If result is a return completion, set result to NormalCompletion(result.[[Value]]).
let return_value = context.vm.take_return_value();
let completion = context let result = context
.vm .vm
.pending_exception .pending_exception
.take() .take()
@ -144,10 +146,12 @@ impl Operation for AsyncGeneratorClose {
drop(gen); drop(gen);
AsyncGenerator::complete_step(&next, completion, true, None, context); // j. Perform AsyncGeneratorCompleteStep(acGenerator, result, true).
// TODO: Upgrade to the latest spec when the problem is fixed. AsyncGenerator::complete_step(&generator, result, true, None, context);
AsyncGenerator::resume_next(&generator, context); // k. Perform AsyncGeneratorDrainQueue(acGenerator).
AsyncGenerator::drain_queue(&generator, context);
// l. Return undefined.
Ok(CompletionType::Normal) Ok(CompletionType::Normal)
} }
} }

51
core/engine/src/vm/opcode/generator/yield_stm.rs

@ -36,36 +36,40 @@ impl Operation for AsyncGeneratorYield {
const COST: u8 = 8; const COST: u8 = 8;
fn execute(context: &mut Context) -> JsResult<CompletionType> { fn execute(context: &mut Context) -> JsResult<CompletionType> {
let value = context.vm.pop(); // AsyncGeneratorYield ( value )
// https://tc39.es/ecma262/#sec-asyncgeneratoryield
// 1. Let genContext be the running execution context.
// 2. Assert: genContext is the execution context of a generator.
// 3. Let generator be the value of the Generator component of genContext.
// 4. Assert: GetGeneratorKind() is async.
let async_generator_object = context let async_generator_object = context
.vm .vm
.frame() .frame()
.async_generator_object(&context.vm.stack) .async_generator_object(&context.vm.stack)
.expect("`AsyncGeneratorYield` must only be called inside async generators"); .expect("`AsyncGeneratorYield` must only be called inside async generators");
let completion = Ok(value);
let async_generator_object = async_generator_object let async_generator_object = async_generator_object
.downcast::<AsyncGenerator>() .downcast::<AsyncGenerator>()
.expect("must be async generator object"); .expect("must be async generator object");
let next = async_generator_object
.borrow_mut()
.data
.queue
.pop_front()
.expect("must have item in queue");
// 5. Let completion be NormalCompletion(value).
let value = context.vm.pop();
let completion = Ok(value);
// TODO: 6. Assert: The execution context stack has at least two elements.
// TODO: 7. Let previousContext be the second to top element of the execution context stack. // TODO: 7. Let previousContext be the second to top element of the execution context stack.
AsyncGenerator::complete_step(&next, completion, false, None, context); // TODO: 8. Let previousRealm be previousContext's Realm.
// 9. Perform AsyncGeneratorCompleteStep(generator, completion, false, previousRealm).
AsyncGenerator::complete_step(&async_generator_object, completion, false, None, context);
// TODO: Upgrade to the latest spec when the problem is fixed.
let mut gen = async_generator_object.borrow_mut(); let mut gen = async_generator_object.borrow_mut();
if gen.data.state == AsyncGeneratorState::Executing {
let Some(next) = gen.data.queue.front() else {
gen.data.state = AsyncGeneratorState::SuspendedYield;
context.vm.set_return_value(JsValue::undefined());
return Ok(CompletionType::Yield);
};
// 10. Let queue be generator.[[AsyncGeneratorQueue]].
// 11. If queue is not empty, then
// a. NOTE: Execution continues without suspending the generator.
// b. Let toYield be the first element of queue.
if let Some(next) = gen.data.queue.front() {
// c. Let resumptionValue be Completion(toYield.[[Completion]]).
let resume_kind = match next.completion.clone() { let resume_kind = match next.completion.clone() {
CompletionRecord::Normal(val) => { CompletionRecord::Normal(val) => {
context.vm.push(val); context.vm.push(val);
@ -84,17 +88,20 @@ impl Operation for AsyncGeneratorYield {
context.vm.push(resume_kind); context.vm.push(resume_kind);
// d. Return ? AsyncGeneratorUnwrapYieldResumption(resumptionValue).
return Ok(CompletionType::Normal); return Ok(CompletionType::Normal);
} }
assert!(matches!( // 12. Else,
gen.data.state,
AsyncGeneratorState::AwaitingReturn | AsyncGeneratorState::Completed
));
AsyncGenerator::resume_next(&async_generator_object, context); // a. Set generator.[[AsyncGeneratorState]] to suspended-yield.
gen.data.state = AsyncGeneratorState::SuspendedYield;
async_generator_object.borrow_mut().data.state = AsyncGeneratorState::SuspendedYield; // TODO: b. Remove genContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context.
// TODO: c. Let callerContext be the running execution context.
// d. Resume callerContext passing undefined. If genContext is ever resumed again, let resumptionValue be the Completion Record with which it is resumed.
// e. Assert: If control reaches here, then genContext is the running execution context again.
// f. Return ? AsyncGeneratorUnwrapYieldResumption(resumptionValue).
context.vm.set_return_value(JsValue::undefined()); context.vm.set_return_value(JsValue::undefined());
Ok(CompletionType::Yield) Ok(CompletionType::Yield)
} }

14
test262_config.toml

@ -1,4 +1,4 @@
commit = "3a7a72aef5009eb22117231d40f9a5a66a9a595a" commit = "12307f5c20a4c4211e69823939fd1872212894c5"
[ignored] [ignored]
# Not implemented yet: # Not implemented yet:
@ -50,6 +50,10 @@ features = [
# https://github.com/tc39/proposal-json-parse-with-source # https://github.com/tc39/proposal-json-parse-with-source
"json-parse-with-source", "json-parse-with-source",
# RegExp.escape
# https://github.com/tc39/proposal-regex-escaping
"RegExp.escape",
# https://github.com/tc39/proposal-iterator-helpers # https://github.com/tc39/proposal-iterator-helpers
"iterator-helpers", "iterator-helpers",
@ -57,6 +61,14 @@ features = [
# https://github.com/tc39/proposal-set-methods # https://github.com/tc39/proposal-set-methods
"set-methods", "set-methods",
# Uint8Array Base64
# https://github.com/tc39/proposal-arraybuffer-base64
"uint8array-base64",
# Atomics.pause
# https://github.com/tc39/proposal-atomics-microwait
"Atomics.pause",
### Non-standard ### Non-standard
"caller", "caller",
] ]

17
tests/tester/src/edition.rs

@ -73,6 +73,10 @@ static FEATURE_EDITION: phf::Map<&'static str, SpecEdition> = phf::phf_map! {
// https://github.com/tc39/proposal-json-parse-with-source // https://github.com/tc39/proposal-json-parse-with-source
"json-parse-with-source" => SpecEdition::ESNext, "json-parse-with-source" => SpecEdition::ESNext,
// RegExp.escape
// https://github.com/tc39/proposal-regex-escaping
"RegExp.escape" => SpecEdition::ESNext,
// Regular expression modifiers // Regular expression modifiers
// https://github.com/tc39/proposal-regexp-modifiers // https://github.com/tc39/proposal-regexp-modifiers
"regexp-modifiers" => SpecEdition::ESNext, "regexp-modifiers" => SpecEdition::ESNext,
@ -85,10 +89,6 @@ static FEATURE_EDITION: phf::Map<&'static str, SpecEdition> = phf::phf_map! {
// https://github.com/tc39/proposal-promise-try // https://github.com/tc39/proposal-promise-try
"promise-try" => SpecEdition::ESNext, "promise-try" => SpecEdition::ESNext,
// Set methods
// https://github.com/tc39/proposal-set-methods
"set-methods" => SpecEdition::ESNext,
// Explicit Resource Management // Explicit Resource Management
// https://github.com/tc39/proposal-explicit-resource-management // https://github.com/tc39/proposal-explicit-resource-management
"explicit-resource-management" => SpecEdition::ESNext, "explicit-resource-management" => SpecEdition::ESNext,
@ -107,6 +107,14 @@ static FEATURE_EDITION: phf::Map<&'static str, SpecEdition> = phf::phf_map! {
// test262 special specifier // test262 special specifier
"source-phase-imports-module-source" => SpecEdition::ESNext, "source-phase-imports-module-source" => SpecEdition::ESNext,
// Uint8Array Base64
// https://github.com/tc39/proposal-arraybuffer-base64
"uint8array-base64" => SpecEdition::ESNext,
// Atomics.pause
// https://github.com/tc39/proposal-atomics-microwait
"Atomics.pause" => SpecEdition::ESNext,
// Part of the next ES15 edition // Part of the next ES15 edition
"Atomics.waitAsync" => SpecEdition::ESNext, "Atomics.waitAsync" => SpecEdition::ESNext,
"regexp-v-flag" => SpecEdition::ESNext, "regexp-v-flag" => SpecEdition::ESNext,
@ -115,6 +123,7 @@ static FEATURE_EDITION: phf::Map<&'static str, SpecEdition> = phf::phf_map! {
"resizable-arraybuffer" => SpecEdition::ESNext, "resizable-arraybuffer" => SpecEdition::ESNext,
"promise-with-resolvers" => SpecEdition::ESNext, "promise-with-resolvers" => SpecEdition::ESNext,
"array-grouping" => SpecEdition::ESNext, "array-grouping" => SpecEdition::ESNext,
"set-methods" => SpecEdition::ESNext,
// Standard language features // Standard language features
"AggregateError" => SpecEdition::ES12, "AggregateError" => SpecEdition::ES12,

Loading…
Cancel
Save