From 12f9f3d9ef3c7293417f7eaf235d547d2a5f4d49 Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Thu, 24 Nov 2022 09:41:24 -0500 Subject: [PATCH] LibJS: Support instrinsic Object properties with deferred evaluation For performance, it is desirable to defer evaluation of intrinsics that are stored on the GlobalObject for every created Realm. To support this, Object now maintains a global storage map to store lambdas that will return the associated intrinsic when evaluated. Once accessed, the instrinsic is moved from this global map to normal Object storage. To prevent this flow from becoming observable, when a deferred intrinsic is stored, we still place an empty object in the normal Object storage. This is so we still create the metadata for the object, and in doing so, can preserve insertion order of the Object storage. Otherwise, this will be observable by way of Object.getOwnPropertyDescriptors. --- Userland/Libraries/LibJS/Runtime/Object.cpp | 49 +++++++++++++++++++++ Userland/Libraries/LibJS/Runtime/Object.h | 5 ++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/Userland/Libraries/LibJS/Runtime/Object.cpp b/Userland/Libraries/LibJS/Runtime/Object.cpp index 99076d57735..6b8d18acc1b 100644 --- a/Userland/Libraries/LibJS/Runtime/Object.cpp +++ b/Userland/Libraries/LibJS/Runtime/Object.cpp @@ -24,6 +24,8 @@ namespace JS { +static HashMap> s_intrinsics; + // 10.1.12 OrdinaryObjectCreate ( proto [ , additionalInternalSlotsList ] ), https://tc39.es/ecma262/#sec-ordinaryobjectcreate Object* Object::create(Realm& realm, Object* prototype) { @@ -67,6 +69,11 @@ Object::Object(Shape& shape) m_storage.resize(shape.property_count()); } +Object::~Object() +{ + s_intrinsics.remove(this); +} + void Object::initialize(Realm&) { } @@ -970,6 +977,23 @@ ThrowCompletionOr Object::set_immutable_prototype(Object* prototype) return false; } +static Optional find_intrinsic_accessor(Object const* object, PropertyKey const& property_key) +{ + if (!property_key.is_string()) + return {}; + + auto intrinsics = s_intrinsics.find(object); + if (intrinsics == s_intrinsics.end()) + return {}; + + auto accessor = intrinsics->value.find(property_key.as_string()); + if (accessor == intrinsics->value.end()) + return {}; + + intrinsics->value.remove(accessor); + return move(accessor->value); +} + Optional Object::storage_get(PropertyKey const& property_key) const { VERIFY(property_key.is_valid()); @@ -987,9 +1011,14 @@ Optional Object::storage_get(PropertyKey const& property_key auto metadata = shape().lookup(property_key.to_string_or_symbol()); if (!metadata.has_value()) return {}; + + if (auto accessor = find_intrinsic_accessor(this, property_key); accessor.has_value()) + const_cast(*this).m_storage[metadata->offset] = (*accessor)(shape().realm()); + value = m_storage[metadata->offset]; attributes = metadata->attributes; } + return ValueAndAttributes { .value = value, .attributes = attributes }; } @@ -1013,6 +1042,11 @@ void Object::storage_set(PropertyKey const& property_key, ValueAndAttributes con return; } + if (property_key.is_string()) { + if (auto intrinsics = s_intrinsics.find(this); intrinsics != s_intrinsics.end()) + intrinsics->value.remove(property_key.as_string()); + } + auto property_key_string_or_symbol = property_key.to_string_or_symbol(); auto metadata = shape().lookup(property_key_string_or_symbol); @@ -1050,6 +1084,11 @@ void Object::storage_delete(PropertyKey const& property_key) if (property_key.is_number()) return m_indexed_properties.remove(property_key.as_number()); + if (property_key.is_string()) { + if (auto intrinsics = s_intrinsics.find(this); intrinsics != s_intrinsics.end()) + intrinsics->value.remove(property_key.as_string()); + } + auto metadata = shape().lookup(property_key.to_string_or_symbol()); VERIFY(metadata.has_value()); @@ -1098,6 +1137,16 @@ void Object::define_direct_accessor(PropertyKey const& property_key, FunctionObj } } +void Object::define_intrinsic_accessor(PropertyKey const& property_key, PropertyAttributes attributes, IntrinsicAccessor accessor) +{ + VERIFY(property_key.is_string()); + + storage_set(property_key, { {}, attributes }); + + auto& intrinsics = s_intrinsics.ensure(this); + intrinsics.set(property_key.as_string(), move(accessor)); +} + void Object::ensure_shape_is_unique() { if (shape().is_unique()) diff --git a/Userland/Libraries/LibJS/Runtime/Object.h b/Userland/Libraries/LibJS/Runtime/Object.h index 93a29a02565..1f16bb6021c 100644 --- a/Userland/Libraries/LibJS/Runtime/Object.h +++ b/Userland/Libraries/LibJS/Runtime/Object.h @@ -47,7 +47,7 @@ public: static Object* create(Realm&, Object* prototype); virtual void initialize(Realm&) override; - virtual ~Object() = default; + virtual ~Object(); enum class PropertyKind { Key, @@ -151,6 +151,9 @@ public: void define_direct_property(PropertyKey const& property_key, Value value, PropertyAttributes attributes) { storage_set(property_key, { value, attributes }); }; void define_direct_accessor(PropertyKey const&, FunctionObject* getter, FunctionObject* setter, PropertyAttributes attributes); + using IntrinsicAccessor = Value (*)(Realm&); + virtual void define_intrinsic_accessor(PropertyKey const&, PropertyAttributes attributes, IntrinsicAccessor accessor); + void define_native_function(Realm&, PropertyKey const&, SafeFunction(VM&)>, i32 length, PropertyAttributes attributes); void define_native_accessor(Realm&, PropertyKey const&, SafeFunction(VM&)> getter, SafeFunction(VM&)> setter, PropertyAttributes attributes);