From 59a7c02fdeb41caec6ec8555b3fb866fb9349913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Juli=C3=A1n=20Espina?= Date: Wed, 11 Sep 2024 21:04:56 +0000 Subject: [PATCH] Backport 0.19 fixes (#3989) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Implement new spec changes for `AsyncGenerator` (#3950) * Implement new spec changes for `AsyncGenerator` * Add panic docs * Add spec edition 15 to the tester (#3957) * Allow dead code for code that is newly detected as unused (#3984) * Allow dead code for code that is newly detected as unused * Fix compile errors with nightly rust * Add missing SAFETY section * Increase safety of `FutexWaiters` --------- Co-authored-by: Theo Paris Co-authored-by: José Julián Espina * Enable CI for release branches (#3987) * Allow warnings when running CI on release branches (#3990) * Bump crates version --------- Co-authored-by: Hans Larsen Co-authored-by: Theo Paris --- .github/workflows/pull_request.yml | 1 + .github/workflows/release.yml | 2 +- .github/workflows/rust.yml | 7 +- .github/workflows/test262.yml | 1 + .github/workflows/webassembly.yml | 4 +- Cargo.lock | 34 +- Cargo.toml | 24 +- README.md | 2 +- .../src/builtins/async_generator/mod.rs | 429 ++++++++++-------- core/engine/src/builtins/atomics/futex.rs | 209 +++++---- core/engine/src/builtins/builder.rs | 4 + 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 | 36 +- tests/tester/src/main.rs | 10 + 18 files changed, 480 insertions(+), 387 deletions(-) diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index f90d5dd520f..555fdfdd0b3 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -4,6 +4,7 @@ on: pull_request: branches: - main + - releases/** jobs: runBenchmark: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b9982f43d81..38b6868c781 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,7 +9,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 env: - RUSTFLAGS: -D warnings + RUSTFLAGS: ${{ github.ref == 'refs/heads/main' && '-D warnings' || '' }} steps: - name: Checkout repository uses: actions/checkout@v4 diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 0da5ff0e49a..5ce7b99008b 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -4,20 +4,23 @@ on: pull_request: branches: - main + - releases/** push: branches: - main + - releases/** merge_group: types: [checks_requested] env: - RUSTFLAGS: -Dwarnings + RUSTFLAGS: ${{ github.ref == 'refs/heads/main' && '-D warnings' || '' }} jobs: coverage: name: Coverage runs-on: ubuntu-latest timeout-minutes: 60 + if: github.ref == 'refs/heads/main' steps: - name: Checkout repository uses: actions/checkout@v4 @@ -149,7 +152,7 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 env: - RUSTDOCFLAGS: -D warnings + RUSTDOCFLAGS: ${{ github.ref == 'refs/heads/main' && '-D warnings' || '' }} steps: - name: Checkout repository uses: actions/checkout@v4 diff --git a/.github/workflows/test262.yml b/.github/workflows/test262.yml index 8d1c55d0c97..fe73b5d3189 100644 --- a/.github/workflows/test262.yml +++ b/.github/workflows/test262.yml @@ -3,6 +3,7 @@ on: pull_request: branches: - main + - releases/** merge_group: types: [checks_requested] diff --git a/.github/workflows/webassembly.yml b/.github/workflows/webassembly.yml index 9ff6e514c4f..da877cc98c2 100644 --- a/.github/workflows/webassembly.yml +++ b/.github/workflows/webassembly.yml @@ -2,9 +2,11 @@ on: pull_request: branches: - main + - releases/** push: branches: - main + - releases/** merge_group: types: [checks_requested] @@ -27,7 +29,7 @@ jobs: timeout-minutes: 60 env: WASM_PACK_PATH: ~/.cargo/bin/wasm-pack - RUSTFLAGS: -D warnings + RUSTFLAGS: ${{ github.ref == 'refs/heads/main' && '-Dwarnings' || '' }} steps: - name: Checkout repository uses: actions/checkout@v4 diff --git a/Cargo.lock b/Cargo.lock index fad72a804a0..e2252b5018e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -309,7 +309,7 @@ dependencies = [ [[package]] name = "boa_ast" -version = "0.19.0" +version = "0.19.1" dependencies = [ "arbitrary", "bitflags 2.6.0", @@ -323,7 +323,7 @@ dependencies = [ [[package]] name = "boa_cli" -version = "0.19.0" +version = "0.19.1" dependencies = [ "boa_engine", "boa_gc", @@ -342,7 +342,7 @@ dependencies = [ [[package]] name = "boa_engine" -version = "0.19.0" +version = "0.19.1" dependencies = [ "arrayvec", "bitflags 2.6.0", @@ -413,7 +413,7 @@ dependencies = [ [[package]] name = "boa_examples" -version = "0.19.0" +version = "0.19.1" dependencies = [ "boa_ast", "boa_engine", @@ -429,7 +429,7 @@ dependencies = [ [[package]] name = "boa_gc" -version = "0.19.0" +version = "0.19.1" dependencies = [ "boa_macros", "boa_profiler", @@ -441,7 +441,7 @@ dependencies = [ [[package]] name = "boa_icu_provider" -version = "0.19.0" +version = "0.19.1" dependencies = [ "icu_provider", "icu_provider_adapters", @@ -451,7 +451,7 @@ dependencies = [ [[package]] name = "boa_interner" -version = "0.19.0" +version = "0.19.1" dependencies = [ "arbitrary", "boa_gc", @@ -467,7 +467,7 @@ dependencies = [ [[package]] name = "boa_interop" -version = "0.19.0" +version = "0.19.1" dependencies = [ "boa_engine", "boa_gc", @@ -477,7 +477,7 @@ dependencies = [ [[package]] name = "boa_macros" -version = "0.19.0" +version = "0.19.1" dependencies = [ "proc-macro2", "quote", @@ -487,7 +487,7 @@ dependencies = [ [[package]] name = "boa_macros_tests" -version = "0.19.0" +version = "0.19.1" dependencies = [ "boa_engine", "trybuild", @@ -495,7 +495,7 @@ dependencies = [ [[package]] name = "boa_parser" -version = "0.19.0" +version = "0.19.1" dependencies = [ "bitflags 2.6.0", "boa_ast", @@ -512,7 +512,7 @@ dependencies = [ [[package]] name = "boa_profiler" -version = "0.19.0" +version = "0.19.1" dependencies = [ "measureme", "once_cell", @@ -521,7 +521,7 @@ dependencies = [ [[package]] name = "boa_runtime" -version = "0.19.0" +version = "0.19.1" dependencies = [ "boa_engine", "boa_gc", @@ -532,7 +532,7 @@ dependencies = [ [[package]] name = "boa_string" -version = "0.19.0" +version = "0.19.1" dependencies = [ "fast-float", "paste", @@ -543,7 +543,7 @@ dependencies = [ [[package]] name = "boa_tester" -version = "0.19.0" +version = "0.19.1" dependencies = [ "bitflags 2.6.0", "boa_engine", @@ -567,7 +567,7 @@ dependencies = [ [[package]] name = "boa_wasm" -version = "0.19.0" +version = "0.19.1" dependencies = [ "boa_engine", "console_error_panic_hook", @@ -1353,7 +1353,7 @@ dependencies = [ [[package]] name = "gen-icu4x-data" -version = "0.19.0" +version = "0.19.1" dependencies = [ "icu_casemap", "icu_collator", diff --git a/Cargo.toml b/Cargo.toml index 09c37e312a0..2bff1cc6b2b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ exclude = [ [workspace.package] edition = "2021" -version = "0.19.0" +version = "0.19.1" rust-version = "1.79.0" authors = ["boa-dev"] repository = "https://github.com/boa-dev/boa" @@ -38,17 +38,17 @@ description = "Boa is a Javascript lexer, parser and compiler written in Rust. C [workspace.dependencies] # Repo Crates -boa_ast = { version = "~0.19.0", path = "core/ast" } -boa_engine = { version = "~0.19.0", path = "core/engine" } -boa_gc = { version = "~0.19.0", path = "core/gc" } -boa_icu_provider = { version = "~0.19.0", path = "core/icu_provider" } -boa_interner = { version = "~0.19.0", path = "core/interner" } -boa_interop = { version = "~0.19.0", path = "core/interop" } -boa_macros = { version = "~0.19.0", path = "core/macros" } -boa_parser = { version = "~0.19.0", path = "core/parser" } -boa_profiler = { version = "~0.19.0", path = "core/profiler" } -boa_runtime = { version = "~0.19.0", path = "core/runtime" } -boa_string = { version = "~0.19.0", path = "core/string" } +boa_ast = { version = "~0.19.1", path = "core/ast" } +boa_engine = { version = "~0.19.1", path = "core/engine" } +boa_gc = { version = "~0.19.1", path = "core/gc" } +boa_icu_provider = { version = "~0.19.1", path = "core/icu_provider" } +boa_interner = { version = "~0.19.1", path = "core/interner" } +boa_interop = { version = "~0.19.1", path = "core/interop" } +boa_macros = { version = "~0.19.1", path = "core/macros" } +boa_parser = { version = "~0.19.1", path = "core/parser" } +boa_profiler = { version = "~0.19.1", path = "core/profiler" } +boa_runtime = { version = "~0.19.1", path = "core/runtime" } +boa_string = { version = "~0.19.1", path = "core/string" } # Shared deps arbitrary = "1" diff --git a/README.md b/README.md index 9bec7c557de..a9c25f402e0 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ Add the below dependency to your `Cargo.toml`: ```toml [dependencies] -boa_engine = "0.19.0" +boa_engine = "0.19.1" ``` Then in `main.rs`, copy the below: diff --git a/core/engine/src/builtins/async_generator/mod.rs b/core/engine/src/builtins/async_generator/mod.rs index 71796006b2e..23b6966fc0a 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 ] )` @@ -340,24 +363,34 @@ 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( - 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 +398,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 +417,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 +428,61 @@ impl AsyncGenerator { .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, + 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 )` @@ -402,21 +490,29 @@ 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, + 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 +521,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 +565,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 +607,77 @@ 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] + /// + /// # 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. + 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/builtins/atomics/futex.rs b/core/engine/src/builtins/atomics/futex.rs index f8be6d6b2ca..b10e239e70b 100644 --- a/core/engine/src/builtins/atomics/futex.rs +++ b/core/engine/src/builtins/atomics/futex.rs @@ -139,12 +139,8 @@ #![allow(clippy::expl_impl_clone_on_copy)] #![allow(unstable_name_collisions)] -use std::{ - cell::UnsafeCell, - sync::{atomic::Ordering, Condvar, Mutex}, -}; +use std::{cell::UnsafeCell, sync::atomic::Ordering}; -use intrusive_collections::{intrusive_adapter, LinkedList, LinkedListLink, UnsafeRef}; use sptr::Strict; use crate::{ @@ -152,107 +148,130 @@ use crate::{ array_buffer::{utils::SliceRef, SharedArrayBuffer}, typed_array::Element, }, - small_map::{Entry, SmallMap}, sys::time::{Duration, Instant}, JsNativeError, JsResult, }; -/// Map of shared data addresses and its corresponding list of agents waiting on that location. -pub(crate) static CRITICAL_SECTION: Mutex = Mutex::new(FutexWaiters { - waiters: SmallMap::new(), -}); - -/// A waiter of a memory address. -#[derive(Debug, Default)] -pub(crate) struct FutexWaiter { - pub(super) link: LinkedListLink, - pub(super) cond_var: Condvar, - pub(super) waiting: bool, - addr: usize, -} +mod sync { + use std::sync::{Condvar, Mutex, MutexGuard}; -intrusive_adapter!(FutexWaiterAdapter = UnsafeRef: FutexWaiter { link: LinkedListLink }); + use intrusive_collections::{intrusive_adapter, LinkedList, LinkedListLink, UnsafeRef}; -/// List of memory addresses and its corresponding list of waiters for that address. -#[derive(Debug)] -pub(crate) struct FutexWaiters { - waiters: SmallMap, 16>, -} + use crate::{ + small_map::{Entry, SmallMap}, + JsNativeError, JsResult, + }; -impl FutexWaiters { - /// Notifies at most `max_count` waiters that are waiting on the address `addr`, and - /// returns the number of waiters that were notified. - /// - /// Equivalent to [`RemoveWaiters`][remove] and [`NotifyWaiter`][notify], but in a single operation. - /// - /// [remove]: https://tc39.es/ecma262/#sec-removewaiters - /// [notify]: https://tc39.es/ecma262/#sec-notifywaiter - pub(crate) fn notify_many(&mut self, addr: usize, max_count: u64) -> u64 { - let Entry::Occupied(mut wl) = self.waiters.entry(addr) else { - return 0; - }; - - for i in 0..max_count { - let Some(elem) = wl.get_mut().pop_front() else { - wl.remove(); - return i; + /// A waiter of a memory address. + #[derive(Debug, Default)] + pub(crate) struct FutexWaiter { + pub(super) link: LinkedListLink, + pub(super) cond_var: Condvar, + pub(super) waiting: bool, + addr: usize, + } + + intrusive_adapter!(FutexWaiterAdapter = UnsafeRef: FutexWaiter { link: LinkedListLink }); + + /// List of memory addresses and its corresponding list of waiters for that address. + #[derive(Debug)] + pub(super) struct FutexWaiters { + waiters: SmallMap, 16>, + } + + // SAFETY: `FutexWaiters` is not constructable outside its `get` method, and it's only exposed by + // a global lock, meaning the inner data of `FutexWaiters` (which includes non-Send pointers) + // can only be accessed by a single thread at once. + unsafe impl Send for FutexWaiters {} + + impl FutexWaiters { + /// Gets the map of all shared data addresses and its corresponding list of agents waiting on that location. + pub(super) fn get() -> JsResult> { + static CRITICAL_SECTION: Mutex = Mutex::new(FutexWaiters { + waiters: SmallMap::new(), + }); + + CRITICAL_SECTION.lock().map_err(|_| { + JsNativeError::typ() + .with_message("failed to synchronize with the agent cluster") + .into() + }) + } + + /// Notifies at most `max_count` waiters that are waiting on the address `addr`, and + /// returns the number of waiters that were notified. + /// + /// Equivalent to [`RemoveWaiters`][remove] and [`NotifyWaiter`][notify], but in a single operation. + /// + /// [remove]: https://tc39.es/ecma262/#sec-removewaiters + /// [notify]: https://tc39.es/ecma262/#sec-notifywaiter + pub(super) fn notify_many(&mut self, addr: usize, max_count: u64) -> u64 { + let Entry::Occupied(mut wl) = self.waiters.entry(addr) else { + return 0; }; - elem.cond_var.notify_one(); + for i in 0..max_count { + let Some(elem) = wl.get_mut().pop_front() else { + wl.remove(); + return i; + }; - // SAFETY: all elements of the waiters list are guaranteed to be valid. - unsafe { - (*UnsafeRef::into_raw(elem)).waiting = false; + elem.cond_var.notify_one(); + + // SAFETY: all elements of the waiters list are guaranteed to be valid. + unsafe { + (*UnsafeRef::into_raw(elem)).waiting = false; + } } - } - if wl.get().is_empty() { - wl.remove(); - } + if wl.get().is_empty() { + wl.remove(); + } - max_count - } + max_count + } - /// # Safety - /// - /// - `node` must NOT be linked to an existing waiter list. - /// - `node` must always point to a valid instance of `FutexWaiter` until `node` is - /// removed from its linked list. This can happen by either `remove_waiter` or `notify_many`. - pub(crate) unsafe fn add_waiter(&mut self, node: *mut FutexWaiter, addr: usize) { - // SAFETY: `node` must point to a valid instance. - let node = unsafe { - debug_assert!(!(*node).link.is_linked()); - (*node).waiting = true; - (*node).addr = addr; - UnsafeRef::from_raw(node) - }; - - self.waiters - .entry(addr) - .or_insert_with(|| LinkedList::new(FutexWaiterAdapter::new())) - .push_back(node); - } + /// # Safety + /// + /// - `node` must NOT be linked to an existing waiter list. + /// - `node` must always point to a valid instance of `FutexWaiter` until `node` is + /// removed from its linked list. This can happen by either `remove_waiter` or `notify_many`. + pub(super) unsafe fn add_waiter(&mut self, node: *mut FutexWaiter, addr: usize) { + // SAFETY: `node` must point to a valid instance. + let node = unsafe { + debug_assert!(!(*node).link.is_linked()); + (*node).waiting = true; + (*node).addr = addr; + UnsafeRef::from_raw(node) + }; - /// # Safety - /// - /// - `node` must point to a valid instance of `FutexWaiter`. - /// - `node` must be inside the wait list associated with `node.addr`. - pub(crate) unsafe fn remove_waiter(&mut self, node: *mut FutexWaiter) { - // SAFETY: `node` must point to a valid instance. - let addr = unsafe { (*node).addr }; - - let mut wl = match self.waiters.entry(addr) { - Entry::Occupied(wl) => wl, - Entry::Vacant(_) => return, - }; - - // SAFETY: `node` must be inside the wait list associated with `node.addr`. - unsafe { - wl.get_mut().cursor_mut_from_ptr(node).remove(); + self.waiters + .entry(addr) + .or_insert_with(|| LinkedList::new(FutexWaiterAdapter::new())) + .push_back(node); } - if wl.get().is_empty() { - wl.remove(); + /// # Safety + /// + /// - `node` must point to a valid instance of `FutexWaiter`. + /// - `node` must be inside the wait list associated with `node.addr`. + pub(super) unsafe fn remove_waiter(&mut self, node: *mut FutexWaiter) { + // SAFETY: `node` must point to a valid instance. + let addr = unsafe { (*node).addr }; + + let mut wl = match self.waiters.entry(addr) { + Entry::Occupied(wl) => wl, + Entry::Vacant(_) => return, + }; + + // SAFETY: `node` must be inside the wait list associated with `node.addr`. + unsafe { + wl.get_mut().cursor_mut_from_ptr(node).remove(); + } + + if wl.get().is_empty() { + wl.remove(); + } } } } @@ -281,10 +300,7 @@ pub(super) unsafe fn wait( // 10. Let block be buffer.[[ArrayBufferData]]. // 11. Let WL be GetWaiterList(block, indexedPosition). // 12. Perform EnterCriticalSection(WL). - let mut waiters = CRITICAL_SECTION.lock().map_err(|_| { - // avoids exposing internals of our implementation. - JsNativeError::typ().with_message("failed to synchronize with the agent cluster") - })?; + let mut waiters = sync::FutexWaiters::get()?; let time_info = timeout.map(|timeout| (Instant::now(), timeout)); @@ -307,7 +323,7 @@ pub(super) unsafe fn wait( // 17. Perform AddWaiter(WL, W). // ensure we can have aliased pointers to the waiter in a sound way. - let waiter = UnsafeCell::new(FutexWaiter::default()); + let waiter = UnsafeCell::new(sync::FutexWaiter::default()); let waiter_ptr = waiter.get(); // SAFETY: waiter is valid and we call `remove_node` below. @@ -385,10 +401,7 @@ pub(super) fn notify(buffer: &SharedArrayBuffer, offset: usize, count: u64) -> J // 7. Let WL be GetWaiterList(block, indexedPosition). // 8. Perform EnterCriticalSection(WL). - let mut waiters = CRITICAL_SECTION.lock().map_err(|_| { - // avoids exposing internals of our implementation. - JsNativeError::typ().with_message("failed to synchronize with the agent cluster") - })?; + let mut waiters = sync::FutexWaiters::get()?; // 9. Let S be RemoveWaiters(WL, c). // 10. For each element W of S, do diff --git a/core/engine/src/builtins/builder.rs b/core/engine/src/builtins/builder.rs index 610a910db33..459a5104d9a 100644 --- a/core/engine/src/builtins/builder.rs +++ b/core/engine/src/builtins/builder.rs @@ -16,6 +16,8 @@ use crate::{ use super::{function::ConstructorKind, BuiltInConstructor, IntrinsicObject}; /// Marker for a constructor function. +// TODO: Remove this marker and use `Constructor` directly. +#[allow(dead_code)] pub(crate) struct Constructor { prototype: JsObject, inherits: JsPrototype, @@ -23,6 +25,8 @@ pub(crate) struct Constructor { } /// Marker for a constructor function without a custom prototype for its instances. +// TODO: Remove this marker and use `ConstructorNoProto` directly. +#[allow(dead_code)] pub(crate) struct ConstructorNoProto; /// Marker for an ordinary function. 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..110169dd9b9 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,19 +107,20 @@ static FEATURE_EDITION: phf::Map<&'static str, SpecEdition> = phf::phf_map! { // test262 special specifier "source-phase-imports-module-source" => SpecEdition::ESNext, - // Part of the next ES15 edition - "Atomics.waitAsync" => SpecEdition::ESNext, - "regexp-v-flag" => SpecEdition::ESNext, - "String.prototype.isWellFormed" => SpecEdition::ESNext, - "String.prototype.toWellFormed" => SpecEdition::ESNext, - "resizable-arraybuffer" => SpecEdition::ESNext, - "promise-with-resolvers" => SpecEdition::ESNext, - "array-grouping" => 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, // Standard language features "AggregateError" => SpecEdition::ES12, + "Atomics.waitAsync" => SpecEdition::ES15, "align-detached-buffer-semantics-with-web-reality" => SpecEdition::ES12, "arbitrary-module-namespace-names" => SpecEdition::ES13, + "array-grouping" => SpecEdition::ES15, "ArrayBuffer" => SpecEdition::ES6, "array-find-from-last" => SpecEdition::ES14, "Array.prototype.at" => SpecEdition::ES13, @@ -207,6 +208,7 @@ static FEATURE_EDITION: phf::Map<&'static str, SpecEdition> = phf::phf_map! { "Promise.any" => SpecEdition::ES12, "Promise.prototype.finally" => SpecEdition::ES9, "Proxy" => SpecEdition::ES6, + "promise-with-resolvers" => SpecEdition::ES15, "proxy-missing-checks" => SpecEdition::ES6, "Reflect" => SpecEdition::ES6, "Reflect.construct" => SpecEdition::ES6, @@ -217,6 +219,8 @@ static FEATURE_EDITION: phf::Map<&'static str, SpecEdition> = phf::phf_map! { "regexp-match-indices" => SpecEdition::ES13, "regexp-named-groups" => SpecEdition::ES9, "regexp-unicode-property-escapes" => SpecEdition::ES9, + "regexp-v-flag" => SpecEdition::ES15, + "resizable-arraybuffer" => SpecEdition::ES15, "rest-parameters" => SpecEdition::ES6, "Set" => SpecEdition::ES6, "SharedArrayBuffer" => SpecEdition::ES8, @@ -225,10 +229,13 @@ static FEATURE_EDITION: phf::Map<&'static str, SpecEdition> = phf::phf_map! { "String.prototype.at" => SpecEdition::ES13, "String.prototype.endsWith" => SpecEdition::ES6, "String.prototype.includes" => SpecEdition::ES6, + "String.prototype.isWellFormed" => SpecEdition::ES15, "String.prototype.matchAll" => SpecEdition::ES11, "String.prototype.replaceAll" => SpecEdition::ES12, + "String.prototype.toWellFormed" => SpecEdition::ES15, "String.prototype.trimEnd" => SpecEdition::ES10, "String.prototype.trimStart" => SpecEdition::ES10, + "set-methods" => SpecEdition::ES15, "super" => SpecEdition::ES6, "Symbol" => SpecEdition::ES6, "symbols-as-weakmap-keys" => SpecEdition::ES14, @@ -326,6 +333,10 @@ pub(crate) enum SpecEdition { /// /// ES14, + /// ECMAScript 15th Edition + /// + /// + ES15, /// The edition being worked on right now. /// /// A draft is currently available [here](https://tc39.es/ecma262). @@ -389,6 +400,7 @@ impl SpecEdition { Self::ES12, Self::ES13, Self::ES14, + Self::ES15, Self::ESNext, ] .into_iter() diff --git a/tests/tester/src/main.rs b/tests/tester/src/main.rs index 20876a309ba..bc31822e352 100644 --- a/tests/tester/src/main.rs +++ b/tests/tester/src/main.rs @@ -606,6 +606,7 @@ struct VersionedStats { es12: Statistics, es13: Statistics, es14: Statistics, + es15: Statistics, } impl<'de> Deserialize<'de> for VersionedStats { @@ -626,6 +627,8 @@ impl<'de> Deserialize<'de> for VersionedStats { es13: Statistics, #[serde(default)] es14: Option, + #[serde(default)] + es15: Option, } let inner = Inner::deserialize(deserializer)?; @@ -641,8 +644,10 @@ impl<'de> Deserialize<'de> for VersionedStats { es12, es13, es14, + es15, } = inner; let es14 = es14.unwrap_or(es13); + let es15 = es15.unwrap_or(es14); Ok(Self { es5, @@ -655,6 +660,7 @@ impl<'de> Deserialize<'de> for VersionedStats { es12, es13, es14, + es15, }) } } @@ -684,6 +690,7 @@ impl VersionedStats { SpecEdition::ES12 => self.es12, SpecEdition::ES13 => self.es13, SpecEdition::ES14 => self.es14, + SpecEdition::ES15 => self.es15, SpecEdition::ESNext => return None, }; Some(stats) @@ -703,6 +710,7 @@ impl VersionedStats { SpecEdition::ES12 => &mut self.es12, SpecEdition::ES13 => &mut self.es13, SpecEdition::ES14 => &mut self.es14, + SpecEdition::ES15 => &mut self.es15, SpecEdition::ESNext => return None, }; Some(stats) @@ -724,6 +732,7 @@ impl Add for VersionedStats { es12: self.es12 + rhs.es12, es13: self.es13 + rhs.es13, es14: self.es14 + rhs.es14, + es15: self.es15 + rhs.es15, } } } @@ -740,6 +749,7 @@ impl AddAssign for VersionedStats { self.es12 += rhs.es12; self.es13 += rhs.es13; self.es14 += rhs.es14; + self.es15 += rhs.es15; } }