Skip to content

Commit 183c847

Browse files
committed
LibJS: Cache PutById to setters in the prototype chain
This is *extremely* common on the web, but barely shows up at all in JavaScript benchmarks. A typical example is setting Element.innerHTML on a HTMLDivElement. HTMLDivElement doesn't have innerHTML, so it has to travel up the prototype chain until it finds it. Before this change, we didn't cache this at all, so we had to travel the prototype chain every time a setter like this was used. We now use the same mechanism we already had for GetBydId and cache PutById setter accesses in the prototype chain as well. 1.74x speedup on MicroBench/setter-in-prototype-chain.js
1 parent 71665fa commit 183c847

18 files changed

+93
-39
lines changed

Libraries/LibJS/Bytecode/Interpreter.cpp

Lines changed: 42 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1224,20 +1224,54 @@ inline ThrowCompletionOr<void> put_by_property_key(VM& vm, Value base, Value thi
12241224
break;
12251225
}
12261226
case Op::PropertyKind::KeyValue: {
1227-
if (cache && cache->shape == &object->shape()) {
1228-
object->put_direct(*cache->property_offset, value);
1229-
return {};
1227+
if (cache) {
1228+
if (cache->prototype) {
1229+
// OPTIMIZATION: If the prototype chain hasn't been mutated in a way that would invalidate the cache, we can use it.
1230+
bool can_use_cache = [&]() -> bool {
1231+
if (&object->shape() != cache->shape)
1232+
return false;
1233+
if (!cache->prototype_chain_validity)
1234+
return false;
1235+
if (!cache->prototype_chain_validity->is_valid())
1236+
return false;
1237+
return true;
1238+
}();
1239+
if (can_use_cache) {
1240+
auto value_in_prototype = cache->prototype->get_direct(cache->property_offset.value());
1241+
if (value_in_prototype.is_accessor()) {
1242+
TRY(call(vm, value_in_prototype.as_accessor().setter(), this_value, value));
1243+
return {};
1244+
}
1245+
}
1246+
} else if (cache->shape == &object->shape()) {
1247+
auto value_in_object = object->get_direct(cache->property_offset.value());
1248+
if (value_in_object.is_accessor()) {
1249+
TRY(call(vm, value_in_object.as_accessor().setter(), this_value, value));
1250+
} else {
1251+
object->put_direct(*cache->property_offset, value);
1252+
}
1253+
return {};
1254+
}
12301255
}
12311256

12321257
CacheablePropertyMetadata cacheable_metadata;
12331258
bool succeeded = TRY(object->internal_set(name, value, this_value, &cacheable_metadata));
12341259

1235-
if (succeeded && cache && cacheable_metadata.type == CacheablePropertyMetadata::Type::OwnProperty) {
1236-
cache->shape = object->shape();
1237-
cache->property_offset = cacheable_metadata.property_offset.value();
1260+
if (succeeded && cache) {
1261+
if (cacheable_metadata.type == CacheablePropertyMetadata::Type::OwnProperty) {
1262+
*cache = {};
1263+
cache->shape = object->shape();
1264+
cache->property_offset = cacheable_metadata.property_offset.value();
1265+
} else if (cacheable_metadata.type == CacheablePropertyMetadata::Type::InPrototypeChain) {
1266+
*cache = {};
1267+
cache->shape = object->shape();
1268+
cache->property_offset = cacheable_metadata.property_offset.value();
1269+
cache->prototype = *cacheable_metadata.prototype;
1270+
cache->prototype_chain_validity = *cacheable_metadata.prototype->shape().prototype_chain_validity();
1271+
}
12381272
}
12391273

1240-
if (!succeeded && vm.in_strict_mode()) {
1274+
if (!succeeded && vm.in_strict_mode()) [[unlikely]] {
12411275
if (base.is_object())
12421276
return vm.throw_completion<TypeError>(ErrorType::ReferenceNullishSetProperty, name, base.to_string_without_side_effects());
12431277
return vm.throw_completion<TypeError>(ErrorType::ReferencePrimitiveSetProperty, name, base.typeof_(vm)->utf8_string(), base.to_string_without_side_effects());
@@ -2318,7 +2352,7 @@ ThrowCompletionOr<void> SetGlobal::execute_impl(Bytecode::Interpreter& interpret
23182352
if (&shape == cache.shape) {
23192353
auto value = binding_object.get_direct(cache.property_offset.value());
23202354
if (value.is_accessor())
2321-
TRY(call(vm, value.as_accessor().setter(), js_undefined(), value));
2355+
TRY(call(vm, value.as_accessor().setter(), &binding_object, src));
23222356
else
23232357
binding_object.put_direct(cache.property_offset.value(), src);
23242358
return {};

Libraries/LibJS/Runtime/ArgumentsObject.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ ThrowCompletionOr<Value> ArgumentsObject::internal_get(PropertyKey const& proper
5454
}
5555

5656
// 10.4.4.4 [[Set]] ( P, V, Receiver ), https://tc39.es/ecma262/#sec-arguments-exotic-objects-set-p-v-receiver
57-
ThrowCompletionOr<bool> ArgumentsObject::internal_set(PropertyKey const& property_key, Value value, Value receiver, CacheablePropertyMetadata*)
57+
ThrowCompletionOr<bool> ArgumentsObject::internal_set(PropertyKey const& property_key, Value value, Value receiver, CacheablePropertyMetadata*, PropertyLookupPhase)
5858
{
5959
bool is_mapped = false;
6060

Libraries/LibJS/Runtime/ArgumentsObject.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class ArgumentsObject final : public Object {
2525
virtual ThrowCompletionOr<Optional<PropertyDescriptor>> internal_get_own_property(PropertyKey const&) const override;
2626
virtual ThrowCompletionOr<bool> internal_define_own_property(PropertyKey const&, PropertyDescriptor const&, Optional<PropertyDescriptor>* precomputed_get_own_property = nullptr) override;
2727
virtual ThrowCompletionOr<Value> internal_get(PropertyKey const&, Value receiver, CacheablePropertyMetadata*, PropertyLookupPhase) const override;
28-
virtual ThrowCompletionOr<bool> internal_set(PropertyKey const&, Value value, Value receiver, CacheablePropertyMetadata*) override;
28+
virtual ThrowCompletionOr<bool> internal_set(PropertyKey const&, Value value, Value receiver, CacheablePropertyMetadata*, PropertyLookupPhase) override;
2929
virtual ThrowCompletionOr<bool> internal_delete(PropertyKey const&) override;
3030

3131
// [[ParameterMap]]

Libraries/LibJS/Runtime/ModuleNamespaceObject.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ ThrowCompletionOr<Value> ModuleNamespaceObject::internal_get(PropertyKey const&
185185
}
186186

187187
// 10.4.6.9 [[Set]] ( P, V, Receiver ), https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-set-p-v-receiver
188-
ThrowCompletionOr<bool> ModuleNamespaceObject::internal_set(PropertyKey const&, Value, Value, CacheablePropertyMetadata*)
188+
ThrowCompletionOr<bool> ModuleNamespaceObject::internal_set(PropertyKey const&, Value, Value, CacheablePropertyMetadata*, PropertyLookupPhase)
189189
{
190190
// 1. Return false.
191191
return false;

Libraries/LibJS/Runtime/ModuleNamespaceObject.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ class ModuleNamespaceObject final : public Object {
2727
virtual ThrowCompletionOr<bool> internal_define_own_property(PropertyKey const&, PropertyDescriptor const&, Optional<PropertyDescriptor>* precomputed_get_own_property = nullptr) override;
2828
virtual ThrowCompletionOr<bool> internal_has_property(PropertyKey const&) const override;
2929
virtual ThrowCompletionOr<Value> internal_get(PropertyKey const&, Value receiver, CacheablePropertyMetadata* = nullptr, PropertyLookupPhase = PropertyLookupPhase::OwnProperty) const override;
30-
virtual ThrowCompletionOr<bool> internal_set(PropertyKey const&, Value value, Value receiver, CacheablePropertyMetadata*) override;
30+
virtual ThrowCompletionOr<bool> internal_set(PropertyKey const&, Value value, Value receiver, CacheablePropertyMetadata*, PropertyLookupPhase) override;
3131
virtual ThrowCompletionOr<bool> internal_delete(PropertyKey const&) override;
3232
virtual ThrowCompletionOr<GC::RootVector<Value>> internal_own_property_keys() const override;
3333
virtual void initialize(Realm&) override;

Libraries/LibJS/Runtime/Object.cpp

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -952,7 +952,7 @@ ThrowCompletionOr<Value> Object::internal_get(PropertyKey const& property_key, V
952952

953953
// 10.1.9 [[Set]] ( P, V, Receiver ), https://tc39.es/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-set-p-v-receiver
954954
// 10.1.9.1 OrdinarySet ( O, P, V, Receiver ), https://tc39.es/ecma262/#sec-ordinaryset
955-
ThrowCompletionOr<bool> Object::internal_set(PropertyKey const& property_key, Value value, Value receiver, CacheablePropertyMetadata* cacheable_metadata)
955+
ThrowCompletionOr<bool> Object::internal_set(PropertyKey const& property_key, Value value, Value receiver, CacheablePropertyMetadata* cacheable_metadata, PropertyLookupPhase phase)
956956
{
957957
VERIFY(!value.is_special_empty_value());
958958
VERIFY(!receiver.is_special_empty_value());
@@ -961,11 +961,11 @@ ThrowCompletionOr<bool> Object::internal_set(PropertyKey const& property_key, Va
961961
auto own_descriptor = TRY(internal_get_own_property(property_key));
962962

963963
// 3. Return ? OrdinarySetWithOwnDescriptor(O, P, V, Receiver, ownDesc).
964-
return ordinary_set_with_own_descriptor(property_key, value, receiver, own_descriptor, cacheable_metadata);
964+
return ordinary_set_with_own_descriptor(property_key, value, receiver, own_descriptor, cacheable_metadata, phase);
965965
}
966966

967967
// 10.1.9.2 OrdinarySetWithOwnDescriptor ( O, P, V, Receiver, ownDesc ), https://tc39.es/ecma262/#sec-ordinarysetwithowndescriptor
968-
ThrowCompletionOr<bool> Object::ordinary_set_with_own_descriptor(PropertyKey const& property_key, Value value, Value receiver, Optional<PropertyDescriptor> own_descriptor, CacheablePropertyMetadata* cacheable_metadata)
968+
ThrowCompletionOr<bool> Object::ordinary_set_with_own_descriptor(PropertyKey const& property_key, Value value, Value receiver, Optional<PropertyDescriptor> own_descriptor, CacheablePropertyMetadata* cacheable_metadata, PropertyLookupPhase phase)
969969
{
970970
VERIFY(!value.is_special_empty_value());
971971
VERIFY(!receiver.is_special_empty_value());
@@ -980,7 +980,7 @@ ThrowCompletionOr<bool> Object::ordinary_set_with_own_descriptor(PropertyKey con
980980
// b. If parent is not null, then
981981
if (parent) {
982982
// i. Return ? parent.[[Set]](P, V, Receiver).
983-
return TRY(parent->internal_set(property_key, value, receiver));
983+
return TRY(parent->internal_set(property_key, value, receiver, cacheable_metadata, PropertyLookupPhase::PrototypeChain));
984984
}
985985
// c. Else,
986986
else {
@@ -994,6 +994,27 @@ ThrowCompletionOr<bool> Object::ordinary_set_with_own_descriptor(PropertyKey con
994994
}
995995
}
996996

997+
auto update_inline_cache = [&] {
998+
// Non-standard: If the caller has requested cacheable metadata and the property is an own property, fill it in.
999+
if (!cacheable_metadata || !own_descriptor->property_offset.has_value() || !shape().is_cacheable())
1000+
return;
1001+
if (phase == PropertyLookupPhase::OwnProperty) {
1002+
*cacheable_metadata = CacheablePropertyMetadata {
1003+
.type = CacheablePropertyMetadata::Type::OwnProperty,
1004+
.property_offset = own_descriptor->property_offset.value(),
1005+
.prototype = nullptr,
1006+
};
1007+
} else if (phase == PropertyLookupPhase::PrototypeChain) {
1008+
VERIFY(shape().is_prototype_shape());
1009+
VERIFY(shape().prototype_chain_validity()->is_valid());
1010+
*cacheable_metadata = CacheablePropertyMetadata {
1011+
.type = CacheablePropertyMetadata::Type::InPrototypeChain,
1012+
.property_offset = own_descriptor->property_offset.value(),
1013+
.prototype = this,
1014+
};
1015+
}
1016+
};
1017+
9971018
// 2. If IsDataDescriptor(ownDesc) is true, then
9981019
if (own_descriptor->is_data_descriptor()) {
9991020
// a. If ownDesc.[[Writable]] is false, return false.
@@ -1022,13 +1043,10 @@ ThrowCompletionOr<bool> Object::ordinary_set_with_own_descriptor(PropertyKey con
10221043
// iii. Let valueDesc be the PropertyDescriptor { [[Value]]: V }.
10231044
auto value_descriptor = PropertyDescriptor { .value = value };
10241045

1025-
if (cacheable_metadata && own_descriptor.has_value() && own_descriptor->property_offset.has_value() && shape().is_cacheable()) {
1026-
*cacheable_metadata = CacheablePropertyMetadata {
1027-
.type = CacheablePropertyMetadata::Type::OwnProperty,
1028-
.property_offset = own_descriptor->property_offset.value(),
1029-
.prototype = nullptr,
1030-
};
1031-
}
1046+
// NOTE: We don't cache non-setter properties in the prototype chain, as that's a weird
1047+
// use-case, and doesn't seem like something in need of optimization.
1048+
if (phase == PropertyLookupPhase::OwnProperty)
1049+
update_inline_cache();
10321050

10331051
// iv. Return ? Receiver.[[DefineOwnProperty]](P, valueDesc).
10341052
return TRY(receiver_object.internal_define_own_property(property_key, value_descriptor, &existing_descriptor));
@@ -1053,6 +1071,8 @@ ThrowCompletionOr<bool> Object::ordinary_set_with_own_descriptor(PropertyKey con
10531071
if (!setter)
10541072
return false;
10551073

1074+
update_inline_cache();
1075+
10561076
// 6. Perform ? Call(setter, Receiver, « V »).
10571077
(void)TRY(call(vm, *setter, receiver, value));
10581078

Libraries/LibJS/Runtime/Object.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ class Object : public Cell
145145
PrototypeChain,
146146
};
147147
virtual ThrowCompletionOr<Value> internal_get(PropertyKey const&, Value receiver, CacheablePropertyMetadata* = nullptr, PropertyLookupPhase = PropertyLookupPhase::OwnProperty) const;
148-
virtual ThrowCompletionOr<bool> internal_set(PropertyKey const&, Value value, Value receiver, CacheablePropertyMetadata* = nullptr);
148+
virtual ThrowCompletionOr<bool> internal_set(PropertyKey const&, Value value, Value receiver, CacheablePropertyMetadata* = nullptr, PropertyLookupPhase = PropertyLookupPhase::OwnProperty);
149149
virtual ThrowCompletionOr<bool> internal_delete(PropertyKey const&);
150150
virtual ThrowCompletionOr<GC::RootVector<Value>> internal_own_property_keys() const;
151151

@@ -155,7 +155,7 @@ class Object : public Cell
155155
// might not hold when property access behaves differently.
156156
bool may_interfere_with_indexed_property_access() const { return m_may_interfere_with_indexed_property_access; }
157157

158-
ThrowCompletionOr<bool> ordinary_set_with_own_descriptor(PropertyKey const&, Value, Value, Optional<PropertyDescriptor>, CacheablePropertyMetadata* = nullptr);
158+
ThrowCompletionOr<bool> ordinary_set_with_own_descriptor(PropertyKey const&, Value, Value, Optional<PropertyDescriptor>, CacheablePropertyMetadata* = nullptr, PropertyLookupPhase = PropertyLookupPhase::OwnProperty);
159159

160160
// 10.4.7 Immutable Prototype Exotic Objects, https://tc39.es/ecma262/#sec-immutable-prototype-exotic-objects
161161

Libraries/LibJS/Runtime/ProxyObject.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -535,7 +535,7 @@ ThrowCompletionOr<Value> ProxyObject::internal_get(PropertyKey const& property_k
535535
}
536536

537537
// 10.5.9 [[Set]] ( P, V, Receiver ), https://tc39.es/ecma262/#sec-proxy-object-internal-methods-and-internal-slots-set-p-v-receiver
538-
ThrowCompletionOr<bool> ProxyObject::internal_set(PropertyKey const& property_key, Value value, Value receiver, CacheablePropertyMetadata*)
538+
ThrowCompletionOr<bool> ProxyObject::internal_set(PropertyKey const& property_key, Value value, Value receiver, CacheablePropertyMetadata*, PropertyLookupPhase)
539539
{
540540
LIMIT_PROXY_RECURSION_DEPTH();
541541

Libraries/LibJS/Runtime/ProxyObject.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class ProxyObject final : public FunctionObject {
3939
virtual ThrowCompletionOr<bool> internal_define_own_property(PropertyKey const&, PropertyDescriptor const&, Optional<PropertyDescriptor>* precomputed_get_own_property = nullptr) override;
4040
virtual ThrowCompletionOr<bool> internal_has_property(PropertyKey const&) const override;
4141
virtual ThrowCompletionOr<Value> internal_get(PropertyKey const&, Value receiver, CacheablePropertyMetadata*, PropertyLookupPhase) const override;
42-
virtual ThrowCompletionOr<bool> internal_set(PropertyKey const&, Value value, Value receiver, CacheablePropertyMetadata*) override;
42+
virtual ThrowCompletionOr<bool> internal_set(PropertyKey const&, Value value, Value receiver, CacheablePropertyMetadata*, PropertyLookupPhase) override;
4343
virtual ThrowCompletionOr<bool> internal_delete(PropertyKey const&) override;
4444
virtual ThrowCompletionOr<GC::RootVector<Value>> internal_own_property_keys() const override;
4545
virtual ThrowCompletionOr<Value> internal_call(ExecutionContext&, Value this_argument) override;

Libraries/LibJS/Runtime/TypedArray.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ class TypedArray : public TypedArrayBase {
352352
}
353353

354354
// 10.4.5.6 [[Set]] ( P, V, Receiver ), https://tc39.es/ecma262/#sec-integer-indexed-exotic-objects-set-p-v-receiver
355-
virtual ThrowCompletionOr<bool> internal_set(PropertyKey const& property_key, Value value, Value receiver, CacheablePropertyMetadata*) override
355+
virtual ThrowCompletionOr<bool> internal_set(PropertyKey const& property_key, Value value, Value receiver, CacheablePropertyMetadata*, PropertyLookupPhase) override
356356
{
357357
VERIFY(!value.is_special_empty_value());
358358
VERIFY(!receiver.is_special_empty_value());

0 commit comments

Comments
 (0)