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:
parent
05c3320da3
commit
681787de76
17 changed files with 399 additions and 36 deletions
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
29
Userland/Libraries/LibJS/Runtime/AsyncFunctionConstructor.h
Normal file
29
Userland/Libraries/LibJS/Runtime/AsyncFunctionConstructor.h
Normal 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; }
|
||||
};
|
||||
|
||||
}
|
26
Userland/Libraries/LibJS/Runtime/AsyncFunctionPrototype.cpp
Normal file
26
Userland/Libraries/LibJS/Runtime/AsyncFunctionPrototype.cpp
Normal 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);
|
||||
}
|
||||
|
||||
}
|
22
Userland/Libraries/LibJS/Runtime/AsyncFunctionPrototype.h
Normal file
22
Userland/Libraries/LibJS/Runtime/AsyncFunctionPrototype.h
Normal 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;
|
||||
};
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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]]
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue