Browse Source

Implement Async Generators (#2200)

This Pull Request fixes #1560.

It changes the following:

- Implement `AsyncGeneratorFunction` builtin object.
- Implement `AsyncGenerator` builtin object.
- Implement async generator execution.
- Add some parse errors for async generators.

The `AsyncGenerator.prototype.return()` method currently does not work correctly with `finally` blocks, but I think we should merge this first to not increase the complexity of the pr too much.
pull/2213/head
raskad 2 years ago
parent
commit
5909e9a963
  1. 754
      boa_engine/src/builtins/async_generator/mod.rs
  2. 129
      boa_engine/src/builtins/async_generator_function/mod.rs
  3. 67
      boa_engine/src/builtins/function/mod.rs
  4. 30
      boa_engine/src/builtins/iterable/mod.rs
  5. 11
      boa_engine/src/builtins/mod.rs
  6. 6
      boa_engine/src/builtins/promise/mod.rs
  7. 194
      boa_engine/src/bytecompiler/function.rs
  8. 256
      boa_engine/src/bytecompiler/mod.rs
  9. 14
      boa_engine/src/context/intrinsics.rs
  10. 1
      boa_engine/src/context/mod.rs
  11. 66
      boa_engine/src/object/mod.rs
  12. 6
      boa_engine/src/syntax/ast/node/declaration/async_generator_decl/mod.rs
  13. 2
      boa_engine/src/syntax/ast/node/mod.rs
  14. 13
      boa_engine/src/syntax/ast/node/parameters.rs
  15. 16
      boa_engine/src/syntax/parser/expression/primary/async_generator_expression/mod.rs
  16. 24
      boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs
  17. 8
      boa_engine/src/vm/call_frame.rs
  18. 204
      boa_engine/src/vm/code_block.rs
  19. 130
      boa_engine/src/vm/mod.rs
  20. 18
      boa_engine/src/vm/opcode.rs

754
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<JsValue>, 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<Gc<Cell<GeneratorContext>>>,
/// The `[[AsyncGeneratorQueue]]` internal slot.
pub(crate) queue: VecDeque<AsyncGeneratorRequest>,
}
impl BuiltIn for AsyncGenerator {
const NAME: &'static str = "AsyncGenerator";
fn init(context: &mut Context) -> Option<JsValue> {
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<JsValue> {
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<JsValue> {
// 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<JsValue> {
// 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<JsValue> {
// 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<JsValue>, 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<JsValue>,
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<Cell<GeneratorContext>>,
completion: (JsResult<JsValue>, 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<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]]).
// 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;
}
}
}
}
}
}

129
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<JsValue> {
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<JsValue> {
BuiltInFunctionObject::create_dynamic_function(new_target, args, true, true, context)
.map(Into::into)
}
}

67
boa_engine/src/builtins/function/mod.rs

