Skip to content

Commit 1dcea24

Browse files
committed
OS#18076647: Invalidate prototype inline caches for properties of objects added to the prototype chain
1 parent 2bd57e5 commit 1dcea24

File tree

12 files changed

+104
-44
lines changed

12 files changed

+104
-44
lines changed

lib/Runtime/Base/CrossSiteObject.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,8 @@ namespace Js
3333
virtual DescriptorFlags GetSetter(PropertyId propertyId, Var* setterValue, PropertyValueInfo* info, ScriptContext* requestContext) override;
3434
virtual DescriptorFlags GetSetter(JavascriptString* propertyNameString, Var* setterValue, PropertyValueInfo* info, ScriptContext* requestContext) override;
3535
virtual BOOL SetAccessors(PropertyId propertyId, Var getter, Var setter, PropertyOperationFlags flags) override;
36-
virtual void RemoveFromPrototype(ScriptContext * requestContext) override;
37-
virtual void AddToPrototype(ScriptContext * requestContext) override;
36+
virtual void RemoveFromPrototype(ScriptContext * requestContext, bool * allProtoCachesInvalidated) override;
37+
virtual void AddToPrototype(ScriptContext * requestContext, bool * allProtoCachesInvalidated) override;
3838
virtual void SetPrototype(RecyclableObject* newPrototype) override;
3939

4040
virtual BOOL IsCrossSiteObject() const override { return TRUE; }
@@ -227,15 +227,15 @@ namespace Js
227227
}
228228

229229
template <typename T>
230-
void CrossSiteObject<T>::RemoveFromPrototype(ScriptContext * requestContext)
230+
void CrossSiteObject<T>::RemoveFromPrototype(ScriptContext * requestContext, bool * allProtoCachesInvalidated)
231231
{
232-
__super::RemoveFromPrototype(this->GetScriptContext());
232+
__super::RemoveFromPrototype(this->GetScriptContext(), allProtoCachesInvalidated);
233233
}
234234

235235
template <typename T>
236-
void CrossSiteObject<T>::AddToPrototype(ScriptContext * requestContext)
236+
void CrossSiteObject<T>::AddToPrototype(ScriptContext * requestContext, bool * allProtoCachesInvalidated)
237237
{
238-
__super::AddToPrototype(this->GetScriptContext());
238+
__super::AddToPrototype(this->GetScriptContext(), allProtoCachesInvalidated);
239239
}
240240

241241
template <typename T>

lib/Runtime/Language/ModuleNamespace.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ namespace Js
7979
virtual BOOL GetDiagValueString(StringBuilder<ArenaAllocator>* stringBuilder, ScriptContext* requestContext) override;
8080
virtual BOOL GetDiagTypeString(StringBuilder<ArenaAllocator>* stringBuilder, ScriptContext* requestContext) override;
8181

82-
virtual void RemoveFromPrototype(ScriptContext * requestContext) override { Assert(false); }
83-
virtual void AddToPrototype(ScriptContext * requestContext) override { Assert(false); }
82+
virtual void RemoveFromPrototype(ScriptContext * requestContext, bool * allProtoCachesInvalidated) override { Assert(false); }
83+
virtual void AddToPrototype(ScriptContext * requestContext, bool * allProtoCachesInvalidated) override { Assert(false); }
8484
virtual void SetPrototype(RecyclableObject* newPrototype) override { Assert(false); return; }
8585

8686
private:

lib/Runtime/Library/JavascriptObject.cpp

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -227,10 +227,13 @@ using namespace Js;
227227

