Skip to content

Commit

Permalink
Implement new spec changes for AsyncGenerator
Browse files Browse the repository at this point in the history
  • Loading branch information
jedel1043 committed Aug 15, 2024
1 parent 5ee0dc1 commit c06b882
Show file tree
Hide file tree
Showing 7 changed files with 287 additions and 247 deletions.
413 changes: 214 additions & 199 deletions core/engine/src/builtins/async_generator/mod.rs

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions core/engine/src/vm/completion_record.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<JsValue>`
// NOTE: rustc bug around evaluating destructors that prevents this from being a const function.
// Related issue(s):
Expand Down
11 changes: 0 additions & 11 deletions core/engine/src/vm/opcode/await/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<AsyncGenerator>()
.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:
Expand Down Expand Up @@ -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.
Expand Down
24 changes: 14 additions & 10 deletions core/engine/src/vm/opcode/generator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down Expand Up @@ -128,26 +128,30 @@ 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()
.map_or(Ok(return_value), Err);

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)
}
}
Expand Down
51 changes: 29 additions & 22 deletions core/engine/src/vm/opcode/generator/yield_stm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,36 +36,40 @@ impl Operation for AsyncGeneratorYield {
const COST: u8 = 8;

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
.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::<AsyncGenerator>()
.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);
Expand All @@ -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)
}
Expand Down
14 changes: 13 additions & 1 deletion test262_config.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
commit = "3a7a72aef5009eb22117231d40f9a5a66a9a595a"
commit = "12307f5c20a4c4211e69823939fd1872212894c5"

[ignored]
# Not implemented yet:
Expand Down Expand Up @@ -50,13 +50,25 @@ 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",

# Set methods
# 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",
]
Expand Down
17 changes: 13 additions & 4 deletions tests/tester/src/edition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand All @@ -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,
Expand Down

0 comments on commit c06b882

Please sign in to comment.