@ -13,6 +13,7 @@
use crate::{ use crate::{
builtins::{BuiltIn, JsArgs}, builtins::{BuiltIn, JsArgs},
bytecompiler::{FunctionCompiler, FunctionKind},
context::intrinsics::StandardConstructors, context::intrinsics::StandardConstructors,
environments::DeclarativeEnvironmentStack, environments::DeclarativeEnvironmentStack,
object::{ object::{
@ -246,6 +247,10 @@ pub enum Function {
code: Gc<crate::vm::CodeBlock>, code: Gc<crate::vm::CodeBlock>,
environments: DeclarativeEnvironmentStack, environments: DeclarativeEnvironmentStack,
}, },
AsyncGenerator {
code: Gc<crate::vm::CodeBlock>,
environments: DeclarativeEnvironmentStack,
},
} }
unsafe impl Trace for Function { unsafe impl Trace for Function {
@ -267,7 +272,8 @@ unsafe impl Trace for Function {
mark(environments); mark(environments);
mark(promise_capability); mark(promise_capability);
} }
Self::Generator { code, environments } => { Self::Generator { code, environments }
| Self::AsyncGenerator { code, environments } => {
mark(code); mark(code);
mark(environments); mark(environments);
} }
@ -288,7 +294,7 @@ impl Function {
Self::Native { constructor, .. } | Self::Closure { constructor, .. } => { Self::Native { constructor, .. } | Self::Closure { constructor, .. } => {
constructor.is_some() constructor.is_some()
} }
Self::Generator { .. } | Self::Async { .. } => false, Self::Generator { .. } | Self::AsyncGenerator { .. } | Self::Async { .. } => false,
Self::Ordinary { code, .. } => !(code.this_mode == ThisMode::Lexical), Self::Ordinary { code, .. } => !(code.this_mode == ThisMode::Lexical),
} }
} }
@ -476,7 +482,9 @@ impl BuiltInFunctionObject {
generator: bool, generator: bool,
context: &mut Context, context: &mut Context,
) -> JsResult<JsObject> { ) -> JsResult<JsObject> {
let default = if r#async { let default = if r#async && generator {
StandardConstructors::async_generator_function
} else if r#async {
StandardConstructors::async_function StandardConstructors::async_function
} else if generator { } else if generator {
StandardConstructors::generator_function StandardConstructors::generator_function
@ -518,6 +526,20 @@ impl BuiltInFunctionObject {
parameters 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_arg = body_arg.to_string(context)?;
let body = match Parser::new(body_arg.as_bytes()).parse_function_body( 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( let code = FunctionCompiler::new()
crate::bytecompiler::FunctionKind::Expression, .name(Sym::ANONYMOUS)
Some(Sym::ANONYMOUS), .generator(generator)
&parameters, .r#async(r#async)
&body, .kind(FunctionKind::Expression)
generator, .compile(&parameters, &body, context)?;
false,
context,
)?;
let environments = context.realm.environments.pop_to_global(); let environments = context.realm.environments.pop_to_global();
let function_object = if generator { let function_object = if generator {
crate::vm::create_generator_function_object(code, context) crate::vm::create_generator_function_object(code, r#async, context)
} else { } else {
crate::vm::create_function_object(code, r#async, Some(prototype), context) crate::vm::create_function_object(code, r#async, Some(prototype), context)
}; };
@ -603,29 +622,29 @@ impl BuiltInFunctionObject {
Ok(function_object) Ok(function_object)
} else if generator { } else if generator {
let code = crate::bytecompiler::ByteCompiler::compile_function_code( let code = FunctionCompiler::new()
crate::bytecompiler::FunctionKind::Expression, .name(Sym::ANONYMOUS)
Some(Sym::ANONYMOUS), .generator(true)
.kind(FunctionKind::Expression)
.compile(
&FormalParameterList::empty(), &FormalParameterList::empty(),
&StatementList::default(), &StatementList::default(),
true,
false,
context, context,
)?; )?;
let environments = context.realm.environments.pop_to_global(); 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); context.realm.environments.extend(environments);
Ok(function_object) Ok(function_object)
} else { } else {
let code = crate::bytecompiler::ByteCompiler::compile_function_code( let code = FunctionCompiler::new()
crate::bytecompiler::FunctionKind::Expression, .name(Sym::ANONYMOUS)
Some(Sym::ANONYMOUS), .kind(FunctionKind::Expression)
.compile(
&FormalParameterList::empty(), &FormalParameterList::empty(),
&StatementList::default(), &StatementList::default(),
false,
false,
context, context,
)?; )?;

30
boa_engine/src/builtins/iterable/mod.rs

@ -14,6 +14,8 @@ use boa_profiler::Profiler;
pub struct IteratorPrototypes { pub struct IteratorPrototypes {
/// %IteratorPrototype% /// %IteratorPrototype%
iterator_prototype: JsObject, iterator_prototype: JsObject,
/// %AsyncIteratorPrototype%
async_iterator_prototype: JsObject,
/// %MapIteratorPrototype% /// %MapIteratorPrototype%
array_iterator: JsObject, array_iterator: JsObject,
/// %SetIteratorPrototype% /// %SetIteratorPrototype%
@ -33,6 +35,7 @@ impl IteratorPrototypes {
let _timer = Profiler::global().start_event("IteratorPrototypes::init", "init"); let _timer = Profiler::global().start_event("IteratorPrototypes::init", "init");
let iterator_prototype = create_iterator_prototype(context); let iterator_prototype = create_iterator_prototype(context);
let async_iterator_prototype = create_async_iterator_prototype(context);
Self { Self {
array_iterator: ArrayIterator::create_prototype(iterator_prototype.clone(), context), array_iterator: ArrayIterator::create_prototype(iterator_prototype.clone(), context),
set_iterator: SetIterator::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), map_iterator: MapIterator::create_prototype(iterator_prototype.clone(), context),
for_in_iterator: ForInIterator::create_prototype(iterator_prototype.clone(), context), for_in_iterator: ForInIterator::create_prototype(iterator_prototype.clone(), context),
iterator_prototype, iterator_prototype,
async_iterator_prototype,
} }
} }
@ -57,6 +61,11 @@ impl IteratorPrototypes {
self.iterator_prototype.clone() self.iterator_prototype.clone()
} }
#[inline]
pub fn async_iterator_prototype(&self) -> JsObject {
self.async_iterator_prototype.clone()
}
#[inline] #[inline]
pub fn set_iterator(&self) -> JsObject { pub fn set_iterator(&self) -> JsObject {
self.set_iterator.clone() self.set_iterator.clone()
@ -506,3 +515,24 @@ macro_rules! if_abrupt_close_iterator {
// Export macro to crate level // Export macro to crate level
pub(crate) use if_abrupt_close_iterator; 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
}

11
boa_engine/src/builtins/mod.rs

@ -3,6 +3,8 @@
pub mod array; pub mod array;
pub mod array_buffer; pub mod array_buffer;
pub mod async_function; pub mod async_function;
pub mod async_generator;
pub mod async_generator_function;
pub mod bigint; pub mod bigint;
pub mod boolean; pub mod boolean;
pub mod dataview; pub mod dataview;
@ -77,8 +79,9 @@ pub(crate) use self::{
use crate::{ use crate::{
builtins::{ builtins::{
array_buffer::ArrayBuffer, generator::Generator, generator_function::GeneratorFunction, array_buffer::ArrayBuffer, async_generator::AsyncGenerator,
typed_array::TypedArray, async_generator_function::AsyncGeneratorFunction, generator::Generator,
generator_function::GeneratorFunction, typed_array::TypedArray,
}, },
property::{Attribute, PropertyDescriptor}, property::{Attribute, PropertyDescriptor},
Context, JsValue, Context, JsValue,
@ -188,7 +191,9 @@ pub fn init(context: &mut Context) {
Generator, Generator,
GeneratorFunction, GeneratorFunction,
Promise, Promise,
AsyncFunction AsyncFunction,
AsyncGenerator,
AsyncGeneratorFunction
}; };
#[cfg(feature = "intl")] #[cfg(feature = "intl")]

6
boa_engine/src/builtins/promise/mod.rs

@ -40,13 +40,13 @@ macro_rules! if_abrupt_reject_promise {
Err(value) => { Err(value) => {
// a. Perform ? Call(capability.[[Reject]], undefined, « value.[[Value]] »). // a. Perform ? Call(capability.[[Reject]], undefined, « value.[[Value]] »).
$context.call( $context.call(
&$capability.reject.clone().into(), &$capability.reject().clone().into(),
&JsValue::undefined(), &JsValue::undefined(),
&[value], &[value],
)?; )?;
// b. Return capability.[[Promise]]. // 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]]. // 2. Else if value is a Completion Record, set value to value.[[Value]].
Ok(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)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum PromiseState { enum PromiseState {
Pending, Pending,

194
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<N>(mut self, name: N) -> Self
where
N: Into<Option<Sym>>,
{
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<Gc<CodeBlock>> {
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()))
}
}

256
boa_engine/src/bytecompiler.rs → boa_engine/src/bytecompiler/mod.rs

@ -1,5 +1,6 @@
mod function;
use crate::{ use crate::{
builtins::function::ThisMode,
environments::{BindingLocator, CompileTimeEnvironment}, environments::{BindingLocator, CompileTimeEnvironment},
syntax::ast::{ syntax::ast::{
node::{ node::{
@ -11,8 +12,7 @@ use crate::{
object::{MethodDefinition, PropertyDefinition, PropertyName}, object::{MethodDefinition, PropertyDefinition, PropertyName},
operator::assign::AssignTarget, operator::assign::AssignTarget,
template::TemplateElement, template::TemplateElement,
Class, Declaration, FormalParameterList, GetConstField, GetField, GetSuperField, Class, Declaration, GetConstField, GetField, GetSuperField,
StatementList,
}, },
op::{AssignOp, BinOp, BitOp, CompOp, LogOp, NumOp, UnaryOp}, op::{AssignOp, BinOp, BitOp, CompOp, LogOp, NumOp, UnaryOp},
Const, Node, Const, Node,
@ -25,6 +25,8 @@ use boa_interner::{Interner, Sym};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use std::mem::size_of; use std::mem::size_of;
pub(crate) use function::FunctionCompiler;
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum Literal { enum Literal {
String(JsString), String(JsString),
@ -72,6 +74,7 @@ pub struct ByteCompiler<'b> {
names_map: FxHashMap<Sym, u32>, names_map: FxHashMap<Sym, u32>,
bindings_map: FxHashMap<BindingLocator, u32>, bindings_map: FxHashMap<BindingLocator, u32>,
jump_info: Vec<JumpControlInfo>, jump_info: Vec<JumpControlInfo>,
in_async_generator: bool,
context: &'b mut Context, context: &'b mut Context,
} }
@ -87,6 +90,7 @@ impl<'b> ByteCompiler<'b> {
names_map: FxHashMap::default(), names_map: FxHashMap::default(),
bindings_map: FxHashMap::default(), bindings_map: FxHashMap::default(),
jump_info: Vec::new(), jump_info: Vec::new(),
in_async_generator: false,
context, context,
} }
} }
@ -896,8 +900,7 @@ impl<'b> ByteCompiler<'b> {
self.emit_opcode(Opcode::DefineOwnPropertyByValue); self.emit_opcode(Opcode::DefineOwnPropertyByValue);
} }
}, },
PropertyDefinition::MethodDefinition(kind, name) => { PropertyDefinition::MethodDefinition(kind, name) => match kind {
match kind {
MethodDefinition::Get(expr) => match name { MethodDefinition::Get(expr) => match name {
PropertyName::Literal(name) => { PropertyName::Literal(name) => {
self.function(&expr.clone().into(), true)?; self.function(&expr.clone().into(), true)?;
@ -968,12 +971,9 @@ impl<'b> ByteCompiler<'b> {
self.emit_opcode(Opcode::DefineOwnPropertyByValue); self.emit_opcode(Opcode::DefineOwnPropertyByValue);
} }
}, },
// TODO: Implement async generators MethodDefinition::AsyncGenerator(expr) => match name {
MethodDefinition::AsyncGenerator(_) => {
// TODO: Implement async
match name {
PropertyName::Literal(name) => { PropertyName::Literal(name) => {
self.emit_opcode(Opcode::PushUndefined); self.function(&expr.clone().into(), true)?;
self.emit_opcode(Opcode::Swap); self.emit_opcode(Opcode::Swap);
let index = self.get_or_insert_name(*name); let index = self.get_or_insert_name(*name);
self.emit(Opcode::DefineOwnPropertyByName, &[index]); self.emit(Opcode::DefineOwnPropertyByName, &[index]);
@ -981,13 +981,11 @@ impl<'b> ByteCompiler<'b> {
PropertyName::Computed(name_node) => { PropertyName::Computed(name_node) => {
self.compile_stmt(name_node, true)?; self.compile_stmt(name_node, true)?;
self.emit_opcode(Opcode::ToPropertyKey); self.emit_opcode(Opcode::ToPropertyKey);
self.emit_opcode(Opcode::PushUndefined); self.function(&expr.clone().into(), true)?;
self.emit_opcode(Opcode::DefineOwnPropertyByValue); self.emit_opcode(Opcode::DefineOwnPropertyByValue);
} }
} },
} },
}
}
PropertyDefinition::SpreadObject(expr) => { PropertyDefinition::SpreadObject(expr) => {
self.compile_expr(expr, true)?; self.compile_expr(expr, true)?;
self.emit_opcode(Opcode::Swap); self.emit_opcode(Opcode::Swap);
@ -1144,11 +1142,9 @@ impl<'b> ByteCompiler<'b> {
self.emit_opcode(Opcode::Pop); self.emit_opcode(Opcode::Pop);
} }
} }
// TODO: implement AsyncGeneratorExpr Node::GeneratorExpr(_) | Node::AsyncFunctionExpr(_) | Node::AsyncGeneratorExpr(_) => {
Node::AsyncGeneratorExpr(_) => { self.function(expr, use_expr)?;
self.emit_opcode(Opcode::PushUndefined);
} }
Node::GeneratorExpr(_) | Node::AsyncFunctionExpr(_) => self.function(expr, use_expr)?,
Node::Yield(r#yield) => { Node::Yield(r#yield) => {
if let Some(expr) = r#yield.expr() { if let Some(expr) = r#yield.expr() {
self.compile_expr(expr, true)?; self.compile_expr(expr, true)?;
@ -1163,6 +1159,17 @@ impl<'b> ByteCompiler<'b> {
let start = self.emit_opcode_with_operand(Opcode::GeneratorNextDelegate); let start = self.emit_opcode_with_operand(Opcode::GeneratorNextDelegate);
self.emit(Opcode::Jump, &[start_address]); self.emit(Opcode::Jump, &[start_address]);
self.patch_jump(start); 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 { } else {
self.emit_opcode(Opcode::Yield); self.emit_opcode(Opcode::Yield);
self.emit_opcode(Opcode::GeneratorNext); self.emit_opcode(Opcode::GeneratorNext);
@ -1942,10 +1949,8 @@ impl<'b> ByteCompiler<'b> {
self.pop_try_control_info(None); self.pop_try_control_info(None);
} }
} }
Node::GeneratorDecl(_) | Node::AsyncFunctionDecl(_) => self.function(node, false)?, Node::GeneratorDecl(_) | Node::AsyncFunctionDecl(_) | Node::AsyncGeneratorDecl(_) => {
// TODO: implement AsyncGeneratorDecl self.function(node, false)?;
Node::AsyncGeneratorDecl(_) => {
self.emit_opcode(Opcode::PushUndefined);
} }
Node::ClassDecl(class) => self.class(class, false)?, Node::ClassDecl(class) => self.class(class, false)?,
Node::Empty => {} Node::Empty => {}
@ -1954,126 +1959,6 @@ impl<'b> ByteCompiler<'b> {
Ok(()) Ok(())
} }
/// Compile a function statement list and it's parameters into bytecode.
pub(crate) fn compile_function_code(
kind: FunctionKind,
name: Option<Sym>,
parameters: &FormalParameterList,
body: &StatementList,
generator: bool,
strict: bool,
context: &mut Context,
) -> JsResult<Gc<CodeBlock>> {
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. /// Compile a function AST Node into bytecode.
pub(crate) fn function(&mut self, function: &Node, use_expr: bool) -> JsResult<()> { pub(crate) fn function(&mut self, function: &Node, use_expr: bool) -> JsResult<()> {
let (kind, name, parameters, body, generator, r#async) = match function { let (kind, name, parameters, body, generator, r#async) = match function {
@ -2101,6 +1986,14 @@ impl<'b> ByteCompiler<'b> {
true, true,
false, false,
), ),
Node::AsyncGeneratorDecl(generator) => (
FunctionKind::Declaration,
Some(generator.name()),
generator.parameters(),
generator.body(),
true,
true,
),
Node::FunctionExpr(function) => ( Node::FunctionExpr(function) => (
FunctionKind::Expression, FunctionKind::Expression,
function.name(), function.name(),
@ -2125,6 +2018,14 @@ impl<'b> ByteCompiler<'b> {
true, true,
false, false,
), ),
Node::AsyncGeneratorExpr(generator) => (
FunctionKind::Expression,
generator.name(),
generator.parameters(),
generator.body(),
true,
true,
),
Node::ArrowFunctionDecl(function) => ( Node::ArrowFunctionDecl(function) => (
FunctionKind::Arrow, FunctionKind::Arrow,
function.name(), function.name(),
@ -2136,20 +2037,20 @@ impl<'b> ByteCompiler<'b> {
_ => unreachable!(), _ => unreachable!(),
}; };
let code = Self::compile_function_code( let code = FunctionCompiler::new()
kind, .name(name)
name, .generator(generator)
parameters, .r#async(r#async)
body, .strict(self.code_block.strict)
generator, .kind(kind)
self.code_block.strict, .compile(parameters, body, self.context)?;
self.context,
)?;
let index = self.code_block.functions.len() as u32; let index = self.code_block.functions.len() as u32;
self.code_block.functions.push(code); 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]); self.emit(Opcode::GetGenerator, &[index]);
} else if r#async { } else if r#async {
self.emit(Opcode::GetFunctionAsync, &[index]); self.emit(Opcode::GetFunctionAsync, &[index]);
@ -2627,6 +2528,7 @@ impl<'b> ByteCompiler<'b> {
names_map: FxHashMap::default(), names_map: FxHashMap::default(),
bindings_map: FxHashMap::default(), bindings_map: FxHashMap::default(),
jump_info: Vec::new(), jump_info: Vec::new(),
in_async_generator: false,
context: self.context, context: self.context,
}; };
compiler.context.push_compile_time_environment(true); compiler.context.push_compile_time_environment(true);
@ -2806,8 +2708,20 @@ impl<'b> ByteCompiler<'b> {
self.emit_opcode(Opcode::DefineClassMethodByValue); self.emit_opcode(Opcode::DefineClassMethodByValue);
} }
}, },
// TODO: implement async MethodDefinition::AsyncGenerator(expr) => match name {
MethodDefinition::AsyncGenerator(_) => {} 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) => { ClassElement::PrivateStaticMethodDefinition(name, method_definition) => {
@ -2838,8 +2752,11 @@ impl<'b> ByteCompiler<'b> {
let index = self.get_or_insert_name(*name); let index = self.get_or_insert_name(*name);
self.emit(Opcode::SetPrivateMethod, &[index]); self.emit(Opcode::SetPrivateMethod, &[index]);
} }
// TODO: implement async MethodDefinition::AsyncGenerator(expr) => {
MethodDefinition::AsyncGenerator(_) => {} self.function(&expr.clone().into(), true)?;
let index = self.get_or_insert_name(*name);
self.emit(Opcode::SetPrivateMethod, &[index]);
}
} }
} }
ClassElement::FieldDefinition(name, field) => { ClassElement::FieldDefinition(name, field) => {
@ -2861,6 +2778,7 @@ impl<'b> ByteCompiler<'b> {
names_map: FxHashMap::default(), names_map: FxHashMap::default(),
bindings_map: FxHashMap::default(), bindings_map: FxHashMap::default(),
jump_info: Vec::new(), jump_info: Vec::new(),
in_async_generator: false,
context: self.context, context: self.context,
}; };
field_compiler.context.push_compile_time_environment(true); field_compiler.context.push_compile_time_environment(true);
@ -2894,6 +2812,7 @@ impl<'b> ByteCompiler<'b> {
names_map: FxHashMap::default(), names_map: FxHashMap::default(),
bindings_map: FxHashMap::default(), bindings_map: FxHashMap::default(),
jump_info: Vec::new(), jump_info: Vec::new(),
in_async_generator: false,
context: self.context, context: self.context,
}; };
field_compiler.context.push_compile_time_environment(true); field_compiler.context.push_compile_time_environment(true);
@ -3002,8 +2921,11 @@ impl<'b> ByteCompiler<'b> {
let index = self.get_or_insert_name(*name); let index = self.get_or_insert_name(*name);
self.emit(Opcode::PushClassPrivateMethod, &[index]); self.emit(Opcode::PushClassPrivateMethod, &[index]);
} }
// TODO: implement async MethodDefinition::AsyncGenerator(expr) => {
MethodDefinition::AsyncGenerator(_) => {} self.function(&expr.clone().into(), true)?;
let index = self.get_or_insert_name(*name);
self.emit(Opcode::PushClassPrivateMethod, &[index]);
}
} }
} }
ClassElement::MethodDefinition(..) => {} ClassElement::MethodDefinition(..) => {}
@ -3087,10 +3009,20 @@ impl<'b> ByteCompiler<'b> {
self.emit_opcode(Opcode::DefineClassMethodByValue); self.emit_opcode(Opcode::DefineClassMethodByValue);
} }
}, },
// TODO: implement async MethodDefinition::AsyncGenerator(expr) => match name {
MethodDefinition::AsyncGenerator(_) => { PropertyName::Literal(name) => {
self.emit_opcode(Opcode::Pop); 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(..) ClassElement::PrivateMethodDefinition(..)

14
boa_engine/src/context/intrinsics.rs

@ -72,6 +72,8 @@ impl StandardConstructor {
/// Cached core standard constructors. /// Cached core standard constructors.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct StandardConstructors { pub struct StandardConstructors {
async_generator_function: StandardConstructor,
async_generator: StandardConstructor,
object: StandardConstructor, object: StandardConstructor,
proxy: StandardConstructor, proxy: StandardConstructor,
date: StandardConstructor, date: StandardConstructor,
@ -117,6 +119,8 @@ pub struct StandardConstructors {
impl Default for StandardConstructors { impl Default for StandardConstructors {
fn default() -> Self { fn default() -> Self {
let result = Self { let result = Self {
async_generator_function: StandardConstructor::default(),
async_generator: StandardConstructor::default(),
object: StandardConstructor::default(), object: StandardConstructor::default(),
proxy: StandardConstructor::default(), proxy: StandardConstructor::default(),
date: StandardConstructor::default(), date: StandardConstructor::default(),
@ -187,6 +191,16 @@ impl Default for StandardConstructors {
} }
impl 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] #[inline]
pub fn object(&self) -> &StandardConstructor { pub fn object(&self) -> &StandardConstructor {
&self.object &self.object

1
boa_engine/src/context/mod.rs

@ -729,6 +729,7 @@ impl Context {
arg_count: 0, arg_count: 0,
generator_resume_kind: GeneratorResumeKind::Normal, generator_resume_kind: GeneratorResumeKind::Normal,
thrown: false, thrown: false,
async_generator: None,
}); });
self.realm.set_global_binding_number(); self.realm.set_global_binding_number();

66
boa_engine/src/object/mod.rs

@ -26,6 +26,7 @@ use crate::{
builtins::{ builtins::{
array::array_iterator::ArrayIterator, array::array_iterator::ArrayIterator,
array_buffer::ArrayBuffer, array_buffer::ArrayBuffer,
async_generator::AsyncGenerator,
function::arguments::Arguments, function::arguments::Arguments,
function::{ function::{
arguments::ParameterMap, BoundFunction, Captures, ConstructorKind, Function, arguments::ParameterMap, BoundFunction, Captures, ConstructorKind, Function,
@ -164,6 +165,8 @@ pub struct ObjectData {
/// Defines the different types of objects. /// Defines the different types of objects.
#[derive(Debug, Trace, Finalize)] #[derive(Debug, Trace, Finalize)]
pub enum ObjectKind { pub enum ObjectKind {
AsyncGenerator(AsyncGenerator),
AsyncGeneratorFunction(Function),
Array, Array,
ArrayIterator(ArrayIterator), ArrayIterator(ArrayIterator),
ArrayBuffer(ArrayBuffer), ArrayBuffer(ArrayBuffer),
@ -199,6 +202,26 @@ pub enum ObjectKind {
} }
impl ObjectData { 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 /// Create the `Array` object data and reference its exclusive internal methods
pub fn array() -> Self { pub fn array() -> Self {
Self { Self {
@ -474,6 +497,8 @@ impl ObjectData {
impl Display for ObjectKind { impl Display for ObjectKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self { f.write_str(match self {
Self::AsyncGenerator(_) => "AsyncGenerator",
Self::AsyncGeneratorFunction(_) => "AsyncGeneratorFunction",
Self::Array => "Array", Self::Array => "Array",
Self::ArrayIterator(_) => "ArrayIterator", Self::ArrayIterator(_) => "ArrayIterator",
Self::ArrayBuffer(_) => "ArrayBuffer", Self::ArrayBuffer(_) => "ArrayBuffer",
@ -539,6 +564,42 @@ impl Object {
&self.data.kind &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. /// Checks if it an `Array` object.
#[inline] #[inline]
pub fn is_array(&self) -> bool { pub fn is_array(&self) -> bool {
@ -1601,7 +1662,10 @@ impl<'context> FunctionBuilder<'context> {
} => { } => {
*constructor = yes.then(|| ConstructorKind::Base); *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"); unreachable!("function must be native or closure");
} }
} }

6
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. /// Gets the body of the async function declaration.
pub fn body(&self) -> &[Node] { pub fn body(&self) -> &StatementList {
self.body.items() &self.body
} }
/// Implements the display formatting with indentation. /// Implements the display formatting with indentation.
@ -60,7 +60,7 @@ impl AsyncGeneratorDecl {
interner.resolve_expect(self.name), interner.resolve_expect(self.name),
join_nodes(interner, &self.parameters.parameters) join_nodes(interner, &self.parameters.parameters)
); );
if self.body().is_empty() { if self.body().items().is_empty() {
buf.push_str(") {}"); buf.push_str(") {}");
} else { } else {
buf.push_str(&format!( buf.push_str(&format!(

2
boa_engine/src/syntax/ast/node/mod.rs

@ -945,6 +945,7 @@ impl Node {
return true; return true;
} }
} }
Node::AwaitExpr(_) if symbol == ContainsSymbol::AwaitExpression => return true,
Node::AwaitExpr(expr) => { Node::AwaitExpr(expr) => {
if expr.expr().contains(symbol) { if expr.expr().contains(symbol) {
return true; return true;
@ -1259,6 +1260,7 @@ pub(crate) enum ContainsSymbol {
SuperProperty, SuperProperty,
SuperCall, SuperCall,
YieldExpression, YieldExpression,
AwaitExpression,
} }
impl ToInternedString for Node { impl ToInternedString for Node {

13
boa_engine/src/syntax/ast/node/parameters.rs

@ -114,6 +114,19 @@ impl FormalParameterList {
} }
false 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<Vec<FormalParameter>> for FormalParameterList { impl From<Vec<FormalParameter>> for FormalParameterList {

16
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)?; 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( cursor.expect(
Punctuator::CloseParen, Punctuator::CloseParen,
"async generator expression", "async generator expression",

24
boa_engine/src/syntax/parser/expression/primary/object_initializer/mod.rs

@ -780,8 +780,32 @@ where
let name = let name =
ClassElementName::new(self.allow_yield, self.allow_await).parse(cursor, interner)?; 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)?; 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 let body_start = cursor
.expect( .expect(
TokenKind::Punctuator(Punctuator::OpenBlock), TokenKind::Punctuator(Punctuator::OpenBlock),

8
boa_engine/src/vm/call_frame.rs

@ -2,7 +2,7 @@
//! //!
//! This module will provides everything needed to implement the `CallFrame` //! 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}; use boa_gc::{Finalize, Gc, Trace};
#[derive(Clone, Debug, Finalize, Trace)] #[derive(Clone, Debug, Finalize, Trace)]
@ -32,6 +32,10 @@ pub struct CallFrame {
// Indicate that the last try block has thrown an exception. // Indicate that the last try block has thrown an exception.
pub(crate) thrown: bool, 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<JsObject>,
} }
impl CallFrame { impl CallFrame {
@ -111,7 +115,7 @@ pub(crate) enum FinallyReturn {
} }
/// Indicates how a generator function that has been called/resumed should return. /// 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 { pub(crate) enum GeneratorResumeKind {
Normal, Normal,
Throw, Throw,

204
boa_engine/src/vm/code_block.rs

@ -4,6 +4,7 @@
use crate::{ use crate::{
builtins::{ builtins::{
async_generator::{AsyncGenerator, AsyncGeneratorState},
function::{ function::{
arguments::Arguments, ClassFieldDefinition, ConstructorKind, Function, ThisMode, arguments::Arguments, ClassFieldDefinition, ConstructorKind, Function, ThisMode,
}, },
@ -24,7 +25,7 @@ use crate::{
use boa_gc::{Cell, Finalize, Gc, Trace}; use boa_gc::{Cell, Finalize, Gc, Trace};
use boa_interner::{Interner, Sym, ToInternedString}; use boa_interner::{Interner, Sym, ToInternedString};
use boa_profiler::Profiler; 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. /// This represents whether a value can be read from [`CodeBlock`] code.
/// ///
@ -223,7 +224,10 @@ impl CodeBlock {
*pc += size_of::<u32>(); *pc += size_of::<u32>();
format!("{operand1}, {operand2}") format!("{operand1}, {operand2}")
} }
Opcode::GetFunction | Opcode::GetFunctionAsync | Opcode::GetGenerator => { Opcode::GetFunction
| Opcode::GetFunctionAsync
| Opcode::GetGenerator
| Opcode::GetGeneratorAsync => {
let operand = self.read::<u32>(*pc); let operand = self.read::<u32>(*pc);
*pc += size_of::<u32>(); *pc += size_of::<u32>();
format!( format!(
@ -364,6 +368,7 @@ impl CodeBlock {
| Opcode::PopOnReturnSub | Opcode::PopOnReturnSub
| Opcode::Yield | Opcode::Yield
| Opcode::GeneratorNext | Opcode::GeneratorNext
| Opcode::AsyncGeneratorNext
| Opcode::PushClassField | Opcode::PushClassField
| Opcode::SuperCallDerived | Opcode::SuperCallDerived
| Opcode::Await | Opcode::Await
@ -546,13 +551,22 @@ pub(crate) fn create_function_object(
/// Creates a new generator function object. /// Creates a new generator function object.
pub(crate) fn create_generator_function_object( pub(crate) fn create_generator_function_object(
code: Gc<CodeBlock>, code: Gc<CodeBlock>,
r#async: bool,
context: &mut Context, context: &mut Context,
) -> JsObject { ) -> JsObject {
let function_prototype = context let function_prototype = if r#async {
context
.intrinsics()
.constructors()
.async_generator_function()
.prototype()
} else {
context
.intrinsics() .intrinsics()
.constructors() .constructors()
.generator_function() .generator_function()
.prototype(); .prototype()
};
let name_property = PropertyDescriptor::builder() let name_property = PropertyDescriptor::builder()
.value(context.interner().resolve_expect(code.name)) .value(context.interner().resolve_expect(code.name))
@ -569,17 +583,34 @@ pub(crate) fn create_generator_function_object(
.build(); .build();
let prototype = JsObject::from_proto_and_data( 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(), ObjectData::ordinary(),
); );
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 { let function = Function::Generator {
code, code,
environments: context.realm.environments.clone(), 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() let prototype_property = PropertyDescriptor::builder()
.value(prototype) .value(prototype)
@ -748,6 +779,7 @@ impl JsObject {
arg_count, arg_count,
generator_resume_kind: GeneratorResumeKind::Normal, generator_resume_kind: GeneratorResumeKind::Normal,
thrown: false, thrown: false,
async_generator: None,
}); });
let result = context.run(); let result = context.run();
@ -870,6 +902,7 @@ impl JsObject {
arg_count, arg_count,
generator_resume_kind: GeneratorResumeKind::Normal, generator_resume_kind: GeneratorResumeKind::Normal,
thrown: false, thrown: false,
async_generator: None,
}); });
let _result = context.run(); let _result = context.run();
@ -982,6 +1015,7 @@ impl JsObject {
arg_count, arg_count,
generator_resume_kind: GeneratorResumeKind::Normal, generator_resume_kind: GeneratorResumeKind::Normal,
thrown: false, thrown: false,
async_generator: None,
}; };
let mut stack = args; let mut stack = args;
@ -1018,6 +1052,155 @@ impl JsObject {
init_result?; 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()) Ok(generator.into())
} }
} }
@ -1215,6 +1398,7 @@ impl JsObject {
arg_count, arg_count,
generator_resume_kind: GeneratorResumeKind::Normal, generator_resume_kind: GeneratorResumeKind::Normal,
thrown: false, thrown: false,
async_generator: None,
}); });
let result = context.run(); let result = context.run();
@ -1253,7 +1437,9 @@ impl JsObject {
} }
} }
} }
Function::Generator { .. } | Function::Async { .. } => { Function::Generator { .. }
| Function::Async { .. }
| Function::AsyncGenerator { .. } => {
unreachable!("not a constructor") unreachable!("not a constructor")
} }
} }

130
boa_engine/src/vm/mod.rs

@ -4,6 +4,7 @@
use crate::{ use crate::{
builtins::{ builtins::{
async_generator::{AsyncGenerator, AsyncGeneratorState},
function::{ConstructorKind, Function}, function::{ConstructorKind, Function},
iterable::IteratorRecord, iterable::IteratorRecord,
Array, ForInIterator, JsArgs, Number, Promise, Array, ForInIterator, JsArgs, Number, Promise,
@ -1695,7 +1696,13 @@ impl Context {
Opcode::GetGenerator => { Opcode::GetGenerator => {
let index = self.vm.read::<u32>(); let index = self.vm.read::<u32>();
let code = self.vm.frame().code.functions[index as usize].clone(); 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::<u32>();
let code = self.vm.frame().code.functions[index as usize].clone();
let function = create_generator_function_object(code, true, self);
self.vm.push(function); self.vm.push(function);
} }
Opcode::CallEval => { Opcode::CallEval => {
@ -2179,6 +2186,54 @@ impl Context {
return Ok(ShouldExit::True); 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 => { Opcode::GeneratorNextDelegate => {
let done_address = self.vm.read::<u32>(); let done_address = self.vm.read::<u32>();
let received = self.vm.pop(); let received = self.vm.pop();
@ -2483,6 +2538,24 @@ impl Context {
.call(&JsValue::undefined(), &[result.clone()], self) .call(&JsValue::undefined(), &[result.clone()], self)
.expect("cannot fail per spec"); .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)); return Ok((result, ReturnType::Normal));
} }
@ -2548,6 +2621,26 @@ impl Context {
return Ok((e, ReturnType::Normal)); 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); return Err(e);
} }
@ -2586,6 +2679,23 @@ impl Context {
.call(&JsValue::undefined(), &[], self) .call(&JsValue::undefined(), &[], self)
.expect("cannot fail per spec"); .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)); return Ok((JsValue::undefined(), ReturnType::Normal));
} }
@ -2600,6 +2710,24 @@ impl Context {
.call(&JsValue::undefined(), &[result.clone()], self) .call(&JsValue::undefined(), &[result.clone()], self)
.expect("cannot fail per spec"); .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)) Ok((result, ReturnType::Normal))
} }

18
boa_engine/src/vm/opcode.rs

@ -927,6 +927,13 @@ pub enum Opcode {
/// Stack: **=>** func /// Stack: **=>** func
GetGenerator, GetGenerator,
/// Get async generator function from the pre-compiled inner functions.
///
/// Operands: address: `u32`
///
/// Stack: **=>** func
GetGeneratorAsync,
/// Call a function named "eval". /// Call a function named "eval".
/// ///
/// Operands: argument_count: `u32` /// Operands: argument_count: `u32`
@ -1125,6 +1132,13 @@ pub enum Opcode {
/// Stack: received **=>** /// Stack: received **=>**
GeneratorNext, GeneratorNext,
/// Resumes the current generator function.
///
/// Operands:
///
/// Stack: received **=>** Option<value>, skip_0, skip_1
AsyncGeneratorNext,
/// Delegates the current generator function another generator. /// Delegates the current generator function another generator.
/// ///
/// Operands: done_address: `u32` /// Operands: done_address: `u32`
@ -1285,6 +1299,7 @@ impl Opcode {
Self::GetFunction => "GetFunction", Self::GetFunction => "GetFunction",
Self::GetFunctionAsync => "GetFunctionAsync", Self::GetFunctionAsync => "GetFunctionAsync",
Self::GetGenerator => "GetGenerator", Self::GetGenerator => "GetGenerator",
Self::GetGeneratorAsync => "GetGeneratorAsync",
Self::CallEval => "CallEval", Self::CallEval => "CallEval",
Self::CallEvalWithRest => "CallEvalWithRest", Self::CallEvalWithRest => "CallEvalWithRest",
Self::Call => "Call", Self::Call => "Call",
@ -1313,6 +1328,7 @@ impl Opcode {
Self::PopOnReturnSub => "PopOnReturnSub", Self::PopOnReturnSub => "PopOnReturnSub",
Self::Yield => "Yield", Self::Yield => "Yield",
Self::GeneratorNext => "GeneratorNext", Self::GeneratorNext => "GeneratorNext",
Self::AsyncGeneratorNext => "AsyncGeneratorNext",
Self::Await => "Await", Self::Await => "Await",
Self::GeneratorNextDelegate => "GeneratorNextDelegate", Self::GeneratorNextDelegate => "GeneratorNextDelegate",
Self::Nop => "Nop", Self::Nop => "Nop",
@ -1425,6 +1441,7 @@ impl Opcode {
Self::GetFunction => "INST - GetFunction", Self::GetFunction => "INST - GetFunction",
Self::GetFunctionAsync => "INST - GetFunctionAsync", Self::GetFunctionAsync => "INST - GetFunctionAsync",
Self::GetGenerator => "INST - GetGenerator", Self::GetGenerator => "INST - GetGenerator",
Self::GetGeneratorAsync => "INST - GetGeneratorAsync",
Self::CallEval => "INST - CallEval", Self::CallEval => "INST - CallEval",
Self::CallEvalWithRest => "INST - CallEvalWithRest", Self::CallEvalWithRest => "INST - CallEvalWithRest",
Self::Call => "INST - Call", Self::Call => "INST - Call",
@ -1453,6 +1470,7 @@ impl Opcode {
Self::PopOnReturnSub => "INST - PopOnReturnSub", Self::PopOnReturnSub => "INST - PopOnReturnSub",
Self::Yield => "INST - Yield", Self::Yield => "INST - Yield",
Self::GeneratorNext => "INST - GeneratorNext", Self::GeneratorNext => "INST - GeneratorNext",
Self::AsyncGeneratorNext => "INST - AsyncGeneratorNext",
Self::Await => "INST - Await", Self::Await => "INST - Await",
Self::GeneratorNextDelegate => "INST - GeneratorNextDelegate", Self::GeneratorNextDelegate => "INST - GeneratorNextDelegate",
Self::Nop => "INST - Nop", Self::Nop => "INST - Nop",

Loading…
Cancel
Save