mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-06-08 05:27:14 +09:00
LibJS: Skip prototype chain lookup in internal_set()
for arrays
...when Array.prototype and Object.prototype are intact. If `internal_set()` is called on an array exotic object with a numeric PropertyKey, and: - the prototype chain has not been modified (i.e., there are no getters or setters for indexed properties), and - the array is not the target of a Proxy object, then we can directly store the value in the receiver's indexed properties, without checking whether it already exists somewhere in the prototype chain. 1.7x improvement on the following program: ```js function f() { let a = []; let i = 0; while (i < 10_000_000) { a.push(i); i++; } } f(); ```
This commit is contained in:
parent
6404f6db57
commit
bd6750aaa5
Notes:
github-actions[bot]
2025-05-23 12:52:28 +00:00
Author: https://github.com/kalenikaliaksandr
Commit: bd6750aaa5
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/4846
9 changed files with 133 additions and 8 deletions
|
@ -35,7 +35,7 @@ ThrowCompletionOr<GC::Ref<Array>> Array::create(Realm& realm, u64 length, Object
|
|||
// 3. Let A be MakeBasicObject(« [[Prototype]], [[Extensible]] »).
|
||||
// 4. Set A.[[Prototype]] to proto.
|
||||
// 5. Set A.[[DefineOwnProperty]] as specified in 10.4.2.1.
|
||||
auto array = realm.create<Array>(*prototype);
|
||||
auto array = realm.create<Array>(realm, *prototype);
|
||||
|
||||
// 6. Perform ! OrdinaryDefineOwnProperty(A, "length", PropertyDescriptor { [[Value]]: 𝔽(length), [[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: false }).
|
||||
MUST(array->internal_define_own_property(vm.names.length, { .value = Value(length), .writable = true, .enumerable = false, .configurable = false }));
|
||||
|
@ -63,12 +63,19 @@ GC::Ref<Array> Array::create_from(Realm& realm, ReadonlySpan<Value> elements)
|
|||
return array;
|
||||
}
|
||||
|
||||
Array::Array(Object& prototype)
|
||||
Array::Array(Realm& realm, Object& prototype)
|
||||
: Object(ConstructWithPrototypeTag::Tag, prototype)
|
||||
, m_realm(realm)
|
||||
{
|
||||
m_has_magical_length_property = true;
|
||||
}
|
||||
|
||||
void Array::visit_edges(Cell::Visitor& visitor)
|
||||
{
|
||||
Base::visit_edges(visitor);
|
||||
visitor.visit(m_realm);
|
||||
}
|
||||
|
||||
// 10.4.2.4 ArraySetLength ( A, Desc ), https://tc39.es/ecma262/#sec-arraysetlength
|
||||
ThrowCompletionOr<bool> Array::set_length(PropertyDescriptor const& property_descriptor)
|
||||
{
|
||||
|
@ -276,6 +283,72 @@ ThrowCompletionOr<Optional<PropertyDescriptor>> Array::internal_get_own_property
|
|||
return Object::internal_get_own_property(property_key);
|
||||
}
|
||||
|
||||
ThrowCompletionOr<bool> Array::internal_set(PropertyKey const& property_key, Value value, Value receiver, CacheablePropertyMetadata* cacheable_metadata, PropertyLookupPhase phase)
|
||||
{
|
||||
auto& vm = this->vm();
|
||||
|
||||
auto default_prototype_chain_intact = [&] {
|
||||
auto const& intrinsics = m_realm->intrinsics();
|
||||
auto* array_prototype = shape().prototype();
|
||||
if (!array_prototype)
|
||||
return false;
|
||||
if (!array_prototype->indexed_properties().is_empty())
|
||||
return false;
|
||||
auto& array_prototype_shape = shape().prototype()->shape();
|
||||
if (intrinsics.default_array_prototype_shape().ptr() != &array_prototype_shape)
|
||||
return false;
|
||||
|
||||
auto* object_prototype = array_prototype_shape.prototype();
|
||||
if (!object_prototype)
|
||||
return false;
|
||||
if (!object_prototype->indexed_properties().is_empty())
|
||||
return false;
|
||||
auto& object_prototype_shape = array_prototype_shape.prototype()->shape();
|
||||
if (intrinsics.default_object_prototype_shape().ptr() != &object_prototype_shape)
|
||||
return false;
|
||||
if (object_prototype_shape.prototype())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
VERIFY(receiver.is_object());
|
||||
auto& receiver_object = receiver.as_object();
|
||||
|
||||
// Fast path for arrays with intact prototype chain
|
||||
if (&receiver_object == this && !m_is_proxy_target && default_prototype_chain_intact()) {
|
||||
if (property_key.is_number()) {
|
||||
auto index = property_key.as_number();
|
||||
auto property_descriptor = TRY(internal_get_own_property(property_key));
|
||||
if (!property_descriptor.has_value()) {
|
||||
if (!TRY(is_extensible()))
|
||||
return false;
|
||||
PropertyAttributes attributes;
|
||||
attributes.set_writable(true);
|
||||
attributes.set_enumerable(true);
|
||||
attributes.set_configurable(true);
|
||||
indexed_properties().put(index, value, attributes);
|
||||
return true;
|
||||
}
|
||||
if (property_descriptor->is_data_descriptor()) {
|
||||
if (property_descriptor->writable.has_value() && !*property_descriptor->writable)
|
||||
return false;
|
||||
auto attributes = property_descriptor->attributes();
|
||||
indexed_properties().put(index, value, attributes);
|
||||
return true;
|
||||
}
|
||||
} else if (property_key == vm.names.length) {
|
||||
auto property_descriptor = TRY(internal_get_own_property(property_key));
|
||||
if (property_descriptor->writable.has_value() && !*property_descriptor->writable)
|
||||
return false;
|
||||
property_descriptor->value = value;
|
||||
return TRY(set_length(*property_descriptor));
|
||||
}
|
||||
}
|
||||
|
||||
return Object::internal_set(property_key, value, receiver, cacheable_metadata, phase);
|
||||
}
|
||||
|
||||
// 10.4.2.1 [[DefineOwnProperty]] ( P, Desc ), https://tc39.es/ecma262/#sec-array-exotic-objects-defineownproperty-p-desc
|
||||
ThrowCompletionOr<bool> Array::internal_define_own_property(PropertyKey const& property_key, PropertyDescriptor const& property_descriptor, Optional<PropertyDescriptor>* precomputed_get_own_property)
|
||||
{
|
||||
|
|
|
@ -48,21 +48,28 @@ public:
|
|||
virtual ~Array() override = default;
|
||||
|
||||
virtual ThrowCompletionOr<Optional<PropertyDescriptor>> internal_get_own_property(PropertyKey const&) const override final;
|
||||
virtual ThrowCompletionOr<bool> internal_set(PropertyKey const&, Value value, Value receiver, CacheablePropertyMetadata*, PropertyLookupPhase) override;
|
||||
virtual ThrowCompletionOr<bool> internal_define_own_property(PropertyKey const&, PropertyDescriptor const&, Optional<PropertyDescriptor>* precomputed_get_own_property = nullptr) override final;
|
||||
virtual ThrowCompletionOr<bool> internal_delete(PropertyKey const&) override;
|
||||
virtual ThrowCompletionOr<GC::RootVector<Value>> internal_own_property_keys() const override final;
|
||||
|
||||
[[nodiscard]] bool length_is_writable() const { return m_length_writable; }
|
||||
|
||||
void set_is_proxy_target(bool is_proxy_target) { m_is_proxy_target = is_proxy_target; }
|
||||
|
||||
virtual void visit_edges(Cell::Visitor& visitor) override;
|
||||
|
||||
protected:
|
||||
explicit Array(Object& prototype);
|
||||
explicit Array(Realm& realm, Object& prototype);
|
||||
|
||||
private:
|
||||
virtual bool is_array_exotic_object() const final { return true; }
|
||||
|
||||
ThrowCompletionOr<bool> set_length(PropertyDescriptor const&);
|
||||
|
||||
GC::Ref<Realm> m_realm;
|
||||
bool m_length_writable { true };
|
||||
bool m_is_proxy_target { false };
|
||||
};
|
||||
|
||||
template<>
|
||||
|
|
|
@ -32,7 +32,7 @@ GC_DEFINE_ALLOCATOR(ArrayPrototype);
|
|||
static HashTable<GC::Ref<Object>> s_array_join_seen_objects;
|
||||
|
||||
ArrayPrototype::ArrayPrototype(Realm& realm)
|
||||
: Array(realm.intrinsics().object_prototype())
|
||||
: Array(realm, realm.intrinsics().object_prototype())
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -316,6 +316,13 @@ void Intrinsics::initialize_intrinsics(Realm& realm)
|
|||
m_json_parse_function = &json_object()->get_without_side_effects(vm.names.parse).as_function();
|
||||
m_json_stringify_function = &json_object()->get_without_side_effects(vm.names.stringify).as_function();
|
||||
m_object_prototype_to_string_function = &object_prototype()->get_without_side_effects(vm.names.toString).as_function();
|
||||
|
||||
array_prototype()->convert_to_prototype_if_needed();
|
||||
m_default_array_prototype_shape = array_prototype()->shape();
|
||||
m_default_object_prototype_shape = object_prototype()->shape();
|
||||
|
||||
VERIFY(array_prototype()->indexed_properties().is_empty());
|
||||
VERIFY(object_prototype()->indexed_properties().is_empty());
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
|
@ -416,6 +423,8 @@ void Intrinsics::visit_edges(Visitor& visitor)
|
|||
visitor.visit(m_native_function_shape);
|
||||
visitor.visit(m_unmapped_arguments_object_shape);
|
||||
visitor.visit(m_mapped_arguments_object_shape);
|
||||
visitor.visit(m_default_array_prototype_shape);
|
||||
visitor.visit(m_default_object_prototype_shape);
|
||||
visitor.visit(m_proxy_constructor);
|
||||
visitor.visit(m_async_from_sync_iterator_prototype);
|
||||
visitor.visit(m_async_generator_prototype);
|
||||
|
|
|
@ -50,6 +50,9 @@ public:
|
|||
[[nodiscard]] u32 mapped_arguments_object_well_known_symbol_iterator_offset() const { return m_mapped_arguments_object_well_known_symbol_iterator_offset; }
|
||||
[[nodiscard]] u32 mapped_arguments_object_callee_offset() const { return m_mapped_arguments_object_callee_offset; }
|
||||
|
||||
[[nodiscard]] GC::Ref<Shape> default_array_prototype_shape() const { return *m_default_array_prototype_shape; }
|
||||
[[nodiscard]] GC::Ref<Shape> default_object_prototype_shape() const { return *m_default_object_prototype_shape; }
|
||||
|
||||
[[nodiscard]] GC::Ref<Accessor> throw_type_error_accessor() { return *m_throw_type_error_accessor; }
|
||||
|
||||
// Not included in JS_ENUMERATE_NATIVE_OBJECTS due to missing distinct prototype
|
||||
|
@ -179,6 +182,9 @@ private:
|
|||
u32 m_mapped_arguments_object_well_known_symbol_iterator_offset { 0 };
|
||||
u32 m_mapped_arguments_object_callee_offset { 0 };
|
||||
|
||||
GC::Ptr<Shape> m_default_array_prototype_shape;
|
||||
GC::Ptr<Shape> m_default_object_prototype_shape;
|
||||
|
||||
GC::Ptr<Accessor> m_throw_type_error_accessor;
|
||||
|
||||
// Not included in JS_ENUMERATE_NATIVE_OBJECTS due to missing distinct prototype
|
||||
|
|
|
@ -46,6 +46,10 @@ ProxyObject::ProxyObject(Object& target, Object& handler, Object& prototype)
|
|||
, m_target(target)
|
||||
, m_handler(handler)
|
||||
{
|
||||
if (target.is_array_exotic_object()) {
|
||||
auto& array = static_cast<Array&>(target);
|
||||
array.set_is_proxy_target(true);
|
||||
}
|
||||
}
|
||||
|
||||
static Value property_key_to_value(VM& vm, PropertyKey const& property_key)
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
test("proxy traps should be invoked in the correct order", () => {
|
||||
var log = [];
|
||||
var target = [];
|
||||
var proxy = new Proxy(
|
||||
target,
|
||||
new Proxy(
|
||||
{},
|
||||
{
|
||||
get(t, pk, r) {
|
||||
log.push(pk);
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
proxy.push(1);
|
||||
|
||||
expect(log.length, 8);
|
||||
expect(log[0]).toBe("get");
|
||||
expect(log[1]).toBe("get");
|
||||
expect(log[2]).toBe("set");
|
||||
expect(log[3]).toBe("getOwnPropertyDescriptor");
|
||||
expect(log[4]).toBe("defineProperty");
|
||||
expect(log[5]).toBe("set");
|
||||
expect(log[6]).toBe("getOwnPropertyDescriptor");
|
||||
expect(log[7]).toBe("defineProperty");
|
||||
});
|
|
@ -14,11 +14,11 @@ GC_DEFINE_ALLOCATOR(ObservableArray);
|
|||
GC::Ref<ObservableArray> ObservableArray::create(JS::Realm& realm)
|
||||
{
|
||||
auto prototype = realm.intrinsics().array_prototype();
|
||||
return realm.create<ObservableArray>(prototype);
|
||||
return realm.create<ObservableArray>(realm, prototype);
|
||||
}
|
||||
|
||||
ObservableArray::ObservableArray(Object& prototype)
|
||||
: JS::Array(prototype)
|
||||
ObservableArray::ObservableArray(JS::Realm& realm, Object& prototype)
|
||||
: JS::Array(realm, prototype)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
explicit ObservableArray(Object& prototype);
|
||||
explicit ObservableArray(JS::Realm&, Object& prototype);
|
||||
|
||||
virtual void visit_edges(JS::Cell::Visitor&) override;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue