From cdc49e35eaac842ba33d89722cdd9c433123c936 Mon Sep 17 00:00:00 2001 From: raskad <32105367+raskad@users.noreply.github.com> Date: Tue, 5 Jul 2022 10:05:42 +0000 Subject: [PATCH] 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. --- boa_engine/src/builtins/async_function/mod.rs | 107 ++++++++++ boa_engine/src/builtins/function/mod.rs | 58 +++++- boa_engine/src/builtins/mod.rs | 5 +- boa_engine/src/builtins/promise/mod.rs | 27 ++- boa_engine/src/bytecompiler.rs | 108 ++++++++-- boa_engine/src/context/intrinsics.rs | 7 + boa_engine/src/object/mod.rs | 2 +- .../declaration/async_function_decl/mod.rs | 6 +- .../src/syntax/ast/node/statement_list/mod.rs | 2 +- .../expression/assignment/exponentiation.rs | 39 ++-- .../src/syntax/parser/expression/unary.rs | 13 +- .../hoistable/function_decl/mod.rs | 4 +- boa_engine/src/vm/code_block.rs | 185 ++++++++++++++++-- boa_engine/src/vm/mod.rs | 171 +++++++++++++++- boa_engine/src/vm/opcode.rs | 18 ++ 15 files changed, 674 insertions(+), 78 deletions(-) create mode 100644 boa_engine/src/builtins/async_function/mod.rs diff --git a/boa_engine/src/builtins/async_function/mod.rs b/boa_engine/src/builtins/async_function/mod.rs new file mode 100644 index 0000000000..51ee1d25c7 --- /dev/null +++ b/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 { + 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 { + crate::builtins::function::BuiltInFunctionObject::create_dynamic_function( + new_target, args, true, context, + ) + .map(Into::into) + } +} diff --git a/boa_engine/src/builtins/function/mod.rs b/boa_engine/src/builtins/function/mod.rs index 350a8743a6..2d401b721e 100644 --- a/boa_engine/src/builtins/function/mod.rs +++ b/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, + environments: DeclarativeEnvironmentStack, + promise_capability: PromiseCapability, + }, Generator { code: Gc, 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 { - 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 { 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) diff --git a/boa_engine/src/builtins/mod.rs b/boa_engine/src/builtins/mod.rs index 2d4bcd5596..446a2d7bd0 100644 --- a/boa_engine/src/builtins/mod.rs +++ b/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")] diff --git a/boa_engine/src/builtins/promise/mod.rs b/boa_engine/src/builtins/promise/mod.rs index 1acacec930..bccd87ad84 100644 --- a/boa_engine/src/builtins/promise/mod.rs +++ b/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 { + pub(crate) fn new(c: &JsValue, context: &mut Context) -> JsResult { #[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 { + pub(crate) fn promise_resolve( + c: JsObject, + x: JsValue, + context: &mut Context, + ) -> JsResult { // 1. If IsPromise(x) is true, then if let Some(x) = x.as_promise() { // a. Let xConstructor be ? Get(x, "constructor"). diff --git a/boa_engine/src/bytecompiler.rs b/boa_engine/src/bytecompiler.rs index 6d9ad7d071..dd427fb0ac 100644 --- a/boa_engine/src/bytecompiler.rs +++ b/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); } } diff --git a/boa_engine/src/context/intrinsics.rs b/boa_engine/src/context/intrinsics.rs index eec70548f5..300439cd7b 100644 --- a/boa_engine/src/context/intrinsics.rs +++ b/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 diff --git a/boa_engine/src/object/mod.rs b/boa_engine/src/object/mod.rs index fc22b694d8..73b80427d9 100644 --- a/boa_engine/src/object/mod.rs +++ b/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"); } } diff --git a/boa_engine/src/syntax/ast/node/declaration/async_function_decl/mod.rs b/boa_engine/src/syntax/ast/node/declaration/async_function_decl/mod.rs index b0bd32bc27..167ef57b60 100644 --- a/boa_engine/src/syntax/ast/node/declaration/async_function_decl/mod.rs +++ b/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!( diff --git a/boa_engine/src/syntax/ast/node/statement_list/mod.rs b/boa_engine/src/syntax/ast/node/statement_list/mod.rs index 66d2918e67..719d8894bf 100644 --- a/boa_engine/src/syntax/ast/node/statement_list/mod.rs +++ b/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, diff --git a/boa_engine/src/syntax/parser/expression/assignment/exponentiation.rs b/boa_engine/src/syntax/parser/expression/assignment/exponentiation.rs index ed7b6d97c3..61197f4808 100644 --- a/boa_engine/src/syntax/parser/expression/assignment/exponentiation.rs +++ b/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( - cursor: &mut Cursor, - interner: &mut Interner, -) -> Result -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 TokenParser for ExponentiationExpression where R: Read, @@ -88,9 +67,21 @@ where fn parse(self, cursor: &mut Cursor, interner: &mut Interner) -> ParseResult { let _timer = Profiler::global().start_event("ExponentiationExpression", "Parsing"); - if is_unary_expression(cursor, interner)? { - return UnaryExpression::new(self.name, self.allow_yield, self.allow_await) - .parse(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) diff --git a/boa_engine/src/syntax/parser/expression/unary.rs b/boa_engine/src/syntax/parser/expression/unary.rs index 011c927715..eb1ef9389d 100644 --- a/boa_engine/src/syntax/parser/expression/unary.rs +++ b/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), } diff --git a/boa_engine/src/syntax/parser/statement/declaration/hoistable/function_decl/mod.rs b/boa_engine/src/syntax/parser/statement/declaration/hoistable/function_decl/mod.rs index 7a62c28c8f..d801045940 100644 --- a/boa_engine/src/syntax/parser/statement/declaration/hoistable/function_decl/mod.rs +++ b/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 } } diff --git a/boa_engine/src/vm/code_block.rs b/boa_engine/src/vm/code_block.rs index 9cc52a32a6..c39cb2fbee 100644 --- a/boa_engine/src/vm/code_block.rs +++ b/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::(); format!("{operand1}, {operand2}") } - Opcode::GetFunction | Opcode::GetGenerator => { + Opcode::GetFunction | Opcode::GetFunctionAsync | Opcode::GetGenerator => { let operand = self.read::(*pc); *pc += size_of::(); 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, context: &mut Context) -> JsObject { +pub(crate) fn create_function_object( + code: Gc, + 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, context: &mut Context) .configurable(true) .build(); - let function = Function::Ordinary { - code, - environments: context.realm.environments.clone(), - constructor_kind: ConstructorKind::Base, - home_object: None, - fields: Vec::new(), - private_methods: Vec::new(), + 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, context: &mut Context) .configurable(false) .build(); - constructor - .define_property_or_throw("prototype", prototype_property, context) - .expect("failed to define the prototype property of the function"); + 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") } } } diff --git a/boa_engine/src/vm/mod.rs b/boa_engine/src/vm/mod.rs index 62cac3821b..8acbd47d38 100644 --- a/boa_engine/src/vm/mod.rs +++ b/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::(); 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::(); + 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)) } } diff --git a/boa_engine/src/vm/opcode.rs b/boa_engine/src/vm/opcode.rs index 005ee91818..72303c9359 100644 --- a/boa_engine/src/vm/opcode.rs +++ b/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",