Skip to content

Commit a63b0cf

Browse files
Lubrsiawesomekling
authored andcommitted
LibJS: Introduce NativeJavaScriptBackedFunction
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.
1 parent 899c6eb commit a63b0cf

21 files changed

+412
-52
lines changed

Libraries/LibJS/Bytecode/ASTCodegen.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -474,8 +474,10 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> Identifier::generate_by
474474
return local;
475475
}
476476

477-
if (is_global() && m_string == "undefined"sv) {
478-
return generator.add_constant(js_undefined());
477+
if (is_global()) {
478+
auto maybe_constant = TRY(generator.maybe_generate_builtin_constant(*this));
479+
if (maybe_constant.has_value())
480+
return maybe_constant.release_value();
479481
}
480482

481483
auto dst = choose_dst(generator, preferred_dst);
@@ -1718,6 +1720,7 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> CallExpression::generat
17181720

17191721
Optional<ScopedOperand> original_callee;
17201722
auto original_this_value = generator.add_constant(js_undefined());
1723+
auto dst = choose_dst(generator, preferred_dst);
17211724
Bytecode::Op::CallType call_type = Bytecode::Op::CallType::Call;
17221725

17231726
if (is<NewExpression>(this)) {
@@ -1741,6 +1744,11 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> CallExpression::generat
17411744
// NOTE: If the identifier refers to a known "local" or "global", we know it can't be
17421745
// a `with` binding, so we can skip this.
17431746
auto& identifier = static_cast<Identifier const&>(*m_callee);
1747+
if (generator.builtin_abstract_operations_enabled() && identifier.is_global()) {
1748+
TRY(generator.generate_builtin_abstract_operation(identifier, arguments(), dst));
1749+
return dst;
1750+
}
1751+
17441752
if (identifier.string() == "eval"sv) {
17451753
call_type = Bytecode::Op::CallType::DirectEval;
17461754
}
@@ -1776,7 +1784,6 @@ Bytecode::CodeGenerationErrorOr<Optional<ScopedOperand>> CallExpression::generat
17761784
expression_string_index = generator.intern_string(expression_string.release_value());
17771785

17781786
bool has_spread = any_of(arguments(), [](auto& argument) { return argument.is_spread; });
1779-
auto dst = choose_dst(generator, preferred_dst);
17801787

17811788
if (has_spread) {
17821789
auto arguments = TRY(arguments_to_array_for_call(generator, this->arguments())).value();
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/*
2+
* Copyright (c) 2025, Luke Wilde <luke@ladybird.org>
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#pragma once
8+
9+
namespace JS::Bytecode {
10+
11+
enum class BuiltinAbstractOperationsEnabled : bool {
12+
No,
13+
Yes,
14+
};
15+
16+
}

Libraries/LibJS/Bytecode/Generator.cpp

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@
1313
#include <LibJS/Bytecode/Op.h>
1414
#include <LibJS/Bytecode/Register.h>
1515
#include <LibJS/Runtime/ECMAScriptFunctionObject.h>
16+
#include <LibJS/Runtime/NativeJavaScriptBackedFunction.h>
1617
#include <LibJS/Runtime/VM.h>
1718

1819
namespace JS::Bytecode {
1920

20-
Generator::Generator(VM& vm, GC::Ptr<SharedFunctionInstanceData const> shared_function_instance_data, MustPropagateCompletion must_propagate_completion)
21+
Generator::Generator(VM& vm, GC::Ptr<SharedFunctionInstanceData const> shared_function_instance_data, MustPropagateCompletion must_propagate_completion, BuiltinAbstractOperationsEnabled builtin_abstract_operations_enabled)
2122
: m_vm(vm)
2223
, m_string_table(make<StringTable>())
2324
, m_identifier_table(make<IdentifierTable>())
@@ -26,6 +27,7 @@ Generator::Generator(VM& vm, GC::Ptr<SharedFunctionInstanceData const> shared_fu
2627
, m_accumulator(*this, Operand(Register::accumulator()))
2728
, m_this_value(*this, Operand(Register::this_value()))
2829
, m_must_propagate_completion(must_propagate_completion == MustPropagateCompletion::Yes)
30+
, m_builtin_abstract_operations_enabled(builtin_abstract_operations_enabled == BuiltinAbstractOperationsEnabled::Yes)
2931
, m_shared_function_instance_data(shared_function_instance_data)
3032
{
3133
}
@@ -215,9 +217,9 @@ CodeGenerationErrorOr<void> Generator::emit_function_declaration_instantiation(S
215217
return {};
216218
}
217219

218-
CodeGenerationErrorOr<GC::Ref<Executable>> Generator::compile(VM& vm, ASTNode const& node, FunctionKind enclosing_function_kind, GC::Ptr<SharedFunctionInstanceData const> shared_function_instance_data, MustPropagateCompletion must_propagate_completion, Vector<LocalVariable> local_variable_names)
220+
CodeGenerationErrorOr<GC::Ref<Executable>> Generator::compile(VM& vm, ASTNode const& node, FunctionKind enclosing_function_kind, GC::Ptr<SharedFunctionInstanceData const> shared_function_instance_data, MustPropagateCompletion must_propagate_completion, BuiltinAbstractOperationsEnabled builtin_abstract_operations_enabled, Vector<LocalVariable> local_variable_names)
219221
{
220-
Generator generator(vm, shared_function_instance_data, must_propagate_completion);
222+
Generator generator(vm, shared_function_instance_data, must_propagate_completion, builtin_abstract_operations_enabled);
221223

222224
if (is<Program>(node))
223225
generator.m_strict = static_cast<Program const&>(node).is_strict_mode() ? Strict::Yes : Strict::No;
@@ -498,12 +500,12 @@ CodeGenerationErrorOr<GC::Ref<Executable>> Generator::generate_from_ast_node(VM&
498500
Vector<LocalVariable> local_variable_names;
499501
if (is<ScopeNode>(node))
500502
local_variable_names = static_cast<ScopeNode const&>(node).local_variables_names();
501-
return compile(vm, node, enclosing_function_kind, {}, MustPropagateCompletion::Yes, move(local_variable_names));
503+
return compile(vm, node, enclosing_function_kind, {}, MustPropagateCompletion::Yes, BuiltinAbstractOperationsEnabled::No, move(local_variable_names));
502504
}
503505

504-
CodeGenerationErrorOr<GC::Ref<Executable>> Generator::generate_from_function(VM& vm, GC::Ref<SharedFunctionInstanceData const> shared_function_instance_data)
506+
CodeGenerationErrorOr<GC::Ref<Executable>> Generator::generate_from_function(VM& vm, GC::Ref<SharedFunctionInstanceData const> shared_function_instance_data, BuiltinAbstractOperationsEnabled builtin_abstract_operations_enabled)
505507
{
506-
return compile(vm, *shared_function_instance_data->m_ecmascript_code, shared_function_instance_data->m_kind, shared_function_instance_data, MustPropagateCompletion::No, shared_function_instance_data->m_local_variables_names);
508+
return compile(vm, *shared_function_instance_data->m_ecmascript_code, shared_function_instance_data->m_kind, shared_function_instance_data, MustPropagateCompletion::No, builtin_abstract_operations_enabled, shared_function_instance_data->m_local_variables_names);
507509
}
508510

509511
void Generator::grow(size_t additional_size)
@@ -1470,4 +1472,43 @@ ScopedOperand Generator::add_constant(Value value)
14701472
return append_new_constant();
14711473
}
14721474

1475+
CodeGenerationErrorOr<void> Generator::generate_builtin_abstract_operation(Identifier const& builtin_identifier, ReadonlySpan<CallExpression::Argument> arguments, ScopedOperand const&)
1476+
{
1477+
VERIFY(m_builtin_abstract_operations_enabled);
1478+
for (auto const& argument : arguments) {
1479+
if (argument.is_spread) {
1480+
return CodeGenerationError {
1481+
argument.value.ptr(),
1482+
"Spread arguments not allowed for builtin abstract operations"sv,
1483+
};
1484+
}
1485+
}
1486+
1487+
auto const& operation_name = builtin_identifier.string();
1488+
1489+
dbgln("Unknown builtin abstract operation: '{}'", operation_name);
1490+
return CodeGenerationError {
1491+
&builtin_identifier,
1492+
"Unknown builtin abstract operation"sv,
1493+
};
1494+
}
1495+
1496+
CodeGenerationErrorOr<Optional<ScopedOperand>> Generator::maybe_generate_builtin_constant(Identifier const& builtin_identifier)
1497+
{
1498+
auto const& constant_name = builtin_identifier.string();
1499+
1500+
if (constant_name == "undefined"sv) {
1501+
return add_constant(js_undefined());
1502+
}
1503+
1504+
if (!m_builtin_abstract_operations_enabled)
1505+
return OptionalNone {};
1506+
1507+
dbgln("Unknown builtin constant: '{}'", constant_name);
1508+
return CodeGenerationError {
1509+
&builtin_identifier,
1510+
"Unknown builtin constant"sv,
1511+
};
1512+
}
1513+
14731514
}

Libraries/LibJS/Bytecode/Generator.h

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <AK/SinglyLinkedList.h>
1111
#include <LibJS/AST.h>
1212
#include <LibJS/Bytecode/BasicBlock.h>
13+
#include <LibJS/Bytecode/BuiltinAbstractOperationsEnabled.h>
1314
#include <LibJS/Bytecode/CodeGenerationError.h>
1415
#include <LibJS/Bytecode/Executable.h>
1516
#include <LibJS/Bytecode/IdentifierTable.h>
@@ -40,7 +41,7 @@ class Generator {
4041
};
4142

4243
static CodeGenerationErrorOr<GC::Ref<Executable>> generate_from_ast_node(VM&, ASTNode const&, FunctionKind = FunctionKind::Normal);
43-
static CodeGenerationErrorOr<GC::Ref<Executable>> generate_from_function(VM&, GC::Ref<SharedFunctionInstanceData const> shared_function_instance_data);
44+
static CodeGenerationErrorOr<GC::Ref<Executable>> generate_from_function(VM&, GC::Ref<SharedFunctionInstanceData const> shared_function_instance_data, BuiltinAbstractOperationsEnabled builtin_abstract_operations_enabled = BuiltinAbstractOperationsEnabled::No);
4445

4546
CodeGenerationErrorOr<void> emit_function_declaration_instantiation(SharedFunctionInstanceData const& shared_function_instance_data);
4647

@@ -361,10 +362,15 @@ class Generator {
361362

362363
[[nodiscard]] bool must_propagate_completion() const { return m_must_propagate_completion; }
363364

365+
[[nodiscard]] bool builtin_abstract_operations_enabled() const { return m_builtin_abstract_operations_enabled; }
366+
367+
CodeGenerationErrorOr<void> generate_builtin_abstract_operation(Identifier const& builtin_identifier, ReadonlySpan<CallExpression::Argument> arguments, ScopedOperand const& dst);
368+
CodeGenerationErrorOr<Optional<ScopedOperand>> maybe_generate_builtin_constant(Identifier const& builtin_identifier);
369+
364370
private:
365371
VM& m_vm;
366372

367-
static CodeGenerationErrorOr<GC::Ref<Executable>> compile(VM&, ASTNode const&, FunctionKind, GC::Ptr<SharedFunctionInstanceData const>, MustPropagateCompletion, Vector<LocalVariable> local_variable_names);
373+
static CodeGenerationErrorOr<GC::Ref<Executable>> compile(VM&, ASTNode const&, FunctionKind, GC::Ptr<SharedFunctionInstanceData const>, MustPropagateCompletion, BuiltinAbstractOperationsEnabled, Vector<LocalVariable> local_variable_names);
368374

369375
enum class JumpType {
370376
Continue,
@@ -373,7 +379,7 @@ class Generator {
373379
void generate_scoped_jump(JumpType);
374380
void generate_labelled_jump(JumpType, FlyString const& label);
375381

376-
Generator(VM&, GC::Ptr<SharedFunctionInstanceData const>, MustPropagateCompletion);
382+
Generator(VM&, GC::Ptr<SharedFunctionInstanceData const>, MustPropagateCompletion, BuiltinAbstractOperationsEnabled);
377383
~Generator() = default;
378384

379385
void grow(size_t);
@@ -426,6 +432,7 @@ class Generator {
426432

427433
bool m_finished { false };
428434
bool m_must_propagate_completion { true };
435+
bool m_builtin_abstract_operations_enabled { false };
429436

430437
GC::Ptr<SharedFunctionInstanceData const> m_shared_function_instance_data;
431438

Libraries/LibJS/Bytecode/Interpreter.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -739,11 +739,11 @@ ThrowCompletionOr<GC::Ref<Bytecode::Executable>> compile(VM& vm, ASTNode const&
739739
return bytecode_executable;
740740
}
741741

742-
ThrowCompletionOr<GC::Ref<Bytecode::Executable>> compile(VM& vm, GC::Ref<SharedFunctionInstanceData const> shared_function_instance_data)
742+
ThrowCompletionOr<GC::Ref<Bytecode::Executable>> compile(VM& vm, GC::Ref<SharedFunctionInstanceData const> shared_function_instance_data, BuiltinAbstractOperationsEnabled builtin_abstract_operations_enabled)
743743
{
744744
auto const& name = shared_function_instance_data->m_name;
745745

746-
auto executable_result = Bytecode::Generator::generate_from_function(vm, shared_function_instance_data);
746+
auto executable_result = Bytecode::Generator::generate_from_function(vm, shared_function_instance_data, builtin_abstract_operations_enabled);
747747
if (executable_result.is_error())
748748
return vm.throw_completion<InternalError>(ErrorType::NotImplemented, TRY_OR_THROW_OOM(vm, executable_result.error().to_string()));
749749

@@ -1290,7 +1290,7 @@ inline Value new_function(Interpreter& interpreter, FunctionNode const& function
12901290

12911291
if (home_object.has_value()) {
12921292
auto home_object_value = interpreter.get(home_object.value());
1293-
static_cast<ECMAScriptFunctionObject&>(value.as_function()).set_home_object(&home_object_value.as_object());
1293+
as<ECMAScriptFunctionObject>(value.as_function()).set_home_object(&home_object_value.as_object());
12941294
}
12951295

12961296
return value;
@@ -2896,7 +2896,7 @@ ThrowCompletionOr<void> SuperCallWithArgumentArray::execute_impl(Bytecode::Inter
28962896
TRY(this_environment.bind_this_value(vm, result));
28972897

28982898
// 9. Let F be thisER.[[FunctionObject]].
2899-
auto& f = this_environment.function_object();
2899+
auto& f = as<ECMAScriptFunctionObject>(this_environment.function_object());
29002900

29012901
// 10. Assert: F is an ECMAScript function object.
29022902
// NOTE: This is implied by the strong C++ type.

Libraries/LibJS/Bytecode/Interpreter.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
#pragma once
88

9+
#include <LibJS/Bytecode/BuiltinAbstractOperationsEnabled.h>
910
#include <LibJS/Bytecode/Executable.h>
1011
#include <LibJS/Bytecode/Label.h>
1112
#include <LibJS/Bytecode/Register.h>
@@ -102,6 +103,6 @@ class JS_API Interpreter {
102103
JS_API extern bool g_dump_bytecode;
103104

104105
ThrowCompletionOr<GC::Ref<Bytecode::Executable>> compile(VM&, ASTNode const&, JS::FunctionKind kind, Utf16FlyString const& name);
105-
ThrowCompletionOr<GC::Ref<Bytecode::Executable>> compile(VM&, GC::Ref<SharedFunctionInstanceData const>);
106+
ThrowCompletionOr<GC::Ref<Bytecode::Executable>> compile(VM&, GC::Ref<SharedFunctionInstanceData const>, BuiltinAbstractOperationsEnabled builtin_abstract_operations_enabled);
106107

107108
}

Libraries/LibJS/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ set(SOURCES
158158
Runtime/ModuleEnvironment.cpp
159159
Runtime/ModuleNamespaceObject.cpp
160160
Runtime/NativeFunction.cpp
161+
Runtime/NativeJavaScriptBackedFunction.cpp
161162
Runtime/NumberConstructor.cpp
162163
Runtime/NumberObject.cpp
163164
Runtime/NumberPrototype.cpp

Libraries/LibJS/Forward.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ class ModuleEnvironment;
201201
class Module;
202202
struct ModuleRequest;
203203
class NativeFunction;
204+
class NativeJavaScriptBackedFunction;
204205
class ObjectEnvironment;
205206
class Parser;
206207
struct ParserError;

Libraries/LibJS/Runtime/AbstractOperations.cpp

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <LibJS/Runtime/FunctionObject.h>
2626
#include <LibJS/Runtime/GlobalEnvironment.h>
2727
#include <LibJS/Runtime/GlobalObject.h>
28+
#include <LibJS/Runtime/NativeJavaScriptBackedFunction.h>
2829
#include <LibJS/Runtime/Object.h>
2930
#include <LibJS/Runtime/ObjectEnvironment.h>
3031
#include <LibJS/Runtime/PromiseCapability.h>
@@ -458,6 +459,36 @@ GC::Ref<FunctionEnvironment> new_function_environment(ECMAScriptFunctionObject&
458459
return env;
459460
}
460461

462+
// 9.1.2.4 NewFunctionEnvironment ( F, newTarget ), https://tc39.es/ecma262/#sec-newfunctionenvironment
463+
// 4.1.2.2 NewFunctionEnvironment ( F, newTarget ), https://tc39.es/proposal-explicit-resource-management/#sec-newfunctionenvironment
464+
GC::Ref<FunctionEnvironment> new_function_environment(NativeJavaScriptBackedFunction& function, Object* new_target)
465+
{
466+
auto& heap = function.heap();
467+
468+
// 1. Let env be a new function Environment Record containing no bindings.
469+
auto env = heap.allocate<FunctionEnvironment>(nullptr);
470+
471+
// 2. Set env.[[FunctionObject]] to F.
472+
env->set_function_object(function);
473+
474+
// 3. If F.[[ThisMode]] is lexical, set env.[[ThisBindingStatus]] to lexical.
475+
if (function.this_mode() == ThisMode::Lexical)
476+
env->set_this_binding_status(FunctionEnvironment::ThisBindingStatus::Lexical);
477+
// 4. Else, set env.[[ThisBindingStatus]] to uninitialized.
478+
else
479+
env->set_this_binding_status(FunctionEnvironment::ThisBindingStatus::Uninitialized);
480+
481+
// 5. Set env.[[NewTarget]] to newTarget.
482+
env->set_new_target(new_target ?: js_undefined());
483+
484+
// 6. Set env.[[OuterEnv]] to F.[[Environment]].
485+
// 7. Set env.[[DisposeCapability]] to NewDisposeCapability().
486+
// NOTE: Done in step 1 via the FunctionEnvironment constructor.
487+
488+
// 8. Return env.
489+
return env;
490+
}
491+
461492
// 9.2.1.1 NewPrivateEnvironment ( outerPrivEnv ), https://tc39.es/ecma262/#sec-newprivateenvironment
462493
GC::Ref<PrivateEnvironment> new_private_environment(VM& vm, PrivateEnvironment* outer)
463494
{
@@ -586,7 +617,7 @@ ThrowCompletionOr<Value> perform_eval(VM& vm, Value x, CallerMode strict_caller,
586617
auto& this_function_environment_record = static_cast<FunctionEnvironment&>(*this_environment_record);
587618

588619
// i. Let F be thisEnvRec.[[FunctionObject]].
589-
auto& function = this_function_environment_record.function_object();
620+
auto& function = as<ECMAScriptFunctionObject>(this_function_environment_record.function_object());
590621

591622
// ii. Set inFunction to true.
592623
in_function = true;

Libraries/LibJS/Runtime/AbstractOperations.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ namespace JS {
2828
GC::Ref<DeclarativeEnvironment> new_declarative_environment(Environment&);
2929
JS_API GC::Ref<ObjectEnvironment> new_object_environment(Object&, bool is_with_environment, Environment*);
3030
GC::Ref<FunctionEnvironment> new_function_environment(ECMAScriptFunctionObject&, Object* new_target);
31+
GC::Ref<FunctionEnvironment> new_function_environment(NativeJavaScriptBackedFunction&, Object* new_target);
3132
GC::Ref<PrivateEnvironment> new_private_environment(VM& vm, PrivateEnvironment* outer);
3233
GC::Ref<Environment> get_this_environment(VM&);
3334
JS_API bool can_be_held_weakly(Value);

0 commit comments

Comments
 (0)