228228
if (isInvalidationOfInlineCacheNeeded)
229229
{
230+
bool allProtoCachesInvalidated = false;
231+
230232
// Notify old prototypes that they are being removed from a prototype chain. This triggers invalidating protocache, etc.
231-
JavascriptOperators::MapObjectAndPrototypes<true>(object->GetPrototype(), [=](RecyclableObject* obj)
233+
JavascriptOperators::MapObjectAndPrototypesUntil<true>(object->GetPrototype(), [&](RecyclableObject* obj)->bool
232234
{
233-
obj->RemoveFromPrototype(scriptContext);
235+
obj->RemoveFromPrototype(scriptContext, &allProtoCachesInvalidated);
236+
return allProtoCachesInvalidated;
234237
});
235238

236239
// Examine new prototype chain. If it brings in any special property, we need to invalidate related caches.
@@ -271,17 +274,20 @@ using namespace Js;
271274
object->GetLibrary()->GetTypesWithOnlyWritablePropertyProtoChainCache()->Clear();
272275
}
273276

274-
if (!objectAndPrototypeChainHasOnlyWritableDataProperties)
277+
if (!allProtoCachesInvalidated)
275278
{
276279
// Invalidate StoreField/PropertyGuards for any non-WritableData property in the new chain
277-
JavascriptOperators::MapObjectAndPrototypes<true>(newPrototype, [=](RecyclableObject* obj)
280+
JavascriptOperators::MapObjectAndPrototypesUntil<true>(newPrototype, [&](RecyclableObject* obj)->bool
278281
{
279-
if (!obj->HasOnlyWritableDataProperties())
280-
{
281-
obj->AddToPrototype(scriptContext);
282-
}
282+
obj->AddToPrototype(scriptContext, &allProtoCachesInvalidated);
283+
return allProtoCachesInvalidated;
283284
});
284285
}
286+
287+
JavascriptOperators::MapObjectAndPrototypesUntil<true>(object->GetPrototype(), [](RecyclableObject* obj)->bool
288+
{
289+
return obj->ClearProtoCachesWereInvalidated();
290+
});
285291
}
286292

287293
// Set to new prototype

lib/Runtime/Library/JavascriptProxy.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1507,12 +1507,12 @@ namespace Js
15071507
return nullptr;
15081508
}
15091509

1510-
void JavascriptProxy::RemoveFromPrototype(ScriptContext * requestContext)
1510+
void JavascriptProxy::RemoveFromPrototype(ScriptContext * requestContext, bool * allProtoCachesInvalidated)
15111511
{
15121512
Assert(FALSE);
15131513
}
15141514

1515-
void JavascriptProxy::AddToPrototype(ScriptContext * requestContext)
1515+
void JavascriptProxy::AddToPrototype(ScriptContext * requestContext, bool * allProtoCachesInvalidated)
15161516
{
15171517
Assert(FALSE);
15181518
}

lib/Runtime/Library/JavascriptProxy.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,8 @@ namespace Js
138138
virtual bool CanStorePropertyValueDirectly(PropertyId propertyId, bool allowLetConst) { Assert(false); return false; };
139139
#endif
140140

141-
virtual void RemoveFromPrototype(ScriptContext * requestContext) override;
142-
virtual void AddToPrototype(ScriptContext * requestContext) override;
141+
virtual void RemoveFromPrototype(ScriptContext * requestContext, bool * allProtoCachesInvalidated) override;
142+
virtual void AddToPrototype(ScriptContext * requestContext, bool * allProtoCachesInvalidated) override;
143143
virtual void SetPrototype(RecyclableObject* newPrototype) override;
144144

145145
BOOL SetPrototypeTrap(RecyclableObject* newPrototype, bool showThrow, ScriptContext * requestContext);

lib/Runtime/Types/DynamicObject.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -296,8 +296,9 @@ namespace Js
296296
virtual bool CanStorePropertyValueDirectly(PropertyId propertyId, bool allowLetConst) override;
297297
#endif
298298

299-
virtual void RemoveFromPrototype(ScriptContext * requestContext) override;
300-
virtual void AddToPrototype(ScriptContext * requestContext) override;
299+
virtual void RemoveFromPrototype(ScriptContext * requestContext, bool * allProtoCachesInvalidated) override;
300+
virtual void AddToPrototype(ScriptContext * requestContext, bool * allProtoCachesInvalidated) override;
301+
virtual bool ClearProtoCachesWereInvalidated() override;
301302
virtual void SetPrototype(RecyclableObject* newPrototype) override;
302303

303304
virtual BOOL IsCrossSiteObject() const { return FALSE; }

lib/Runtime/Types/DynamicType.cpp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -609,14 +609,19 @@ namespace Js
609609
}
610610
#endif
611611

612-
void DynamicObject::RemoveFromPrototype(ScriptContext * requestContext)
612+
bool DynamicObject::ClearProtoCachesWereInvalidated()
613613
{
614-
GetTypeHandler()->RemoveFromPrototype(this, requestContext);
614+
return GetTypeHandler()->ClearProtoCachesWereInvalidated();
615615
}
616616

