mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-12-08 06:09:58 +00:00
This hosts the ability to compile and run JavaScript to implement native functions. This is particularly useful for any native function that is not a normal function, for example async functions such as Array.fromAsync, which require yielding. These functions are not allowed to observe anything from outside their environment. Any global identifiers will instead be assumed to be a reference to an abstract operation or a constant. The generator will inject the appropriate bytecode if the name of the global identifier matches a known name. Anything else will cause a code generation error.
644 lines
28 KiB
C++
644 lines
28 KiB
C++
/*
|
||
* Copyright (c) 2020, Stephan Unverwerth <s.unverwerth@serenityos.org>
|
||
* Copyright (c) 2020-2023, Linus Groh <linusg@serenityos.org>
|
||
* Copyright (c) 2023-2025, Andreas Kling <andreas@ladybird.org>
|
||
* Copyright (c) 2023, Shannon Booth <shannon@serenityos.org>
|
||
*
|
||
* SPDX-License-Identifier: BSD-2-Clause
|
||
*/
|
||
|
||
#include <AK/Debug.h>
|
||
#include <AK/Function.h>
|
||
#include <LibJS/AST.h>
|
||
#include <LibJS/Bytecode/BasicBlock.h>
|
||
#include <LibJS/Bytecode/Generator.h>
|
||
#include <LibJS/Bytecode/Interpreter.h>
|
||
#include <LibJS/Runtime/AbstractOperations.h>
|
||
#include <LibJS/Runtime/Array.h>
|
||
#include <LibJS/Runtime/AsyncFunctionDriverWrapper.h>
|
||
#include <LibJS/Runtime/AsyncGenerator.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/GlobalEnvironment.h>
|
||
#include <LibJS/Runtime/GlobalObject.h>
|
||
#include <LibJS/Runtime/NativeFunction.h>
|
||
#include <LibJS/Runtime/PromiseCapability.h>
|
||
#include <LibJS/Runtime/PromiseConstructor.h>
|
||
#include <LibJS/Runtime/Value.h>
|
||
#include <LibJS/Runtime/ValueInlines.h>
|
||
|
||
namespace JS {
|
||
|
||
GC_DEFINE_ALLOCATOR(ECMAScriptFunctionObject);
|
||
|
||
GC::Ref<ECMAScriptFunctionObject> ECMAScriptFunctionObject::create(Realm& realm, Utf16FlyString name, ByteString source_text, Statement const& ecmascript_code, NonnullRefPtr<FunctionParameters const> parameters, i32 function_length, Vector<LocalVariable> local_variables_names, Environment* parent_environment, PrivateEnvironment* private_environment, FunctionKind kind, bool is_strict, FunctionParsingInsights parsing_insights, bool is_arrow_function, Variant<PropertyKey, PrivateName, Empty> class_field_initializer_name)
|
||
{
|
||
Object* prototype = nullptr;
|
||
switch (kind) {
|
||
case FunctionKind::Normal:
|
||
prototype = realm.intrinsics().function_prototype();
|
||
break;
|
||
case FunctionKind::Generator:
|
||
prototype = realm.intrinsics().generator_function_prototype();
|
||
break;
|
||
case FunctionKind::Async:
|
||
prototype = realm.intrinsics().async_function_prototype();
|
||
break;
|
||
case FunctionKind::AsyncGenerator:
|
||
prototype = realm.intrinsics().async_generator_function_prototype();
|
||
break;
|
||
}
|
||
|
||
auto shared_data = realm.heap().allocate<SharedFunctionInstanceData>(
|
||
realm.vm(),
|
||
kind,
|
||
move(name),
|
||
function_length,
|
||
*parameters,
|
||
ecmascript_code,
|
||
source_text,
|
||
is_strict,
|
||
is_arrow_function,
|
||
parsing_insights,
|
||
move(local_variables_names));
|
||
|
||
shared_data->m_class_field_initializer_name = move(class_field_initializer_name);
|
||
|
||
return realm.create<ECMAScriptFunctionObject>(
|
||
move(shared_data),
|
||
parent_environment,
|
||
private_environment,
|
||
*prototype);
|
||
}
|
||
|
||
GC::Ref<ECMAScriptFunctionObject> ECMAScriptFunctionObject::create(Realm& realm, Utf16FlyString name, Object& prototype, ByteString source_text, Statement const& ecmascript_code, NonnullRefPtr<FunctionParameters const> parameters, i32 function_length, Vector<LocalVariable> local_variables_names, Environment* parent_environment, PrivateEnvironment* private_environment, FunctionKind kind, bool is_strict, FunctionParsingInsights parsing_insights, bool is_arrow_function, Variant<PropertyKey, PrivateName, Empty> class_field_initializer_name)
|
||
{
|
||
auto shared_data = realm.heap().allocate<SharedFunctionInstanceData>(
|
||
realm.vm(),
|
||
kind,
|
||
move(name),
|
||
function_length,
|
||
*parameters,
|
||
ecmascript_code,
|
||
source_text,
|
||
is_strict,
|
||
is_arrow_function,
|
||
parsing_insights,
|
||
move(local_variables_names));
|
||
shared_data->m_class_field_initializer_name = move(class_field_initializer_name);
|
||
return realm.create<ECMAScriptFunctionObject>(
|
||
move(shared_data),
|
||
parent_environment,
|
||
private_environment,
|
||
prototype);
|
||
}
|
||
|
||
GC::Ref<ECMAScriptFunctionObject> ECMAScriptFunctionObject::create_from_function_node(
|
||
FunctionNode const& function_node,
|
||
Utf16FlyString name,
|
||
GC::Ref<Realm> realm,
|
||
GC::Ptr<Environment> parent_environment,
|
||
GC::Ptr<PrivateEnvironment> private_environment)
|
||
{
|
||
GC::Ptr<Object> prototype = nullptr;
|
||
switch (function_node.kind()) {
|
||
case FunctionKind::Normal:
|
||
prototype = realm->intrinsics().function_prototype();
|
||
break;
|
||
case FunctionKind::Generator:
|
||
prototype = realm->intrinsics().generator_function_prototype();
|
||
break;
|
||
case FunctionKind::Async:
|
||
prototype = realm->intrinsics().async_function_prototype();
|
||
break;
|
||
case FunctionKind::AsyncGenerator:
|
||
prototype = realm->intrinsics().async_generator_function_prototype();
|
||
break;
|
||
}
|
||
|
||
auto shared_data = function_node.shared_data();
|
||
|
||
if (!shared_data) {
|
||
shared_data = realm->heap().allocate<SharedFunctionInstanceData>(
|
||
realm->vm(),
|
||
function_node.kind(),
|
||
move(name),
|
||
function_node.function_length(),
|
||
function_node.parameters(),
|
||
*function_node.body_ptr(),
|
||
function_node.source_text(),
|
||
function_node.is_strict_mode(),
|
||
function_node.is_arrow_function(),
|
||
function_node.parsing_insights(),
|
||
function_node.local_variables_names());
|
||
function_node.set_shared_data(shared_data);
|
||
}
|
||
|
||
return realm->create<ECMAScriptFunctionObject>(
|
||
*shared_data,
|
||
parent_environment,
|
||
private_environment,
|
||
*prototype);
|
||
}
|
||
|
||
ECMAScriptFunctionObject::ECMAScriptFunctionObject(
|
||
GC::Ref<SharedFunctionInstanceData> shared_data,
|
||
Environment* parent_environment,
|
||
PrivateEnvironment* private_environment,
|
||
Object& prototype)
|
||
: FunctionObject(prototype)
|
||
, m_shared_data(shared_data)
|
||
, m_environment(parent_environment)
|
||
, m_private_environment(private_environment)
|
||
{
|
||
if (!is_arrow_function() && kind() == FunctionKind::Normal)
|
||
unsafe_set_shape(realm()->intrinsics().normal_function_shape());
|
||
|
||
// 15. Set F.[[ScriptOrModule]] to GetActiveScriptOrModule().
|
||
m_script_or_module = vm().get_active_script_or_module();
|
||
}
|
||
|
||
void ECMAScriptFunctionObject::initialize(Realm& realm)
|
||
{
|
||
auto& vm = this->vm();
|
||
Base::initialize(realm);
|
||
// Note: The ordering of these properties must be: length, name, prototype which is the order
|
||
// they are defined in the spec: https://tc39.es/ecma262/#sec-function-instances .
|
||
// This is observable through something like: https://tc39.es/ecma262/#sec-ordinaryownpropertykeys
|
||
// which must give the properties in chronological order which in this case is the order they
|
||
// are defined in the spec.
|
||
|
||
m_name_string = PrimitiveString::create(vm, name());
|
||
|
||
if (!is_arrow_function() && kind() == FunctionKind::Normal) {
|
||
put_direct(realm.intrinsics().normal_function_length_offset(), Value(function_length()));
|
||
put_direct(realm.intrinsics().normal_function_name_offset(), m_name_string);
|
||
|
||
auto prototype = Object::create_with_premade_shape(realm.intrinsics().normal_function_prototype_shape());
|
||
prototype->put_direct(realm.intrinsics().normal_function_prototype_constructor_offset(), this);
|
||
put_direct(realm.intrinsics().normal_function_prototype_offset(), prototype);
|
||
} else {
|
||
PropertyDescriptor length_descriptor { .value = Value(function_length()), .writable = false, .enumerable = false, .configurable = true };
|
||
MUST(define_property_or_throw(vm.names.length, length_descriptor));
|
||
PropertyDescriptor name_descriptor { .value = m_name_string, .writable = false, .enumerable = false, .configurable = true };
|
||
MUST(define_property_or_throw(vm.names.name, name_descriptor));
|
||
|
||
if (!is_arrow_function()) {
|
||
Object* prototype = nullptr;
|
||
switch (kind()) {
|
||
case FunctionKind::Normal:
|
||
VERIFY_NOT_REACHED();
|
||
break;
|
||
case FunctionKind::Generator:
|
||
// prototype is "g1.prototype" in figure-2 (https://tc39.es/ecma262/img/figure-2.png)
|
||
prototype = Object::create_prototype(realm, realm.intrinsics().generator_function_prototype_prototype());
|
||
break;
|
||
case FunctionKind::Async:
|
||
break;
|
||
case FunctionKind::AsyncGenerator:
|
||
prototype = Object::create_prototype(realm, realm.intrinsics().async_generator_function_prototype_prototype());
|
||
break;
|
||
}
|
||
// 27.7.4 AsyncFunction Instances, https://tc39.es/ecma262/#sec-async-function-instances
|
||
// AsyncFunction instances do not have a prototype property as they are not constructible.
|
||
if (kind() != FunctionKind::Async)
|
||
define_direct_property(vm.names.prototype, prototype, Attribute::Writable);
|
||
}
|
||
}
|
||
}
|
||
|
||
ThrowCompletionOr<void> ECMAScriptFunctionObject::get_stack_frame_size(size_t& registers_and_constants_and_locals_count, size_t& argument_count)
|
||
{
|
||
auto& executable = shared_data().m_executable;
|
||
if (!executable) {
|
||
if (is_module_wrapper()) {
|
||
executable = TRY(Bytecode::compile(vm(), ecmascript_code(), kind(), name()));
|
||
} else {
|
||
executable = TRY(Bytecode::compile(vm(), shared_data(), Bytecode::BuiltinAbstractOperationsEnabled::No));
|
||
}
|
||
}
|
||
registers_and_constants_and_locals_count = executable->registers_and_constants_and_locals_count;
|
||
argument_count = max(argument_count, formal_parameters().size());
|
||
return {};
|
||
}
|
||
|
||
// 10.2.1 [[Call]] ( thisArgument, argumentsList ), https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist
|
||
FLATTEN ThrowCompletionOr<Value> ECMAScriptFunctionObject::internal_call(ExecutionContext& callee_context, Value this_argument)
|
||
{
|
||
auto& vm = this->vm();
|
||
|
||
ASSERT(bytecode_executable());
|
||
|
||
// 1. Let callerContext be the running execution context.
|
||
// NOTE: No-op, kept by the VM in its execution context stack.
|
||
|
||
// 2. Let calleeContext be PrepareForOrdinaryCall(F, undefined).
|
||
prepare_for_ordinary_call(vm, callee_context, nullptr);
|
||
|
||
// 3. Assert: calleeContext is now the running execution context.
|
||
ASSERT(&vm.running_execution_context() == &callee_context);
|
||
|
||
// 4. If F.[[IsClassConstructor]] is true, then
|
||
if (is_class_constructor()) [[unlikely]] {
|
||
// a. Let error be a newly created TypeError object.
|
||
// b. NOTE: error is created in calleeContext with F's associated Realm Record.
|
||
auto throw_completion = vm.throw_completion<TypeError>(ErrorType::ClassConstructorWithoutNew, name());
|
||
|
||
// c. Remove calleeContext from the execution context stack and restore callerContext as the running execution context.
|
||
vm.pop_execution_context();
|
||
|
||
// d. Return ThrowCompletion(error).
|
||
return throw_completion;
|
||
}
|
||
|
||
// 5. Perform OrdinaryCallBindThis(F, calleeContext, thisArgument).
|
||
if (uses_this())
|
||
ordinary_call_bind_this(vm, callee_context, this_argument);
|
||
|
||
// 6. Let result be Completion(OrdinaryCallEvaluateBody(F, argumentsList)).
|
||
auto result = ordinary_call_evaluate_body(vm, callee_context);
|
||
|
||
// 7. Remove calleeContext from the execution context stack and restore callerContext as the running execution context.
|
||
vm.pop_execution_context();
|
||
|
||
// 8. If result.[[Type]] is return, return result.[[Value]].
|
||
// 9. Assert: result is a throw completion.
|
||
// 10. Return ? result.
|
||
return result;
|
||
}
|
||
|
||
// 10.2.2 [[Construct]] ( argumentsList, newTarget ), https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget
|
||
FLATTEN ThrowCompletionOr<GC::Ref<Object>> ECMAScriptFunctionObject::internal_construct(ExecutionContext& callee_context, FunctionObject& new_target)
|
||
{
|
||
auto& vm = this->vm();
|
||
|
||
ASSERT(bytecode_executable());
|
||
|
||
// 1. Let callerContext be the running execution context.
|
||
// NOTE: No-op, kept by the VM in its execution context stack.
|
||
|
||
// 2. Let kind be F.[[ConstructorKind]].
|
||
auto kind = constructor_kind();
|
||
|
||
GC::Ptr<Object> this_argument;
|
||
|
||
// 3. If kind is base, then
|
||
if (kind == ConstructorKind::Base) {
|
||
// a. Let thisArgument be ? OrdinaryCreateFromConstructor(newTarget, "%Object.prototype%").
|
||
this_argument = TRY(ordinary_create_from_constructor<Object>(vm, *realm(), new_target, &Intrinsics::object_prototype, ConstructWithPrototypeTag::Tag));
|
||
}
|
||
|
||
// 4. Let calleeContext be PrepareForOrdinaryCall(F, newTarget).
|
||
prepare_for_ordinary_call(vm, callee_context, &new_target);
|
||
|
||
// 5. Assert: calleeContext is now the running execution context.
|
||
ASSERT(&vm.running_execution_context() == &callee_context);
|
||
|
||
// 6. If kind is base, then
|
||
if (kind == ConstructorKind::Base) {
|
||
// a. Perform OrdinaryCallBindThis(F, calleeContext, thisArgument).
|
||
if (uses_this())
|
||
ordinary_call_bind_this(vm, callee_context, this_argument);
|
||
|
||
// b. Let initializeResult be Completion(InitializeInstanceElements(thisArgument, F)).
|
||
auto initialize_result = this_argument->initialize_instance_elements(*this);
|
||
|
||
// c. If initializeResult is an abrupt completion, then
|
||
if (initialize_result.is_throw_completion()) {
|
||
// i. Remove calleeContext from the execution context stack and restore callerContext as the running execution context.
|
||
vm.pop_execution_context();
|
||
|
||
// ii. Return ? initializeResult.
|
||
return initialize_result.throw_completion();
|
||
}
|
||
}
|
||
|
||
// 7. Let constructorEnv be the LexicalEnvironment of calleeContext.
|
||
auto constructor_env = callee_context.lexical_environment;
|
||
|
||
// 8. Let result be Completion(OrdinaryCallEvaluateBody(F, argumentsList)).
|
||
auto result = ordinary_call_evaluate_body(vm, callee_context);
|
||
|
||
// 9. Remove calleeContext from the execution context stack and restore callerContext as the running execution context.
|
||
vm.pop_execution_context();
|
||
|
||
// 10. If result is a throw completion, then
|
||
if (result.is_error()) {
|
||
// a. Return ? result.
|
||
return result.release_error();
|
||
}
|
||
|
||
// 11. Assert: result is a return completion.
|
||
// NOTE: We already checked !is_error() above.
|
||
|
||
// 12. If Type(result.[[Value]]) is Object, return result.[[Value]].
|
||
if (result.value().is_object())
|
||
return GC::Ref<Object> { const_cast<Object&>(result.value().as_object()) };
|
||
|
||
// 13. If kind is base, return thisArgument.
|
||
if (kind == ConstructorKind::Base)
|
||
return *this_argument;
|
||
|
||
// 14. If result.[[Value]] is not undefined, throw a TypeError exception.
|
||
if (!result.value().is_undefined())
|
||
return vm.throw_completion<TypeError>(ErrorType::DerivedConstructorReturningInvalidValue);
|
||
|
||
// 15. Let thisBinding be ? constructorEnv.GetThisBinding().
|
||
auto this_binding = TRY(constructor_env->get_this_binding(vm));
|
||
|
||
// 16. Assert: Type(thisBinding) is Object.
|
||
ASSERT(this_binding.is_object());
|
||
|
||
// 17. Return thisBinding.
|
||
return this_binding.as_object();
|
||
}
|
||
|
||
void ECMAScriptFunctionObject::visit_edges(Visitor& visitor)
|
||
{
|
||
Base::visit_edges(visitor);
|
||
visitor.visit(m_environment);
|
||
visitor.visit(m_private_environment);
|
||
visitor.visit(m_home_object);
|
||
visitor.visit(m_name_string);
|
||
visitor.visit(m_shared_data);
|
||
|
||
if (m_class_data) {
|
||
for (auto& field : m_class_data->fields) {
|
||
field.initializer.visit(
|
||
[&visitor](GC::Ref<ECMAScriptFunctionObject>& initializer) {
|
||
visitor.visit(initializer);
|
||
},
|
||
[&visitor](Value initializer) {
|
||
visitor.visit(initializer);
|
||
},
|
||
[](Empty) {});
|
||
if (auto* property_key_ptr = field.name.get_pointer<PropertyKey>(); property_key_ptr && property_key_ptr->is_symbol())
|
||
visitor.visit(property_key_ptr->as_symbol());
|
||
}
|
||
|
||
for (auto& private_element : m_class_data->private_methods)
|
||
visitor.visit(private_element.value);
|
||
}
|
||
|
||
m_script_or_module.visit(
|
||
[](Empty) {},
|
||
[&](auto& script_or_module) {
|
||
visitor.visit(script_or_module);
|
||
});
|
||
}
|
||
|
||
// 10.2.7 MakeMethod ( F, homeObject ), https://tc39.es/ecma262/#sec-makemethod
|
||
void ECMAScriptFunctionObject::make_method(Object& home_object)
|
||
{
|
||
// 1. Set F.[[HomeObject]] to homeObject.
|
||
m_home_object = &home_object;
|
||
|
||
// 2. Return unused.
|
||
}
|
||
|
||
// 10.2.1.1 PrepareForOrdinaryCall ( F, newTarget ), https://tc39.es/ecma262/#sec-prepareforordinarycall
|
||
void ECMAScriptFunctionObject::prepare_for_ordinary_call(VM& vm, ExecutionContext& callee_context, Object* new_target)
|
||
{
|
||
// 1. Let callerContext be the running execution context.
|
||
// 2. Let calleeContext be a new ECMAScript code execution context.
|
||
|
||
// 3. Set the Function of calleeContext to F.
|
||
callee_context.function = this;
|
||
|
||
// 4. Let calleeRealm be F.[[Realm]].
|
||
// 5. Set the Realm of calleeContext to calleeRealm.
|
||
callee_context.realm = realm();
|
||
|
||
// 6. Set the ScriptOrModule of calleeContext to F.[[ScriptOrModule]].
|
||
callee_context.script_or_module = m_script_or_module;
|
||
|
||
if (function_environment_needed()) {
|
||
// 7. Let localEnv be NewFunctionEnvironment(F, newTarget).
|
||
auto local_environment = new_function_environment(*this, new_target);
|
||
local_environment->ensure_capacity(shared_data().m_function_environment_bindings_count);
|
||
|
||
// 8. Set the LexicalEnvironment of calleeContext to localEnv.
|
||
callee_context.lexical_environment = local_environment;
|
||
|
||
// 9. Set the VariableEnvironment of calleeContext to localEnv.
|
||
callee_context.variable_environment = local_environment;
|
||
} else {
|
||
callee_context.lexical_environment = environment();
|
||
callee_context.variable_environment = environment();
|
||
}
|
||
|
||
// 10. Set the PrivateEnvironment of calleeContext to F.[[PrivateEnvironment]].
|
||
callee_context.private_environment = m_private_environment;
|
||
|
||
// 11. If callerContext is not already suspended, suspend callerContext.
|
||
// 12. Push calleeContext onto the execution context stack; calleeContext is now the running execution context.
|
||
|
||
// NOTE: We don't check for stack overflow here. The bytecode interpreter will do it anyway
|
||
// when entering the function we're about to call.
|
||
vm.push_execution_context(callee_context);
|
||
|
||
// 13. NOTE: Any exception objects produced after this point are associated with calleeRealm.
|
||
// 14. Return calleeContext.
|
||
// NOTE: See the comment after step 2 above about how contexts are allocated on the C++ stack.
|
||
}
|
||
|
||
// 10.2.1.2 OrdinaryCallBindThis ( F, calleeContext, thisArgument ), https://tc39.es/ecma262/#sec-ordinarycallbindthis
|
||
void ECMAScriptFunctionObject::ordinary_call_bind_this(VM& vm, ExecutionContext& callee_context, Value this_argument)
|
||
{
|
||
// 1. Let thisMode be F.[[ThisMode]].
|
||
// If thisMode is lexical, return unused.
|
||
if (this_mode() == ThisMode::Lexical)
|
||
return;
|
||
|
||
// 3. Let calleeRealm be F.[[Realm]].
|
||
auto callee_realm = realm();
|
||
|
||
// 4. Let localEnv be the LexicalEnvironment of calleeContext.
|
||
auto local_env = callee_context.lexical_environment;
|
||
|
||
Value this_value;
|
||
|
||
// 5. If thisMode is strict, let thisValue be thisArgument.
|
||
if (this_mode() == ThisMode::Strict) {
|
||
this_value = this_argument;
|
||
}
|
||
// 6. Else,
|
||
else {
|
||
// a. If thisArgument is undefined or null, then
|
||
if (this_argument.is_nullish()) {
|
||
// i. Let globalEnv be calleeRealm.[[GlobalEnv]].
|
||
// ii. Assert: globalEnv is a global Environment Record.
|
||
auto& global_env = callee_realm->global_environment();
|
||
|
||
// iii. Let thisValue be globalEnv.[[GlobalThisValue]].
|
||
this_value = &global_env.global_this_value();
|
||
}
|
||
// b. Else,
|
||
else {
|
||
// i. Let thisValue be ! ToObject(thisArgument).
|
||
this_value = MUST(this_argument.to_object(vm));
|
||
|
||
// ii. NOTE: ToObject produces wrapper objects using calleeRealm.
|
||
ASSERT(vm.current_realm() == callee_realm);
|
||
}
|
||
}
|
||
|
||
// 7. Assert: localEnv is a function Environment Record.
|
||
// 8. Assert: The next step never returns an abrupt completion because localEnv.[[ThisBindingStatus]] is not initialized.
|
||
// 9. Perform ! localEnv.BindThisValue(thisValue).
|
||
callee_context.this_value = this_value;
|
||
if (function_environment_needed())
|
||
MUST(as<FunctionEnvironment>(*local_env).bind_this_value(vm, this_value));
|
||
|
||
// 10. Return unused.
|
||
}
|
||
|
||
// 27.7.5.1 AsyncFunctionStart ( promiseCapability, asyncFunctionBody ), https://tc39.es/ecma262/#sec-async-functions-abstract-operations-async-function-start
|
||
template<typename T>
|
||
void async_function_start(VM& vm, PromiseCapability const& promise_capability, T const& async_function_body)
|
||
{
|
||
// 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(vm, async_function_body, promise_capability, *async_context);
|
||
|
||
// 5. Return unused.
|
||
}
|
||
|
||
// 27.7.5.2 AsyncBlockStart ( promiseCapability, asyncBody, asyncContext ), https://tc39.es/ecma262/#sec-asyncblockstart
|
||
template<typename T>
|
||
void async_block_start(VM& vm, T const& async_body, PromiseCapability const& promise_capability, ExecutionContext& async_context)
|
||
{
|
||
auto& realm = *vm.current_realm();
|
||
|
||
// 1. Let runningContext be the running execution context.
|
||
auto& running_context = vm.running_execution_context();
|
||
|
||
// 2. Let closure be a new Abstract Closure with no parameters that captures promiseCapability and asyncBody and performs the following steps when called:
|
||
auto closure = NativeFunction::create(realm, {}, [&async_body, &promise_capability](auto& vm) -> ThrowCompletionOr<Value> {
|
||
Completion result;
|
||
|
||
// a. Let acAsyncContext be the running execution context.
|
||
|
||
// b. If asyncBody is a Parse Node, then
|
||
if constexpr (!IsSame<T, GC::Function<Completion()>>) {
|
||
// i. Let result be Completion(Evaluation of asyncBody).
|
||
auto maybe_executable = Bytecode::compile(vm, async_body, FunctionKind::Async, "AsyncBlockStart"_utf16_fly_string);
|
||
if (maybe_executable.is_error())
|
||
result = maybe_executable.release_error();
|
||
else
|
||
result = vm.bytecode_interpreter().run_executable(vm.running_execution_context(), *maybe_executable.value(), {});
|
||
}
|
||
// c. Else,
|
||
else {
|
||
// i. Assert: asyncBody is an Abstract Closure with no parameters.
|
||
// ii. Let result be asyncBody().
|
||
result = async_body.function()();
|
||
}
|
||
// d. Assert: If we return here, the async function either threw an exception or performed an implicit or explicit return; all awaiting is done.
|
||
// e. Remove acAsyncContext 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();
|
||
|
||
// f. If result is a normal completion, then
|
||
if (result.type() == Completion::Type::Normal) {
|
||
// i. Perform ! Call(promiseCapability.[[Resolve]], undefined, « undefined »).
|
||
MUST(call(vm, *promise_capability.resolve(), js_undefined(), js_undefined()));
|
||
}
|
||
// g. Else if result is a return completion, then
|
||
else if (result.type() == Completion::Type::Return) {
|
||
// i. Perform ! Call(promiseCapability.[[Resolve]], undefined, « result.[[Value]] »).
|
||
MUST(call(vm, *promise_capability.resolve(), js_undefined(), result.value()));
|
||
}
|
||
// h. Else,
|
||
else {
|
||
// i. Assert: result is a throw completion.
|
||
VERIFY(result.type() == Completion::Type::Throw);
|
||
|
||
// ii. Perform ! Call(promiseCapability.[[Reject]], undefined, « result.[[Value]] »).
|
||
MUST(call(vm, *promise_capability.reject(), js_undefined(), result.value()));
|
||
}
|
||
// i. Return unused.
|
||
// NOTE: We don't support returning an empty/optional/unused value here.
|
||
return js_undefined();
|
||
});
|
||
|
||
// 3. Set the code evaluation state of asyncContext such that when evaluation is resumed for that execution context, closure will be called with no arguments.
|
||
// 4. Push asyncContext onto the execution context stack; asyncContext is now the running execution context.
|
||
auto push_result = vm.push_execution_context(async_context, {});
|
||
if (push_result.is_error())
|
||
return;
|
||
|
||
// 5. Resume the suspended evaluation of asyncContext. Let result be the value returned by the resumed computation.
|
||
auto result = call(vm, *closure, *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 unused. The possible sources of this value are Await or, if the async function doesn't await anything, step 2.i above.
|
||
VERIFY(result.has_value() && result.value().is_undefined());
|
||
|
||
// 8. Return unused.
|
||
}
|
||
|
||
template void async_block_start(VM&, NonnullRefPtr<Statement const> const& async_body, PromiseCapability const&, ExecutionContext&);
|
||
template void async_function_start(VM&, PromiseCapability const&, NonnullRefPtr<Statement const> const& async_function_body);
|
||
|
||
template void async_block_start(VM&, GC::Function<Completion()> const& async_body, PromiseCapability const&, ExecutionContext&);
|
||
template void async_function_start(VM&, PromiseCapability const&, GC::Function<Completion()> const& async_function_body);
|
||
|
||
// 10.2.1.4 OrdinaryCallEvaluateBody ( F, argumentsList ), https://tc39.es/ecma262/#sec-ordinarycallevaluatebody
|
||
// 15.8.4 Runtime Semantics: EvaluateAsyncFunctionBody, https://tc39.es/ecma262/#sec-runtime-semantics-evaluatefunctionbody
|
||
ThrowCompletionOr<Value> ECMAScriptFunctionObject::ordinary_call_evaluate_body(VM& vm, ExecutionContext& context)
|
||
{
|
||
auto result = TRY(vm.bytecode_interpreter().run_executable(context, *bytecode_executable(), {}));
|
||
|
||
// NOTE: Running the bytecode should eventually return a completion.
|
||
// Until it does, we assume "return" and include the undefined fallback from the call site.
|
||
if (kind() == FunctionKind::Normal)
|
||
return result;
|
||
|
||
if (kind() == FunctionKind::AsyncGenerator)
|
||
return AsyncGenerator::create(*context.realm, result, GC::Ref { *this }, context.copy());
|
||
|
||
auto generator_object = GeneratorObject::create(*context.realm, result, GC::Ref { *this }, context.copy());
|
||
|
||
// NOTE: Async functions are entirely transformed to generator functions, and wrapped in a custom driver that returns a promise
|
||
// See AwaitExpression::generate_bytecode() for the transformation.
|
||
if (kind() == FunctionKind::Async)
|
||
return AsyncFunctionDriverWrapper::create(*context.realm, generator_object);
|
||
|
||
ASSERT(kind() == FunctionKind::Generator);
|
||
return generator_object;
|
||
}
|
||
|
||
void ECMAScriptFunctionObject::set_name(Utf16FlyString const& name)
|
||
{
|
||
auto& vm = this->vm();
|
||
const_cast<SharedFunctionInstanceData&>(shared_data()).m_name = name;
|
||
m_name_string = PrimitiveString::create(vm, name);
|
||
PropertyDescriptor descriptor { .value = m_name_string, .writable = false, .enumerable = false, .configurable = true };
|
||
MUST(define_property_or_throw(vm.names.name, descriptor));
|
||
}
|
||
|
||
ECMAScriptFunctionObject::ClassData& ECMAScriptFunctionObject::ensure_class_data() const
|
||
{
|
||
if (!m_class_data)
|
||
m_class_data = make<ClassData>();
|
||
return *m_class_data;
|
||
}
|
||
|
||
Utf16String ECMAScriptFunctionObject::name_for_call_stack() const
|
||
{
|
||
return m_name_string->utf16_string();
|
||
}
|
||
|
||
}
|