diff --git a/Meta/gn/secondary/Userland/Libraries/LibJS/BUILD.gn b/Meta/gn/secondary/Userland/Libraries/LibJS/BUILD.gn index f77e404f55..d8bf09c3d2 100644 --- a/Meta/gn/secondary/Userland/Libraries/LibJS/BUILD.gn +++ b/Meta/gn/secondary/Userland/Libraries/LibJS/BUILD.gn @@ -25,6 +25,7 @@ shared_library("LibJS") { "AST.cpp", "Bytecode/ASTCodegen.cpp", "Bytecode/BasicBlock.cpp", + "Bytecode/Builtins.cpp", "Bytecode/CodeGenerationError.cpp", "Bytecode/CommonImplementations.cpp", "Bytecode/Executable.cpp", diff --git a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp index 03d06e9bfb..40ece11b4a 100644 --- a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp +++ b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp @@ -1521,6 +1521,8 @@ Bytecode::CodeGenerationErrorOr CallExpression::generate_bytecode(Bytecode generator.emit(js_undefined()); generator.emit(this_reg); + Optional builtin; + if (is(this)) { TRY(m_callee->generate_bytecode(generator)); generator.emit(callee_reg); @@ -1528,6 +1530,7 @@ Bytecode::CodeGenerationErrorOr CallExpression::generate_bytecode(Bytecode auto& member_expression = static_cast(*m_callee); TRY(get_base_and_value_from_member_expression(generator, member_expression, this_reg)); generator.emit(callee_reg); + builtin = Bytecode::get_builtin(member_expression); } else if (is(*m_callee)) { auto& optional_chain = static_cast(*m_callee); TRY(generate_optional_chain(generator, optional_chain, callee_reg, this_reg)); @@ -1581,7 +1584,7 @@ Bytecode::CodeGenerationErrorOr CallExpression::generate_bytecode(Bytecode generator.emit(Bytecode::Register { first_argument_reg.value().index() + register_offset }); register_offset += 1; } - generator.emit(call_type, callee_reg, this_reg, first_argument_reg.value_or(Bytecode::Register { 0 }), arguments().size(), expression_string_index); + generator.emit(call_type, callee_reg, this_reg, first_argument_reg.value_or(Bytecode::Register { 0 }), arguments().size(), expression_string_index, builtin); } return {}; diff --git a/Userland/Libraries/LibJS/Bytecode/Builtins.cpp b/Userland/Libraries/LibJS/Bytecode/Builtins.cpp new file mode 100644 index 0000000000..900e1a4b1c --- /dev/null +++ b/Userland/Libraries/LibJS/Bytecode/Builtins.cpp @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023, Simon Wanner + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include + +namespace JS::Bytecode { + +Optional get_builtin(MemberExpression const& expression) +{ + if (expression.is_computed() || !expression.object().is_identifier() || !expression.property().is_identifier()) + return {}; + auto base_name = static_cast(expression.object()).string(); + auto property_name = static_cast(expression.property()).string(); +#define CHECK_MEMBER_BUILTIN(name, snake_case_name, base, property, ...) \ + if (base_name == #base##sv && property_name == #property##sv) \ + return Builtin::name; + JS_ENUMERATE_BUILTINS(CHECK_MEMBER_BUILTIN) +#undef CHECK_MEMBER_BUILTIN + return {}; +} + +} diff --git a/Userland/Libraries/LibJS/Bytecode/Builtins.h b/Userland/Libraries/LibJS/Bytecode/Builtins.h new file mode 100644 index 0000000000..200164c3af --- /dev/null +++ b/Userland/Libraries/LibJS/Bytecode/Builtins.h @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2023, Simon Wanner + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include + +namespace JS::Bytecode { + +// TitleCaseName, snake_case_name, base, property, argument_count +#define JS_ENUMERATE_BUILTINS(O) + +enum class Builtin { +#define DEFINE_BUILTIN_ENUM(name, ...) name, + JS_ENUMERATE_BUILTINS(DEFINE_BUILTIN_ENUM) +#undef DEFINE_BUILTIN_ENUM + __Count, +}; + +static StringView builtin_name(Builtin value) +{ + switch (value) { +#define DEFINE_BUILTIN_CASE(name, snake_case_name, base, property, ...) \ + case Builtin::name: \ + return #base "." #property##sv; + JS_ENUMERATE_BUILTINS(DEFINE_BUILTIN_CASE) +#undef DEFINE_BUILTIN_CASE + case Builtin::__Count: + VERIFY_NOT_REACHED(); + } + VERIFY_NOT_REACHED(); +} + +inline size_t builtin_argument_count(Builtin value) +{ + switch (value) { +#define DEFINE_BUILTIN_CASE(name, snake_case_name, base, property, arg_count, ...) \ + case Builtin::name: \ + return arg_count; + JS_ENUMERATE_BUILTINS(DEFINE_BUILTIN_CASE) +#undef DEFINE_BUILTIN_CASE + case Builtin::__Count: + VERIFY_NOT_REACHED(); + } + VERIFY_NOT_REACHED(); +} + +Optional get_builtin(MemberExpression const& expression); + +} + +namespace AK { + +template<> +struct Formatter : Formatter { + ErrorOr format(FormatBuilder& builder, JS::Bytecode::Builtin value) + { + return Formatter::format(builder, builtin_name(value)); + } +}; + +} diff --git a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp index 7299e4b936..d82005d954 100644 --- a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp @@ -1514,6 +1514,8 @@ static StringView call_type_to_string(CallType type) DeprecatedString Call::to_deprecated_string_impl(Bytecode::Executable const& executable) const { auto type = call_type_to_string(m_type); + if (m_builtin.has_value()) + return DeprecatedString::formatted("Call{} callee:{}, this:{}, first_arg:{} (builtin {})", type, m_callee, m_this_value, m_first_argument, m_builtin.value()); if (m_expression_string.has_value()) return DeprecatedString::formatted("Call{} callee:{}, this:{}, first_arg:{} ({})", type, m_callee, m_this_value, m_first_argument, executable.get_string(m_expression_string.value())); return DeprecatedString::formatted("Call{} callee:{}, this:{}, first_arg:{}", type, m_callee, m_first_argument, m_this_value); diff --git a/Userland/Libraries/LibJS/Bytecode/Op.h b/Userland/Libraries/LibJS/Bytecode/Op.h index 1f3525c1e6..f20e930aec 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.h +++ b/Userland/Libraries/LibJS/Bytecode/Op.h @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -984,7 +985,7 @@ enum class CallType { class Call final : public Instruction { public: - Call(CallType type, Register callee, Register this_value, Register first_argument, u32 argument_count, Optional expression_string = {}) + Call(CallType type, Register callee, Register this_value, Register first_argument, u32 argument_count, Optional expression_string = {}, Optional builtin = {}) : Instruction(Type::Call, sizeof(*this)) , m_callee(callee) , m_this_value(this_value) @@ -992,6 +993,7 @@ public: , m_argument_count(argument_count) , m_type(type) , m_expression_string(expression_string) + , m_builtin(builtin) { } @@ -1003,6 +1005,8 @@ public: Register first_argument() const { return m_first_argument; } u32 argument_count() const { return m_argument_count; } + Optional const& builtin() const { return m_builtin; } + ThrowCompletionOr execute_impl(Bytecode::Interpreter&) const; DeprecatedString to_deprecated_string_impl(Bytecode::Executable const&) const; @@ -1013,6 +1017,7 @@ private: u32 m_argument_count { 0 }; CallType m_type; Optional m_expression_string; + Optional m_builtin; }; class CallWithArgumentArray final : public Instruction { diff --git a/Userland/Libraries/LibJS/CMakeLists.txt b/Userland/Libraries/LibJS/CMakeLists.txt index 81a1ff9dbc..ba2ceb0692 100644 --- a/Userland/Libraries/LibJS/CMakeLists.txt +++ b/Userland/Libraries/LibJS/CMakeLists.txt @@ -2,6 +2,7 @@ set(SOURCES AST.cpp Bytecode/ASTCodegen.cpp Bytecode/BasicBlock.cpp + Bytecode/Builtins.cpp Bytecode/CodeGenerationError.cpp Bytecode/CommonImplementations.cpp Bytecode/Executable.cpp diff --git a/Userland/Libraries/LibJS/Forward.h b/Userland/Libraries/LibJS/Forward.h index 64f5bc6991..f733a32257 100644 --- a/Userland/Libraries/LibJS/Forward.h +++ b/Userland/Libraries/LibJS/Forward.h @@ -303,6 +303,7 @@ class MarkedVector; namespace Bytecode { class BasicBlock; +enum class Builtin; class Executable; class Generator; class Instruction; diff --git a/Userland/Libraries/LibJS/JIT/Compiler.cpp b/Userland/Libraries/LibJS/JIT/Compiler.cpp index e1c3ae7d26..891b2a11fd 100644 --- a/Userland/Libraries/LibJS/JIT/Compiler.cpp +++ b/Userland/Libraries/LibJS/JIT/Compiler.cpp @@ -2518,9 +2518,56 @@ static Value cxx_call(VM& vm, Value callee, u32 first_argument_index, u32 argume return TRY_OR_SET_EXCEPTION(perform_call(vm.bytecode_interpreter(), this_value, call_type, callee, move(argument_values))); } +Assembler::Reg Compiler::argument_register(u32 index) +{ + switch (index) { + case 0: + return ARG0; + case 1: + return ARG1; + case 2: + return ARG2; + case 3: + return ARG3; + case 4: + return ARG4; + case 5: + return ARG5; + } + VERIFY_NOT_REACHED(); +} + void Compiler::compile_call(Bytecode::Op::Call const& op) { + Assembler::Label slow_case {}; + Assembler::Label end {}; load_vm_register(ARG1, op.callee()); + if (op.call_type() == Bytecode::Op::CallType::Call && op.builtin().has_value() && op.argument_count() == Bytecode::builtin_argument_count(op.builtin().value())) { + auto builtin = op.builtin().value(); + + // GPR0 = vm.running_execution_context().realm; + m_assembler.mov( + Assembler::Operand::Register(GPR0), + Assembler::Operand::Mem64BaseAndOffset(RUNNING_EXECUTION_CONTEXT_BASE, ExecutionContext::realm_offset())); + + // GPR0 = GPR0->m_builtins[to_underlying(builtin)] + m_assembler.mov( + Assembler::Operand::Register(GPR0), + Assembler::Operand::Mem64BaseAndOffset(GPR0, Realm::builtins_offset() + sizeof(Value) * to_underlying(builtin))); + + // if (callee != GPR0) goto slow_case; + m_assembler.jump_if( + Assembler::Operand::Register(ARG1), + Assembler::Condition::NotEqualTo, + Assembler::Operand::Register(GPR0), + slow_case); + + // Load arguments into ARG2, ARG3, ... + for (u32 arg = 0; arg < op.argument_count(); arg++) + load_vm_register(argument_register(arg + 2), Bytecode::Register { op.first_argument().index() + arg }); + compile_builtin(builtin, slow_case, end); + } + slow_case.link(m_assembler); m_assembler.mov( Assembler::Operand::Register(ARG2), Assembler::Operand::Imm(op.first_argument().index())); @@ -2537,6 +2584,21 @@ void Compiler::compile_call(Bytecode::Op::Call const& op) native_call((void*)cxx_call, { Assembler::Operand::Register(GPR0) }); store_accumulator(RET); check_exception(); + end.link(m_assembler); +} + +void Compiler::compile_builtin(Bytecode::Builtin builtin, [[maybe_unused]] Assembler::Label& slow_case, [[maybe_unused]] Assembler::Label& end) +{ + switch (builtin) { +# define DEFINE_BUILTIN_CASE(name, snake_case_name, ...) \ + case Bytecode::Builtin::name: \ + compile_builtin_##snake_case_name(slow_case, end); \ + break; + JS_ENUMERATE_BUILTINS(DEFINE_BUILTIN_CASE) +# undef DEFINE_BUILTIN_CASE + case Bytecode::Builtin::__Count: + VERIFY_NOT_REACHED(); + } } static Value cxx_call_with_argument_array(VM& vm, Value arguments, Value callee, Value this_value, Bytecode::Op::CallType call_type, Optional const& expression_string) diff --git a/Userland/Libraries/LibJS/JIT/Compiler.h b/Userland/Libraries/LibJS/JIT/Compiler.h index 0e26c9b365..01b2ab08ca 100644 --- a/Userland/Libraries/LibJS/JIT/Compiler.h +++ b/Userland/Libraries/LibJS/JIT/Compiler.h @@ -9,6 +9,7 @@ #include #include +#include #include #include #include @@ -44,6 +45,8 @@ private: static constexpr auto RUNNING_EXECUTION_CONTEXT_BASE = Assembler::Reg::R15; # endif + static Assembler::Reg argument_register(u32); + # define JS_ENUMERATE_COMMON_BINARY_OPS_WITHOUT_FAST_PATH(O) \ O(Div, div) \ O(Exp, exp) \ @@ -147,6 +150,12 @@ private: JS_ENUMERATE_IMPLEMENTED_JIT_OPS(DECLARE_COMPILE_OP) # undef DECLARE_COMPILE_OP + void compile_builtin(Bytecode::Builtin, Assembler::Label& slow_case, Assembler::Label& end); +# define DECLARE_COMPILE_BUILTIN(name, snake_case_name, ...) \ + void compile_builtin_##snake_case_name(Assembler::Label& slow_case, Assembler::Label& end); + JS_ENUMERATE_BUILTINS(DECLARE_COMPILE_BUILTIN) +# undef DECLARE_COMPILE_BUILTIN + void store_vm_register(Bytecode::Register, Assembler::Reg); void load_vm_register(Assembler::Reg, Bytecode::Register); diff --git a/Userland/Libraries/LibJS/Runtime/Object.cpp b/Userland/Libraries/LibJS/Runtime/Object.cpp index 52ac334bdd..e8edf0c770 100644 --- a/Userland/Libraries/LibJS/Runtime/Object.cpp +++ b/Userland/Libraries/LibJS/Runtime/Object.cpp @@ -1269,10 +1269,12 @@ Value Object::get_without_side_effects(PropertyKey const& property_key) const return {}; } -void Object::define_native_function(Realm& realm, PropertyKey const& property_key, Function(VM&)> native_function, i32 length, PropertyAttributes attribute) +void Object::define_native_function(Realm& realm, PropertyKey const& property_key, Function(VM&)> native_function, i32 length, PropertyAttributes attribute, Optional builtin) { auto function = NativeFunction::create(realm, move(native_function), length, property_key, &realm); define_direct_property(property_key, function, attribute); + if (builtin.has_value()) + realm.define_builtin(builtin.value(), function); } // 20.1.2.3.1 ObjectDefineProperties ( O, Properties ), https://tc39.es/ecma262/#sec-objectdefineproperties diff --git a/Userland/Libraries/LibJS/Runtime/Object.h b/Userland/Libraries/LibJS/Runtime/Object.h index 552107b710..ee31cda5e4 100644 --- a/Userland/Libraries/LibJS/Runtime/Object.h +++ b/Userland/Libraries/LibJS/Runtime/Object.h @@ -177,7 +177,7 @@ public: using IntrinsicAccessor = Value (*)(Realm&); void define_intrinsic_accessor(PropertyKey const&, PropertyAttributes attributes, IntrinsicAccessor accessor); - void define_native_function(Realm&, PropertyKey const&, Function(VM&)>, i32 length, PropertyAttributes attributes); + void define_native_function(Realm&, PropertyKey const&, Function(VM&)>, i32 length, PropertyAttributes attributes, Optional builtin = {}); void define_native_accessor(Realm&, PropertyKey const&, Function(VM&)> getter, Function(VM&)> setter, PropertyAttributes attributes); virtual bool is_dom_node() const { return false; } diff --git a/Userland/Libraries/LibJS/Runtime/Realm.h b/Userland/Libraries/LibJS/Runtime/Realm.h index bd204ea133..7d1d746cec 100644 --- a/Userland/Libraries/LibJS/Runtime/Realm.h +++ b/Userland/Libraries/LibJS/Runtime/Realm.h @@ -11,8 +11,10 @@ #include #include #include +#include #include #include +#include namespace JS { @@ -48,7 +50,13 @@ public: HostDefined* host_defined() { return m_host_defined; } void set_host_defined(OwnPtr host_defined) { m_host_defined = move(host_defined); } + void define_builtin(Bytecode::Builtin builtin, Value value) + { + m_builtins[to_underlying(builtin)] = value; + } + static FlatPtr global_environment_offset() { return OFFSET_OF(Realm, m_global_environment); } + static FlatPtr builtins_offset() { return OFFSET_OF(Realm, m_builtins); } private: Realm() = default; @@ -59,6 +67,7 @@ private: GCPtr m_global_object; // [[GlobalObject]] GCPtr m_global_environment; // [[GlobalEnv]] OwnPtr m_host_defined; // [[HostDefined]] + AK::Array m_builtins; }; }