617-
void DynamicObject::AddToPrototype(ScriptContext * requestContext)
617+
void DynamicObject::RemoveFromPrototype(ScriptContext * requestContext, bool * allProtoCachesInvalidated)
618618
{
619-
GetTypeHandler()->AddToPrototype(this, requestContext);
619+
GetTypeHandler()->RemoveFromPrototype(this, requestContext, allProtoCachesInvalidated);
620+
}
621+
622+
void DynamicObject::AddToPrototype(ScriptContext * requestContext, bool * allProtoCachesInvalidated)
623+
{
624+
GetTypeHandler()->AddToPrototype(this, requestContext, allProtoCachesInvalidated);
620625
}
621626

622627
void DynamicObject::SetPrototype(RecyclableObject* newPrototype)

lib/Runtime/Types/RecyclableObject.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -369,9 +369,10 @@ namespace Js {
369369
virtual bool CanStorePropertyValueDirectly(PropertyId propertyId, bool allowLetConst) { Assert(false); return false; };
370370
#endif
371371

372-
virtual void RemoveFromPrototype(ScriptContext * requestContext) { AssertMsg(false, "Shouldn't call this implementation."); }
373-
virtual void AddToPrototype(ScriptContext * requestContext) { AssertMsg(false, "Shouldn't call this implementation."); }
372+
virtual void RemoveFromPrototype(ScriptContext * requestContext, bool * allProtoCachesInvalidated) { AssertMsg(false, "Shouldn't call this implementation."); }
373+
virtual void AddToPrototype(ScriptContext * requestContext, bool * allProtoCachesInvalidated) { AssertMsg(false, "Shouldn't call this implementation."); }
374374
virtual void SetPrototype(RecyclableObject* newPrototype) { AssertMsg(false, "Shouldn't call this implementation."); }
375+
virtual bool ClearProtoCachesWereInvalidated() { AssertMsg(false, "Shouldn't call this implementation."); return false; }
375376

376377
virtual BOOL ToString(Js::Var* value, Js::ScriptContext* scriptContext) { AssertMsg(FALSE, "Do not use this function."); return false; }
377378

lib/Runtime/Types/TypeHandler.cpp

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ using namespace Js;
6464
flags(flags),
6565
propertyTypes(PropertyTypesWritableDataOnly | PropertyTypesReserved),
6666
offsetOfInlineSlots(offsetOfInlineSlots),
67-
unusedBytes(Js::AtomTag)
67+
unusedBytes(Js::AtomTag),
68+
protoCachesWereInvalidated(false)
6869
{
6970
Assert(!GetIsOrMayBecomeShared() || GetIsLocked());
7071
Assert(offsetOfInlineSlots != 0 || inlineSlotCapacity == 0);
@@ -263,7 +264,7 @@ using namespace Js;
263264
}
264265

265266
template<bool isStoreField>
266-
void DynamicTypeHandler::InvalidateInlineCachesForAllProperties(ScriptContext* requestContext)
267+
bool DynamicTypeHandler::InvalidateInlineCachesForAllProperties(ScriptContext* requestContext)
267268
{
268269
int count = GetPropertyCount();
269270
if (count < 128) // Invalidate a propertyId involves dictionary lookups. Only do this when the number is relatively small.
@@ -276,31 +277,51 @@ using namespace Js;
276277
isStoreField ? requestContext->InvalidateStoreFieldCaches(propertyId) : requestContext->InvalidateProtoCaches(propertyId);
277278
}
278279
}
280+
return false;
279281
}
280282
else
281283
{
282284
isStoreField ? requestContext->InvalidateAllStoreFieldCaches() : requestContext->InvalidateAllProtoCaches();
285+
return true;
283286
}
284287
}
285288

286-
void DynamicTypeHandler::InvalidateProtoCachesForAllProperties(ScriptContext* requestContext)
289+
bool DynamicTypeHandler::InvalidateProtoCachesForAllProperties(ScriptContext* requestContext)
290+
{
291+
bool result = InvalidateInlineCachesForAllProperties<false>(requestContext);
292+
this->SetProtoCachesWereInvalidated();
293+
return result;
294+
}
295+
296+
bool DynamicTypeHandler::InvalidateStoreFieldCachesForAllProperties(ScriptContext* requestContext)
287297
{
288-
InvalidateInlineCachesForAllProperties<false>(requestContext);
298+
return InvalidateInlineCachesForAllProperties<true>(requestContext);
289299
}
290300

