Skip to content
Permalink
Browse files
Clear StructureCache if it has Structure with relevant JSGlobalObjects
https://bugs.webkit.org/show_bug.cgi?id=240768
rdar://93232129

Reviewed by Saam Barati.

We need to clear Structures in StructureCache when having-a-bad-time: it is possible that Structure could have this have-a-bad-time
relevant JSGlobalObjects in its prototype chain. We are clearing it for InternalFunction's allocation cache. We should do the
same thing for JSGlobalObject's StructureCache.

This patch adds new watchpoint, structureCacheClearedWatchpoint. And use it in DFG. This watchpoint fires when the cache is cleared,
and it can happen even though JSGlobalObject is not getting have-a-bad-time.

* JSTests/stress/global-object-have-a-bad-time-dependency.js: Added.
(shouldBe):
(cons):
* Source/JavaScriptCore/dfg/DFGAbstractInterpreterInlines.h:
(JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):
* Source/JavaScriptCore/dfg/DFGConstantFoldingPhase.cpp:
(JSC::DFG::ConstantFoldingPhase::foldConstants):
* Source/JavaScriptCore/runtime/JSGlobalObject.cpp:
(JSC::JSGlobalObject::JSGlobalObject):
(JSC::JSGlobalObject::fireWatchpointAndMakeAllArrayStructuresSlowPut):
(JSC::JSGlobalObject::clearStructureCache):
* Source/JavaScriptCore/runtime/JSGlobalObject.h:
(JSC::JSGlobalObject::structureCacheClearedWatchpoint):
(JSC::JSGlobalObject::isStructureCacheCleared const):
* Source/JavaScriptCore/runtime/StructureCache.h:
(JSC::StructureCache::forEach):
* Source/JavaScriptCore/runtime/WeakGCMap.h:

Canonical link: https://commits.webkit.org/250845@main
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@294619 268f45cc-cd09-0410-ab3c-d52691b4dbfc
  • Loading branch information
Constellation committed May 22, 2022
1 parent 7b0eb35 commit fd038f440a814e6f5c2ca81057ce5c6f5a9b1dd6
Showing 9 changed files with 123 additions and 29 deletions.
@@ -0,0 +1,30 @@
function shouldBe(actual, expected) {
if (actual !== expected)
throw new Error('bad value: ' + actual);
}

const alien_global_object = createGlobalObject();

const a = {};
const b = alien_global_object.Object();

a.__proto__ = b;

function cons() {

}

cons.prototype = a;

// Cache
Reflect.construct(Array, [1.1, 2.2, 3.3], cons);

// Clear rareData to avoid the check in ObjectsWithBrokenIndexingFinder<mode>::visit(JSObject* object).
cons.prototype = null;
cons.prototype = a;

// Have a bad time.
b.__proto__ = new Proxy({}, {});

