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.
144 lines
5.9 KiB
C++
144 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_constants_and_locals_count, size_t& argument_count)
|
|
{
|
|
auto& bytecode_executable = this->bytecode_executable();
|
|
registers_and_constants_and_locals_count = bytecode_executable.number_of_registers + bytecode_executable.constants.size() + bytecode_executable.local_variable_names.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;
|
|
}
|
|
|
|
}
|