From 699a842b1cd2c9f2830171d73bbf369fd8ecad23 Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Wed, 14 Aug 2024 01:49:44 -0600 Subject: [PATCH 1/2] Implement new spec changes for `AsyncGenerator` --- .../src/builtins/async_generator/mod.rs | 413 +++++++++--------- core/engine/src/vm/completion_record.rs | 4 + core/engine/src/vm/opcode/await/mod.rs | 11 - core/engine/src/vm/opcode/generator/mod.rs | 24 +- .../src/vm/opcode/generator/yield_stm.rs | 51 ++- test262_config.toml | 14 +- tests/tester/src/edition.rs | 17 +- 7 files changed, 287 insertions(+), 247 deletions(-) diff --git a/core/engine/src/builtins/async_generator/mod.rs b/core/engine/src/builtins/async_generator/mod.rs index 71796006b2e..07626a5325b 100644 --- a/core/engine/src/builtins/async_generator/mod.rs +++ b/core/engine/src/builtins/async_generator/mod.rs @@ -37,7 +37,7 @@ pub(crate) enum AsyncGeneratorState { SuspendedStart, SuspendedYield, Executing, - AwaitingReturn, + DrainingQueue, Completed, } @@ -168,7 +168,15 @@ impl AsyncGenerator { let completion = CompletionRecord::Normal(args.get_or_undefined(0).clone()); // 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]]. Ok(promise_capability.promise().clone().into()) @@ -212,10 +220,29 @@ impl AsyncGenerator { // 5. Let completion be Completion Record { [[Type]]: return, [[Value]]: value, [[Target]]: empty }. 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). - 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]]. Ok(promise_capability.promise().clone().into()) @@ -294,12 +321,16 @@ impl AsyncGenerator { CompletionRecord::Throw(JsError::from_opaque(args.get_or_undefined(0).clone())); // 9. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability). - Self::enqueue( - &generator, - completion.clone(), - promise_capability.clone(), - context, - ); + Self::enqueue(&generator, completion.clone(), promise_capability.clone()); + + // 10. If state is suspended-yield, then + if state == AsyncGeneratorState::SuspendedYield { + // 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]]. Ok(promise_capability.promise().clone().into()) @@ -315,7 +346,6 @@ impl AsyncGenerator { generator: &JsObject, completion: CompletionRecord, promise_capability: PromiseCapability, - context: &mut Context, ) { let mut gen = generator.borrow_mut(); // 1. Let request be AsyncGeneratorRequest { [[Completion]]: completion, [[Capability]]: promiseCapability }. @@ -326,13 +356,6 @@ impl AsyncGenerator { // 2. Append request to the end of generator.[[AsyncGeneratorQueue]]. 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 ] )` @@ -342,22 +365,28 @@ impl AsyncGenerator { /// /// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorcompletestep pub(crate) fn complete_step( - next: &AsyncGeneratorRequest, + generator: &JsObject, completion: JsResult, done: bool, realm: Option, context: &mut Context, ) { - // 1. Let queue be generator.[[AsyncGeneratorQueue]]. - // 2. Assert: queue is not empty. - // 3. Let next be the first element of queue. - // 4. Remove the first element from queue. - // 5. Let promiseCapability be next.[[Capability]]. + // 1. Assert: generator.[[AsyncGeneratorQueue]] is not empty. + // 2. Let next be the first element of generator.[[AsyncGeneratorQueue]]. + // 3. Remove the first element from generator.[[AsyncGeneratorQueue]]. + let next = generator + .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; - // 6. Let value be completion.[[Value]]. + // 5. Let value be completion.[[Value]]. match completion { - // 7. If completion.[[Type]] is throw, then + // 6. If completion is a throw completion, then Err(e) => { // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « value »). promise_capability @@ -365,17 +394,17 @@ impl AsyncGenerator { .call(&JsValue::undefined(), &[e.to_opaque(context)], context) .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 let iterator_result = if let Some(realm) = realm { // i. Let oldRealm be the running execution context's Realm. // ii. Set the running execution context's Realm to 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); // iv. Set the running execution context's Realm to oldRealm. @@ -384,7 +413,7 @@ impl AsyncGenerator { iterator_result } else { // c. Else, - // i. Let iteratorResult be CreateIterResultObject(value, done). + // i. Let iteratorResult be CreateIteratorResultObject(value, done). create_iter_result_object(value, done, context) }; @@ -395,6 +424,57 @@ impl AsyncGenerator { .expect("cannot fail per spec"); } } + // 8. Return unused. + } + + /// `AsyncGeneratorResume ( generator, completion )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorresume + pub(crate) fn resume( + generator: &JsObject, + completion: CompletionRecord, + context: &mut Context, + ) { + // 1. Assert: generator.[[AsyncGeneratorState]] is either suspendedStart or suspendedYield. + 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 )` @@ -404,19 +484,23 @@ impl AsyncGenerator { /// /// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorawaitreturn pub(crate) fn await_return( - generator: JsObject, + generator: &JsObject, value: JsValue, context: &mut Context, ) { - // 1. Let queue be generator.[[AsyncGeneratorQueue]]. - // 2. Assert: queue is not empty. - // 3. Let next be the first element of queue. - // 4. Let completion be Completion(next.[[Completion]]). + // 1. Assert: generator.[[AsyncGeneratorState]] is draining-queue. + assert_eq!( + generator.borrow().data.state, + AsyncGeneratorState::DrainingQueue + ); - // Note: The spec is currently broken here. - // See: https://github.com/tc39/ecma262/pull/2683 + // 2. Let queue be generator.[[AsyncGeneratorQueue]]. + // 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( &context.intrinsics().constructors().promise().constructor(), value, @@ -425,43 +509,39 @@ impl AsyncGenerator { let promise = match promise_completion { Ok(value) => value, - Err(value) => { - let next = { - let mut gen = generator.borrow_mut(); - gen.data.state = AsyncGeneratorState::Completed; - gen.data.context = None; - gen.data.queue.pop_front().expect("queue must not be empty") - }; - Self::complete_step(&next, Err(value), true, None, context); - Self::resume_next(&generator, context); + // 8. If promiseCompletion is an abrupt completion, then + Err(e) => { + // a. Perform AsyncGeneratorCompleteStep(generator, promiseCompletion, true). + Self::complete_step(generator, Err(e), true, None, context); + // b. Perform AsyncGeneratorDrainQueue(generator). + Self::drain_queue(generator, context); + // c. Return unused. return; } }; - // 7. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures generator and performs the following steps when called: - // 8. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »). + // 9. Assert: promiseCompletion is a normal completion. + // 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( context.realm(), NativeFunction::from_copy_closure_with_captures( |_this, args, generator, context| { - let next = { - let mut gen = generator.borrow_mut(); - - // a. Set generator.[[AsyncGeneratorState]] to completed. - gen.data.state = AsyncGeneratorState::Completed; - gen.data.context = None; - - gen.data.queue.pop_front().expect("must have one entry") - }; + // a. Assert: generator.[[AsyncGeneratorState]] is draining-queue. + assert_eq!( + generator.borrow().data.state, + AsyncGeneratorState::DrainingQueue + ); // b. Let result be NormalCompletion(value). let result = Ok(args.get_or_undefined(0).clone()); // 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). - Self::resume_next(generator, context); + Self::drain_queue(generator, context); // e. Return undefined. Ok(JsValue::undefined()) @@ -473,42 +553,39 @@ impl AsyncGenerator { .length(1) .build(); - // 9. 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, "", « »). + // 13. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures generator and performs the following steps when called: + // 14. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »). let on_rejected = FunctionObjectBuilder::new( context.realm(), NativeFunction::from_copy_closure_with_captures( |_this, args, generator, context| { - let next = { - let mut gen = generator.borrow_mut(); - - // a. Set generator.[[AsyncGeneratorState]] to completed. - gen.data.state = AsyncGeneratorState::Completed; - gen.data.context = None; - - gen.data.queue.pop_front().expect("must have one entry") - }; + // a. Assert: generator.[[AsyncGeneratorState]] is draining-queue. + assert_eq!( + generator.borrow().data.state, + AsyncGeneratorState::DrainingQueue + ); // b. Let result be ThrowCompletion(reason). let result = Err(JsError::from_opaque(args.get_or_undefined(0).clone())); // 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). - Self::resume_next(generator, context); + Self::drain_queue(generator, context); // e. Return undefined. Ok(JsValue::undefined()) }, - generator, + generator.clone(), ), ) .name(js_string!("")) .length(1) .build(); - // 11. Perform PerformPromiseThen(promise, onFulfilled, onRejected). + // 15. Perform PerformPromiseThen(promise, onFulfilled, onRejected). + // 16. Return unused. Promise::perform_promise_then( &promise, Some(on_fulfilled), @@ -518,135 +595,73 @@ impl AsyncGenerator { ); } - /// [`AsyncGeneratorResumeNext ( generator )`][spec] + /// `AsyncGeneratorDrainQueue ( generator )` /// - /// [spec]: https://262.ecma-international.org/12.0/#sec-asyncgeneratorresumenext - pub(crate) fn resume_next(generator: &JsObject, context: &mut Context) { - // 1. Assert: generator is an AsyncGenerator instance. - let mut gen = generator.borrow_mut(); - // 2. Let state be generator.[[AsyncGeneratorState]]. - match gen.data.state { - // 3. Assert: state is not executing. - AsyncGeneratorState::Executing => panic!("3. Assert: state is not executing."), - // 4. If state is awaiting-return, return undefined. - AsyncGeneratorState::AwaitingReturn => return, - _ => {} - } + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratordrainqueue + pub(crate) fn drain_queue(generator: &JsObject, context: &mut Context) { + // 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 { + // 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; - }; - // 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); + } + + // 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 + .queue + .front() + .expect("must have entry") + .completion + .clone(); + + // 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; + } } } - // 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. - 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 - .data - .queue - .pop_front() - .expect("already have a reference to the front"); - drop(gen); - AsyncGenerator::complete_step(&next, Err(e), true, None, context); - // 3. Return undefined. - return AsyncGenerator::resume_next(generator, context); - } - _ => {} } - // 12. Assert: state is either suspendedStart or suspendedYield. - 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. + // 6. Return unused. } } diff --git a/core/engine/src/vm/completion_record.rs b/core/engine/src/vm/completion_record.rs index 181347487d1..18b45d1213f 100644 --- a/core/engine/src/vm/completion_record.rs +++ b/core/engine/src/vm/completion_record.rs @@ -29,6 +29,10 @@ unsafe impl Trace for CompletionRecord { // ---- `CompletionRecord` methods ---- 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` // NOTE: rustc bug around evaluating destructors that prevents this from being a const function. // Related issue(s): diff --git a/core/engine/src/vm/opcode/await/mod.rs b/core/engine/src/vm/opcode/await/mod.rs index 1211b35581d..ec480a7d12f 100644 --- a/core/engine/src/vm/opcode/await/mod.rs +++ b/core/engine/src/vm/opcode/await/mod.rs @@ -48,16 +48,6 @@ impl Operation for Await { 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::() - .expect("must be async generator") - .context = Some(gen.clone()); - } - 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: @@ -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. // 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. - 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. diff --git a/core/engine/src/vm/opcode/generator/mod.rs b/core/engine/src/vm/opcode/generator/mod.rs index d5a47e22a05..d7280244e41 100644 --- a/core/engine/src/vm/opcode/generator/mod.rs +++ b/core/engine/src/vm/opcode/generator/mod.rs @@ -15,7 +15,7 @@ use crate::{ opcode::{Operation, ReThrow}, CallFrame, CompletionType, }, - Context, JsError, JsObject, JsResult, JsValue, + Context, JsError, JsObject, JsResult, }; pub(crate) use yield_stm::*; @@ -128,15 +128,17 @@ impl Operation for AsyncGeneratorClose { let mut gen = generator.borrow_mut(); - gen.data.state = AsyncGeneratorState::Completed; - gen.data.context = None; + // e. Assert: If we return here, the async generator either threw an exception or performed either an implicit or explicit return. + // 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(); - context.vm.set_return_value(JsValue::undefined()); + // h. If result is a normal completion, set result to NormalCompletion(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 .pending_exception .take() @@ -144,10 +146,12 @@ impl Operation for AsyncGeneratorClose { drop(gen); - AsyncGenerator::complete_step(&next, completion, true, None, context); - // TODO: Upgrade to the latest spec when the problem is fixed. - AsyncGenerator::resume_next(&generator, context); + // j. Perform AsyncGeneratorCompleteStep(acGenerator, result, true). + AsyncGenerator::complete_step(&generator, result, true, None, context); + // k. Perform AsyncGeneratorDrainQueue(acGenerator). + AsyncGenerator::drain_queue(&generator, context); + // l. Return undefined. Ok(CompletionType::Normal) } } diff --git a/core/engine/src/vm/opcode/generator/yield_stm.rs b/core/engine/src/vm/opcode/generator/yield_stm.rs index 27cc18335f0..d83b7418f36 100644 --- a/core/engine/src/vm/opcode/generator/yield_stm.rs +++ b/core/engine/src/vm/opcode/generator/yield_stm.rs @@ -36,36 +36,40 @@ impl Operation for AsyncGeneratorYield { const COST: u8 = 8; fn execute(context: &mut Context) -> JsResult { - 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 .vm .frame() .async_generator_object(&context.vm.stack) .expect("`AsyncGeneratorYield` must only be called inside async generators"); - let completion = Ok(value); let async_generator_object = async_generator_object .downcast::() .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. - 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(); - 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() { CompletionRecord::Normal(val) => { context.vm.push(val); @@ -84,17 +88,20 @@ impl Operation for AsyncGeneratorYield { context.vm.push(resume_kind); + // d. Return ? AsyncGeneratorUnwrapYieldResumption(resumptionValue). return Ok(CompletionType::Normal); } - assert!(matches!( - gen.data.state, - AsyncGeneratorState::AwaitingReturn | AsyncGeneratorState::Completed - )); + // 12. Else, - 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()); Ok(CompletionType::Yield) } diff --git a/test262_config.toml b/test262_config.toml index b6678b3e185..1f3d0c9e621 100644 --- a/test262_config.toml +++ b/test262_config.toml @@ -1,4 +1,4 @@ -commit = "3a7a72aef5009eb22117231d40f9a5a66a9a595a" +commit = "12307f5c20a4c4211e69823939fd1872212894c5" [ignored] # Not implemented yet: @@ -50,6 +50,10 @@ features = [ # https://github.com/tc39/proposal-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 "iterator-helpers", @@ -57,6 +61,14 @@ features = [ # https://github.com/tc39/proposal-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 "caller", ] diff --git a/tests/tester/src/edition.rs b/tests/tester/src/edition.rs index a1a289d2a8a..d0cbf1c55ad 100644 --- a/tests/tester/src/edition.rs +++ b/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 "json-parse-with-source" => SpecEdition::ESNext, + // RegExp.escape + // https://github.com/tc39/proposal-regex-escaping + "RegExp.escape" => SpecEdition::ESNext, + // Regular expression modifiers // https://github.com/tc39/proposal-regexp-modifiers "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 "promise-try" => SpecEdition::ESNext, - // Set methods - // https://github.com/tc39/proposal-set-methods - "set-methods" => SpecEdition::ESNext, - // Explicit Resource Management // https://github.com/tc39/proposal-explicit-resource-management "explicit-resource-management" => SpecEdition::ESNext, @@ -107,6 +107,14 @@ static FEATURE_EDITION: phf::Map<&'static str, SpecEdition> = phf::phf_map! { // test262 special specifier "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 "Atomics.waitAsync" => 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, "promise-with-resolvers" => SpecEdition::ESNext, "array-grouping" => SpecEdition::ESNext, + "set-methods" => SpecEdition::ESNext, // Standard language features "AggregateError" => SpecEdition::ES12, From 8848fd1bd6ef980cb8462524d83bd952cdb680f4 Mon Sep 17 00:00:00 2001 From: jedel1043 Date: Sat, 17 Aug 2024 19:09:01 -0600 Subject: [PATCH 2/2] Add panic docs --- .../engine/src/builtins/async_generator/mod.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/core/engine/src/builtins/async_generator/mod.rs b/core/engine/src/builtins/async_generator/mod.rs index 07626a5325b..23b6966fc0a 100644 --- a/core/engine/src/builtins/async_generator/mod.rs +++ b/core/engine/src/builtins/async_generator/mod.rs @@ -363,6 +363,10 @@ impl AsyncGenerator { /// More information: /// - [ECMAScript reference][spec] /// + /// # Panics + /// + /// Panics if the async generator request queue of `generator` is empty. + /// /// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorcompletestep pub(crate) fn complete_step( generator: &JsObject, @@ -432,13 +436,17 @@ impl AsyncGenerator { /// 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, completion: CompletionRecord, context: &mut Context, ) { - // 1. Assert: generator.[[AsyncGeneratorState]] is either suspendedStart or suspendedYield. + // 1. Assert: generator.[[AsyncGeneratorState]] is either suspended-start or suspended-yield. assert!(matches!( generator.borrow().data.state, AsyncGeneratorState::SuspendedStart | AsyncGeneratorState::SuspendedYield @@ -482,6 +490,10 @@ impl AsyncGenerator { /// More information: /// - [ECMAScript reference][spec] /// + /// # Panics + /// + /// Panics if `generator` is not in the `DrainingQueue` state. + /// /// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorawaitreturn pub(crate) fn await_return( generator: &JsObject, @@ -600,6 +612,10 @@ impl AsyncGenerator { /// More information: /// - [ECMAScript reference][spec] /// + /// # Panics + /// + /// Panics if `generator` is not in the `DrainingQueue` state. + /// /// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratordrainqueue pub(crate) fn drain_queue(generator: &JsObject, context: &mut Context) { // 1. Assert: generator.[[AsyncGeneratorState]] is draining-queue.