// This will create a double array having a Proxy object in its prototype chain.
shouldBe(!!describe(Reflect.construct(Array, [1.1, 2.2, 3.3], cons)).match(/ArrayWithSlowPutArrayStorage/), true);
@@ -3134,14 +3134,8 @@ bool AbstractInterpreter<AbstractStateType>::executeEffects(unsigned clobberLimi
structure = globalObject->nullPrototypeObjectStructure();
else if (base.isObject()) {
// Having a bad time clears the structureCache, and so it should invalidate this structure.
bool isHavingABadTime = globalObject->isHavingABadTime();
// Normally, we would always install a watchpoint. In this case, however, if we haveABadTime, we
// still want to optimize. There is no watchpoint for that case though, so we need to make sure this load
// does not get hoisted above the check.
WTF::loadLoadFence();
if (!isHavingABadTime)
m_graph.watchpoints().addLazily(globalObject->havingABadTimeWatchpoint());
structure = globalObject->structureCache().emptyObjectStructureConcurrently(base.getObject(), JSFinalObject::defaultInlineCapacity);
if (m_graph.isWatchingStructureCacheClearedWatchpoint(globalObject))
structure = globalObject->structureCache().emptyObjectStructureConcurrently(base.getObject(), JSFinalObject::defaultInlineCapacity);
}

if (structure) {
@@ -838,14 +838,8 @@ class ConstantFoldingPhase : public Phase {
structure = globalObject->nullPrototypeObjectStructure();
else if (base.isObject()) {
// Having a bad time clears the structureCache, and so it should invalidate this structure.
bool isHavingABadTime = globalObject->isHavingABadTime();
// Normally, we would always install a watchpoint. In this case, however, if we haveABadTime, we
// still want to optimize. There is no watchpoint for that case though, so we need to make sure this load
// does not get hoisted above the check.
WTF::loadLoadFence();
if (!isHavingABadTime)
m_graph.watchpoints().addLazily(globalObject->havingABadTimeWatchpoint());
structure = globalObject->structureCache().emptyObjectStructureConcurrently(base.getObject(), JSFinalObject::defaultInlineCapacity);
if (m_graph.isWatchingStructureCacheClearedWatchpoint(globalObject))
structure = globalObject->structureCache().emptyObjectStructureConcurrently(base.getObject(), JSFinalObject::defaultInlineCapacity);
}

if (structure) {
@@ -831,6 +831,15 @@ class Graph final : public virtual Scannable {
return isWatchingGlobalObjectWatchpoint(globalObject, set);
}

bool isWatchingStructureCacheClearedWatchpoint(JSGlobalObject* globalObject)
{
if (m_plan.isUnlinked())
return false;

InlineWatchpointSet& set = globalObject->structureCacheClearedWatchpoint();
return isWatchingGlobalObjectWatchpoint(globalObject, set);
}

Profiler::Compilation* compilation() { return m_plan.compilation(); }

DesiredIdentifiers& identifiers() { return m_plan.identifiers(); }
@@ -647,6 +647,7 @@ JSGlobalObject::JSGlobalObject(VM& vm, Structure* structure, const GlobalObjectM
, m_setAddWatchpointSet(IsWatched)
, m_arrayJoinWatchpointSet(IsWatched)
, m_numberToStringWatchpointSet(IsWatched)
, m_structureCacheClearedWatchpoint(IsWatched)
, m_runtimeFlags()
, m_stackTraceLimit(Options::defaultErrorStackTraceLimit())
, m_customGetterFunctionSet(vm)
@@ -1961,27 +1962,60 @@ inline IterationStatus ObjectsWithBrokenIndexingFinder<mode>::visit(JSObject* ob
RELEASE_ASSERT_NOT_REACHED();
};

if (JSFunction* function = jsDynamicCast<JSFunction*>(object)) {
auto checkStructureHasRelevantGlobalObject = [&](Structure* structure) -> bool {
if (hasBrokenIndexing(structure->indexingType())) {
bool isRelevantGlobalObject =
(mode == BadTimeFinderMode::SingleGlobal
? m_globalObject == structure->globalObject()
: m_globalObjects->contains(structure->globalObject()))
|| (structure->hasMonoProto() && !structure->storedPrototype().isNull() && isInAffectedGlobalObject(asObject(structure->storedPrototype())));
return isRelevantGlobalObject;
}
return false;
};

if (object->inherits<JSFunction>()) {
JSFunction* function = jsCast<JSFunction*>(object);
if (FunctionRareData* rareData = function->rareData()) {
// We only use this to cache JSFinalObjects. They do not start off with a broken indexing type.
ASSERT(!(rareData->objectAllocationStructure() && hasBrokenIndexing(rareData->objectAllocationStructure()->indexingType())));

if (Structure* structure = rareData->internalFunctionAllocationStructure()) {
if (hasBrokenIndexing(structure->indexingType())) {
bool isRelevantGlobalObject =
(mode == BadTimeFinderMode::SingleGlobal
? m_globalObject == structure->globalObject()
: m_globalObjects->contains(structure->globalObject()))
|| (structure->hasMonoProto() && !structure->storedPrototype().isNull() && isInAffectedGlobalObject(asObject(structure->storedPrototype())));
if (mode == BadTimeFinderMode::SingleGlobal && m_needsMultiGlobalsScan)
return IterationStatus::Done; // Bailing early and let the MultipleGlobals path handle everything.
if (isRelevantGlobalObject)
rareData->clearInternalFunctionAllocationProfile("have a bad time breaking internal function allocation");
}
bool isRelevantGlobalObject = checkStructureHasRelevantGlobalObject(structure);
if (mode == BadTimeFinderMode::SingleGlobal && m_needsMultiGlobalsScan)
return IterationStatus::Done; // Bailing early and let the MultipleGlobals path handle everything.
if (isRelevantGlobalObject)
rareData->clearInternalFunctionAllocationProfile("have a bad time breaking internal function allocation");
}
}
}

if (object->inherits<JSGlobalObject>()) {
JSGlobalObject* globalObject = jsCast<JSGlobalObject*>(object);
// If this globalObject is already having a bad time, then structures in its StructureCache
// does not affect on this new JSGlobalObject's haveABadTime since they are already slow mode.
if (!globalObject->isHavingABadTime()) {
VM& vm = globalObject->vm();
ASSERT(vm.heap.isDeferred());
bool willClear = false;
globalObject->structureCache().forEach([&](Structure* structure) {
bool isRelevantGlobalObject = checkStructureHasRelevantGlobalObject(structure);
if (mode == BadTimeFinderMode::SingleGlobal && m_needsMultiGlobalsScan)
return IterationStatus::Done;
if (isRelevantGlobalObject)
willClear = true;
return IterationStatus::Continue;
});
if (mode == BadTimeFinderMode::SingleGlobal && m_needsMultiGlobalsScan)
return IterationStatus::Done; // Bailing early and let the MultipleGlobals path handle everything.

// StructureCache contains Structures which is no longer valid after relevant JSGlobalObject's haveABadTime.
// We do not make such a JSGlobalObject status haveABadTime since still its own objects are intact.
if (willClear)
globalObject->clearStructureCache(vm);
}
}

// Run this filter first, since it's cheap, and ought to filter out a lot of objects.
if (!hasBrokenIndexing(object))
return IterationStatus::Continue;
@@ -2022,7 +2056,7 @@ void JSGlobalObject::fireWatchpointAndMakeAllArrayStructuresSlowPut(VM& vm)
// W_SC, R_BT, R_SC, W_BT: ^ Same
// W_SC, R_BT, W_BT, R_SC: ^ Same
// W_SC, W_BT, R_BT, R_SC: No watchpoint is installed, but we could not see old structures from the cache.
m_structureCache.clear(); // We may be caching array structures in here.
clearStructureCache(vm);

// Make sure that all JSArray allocations that load the appropriate structure from
// this object now load a structure that uses SlowPut.
@@ -2051,6 +2085,12 @@ void JSGlobalObject::fireWatchpointAndMakeAllArrayStructuresSlowPut(VM& vm)
ASSERT(isHavingABadTime()); // The watchpoint is what tells us that we're having a bad time.
};

void JSGlobalObject::clearStructureCache(VM& vm)
{
m_structureCache.clear(); // We may be caching array structures in here.
m_structureCacheClearedWatchpoint.fireAll(vm, "Clearing StructureCache");
}

void JSGlobalObject::haveABadTime(VM& vm)
{
ASSERT(&vm == &this->vm());
@@ -528,6 +528,7 @@ class JSGlobalObject : public JSSegmentedVariableObject {
InlineWatchpointSet m_arraySpeciesWatchpointSet { ClearWatchpoint };
InlineWatchpointSet m_arrayJoinWatchpointSet;
InlineWatchpointSet m_numberToStringWatchpointSet;
InlineWatchpointSet m_structureCacheClearedWatchpoint;
InlineWatchpointSet m_arrayBufferSpeciesWatchpointSet { ClearWatchpoint };
InlineWatchpointSet m_sharedArrayBufferSpeciesWatchpointSet { ClearWatchpoint };
std::unique_ptr<ObjectPropertyChangeAdaptiveWatchpoint<InlineWatchpointSet>> m_arrayConstructorSpeciesWatchpoint;
@@ -563,6 +564,7 @@ class JSGlobalObject : public JSSegmentedVariableObject {
RELEASE_ASSERT(Options::useJIT());
return m_numberToStringWatchpointSet;
}
InlineWatchpointSet& structureCacheClearedWatchpoint() { return m_structureCacheClearedWatchpoint; }
InlineWatchpointSet& arrayBufferSpeciesWatchpointSet(ArrayBufferSharingMode sharingMode)
{
switch (sharingMode) {
@@ -1072,6 +1074,7 @@ class JSGlobalObject : public JSSegmentedVariableObject {
}

void haveABadTime(VM&);
void clearStructureCache(VM&);

static bool objectPrototypeIsSaneConcurrently(Structure* objectPrototypeStructure);
bool arrayPrototypeChainIsSaneConcurrently(Structure* arrayPrototypeStructure, Structure* objectPrototypeStructure);
@@ -54,6 +54,13 @@ class StructureCache {
JS_EXPORT_PRIVATE Structure* emptyStructureForPrototypeFromBaseStructure(JSGlobalObject*, JSObject*, Structure*);
JS_EXPORT_PRIVATE Structure* emptyObjectStructureConcurrently(JSObject* prototype, unsigned inlineCapacity);

template<typename Func>
void forEach(Func func)
{
Locker locker { m_lock };
m_structures.forEach(func);
}

private:
Structure* createEmptyStructure(JSGlobalObject*, JSObject* prototype, const TypeInfo&, const ClassInfo*, IndexingType, unsigned inlineCapacity, bool makePolyProtoStructure, FunctionExecutable*);

@@ -103,6 +103,9 @@ class WeakGCMap final : public WeakGCHashTable {

void pruneStaleEntries() final;

template<typename Func>
void forEach(Func);

private:
HashMapType m_map;
VM& m_vm;
@@ -28,6 +28,7 @@
#include "HeapInlines.h"
#include "WeakGCMap.h"
#include "WeakInlines.h"
#include <wtf/IterationStatus.h>

namespace JSC {

@@ -74,4 +75,17 @@ NEVER_INLINE void WeakGCMap<KeyArg, ValueArg, HashArg, KeyTraitsArg>::pruneStale
});
}

template<typename KeyArg, typename ValueArg, typename HashArg, typename KeyTraitsArg>
template<typename Func>
inline void WeakGCMap<KeyArg, ValueArg, HashArg, KeyTraitsArg>::forEach(Func func)
{
ASSERT(m_vm.heap.isDeferred());
for (auto& entry : m_map) {
if (entry.value) {
if (func(entry.value.get()) == IterationStatus::Done)
return;
}
}
}

} // namespace JSC

0 comments on commit fd038f4

Please sign in to comment.