1
Fork 0
mirror of https://github.com/RGBCube/serenity synced 2025-07-27 13:47:35 +00:00

LibJS: Add support for async functions

This commit adds support for the most bare bones version of async
functions, support for async generator functions, async arrow functions
and await expressions are TODO.
This commit is contained in:
Idan Horowitz 2021-11-09 20:39:22 +02:00 committed by Linus Groh
parent 05c3320da3
commit 681787de76
17 changed files with 399 additions and 36 deletions

View file

@ -0,0 +1,60 @@
/*
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Interpreter.h>
#include <LibJS/Runtime/AsyncFunctionConstructor.h>
#include <LibJS/Runtime/ECMAScriptFunctionObject.h>
#include <LibJS/Runtime/FunctionConstructor.h>
#include <LibJS/Runtime/FunctionObject.h>
#include <LibJS/Runtime/GlobalObject.h>
namespace JS {
AsyncFunctionConstructor::AsyncFunctionConstructor(GlobalObject& global_object)
: NativeFunction(vm().names.AsyncFunction.as_string(), *global_object.function_prototype())
{
}
void AsyncFunctionConstructor::initialize(GlobalObject& global_object)
{
auto& vm = this->vm();
NativeFunction::initialize(global_object);
// 27.7.2.2 AsyncFunction.prototype, https://tc39.es/ecma262/#sec-async-function-constructor-prototype
define_direct_property(vm.names.prototype, global_object.async_function_prototype(), 0);
define_direct_property(vm.names.length, Value(1), Attribute::Configurable);
}
// 27.7.1.1 AsyncFunction ( p1, p2, … , pn, body ), https://tc39.es/ecma262/#sec-async-function-constructor-arguments
ThrowCompletionOr<Value> AsyncFunctionConstructor::call()
{
return TRY(construct(*this));
}
// 27.7.1.1 AsyncFunction ( p1, p2, … , pn, body ), https://tc39.es/ecma262/#sec-async-function-constructor-arguments
ThrowCompletionOr<Object*> AsyncFunctionConstructor::construct(FunctionObject& new_target)
{
auto& vm = this->vm();
auto function = TRY(FunctionConstructor::create_dynamic_function_node(global_object(), new_target, FunctionKind::Async));
OwnPtr<Interpreter> local_interpreter;
Interpreter* interpreter = vm.interpreter_if_exists();
if (!interpreter) {
local_interpreter = Interpreter::create_with_existing_realm(*realm());
interpreter = local_interpreter.ptr();
}
VM::InterpreterExecutionScope scope(*interpreter);
auto result = function->execute(*interpreter, global_object());
if (auto* exception = vm.exception())
return throw_completion(exception->value());
VERIFY(result.is_object() && is<ECMAScriptFunctionObject>(result.as_object()));
return &result.as_object();
}
}

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibJS/AST.h>
#include <LibJS/Runtime/NativeFunction.h>
namespace JS {
class AsyncFunctionConstructor final : public NativeFunction {
JS_OBJECT(AsyncFunctionConstructor, NativeFunction);
public:
explicit AsyncFunctionConstructor(GlobalObject&);
virtual void initialize(GlobalObject&) override;
virtual ~AsyncFunctionConstructor() override = default;
virtual ThrowCompletionOr<Value> call() override;
virtual ThrowCompletionOr<Object*> construct(FunctionObject& new_target) override;
private:
virtual bool has_constructor() const override { return true; }
};
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/AsyncFunctionPrototype.h>
#include <LibJS/Runtime/GlobalObject.h>
namespace JS {
AsyncFunctionPrototype::AsyncFunctionPrototype(GlobalObject& global_object)
: Object(*global_object.function_prototype())
{
}
void AsyncFunctionPrototype::initialize(GlobalObject& global_object)
{
auto& vm = this->vm();
Object::initialize(global_object);
// 27.7.3.2 AsyncFunction.prototype [ @@toStringTag ], https://tc39.es/ecma262/#sec-async-function-prototype-properties-toStringTag
define_direct_property(*vm.well_known_symbol_to_string_tag(), js_string(vm, vm.names.AsyncFunction.as_string()), Attribute::Configurable);
}
}

View file

@ -0,0 +1,22 @@
/*
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#pragma once
#include <LibJS/Runtime/Object.h>
namespace JS {
class AsyncFunctionPrototype final : public Object {
JS_OBJECT(AsyncFunctionPrototype, Object);
public:
explicit AsyncFunctionPrototype(GlobalObject&);
virtual void initialize(GlobalObject&) override;
virtual ~AsyncFunctionPrototype() override = default;
};
}

View file

@ -15,11 +15,14 @@
#include <LibJS/Runtime/Array.h>
#include <LibJS/Runtime/ECMAScriptFunctionObject.h>
#include <LibJS/Runtime/Error.h>
#include <LibJS/Runtime/ExecutionContext.h>
#include <LibJS/Runtime/FunctionEnvironment.h>
#include <LibJS/Runtime/GeneratorObject.h>
#include <LibJS/Runtime/GeneratorObjectPrototype.h>
#include <LibJS/Runtime/GlobalObject.h>
#include <LibJS/Runtime/NativeFunction.h>
#include <LibJS/Runtime/PromiseConstructor.h>
#include <LibJS/Runtime/PromiseReaction.h>
#include <LibJS/Runtime/Value.h>
namespace JS {
@ -34,6 +37,9 @@ ECMAScriptFunctionObject* ECMAScriptFunctionObject::create(GlobalObject& global_
case FunctionKind::Generator:
prototype = global_object.generator_function_prototype();
break;
case FunctionKind::Async:
prototype = global_object.async_function_prototype();
break;
}
return global_object.heap().allocate<ECMAScriptFunctionObject>(global_object, move(name), ecmascript_code, move(parameters), m_function_length, parent_scope, private_scope, *prototype, kind, is_strict, might_need_arguments_object, contains_direct_call_to_eval, is_arrow_function);
}
@ -96,6 +102,8 @@ void ECMAScriptFunctionObject::initialize(GlobalObject& global_object)
// prototype is "g1.prototype" in figure-2 (https://tc39.es/ecma262/img/figure-2.png)
set_prototype(global_object.generator_object_prototype());
break;
case FunctionKind::Async:
break;
}
define_direct_property(vm.names.prototype, prototype, Attribute::Writable);
}
@ -659,6 +667,84 @@ void ECMAScriptFunctionObject::ordinary_call_bind_this(ExecutionContext& callee_
MUST(verify_cast<FunctionEnvironment>(local_env)->bind_this_value(global_object(), this_value));
}
// 27.7.5.1 AsyncFunctionStart ( promiseCapability, asyncFunctionBody ), https://tc39.es/ecma262/#sec-async-functions-abstract-operations-async-function-start
void ECMAScriptFunctionObject::async_function_start(PromiseCapability const& promise_capability)
{
auto& vm = this->vm();
// 1. Let runningContext be the running execution context.
auto& running_context = vm.running_execution_context();
// 2. Let asyncContext be a copy of runningContext.
auto async_context = running_context.copy();
// 3. NOTE: Copying the execution state is required for AsyncBlockStart to resume its execution. It is ill-defined to resume a currently executing context.
// 4. Perform ! AsyncBlockStart(promiseCapability, asyncFunctionBody, asyncContext).
async_block_start(promise_capability, async_context);
}
// 27.7.5.2 AsyncBlockStart ( promiseCapability, asyncBody, asyncContext ), https://tc39.es/ecma262/#sec-asyncblockstart
void ECMAScriptFunctionObject::async_block_start(PromiseCapability const& promise_capability, ExecutionContext& async_context)
{
auto& vm = this->vm();
// 1. Assert: promiseCapability is a PromiseCapability Record.
// 2. Let runningContext be the running execution context.
auto& running_context = vm.running_execution_context();
// 3. Set the code evaluation state of asyncContext such that when evaluation is resumed for that execution context the following steps will be performed:
auto* execution_steps = NativeFunction::create(global_object(), "", [async_body = m_ecmascript_code, &promise_capability](auto& vm, auto& global_object) -> ThrowCompletionOr<Value> {
// a. Let result be the result of evaluating asyncBody.
auto result = async_body->execute(vm.interpreter(), global_object);
// b. Assert: If we return here, the async function either threw an exception or performed an implicit or explicit return; all awaiting is done.
// c. Remove asyncContext from the execution context stack and restore the execution context that is at the top of the execution context stack as the running execution context.
vm.pop_execution_context();
// NOTE: Running the AST node should eventually return a completion.
// Until it does, we assume "return" and include the undefined fallback from the call site.
// d. If result.[[Type]] is normal, then
if (false) {
// i. Perform ! Call(promiseCapability.[[Resolve]], undefined, « undefined »).
MUST(call(global_object, promise_capability.resolve, js_undefined(), js_undefined()));
}
// e. Else if result.[[Type]] is return, then
else if (!vm.exception()) {
// i. Perform ! Call(promiseCapability.[[Resolve]], undefined, « result.[[Value]] »).
MUST(call(global_object, promise_capability.resolve, js_undefined(), result.value_or(js_undefined())));
}
// f. Else,
else {
// i. Assert: result.[[Type]] is throw.
// ii. Perform ! Call(promiseCapability.[[Reject]], undefined, « result.[[Value]] »).
auto reason = vm.exception()->value();
vm.clear_exception();
vm.stop_unwind();
MUST(call(global_object, promise_capability.reject, js_undefined(), reason));
}
// g. Return.
return js_undefined();
});
// 4. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
vm.push_execution_context(async_context, global_object());
// 5. Resume the suspended evaluation of asyncContext. Let result be the value returned by the resumed computation.
auto result = vm.call(*execution_steps, async_context.this_value.is_empty() ? js_undefined() : async_context.this_value);
// 6. Assert: When we return here, asyncContext has already been removed from the execution context stack and runningContext is the currently running execution context.
VERIFY(&vm.running_execution_context() == &running_context);
// 7. Assert: result is a normal completion with a value of undefined. The possible sources of completion values are Await or, if the async function doesn't await anything, step 3.g above.
VERIFY(result.has_value() && result.value().is_undefined());
// 8. Return.
}
// 10.2.1.4 OrdinaryCallEvaluateBody ( F, argumentsList ), https://tc39.es/ecma262/#sec-ordinarycallevaluatebody
Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body()
{
@ -666,6 +752,8 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body()
auto* bytecode_interpreter = Bytecode::Interpreter::current();
if (bytecode_interpreter) {
if (m_kind == FunctionKind::Async)
return vm.throw_completion<InternalError>(global_object(), ErrorType::NotImplemented, "Async function execution in Bytecode interpreter");
// FIXME: pass something to evaluate default arguments with
TRY(function_declaration_instantiation(nullptr));
if (!m_bytecode_executable.has_value()) {
@ -690,8 +778,8 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body()
return normal_completion(TRY(GeneratorObject::create(global_object(), result, this, vm.running_execution_context().lexical_environment, bytecode_interpreter->snapshot_frame())));
} else {
if (m_kind != FunctionKind::Regular)
return vm.throw_completion<InternalError>(global_object(), ErrorType::NotImplemented, "Non regular function execution in AST interpreter");
if (m_kind == FunctionKind::Generator)
return vm.throw_completion<InternalError>(global_object(), ErrorType::NotImplemented, "Generator function execution in AST interpreter");
OwnPtr<Interpreter> local_interpreter;
Interpreter* ast_interpreter = vm.interpreter_if_exists();
@ -702,14 +790,36 @@ Completion ECMAScriptFunctionObject::ordinary_call_evaluate_body()
VM::InterpreterExecutionScope scope(*ast_interpreter);
TRY(function_declaration_instantiation(ast_interpreter));
if (m_kind == FunctionKind::Regular) {
TRY(function_declaration_instantiation(ast_interpreter));
auto result = m_ecmascript_code->execute(*ast_interpreter, global_object());
if (auto* exception = vm.exception())
return throw_completion(exception->value());
// NOTE: Running the AST node should eventually return a completion.
// Until it does, we assume "return" and include the undefined fallback from the call site.
return { Completion::Type::Return, result.value_or(js_undefined()), {} };
auto result = m_ecmascript_code->execute(*ast_interpreter, global_object());
if (auto* exception = vm.exception())
return throw_completion(exception->value());
// NOTE: Running the AST node should eventually return a completion.
// Until it does, we assume "return" and include the undefined fallback from the call site.
return { Completion::Type::Return, result.value_or(js_undefined()), {} };
} else if (m_kind == FunctionKind::Async) {
// 1. Let promiseCapability be ! NewPromiseCapability(%Promise%).
auto promise_capability = MUST(new_promise_capability(global_object(), global_object().promise_constructor()));
// 2. Let declResult be FunctionDeclarationInstantiation(functionObject, argumentsList).
auto declaration_result = function_declaration_instantiation(ast_interpreter);
// 3. If declResult is not an abrupt completion, then
if (!declaration_result.is_throw_completion() || !declaration_result.throw_completion().is_abrupt()) {
// a. Perform ! AsyncFunctionStart(promiseCapability, FunctionBody).
async_function_start(promise_capability);
}
// 4. Else,
else {
// a. Perform ! Call(promiseCapability.[[Reject]], undefined, « declResult.[[Value]] »).
MUST(call(global_object(), promise_capability.reject, js_undefined(), declaration_result.throw_completion().value()));
}
// 5. Return Completion { [[Type]]: return, [[Value]]: promiseCapability.[[Promise]], [[Target]]: empty }.
return Completion { Completion::Type::Return, promise_capability.promise, {} };
}
}
VERIFY_NOT_REACHED();
}

View file

@ -73,7 +73,7 @@ public:
bool has_simple_parameter_list() const { return m_has_simple_parameter_list; }
// Equivalent to absence of [[Construct]]
virtual bool has_constructor() const override { return !(m_is_arrow_function || m_kind == FunctionKind::Generator); }
virtual bool has_constructor() const override { return m_kind == FunctionKind::Regular && !m_is_arrow_function; }
protected:
virtual bool is_strict_mode() const final { return m_strict; }
@ -87,6 +87,9 @@ private:
void prepare_for_ordinary_call(ExecutionContext& callee_context, Object* new_target);
void ordinary_call_bind_this(ExecutionContext&, Value this_argument);
void async_function_start(PromiseCapability const&);
void async_block_start(PromiseCapability const&, ExecutionContext&);
ThrowCompletionOr<void> function_declaration_instantiation(Interpreter*);
// Internal Slots of ECMAScript Function Objects, https://tc39.es/ecma262/#table-internal-slots-of-ecmascript-function-objects

View file

@ -22,6 +22,30 @@ struct ExecutionContext {
{
}
[[nodiscard]] ExecutionContext copy() const
{
ExecutionContext copy { arguments.copy() };
copy.function = function;
copy.realm = realm;
copy.lexical_environment = lexical_environment;
copy.variable_environment = variable_environment;
copy.private_environment = private_environment;
copy.current_node = current_node;
copy.function_name = function_name;
copy.this_value = this_value;
copy.is_strict_mode = is_strict_mode;
return copy;
}
private:
explicit ExecutionContext(MarkedValueList existing_arguments)
: arguments(move(existing_arguments))
{
}
public:
FunctionObject* function { nullptr }; // [[Function]]
Realm* realm { nullptr }; // [[Realm]]
Environment* lexical_environment { nullptr }; // [[LexicalEnvironment]]

View file

@ -54,7 +54,8 @@ ThrowCompletionOr<RefPtr<FunctionExpression>> FunctionConstructor::create_dynami
body_source = TRY(vm.argument(vm.argument_count() - 1).to_string(global_object));
}
auto is_generator = kind == FunctionKind::Generator;
auto source = String::formatted("function{} anonymous({}\n) {{\n{}\n}}", is_generator ? "*" : "", parameters_source, body_source);
auto is_async = kind == FunctionKind::Async;
auto source = String::formatted("{}function{} anonymous({}\n) {{\n{}\n}}", is_async ? "async " : "", is_generator ? "*" : "", parameters_source, body_source);
auto parser = Parser(Lexer(source));
auto function = parser.parse_function_node<FunctionExpression>();
if (parser.has_errors()) {

View file

@ -20,6 +20,8 @@
#include <LibJS/Runtime/ArrayConstructor.h>
#include <LibJS/Runtime/ArrayIteratorPrototype.h>
#include <LibJS/Runtime/ArrayPrototype.h>
#include <LibJS/Runtime/AsyncFunctionConstructor.h>
#include <LibJS/Runtime/AsyncFunctionPrototype.h>
#include <LibJS/Runtime/AtomicsObject.h>
#include <LibJS/Runtime/BigIntConstructor.h>
#include <LibJS/Runtime/BigIntPrototype.h>