222 lines
7.4 KiB
C++
222 lines
7.4 KiB
C++
/*
|
|
* Copyright (c) 2020-2024, Andreas Kling <andreas@ladybird.org>
|
|
* Copyright (c) 2025, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
|
|
*
|
|
* SPDX-License-Identifier: BSD-2-Clause
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
#include <AK/HashMap.h>
|
|
#include <AK/IterationDecision.h>
|
|
#include <AK/OwnPtr.h>
|
|
#include <AK/StringView.h>
|
|
#include <AK/Vector.h>
|
|
#include <AK/Weakable.h>
|
|
#include <LibGC/Weak.h>
|
|
#include <LibGC/WeakInlines.h>
|
|
#include <LibJS/Export.h>
|
|
#include <LibJS/Forward.h>
|
|
#include <LibJS/Heap/Cell.h>
|
|
#include <LibJS/Runtime/DescriptorArray.h>
|
|
#include <LibJS/Runtime/PropertyAttributes.h>
|
|
#include <LibJS/Runtime/PropertyKey.h>
|
|
#include <LibJS/Runtime/Value.h>
|
|
|
|
namespace JS {
|
|
|
|
struct TransitionKey {
|
|
PropertyKey property_key;
|
|
PropertyAttributes attributes { 0 };
|
|
|
|
bool operator==(TransitionKey const& other) const
|
|
{
|
|
return property_key == other.property_key && attributes == other.attributes;
|
|
}
|
|
|
|
void visit_edges(Cell::Visitor& visitor)
|
|
{
|
|
property_key.visit_edges(visitor);
|
|
}
|
|
};
|
|
|
|
class PrototypeChainValidity final : public Cell {
|
|
GC_CELL(PrototypeChainValidity, Cell);
|
|
GC_DECLARE_ALLOCATOR(PrototypeChainValidity);
|
|
|
|
public:
|
|
[[nodiscard]] bool is_valid() const { return m_valid; }
|
|
void set_valid(bool valid) { m_valid = valid; }
|
|
|
|
private:
|
|
bool m_valid { true };
|
|
size_t padding { 0 };
|
|
};
|
|
|
|
class JS_API Shape final : public Cell {
|
|
GC_CELL(Shape, Cell);
|
|
GC_DECLARE_ALLOCATOR(Shape);
|
|
|
|
public:
|
|
static constexpr bool OVERRIDES_FINALIZE = true;
|
|
|
|
virtual ~Shape() override;
|
|
virtual void finalize() override;
|
|
|
|
[[nodiscard]] GC::Ref<Shape> create_put_transition(PropertyKey const&, PropertyAttributes attributes);
|
|
[[nodiscard]] GC::Ref<Shape> create_configure_transition(PropertyKey const&, PropertyAttributes attributes);
|
|
[[nodiscard]] GC::Ref<Shape> create_prototype_transition(Object* new_prototype);
|
|
[[nodiscard]] GC::Ref<Shape> create_delete_transition(PropertyKey const&);
|
|
[[nodiscard]] GC::Ref<Shape> create_dictionary_transition();
|
|
[[nodiscard]] GC::Ref<Shape> clone_for_prototype();
|
|
|
|
void add_property_without_transition(PropertyKey const&, PropertyAttributes);
|
|
|
|
void remove_property_without_transition(PropertyKey const&, u32 offset);
|
|
void set_property_attributes_without_transition(PropertyKey const&, PropertyAttributes);
|
|
|
|
[[nodiscard]] bool is_dictionary() const { return m_dictionary; }
|
|
[[nodiscard]] bool has_parameter_map() const { return m_has_parameter_map; }
|
|
void set_has_parameter_map() { m_has_parameter_map = true; }
|
|
|
|
[[nodiscard]] u32 dictionary_generation() const { return m_dictionary_generation; }
|
|
|
|
[[nodiscard]] bool is_prototype_shape() const { return m_prototype_chain_validity; }
|
|
void set_prototype_shape();
|
|
|
|
GC::Ptr<PrototypeChainValidity> prototype_chain_validity() const { return m_prototype_chain_validity; }
|
|
|
|
Realm& realm() const { return m_realm; }
|
|
|
|
Object* prototype() { return m_prototype; }
|
|
Object const* prototype() const { return m_prototype; }
|
|
|
|
Optional<PropertyMetadata> lookup(PropertyKey const&) const;
|
|
template<typename Callback>
|
|
void for_each_property_in_insertion_order(Callback&& callback) const
|
|
{
|
|
if (m_dictionary) {
|
|
for (auto const& [property_key, metadata] : property_table()) {
|
|
if constexpr (IsSame<IterationDecision, decltype(callback(property_key, metadata))>) {
|
|
if (callback(property_key, metadata) == IterationDecision::Break)
|
|
return;
|
|
} else {
|
|
callback(property_key, metadata);
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
if (!descriptors())
|
|
return;
|
|
descriptors()->for_each_in_insertion_order(forward<Callback>(callback), m_property_count);
|
|
}
|
|
u32 property_count() const { return m_property_count; }
|
|
|
|
void set_prototype_without_transition(Object* new_prototype);
|
|
|
|
private:
|
|
enum class PropertyCountChange : u8 {
|
|
Preserve,
|
|
Increment,
|
|
Decrement,
|
|
};
|
|
|
|
enum class ForwardTransitionStorage : u8 {
|
|
Empty,
|
|
Single,
|
|
Multiple,
|
|
};
|
|
|
|
explicit Shape(Realm&);
|
|
Shape(Shape& previous_shape, PropertyCountChange);
|
|
Shape(Shape& previous_shape, Object* new_prototype);
|
|
|
|
void invalidate_prototype_if_needed_for_new_prototype(GC::Ref<Shape> new_prototype_shape);
|
|
void invalidate_prototype_if_needed_for_change_without_transition();
|
|
void invalidate_all_prototype_chains_leading_to_this();
|
|
|
|
void add_child_prototype_shape(GC::Ref<Shape>);
|
|
|
|
virtual void visit_edges(Visitor&) override;
|
|
virtual size_t external_memory_size() const override;
|
|
|
|
[[nodiscard]] GC::Ptr<Shape> get_or_prune_cached_forward_transition(TransitionKey const&);
|
|
void cache_forward_transition(TransitionKey const&, GC::Ref<Shape>);
|
|
void clear_forward_transitions();
|
|
[[nodiscard]] GC::Ptr<Shape> get_or_prune_cached_prototype_transition(Object* prototype);
|
|
[[nodiscard]] GC::Ptr<Shape> get_or_prune_cached_delete_transition(PropertyKey const&);
|
|
|
|
void ensure_descriptor_array();
|
|
[[nodiscard]] GC::Ref<DescriptorArray> copy_descriptors() const;
|
|
void copy_properties_to_dictionary_shape(Shape&) const;
|
|
|
|
using PropertyTable = OrderedHashMap<PropertyKey, PropertyMetadata>;
|
|
using PropertyTablePtr = OwnPtr<PropertyTable>;
|
|
using ForwardTransitionMap = HashMap<TransitionKey, GC::Weak<Shape>>;
|
|
using ForwardTransitionMapPtr = OwnPtr<ForwardTransitionMap>;
|
|
using ForwardTransitionTarget = GC::Weak<Shape>;
|
|
|
|
static_assert(IsTriviallyDestructible<GC::Ptr<DescriptorArray>>);
|
|
|
|
GC::Ptr<DescriptorArray> descriptors() const;
|
|
void set_descriptors(GC::Ptr<DescriptorArray>);
|
|
PropertyTable& property_table();
|
|
PropertyTable const& property_table() const;
|
|
void become_dictionary_shape();
|
|
|
|
union PropertyStorage {
|
|
PropertyStorage()
|
|
: descriptors(nullptr)
|
|
{
|
|
}
|
|
|
|
~PropertyStorage() { }
|
|
|
|
GC::Ptr<DescriptorArray> descriptors;
|
|
PropertyTablePtr property_table;
|
|
};
|
|
|
|
union ForwardTransitions {
|
|
ForwardTransitions() { }
|
|
~ForwardTransitions() { }
|
|
|
|
PropertyKey property_key;
|
|
ForwardTransitionMapPtr map;
|
|
};
|
|
|
|
bool m_dictionary : 1 { false };
|
|
bool m_has_parameter_map : 1 { false };
|
|
ForwardTransitionStorage m_forward_transition_storage : 2 { ForwardTransitionStorage::Empty };
|
|
u8 m_single_forward_transition_attributes { 0 };
|
|
|
|
GC::Ref<Realm> m_realm;
|
|
|
|
PropertyStorage m_property_storage;
|
|
|
|
ForwardTransitions m_forward_transitions;
|
|
ForwardTransitionTarget m_single_forward_transition;
|
|
OwnPtr<HashMap<GC::Ptr<Object>, GC::Weak<Shape>>> m_prototype_transitions;
|
|
OwnPtr<HashMap<PropertyKey, GC::Weak<Shape>>> m_delete_transitions;
|
|
GC::Ptr<Object> m_prototype;
|
|
|
|
// A non-null validity cell marks this as a prototype shape. Child shape references only exist for prototype shapes.
|
|
GC::Ptr<PrototypeChainValidity> m_prototype_chain_validity;
|
|
OwnPtr<Vector<GC::Weak<Shape>>> m_child_prototype_shapes;
|
|
|
|
u32 m_property_count { 0 };
|
|
u32 m_dictionary_generation { 0 };
|
|
};
|
|
|
|
#if !defined(AK_OS_WINDOWS)
|
|
static_assert(sizeof(Shape) == 96, "Keep the size of JS::Shape down!");
|
|
#endif
|
|
|
|
}
|
|
|
|
template<>
|
|
struct AK::Traits<JS::TransitionKey> : public DefaultTraits<JS::TransitionKey> {
|
|
static unsigned hash(JS::TransitionKey const& key)
|
|
{
|
|
return pair_int_hash(key.attributes.bits(), Traits<JS::PropertyKey>::hash(key.property_key));
|
|
}
|
|
};
|