瀏覽代碼

LibJS/JIT: Add fast path for cached GetVariable accesses

We can now stay in machine code for environment variable read accesses
as long as they are cached and initialized.

20% speed-up on Octane/zlib.js :^)
Andreas Kling 1 年之前
父節點
當前提交
1d8ec677a3

+ 133 - 3
Userland/Libraries/LibJS/JIT/Compiler.cpp

@@ -1091,15 +1091,145 @@ static Value cxx_get_variable(VM& vm, DeprecatedFlyString const& name, Bytecode:
 
 void Compiler::compile_get_variable(Bytecode::Op::GetVariable const& op)
 {
-    m_assembler.mov(
-        Assembler::Operand::Register(ARG1),
-        Assembler::Operand::Imm(bit_cast<u64>(&m_bytecode_executable.get_identifier(op.identifier()))));
+    Assembler::Label slow_case;
+
+    // if (!cache.has_value()) goto slow_case;
     m_assembler.mov(
         Assembler::Operand::Register(ARG2),
         Assembler::Operand::Imm(bit_cast<u64>(&m_bytecode_executable.environment_variable_caches[op.cache_index()])));
+
+    // FIXME: Figure out a nicer way to load a single byte. :^)
+    m_assembler.mov(
+        Assembler::Operand::Register(GPR0),
+        Assembler::Operand::Mem64BaseAndOffset(ARG2, Bytecode::EnvironmentVariableCache::has_value_offset()));
+    m_assembler.bitwise_and(
+        Assembler::Operand::Register(GPR0),
+        Assembler::Operand::Imm(0xff));
+
+    m_assembler.jump_if(
+        Assembler::Operand::Register(GPR0),
+        Assembler::Condition::EqualTo,
+        Assembler::Operand::Imm(0),
+        slow_case);
+
+    // auto environment = vm.running_execution_context().lexical_environment;
+    // GPR1 = current lexical environment
+    m_assembler.mov(
+        Assembler::Operand::Register(GPR1),
+        Assembler::Operand::Mem64BaseAndOffset(RUNNING_EXECUTION_CONTEXT_BASE, ExecutionContext::lexical_environment_offset()));
+
+    // for (size_t i = 0; i < cache->hops; ++i)
+    //     environment = environment->outer_environment();
+
+    // GPR0 = hops
+    // FIXME: Load 32 bits directly instead of 64 and masking.
+    m_assembler.mov(
+        Assembler::Operand::Register(GPR0),
+        Assembler::Operand::Mem64BaseAndOffset(ARG2, Bytecode::EnvironmentVariableCache::value_offset() + EnvironmentCoordinate::hops_offset()));
+    m_assembler.mov(
+        Assembler::Operand::Register(GPR2),
+        Assembler::Operand::Imm(0xffffffff));
+    m_assembler.bitwise_and(
+        Assembler::Operand::Register(GPR0),
+        Assembler::Operand::Register(GPR2));
+
+    {
+        // while (GPR0--)
+        //     GPR1 = GPR1->outer_environment()
+        Assembler::Label loop_start;
+        Assembler::Label loop_end;
+        loop_start.link(m_assembler);
+        m_assembler.jump_if(
+            Assembler::Operand::Register(GPR0),
+            Assembler::Condition::EqualTo,
+            Assembler::Operand::Imm(0),
+            loop_end);
+        m_assembler.sub(
+            Assembler::Operand::Register(GPR0),
+            Assembler::Operand::Imm(1));
+        m_assembler.mov(
+            Assembler::Operand::Register(GPR1),
+            Assembler::Operand::Mem64BaseAndOffset(GPR1, Environment::outer_environment_offset()));
+        m_assembler.jump(loop_start);
+        loop_end.link(m_assembler);
+    }
+
+    // GPR1 now points to the environment holding our binding.
+
+    // if (environment->is_permanently_screwed_by_eval()) goto slow_case;
+    // FIXME: Load 8 bits here directly instead of loading 64 and masking.
+    m_assembler.mov(
+        Assembler::Operand::Register(GPR0),
+        Assembler::Operand::Mem64BaseAndOffset(GPR1, Environment::is_permanently_screwed_by_eval_offset()));
+    m_assembler.bitwise_and(
+        Assembler::Operand::Register(GPR0),
+        Assembler::Operand::Imm(0xff));
+    m_assembler.jump_if(
+        Assembler::Operand::Register(GPR0),
+        Assembler::Condition::NotEqualTo,
+        Assembler::Operand::Imm(0),
+        slow_case);
+
+    // GPR1 = environment->m_bindings.outline_buffer()
+    m_assembler.mov(
+        Assembler::Operand::Register(GPR1),
+        Assembler::Operand::Mem64BaseAndOffset(GPR1, DeclarativeEnvironment::bindings_offset() + Vector<DeclarativeEnvironment::Binding>::outline_buffer_offset()));
+
+    // GPR0 = index
+    // FIXME: Load 32 bits directly instead of 64 and masking.
+    m_assembler.mov(
+        Assembler::Operand::Register(GPR0),
+        Assembler::Operand::Mem64BaseAndOffset(ARG2, Bytecode::EnvironmentVariableCache::value_offset() + EnvironmentCoordinate::index_offset()));
+    m_assembler.mov(
+        Assembler::Operand::Register(GPR2),
+        Assembler::Operand::Imm(0xffffffff));
+    m_assembler.bitwise_and(
+        Assembler::Operand::Register(GPR0),
+        Assembler::Operand::Register(GPR2));
+
+    // GPR0 *= sizeof(DeclarativeEnvironment::Binding)
+    m_assembler.mul32(
+        Assembler::Operand::Register(GPR0),
+        Assembler::Operand::Imm(sizeof(DeclarativeEnvironment::Binding)),
+        slow_case);
+
+    // GPR1 = &binding
+    m_assembler.add(
+        Assembler::Operand::Register(GPR1),
+        Assembler::Operand::Register(GPR0));
+
+    // if (!binding.initialized) goto slow_case;
+    m_assembler.mov(
+        Assembler ::Operand::Register(GPR0),
+        Assembler::Operand::Mem64BaseAndOffset(GPR1, DeclarativeEnvironment::Binding::initialized_offset()));
+    m_assembler.bitwise_and(
+        Assembler::Operand::Register(GPR0),
+        Assembler::Operand::Imm(0xff));
+    m_assembler.jump_if(
+        Assembler::Operand::Register(GPR0),
+        Assembler::Condition::EqualTo,
+        Assembler::Operand::Imm(0),
+        slow_case);
+
+    // accumulator = binding.value;
+    m_assembler.mov(
+        Assembler::Operand::Register(GPR0),
+        Assembler::Operand::Mem64BaseAndOffset(GPR1, DeclarativeEnvironment::Binding::value_offset()));
+
+    store_accumulator(GPR0);
+    Assembler::Label end;
+    m_assembler.jump(end);
+
+    // Slow case: Uncached access. Call C++ helper.
+    slow_case.link(m_assembler);
+    m_assembler.mov(
+        Assembler::Operand::Register(ARG1),
+        Assembler::Operand::Imm(bit_cast<u64>(&m_bytecode_executable.get_identifier(op.identifier()))));
     native_call((void*)cxx_get_variable);
     store_accumulator(RET);
     check_exception();
+
+    end.link(m_assembler);
 }
 
 static Value cxx_get_callee_and_this_from_environment(VM& vm, DeprecatedFlyString const& name, u32 cache_index, Bytecode::Register callee_reg, Bytecode::Register this_reg)

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

@@ -27,6 +27,7 @@ private:
 #    if ARCH(X86_64)
     static constexpr auto GPR0 = Assembler::Reg::RAX;
     static constexpr auto GPR1 = Assembler::Reg::RCX;
+    static constexpr auto GPR2 = Assembler::Reg::R12;
     static constexpr auto ARG0 = Assembler::Reg::RDI;
     static constexpr auto ARG1 = Assembler::Reg::RSI;
     static constexpr auto ARG2 = Assembler::Reg::RDX;

+ 5 - 0
Userland/Libraries/LibJS/Runtime/DeclarativeEnvironment.h

@@ -19,6 +19,9 @@ class DeclarativeEnvironment : public Environment {
     JS_ENVIRONMENT(DeclarativeEnvironment, Environment);
 
     struct Binding {
+        static FlatPtr value_offset() { return OFFSET_OF(Binding, value); }
+        static FlatPtr initialized_offset() { return OFFSET_OF(Binding, initialized); }
+
         DeprecatedFlyString name;
         Value value;
         bool strict { false };
@@ -67,6 +70,8 @@ public:
 
     [[nodiscard]] u64 environment_serial_number() const { return m_environment_serial_number; }
 
+    static FlatPtr bindings_offset() { return OFFSET_OF(DeclarativeEnvironment, m_bindings); }
+
 private:
     ThrowCompletionOr<Value> get_binding_value_direct(VM&, Binding&, bool strict);
     ThrowCompletionOr<void> set_mutable_binding_direct(VM&, Binding&, Value, bool strict);

+ 3 - 0
Userland/Libraries/LibJS/Runtime/Environment.h

@@ -57,6 +57,9 @@ public:
     bool is_permanently_screwed_by_eval() const { return m_permanently_screwed_by_eval; }
     void set_permanently_screwed_by_eval();
 
+    static FlatPtr is_permanently_screwed_by_eval_offset() { return OFFSET_OF(Environment, m_permanently_screwed_by_eval); }
+    static FlatPtr outer_environment_offset() { return OFFSET_OF(Environment, m_outer_environment); }
+
 protected:
     explicit Environment(Environment* parent);
 

+ 3 - 0
Userland/Libraries/LibJS/Runtime/EnvironmentCoordinate.h

@@ -15,6 +15,9 @@ struct EnvironmentCoordinate {
     u32 hops { invalid_marker };
     u32 index { invalid_marker };
 
+    static FlatPtr hops_offset() { return OFFSET_OF(EnvironmentCoordinate, hops); }
+    static FlatPtr index_offset() { return OFFSET_OF(EnvironmentCoordinate, index); }
+
     bool is_valid() const { return hops != invalid_marker && index != invalid_marker; }
 
     static constexpr u32 invalid_marker = 0xfffffffe;

+ 2 - 0
Userland/Libraries/LibJS/Runtime/ExecutionContext.h

@@ -30,6 +30,8 @@ struct ExecutionContext {
 
     void visit_edges(Cell::Visitor&);
 
+    static FlatPtr lexical_environment_offset() { return OFFSET_OF(ExecutionContext, lexical_environment); }
+
 private:
     explicit ExecutionContext(MarkedVector<Value> existing_arguments, MarkedVector<Value> existing_local_variables);