291-
void DynamicTypeHandler::InvalidateStoreFieldCachesForAllProperties(ScriptContext* requestContext)
301+
bool DynamicTypeHandler::ClearProtoCachesWereInvalidated()
292302
{
293-
InvalidateInlineCachesForAllProperties<true>(requestContext);
303+
bool done = !this->ProtoCachesWereInvalidated();
304+
this->protoCachesWereInvalidated = false;
305+
return done;
294306
}
295307

296-
void DynamicTypeHandler::RemoveFromPrototype(DynamicObject* instance, ScriptContext * requestContext)
308+
void DynamicTypeHandler::RemoveFromPrototype(DynamicObject* instance, ScriptContext * requestContext, bool * allProtoCachesInvalidated)
297309
{
298-
InvalidateProtoCachesForAllProperties(requestContext);
310+
Assert(!*allProtoCachesInvalidated);
311+
*allProtoCachesInvalidated = InvalidateProtoCachesForAllProperties(requestContext);
299312
}
300313

301-
void DynamicTypeHandler::AddToPrototype(DynamicObject* instance, ScriptContext * requestContext)
314+
void DynamicTypeHandler::AddToPrototype(DynamicObject* instance, ScriptContext * requestContext, bool * allProtoCachesInvalidated)
302315
{
303-
InvalidateStoreFieldCachesForAllProperties(requestContext);
316+
Assert(!*allProtoCachesInvalidated);
317+
if (this->ProtoCachesWereInvalidated())
318+
{
319+
*allProtoCachesInvalidated = true;
320+
}
321+
else
322+
{
323+
*allProtoCachesInvalidated = InvalidateProtoCachesForAllProperties(requestContext);
324+
}
304325
}
305326

306327
void DynamicTypeHandler::SetPrototype(DynamicObject* instance, RecyclableObject* newPrototype)

lib/Runtime/Types/TypeHandler.h

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ namespace Js
4949
Field(uint16) unusedBytes; // This always has it's lowest bit set to avoid false references
5050
Field(uint16) inlineSlotCapacity;
5151
Field(bool) isNotPathTypeHandlerOrHasUserDefinedCtor;
52+
Field(bool) protoCachesWereInvalidated;
5253

5354
public:
5455
DEFINE_GETCPPNAME_ABSTRACT();
@@ -58,7 +59,8 @@ namespace Js
5859
slotCapacity(typeHandler->slotCapacity),
5960
offsetOfInlineSlots(typeHandler->offsetOfInlineSlots),
6061
isNotPathTypeHandlerOrHasUserDefinedCtor(typeHandler->isNotPathTypeHandlerOrHasUserDefinedCtor),
61-
unusedBytes(typeHandler->unusedBytes)
62+
unusedBytes(typeHandler->unusedBytes),
63+
protoCachesWereInvalidated(false)
6264
{
6365
}
6466

@@ -528,15 +530,19 @@ namespace Js
528530

529531
private:
530532
template<bool isStoreField>
531-
void InvalidateInlineCachesForAllProperties(ScriptContext* requestContext);
533+
bool InvalidateInlineCachesForAllProperties(ScriptContext* requestContext);
532534

533535
public:
534-
void InvalidateProtoCachesForAllProperties(ScriptContext* requestContext);
535-
void InvalidateStoreFieldCachesForAllProperties(ScriptContext* requestContext);
536+
bool InvalidateProtoCachesForAllProperties(ScriptContext* requestContext);
537+
bool InvalidateStoreFieldCachesForAllProperties(ScriptContext* requestContext);
538+
539+
bool ProtoCachesWereInvalidated() const { return protoCachesWereInvalidated; }
540+
void SetProtoCachesWereInvalidated() { protoCachesWereInvalidated = true; }
541+
bool ClearProtoCachesWereInvalidated();
536542

537543
// For changing __proto__
538-
void RemoveFromPrototype(DynamicObject* instance, ScriptContext * requestContext);
539-
void AddToPrototype(DynamicObject* instance, ScriptContext * requestContext);
544+
void RemoveFromPrototype(DynamicObject* instance, ScriptContext * requestContext, bool * allProtoCachesInvalidated);
545+
void AddToPrototype(DynamicObject* instance, ScriptContext * requestContext, bool * allProtoCachesInvalidated);
540546
virtual void SetPrototype(DynamicObject* instance, RecyclableObject* newPrototype);
541547

542548
virtual void ResetTypeHandler(DynamicObject * instance);

0 commit comments

Comments
 (0)