mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2024-12-04 05:20:30 +00:00
LibJS/Bytecode: Cache object own property accesses
The instructions GetById and GetByIdWithThis now remember the last-seen Shape, and if we see the same object again, we reuse the property offset from last time without doing a new lookup. This allows us to use Object::get_direct(), bypassing the entire lookup machinery and saving lots of time. ~23% speed-up on Kraken/ai-astar.js :^)
This commit is contained in:
parent
52cd671163
commit
de8e4b1853
Notes:
sideshowbarker
2024-07-16 23:34:44 +09:00
Author: https://github.com/awesomekling Commit: https://github.com/SerenityOS/serenity/commit/de8e4b1853 Pull-request: https://github.com/SerenityOS/serenity/pull/19900 Reviewed-by: https://github.com/linusg ✅
8 changed files with 76 additions and 26 deletions
|
@ -1120,7 +1120,7 @@ static Bytecode::CodeGenerationErrorOr<void> generate_object_binding_pattern_byt
|
|||
}
|
||||
|
||||
generator.emit<Bytecode::Op::Load>(value_reg);
|
||||
generator.emit<Bytecode::Op::GetById>(generator.intern_identifier(identifier));
|
||||
generator.emit_get_by_id(generator.intern_identifier(identifier));
|
||||
} else {
|
||||
auto expression = name.get<NonnullRefPtr<Expression const>>();
|
||||
TRY(expression->generate_bytecode(generator));
|
||||
|
@ -1474,7 +1474,7 @@ static Bytecode::CodeGenerationErrorOr<void> get_base_and_value_from_member_expr
|
|||
} else {
|
||||
// 3. Let propertyKey be StringValue of IdentifierName.
|
||||
auto identifier_table_ref = generator.intern_identifier(verify_cast<Identifier>(member_expression.property()).string());
|
||||
generator.emit<Bytecode::Op::GetByIdWithThis>(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<void> get_base_and_value_from_member_expr
|
|||
} else if (is<PrivateIdentifier>(member_expression.property())) {
|
||||
generator.emit<Bytecode::Op::GetPrivateById>(generator.intern_identifier(verify_cast<PrivateIdentifier>(member_expression.property()).string()));
|
||||
} else {
|
||||
generator.emit<Bytecode::Op::GetById>(generator.intern_identifier(verify_cast<Identifier>(member_expression.property()).string()));
|
||||
generator.emit_get_by_id(generator.intern_identifier(verify_cast<Identifier>(member_expression.property()).string()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1587,11 +1587,11 @@ Bytecode::CodeGenerationErrorOr<void> YieldExpression::generate_bytecode(Bytecod
|
|||
// The accumulator is set to an object, for example: { "type": 1 (normal), value: 1337 }
|
||||
generator.emit<Bytecode::Op::Store>(received_completion_register);
|
||||
|
||||
generator.emit<Bytecode::Op::GetById>(type_identifier);
|
||||
generator.emit_get_by_id(type_identifier);
|
||||
generator.emit<Bytecode::Op::Store>(received_completion_type_register);
|
||||
|
||||
generator.emit<Bytecode::Op::Load>(received_completion_register);
|
||||
generator.emit<Bytecode::Op::GetById>(value_identifier);
|
||||
generator.emit_get_by_id(value_identifier);
|
||||
generator.emit<Bytecode::Op::Store>(received_completion_value_register);
|
||||
};
|
||||
|
||||
|
@ -1613,14 +1613,14 @@ Bytecode::CodeGenerationErrorOr<void> 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<Bytecode::Op::GetById>(iterator_identifier);
|
||||
generator.emit_get_by_id(iterator_identifier);
|
||||
generator.emit<Bytecode::Op::Store>(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<Bytecode::Op::Load>(iterator_record_register);
|
||||
generator.emit<Bytecode::Op::GetById>(next_method_identifier);
|
||||
generator.emit_get_by_id(next_method_identifier);
|
||||
generator.emit<Bytecode::Op::Store>(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<Bytecode::Op::Store>(received_completion_register);
|
||||
|
||||
generator.emit<Bytecode::Op::GetById>(type_identifier);
|
||||
generator.emit_get_by_id(type_identifier);
|
||||
generator.emit<Bytecode::Op::Store>(received_completion_type_register);
|
||||
|
||||
generator.emit<Bytecode::Op::Load>(received_completion_register);
|
||||
generator.emit<Bytecode::Op::GetById>(value_identifier);
|
||||
generator.emit_get_by_id(value_identifier);
|
||||
generator.emit<Bytecode::Op::Store>(received_completion_value_register);
|
||||
|
||||
auto& normal_completion_continuation_block = generator.make_block();
|
||||
|
@ -2923,7 +2923,7 @@ static Bytecode::CodeGenerationErrorOr<void> generate_optional_chain(Bytecode::G
|
|||
},
|
||||
[&](OptionalChain::MemberReference const& ref) -> Bytecode::CodeGenerationErrorOr<void> {
|
||||
generator.emit<Bytecode::Op::Store>(current_base_register);
|
||||
generator.emit<Bytecode::Op::GetById>(generator.intern_identifier(ref.identifier->string()));
|
||||
generator.emit_get_by_id(generator.intern_identifier(ref.identifier->string()));
|
||||
generator.emit<Bytecode::Op::Store>(current_value_register);
|
||||
return {};
|
||||
},
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2021-2023, Andreas Kling <kling@serenityos.org>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
@ -8,14 +8,21 @@
|
|||
|
||||
#include <AK/DeprecatedFlyString.h>
|
||||
#include <AK/NonnullOwnPtr.h>
|
||||
#include <AK/WeakPtr.h>
|
||||
#include <LibJS/Bytecode/BasicBlock.h>
|
||||
#include <LibJS/Bytecode/IdentifierTable.h>
|
||||
#include <LibJS/Bytecode/StringTable.h>
|
||||
|
||||
namespace JS::Bytecode {
|
||||
|
||||
struct PropertyLookupCache {
|
||||
WeakPtr<Shape> shape;
|
||||
Optional<u32> property_offset;
|
||||
};
|
||||
|
||||
struct Executable {
|
||||
DeprecatedFlyString name;
|
||||
Vector<PropertyLookupCache> property_lookup_caches;
|
||||
Vector<NonnullOwnPtr<BasicBlock>> basic_blocks;
|
||||
NonnullOwnPtr<StringTable> string_table;
|
||||
NonnullOwnPtr<IdentifierTable> identifier_table;
|
||||
|
|
|
@ -54,8 +54,12 @@ CodeGenerationErrorOr<NonnullOwnPtr<Executable>> Generator::generate(ASTNode con
|
|||
else if (is<FunctionExpression>(node))
|
||||
is_strict_mode = static_cast<FunctionExpression const&>(node).is_strict_mode();
|
||||
|
||||
Vector<PropertyLookupCache> 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<void> Generator::emit_load_from_reference(JS::ASTNode cons
|
|||
} else {
|
||||
// 3. Let propertyKey be StringValue of IdentifierName.
|
||||
auto identifier_table_ref = intern_identifier(verify_cast<Identifier>(expression.property()).string());
|
||||
emit<Bytecode::Op::GetByIdWithThis>(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<void> Generator::emit_load_from_reference(JS::ASTNode cons
|
|||
emit<Bytecode::Op::GetByValue>(object_reg);
|
||||
} else if (expression.property().is_identifier()) {
|
||||
auto identifier_table_ref = intern_identifier(verify_cast<Identifier>(expression.property()).string());
|
||||
emit<Bytecode::Op::GetById>(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<PrivateIdentifier>(expression.property()).string());
|
||||
emit<Bytecode::Op::GetPrivateById>(identifier_table_ref);
|
||||
|
@ -543,4 +547,14 @@ CodeGenerationErrorOr<void> Generator::emit_named_evaluation_if_anonymous_functi
|
|||
return {};
|
||||
}
|
||||
|
||||
void Generator::emit_get_by_id(IdentifierTableIndex id)
|
||||
{
|
||||
emit<Op::GetById>(id, m_next_property_lookup_cache++);
|
||||
}
|
||||
|
||||
void Generator::emit_get_by_id_with_this(IdentifierTableIndex id, Register this_reg)
|
||||
{
|
||||
emit<Op::GetByIdWithThis>(id, this_reg, m_next_property_lookup_cache++);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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<LabelableScope> m_continuable_scopes;
|
||||
Vector<LabelableScope> m_breakable_scopes;
|
||||
|
|
|
@ -193,7 +193,7 @@ ThrowCompletionOr<Value> 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);
|
||||
|
||||
|
|
|
@ -50,7 +50,7 @@ public:
|
|||
ThrowCompletionOr<Value> run(Script&, JS::GCPtr<Environment> lexical_environment_override = nullptr);
|
||||
ThrowCompletionOr<Value> run(SourceTextModule&);
|
||||
|
||||
ThrowCompletionOr<Value> run(Realm& realm, Bytecode::Executable const& executable, Bytecode::BasicBlock const* entry_point = nullptr)
|
||||
ThrowCompletionOr<Value> 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> value;
|
||||
OwnPtr<RegisterWindow> 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<void> 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<Value> m_return_value;
|
||||
Optional<Value> m_saved_return_value;
|
||||
Optional<Value> m_saved_exception;
|
||||
Executable const* m_current_executable { nullptr };
|
||||
Executable* m_current_executable { nullptr };
|
||||
OwnPtr<JS::Interpreter> m_ast_interpreter;
|
||||
BasicBlock const* m_current_block { nullptr };
|
||||
InstructionStreamIterator* m_pc { nullptr };
|
||||
|
|
|
@ -515,15 +515,19 @@ ThrowCompletionOr<void> SetLocal::execute_impl(Bytecode::Interpreter& interprete
|
|||
return {};
|
||||
}
|
||||
|
||||
static ThrowCompletionOr<void> get_by_id(Bytecode::Interpreter& interpreter, IdentifierTableIndex property, Value base_value, Value this_value)
|
||||
static ThrowCompletionOr<void> 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<Object> 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<void> 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<void> 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<void> 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<void> GetPrivateById::execute_impl(Bytecode::Interpreter& interpreter) const
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue