diff --git a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp index fc43a8ff71..e4a873ba09 100644 --- a/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp +++ b/Userland/Libraries/LibJS/Bytecode/ASTCodegen.cpp @@ -1120,7 +1120,7 @@ static Bytecode::CodeGenerationErrorOr generate_object_binding_pattern_byt } generator.emit(value_reg); - generator.emit(generator.intern_identifier(identifier)); + generator.emit_get_by_id(generator.intern_identifier(identifier)); } else { auto expression = name.get>(); TRY(expression->generate_bytecode(generator)); @@ -1474,7 +1474,7 @@ static Bytecode::CodeGenerationErrorOr get_base_and_value_from_member_expr } else { // 3. Let propertyKey be StringValue of IdentifierName. auto identifier_table_ref = generator.intern_identifier(verify_cast(member_expression.property()).string()); - generator.emit(identifier_table_ref, this_reg); + generator.emit_get_by_id_with_this(identifier_table_ref, this_reg); } } else { TRY(member_expression.object().generate_bytecode(generator)); @@ -1485,7 +1485,7 @@ static Bytecode::CodeGenerationErrorOr get_base_and_value_from_member_expr } else if (is(member_expression.property())) { generator.emit(generator.intern_identifier(verify_cast(member_expression.property()).string())); } else { - generator.emit(generator.intern_identifier(verify_cast(member_expression.property()).string())); + generator.emit_get_by_id(generator.intern_identifier(verify_cast(member_expression.property()).string())); } } @@ -1587,11 +1587,11 @@ Bytecode::CodeGenerationErrorOr YieldExpression::generate_bytecode(Bytecod // The accumulator is set to an object, for example: { "type": 1 (normal), value: 1337 } generator.emit(received_completion_register); - generator.emit(type_identifier); + generator.emit_get_by_id(type_identifier); generator.emit(received_completion_type_register); generator.emit(received_completion_register); - generator.emit(value_identifier); + generator.emit_get_by_id(value_identifier); generator.emit(received_completion_value_register); }; @@ -1613,14 +1613,14 @@ Bytecode::CodeGenerationErrorOr YieldExpression::generate_bytecode(Bytecod // 5. Let iterator be iteratorRecord.[[Iterator]]. auto iterator_register = generator.allocate_register(); auto iterator_identifier = generator.intern_identifier("iterator"); - generator.emit(iterator_identifier); + generator.emit_get_by_id(iterator_identifier); generator.emit(iterator_register); // Cache iteratorRecord.[[NextMethod]] for use in step 7.a.i. auto next_method_register = generator.allocate_register(); auto next_method_identifier = generator.intern_identifier("next"); generator.emit(iterator_record_register); - generator.emit(next_method_identifier); + generator.emit_get_by_id(next_method_identifier); generator.emit(next_method_register); // 6. Let received be NormalCompletion(undefined). @@ -2424,11 +2424,11 @@ static void generate_await(Bytecode::Generator& generator) // The accumulator is set to an object, for example: { "type": 1 (normal), value: 1337 } generator.emit(received_completion_register); - generator.emit(type_identifier); + generator.emit_get_by_id(type_identifier); generator.emit(received_completion_type_register); generator.emit(received_completion_register); - generator.emit(value_identifier); + generator.emit_get_by_id(value_identifier); generator.emit(received_completion_value_register); auto& normal_completion_continuation_block = generator.make_block(); @@ -2923,7 +2923,7 @@ static Bytecode::CodeGenerationErrorOr generate_optional_chain(Bytecode::G }, [&](OptionalChain::MemberReference const& ref) -> Bytecode::CodeGenerationErrorOr { generator.emit(current_base_register); - generator.emit(generator.intern_identifier(ref.identifier->string())); + generator.emit_get_by_id(generator.intern_identifier(ref.identifier->string())); generator.emit(current_value_register); return {}; }, diff --git a/Userland/Libraries/LibJS/Bytecode/Executable.h b/Userland/Libraries/LibJS/Bytecode/Executable.h index 1706e7c16f..d3b809aea6 100644 --- a/Userland/Libraries/LibJS/Bytecode/Executable.h +++ b/Userland/Libraries/LibJS/Bytecode/Executable.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Andreas Kling + * Copyright (c) 2021-2023, Andreas Kling * * SPDX-License-Identifier: BSD-2-Clause */ @@ -8,14 +8,21 @@ #include #include +#include #include #include #include namespace JS::Bytecode { +struct PropertyLookupCache { + WeakPtr shape; + Optional property_offset; +}; + struct Executable { DeprecatedFlyString name; + Vector property_lookup_caches; Vector> basic_blocks; NonnullOwnPtr string_table; NonnullOwnPtr identifier_table; diff --git a/Userland/Libraries/LibJS/Bytecode/Generator.cpp b/Userland/Libraries/LibJS/Bytecode/Generator.cpp index b40f24e286..356d543bac 100644 --- a/Userland/Libraries/LibJS/Bytecode/Generator.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Generator.cpp @@ -54,8 +54,12 @@ CodeGenerationErrorOr> Generator::generate(ASTNode con else if (is(node)) is_strict_mode = static_cast(node).is_strict_mode(); + Vector property_lookup_caches; + property_lookup_caches.resize(generator.m_next_property_lookup_cache); + return adopt_own(*new Executable { .name = {}, + .property_lookup_caches = move(property_lookup_caches), .basic_blocks = move(generator.m_root_basic_blocks), .string_table = move(generator.m_string_table), .identifier_table = move(generator.m_identifier_table), @@ -199,7 +203,7 @@ CodeGenerationErrorOr Generator::emit_load_from_reference(JS::ASTNode cons } else { // 3. Let propertyKey be StringValue of IdentifierName. auto identifier_table_ref = intern_identifier(verify_cast(expression.property()).string()); - emit(identifier_table_ref, super_reference.this_value); + emit_get_by_id_with_this(identifier_table_ref, super_reference.this_value); } } else { TRY(expression.object().generate_bytecode(*this)); @@ -212,7 +216,7 @@ CodeGenerationErrorOr Generator::emit_load_from_reference(JS::ASTNode cons emit(object_reg); } else if (expression.property().is_identifier()) { auto identifier_table_ref = intern_identifier(verify_cast(expression.property()).string()); - emit(identifier_table_ref); + emit_get_by_id(identifier_table_ref); } else if (expression.property().is_private_identifier()) { auto identifier_table_ref = intern_identifier(verify_cast(expression.property()).string()); emit(identifier_table_ref); @@ -543,4 +547,14 @@ CodeGenerationErrorOr Generator::emit_named_evaluation_if_anonymous_functi return {}; } +void Generator::emit_get_by_id(IdentifierTableIndex id) +{ + emit(id, m_next_property_lookup_cache++); +} + +void Generator::emit_get_by_id_with_this(IdentifierTableIndex id, Register this_reg) +{ + emit(id, this_reg, m_next_property_lookup_cache++); +} + } diff --git a/Userland/Libraries/LibJS/Bytecode/Generator.h b/Userland/Libraries/LibJS/Bytecode/Generator.h index af3beb59c5..732d0d0703 100644 --- a/Userland/Libraries/LibJS/Bytecode/Generator.h +++ b/Userland/Libraries/LibJS/Bytecode/Generator.h @@ -199,6 +199,9 @@ public: m_boundaries.take_last(); } + void emit_get_by_id(IdentifierTableIndex); + void emit_get_by_id_with_this(IdentifierTableIndex, Register); + private: Generator(); ~Generator() = default; @@ -218,6 +221,7 @@ private: u32 m_next_register { 2 }; u32 m_next_block { 1 }; + u32 m_next_property_lookup_cache { 0 }; FunctionKind m_enclosing_function_kind { FunctionKind::Normal }; Vector m_continuable_scopes; Vector m_breakable_scopes; diff --git a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp index bb9d8bb884..02236be0ba 100644 --- a/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Interpreter.cpp @@ -193,7 +193,7 @@ ThrowCompletionOr Interpreter::run(SourceTextModule& module) return js_undefined(); } -Interpreter::ValueAndFrame Interpreter::run_and_return_frame(Realm& realm, Executable const& executable, BasicBlock const* entry_point, RegisterWindow* in_frame) +Interpreter::ValueAndFrame Interpreter::run_and_return_frame(Realm& realm, Executable& executable, BasicBlock const* entry_point, RegisterWindow* in_frame) { dbgln_if(JS_BYTECODE_DEBUG, "Bytecode::Interpreter will run unit {:p}", &executable); diff --git a/Userland/Libraries/LibJS/Bytecode/Interpreter.h b/Userland/Libraries/LibJS/Bytecode/Interpreter.h index 50e8913d37..24eefaedfe 100644 --- a/Userland/Libraries/LibJS/Bytecode/Interpreter.h +++ b/Userland/Libraries/LibJS/Bytecode/Interpreter.h @@ -50,7 +50,7 @@ public: ThrowCompletionOr run(Script&, JS::GCPtr lexical_environment_override = nullptr); ThrowCompletionOr run(SourceTextModule&); - ThrowCompletionOr run(Realm& realm, Bytecode::Executable const& executable, Bytecode::BasicBlock const* entry_point = nullptr) + ThrowCompletionOr run(Realm& realm, Bytecode::Executable& executable, Bytecode::BasicBlock const* entry_point = nullptr) { auto value_and_frame = run_and_return_frame(realm, executable, entry_point); return move(value_and_frame.value); @@ -60,7 +60,7 @@ public: ThrowCompletionOr value; OwnPtr frame; }; - ValueAndFrame run_and_return_frame(Realm&, Bytecode::Executable const&, Bytecode::BasicBlock const* entry_point, RegisterWindow* = nullptr); + ValueAndFrame run_and_return_frame(Realm&, Bytecode::Executable&, Bytecode::BasicBlock const* entry_point, RegisterWindow* = nullptr); ALWAYS_INLINE Value& accumulator() { return reg(Register::accumulator()); } Value& reg(Register const& r) { return registers()[r.index()]; } @@ -88,7 +88,8 @@ public: void leave_unwind_context(); ThrowCompletionOr continue_pending_unwind(Label const& resume_label); - Executable const& current_executable() { return *m_current_executable; } + Executable& current_executable() { return *m_current_executable; } + Executable const& current_executable() const { return *m_current_executable; } BasicBlock const& current_block() const { return *m_current_block; } size_t pc() const; DeprecatedString debug_position() const; @@ -124,7 +125,7 @@ private: Optional m_return_value; Optional m_saved_return_value; Optional m_saved_exception; - Executable const* m_current_executable { nullptr }; + Executable* m_current_executable { nullptr }; OwnPtr m_ast_interpreter; BasicBlock const* m_current_block { nullptr }; InstructionStreamIterator* m_pc { nullptr }; diff --git a/Userland/Libraries/LibJS/Bytecode/Op.cpp b/Userland/Libraries/LibJS/Bytecode/Op.cpp index 60387a05ab..2faed42f25 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.cpp +++ b/Userland/Libraries/LibJS/Bytecode/Op.cpp @@ -515,15 +515,19 @@ ThrowCompletionOr SetLocal::execute_impl(Bytecode::Interpreter& interprete return {}; } -static ThrowCompletionOr get_by_id(Bytecode::Interpreter& interpreter, IdentifierTableIndex property, Value base_value, Value this_value) +static ThrowCompletionOr get_by_id(Bytecode::Interpreter& interpreter, IdentifierTableIndex property, Value base_value, Value this_value, u32 cache_index) { auto& vm = interpreter.vm(); - auto const& name = interpreter.current_executable().get_identifier(property); + auto& cache = interpreter.current_executable().property_lookup_caches[cache_index]; // OPTIMIZATION: For various primitives we can avoid actually creating a new object for them. GCPtr base_obj; - if (base_value.is_string()) { + if (base_value.is_object()) { + // This would be covered by the `else` branch below, + // but let's avoid all the extra checks if it's already an object. + base_obj = base_value.as_object(); + } else if (base_value.is_string()) { auto string_value = TRY(base_value.as_string().get(vm, name)); if (string_value.has_value()) { interpreter.accumulator() = *string_value; @@ -538,21 +542,37 @@ static ThrowCompletionOr get_by_id(Bytecode::Interpreter& interpreter, Ide base_obj = TRY(base_value.to_object(vm)); } - interpreter.accumulator() = TRY(base_obj->internal_get(name, this_value)); + // OPTIMIZATION: If the shape of the object hasn't changed, we can use the cached property offset. + if (&base_obj->shape() == cache.shape) { + interpreter.accumulator() = base_obj->get_direct(cache.property_offset.value()); + return {}; + } + + CacheablePropertyMetadata cacheable_metadata; + interpreter.accumulator() = TRY(base_obj->internal_get(name, this_value, &cacheable_metadata)); + + if (cacheable_metadata.type == CacheablePropertyMetadata::Type::OwnProperty) { + cache.shape = &base_obj->shape(); + cache.property_offset = cacheable_metadata.property_offset.value(); + } else { + cache.shape = nullptr; + cache.property_offset = {}; + } + return {}; } ThrowCompletionOr GetById::execute_impl(Bytecode::Interpreter& interpreter) const { auto base_value = interpreter.accumulator(); - return get_by_id(interpreter, m_property, base_value, base_value); + return get_by_id(interpreter, m_property, base_value, base_value, m_cache_index); } ThrowCompletionOr GetByIdWithThis::execute_impl(Bytecode::Interpreter& interpreter) const { auto base_value = interpreter.accumulator(); auto this_value = interpreter.reg(m_this_value); - return get_by_id(interpreter, m_property, base_value, this_value); + return get_by_id(interpreter, m_property, base_value, this_value, m_cache_index); } ThrowCompletionOr GetPrivateById::execute_impl(Bytecode::Interpreter& interpreter) const diff --git a/Userland/Libraries/LibJS/Bytecode/Op.h b/Userland/Libraries/LibJS/Bytecode/Op.h index 48d1e75958..0bb111d1ca 100644 --- a/Userland/Libraries/LibJS/Bytecode/Op.h +++ b/Userland/Libraries/LibJS/Bytecode/Op.h @@ -564,9 +564,10 @@ private: class GetById final : public Instruction { public: - explicit GetById(IdentifierTableIndex property) + GetById(IdentifierTableIndex property, u32 cache_index) : Instruction(Type::GetById) , m_property(property) + , m_cache_index(cache_index) { } @@ -577,14 +578,16 @@ public: private: IdentifierTableIndex m_property; + u32 m_cache_index { 0 }; }; class GetByIdWithThis final : public Instruction { public: - GetByIdWithThis(IdentifierTableIndex property, Register this_value) + GetByIdWithThis(IdentifierTableIndex property, Register this_value, u32 cache_index) : Instruction(Type::GetByIdWithThis) , m_property(property) , m_this_value(this_value) + , m_cache_index(cache_index) { } @@ -600,6 +603,7 @@ public: private: IdentifierTableIndex m_property; Register m_this_value; + u32 m_cache_index { 0 }; }; class GetPrivateById final : public Instruction {