diff --git a/boa_engine/src/builtins/async_generator/mod.rs b/boa_engine/src/builtins/async_generator/mod.rs new file mode 100644 index 0000000000..021141007a --- /dev/null +++ b/boa_engine/src/builtins/async_generator/mod.rs @@ -0,0 +1,754 @@ +//! This module implements the global `AsyncGenerator` object. +//! +//! More information: +//! - [ECMAScript reference][spec] +//! +//! [spec]: https://tc39.es/ecma262/#sec-asyncgenerator-objects + +use crate::{ + builtins::{ + generator::GeneratorContext, iterable::create_iter_result_object, + promise::if_abrupt_reject_promise, promise::PromiseCapability, BuiltIn, JsArgs, Promise, + }, + object::{ConstructorBuilder, FunctionBuilder, JsObject, ObjectData}, + property::{Attribute, PropertyDescriptor}, + symbol::WellKnownSymbols, + value::JsValue, + vm::GeneratorResumeKind, + Context, JsResult, +}; +use boa_gc::{Cell, Finalize, Gc, Trace}; +use boa_profiler::Profiler; +use std::collections::VecDeque; + +/// Indicates the state of an async generator. +#[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) enum AsyncGeneratorState { + Undefined, + SuspendedStart, + SuspendedYield, + Executing, + AwaitingReturn, + Completed, +} + +/// `AsyncGeneratorRequest Records` +/// +/// More information: +/// - [ECMAScript reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorrequest-records +#[derive(Debug, Clone, Finalize, Trace)] +pub(crate) struct AsyncGeneratorRequest { + /// The `[[Completion]]` slot. + pub(crate) completion: (JsResult, bool), + + /// The `[[Capability]]` slot. + capability: PromiseCapability, +} + +/// The internal representation on an `AsyncGenerator` object. +#[derive(Debug, Clone, Finalize, Trace)] +pub struct AsyncGenerator { + /// The `[[AsyncGeneratorState]]` internal slot. + #[unsafe_ignore_trace] + pub(crate) state: AsyncGeneratorState, + + /// The `[[AsyncGeneratorContext]]` internal slot. + pub(crate) context: Option>>, + + /// The `[[AsyncGeneratorQueue]]` internal slot. + pub(crate) queue: VecDeque, +} + +impl BuiltIn for AsyncGenerator { + const NAME: &'static str = "AsyncGenerator"; + + fn init(context: &mut Context) -> Option { + let _timer = Profiler::global().start_event(Self::NAME, "init"); + + let iterator_prototype = context + .intrinsics() + .objects() + .iterator_prototypes() + .async_iterator_prototype(); + + let generator_function_prototype = context + .intrinsics() + .constructors() + .async_generator_function() + .prototype(); + + ConstructorBuilder::with_standard_constructor( + context, + Self::constructor, + context + .intrinsics() + .constructors() + .async_generator() + .clone(), + ) + .name(Self::NAME) + .length(0) + .property( + WellKnownSymbols::to_string_tag(), + Self::NAME, + Attribute::READONLY | Attribute::NON_ENUMERABLE | Attribute::CONFIGURABLE, + ) + .method(Self::next, "next", 1) + .method(Self::r#return, "return", 1) + .method(Self::throw, "throw", 1) + .inherit(iterator_prototype) + .build(); + + context + .intrinsics() + .constructors() + .async_generator() + .prototype + .insert_property( + "constructor", + PropertyDescriptor::builder() + .value(generator_function_prototype) + .writable(false) + .enumerable(false) + .configurable(true), + ); + + None + } +} + +impl AsyncGenerator { + #[allow(clippy::unnecessary_wraps)] + pub(crate) fn constructor( + _: &JsValue, + _: &[JsValue], + context: &mut Context, + ) -> JsResult { + let prototype = context + .intrinsics() + .constructors() + .async_generator() + .prototype(); + + let this = JsObject::from_proto_and_data( + prototype, + ObjectData::async_generator(Self { + state: AsyncGeneratorState::Undefined, + context: None, + queue: VecDeque::new(), + }), + ); + + Ok(this.into()) + } + + /// `AsyncGenerator.prototype.next ( value )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-asyncgenerator-prototype-next + pub(crate) fn next( + this: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { + // 1. Let generator be the this value. + let generator = this; + + // 2. Let promiseCapability be ! NewPromiseCapability(%Promise%). + let promise_capability = PromiseCapability::new( + &context + .intrinsics() + .constructors() + .promise() + .constructor() + .into(), + context, + ) + .expect("cannot fail with promise constructor"); + + // 3. Let result be Completion(AsyncGeneratorValidate(generator, empty)). + // 4. IfAbruptRejectPromise(result, promiseCapability). + let generator_object = generator.as_object().ok_or_else(|| { + context.construct_type_error("generator resumed on non generator object") + }); + if_abrupt_reject_promise!(generator_object, promise_capability, context); + let mut generator_obj_mut = generator_object.borrow_mut(); + let generator = generator_obj_mut.as_async_generator_mut().ok_or_else(|| { + context.construct_type_error("generator resumed on non generator object") + }); + if_abrupt_reject_promise!(generator, promise_capability, context); + + // 5. Let state be generator.[[AsyncGeneratorState]]. + let state = generator.state; + + // 6. If state is completed, then + if state == AsyncGeneratorState::Completed { + drop(generator_obj_mut); + + // a. Let iteratorResult be CreateIterResultObject(undefined, true). + let iterator_result = create_iter_result_object(JsValue::undefined(), true, context); + + // b. Perform ! Call(promiseCapability.[[Resolve]], undefined, « iteratorResult »). + promise_capability + .resolve() + .call(&JsValue::undefined(), &[iterator_result], context) + .expect("cannot fail per spec"); + + // c. Return promiseCapability.[[Promise]]. + return Ok(promise_capability.promise().clone().into()); + } + + // 7. Let completion be NormalCompletion(value). + let completion = (Ok(args.get_or_undefined(0).clone()), false); + + // 8. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability). + generator.enqueue(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). + let generator_context = generator + .context + .clone() + .expect("generator context cannot be empty here"); + + drop(generator_obj_mut); + + Self::resume( + generator_object, + state, + &generator_context, + completion, + context, + ); + } + + // 11. Return promiseCapability.[[Promise]]. + Ok(promise_capability.promise().clone().into()) + } + + /// `AsyncGenerator.prototype.return ( value )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-asyncgenerator-prototype-return + pub(crate) fn r#return( + this: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { + // 1. Let generator be the this value. + let generator = this; + + // 2. Let promiseCapability be ! NewPromiseCapability(%Promise%). + let promise_capability = PromiseCapability::new( + &context + .intrinsics() + .constructors() + .promise() + .constructor() + .into(), + context, + ) + .expect("cannot fail with promise constructor"); + + // 3. Let result be Completion(AsyncGeneratorValidate(generator, empty)). + // 4. IfAbruptRejectPromise(result, promiseCapability). + let generator_object = generator.as_object().ok_or_else(|| { + context.construct_type_error("generator resumed on non generator object") + }); + if_abrupt_reject_promise!(generator_object, promise_capability, context); + let mut generator_obj_mut = generator_object.borrow_mut(); + let generator = generator_obj_mut.as_async_generator_mut().ok_or_else(|| { + context.construct_type_error("generator resumed on non generator object") + }); + if_abrupt_reject_promise!(generator, promise_capability, context); + + // 5. Let completion be Completion Record { [[Type]]: return, [[Value]]: value, [[Target]]: empty }. + let completion = (Ok(args.get_or_undefined(0).clone()), true); + + // 6. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability). + generator.enqueue(completion.clone(), promise_capability.clone()); + + // 7. Let state be generator.[[AsyncGeneratorState]]. + let state = generator.state; + + // 8. If state is either suspendedStart or completed, then + if state == AsyncGeneratorState::SuspendedStart || state == AsyncGeneratorState::Completed { + // a. Set generator.[[AsyncGeneratorState]] to awaiting-return. + generator.state = AsyncGeneratorState::AwaitingReturn; + + // b. Perform ! AsyncGeneratorAwaitReturn(generator). + let next = generator + .queue + .front() + .cloned() + .expect("queue cannot be empty here"); + drop(generator_obj_mut); + let (completion, _) = &next.completion; + Self::await_return(generator_object.clone(), completion.clone(), context); + } + // 9. Else if state is suspendedYield, then + else if state == AsyncGeneratorState::SuspendedYield { + // a. Perform AsyncGeneratorResume(generator, completion). + let generator_context = generator + .context + .clone() + .expect("generator context cannot be empty here"); + + drop(generator_obj_mut); + Self::resume( + generator_object, + state, + &generator_context, + completion, + context, + ); + } + + // 11. Return promiseCapability.[[Promise]]. + Ok(promise_capability.promise().clone().into()) + } + + /// `AsyncGenerator.prototype.throw ( exception )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-asyncgenerator-prototype-throw + pub(crate) fn throw( + this: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { + // 1. Let generator be the this value. + let generator = this; + + // 2. Let promiseCapability be ! NewPromiseCapability(%Promise%). + let promise_capability = PromiseCapability::new( + &context + .intrinsics() + .constructors() + .promise() + .constructor() + .into(), + context, + ) + .expect("cannot fail with promise constructor"); + + // 3. Let result be Completion(AsyncGeneratorValidate(generator, empty)). + // 4. IfAbruptRejectPromise(result, promiseCapability). + let generator_object = generator.as_object().ok_or_else(|| { + context.construct_type_error("generator resumed on non generator object") + }); + if_abrupt_reject_promise!(generator_object, promise_capability, context); + let mut generator_obj_mut = generator_object.borrow_mut(); + let generator = generator_obj_mut.as_async_generator_mut().ok_or_else(|| { + context.construct_type_error("generator resumed on non generator object") + }); + if_abrupt_reject_promise!(generator, promise_capability, context); + + // 5. Let state be generator.[[AsyncGeneratorState]]. + let mut state = generator.state; + + // 6. If state is suspendedStart, then + if state == AsyncGeneratorState::SuspendedStart { + // a. Set generator.[[AsyncGeneratorState]] to completed. + generator.state = AsyncGeneratorState::Completed; + + // b. Set state to completed. + state = AsyncGeneratorState::Completed; + } + + // 7. If state is completed, then + if state == AsyncGeneratorState::Completed { + drop(generator_obj_mut); + + // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « exception »). + promise_capability + .reject() + .call( + &JsValue::undefined(), + &[args.get_or_undefined(0).clone()], + context, + ) + .expect("cannot fail per spec"); + + // b. Return promiseCapability.[[Promise]]. + return Ok(promise_capability.promise().clone().into()); + } + + // 8. Let completion be ThrowCompletion(exception). + let completion = (Err(args.get_or_undefined(0).clone()), false); + + // 9. Perform AsyncGeneratorEnqueue(generator, completion, promiseCapability). + generator.enqueue(completion.clone(), promise_capability.clone()); + + // 10. If state is suspendedYield, then + if state == AsyncGeneratorState::SuspendedYield { + let generator_context = generator + .context + .clone() + .expect("generator context cannot be empty here"); + drop(generator_obj_mut); + + // a. Perform AsyncGeneratorResume(generator, completion). + Self::resume( + generator_object, + state, + &generator_context, + completion, + context, + ); + } + + // 12. Return promiseCapability.[[Promise]]. + Ok(promise_capability.promise().clone().into()) + } + + /// `AsyncGeneratorEnqueue ( generator, completion, promiseCapability )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorenqueue + pub(crate) fn enqueue( + &mut self, + completion: (JsResult, bool), + promise_capability: PromiseCapability, + ) { + // 1. Let request be AsyncGeneratorRequest { [[Completion]]: completion, [[Capability]]: promiseCapability }. + let request = AsyncGeneratorRequest { + completion, + capability: promise_capability, + }; + + // 2. Append request to the end of generator.[[AsyncGeneratorQueue]]. + self.queue.push_back(request); + } + + /// `AsyncGeneratorCompleteStep ( generator, completion, done [ , realm ] )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorcompletestep + pub(crate) fn complete_step( + next: &AsyncGeneratorRequest, + completion: JsResult, + done: bool, + 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]]. + let promise_capability = &next.capability; + + // 6. Let value be completion.[[Value]]. + match completion { + // 7. If completion.[[Type]] is throw, then + Err(value) => { + // a. Perform ! Call(promiseCapability.[[Reject]], undefined, « value »). + promise_capability + .reject() + .call(&JsValue::undefined(), &[value], context) + .expect("cannot fail per spec"); + } + // 8. Else, + Ok(value) => { + // a. Assert: completion.[[Type]] is normal. + + // TODO: Realm handling not implemented yet. + // b. If realm is present, then + // i. Let oldRealm be the running execution context's Realm. + // ii. Set the running execution context's Realm to realm. + // iii. Let iteratorResult be CreateIterResultObject(value, done). + // iv. Set the running execution context's Realm to oldRealm. + // c. Else, + // i. Let iteratorResult be CreateIterResultObject(value, done). + let iterator_result = create_iter_result_object(value, done, context); + + // d. Perform ! Call(promiseCapability.[[Resolve]], undefined, « iteratorResult »). + promise_capability + .resolve() + .call(&JsValue::undefined(), &[iterator_result], context) + .expect("cannot fail per spec"); + } + } + } + + /// `AsyncGeneratorResume ( generator, completion )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorresume + pub(crate) fn resume( + generator: &JsObject, + state: AsyncGeneratorState, + generator_context: &Gc>, + completion: (JsResult, bool), + context: &mut Context, + ) { + // 1. Assert: generator.[[AsyncGeneratorState]] is either suspendedStart or suspendedYield. + assert!( + state == AsyncGeneratorState::SuspendedStart + || state == AsyncGeneratorState::SuspendedYield + ); + + // 2. Let genContext be generator.[[AsyncGeneratorContext]]. + let mut generator_context_mut = generator_context.borrow_mut(); + + // 3. Let callerContext be the running execution context. + // 4. Suspend callerContext. + + // 5. Set generator.[[AsyncGeneratorState]] to executing. + generator + .borrow_mut() + .as_async_generator_mut() + .expect("already checked before") + .state = AsyncGeneratorState::Executing; + + // 6. Push genContext onto the execution context stack; genContext is now the running execution context. + std::mem::swap( + &mut context.realm.environments, + &mut generator_context_mut.environments, + ); + std::mem::swap(&mut context.vm.stack, &mut generator_context_mut.stack); + context + .vm + .push_frame(generator_context_mut.call_frame.clone()); + + // 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. + match completion { + (Ok(value), r#return) => { + context.vm.push(value); + if r#return { + context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Return; + } else { + context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Normal; + } + } + (Err(value), _) => { + context.vm.push(value); + context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Throw; + } + } + drop(generator_context_mut); + let result = context.run(); + + let mut generator_context_mut = generator_context.borrow_mut(); + std::mem::swap( + &mut context.realm.environments, + &mut generator_context_mut.environments, + ); + std::mem::swap(&mut context.vm.stack, &mut generator_context_mut.stack); + generator_context_mut.call_frame = + context.vm.pop_frame().expect("generator frame must exist"); + drop(generator_context_mut); + + // 8. Assert: result is never an abrupt completion. + assert!(result.is_ok()); + + // 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 )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorawaitreturn + pub(crate) fn await_return( + generator: JsObject, + completion: JsResult, + 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]]). + + // 5. Assert: completion.[[Type]] is return. + let value = completion.expect("completion must be a return completion"); + + // Note: The spec is currently broken here. + // See: https://github.com/tc39/ecma262/pull/2683 + + // 6. Let promise be ? PromiseResolve(%Promise%, completion.[[Value]]). + let promise_completion = Promise::promise_resolve( + context.intrinsics().constructors().promise().constructor(), + value, + context, + ); + + let promise = match promise_completion { + Ok(value) => value, + Err(value) => { + let mut generator_borrow_mut = generator.borrow_mut(); + let gen = generator_borrow_mut + .as_async_generator_mut() + .expect("already checked before"); + gen.state = AsyncGeneratorState::Completed; + let next = gen.queue.pop_front().expect("queue must not be empty"); + drop(generator_borrow_mut); + Self::complete_step(&next, Err(value), true, context); + Self::drain_queue(&generator, context); + 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, "", « »). + let on_fulfilled = FunctionBuilder::closure_with_captures( + context, + |_this, args, generator, context| { + let mut generator_borrow_mut = generator.borrow_mut(); + let gen = generator_borrow_mut + .as_async_generator_mut() + .expect("already checked before"); + + // a. Set generator.[[AsyncGeneratorState]] to completed. + gen.state = AsyncGeneratorState::Completed; + + // b. Let result be NormalCompletion(value). + let result = Ok(args.get_or_undefined(0).clone()); + + // c. Perform AsyncGeneratorCompleteStep(generator, result, true). + let next = gen.queue.pop_front().expect("must have one entry"); + drop(generator_borrow_mut); + Self::complete_step(&next, result, true, context); + + // d. Perform AsyncGeneratorDrainQueue(generator). + Self::drain_queue(generator, context); + + // e. Return undefined. + Ok(JsValue::undefined()) + }, + generator.clone(), + ) + .name("") + .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, "", « »). + let on_rejected = FunctionBuilder::closure_with_captures( + context, + |_this, args, generator, context| { + let mut generator_borrow_mut = generator.borrow_mut(); + let gen = generator_borrow_mut + .as_async_generator_mut() + .expect("already checked before"); + + // a. Set generator.[[AsyncGeneratorState]] to completed. + gen.state = AsyncGeneratorState::Completed; + + // b. Let result be ThrowCompletion(reason). + let result = Err(args.get_or_undefined(0).clone()); + + // c. Perform AsyncGeneratorCompleteStep(generator, result, true). + let next = gen.queue.pop_front().expect("must have one entry"); + drop(generator_borrow_mut); + Self::complete_step(&next, result, true, context); + + // d. Perform AsyncGeneratorDrainQueue(generator). + Self::drain_queue(generator, context); + + // e. Return undefined. + Ok(JsValue::undefined()) + }, + generator, + ) + .name("") + .length(1) + .build(); + + // 11. Perform PerformPromiseThen(promise, onFulfilled, onRejected). + let promise_obj = promise + .as_object() + .expect("constructed promise must be a promise"); + promise_obj + .borrow_mut() + .as_promise_mut() + .expect("constructed promise must be a promise") + .perform_promise_then(&on_fulfilled.into(), &on_rejected.into(), None, context); + } + + /// `AsyncGeneratorDrainQueue ( generator )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratordrainqueue + pub(crate) fn drain_queue(generator: &JsObject, context: &mut Context) { + let mut generator_borrow_mut = generator.borrow_mut(); + let gen = generator_borrow_mut + .as_async_generator_mut() + .expect("already checked before"); + + // 1. Assert: generator.[[AsyncGeneratorState]] is completed. + assert_eq!(gen.state, AsyncGeneratorState::Completed); + + // 2. Let queue be generator.[[AsyncGeneratorQueue]]. + let queue = &mut gen.queue; + + // 3. If queue is empty, return unused. + if queue.is_empty() { + return; + } + + // 4. Let done be false. + // 5. Repeat, while done is false, + loop { + // a. Let next be the first element of queue. + let next = queue.front().expect("must have entry"); + + // b. Let completion be Completion(next.[[Completion]]). + match &next.completion { + // c. If completion.[[Type]] is return, then + (completion, true) => { + // i. Set generator.[[AsyncGeneratorState]] to awaiting-return. + gen.state = AsyncGeneratorState::AwaitingReturn; + + // ii. Perform ! AsyncGeneratorAwaitReturn(generator). + let completion = completion.clone(); + drop(generator_borrow_mut); + Self::await_return(generator.clone(), completion, context); + + // iii. Set done to true. + break; + } + // d. Else, + (completion, false) => { + // i. If completion.[[Type]] is normal, then + let completion = if completion.is_ok() { + // 1. Set completion to NormalCompletion(undefined). + Ok(JsValue::undefined()) + } else { + completion.clone() + }; + + // ii. Perform AsyncGeneratorCompleteStep(generator, completion, true). + let next = queue.pop_front().expect("must have entry"); + Self::complete_step(&next, completion, true, context); + + // iii. If queue is empty, set done to true. + if queue.is_empty() { + break; + } + } + } + } + } +} diff --git a/boa_engine/src/builtins/async_generator_function/mod.rs b/boa_engine/src/builtins/async_generator_function/mod.rs new file mode 100644 index 0000000000..4bd3a186f3 --- /dev/null +++ b/boa_engine/src/builtins/async_generator_function/mod.rs @@ -0,0 +1,129 @@ +//! This module implements the `AsyncGeneratorFunction` object. +//! +//! More information: +//! - [ECMAScript reference][spec] +//! +//! [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorfunction-objects + +use crate::{ + builtins::{ + function::{BuiltInFunctionObject, ConstructorKind, Function}, + BuiltIn, + }, + object::ObjectData, + property::PropertyDescriptor, + symbol::WellKnownSymbols, + value::JsValue, + Context, JsResult, +}; +use boa_profiler::Profiler; + +/// The internal representation on a `AsyncGeneratorFunction` object. +#[derive(Debug, Clone, Copy)] +pub struct AsyncGeneratorFunction; + +impl BuiltIn for AsyncGeneratorFunction { + const NAME: &'static str = "AsyncGeneratorFunction"; + + fn init(context: &mut Context) -> Option { + let _timer = Profiler::global().start_event(Self::NAME, "init"); + + let prototype = &context + .intrinsics() + .constructors() + .async_generator_function() + .prototype; + let constructor = &context + .intrinsics() + .constructors() + .async_generator_function() + .constructor; + + constructor.set_prototype(Some( + context.intrinsics().constructors().function().constructor(), + )); + let property = PropertyDescriptor::builder() + .value(1) + .writable(false) + .enumerable(false) + .configurable(true); + constructor.borrow_mut().insert("length", property); + let property = PropertyDescriptor::builder() + .value(Self::NAME) + .writable(false) + .enumerable(false) + .configurable(true); + constructor.borrow_mut().insert("name", property); + let property = PropertyDescriptor::builder() + .value( + context + .intrinsics() + .constructors() + .async_generator_function() + .prototype(), + ) + .writable(false) + .enumerable(false) + .configurable(false); + constructor.borrow_mut().insert("prototype", property); + constructor.borrow_mut().data = ObjectData::function(Function::Native { + function: Self::constructor, + constructor: Some(ConstructorKind::Base), + }); + + prototype.set_prototype(Some( + context.intrinsics().constructors().function().prototype(), + )); + let property = PropertyDescriptor::builder() + .value( + context + .intrinsics() + .constructors() + .async_generator_function() + .constructor(), + ) + .writable(false) + .enumerable(false) + .configurable(true); + prototype.borrow_mut().insert("constructor", property); + let property = PropertyDescriptor::builder() + .value( + context + .intrinsics() + .constructors() + .async_generator() + .prototype(), + ) + .writable(false) + .enumerable(false) + .configurable(true); + prototype.borrow_mut().insert("prototype", property); + let property = PropertyDescriptor::builder() + .value("AsyncGeneratorFunction") + .writable(false) + .enumerable(false) + .configurable(true); + prototype + .borrow_mut() + .insert(WellKnownSymbols::to_string_tag(), property); + + None + } +} + +impl AsyncGeneratorFunction { + /// `AsyncGeneratorFunction ( p1, p2, … , pn, body )` + /// + /// More information: + /// - [ECMAScript reference][spec] + /// + /// [spec]: https://tc39.es/ecma262/#sec-asyncgeneratorfunction + pub(crate) fn constructor( + new_target: &JsValue, + args: &[JsValue], + context: &mut Context, + ) -> JsResult { + BuiltInFunctionObject::create_dynamic_function(new_target, args, true, true, context) + .map(Into::into) + } +} diff --git a/boa_engine/src/builtins/function/mod.rs b/boa_engine/src/builtins/function/mod.rs index 102328c3eb..063515998f 100644 --- a/boa_engine/src/builtins/function/mod.rs +++ b/boa_engine/src/builtins/function/mod.rs @@ -13,6 +13,7 @@ use crate::{ builtins::{BuiltIn, JsArgs}, + bytecompiler::{FunctionCompiler, FunctionKind}, context::intrinsics::StandardConstructors, environments::DeclarativeEnvironmentStack, object::{ @@ -246,6 +247,10 @@ pub enum Function { code: Gc, environments: DeclarativeEnvironmentStack, }, + AsyncGenerator { + code: Gc, + environments: DeclarativeEnvironmentStack, + }, } unsafe impl Trace for Function { @@ -267,7 +272,8 @@ unsafe impl Trace for Function { mark(environments); mark(promise_capability); } - Self::Generator { code, environments } => { + Self::Generator { code, environments } + | Self::AsyncGenerator { code, environments } => { mark(code); mark(environments); } @@ -288,7 +294,7 @@ impl Function { Self::Native { constructor, .. } | Self::Closure { constructor, .. } => { constructor.is_some() } - Self::Generator { .. } | Self::Async { .. } => false, + Self::Generator { .. } | Self::AsyncGenerator { .. } | Self::Async { .. } => false, Self::Ordinary { code, .. } => !(code.this_mode == ThisMode::Lexical), } } @@ -476,7 +482,9 @@ impl BuiltInFunctionObject { generator: bool, context: &mut Context, ) -> JsResult { - let default = if r#async { + let default = if r#async && generator { + StandardConstructors::async_generator_function + } else if r#async { StandardConstructors::async_function } else if generator { StandardConstructors::generator_function @@ -518,6 +526,20 @@ impl BuiltInFunctionObject { parameters }; + // It is a Syntax Error if FormalParameters Contains YieldExpression is true. + if generator && r#async && parameters.contains_yield_expression() { + return context.throw_syntax_error( + "yield expression not allowed in async generator parameters", + ); + } + + // It is a Syntax Error if FormalParameters Contains AwaitExpression is true. + if generator && r#async && parameters.contains_await_expression() { + return context.throw_syntax_error( + "await expression not allowed in async generator parameters", + ); + } + let body_arg = body_arg.to_string(context)?; let body = match Parser::new(body_arg.as_bytes()).parse_function_body( @@ -581,20 +603,17 @@ impl BuiltInFunctionObject { } } - let code = crate::bytecompiler::ByteCompiler::compile_function_code( - crate::bytecompiler::FunctionKind::Expression, - Some(Sym::ANONYMOUS), - ¶meters, - &body, - generator, - false, - context, - )?; + let code = FunctionCompiler::new() + .name(Sym::ANONYMOUS) + .generator(generator) + .r#async(r#async) + .kind(FunctionKind::Expression) + .compile(¶meters, &body, context)?; let environments = context.realm.environments.pop_to_global(); let function_object = if generator { - crate::vm::create_generator_function_object(code, context) + crate::vm::create_generator_function_object(code, r#async, context) } else { crate::vm::create_function_object(code, r#async, Some(prototype), context) }; @@ -603,31 +622,31 @@ impl BuiltInFunctionObject { Ok(function_object) } else if generator { - let code = crate::bytecompiler::ByteCompiler::compile_function_code( - crate::bytecompiler::FunctionKind::Expression, - Some(Sym::ANONYMOUS), - &FormalParameterList::empty(), - &StatementList::default(), - true, - false, - context, - )?; + let code = FunctionCompiler::new() + .name(Sym::ANONYMOUS) + .generator(true) + .kind(FunctionKind::Expression) + .compile( + &FormalParameterList::empty(), + &StatementList::default(), + context, + )?; let environments = context.realm.environments.pop_to_global(); - let function_object = crate::vm::create_generator_function_object(code, context); + let function_object = + crate::vm::create_generator_function_object(code, r#async, context); context.realm.environments.extend(environments); Ok(function_object) } else { - let code = crate::bytecompiler::ByteCompiler::compile_function_code( - crate::bytecompiler::FunctionKind::Expression, - Some(Sym::ANONYMOUS), - &FormalParameterList::empty(), - &StatementList::default(), - false, - false, - context, - )?; + let code = FunctionCompiler::new() + .name(Sym::ANONYMOUS) + .kind(FunctionKind::Expression) + .compile( + &FormalParameterList::empty(), + &StatementList::default(), + context, + )?; let environments = context.realm.environments.pop_to_global(); let function_object = diff --git a/boa_engine/src/builtins/iterable/mod.rs b/boa_engine/src/builtins/iterable/mod.rs index ee92160023..78bbfc97f4 100644 --- a/boa_engine/src/builtins/iterable/mod.rs +++ b/boa_engine/src/builtins/iterable/mod.rs @@ -14,6 +14,8 @@ use boa_profiler::Profiler; pub struct IteratorPrototypes { /// %IteratorPrototype% iterator_prototype: JsObject, + /// %AsyncIteratorPrototype% + async_iterator_prototype: JsObject, /// %MapIteratorPrototype% array_iterator: JsObject, /// %SetIteratorPrototype% @@ -33,6 +35,7 @@ impl IteratorPrototypes { let _timer = Profiler::global().start_event("IteratorPrototypes::init", "init"); let iterator_prototype = create_iterator_prototype(context); + let async_iterator_prototype = create_async_iterator_prototype(context); Self { array_iterator: ArrayIterator::create_prototype(iterator_prototype.clone(), context), set_iterator: SetIterator::create_prototype(iterator_prototype.clone(), context), @@ -44,6 +47,7 @@ impl IteratorPrototypes { map_iterator: MapIterator::create_prototype(iterator_prototype.clone(), context), for_in_iterator: ForInIterator::create_prototype(iterator_prototype.clone(), context), iterator_prototype, + async_iterator_prototype, } } @@ -57,6 +61,11 @@ impl IteratorPrototypes { self.iterator_prototype.clone() } + #[inline] + pub fn async_iterator_prototype(&self) -> JsObject { + self.async_iterator_prototype.clone() + } + #[inline] pub fn set_iterator(&self) -> JsObject { self.set_iterator.clone() @@ -506,3 +515,24 @@ macro_rules! if_abrupt_close_iterator { // Export macro to crate level pub(crate) use if_abrupt_close_iterator; + +/// Create the `%AsyncIteratorPrototype%` object +/// +/// More information: +/// - [ECMA reference][spec] +/// +/// [spec]: https://tc39.es/ecma262/#sec-asynciteratorprototype +#[inline] +fn create_async_iterator_prototype(context: &mut Context) -> JsObject { + let _timer = Profiler::global().start_event("AsyncIteratorPrototype", "init"); + + let symbol_iterator = WellKnownSymbols::async_iterator(); + let iterator_prototype = ObjectInitializer::new(context) + .function( + |v, _, _| Ok(v.clone()), + (symbol_iterator, "[Symbol.asyncIterator]"), + 0, + ) + .build(); + iterator_prototype +} diff --git a/boa_engine/src/builtins/mod.rs b/boa_engine/src/builtins/mod.rs index 446a2d7bd0..89d8f32fed 100644 --- a/boa_engine/src/builtins/mod.rs +++ b/boa_engine/src/builtins/mod.rs @@ -3,6 +3,8 @@ pub mod array; pub mod array_buffer; pub mod async_function; +pub mod async_generator; +pub mod async_generator_function; pub mod bigint; pub mod boolean; pub mod dataview; @@ -77,8 +79,9 @@ pub(crate) use self::{ use crate::{ builtins::{ - array_buffer::ArrayBuffer, generator::Generator, generator_function::GeneratorFunction, - typed_array::TypedArray, + array_buffer::ArrayBuffer, async_generator::AsyncGenerator, + async_generator_function::AsyncGeneratorFunction, generator::Generator, + generator_function::GeneratorFunction, typed_array::TypedArray, }, property::{Attribute, PropertyDescriptor}, Context, JsValue, @@ -188,7 +191,9 @@ pub fn init(context: &mut Context) { Generator, GeneratorFunction, Promise, - AsyncFunction + AsyncFunction, + AsyncGenerator, + AsyncGeneratorFunction }; #[cfg(feature = "intl")] diff --git a/boa_engine/src/builtins/promise/mod.rs b/boa_engine/src/builtins/promise/mod.rs index bccd87ad84..42189781a6 100644 --- a/boa_engine/src/builtins/promise/mod.rs +++ b/boa_engine/src/builtins/promise/mod.rs @@ -40,13 +40,13 @@ macro_rules! if_abrupt_reject_promise { Err(value) => { // a. Perform ? Call(capability.[[Reject]], undefined, « value.[[Value]] »). $context.call( - &$capability.reject.clone().into(), + &$capability.reject().clone().into(), &JsValue::undefined(), &[value], )?; // b. Return capability.[[Promise]]. - return Ok($capability.promise.clone().into()); + return Ok($capability.promise().clone().into()); } // 2. Else if value is a Completion Record, set value to value.[[Value]]. Ok(value) => value, @@ -54,6 +54,8 @@ macro_rules! if_abrupt_reject_promise { }; } +pub(crate) use if_abrupt_reject_promise; + #[derive(Debug, Clone, Copy, PartialEq, Eq)] enum PromiseState { Pending, diff --git a/boa_engine/src/bytecompiler/function.rs b/boa_engine/src/bytecompiler/function.rs new file mode 100644 index 0000000000..711e23aed0 --- /dev/null +++ b/boa_engine/src/bytecompiler/function.rs @@ -0,0 +1,194 @@ +use crate::{ + builtins::function::ThisMode, + bytecompiler::{ByteCompiler, FunctionKind}, + syntax::ast::node::{Declaration, FormalParameterList, StatementList}, + vm::{BindingOpcode, CodeBlock, Opcode}, + Context, JsResult, +}; +use boa_gc::Gc; +use boa_interner::Sym; +use rustc_hash::FxHashMap; + +/// `FunctionCompiler` is used to compile AST functions to bytecode. +#[derive(Debug, Clone, Copy)] +pub(crate) struct FunctionCompiler { + name: Sym, + generator: bool, + r#async: bool, + strict: bool, + kind: FunctionKind, +} + +impl FunctionCompiler { + /// Create a new `FunctionCompiler`. + #[inline] + pub(crate) fn new() -> Self { + Self { + name: Sym::EMPTY_STRING, + generator: false, + r#async: false, + strict: false, + kind: FunctionKind::Declaration, + } + } + + /// Set the name of the function. + #[inline] + pub(crate) fn name(mut self, name: N) -> Self + where + N: Into>, + { + let name = name.into(); + if let Some(name) = name { + self.name = name; + } + self + } + + /// Indicate if the function is a generator function. + #[inline] + pub(crate) fn generator(mut self, generator: bool) -> Self { + self.generator = generator; + self + } + + /// Indicate if the function is an async function. + #[inline] + pub(crate) fn r#async(mut self, r#async: bool) -> Self { + self.r#async = r#async; + self + } + + /// Indicate if the function is in a strict context. + #[inline] + pub(crate) fn strict(mut self, strict: bool) -> Self { + self.strict = strict; + self + } + + /// Indicate if the function is a declaration, expression or arrow function. + #[inline] + pub(crate) fn kind(mut self, kind: FunctionKind) -> Self { + self.kind = kind; + self + } + + /// Compile a function statement list and it's parameters into bytecode. + pub(crate) fn compile( + mut self, + parameters: &FormalParameterList, + body: &StatementList, + context: &mut Context, + ) -> JsResult> { + self.strict = self.strict || body.strict(); + + let length = parameters.length(); + let mut code = CodeBlock::new(self.name, length, self.strict); + + if self.kind == FunctionKind::Arrow { + code.this_mode = ThisMode::Lexical; + } + + let mut compiler = ByteCompiler { + code_block: code, + literals_map: FxHashMap::default(), + names_map: FxHashMap::default(), + bindings_map: FxHashMap::default(), + jump_info: Vec::new(), + in_async_generator: self.generator && self.r#async, + context, + }; + + compiler.context.push_compile_time_environment(true); + + // An arguments object is added when all of the following conditions are met + // - If not in an arrow function (10.2.11.16) + // - If the parameter list does not contain `arguments` (10.2.11.17) + // Note: This following just means, that we add an extra environment for the arguments. + // - If there are default parameters or if lexical names and function names do not contain `arguments` (10.2.11.18) + if !(self.kind == FunctionKind::Arrow) && !parameters.has_arguments() { + compiler + .context + .create_mutable_binding(Sym::ARGUMENTS, false); + compiler.code_block.arguments_binding = Some( + compiler + .context + .initialize_mutable_binding(Sym::ARGUMENTS, false), + ); + } + + for parameter in parameters.parameters.iter() { + if parameter.is_rest_param() { + compiler.emit_opcode(Opcode::RestParameterInit); + } + + match parameter.declaration() { + Declaration::Identifier { ident, .. } => { + compiler.context.create_mutable_binding(ident.sym(), false); + if let Some(init) = parameter.declaration().init() { + let skip = compiler.emit_opcode_with_operand(Opcode::JumpIfNotUndefined); + compiler.compile_expr(init, true)?; + compiler.patch_jump(skip); + } + compiler.emit_binding(BindingOpcode::InitArg, ident.sym()); + } + Declaration::Pattern(pattern) => { + for ident in pattern.idents() { + compiler.context.create_mutable_binding(ident, false); + } + compiler.compile_declaration_pattern(pattern, BindingOpcode::InitArg)?; + } + } + } + + if !parameters.has_rest_parameter() { + compiler.emit_opcode(Opcode::RestParameterPop); + } + + let env_label = if parameters.has_expressions() { + compiler.code_block.num_bindings = compiler.context.get_binding_number(); + compiler.context.push_compile_time_environment(true); + compiler.code_block.function_environment_push_location = + compiler.next_opcode_location(); + Some(compiler.emit_opcode_with_two_operands(Opcode::PushFunctionEnvironment)) + } else { + None + }; + + // When a generator object is created from a generator function, the generator executes until here to init parameters. + if self.generator { + compiler.emit_opcode(Opcode::PushUndefined); + compiler.emit_opcode(Opcode::Yield); + } + + compiler.create_declarations(body.items())?; + compiler.compile_statement_list(body.items(), false)?; + + if let Some(env_label) = env_label { + let (num_bindings, compile_environment) = + compiler.context.pop_compile_time_environment(); + let index_compile_environment = compiler.push_compile_environment(compile_environment); + compiler.patch_jump_with_target(env_label.0, num_bindings as u32); + compiler.patch_jump_with_target(env_label.1, index_compile_environment as u32); + + let (_, compile_environment) = compiler.context.pop_compile_time_environment(); + compiler.push_compile_environment(compile_environment); + } else { + let (num_bindings, compile_environment) = + compiler.context.pop_compile_time_environment(); + compiler + .code_block + .compile_environments + .push(compile_environment); + compiler.code_block.num_bindings = num_bindings; + } + + compiler.code_block.params = parameters.clone(); + + // TODO These are redundant if a function returns so may need to check if a function returns and adding these if it doesn't + compiler.emit(Opcode::PushUndefined, &[]); + compiler.emit(Opcode::Return, &[]); + + Ok(Gc::new(compiler.finish())) + } +} diff --git a/boa_engine/src/bytecompiler.rs b/boa_engine/src/bytecompiler/mod.rs similarity index 90% rename from boa_engine/src/bytecompiler.rs rename to boa_engine/src/bytecompiler/mod.rs index c5c34f33f7..fff5878fa1 100644 --- a/boa_engine/src/bytecompiler.rs +++ b/boa_engine/src/bytecompiler/mod.rs @@ -1,5 +1,6 @@ +mod function; + use crate::{ - builtins::function::ThisMode, environments::{BindingLocator, CompileTimeEnvironment}, syntax::ast::{ node::{ @@ -11,8 +12,7 @@ use crate::{ object::{MethodDefinition, PropertyDefinition, PropertyName}, operator::assign::AssignTarget, template::TemplateElement, - Class, Declaration, FormalParameterList, GetConstField, GetField, GetSuperField, - StatementList, + Class, Declaration, GetConstField, GetField, GetSuperField, }, op::{AssignOp, BinOp, BitOp, CompOp, LogOp, NumOp, UnaryOp}, Const, Node, @@ -25,6 +25,8 @@ use boa_interner::{Interner, Sym}; use rustc_hash::FxHashMap; use std::mem::size_of; +pub(crate) use function::FunctionCompiler; + #[derive(Debug, Clone, PartialEq, Eq, Hash)] enum Literal { String(JsString), @@ -72,6 +74,7 @@ pub struct ByteCompiler<'b> { names_map: FxHashMap, bindings_map: FxHashMap, jump_info: Vec, + in_async_generator: bool, context: &'b mut Context, } @@ -87,6 +90,7 @@ impl<'b> ByteCompiler<'b> { names_map: FxHashMap::default(), bindings_map: FxHashMap::default(), jump_info: Vec::new(), + in_async_generator: false, context, } } @@ -896,98 +900,92 @@ impl<'b> ByteCompiler<'b> { self.emit_opcode(Opcode::DefineOwnPropertyByValue); } }, - PropertyDefinition::MethodDefinition(kind, name) => { - match kind { - MethodDefinition::Get(expr) => match name { - PropertyName::Literal(name) => { - self.function(&expr.clone().into(), true)?; - self.emit_opcode(Opcode::Swap); - let index = self.get_or_insert_name(*name); - self.emit(Opcode::SetPropertyGetterByName, &[index]); - } - PropertyName::Computed(name_node) => { - self.compile_stmt(name_node, true)?; - self.emit_opcode(Opcode::ToPropertyKey); - self.function(&expr.clone().into(), true)?; - self.emit_opcode(Opcode::SetPropertyGetterByValue); - } - }, - MethodDefinition::Set(expr) => match name { - PropertyName::Literal(name) => { - self.function(&expr.clone().into(), true)?; - self.emit_opcode(Opcode::Swap); - let index = self.get_or_insert_name(*name); - self.emit(Opcode::SetPropertySetterByName, &[index]); - } - PropertyName::Computed(name_node) => { - self.compile_stmt(name_node, true)?; - self.emit_opcode(Opcode::ToPropertyKey); - self.function(&expr.clone().into(), true)?; - self.emit_opcode(Opcode::SetPropertySetterByValue); - } - }, - MethodDefinition::Ordinary(expr) => match name { - PropertyName::Literal(name) => { - self.function(&expr.clone().into(), true)?; - self.emit_opcode(Opcode::Swap); - let index = self.get_or_insert_name(*name); - self.emit(Opcode::DefineOwnPropertyByName, &[index]); - } - PropertyName::Computed(name_node) => { - self.compile_stmt(name_node, true)?; - self.emit_opcode(Opcode::ToPropertyKey); - self.function(&expr.clone().into(), true)?; - self.emit_opcode(Opcode::DefineOwnPropertyByValue); - } - }, - MethodDefinition::Async(expr) => match name { - PropertyName::Literal(name) => { - self.function(&expr.clone().into(), true)?; - self.emit_opcode(Opcode::Swap); - let index = self.get_or_insert_name(*name); - self.emit(Opcode::DefineOwnPropertyByName, &[index]); - } - PropertyName::Computed(name_node) => { - self.compile_stmt(name_node, true)?; - self.emit_opcode(Opcode::ToPropertyKey); - self.function(&expr.clone().into(), true)?; - self.emit_opcode(Opcode::DefineOwnPropertyByValue); - } - }, - MethodDefinition::Generator(expr) => match name { - PropertyName::Literal(name) => { - self.function(&expr.clone().into(), true)?; - self.emit_opcode(Opcode::Swap); - let index = self.get_or_insert_name(*name); - self.emit(Opcode::DefineOwnPropertyByName, &[index]); - } - PropertyName::Computed(name_node) => { - self.compile_stmt(name_node, true)?; - self.emit_opcode(Opcode::ToPropertyKey); - self.function(&expr.clone().into(), true)?; - self.emit_opcode(Opcode::DefineOwnPropertyByValue); - } - }, - // TODO: Implement async generators - MethodDefinition::AsyncGenerator(_) => { - // TODO: Implement async - match name { - PropertyName::Literal(name) => { - self.emit_opcode(Opcode::PushUndefined); - self.emit_opcode(Opcode::Swap); - let index = self.get_or_insert_name(*name); - self.emit(Opcode::DefineOwnPropertyByName, &[index]); - } - PropertyName::Computed(name_node) => { - self.compile_stmt(name_node, true)?; - self.emit_opcode(Opcode::ToPropertyKey); - self.emit_opcode(Opcode::PushUndefined); - self.emit_opcode(Opcode::DefineOwnPropertyByValue); - } - } + PropertyDefinition::MethodDefinition(kind, name) => match kind { + MethodDefinition::Get(expr) => match name { + PropertyName::Literal(name) => { + self.function(&expr.clone().into(), true)?; + self.emit_opcode(Opcode::Swap); + let index = self.get_or_insert_name(*name); + self.emit(Opcode::SetPropertyGetterByName, &[index]); } - } - } + PropertyName::Computed(name_node) => { + self.compile_stmt(name_node, true)?; + self.emit_opcode(Opcode::ToPropertyKey); + self.function(&expr.clone().into(), true)?; + self.emit_opcode(Opcode::SetPropertyGetterByValue); + } + }, + MethodDefinition::Set(expr) => match name { + PropertyName::Literal(name) => { + self.function(&expr.clone().into(), true)?; + self.emit_opcode(Opcode::Swap); + let index = self.get_or_insert_name(*name); + self.emit(Opcode::SetPropertySetterByName, &[index]); + } + PropertyName::Computed(name_node) => { + self.compile_stmt(name_node, true)?; + self.emit_opcode(Opcode::ToPropertyKey); + self.function(&expr.clone().into(), true)?; + self.emit_opcode(Opcode::SetPropertySetterByValue); + } + }, + MethodDefinition::Ordinary(expr) => match name { + PropertyName::Literal(name) => { + self.function(&expr.clone().into(), true)?; + self.emit_opcode(Opcode::Swap); + let index = self.get_or_insert_name(*name); + self.emit(Opcode::DefineOwnPropertyByName, &[index]); + } + PropertyName::Computed(name_node) => { + self.compile_stmt(name_node, true)?; + self.emit_opcode(Opcode::ToPropertyKey); + self.function(&expr.clone().into(), true)?; + self.emit_opcode(Opcode::DefineOwnPropertyByValue); + } + }, + MethodDefinition::Async(expr) => match name { + PropertyName::Literal(name) => { + self.function(&expr.clone().into(), true)?; + self.emit_opcode(Opcode::Swap); + let index = self.get_or_insert_name(*name); + self.emit(Opcode::DefineOwnPropertyByName, &[index]); + } + PropertyName::Computed(name_node) => { + self.compile_stmt(name_node, true)?; + self.emit_opcode(Opcode::ToPropertyKey); + self.function(&expr.clone().into(), true)?; + self.emit_opcode(Opcode::DefineOwnPropertyByValue); + } + }, + MethodDefinition::Generator(expr) => match name { + PropertyName::Literal(name) => { + self.function(&expr.clone().into(), true)?; + self.emit_opcode(Opcode::Swap); + let index = self.get_or_insert_name(*name); + self.emit(Opcode::DefineOwnPropertyByName, &[index]); + } + PropertyName::Computed(name_node) => { + self.compile_stmt(name_node, true)?; + self.emit_opcode(Opcode::ToPropertyKey); + self.function(&expr.clone().into(), true)?; + self.emit_opcode(Opcode::DefineOwnPropertyByValue); + } + }, + MethodDefinition::AsyncGenerator(expr) => match name { + PropertyName::Literal(name) => { + self.function(&expr.clone().into(), true)?; + self.emit_opcode(Opcode::Swap); + let index = self.get_or_insert_name(*name); + self.emit(Opcode::DefineOwnPropertyByName, &[index]); + } + PropertyName::Computed(name_node) => { + self.compile_stmt(name_node, true)?; + self.emit_opcode(Opcode::ToPropertyKey); + self.function(&expr.clone().into(), true)?; + self.emit_opcode(Opcode::DefineOwnPropertyByValue); + } + }, + }, PropertyDefinition::SpreadObject(expr) => { self.compile_expr(expr, true)?; self.emit_opcode(Opcode::Swap); @@ -1144,11 +1142,9 @@ impl<'b> ByteCompiler<'b> { self.emit_opcode(Opcode::Pop); } } - // TODO: implement AsyncGeneratorExpr - Node::AsyncGeneratorExpr(_) => { - self.emit_opcode(Opcode::PushUndefined); + Node::GeneratorExpr(_) | Node::AsyncFunctionExpr(_) | Node::AsyncGeneratorExpr(_) => { + self.function(expr, use_expr)?; } - Node::GeneratorExpr(_) | Node::AsyncFunctionExpr(_) => self.function(expr, use_expr)?, Node::Yield(r#yield) => { if let Some(expr) = r#yield.expr() { self.compile_expr(expr, true)?; @@ -1163,6 +1159,17 @@ impl<'b> ByteCompiler<'b> { let start = self.emit_opcode_with_operand(Opcode::GeneratorNextDelegate); self.emit(Opcode::Jump, &[start_address]); self.patch_jump(start); + } else if self.in_async_generator { + self.emit_opcode(Opcode::Await); + self.emit_opcode(Opcode::AsyncGeneratorNext); + let jump_return = self.emit_opcode_with_operand(Opcode::JumpIfFalse); + let jump = self.emit_opcode_with_operand(Opcode::JumpIfFalse); + self.emit_opcode(Opcode::Yield); + self.emit_opcode(Opcode::GeneratorNext); + self.patch_jump(jump); + self.emit_opcode(Opcode::Await); + self.emit_opcode(Opcode::GeneratorNext); + self.patch_jump(jump_return); } else { self.emit_opcode(Opcode::Yield); self.emit_opcode(Opcode::GeneratorNext); @@ -1942,10 +1949,8 @@ impl<'b> ByteCompiler<'b> { self.pop_try_control_info(None); } } - Node::GeneratorDecl(_) | Node::AsyncFunctionDecl(_) => self.function(node, false)?, - // TODO: implement AsyncGeneratorDecl - Node::AsyncGeneratorDecl(_) => { - self.emit_opcode(Opcode::PushUndefined); + Node::GeneratorDecl(_) | Node::AsyncFunctionDecl(_) | Node::AsyncGeneratorDecl(_) => { + self.function(node, false)?; } Node::ClassDecl(class) => self.class(class, false)?, Node::Empty => {} @@ -1954,126 +1959,6 @@ impl<'b> ByteCompiler<'b> { Ok(()) } - /// Compile a function statement list and it's parameters into bytecode. - pub(crate) fn compile_function_code( - kind: FunctionKind, - name: Option, - parameters: &FormalParameterList, - body: &StatementList, - generator: bool, - strict: bool, - context: &mut Context, - ) -> JsResult> { - let strict = strict || body.strict(); - let length = parameters.length(); - let mut code = CodeBlock::new(name.unwrap_or(Sym::EMPTY_STRING), length, strict); - - if let FunctionKind::Arrow = kind { - code.this_mode = ThisMode::Lexical; - } - - let mut compiler = ByteCompiler { - code_block: code, - literals_map: FxHashMap::default(), - names_map: FxHashMap::default(), - bindings_map: FxHashMap::default(), - jump_info: Vec::new(), - context, - }; - - compiler.context.push_compile_time_environment(true); - - // An arguments object is added when all of the following conditions are met - // - If not in an arrow function (10.2.11.16) - // - If the parameter list does not contain `arguments` (10.2.11.17) - // Note: This following just means, that we add an extra environment for the arguments. - // - If there are default parameters or if lexical names and function names do not contain `arguments` (10.2.11.18) - if !(kind == FunctionKind::Arrow) && !parameters.has_arguments() { - compiler - .context - .create_mutable_binding(Sym::ARGUMENTS, false); - compiler.code_block.arguments_binding = Some( - compiler - .context - .initialize_mutable_binding(Sym::ARGUMENTS, false), - ); - } - - for parameter in parameters.parameters.iter() { - if parameter.is_rest_param() { - compiler.emit_opcode(Opcode::RestParameterInit); - } - - match parameter.declaration() { - Declaration::Identifier { ident, .. } => { - compiler.context.create_mutable_binding(ident.sym(), false); - if let Some(init) = parameter.declaration().init() { - let skip = compiler.emit_opcode_with_operand(Opcode::JumpIfNotUndefined); - compiler.compile_expr(init, true)?; - compiler.patch_jump(skip); - } - compiler.emit_binding(BindingOpcode::InitArg, ident.sym()); - } - Declaration::Pattern(pattern) => { - for ident in pattern.idents() { - compiler.context.create_mutable_binding(ident, false); - } - compiler.compile_declaration_pattern(pattern, BindingOpcode::InitArg)?; - } - } - } - - if !parameters.has_rest_parameter() { - compiler.emit_opcode(Opcode::RestParameterPop); - } - - let env_label = if parameters.has_expressions() { - compiler.code_block.num_bindings = compiler.context.get_binding_number(); - compiler.context.push_compile_time_environment(true); - compiler.code_block.function_environment_push_location = - compiler.next_opcode_location(); - Some(compiler.emit_opcode_with_two_operands(Opcode::PushFunctionEnvironment)) - } else { - None - }; - - // When a generator object is created from a generator function, the generator executes until here to init parameters. - if generator { - compiler.emit_opcode(Opcode::PushUndefined); - compiler.emit_opcode(Opcode::Yield); - } - - compiler.create_declarations(body.items())?; - compiler.compile_statement_list(body.items(), false)?; - - if let Some(env_label) = env_label { - let (num_bindings, compile_environment) = - compiler.context.pop_compile_time_environment(); - let index_compile_environment = compiler.push_compile_environment(compile_environment); - compiler.patch_jump_with_target(env_label.0, num_bindings as u32); - compiler.patch_jump_with_target(env_label.1, index_compile_environment as u32); - - let (_, compile_environment) = compiler.context.pop_compile_time_environment(); - compiler.push_compile_environment(compile_environment); - } else { - let (num_bindings, compile_environment) = - compiler.context.pop_compile_time_environment(); - compiler - .code_block - .compile_environments - .push(compile_environment); - compiler.code_block.num_bindings = num_bindings; - } - - compiler.code_block.params = parameters.clone(); - - // TODO These are redundant if a function returns so may need to check if a function returns and adding these if it doesn't - compiler.emit(Opcode::PushUndefined, &[]); - compiler.emit(Opcode::Return, &[]); - - Ok(Gc::new(compiler.finish())) - } - /// Compile a function AST Node into bytecode. pub(crate) fn function(&mut self, function: &Node, use_expr: bool) -> JsResult<()> { let (kind, name, parameters, body, generator, r#async) = match function { @@ -2101,6 +1986,14 @@ impl<'b> ByteCompiler<'b> { true, false, ), + Node::AsyncGeneratorDecl(generator) => ( + FunctionKind::Declaration, + Some(generator.name()), + generator.parameters(), + generator.body(), + true, + true, + ), Node::FunctionExpr(function) => ( FunctionKind::Expression, function.name(), @@ -2125,6 +2018,14 @@ impl<'b> ByteCompiler<'b> { true, false, ), + Node::AsyncGeneratorExpr(generator) => ( + FunctionKind::Expression, + generator.name(), + generator.parameters(), + generator.body(), + true, + true, + ), Node::ArrowFunctionDecl(function) => ( FunctionKind::Arrow, function.name(), @@ -2136,20 +2037,20 @@ impl<'b> ByteCompiler<'b> { _ => unreachable!(), }; - let code = Self::compile_function_code( - kind, - name, - parameters, - body, - generator, - self.code_block.strict, - self.context, - )?; + let code = FunctionCompiler::new() + .name(name) + .generator(generator) + .r#async(r#async) + .strict(self.code_block.strict) + .kind(kind) + .compile(parameters, body, self.context)?; let index = self.code_block.functions.len() as u32; self.code_block.functions.push(code); - if generator { + if generator && r#async { + self.emit(Opcode::GetGeneratorAsync, &[index]); + } else if generator { self.emit(Opcode::GetGenerator, &[index]); } else if r#async { self.emit(Opcode::GetFunctionAsync, &[index]); @@ -2627,6 +2528,7 @@ impl<'b> ByteCompiler<'b> { names_map: FxHashMap::default(), bindings_map: FxHashMap::default(), jump_info: Vec::new(), + in_async_generator: false, context: self.context, }; compiler.context.push_compile_time_environment(true); @@ -2806,8 +2708,20 @@ impl<'b> ByteCompiler<'b> { self.emit_opcode(Opcode::DefineClassMethodByValue); } }, - // TODO: implement async - MethodDefinition::AsyncGenerator(_) => {} + MethodDefinition::AsyncGenerator(expr) => match name { + PropertyName::Literal(name) => { + self.function(&expr.clone().into(), true)?; + self.emit_opcode(Opcode::Swap); + let index = self.get_or_insert_name(*name); + self.emit(Opcode::DefineClassMethodByName, &[index]); + } + PropertyName::Computed(name_node) => { + self.compile_stmt(name_node, true)?; + self.emit_opcode(Opcode::ToPropertyKey); + self.function(&expr.clone().into(), true)?; + self.emit_opcode(Opcode::DefineClassMethodByValue); + } + }, } } ClassElement::PrivateStaticMethodDefinition(name, method_definition) => { @@ -2838,8 +2752,11 @@ impl<'b> ByteCompiler<'b> { let index = self.get_or_insert_name(*name); self.emit(Opcode::SetPrivateMethod, &[index]); } - // TODO: implement async - MethodDefinition::AsyncGenerator(_) => {} + MethodDefinition::AsyncGenerator(expr) => { + self.function(&expr.clone().into(), true)?; + let index = self.get_or_insert_name(*name); + self.emit(Opcode::SetPrivateMethod, &[index]); + } } } ClassElement::FieldDefinition(name, field) => { @@ -2861,6 +2778,7 @@ impl<'b> ByteCompiler<'b> { names_map: FxHashMap::default(), bindings_map: FxHashMap::default(), jump_info: Vec::new(), + in_async_generator: false, context: self.context, }; field_compiler.context.push_compile_time_environment(true); @@ -2894,6 +2812,7 @@ impl<'b> ByteCompiler<'b> { names_map: FxHashMap::default(), bindings_map: FxHashMap::default(), jump_info: Vec::new(), + in_async_generator: false, context: self.context, }; field_compiler.context.push_compile_time_environment(true); @@ -3002,8 +2921,11 @@ impl<'b> ByteCompiler<'b> { let index = self.get_or_insert_name(*name); self.emit(Opcode::PushClassPrivateMethod, &[index]); } - // TODO: implement async - MethodDefinition::AsyncGenerator(_) => {} + MethodDefinition::AsyncGenerator(expr) => { + self.function(&expr.clone().into(), true)?; + let index = self.get_or_insert_name(*name); + self.emit(Opcode::PushClassPrivateMethod, &[index]); + } } } ClassElement::MethodDefinition(..) => {} @@ -3087,10 +3009,20 @@ impl<'b> ByteCompiler<'b> { self.emit_opcode(Opcode::DefineClassMethodByValue); } }, - // TODO: implement async - MethodDefinition::AsyncGenerator(_) => { - self.emit_opcode(Opcode::Pop); - } + MethodDefinition::AsyncGenerator(expr) => match name { + PropertyName::Literal(name) => { + self.function(&expr.clone().into(), true)?; + self.emit_opcode(Opcode::Swap); + let index = self.get_or_insert_name(*name); + self.emit(Opcode::DefineClassMethodByName, &[index]); + } + PropertyName::Computed(name_node) => { + self.compile_stmt(name_node, true)?; + self.emit_opcode(Opcode::ToPropertyKey); + self.function(&expr.clone().into(), true)?; + self.emit_opcode(Opcode::DefineClassMethodByValue); + } + }, } } ClassElement::PrivateMethodDefinition(..) diff --git a/boa_engine/src/context/intrinsics.rs b/boa_engine/src/context/intrinsics.rs index 300439cd7b..719b40939d 100644 --- a/boa_engine/src/context/intrinsics.rs +++ b/boa_engine/src/context/intrinsics.rs @@ -72,6 +72,8 @@ impl StandardConstructor { /// Cached core standard constructors. #[derive(Debug, Clone)] pub struct StandardConstructors { + async_generator_function: StandardConstructor, + async_generator: StandardConstructor, object: StandardConstructor, proxy: StandardConstructor, date: StandardConstructor, @@ -117,6 +119,8 @@ pub struct StandardConstructors { impl Default for StandardConstructors { fn default() -> Self { let result = Self { + async_generator_function: StandardConstructor::default(), + async_generator: StandardConstructor::default(), object: StandardConstructor::default(), proxy: StandardConstructor::default(), date: StandardConstructor::default(), @@ -187,6 +191,16 @@ impl Default for StandardConstructors { } impl StandardConstructors { + #[inline] + pub fn async_generator_function(&self) -> &StandardConstructor { + &self.async_generator_function + } + + #[inline] + pub fn async_generator(&self) -> &StandardConstructor { + &self.async_generator + } + #[inline] pub fn object(&self) -> &StandardConstructor { &self.object diff --git a/boa_engine/src/context/mod.rs b/boa_engine/src/context/mod.rs index a0274b0f5c..d2d35e873a 100644 --- a/boa_engine/src/context/mod.rs +++ b/boa_engine/src/context/mod.rs @@ -729,6 +729,7 @@ impl Context { arg_count: 0, generator_resume_kind: GeneratorResumeKind::Normal, thrown: false, + async_generator: None, }); self.realm.set_global_binding_number(); diff --git a/boa_engine/src/object/mod.rs b/boa_engine/src/object/mod.rs index ec8b4b9c33..686612e693 100644 --- a/boa_engine/src/object/mod.rs +++ b/boa_engine/src/object/mod.rs @@ -26,6 +26,7 @@ use crate::{ builtins::{ array::array_iterator::ArrayIterator, array_buffer::ArrayBuffer, + async_generator::AsyncGenerator, function::arguments::Arguments, function::{ arguments::ParameterMap, BoundFunction, Captures, ConstructorKind, Function, @@ -164,6 +165,8 @@ pub struct ObjectData { /// Defines the different types of objects. #[derive(Debug, Trace, Finalize)] pub enum ObjectKind { + AsyncGenerator(AsyncGenerator), + AsyncGeneratorFunction(Function), Array, ArrayIterator(ArrayIterator), ArrayBuffer(ArrayBuffer), @@ -199,6 +202,26 @@ pub enum ObjectKind { } impl ObjectData { + /// Create the `AsyncGenerator` object data + pub fn async_generator(async_generator: AsyncGenerator) -> Self { + Self { + kind: ObjectKind::AsyncGenerator(async_generator), + internal_methods: &ORDINARY_INTERNAL_METHODS, + } + } + + /// Create the `AsyncGeneratorFunction` object data + pub fn async_generator_function(function: Function) -> Self { + Self { + internal_methods: if function.is_constructor() { + &CONSTRUCTOR_INTERNAL_METHODS + } else { + &FUNCTION_INTERNAL_METHODS + }, + kind: ObjectKind::GeneratorFunction(function), + } + } + /// Create the `Array` object data and reference its exclusive internal methods pub fn array() -> Self { Self { @@ -474,6 +497,8 @@ impl ObjectData { impl Display for ObjectKind { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(match self { + Self::AsyncGenerator(_) => "AsyncGenerator", + Self::AsyncGeneratorFunction(_) => "AsyncGeneratorFunction", Self::Array => "Array", Self::ArrayIterator(_) => "ArrayIterator", Self::ArrayBuffer(_) => "ArrayBuffer", @@ -539,6 +564,42 @@ impl Object { &self.data.kind } + /// Checks if it's an `AsyncGenerator` object. + #[inline] + pub fn is_async_generator(&self) -> bool { + matches!( + self.data, + ObjectData { + kind: ObjectKind::AsyncGenerator(_), + .. + } + ) + } + + /// Returns a reference to the async generator data on the object. + #[inline] + pub fn as_async_generator(&self) -> Option<&AsyncGenerator> { + match self.data { + ObjectData { + kind: ObjectKind::AsyncGenerator(ref async_generator), + .. + } => Some(async_generator), + _ => None, + } + } + + /// Returns a mutable reference to the async generator data on the object. + #[inline] + pub fn as_async_generator_mut(&mut self) -> Option<&mut AsyncGenerator> { + match self.data { + ObjectData { + kind: ObjectKind::AsyncGenerator(ref mut async_generator), + .. + } => Some(async_generator), + _ => None, + } + } + /// Checks if it an `Array` object. #[inline] pub fn is_array(&self) -> bool { @@ -1601,7 +1662,10 @@ impl<'context> FunctionBuilder<'context> { } => { *constructor = yes.then(|| ConstructorKind::Base); } - Function::Ordinary { .. } | Function::Generator { .. } | Function::Async { .. } => { + Function::Ordinary { .. } + | Function::Generator { .. } + | Function::AsyncGenerator { .. } + | Function::Async { .. } => { unreachable!("function must be native or closure"); } } diff --git a/boa_engine/src/syntax/ast/node/declaration/async_generator_decl/mod.rs b/boa_engine/src/syntax/ast/node/declaration/async_generator_decl/mod.rs index 897413ca18..bd943ac7ec 100644 --- a/boa_engine/src/syntax/ast/node/declaration/async_generator_decl/mod.rs +++ b/boa_engine/src/syntax/ast/node/declaration/async_generator_decl/mod.rs @@ -45,8 +45,8 @@ impl AsyncGeneratorDecl { } /// Gets the body of the async function declaration. - pub fn body(&self) -> &[Node] { - self.body.items() + pub fn body(&self) -> &StatementList { + &self.body } /// Implements the display formatting with indentation. @@ -60,7 +60,7 @@ impl AsyncGeneratorDecl { interner.resolve_expect(self.name), join_nodes(interner, &self.parameters.parameters) ); - if self.body().is_empty() { + if self.body().items().is_empty() { buf.push_str(") {}"); } else { buf.push_str(&format!( diff --git a/boa_engine/src/syntax/ast/node/mod.rs b/boa_engine/src/syntax/ast/node/mod.rs index 290ab5bf6d..d6a98b48cf 100644 --- a/boa_engine/src/syntax/ast/node/mod.rs +++ b/boa_engine/src/syntax/ast/node/mod.rs @@ -945,6 +945,7 @@ impl Node { return true; } } + Node::AwaitExpr(_) if symbol == ContainsSymbol::AwaitExpression => return true, Node::AwaitExpr(expr) => { if expr.expr().contains(symbol) { return true; @@ -1259,6 +1260,7 @@ pub(crate) enum ContainsSymbol { SuperProperty, SuperCall, YieldExpression, + AwaitExpression, } impl ToInternedString for Node { diff --git a/boa_engine/src/syntax/ast/node/parameters.rs b/boa_engine/src/syntax/ast/node/parameters.rs index 90de723f20..164aa83a1f 100644 --- a/boa_engine/src/syntax/ast/node/parameters.rs +++ b/boa_engine/src/syntax/ast/node/parameters.rs @@ -114,6 +114,19 @@ impl FormalParameterList { } false } + + /// Check if the any of the parameters contains a await expression. + pub(crate) fn contains_await_expression(&self) -> bool { + for parameter in self.parameters.iter() { + if parameter + .declaration() + .contains(ContainsSymbol::AwaitExpression) + { + return true; + } + } + false + } } impl From> for FormalParameterList { diff --git a/boa_engine/src/syntax/parser/expression/primary/async_generator_expression/mod.rs b/boa_engine/src/syntax/parser/expression/primary/async_generator_expression/mod.rs index ea76e44441..ef616eeb97 100644 --- a/boa_engine/src/syntax/parser/expression/primary/async_generator_expression/mod.rs +++ b/boa_engine/src/syntax/parser/expression/primary/async_generator_expression/mod.rs @@ -107,6 +107,22 @@ where let params = FormalParameters::new(true, true).parse(cursor, interner)?; + // It is a Syntax Error if FormalParameters Contains YieldExpression is true. + if params.contains_yield_expression() { + return Err(ParseError::lex(LexError::Syntax( + "yield expression not allowed in async generator expression parameters".into(), + params_start_position, + ))); + } + + // It is a Syntax Error if FormalParameters Contains AwaitExpression is true. + if params.contains_await_expression() { + return Err(ParseError::lex(LexError::Syntax( + "await expression not allowed in async generator expression parameters".into(), + params_start_position, + ))); + } + cursor.expect( Punctuator::CloseParen, "async generator expression", diff --git a/boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs b/boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs index 1c7a2f09da..036ed849ae 100644 --- a/boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs +++ b/boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs @@ -780,8 +780,32 @@ where let name = ClassElementName::new(self.allow_yield, self.allow_await).parse(cursor, interner)?; + let params_start_position = cursor + .peek(0, interner)? + .ok_or(ParseError::AbruptEnd)? + .span() + .start(); + let params = UniqueFormalParameters::new(true, true).parse(cursor, interner)?; + // It is a Syntax Error if FormalParameters Contains YieldExpression is true. + if params.contains_yield_expression() { + return Err(ParseError::lex(LexError::Syntax( + "yield expression not allowed in async generator method definition parameters" + .into(), + params_start_position, + ))); + } + + // It is a Syntax Error if FormalParameters Contains AwaitExpression is true. + if params.contains_await_expression() { + return Err(ParseError::lex(LexError::Syntax( + "await expression not allowed in async generator method definition parameters" + .into(), + params_start_position, + ))); + } + let body_start = cursor .expect( TokenKind::Punctuator(Punctuator::OpenBlock), diff --git a/boa_engine/src/vm/call_frame.rs b/boa_engine/src/vm/call_frame.rs index ba9cfd4f34..b12782be2e 100644 --- a/boa_engine/src/vm/call_frame.rs +++ b/boa_engine/src/vm/call_frame.rs @@ -2,7 +2,7 @@ //! //! This module will provides everything needed to implement the `CallFrame` -use crate::vm::CodeBlock; +use crate::{object::JsObject, vm::CodeBlock}; use boa_gc::{Finalize, Gc, Trace}; #[derive(Clone, Debug, Finalize, Trace)] @@ -32,6 +32,10 @@ pub struct CallFrame { // Indicate that the last try block has thrown an exception. pub(crate) thrown: bool, + + // When an async generator is resumed, the generator object is needed + // to fulfill the steps 4.e-j in [AsyncGeneratorStart](https://tc39.es/ecma262/#sec-asyncgeneratorstart). + pub(crate) async_generator: Option, } impl CallFrame { @@ -111,7 +115,7 @@ pub(crate) enum FinallyReturn { } /// Indicates how a generator function that has been called/resumed should return. -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq)] pub(crate) enum GeneratorResumeKind { Normal, Throw, diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index 9d89e8fb8f..ca603a39c9 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/boa_engine/src/vm/code_block.rs @@ -4,6 +4,7 @@ use crate::{ builtins::{ + async_generator::{AsyncGenerator, AsyncGeneratorState}, function::{ arguments::Arguments, ClassFieldDefinition, ConstructorKind, Function, ThisMode, }, @@ -24,7 +25,7 @@ use crate::{ use boa_gc::{Cell, Finalize, Gc, Trace}; use boa_interner::{Interner, Sym, ToInternedString}; use boa_profiler::Profiler; -use std::{convert::TryInto, mem::size_of}; +use std::{collections::VecDeque, convert::TryInto, mem::size_of}; /// This represents whether a value can be read from [`CodeBlock`] code. /// @@ -223,7 +224,10 @@ impl CodeBlock { *pc += size_of::(); format!("{operand1}, {operand2}") } - Opcode::GetFunction | Opcode::GetFunctionAsync | Opcode::GetGenerator => { + Opcode::GetFunction + | Opcode::GetFunctionAsync + | Opcode::GetGenerator + | Opcode::GetGeneratorAsync => { let operand = self.read::(*pc); *pc += size_of::(); format!( @@ -364,6 +368,7 @@ impl CodeBlock { | Opcode::PopOnReturnSub | Opcode::Yield | Opcode::GeneratorNext + | Opcode::AsyncGeneratorNext | Opcode::PushClassField | Opcode::SuperCallDerived | Opcode::Await @@ -546,13 +551,22 @@ pub(crate) fn create_function_object( /// Creates a new generator function object. pub(crate) fn create_generator_function_object( code: Gc, + r#async: bool, context: &mut Context, ) -> JsObject { - let function_prototype = context - .intrinsics() - .constructors() - .generator_function() - .prototype(); + let function_prototype = if r#async { + context + .intrinsics() + .constructors() + .async_generator_function() + .prototype() + } else { + context + .intrinsics() + .constructors() + .generator_function() + .prototype() + }; let name_property = PropertyDescriptor::builder() .value(context.interner().resolve_expect(code.name)) @@ -569,18 +583,35 @@ pub(crate) fn create_generator_function_object( .build(); let prototype = JsObject::from_proto_and_data( - context.intrinsics().constructors().generator().prototype(), + if r#async { + context + .intrinsics() + .constructors() + .async_generator() + .prototype() + } else { + context.intrinsics().constructors().generator().prototype() + }, ObjectData::ordinary(), ); - let function = Function::Generator { - code, - environments: context.realm.environments.clone(), + let constructor = if r#async { + let function = Function::AsyncGenerator { + code, + environments: context.realm.environments.clone(), + }; + JsObject::from_proto_and_data( + function_prototype, + ObjectData::async_generator_function(function), + ) + } else { + let function = Function::Generator { + code, + environments: context.realm.environments.clone(), + }; + JsObject::from_proto_and_data(function_prototype, ObjectData::generator_function(function)) }; - let constructor = - JsObject::from_proto_and_data(function_prototype, ObjectData::generator_function(function)); - let prototype_property = PropertyDescriptor::builder() .value(prototype) .writable(true) @@ -748,6 +779,7 @@ impl JsObject { arg_count, generator_resume_kind: GeneratorResumeKind::Normal, thrown: false, + async_generator: None, }); let result = context.run(); @@ -870,6 +902,7 @@ impl JsObject { arg_count, generator_resume_kind: GeneratorResumeKind::Normal, thrown: false, + async_generator: None, }); let _result = context.run(); @@ -982,6 +1015,7 @@ impl JsObject { arg_count, generator_resume_kind: GeneratorResumeKind::Normal, thrown: false, + async_generator: None, }; let mut stack = args; @@ -1018,6 +1052,155 @@ impl JsObject { init_result?; + Ok(generator.into()) + } + Function::AsyncGenerator { code, environments } => { + let code = code.clone(); + let mut environments = environments.clone(); + drop(object); + + std::mem::swap(&mut environments, &mut context.realm.environments); + + let lexical_this_mode = code.this_mode == ThisMode::Lexical; + + let this = if lexical_this_mode { + None + } else if code.strict { + Some(this.clone()) + } else if this.is_null_or_undefined() { + Some(context.global_object().clone().into()) + } else { + Some( + this.to_object(context) + .expect("conversion cannot fail") + .into(), + ) + }; + + if code.params.has_expressions() { + context.realm.environments.push_function( + code.num_bindings, + code.compile_environments[1].clone(), + this, + self.clone(), + None, + lexical_this_mode, + ); + } else { + context.realm.environments.push_function( + code.num_bindings, + code.compile_environments[0].clone(), + this, + self.clone(), + None, + lexical_this_mode, + ); + } + + if let Some(binding) = code.arguments_binding { + let arguments_obj = if code.strict || !code.params.is_simple() { + Arguments::create_unmapped_arguments_object(args, context) + } else { + let env = context.realm.environments.current(); + Arguments::create_mapped_arguments_object( + &this_function_object, + &code.params, + args, + &env, + context, + ) + }; + context.realm.environments.put_value( + binding.environment_index(), + binding.binding_index(), + arguments_obj.into(), + ); + } + + let arg_count = args.len(); + + // Push function arguments to the stack. + let mut args = if code.params.parameters.len() > args.len() { + let mut v = args.to_vec(); + v.extend(vec![ + JsValue::Undefined; + code.params.parameters.len() - args.len() + ]); + v + } else { + args.to_vec() + }; + args.reverse(); + + let param_count = code.params.parameters.len(); + + let call_frame = CallFrame { + code, + pc: 0, + catch: Vec::new(), + finally_return: FinallyReturn::None, + finally_jump: Vec::new(), + pop_on_return: 0, + loop_env_stack: Vec::from([0]), + try_env_stack: Vec::from([crate::vm::TryStackEntry { + num_env: 0, + num_loop_stack_entries: 0, + }]), + param_count, + arg_count, + generator_resume_kind: GeneratorResumeKind::Normal, + thrown: false, + async_generator: None, + }; + let mut stack = args; + + std::mem::swap(&mut context.vm.stack, &mut stack); + context.vm.push_frame(call_frame); + + let init_result = context.run(); + + let call_frame = context.vm.pop_frame().expect("frame must exist"); + std::mem::swap(&mut environments, &mut context.realm.environments); + std::mem::swap(&mut context.vm.stack, &mut stack); + + let prototype = if let Some(prototype) = this_function_object + .get("prototype", context) + .expect("AsyncGeneratorFunction must have a prototype property") + .as_object() + { + prototype.clone() + } else { + context + .intrinsics() + .constructors() + .async_generator() + .prototype() + }; + + let generator = Self::from_proto_and_data( + prototype, + ObjectData::async_generator(AsyncGenerator { + state: AsyncGeneratorState::SuspendedStart, + context: Some(Gc::new(Cell::new(GeneratorContext { + environments, + call_frame, + stack, + }))), + queue: VecDeque::new(), + }), + ); + + { + let mut generator_mut = generator.borrow_mut(); + let gen = generator_mut + .as_async_generator_mut() + .expect("must be object here"); + let mut gen_context = gen.context.as_ref().expect("must exist").borrow_mut(); + gen_context.call_frame.async_generator = Some(generator.clone()); + } + + init_result?; + Ok(generator.into()) } } @@ -1215,6 +1398,7 @@ impl JsObject { arg_count, generator_resume_kind: GeneratorResumeKind::Normal, thrown: false, + async_generator: None, }); let result = context.run(); @@ -1253,7 +1437,9 @@ impl JsObject { } } } - Function::Generator { .. } | Function::Async { .. } => { + Function::Generator { .. } + | Function::Async { .. } + | Function::AsyncGenerator { .. } => { unreachable!("not a constructor") } } diff --git a/boa_engine/src/vm/mod.rs b/boa_engine/src/vm/mod.rs index 625471c6eb..1c50e34a0c 100644 --- a/boa_engine/src/vm/mod.rs +++ b/boa_engine/src/vm/mod.rs @@ -4,6 +4,7 @@ use crate::{ builtins::{ + async_generator::{AsyncGenerator, AsyncGeneratorState}, function::{ConstructorKind, Function}, iterable::IteratorRecord, Array, ForInIterator, JsArgs, Number, Promise, @@ -1695,7 +1696,13 @@ impl Context { Opcode::GetGenerator => { let index = self.vm.read::(); let code = self.vm.frame().code.functions[index as usize].clone(); - let function = create_generator_function_object(code, self); + let function = create_generator_function_object(code, false, self); + self.vm.push(function); + } + Opcode::GetGeneratorAsync => { + let index = self.vm.read::(); + let code = self.vm.frame().code.functions[index as usize].clone(); + let function = create_generator_function_object(code, true, self); self.vm.push(function); } Opcode::CallEval => { @@ -2179,6 +2186,54 @@ impl Context { return Ok(ShouldExit::True); } }, + Opcode::AsyncGeneratorNext => { + let value = self.vm.pop(); + + if self.vm.frame().generator_resume_kind == GeneratorResumeKind::Throw { + return Err(value); + } + + let completion = Ok(value); + let generator_object = self + .vm + .frame() + .async_generator + .as_ref() + .expect("must be in generator context here") + .clone(); + let next = generator_object + .borrow_mut() + .as_async_generator_mut() + .expect("must be async generator object") + .queue + .pop_front() + .expect("must have item in queue"); + AsyncGenerator::complete_step(&next, completion, false, self); + + let mut generator_object_mut = generator_object.borrow_mut(); + let gen = generator_object_mut + .as_async_generator_mut() + .expect("must be async generator object"); + + if let Some(next) = gen.queue.front() { + let (completion, r#return) = &next.completion; + if *r#return { + match completion { + Ok(value) | Err(value) => self.vm.push(value), + } + self.vm.push(true); + } else { + self.vm.push(completion.clone()?); + self.vm.push(false); + } + + self.vm.push(false); + } else { + gen.state = AsyncGeneratorState::SuspendedYield; + self.vm.push(true); + self.vm.push(true); + } + } Opcode::GeneratorNextDelegate => { let done_address = self.vm.read::(); let received = self.vm.pop(); @@ -2483,6 +2538,24 @@ impl Context { .call(&JsValue::undefined(), &[result.clone()], self) .expect("cannot fail per spec"); } + // Step 4.e-j in [AsyncGeneratorStart](https://tc39.es/ecma262/#sec-asyncgeneratorstart) + else if let Some(generator_object) = self.vm.frame().async_generator.clone() { + let mut generator_object_mut = generator_object.borrow_mut(); + let generator = generator_object_mut + .as_async_generator_mut() + .expect("must be async generator"); + + generator.state = AsyncGeneratorState::Completed; + + let next = generator + .queue + .pop_front() + .expect("must have item in queue"); + drop(generator_object_mut); + AsyncGenerator::complete_step(&next, Ok(result), true, self); + AsyncGenerator::drain_queue(&generator_object, self); + return Ok((JsValue::undefined(), ReturnType::Normal)); + } return Ok((result, ReturnType::Normal)); } @@ -2548,6 +2621,26 @@ impl Context { return Ok((e, ReturnType::Normal)); } + // Step 4.e-j in [AsyncGeneratorStart](https://tc39.es/ecma262/#sec-asyncgeneratorstart) + else if let Some(generator_object) = + self.vm.frame().async_generator.clone() + { + let mut generator_object_mut = generator_object.borrow_mut(); + let generator = generator_object_mut + .as_async_generator_mut() + .expect("must be async generator"); + + generator.state = AsyncGeneratorState::Completed; + + let next = generator + .queue + .pop_front() + .expect("must have item in queue"); + drop(generator_object_mut); + AsyncGenerator::complete_step(&next, Err(e), true, self); + AsyncGenerator::drain_queue(&generator_object, self); + return Ok((JsValue::undefined(), ReturnType::Normal)); + } return Err(e); } @@ -2586,6 +2679,23 @@ impl Context { .call(&JsValue::undefined(), &[], self) .expect("cannot fail per spec"); } + // Step 4.e-j in [AsyncGeneratorStart](https://tc39.es/ecma262/#sec-asyncgeneratorstart) + else if let Some(generator_object) = self.vm.frame().async_generator.clone() { + let mut generator_object_mut = generator_object.borrow_mut(); + let generator = generator_object_mut + .as_async_generator_mut() + .expect("must be async generator"); + + generator.state = AsyncGeneratorState::Completed; + + let next = generator + .queue + .pop_front() + .expect("must have item in queue"); + drop(generator_object_mut); + AsyncGenerator::complete_step(&next, Ok(JsValue::undefined()), true, self); + AsyncGenerator::drain_queue(&generator_object, self); + } return Ok((JsValue::undefined(), ReturnType::Normal)); } @@ -2600,6 +2710,24 @@ impl Context { .call(&JsValue::undefined(), &[result.clone()], self) .expect("cannot fail per spec"); } + // Step 4.e-j in [AsyncGeneratorStart](https://tc39.es/ecma262/#sec-asyncgeneratorstart) + else if let Some(generator_object) = self.vm.frame().async_generator.clone() { + let mut generator_object_mut = generator_object.borrow_mut(); + let generator = generator_object_mut + .as_async_generator_mut() + .expect("must be async generator"); + + generator.state = AsyncGeneratorState::Completed; + + let next = generator + .queue + .pop_front() + .expect("must have item in queue"); + drop(generator_object_mut); + AsyncGenerator::complete_step(&next, Ok(result), true, self); + AsyncGenerator::drain_queue(&generator_object, self); + return Ok((JsValue::undefined(), ReturnType::Normal)); + } Ok((result, ReturnType::Normal)) } diff --git a/boa_engine/src/vm/opcode.rs b/boa_engine/src/vm/opcode.rs index 72303c9359..dfc77e0ace 100644 --- a/boa_engine/src/vm/opcode.rs +++ b/boa_engine/src/vm/opcode.rs @@ -927,6 +927,13 @@ pub enum Opcode { /// Stack: **=>** func GetGenerator, + /// Get async generator function from the pre-compiled inner functions. + /// + /// Operands: address: `u32` + /// + /// Stack: **=>** func + GetGeneratorAsync, + /// Call a function named "eval". /// /// Operands: argument_count: `u32` @@ -1125,6 +1132,13 @@ pub enum Opcode { /// Stack: received **=>** GeneratorNext, + /// Resumes the current generator function. + /// + /// Operands: + /// + /// Stack: received **=>** Option, skip_0, skip_1 + AsyncGeneratorNext, + /// Delegates the current generator function another generator. /// /// Operands: done_address: `u32` @@ -1285,6 +1299,7 @@ impl Opcode { Self::GetFunction => "GetFunction", Self::GetFunctionAsync => "GetFunctionAsync", Self::GetGenerator => "GetGenerator", + Self::GetGeneratorAsync => "GetGeneratorAsync", Self::CallEval => "CallEval", Self::CallEvalWithRest => "CallEvalWithRest", Self::Call => "Call", @@ -1313,6 +1328,7 @@ impl Opcode { Self::PopOnReturnSub => "PopOnReturnSub", Self::Yield => "Yield", Self::GeneratorNext => "GeneratorNext", + Self::AsyncGeneratorNext => "AsyncGeneratorNext", Self::Await => "Await", Self::GeneratorNextDelegate => "GeneratorNextDelegate", Self::Nop => "Nop", @@ -1425,6 +1441,7 @@ impl Opcode { Self::GetFunction => "INST - GetFunction", Self::GetFunctionAsync => "INST - GetFunctionAsync", Self::GetGenerator => "INST - GetGenerator", + Self::GetGeneratorAsync => "INST - GetGeneratorAsync", Self::CallEval => "INST - CallEval", Self::CallEvalWithRest => "INST - CallEvalWithRest", Self::Call => "INST - Call", @@ -1453,6 +1470,7 @@ impl Opcode { Self::PopOnReturnSub => "INST - PopOnReturnSub", Self::Yield => "INST - Yield", Self::GeneratorNext => "INST - GeneratorNext", + Self::AsyncGeneratorNext => "INST - AsyncGeneratorNext", Self::Await => "INST - Await", Self::GeneratorNextDelegate => "INST - GeneratorNextDelegate", Self::Nop => "INST - Nop",