ladybird/Libraries/LibJS/Runtime/NativeJavaScriptBackedFunction.cpp
Andreas Kling 658ba1d023 LibJS: Clear compile-only data from SharedFunctionInstanceData
After successful bytecode compilation, the m_functions_to_initialize
and m_var_names_to_initialize_binding vectors are no longer needed
as they are only consumed by emit_function_declaration_instantiation()
during code generation.

Add clear_compile_inputs() to release these vectors post-compile,
and call it from both ECMAScriptFunctionObject::get_stack_frame_size()
and NativeJavaScriptBackedFunction::bytecode_executable() after their
respective lazy compilation succeeds.

Also add a pre-compile assertion in Generator::generate_from_function()
to verify we never try to compile the same function data twice, and a
VERIFY in ECMAScriptFunctionObject::ecmascript_code() to guard against
null dereference.
2026-02-11 23:57:41 +01:00

146 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> 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));
m_shared_function_instance_data->clear_compile_inputs();
}
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;
}
}