mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2026-04-18 18:00:31 +00:00
Every function call allocates an ExecutionContext with a trailing array of Values for registers, locals, constants, and arguments. Previously, the constructor would initialize all slots to js_special_empty_value(), but constant slots were then immediately overwritten by the interpreter copying in values from the Executable before execution began. To eliminate this redundant initialization, we rearrange the layout from [registers | constants | locals] to [registers | locals | constants]. This groups registers and locals together at the front, allowing us to initialize only those slots while leaving constant slots uninitialized until they're populated with their actual values. This reduces the per-call initialization cost from O(registers + locals + constants) to O(registers + locals). Also tightens up the types involved (size_t -> u32) and adds VERIFYs to guard against overflow when computing the combined slot counts, and to ensure the total fits within the 29-bit operand index field.
145 lines
5.9 KiB
C++
145 lines
5.9 KiB
C++
/*
|
|
* Copyright (c) 2025, Luke Wilde <luke@ladybird.org>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#include <AK/TypeCasts.h>
|
|
#include <LibJS/Bytecode/BuiltinAbstractOperationsEnabled.h>
|
|
#include <LibJS/Bytecode/Generator.h>
|
|
#include <LibJS/Bytecode/Interpreter.h>
|
|
#include <LibJS/Runtime/AsyncFunctionDriverWrapper.h>
|
|
#include <LibJS/Runtime/AsyncGenerator.h>
|
|
#include <LibJS/Runtime/GeneratorObject.h>
|
|
#include <LibJS/Runtime/NativeJavaScriptBackedFunction.h>
|
|
|
|
namespace JS {
|
|
|
|
GC_DEFINE_ALLOCATOR(NativeJavaScriptBackedFunction);
|
|
|
|
// 10.3.3 CreateBuiltinFunction ( behaviour, length, name, additionalInternalSlotsList [ , realm [ , prototype [ , prefix ] ] ] ), https://tc39.es/ecma262/#sec-createbuiltinfunction
|
|
GC::Ref<NativeJavaScriptBackedFunction> NativeJavaScriptBackedFunction::create(Realm& realm, FunctionNode const& function_node, PropertyKey const& name, i32 length)
|
|
{
|
|
// 1. If realm is not present, set realm to the current Realm Record.
|
|
// 2. If prototype is not present, set prototype to realm.[[Intrinsics]].[[%Function.prototype%]].
|
|
auto prototype = realm.intrinsics().function_prototype();
|
|
|
|
// 3. Let internalSlotsList be a List containing the names of all the internal slots that 10.3 requires for the built-in function object that is about to be created.
|
|
// 4. Append to internalSlotsList the elements of additionalInternalSlotsList.
|
|
|
|
// 5. Let func be a new built-in function object that, when called, performs the action described by behaviour using the provided arguments as the values of the corresponding parameters specified by behaviour. The new function object has internal slots whose names are the elements of internalSlotsList, and an [[InitialName]] internal slot.
|
|
// 6. Set func.[[Prototype]] to prototype.
|
|
// 7. Set func.[[Extensible]] to true.
|
|
// 8. Set func.[[Realm]] to realm.
|
|
// 9. Set func.[[InitialName]] to null.
|
|
auto shared_data = realm.heap().allocate<SharedFunctionInstanceData>(realm.vm(),
|
|
function_node.kind(),
|
|
function_node.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());
|
|
|
|
auto function = realm.create<NativeJavaScriptBackedFunction>(shared_data, *prototype);
|
|
|
|
function->unsafe_set_shape(realm.intrinsics().native_function_shape());
|
|
|
|
// 10. Perform SetFunctionLength(func, length).
|
|
function->put_direct(realm.intrinsics().native_function_length_offset(), Value { length });
|
|
|
|
// 11. If prefix is not present, then
|
|
// a. Perform SetFunctionName(func, name).
|
|
// 12. Else,
|
|
// a. Perform SetFunctionName(func, name, prefix).
|
|
function->put_direct(realm.intrinsics().native_function_name_offset(), function->make_function_name(name, OptionalNone {}));
|
|
|
|
// 13. Return func.
|
|
return function;
|
|
}
|
|
|
|
NativeJavaScriptBackedFunction::NativeJavaScriptBackedFunction(GC::Ref<SharedFunctionInstanceData const> shared_function_instance_data, Object& prototype)
|
|
: NativeFunction(shared_function_instance_data->m_name, prototype)
|
|
, m_shared_function_instance_data(shared_function_instance_data)
|
|
{
|
|
}
|
|
|
|
void NativeJavaScriptBackedFunction::visit_edges(Visitor& visitor)
|
|
{
|
|
Base::visit_edges(visitor);
|
|
visitor.visit(m_shared_function_instance_data);
|
|
}
|
|
|
|
ThrowCompletionOr<void> NativeJavaScriptBackedFunction::get_stack_frame_size(size_t& registers_and_locals_count, size_t& constants_count, size_t& argument_count)
|
|
{
|
|
auto& bytecode_executable = this->bytecode_executable();
|
|
registers_and_locals_count = bytecode_executable.registers_and_locals_count;
|
|
constants_count = bytecode_executable.constants.size();
|
|
argument_count = max(argument_count, m_shared_function_instance_data->m_function_length);
|
|
return {};
|
|
}
|
|
|
|
ThrowCompletionOr<Value> NativeJavaScriptBackedFunction::call()
|
|
{
|
|
auto& vm = this->vm();
|
|
|
|
auto result = TRY(vm.bytecode_interpreter().run_executable(vm.running_execution_context(), bytecode_executable(), {}));
|
|
|
|
auto kind = this->kind();
|
|
if (kind == FunctionKind::Normal)
|
|
return result;
|
|
|
|
auto& realm = *vm.current_realm();
|
|
if (kind == FunctionKind::AsyncGenerator)
|
|
return AsyncGenerator::create(realm, result, GC::Ref { *this }, vm.running_execution_context().copy());
|
|
|
|
auto generator_object = GeneratorObject::create(realm, result, GC::Ref { *this }, vm.running_execution_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(realm, generator_object);
|
|
|
|
VERIFY(kind == FunctionKind::Generator);
|
|
return generator_object;
|
|
}
|
|
|
|
Bytecode::Executable& NativeJavaScriptBackedFunction::bytecode_executable()
|
|
{
|
|
auto& executable = m_shared_function_instance_data->m_executable;
|
|
if (!executable) {
|
|
executable = MUST(Bytecode::compile(vm(), m_shared_function_instance_data, Bytecode::BuiltinAbstractOperationsEnabled::Yes));
|
|
}
|
|
|
|
return *executable;
|
|
}
|
|
|
|
FunctionKind NativeJavaScriptBackedFunction::kind() const
|
|
{
|
|
return m_shared_function_instance_data->m_kind;
|
|
}
|
|
|
|
ThisMode NativeJavaScriptBackedFunction::this_mode() const
|
|
{
|
|
return m_shared_function_instance_data->m_this_mode;
|
|
}
|
|
|
|
bool NativeJavaScriptBackedFunction::function_environment_needed() const
|
|
{
|
|
return m_shared_function_instance_data->m_function_environment_needed;
|
|
}
|
|
|
|
size_t NativeJavaScriptBackedFunction::function_environment_bindings_count() const
|
|
{
|
|
return m_shared_function_instance_data->m_function_environment_bindings_count;
|
|
}
|
|
|
|
bool NativeJavaScriptBackedFunction::is_strict_mode() const
|
|
{
|
|
return m_shared_function_instance_data->m_strict;
|
|
}
|
|
|
|
}
|