Browse Source

Implement `async function` and `await` (#2158)

This Pull Request changes the following:

- Implement `AsyncFunction` builtin object.
- Add `Async` as a function object data type.
- Implement `async function` and `await` compilation and execution.
- Parse `await` in more positions.
pull/2164/head
raskad 2 years ago
parent
commit
cdc49e35ea
  1. 107
      boa_engine/src/builtins/async_function/mod.rs
  2. 58
      boa_engine/src/builtins/function/mod.rs
  3. 5
      boa_engine/src/builtins/mod.rs
  4. 27
      boa_engine/src/builtins/promise/mod.rs
  5. 108
      boa_engine/src/bytecompiler.rs
  6. 7
      boa_engine/src/context/intrinsics.rs
  7. 2
      boa_engine/src/object/mod.rs
  8. 6
      boa_engine/src/syntax/ast/node/declaration/async_function_decl/mod.rs
  9. 2
      boa_engine/src/syntax/ast/node/statement_list/mod.rs
  10. 35
      boa_engine/src/syntax/parser/expression/assignment/exponentiation.rs
  11. 13
      boa_engine/src/syntax/parser/expression/unary.rs
  12. 4
      boa_engine/src/syntax/parser/statement/declaration/hoistable/function_decl/mod.rs
  13. 167
      boa_engine/src/vm/code_block.rs
  14. 171
      boa_engine/src/vm/mod.rs
  15. 18
      boa_engine/src/vm/opcode.rs

107
boa_engine/src/builtins/async_function/mod.rs

@ -0,0 +1,107 @@
//! This module implements the global `AsyncFunction` object.
//!
//! More information:
//! - [ECMAScript reference][spec]
//! - [MDN documentation][mdn]
//!
//! [spec]: https://tc39.es/ecma262/#sec-async-function-objects
//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncFunction
use crate::{
builtins::{
function::{ConstructorKind, Function},
BuiltIn,
},
object::ObjectData,
property::PropertyDescriptor,
symbol::WellKnownSymbols,
Context, JsResult, JsValue,
};
use boa_profiler::Profiler;
#[derive(Debug, Clone, Copy)]
pub struct AsyncFunction;
impl BuiltIn for AsyncFunction {
const NAME: &'static str = "AsyncFunction";
fn init(context: &mut Context) -> Option<JsValue> {
let _timer = Profiler::global().start_event(Self::NAME, "init");
let prototype = &context
.intrinsics()
.constructors()
.async_function()
.prototype;
let constructor = &context
.intrinsics()
.constructors()
.async_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(prototype.clone())
.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(constructor.clone())
.writable(false)
.enumerable(false)
.configurable(true);
prototype.borrow_mut().insert("constructor", property);
let property = PropertyDescriptor::builder()
.value(Self::NAME)
.writable(false)
.enumerable(false)
.configurable(true);
prototype
.borrow_mut()
.insert(WellKnownSymbols::to_string_tag(), property);
None
}
}
impl AsyncFunction {
/// `AsyncFunction ( p1, p2, … , pn, body )`
///
/// More information:
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-async-function-constructor-arguments
fn constructor(
new_target: &JsValue,
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
crate::builtins::function::BuiltInFunctionObject::create_dynamic_function(
new_target, args, true, context,
)
.map(Into::into)
}
}

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

@ -22,7 +22,10 @@ use crate::{
object::{ConstructorBuilder, FunctionBuilder, JsFunction, PrivateElement, Ref, RefMut},
property::{Attribute, PropertyDescriptor, PropertyKey},
symbol::WellKnownSymbols,
syntax::{ast::node::FormalParameterList, Parser},
syntax::{
ast::node::{FormalParameterList, StatementList},
Parser,
},
value::IntegerOrInfinity,
Context, JsResult, JsString, JsValue,
};
@ -38,6 +41,8 @@ use std::{
};
use tap::{Conv, Pipe};
use super::promise::PromiseCapability;
pub(crate) mod arguments;
#[cfg(test)]
mod tests;
@ -232,6 +237,11 @@ pub enum Function {
/// The `[[PrivateMethods]]` internal slot.
private_methods: Vec<(Sym, PrivateElement)>,
},
Async {
code: Gc<crate::vm::CodeBlock>,
environments: DeclarativeEnvironmentStack,
promise_capability: PromiseCapability,
},
Generator {
code: Gc<crate::vm::CodeBlock>,
environments: DeclarativeEnvironmentStack,
@ -252,6 +262,11 @@ unsafe impl Trace for Function {
mark(elem);
}
}
Self::Async { code, environments, promise_capability } => {
mark(code);
mark(environments);
mark(promise_capability);
}
Self::Generator { code, environments } => {
mark(code);
mark(environments);
@ -273,7 +288,7 @@ impl Function {
Self::Native { constructor, .. } | Self::Closure { constructor, .. } => {
constructor.is_some()
}
Self::Generator { .. } => false,
Self::Generator { .. } | Self::Async { .. } => false,
Self::Ordinary { code, .. } => !(code.this_mode == ThisMode::Lexical),
}
}
@ -350,6 +365,18 @@ impl Function {
private_methods.push((name, method));
}
}
/// Returns the promise capability if the function is an async function.
pub(crate) fn get_promise_capability(&self) -> Option<&PromiseCapability> {
if let Self::Async {
promise_capability, ..
} = self
{
Some(promise_capability)
} else {
None
}
}
}
/// Creates a new member function of a `Object` or `prototype`.
@ -433,7 +460,7 @@ impl BuiltInFunctionObject {
args: &[JsValue],
context: &mut Context,
) -> JsResult<JsValue> {
Self::create_dynamic_function(new_target, args, context).map(Into::into)
Self::create_dynamic_function(new_target, args, false, context).map(Into::into)
}
/// `CreateDynamicFunction ( constructor, newTarget, kind, args )`
@ -442,9 +469,10 @@ impl BuiltInFunctionObject {
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-createdynamicfunction
fn create_dynamic_function(
pub(crate) fn create_dynamic_function(
new_target: &JsValue,
args: &[JsValue],
r#async: bool,
context: &mut Context,
) -> JsResult<JsObject> {
let prototype =
@ -462,7 +490,7 @@ impl BuiltInFunctionObject {
parameters.push(')');
let parameters = match Parser::new(parameters.as_bytes())
.parse_formal_parameters(context.interner_mut(), false, false)
.parse_formal_parameters(context.interner_mut(), false, r#async)
{
Ok(parameters) => parameters,
Err(e) => {
@ -479,7 +507,7 @@ impl BuiltInFunctionObject {
let body = match Parser::new(body_arg.as_bytes()).parse_function_body(
context.interner_mut(),
false,
false,
r#async,
) {
Ok(statement_list) => statement_list,
Err(e) => {
@ -548,7 +576,23 @@ impl BuiltInFunctionObject {
)?;
let environments = context.realm.environments.pop_to_global();
let function_object = crate::vm::create_function_object(code, context);
let function_object = crate::vm::create_function_object(code, r#async, context);
context.realm.environments.extend(environments);
Ok(function_object)
} else if r#async {
let code = crate::bytecompiler::ByteCompiler::compile_function_code(
crate::bytecompiler::FunctionKind::Expression,
Some(Sym::EMPTY_STRING),
&FormalParameterList::empty(),
&StatementList::default(),
false,
false,
context,
)?;
let environments = context.realm.environments.pop_to_global();
let function_object = crate::vm::create_function_object(code, r#async, context);
context.realm.environments.extend(environments);
Ok(function_object)

5
boa_engine/src/builtins/mod.rs

@ -2,6 +2,7 @@
pub mod array;
pub mod array_buffer;
pub mod async_function;
pub mod bigint;
pub mod boolean;
pub mod dataview;
@ -38,6 +39,7 @@ pub mod intl;
pub(crate) use self::{
array::{array_iterator::ArrayIterator, Array},
async_function::AsyncFunction,
bigint::BigInt,
boolean::Boolean,
dataview::DataView,
@ -185,7 +187,8 @@ pub fn init(context: &mut Context) {
Reflect,
Generator,
GeneratorFunction,
Promise
Promise,
AsyncFunction
};
#[cfg(feature = "intl")]

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

@ -86,7 +86,7 @@ enum ReactionType {
}
#[derive(Debug, Clone, Trace, Finalize)]
struct PromiseCapability {
pub struct PromiseCapability {
promise: JsObject,
resolve: JsFunction,
reject: JsFunction,
@ -99,7 +99,7 @@ impl PromiseCapability {
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-newpromisecapability
fn new(c: &JsValue, context: &mut Context) -> JsResult<Self> {
pub(crate) fn new(c: &JsValue, context: &mut Context) -> JsResult<Self> {
#[derive(Debug, Clone, Trace, Finalize)]
struct RejectResolve {
reject: JsValue,
@ -194,6 +194,21 @@ impl PromiseCapability {
}
}
}
/// Returns the promise object.
pub(crate) fn promise(&self) -> &JsObject {
&self.promise
}
/// Returns the resolve function.
pub(crate) fn resolve(&self) -> &JsFunction {
&self.resolve
}
/// Returns the reject function.
pub(crate) fn reject(&self) -> &JsFunction {
&self.reject
}
}
impl BuiltIn for Promise {
@ -1878,7 +1893,7 @@ impl Promise {
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-performpromisethen
fn perform_promise_then(
pub(crate) fn perform_promise_then(
&mut self,
on_fulfilled: &JsValue,
on_rejected: &JsValue,
@ -2000,7 +2015,11 @@ impl Promise {
/// - [ECMAScript reference][spec]
///
/// [spec]: https://tc39.es/ecma262/#sec-promise-resolve
fn promise_resolve(c: JsObject, x: JsValue, context: &mut Context) -> JsResult<JsValue> {
pub(crate) fn promise_resolve(
c: JsObject,
x: JsValue,
context: &mut Context,
) -> JsResult<JsValue> {
// 1. If IsPromise(x) is true, then
if let Some(x) = x.as_promise() {
// a. Let xConstructor be ? Get(x, "constructor").

108
boa_engine/src/bytecompiler.rs

@ -940,6 +940,20 @@ impl<'b> ByteCompiler<'b> {
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)?;
@ -954,10 +968,8 @@ impl<'b> ByteCompiler<'b> {
self.emit_opcode(Opcode::DefineOwnPropertyByValue);
}
},
// TODO: Implement async
// TODO: Implement async generators
MethodDefinition::Async(_)
| MethodDefinition::AsyncGenerator(_) => {
MethodDefinition::AsyncGenerator(_) => {
// TODO: Implement async
match name {
PropertyName::Literal(name) => {
@ -1119,13 +1131,19 @@ impl<'b> ByteCompiler<'b> {
self.emit(Opcode::Pop, &[]);
}
}
// TODO: implement AsyncFunctionExpr
// TODO: implement AwaitExpr
Node::AwaitExpr(expr) => {
self.compile_expr(expr.expr(), true)?;
self.emit_opcode(Opcode::Await);
self.emit_opcode(Opcode::GeneratorNext);
if !use_expr {
self.emit_opcode(Opcode::Pop);
}
}
// TODO: implement AsyncGeneratorExpr
Node::AsyncFunctionExpr(_) | Node::AwaitExpr(_) | Node::AsyncGeneratorExpr(_) => {
Node::AsyncGeneratorExpr(_) => {
self.emit_opcode(Opcode::PushUndefined);
}
Node::GeneratorExpr(_) => 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)?;
@ -1919,10 +1937,9 @@ impl<'b> ByteCompiler<'b> {
self.pop_try_control_info(None);
}
}
// TODO: implement AsyncFunctionDecl
Node::GeneratorDecl(_) => self.function(node, false)?,
Node::GeneratorDecl(_) | Node::AsyncFunctionDecl(_) => self.function(node, false)?,
// TODO: implement AsyncGeneratorDecl
Node::AsyncFunctionDecl(_) | Node::AsyncGeneratorDecl(_) => {
Node::AsyncGeneratorDecl(_) => {
self.emit_opcode(Opcode::PushUndefined);
}
Node::ClassDecl(class) => self.class(class, false)?,
@ -2052,13 +2069,22 @@ impl<'b> ByteCompiler<'b> {
/// 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) = match function {
let (kind, name, parameters, body, generator, r#async) = match function {
Node::FunctionDecl(function) => (
FunctionKind::Declaration,
Some(function.name()),
function.parameters(),
function.body(),
false,
false,
),
Node::AsyncFunctionDecl(function) => (
FunctionKind::Declaration,
Some(function.name()),
function.parameters(),
function.body(),
false,
true,
),
Node::GeneratorDecl(generator) => (
FunctionKind::Declaration,
@ -2066,6 +2092,7 @@ impl<'b> ByteCompiler<'b> {
generator.parameters(),
generator.body(),
true,
false,
),
Node::FunctionExpr(function) => (
FunctionKind::Expression,
@ -2073,6 +2100,15 @@ impl<'b> ByteCompiler<'b> {
function.parameters(),
function.body(),
false,
false,
),
Node::AsyncFunctionExpr(function) => (
FunctionKind::Expression,
function.name(),
function.parameters(),
function.body(),
false,
true,
),
Node::GeneratorExpr(generator) => (
FunctionKind::Expression,
@ -2080,6 +2116,7 @@ impl<'b> ByteCompiler<'b> {
generator.parameters(),
generator.body(),
true,
false,
),
Node::ArrowFunctionDecl(function) => (
FunctionKind::Arrow,
@ -2087,6 +2124,7 @@ impl<'b> ByteCompiler<'b> {
function.params(),
function.body(),
false,
false,
),
_ => unreachable!(),
};
@ -2106,6 +2144,8 @@ impl<'b> ByteCompiler<'b> {
if generator {
self.emit(Opcode::GetGenerator, &[index]);
} else if r#async {
self.emit(Opcode::GetFunctionAsync, &[index]);
} else {
self.emit(Opcode::GetFunction, &[index]);
}
@ -2729,6 +2769,20 @@ impl<'b> ByteCompiler<'b> {
self.emit_opcode(Opcode::DefineClassMethodByValue);
}
},
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::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);
}
},
MethodDefinition::Generator(expr) => match name {
PropertyName::Literal(name) => {
self.function(&expr.clone().into(), true)?;
@ -2744,7 +2798,7 @@ impl<'b> ByteCompiler<'b> {
}
},
// TODO: implement async
MethodDefinition::AsyncGenerator(_) | MethodDefinition::Async(_) => {}
MethodDefinition::AsyncGenerator(_) => {}
}
}
ClassElement::PrivateStaticMethodDefinition(name, method_definition) => {
@ -2765,13 +2819,18 @@ impl<'b> ByteCompiler<'b> {
let index = self.get_or_insert_name(*name);
self.emit(Opcode::SetPrivateMethod, &[index]);
}
MethodDefinition::Async(expr) => {
self.function(&expr.clone().into(), true)?;
let index = self.get_or_insert_name(*name);
self.emit(Opcode::SetPrivateMethod, &[index]);
}
MethodDefinition::Generator(expr) => {
self.function(&expr.clone().into(), true)?;
let index = self.get_or_insert_name(*name);
self.emit(Opcode::SetPrivateMethod, &[index]);
}
// TODO: implement async
MethodDefinition::AsyncGenerator(_) | MethodDefinition::Async(_) => {}
MethodDefinition::AsyncGenerator(_) => {}
}
}
ClassElement::FieldDefinition(name, field) => {
@ -2924,13 +2983,18 @@ impl<'b> ByteCompiler<'b> {
let index = self.get_or_insert_name(*name);
self.emit(Opcode::PushClassPrivateMethod, &[index]);
}
MethodDefinition::Async(expr) => {
self.function(&expr.clone().into(), true)?;
let index = self.get_or_insert_name(*name);
self.emit(Opcode::PushClassPrivateMethod, &[index]);
}
MethodDefinition::Generator(expr) => {
self.function(&expr.clone().into(), true)?;
let index = self.get_or_insert_name(*name);
self.emit(Opcode::PushClassPrivateMethod, &[index]);
}
// TODO: implement async
MethodDefinition::AsyncGenerator(_) | MethodDefinition::Async(_) => {}
MethodDefinition::AsyncGenerator(_) => {}
}
}
ClassElement::MethodDefinition(..) => {}
@ -2986,6 +3050,20 @@ impl<'b> ByteCompiler<'b> {
self.emit_opcode(Opcode::DefineClassMethodByValue);
}
},
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::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);
}
},
MethodDefinition::Generator(expr) => match name {
PropertyName::Literal(name) => {
self.function(&expr.clone().into(), true)?;
@ -3001,7 +3079,7 @@ impl<'b> ByteCompiler<'b> {
}
},
// TODO: implement async
MethodDefinition::AsyncGenerator(_) | MethodDefinition::Async(_) => {
MethodDefinition::AsyncGenerator(_) => {
self.emit_opcode(Opcode::Pop);
}
}

7
boa_engine/src/context/intrinsics.rs

@ -76,6 +76,7 @@ pub struct StandardConstructors {
proxy: StandardConstructor,
date: StandardConstructor,
function: StandardConstructor,
async_function: StandardConstructor,
generator: StandardConstructor,
generator_function: StandardConstructor,
array: StandardConstructor,
@ -120,6 +121,7 @@ impl Default for StandardConstructors {
proxy: StandardConstructor::default(),
date: StandardConstructor::default(),
function: StandardConstructor::default(),
async_function: StandardConstructor::default(),
generator: StandardConstructor::default(),
generator_function: StandardConstructor::default(),
array: StandardConstructor::with_prototype(JsObject::from_proto_and_data(
@ -205,6 +207,11 @@ impl StandardConstructors {
&self.function
}
#[inline]
pub fn async_function(&self) -> &StandardConstructor {
&self.async_function
}
#[inline]
pub fn generator(&self) -> &StandardConstructor {
&self.generator

2
boa_engine/src/object/mod.rs

@ -1574,7 +1574,7 @@ impl<'context> FunctionBuilder<'context> {
} => {
*constructor = yes.then(|| ConstructorKind::Base);
}
Function::Ordinary { .. } | Function::Generator { .. } => {
Function::Ordinary { .. } | Function::Generator { .. } | Function::Async { .. } => {
unreachable!("function must be native or closure");
}
}

6
boa_engine/src/syntax/ast/node/declaration/async_function_decl/mod.rs

@ -47,8 +47,8 @@ impl AsyncFunctionDecl {
}
/// 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.
@ -62,7 +62,7 @@ impl AsyncFunctionDecl {
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!(

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

@ -19,7 +19,7 @@ mod tests;
///
/// [spec]: https://tc39.es/ecma262/#prod-StatementList
#[cfg_attr(feature = "deser", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq)]
#[derive(Clone, Debug, Default, PartialEq)]
pub struct StatementList {
items: Box<[Node]>,
strict: bool,

35
boa_engine/src/syntax/parser/expression/assignment/exponentiation.rs

@ -59,27 +59,6 @@ impl ExponentiationExpression {
}
}
/// Checks by looking at the next token to see whether it's a unary operator or not.
fn is_unary_expression<R>(
cursor: &mut Cursor<R>,
interner: &mut Interner,
) -> Result<bool, ParseError>
where
R: Read,
{
Ok(if let Some(tok) = cursor.peek(0, interner)? {
matches!(
tok.kind(),
TokenKind::Keyword((Keyword::Delete | Keyword::Void | Keyword::TypeOf, _))
| TokenKind::Punctuator(
Punctuator::Add | Punctuator::Sub | Punctuator::Not | Punctuator::Neg
)
)
} else {
false
})
}
impl<R> TokenParser<R> for ExponentiationExpression
where
R: Read,
@ -88,10 +67,22 @@ where
fn parse(self, cursor: &mut Cursor<R>, interner: &mut Interner) -> ParseResult {
let _timer = Profiler::global().start_event("ExponentiationExpression", "Parsing");
if is_unary_expression(cursor, interner)? {
let next = cursor.peek(0, interner)?.ok_or(ParseError::AbruptEnd)?;
match next.kind() {
TokenKind::Keyword((Keyword::Delete | Keyword::Void | Keyword::TypeOf, _))
| TokenKind::Punctuator(
Punctuator::Add | Punctuator::Sub | Punctuator::Not | Punctuator::Neg,
) => {
return UnaryExpression::new(self.name, self.allow_yield, self.allow_await)
.parse(cursor, interner);
}
TokenKind::Keyword((Keyword::Await, _)) if self.allow_await.0 => {
return UnaryExpression::new(self.name, self.allow_yield, self.allow_await)
.parse(cursor, interner);
}
_ => {}
}
let lhs = UpdateExpression::new(self.name, self.allow_yield, self.allow_await)
.parse(cursor, interner)?;

13
boa_engine/src/syntax/parser/expression/unary.rs

@ -15,8 +15,8 @@ use crate::syntax::{
},
lexer::{Error as LexError, TokenKind},
parser::{
expression::update::UpdateExpression, AllowAwait, AllowYield, Cursor, ParseError,
ParseResult, TokenParser,
expression::{await_expr::AwaitExpression, update::UpdateExpression},
AllowAwait, AllowYield, Cursor, ParseError, ParseResult, TokenParser,
},
};
use boa_interner::{Interner, Sym};
@ -120,6 +120,15 @@ where
cursor.next(interner)?.expect("! token vanished"); // Consume the token.
Ok(node::UnaryOp::new(UnaryOp::Not, self.parse(cursor, interner)?).into())
}
TokenKind::Keyword((Keyword::Await, true)) if self.allow_await.0 => {
Err(ParseError::general(
"Keyword 'await' must not contain escaped characters",
token_start,
))
}
TokenKind::Keyword((Keyword::Await, false)) if self.allow_await.0 => {
Ok((AwaitExpression::new(self.allow_yield).parse(cursor, interner)?).into())
}
_ => UpdateExpression::new(self.name, self.allow_yield, self.allow_await)
.parse(cursor, interner),
}

4
boa_engine/src/syntax/parser/statement/declaration/hoistable/function_decl/mod.rs

@ -67,10 +67,10 @@ impl CallableDeclaration for FunctionDeclaration {
false
}
fn body_allow_yield(&self) -> bool {
self.allow_yield.0
false
}
fn body_allow_await(&self) -> bool {
self.allow_await.0
false
}
}

167
boa_engine/src/vm/code_block.rs

@ -8,6 +8,7 @@ use crate::{
arguments::Arguments, ClassFieldDefinition, ConstructorKind, Function, ThisMode,
},
generator::{Generator, GeneratorContext, GeneratorState},
promise::PromiseCapability,
},
context::intrinsics::StandardConstructors,
environments::{BindingLocator, CompileTimeEnvironment},
@ -215,7 +216,7 @@ impl CodeBlock {
*pc += size_of::<u32>();
format!("{operand1}, {operand2}")
}
Opcode::GetFunction | Opcode::GetGenerator => {
Opcode::GetFunction | Opcode::GetFunctionAsync | Opcode::GetGenerator => {
let operand = self.read::<u32>(*pc);
*pc += size_of::<u32>();
format!(
@ -358,6 +359,7 @@ impl CodeBlock {
| Opcode::GeneratorNext
| Opcode::PushClassField
| Opcode::SuperCallDerived
| Opcode::Await
| Opcode::Nop => String::new(),
}
}
@ -434,10 +436,22 @@ impl ToInternedString for CodeBlock {
}
/// Creates a new function object.
pub(crate) fn create_function_object(code: Gc<CodeBlock>, context: &mut Context) -> JsObject {
pub(crate) fn create_function_object(
code: Gc<CodeBlock>,
r#async: bool,
context: &mut Context,
) -> JsObject {
let _timer = Profiler::global().start_event("JsVmFunction::new", "vm");
let function_prototype = context.intrinsics().constructors().function().prototype();
let function_prototype = if r#async {
context
.intrinsics()
.constructors()
.async_function()
.prototype()
} else {
context.intrinsics().constructors().function().prototype()
};
let prototype = context.construct_object();
@ -455,13 +469,32 @@ pub(crate) fn create_function_object(code: Gc<CodeBlock>, context: &mut Context)
.configurable(true)
.build();
let function = Function::Ordinary {
let function = if r#async {
let promise_capability = PromiseCapability::new(
&context
.intrinsics()
.constructors()
.promise()
.constructor()
.into(),
context,
)
.expect("cannot fail per spec");
Function::Async {
code,
environments: context.realm.environments.clone(),
promise_capability,
}
} else {
Function::Ordinary {
code,
environments: context.realm.environments.clone(),
constructor_kind: ConstructorKind::Base,
home_object: None,
fields: Vec::new(),
private_methods: Vec::new(),
}
};
let constructor =
@ -485,9 +518,11 @@ pub(crate) fn create_function_object(code: Gc<CodeBlock>, context: &mut Context)
.configurable(false)
.build();
if !r#async {
constructor
.define_property_or_throw("prototype", prototype_property, context)
.expect("failed to define the prototype property of the function");
}
constructor
.define_property_or_throw("name", name_property, context)
.expect("failed to define the name property of the function");
@ -719,6 +754,126 @@ impl JsObject {
let (result, _) = result?;
Ok(result)
}
Function::Async {
code,
environments,
promise_capability,
} => {
let code = code.clone();
let mut environments = environments.clone();
let promise = promise_capability.promise().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 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()
};
for arg in args.iter().rev() {
context.vm.push(arg);
}
let param_count = code.params.parameters.len();
let has_expressions = code.params.has_expressions();
context.vm.push_frame(CallFrame {
prev: None,
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,
});
let _result = context.run();
context.vm.pop_frame().expect("must have frame");
context.realm.environments.pop();
if has_expressions {
context.realm.environments.pop();
}
std::mem::swap(&mut environments, &mut context.realm.environments);
Ok(promise.into())
}
Function::Generator { code, environments } => {
let code = code.clone();
let mut environments = environments.clone();
@ -1088,8 +1243,8 @@ impl JsObject {
}
}
}
Function::Generator { .. } => {
unreachable!("generator function cannot be a constructor")
Function::Generator { .. } | Function::Async { .. } => {
unreachable!("not a constructor")
}
}
}

171
boa_engine/src/vm/mod.rs

@ -6,10 +6,10 @@ use crate::{
builtins::{
function::{ConstructorKind, Function},
iterable::IteratorRecord,
Array, ForInIterator, Number,
Array, ForInIterator, JsArgs, Number, Promise,
},
environments::EnvironmentSlots,
object::{JsFunction, JsObject, ObjectData, PrivateElement},
object::{FunctionBuilder, JsFunction, JsObject, ObjectData, PrivateElement},
property::{DescriptorKind, PropertyDescriptor, PropertyDescriptorBuilder, PropertyKey},
value::Numeric,
vm::{
@ -115,6 +115,7 @@ enum ShouldExit {
True,
False,
Yield,
Await,
}
/// Indicates if the execution of a codeblock has ended normally or has been yielded.
@ -1686,7 +1687,13 @@ impl Context {
Opcode::GetFunction => {
let index = self.vm.read::<u32>();
let code = self.vm.frame().code.functions[index as usize].clone();
let function = create_function_object(code, self);
let function = create_function_object(code, false, self);
self.vm.push(function);
}
Opcode::GetFunctionAsync => {
let index = self.vm.read::<u32>();
let code = self.vm.frame().code.functions[index as usize].clone();
let function = create_function_object(code, true, self);
self.vm.push(function);
}
Opcode::GetGenerator => {
@ -2270,6 +2277,106 @@ impl Context {
}
}
}
Opcode::Await => {
let value = self.vm.pop();
// 2. Let promise be ? PromiseResolve(%Promise%, value).
let promise = Promise::promise_resolve(
self.intrinsics().constructors().promise().constructor(),
value,
self,
)?;
// 3. Let fulfilledClosure be a new Abstract Closure with parameters (value) that captures asyncContext and performs the following steps when called:
// 4. Let onFulfilled be CreateBuiltinFunction(fulfilledClosure, 1, "", « »).
let on_fulfilled = FunctionBuilder::closure_with_captures(
self,
|_this, args, (environment, stack, frame), context| {
// a. Let prevContext be the running execution context.
// b. Suspend prevContext.
// c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
// d. Resume the suspended evaluation of asyncContext using NormalCompletion(value) as the result of the operation that suspended it.
// e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context.
// f. Return undefined.
std::mem::swap(&mut context.realm.environments, environment);
std::mem::swap(&mut context.vm.stack, stack);
context.vm.push_frame(frame.clone());
context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Normal;
context.vm.push(args.get_or_undefined(0));
context.run()?;
*frame = *context
.vm
.pop_frame()
.expect("generator call frame must exist");
std::mem::swap(&mut context.realm.environments, environment);
std::mem::swap(&mut context.vm.stack, stack);
Ok(JsValue::undefined())
},
(
self.realm.environments.clone(),
self.vm.stack.clone(),
self.vm.frame().clone(),
),
)
.name("")
.length(1)
.build();
// 5. Let rejectedClosure be a new Abstract Closure with parameters (reason) that captures asyncContext and performs the following steps when called:
// 6. Let onRejected be CreateBuiltinFunction(rejectedClosure, 1, "", « »).
let on_rejected = FunctionBuilder::closure_with_captures(
self,
|_this, args, (environment, stack, frame), context| {
// a. Let prevContext be the running execution context.
// b. Suspend prevContext.
// c. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
// d. Resume the suspended evaluation of asyncContext using ThrowCompletion(reason) as the result of the operation that suspended it.
// e. Assert: When we reach this step, asyncContext has already been removed from the execution context stack and prevContext is the currently running execution context.
// f. Return undefined.
std::mem::swap(&mut context.realm.environments, environment);
std::mem::swap(&mut context.vm.stack, stack);
context.vm.push_frame(frame.clone());
context.vm.frame_mut().generator_resume_kind = GeneratorResumeKind::Throw;
context.vm.push(args.get_or_undefined(0));
context.run()?;
*frame = *context
.vm
.pop_frame()
.expect("generator call frame must exist");
std::mem::swap(&mut context.realm.environments, environment);
std::mem::swap(&mut context.vm.stack, stack);
Ok(JsValue::undefined())
},
(
self.realm.environments.clone(),
self.vm.stack.clone(),
self.vm.frame().clone(),
),
)
.name("")
.length(1)
.build();
// 7. Perform PerformPromiseThen(promise, onFulfilled, onRejected).
promise
.as_object()
.expect("promise was not an object")
.borrow_mut()
.as_promise_mut()
.expect("promise was not a promise")
.perform_promise_then(&on_fulfilled.into(), &on_rejected.into(), None, self);
self.vm.push(JsValue::undefined());
return Ok(ShouldExit::Await);
}
}
Ok(ShouldExit::False)
@ -2309,6 +2416,22 @@ impl Context {
let start_stack_size = self.vm.stack.len();
// If the current executing function is an async function we have to resolve/reject it's promise at the end.
// The relevant spec section is 3. in [AsyncBlockStart](https://tc39.es/ecma262/#sec-asyncblockstart).
let promise_capability = self
.realm
.environments
.get_this_environment()
.as_function_slots()
.and_then(|slots| {
let slots_borrow = slots.borrow();
let function_object = slots_borrow.function_object();
let function = function_object.borrow();
function
.as_function()
.and_then(|f| f.get_promise_capability().cloned())
});
while self.vm.frame().pc < self.vm.frame().code.code.len() {
let result = if self.vm.trace {
let mut pc = self.vm.frame().pc;
@ -2356,6 +2479,20 @@ impl Context {
Ok(ShouldExit::True) => {
let result = self.vm.pop();
self.vm.stack.truncate(start_stack_size);
// Step 3.e in [AsyncBlockStart](https://tc39.es/ecma262/#sec-asyncblockstart).
if let Some(promise_capability) = promise_capability {
promise_capability
.resolve()
.call(&JsValue::undefined(), &[result.clone()], self)
.expect("cannot fail per spec");
}
return Ok((result, ReturnType::Normal));
}
Ok(ShouldExit::Await) => {
let result = self.vm.pop();
self.vm.stack.truncate(start_stack_size);
return Ok((result, ReturnType::Normal));
}
Ok(ShouldExit::False) => {}
@ -2405,6 +2542,17 @@ impl Context {
self.vm.push(e);
} else {
self.vm.stack.truncate(start_stack_size);
// Step 3.f in [AsyncBlockStart](https://tc39.es/ecma262/#sec-asyncblockstart).
if let Some(promise_capability) = promise_capability {
promise_capability
.reject()
.call(&JsValue::undefined(), &[e.clone()], self)
.expect("cannot fail per spec");
return Ok((e, ReturnType::Normal));
}
return Err(e);
}
}
@ -2435,11 +2583,28 @@ impl Context {
}
if self.vm.stack.len() <= start_stack_size {
// Step 3.d in [AsyncBlockStart](https://tc39.es/ecma262/#sec-asyncblockstart).
if let Some(promise_capability) = promise_capability {
promise_capability
.resolve()
.call(&JsValue::undefined(), &[], self)
.expect("cannot fail per spec");
}
return Ok((JsValue::undefined(), ReturnType::Normal));
}
let result = self.vm.pop();
self.vm.stack.truncate(start_stack_size);
// Step 3.d in [AsyncBlockStart](https://tc39.es/ecma262/#sec-asyncblockstart).
if let Some(promise_capability) = promise_capability {
promise_capability
.resolve()
.call(&JsValue::undefined(), &[result.clone()], self)
.expect("cannot fail per spec");
}
Ok((result, ReturnType::Normal))
}
}

18
boa_engine/src/vm/opcode.rs

@ -913,6 +913,13 @@ pub enum Opcode {
/// Stack: **=>** func
GetFunction,
/// Get async function from the pre-compiled inner functions.
///
/// Operands: address: `u32`
///
/// Stack: **=>** func
GetFunctionAsync,
/// Get generator function from the pre-compiled inner functions.
///
/// Operands: address: `u32`
@ -1125,6 +1132,13 @@ pub enum Opcode {
/// Stack: iterator, next_method, done, received **=>** iterator, next_method, done
GeneratorNextDelegate,
/// Stops the current async function and schedules it to resume later.
///
/// Operands:
///
/// Stack: promise **=>**
Await,
/// No-operation instruction, does nothing.
///
/// Operands:
@ -1269,6 +1283,7 @@ impl Opcode {
Self::Case => "Case",
Self::Default => "Default",
Self::GetFunction => "GetFunction",
Self::GetFunctionAsync => "GetFunctionAsync",
Self::GetGenerator => "GetGenerator",
Self::CallEval => "CallEval",
Self::CallEvalWithRest => "CallEvalWithRest",
@ -1298,6 +1313,7 @@ impl Opcode {
Self::PopOnReturnSub => "PopOnReturnSub",
Self::Yield => "Yield",
Self::GeneratorNext => "GeneratorNext",
Self::Await => "Await",
Self::GeneratorNextDelegate => "GeneratorNextDelegate",
Self::Nop => "Nop",
}
@ -1407,6 +1423,7 @@ impl Opcode {
Self::Case => "INST - Case",
Self::Default => "INST - Default",
Self::GetFunction => "INST - GetFunction",
Self::GetFunctionAsync => "INST - GetFunctionAsync",
Self::GetGenerator => "INST - GetGenerator",
Self::CallEval => "INST - CallEval",
Self::CallEvalWithRest => "INST - CallEvalWithRest",
@ -1436,6 +1453,7 @@ impl Opcode {
Self::PopOnReturnSub => "INST - PopOnReturnSub",
Self::Yield => "INST - Yield",
Self::GeneratorNext => "INST - GeneratorNext",
Self::Await => "INST - Await",
Self::GeneratorNextDelegate => "INST - GeneratorNextDelegate",
Self::Nop => "INST - Nop",
Self::PushClassPrototype => "INST - PushClassPrototype",

Loading…
Cancel
Save