Quellcode durchsuchen

LibJS: Introduce Builtins

Builtins are functions that can be detected during bytecode generation
and enable fast-paths in the JIT.
Simon Wanner vor 1 Jahr
Ursprung
Commit
86b85aa68b

+ 1 - 0
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",

+ 4 - 1
Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp

@@ -1521,6 +1521,8 @@ Bytecode::CodeGenerationErrorOr<void> CallExpression::generate_bytecode(Bytecode
     generator.emit<Bytecode::Op::LoadImmediate>(js_undefined());
     generator.emit<Bytecode::Op::Store>(this_reg);
 
+    Optional<Bytecode::Builtin> builtin;
+
     if (is<NewExpression>(this)) {
         TRY(m_callee->generate_bytecode(generator));
         generator.emit<Bytecode::Op::Store>(callee_reg);
@@ -1528,6 +1530,7 @@ Bytecode::CodeGenerationErrorOr<void> CallExpression::generate_bytecode(Bytecode
         auto& member_expression = static_cast<MemberExpression const&>(*m_callee);
         TRY(get_base_and_value_from_member_expression(generator, member_expression, this_reg));
         generator.emit<Bytecode::Op::Store>(callee_reg);
+        builtin = Bytecode::get_builtin(member_expression);
     } else if (is<OptionalChain>(*m_callee)) {
         auto& optional_chain = static_cast<OptionalChain const&>(*m_callee);
         TRY(generate_optional_chain(generator, optional_chain, callee_reg, this_reg));
@@ -1581,7 +1584,7 @@ Bytecode::CodeGenerationErrorOr<void> CallExpression::generate_bytecode(Bytecode
             generator.emit<Bytecode::Op::Store>(Bytecode::Register { first_argument_reg.value().index() + register_offset });
             register_offset += 1;
         }
-        generator.emit<Bytecode::Op::Call>(call_type, callee_reg, this_reg, first_argument_reg.value_or(Bytecode::Register { 0 }), arguments().size(), expression_string_index);
+        generator.emit<Bytecode::Op::Call>(call_type, callee_reg, this_reg, first_argument_reg.value_or(Bytecode::Register { 0 }), arguments().size(), expression_string_index, builtin);
     }
 
     return {};

+ 26 - 0
Userland/Libraries/LibJS/Bytecode/Builtins.cpp

@@ -0,0 +1,26 @@
+/*
+ * Copyright (c) 2023, Simon Wanner <simon@skyrising.xyz>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <LibJS/AST.h>
+#include <LibJS/Bytecode/Builtins.h>
+
+namespace JS::Bytecode {
+
+Optional<Builtin> get_builtin(MemberExpression const& expression)
+{
+    if (expression.is_computed() || !expression.object().is_identifier() || !expression.property().is_identifier())
+        return {};
+    auto base_name = static_cast<Identifier const&>(expression.object()).string();
+    auto property_name = static_cast<Identifier const&>(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 {};
+}
+
+}

+ 66 - 0
Userland/Libraries/LibJS/Bytecode/Builtins.h

@@ -0,0 +1,66 @@
+/*
+ * Copyright (c) 2023, Simon Wanner <simon@skyrising.xyz>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#pragma once
+
+#include <AK/Format.h>
+#include <LibJS/Forward.h>
+
+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<Builtin> get_builtin(MemberExpression const& expression);
+
+}
+
+namespace AK {
+
+template<>
+struct Formatter<JS::Bytecode::Builtin> : Formatter<StringView> {
+    ErrorOr<void> format(FormatBuilder& builder, JS::Bytecode::Builtin value)
+    {
+        return Formatter<StringView>::format(builder, builtin_name(value));
+    }
+};
+
+}

+ 2 - 0
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);

+ 6 - 1
Userland/Libraries/LibJS/Bytecode/Op.h

@@ -10,6 +10,7 @@
 
 #include <AK/StdLibExtras.h>
 #include <LibCrypto/BigInt/SignedBigInteger.h>
+#include <LibJS/Bytecode/Builtins.h>
 #include <LibJS/Bytecode/IdentifierTable.h>
 #include <LibJS/Bytecode/Instruction.h>
 #include <LibJS/Bytecode/Label.h>
@@ -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<StringTableIndex> expression_string = {})
+    Call(CallType type, Register callee, Register this_value, Register first_argument, u32 argument_count, Optional<StringTableIndex> expression_string = {}, Optional<Builtin> 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<Builtin> const& builtin() const { return m_builtin; }
+
     ThrowCompletionOr<void> 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<StringTableIndex> m_expression_string;
+    Optional<Builtin> m_builtin;
 };
 
 class CallWithArgumentArray final : public Instruction {

+ 1 - 0
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

+ 1 - 0
Userland/Libraries/LibJS/Forward.h

@@ -303,6 +303,7 @@ class MarkedVector;
 
 namespace Bytecode {
 class BasicBlock;
+enum class Builtin;
 class Executable;
 class Generator;
 class Instruction;

+ 62 - 0
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<Bytecode::StringTableIndex> const& expression_string)

+ 9 - 0
Userland/Libraries/LibJS/JIT/Compiler.h

@@ -9,6 +9,7 @@
 
 #include <AK/Platform.h>
 #include <LibJIT/Assembler.h>
+#include <LibJS/Bytecode/Builtins.h>
 #include <LibJS/Bytecode/Executable.h>
 #include <LibJS/Bytecode/Op.h>
 #include <LibJS/JIT/NativeExecutable.h>
@@ -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);
 

+ 3 - 1
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<ThrowCompletionOr<Value>(VM&)> native_function, i32 length, PropertyAttributes attribute)
+void Object::define_native_function(Realm& realm, PropertyKey const& property_key, Function<ThrowCompletionOr<Value>(VM&)> native_function, i32 length, PropertyAttributes attribute, Optional<Bytecode::Builtin> 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

+ 1 - 1
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<ThrowCompletionOr<Value>(VM&)>, i32 length, PropertyAttributes attributes);
+    void define_native_function(Realm&, PropertyKey const&, Function<ThrowCompletionOr<Value>(VM&)>, i32 length, PropertyAttributes attributes, Optional<Bytecode::Builtin> builtin = {});
     void define_native_accessor(Realm&, PropertyKey const&, Function<ThrowCompletionOr<Value>(VM&)> getter, Function<ThrowCompletionOr<Value>(VM&)> setter, PropertyAttributes attributes);
 
     virtual bool is_dom_node() const { return false; }

+ 9 - 0
Userland/Libraries/LibJS/Runtime/Realm.h

@@ -11,8 +11,10 @@
 #include <AK/OwnPtr.h>
 #include <AK/StringView.h>
 #include <AK/Weakable.h>
+#include <LibJS/Bytecode/Builtins.h>
 #include <LibJS/Heap/Cell.h>
 #include <LibJS/Runtime/Intrinsics.h>
+#include <LibJS/Runtime/Value.h>
 
 namespace JS {
 
@@ -48,7 +50,13 @@ public:
     HostDefined* host_defined() { return m_host_defined; }
     void set_host_defined(OwnPtr<HostDefined> 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<Object> m_global_object;                 // [[GlobalObject]]
     GCPtr<GlobalEnvironment> m_global_environment; // [[GlobalEnv]]
     OwnPtr<HostDefined> m_host_defined;            // [[HostDefined]]
+    AK::Array<Value, to_underlying(Bytecode::Builtin::__Count)> m_builtins;
 };
 
 }