Canonicalize how we prepare the prototype chain for inline caching
https://bugs.webkit.org/show_bug.cgi?id=202827
<rdar://problem/56193919>

Reviewed by Yusuke Suzuki.

JSTests:

* stress/cache-correct-offset-after-flattening.js: Added.
(assert):

Source/JavaScriptCore:

This patch canonicalizes how we prepare the prototype chain for caching. Both
in the poly proto chain, and the generateConditions*, we were flattening
dictionaries as we walk the prototype chain. We now unify that into one
function called `preparePrototypeChainForCaching`. In that, we flatten
dictionaries as we traverse the prototype chain, and note if any objects
are poly proto.

My patch in r250540 made it so we now flatten uncacheable dictionaries (this
was the intention all along, but it was a perf bug that we didn't do this). That
revealed that the inline caching code could use a stale PropertyOffset when
flattening an uncacheable dictionary. This patch makes it so we universally
try just defer caching to later if we encounter a situation where we flatten
a dictionary that could be a property holder.

* bytecode/ObjectPropertyConditionSet.cpp:
(JSC::generateConditionsForPrototypeEquivalenceConcurrently):
(JSC::generateConditionsForPropertyMissConcurrently):
(JSC::generateConditionsForPropertySetterMissConcurrently):
(JSC::preparePrototypeChainForCaching):
* bytecode/ObjectPropertyConditionSet.h:
* bytecode/PolyProtoAccessChain.cpp:
(JSC::PolyProtoAccessChain::create):
* bytecode/PolyProtoAccessChain.h:
(JSC::PolyProtoAccessChain::slotBaseStructure const):
* jit/Repatch.cpp:
(JSC::tryCacheGetByID):
(JSC::tryCachePutByID):
(JSC::tryCacheInByID):
(JSC::tryCacheInstanceOf):
* llint/LLIntSlowPaths.cpp:
(JSC::LLInt::setupGetByIdPrototypeCache):
* runtime/StructureRareData.cpp:
(JSC::StructureRareData::setObjectToStringValue):

Tools:

* Scripts/run-jsc-stress-tests:


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@251085 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/JSTests/ChangeLog b/JSTests/ChangeLog
index 5dafd32..6a848cb 100644
--- a/JSTests/ChangeLog
+++ b/JSTests/ChangeLog
@@ -1,3 +1,14 @@
+2019-10-14  Saam Barati  <sbarati@apple.com>
+
+        Canonicalize how we prepare the prototype chain for inline caching
+        https://bugs.webkit.org/show_bug.cgi?id=202827
+        <rdar://problem/56193919>
+
+        Reviewed by Yusuke Suzuki.
+
+        * stress/cache-correct-offset-after-flattening.js: Added.
+        (assert):
+
 2019-10-14  Paulo Matos  <pmatos@igalia.com>
 
         Skip memcpy-typed-loop timing out on ARMv7 pending investigation
diff --git a/JSTests/stress/cache-correct-offset-after-flattening.js b/JSTests/stress/cache-correct-offset-after-flattening.js
new file mode 100644
index 0000000..92343c7
--- /dev/null
+++ b/JSTests/stress/cache-correct-offset-after-flattening.js
@@ -0,0 +1,24 @@
+function assert(b) {
+    if (!b)
+        throw new Error;
+}
+
+let p = {};
+let o = {};
+o.__proto__ = p;
+
+for (let i = 0; i < 1000; ++i) {
+    p["p" + i] = i;
+}
+p.foo = 42;
+delete p.p0;
+delete p.p1;
+delete p.p2;
+delete p.p3;
+delete p.p4;
+delete p.p5;
+
+for (let i = 0; i < 10; ++i) {
+    p.foo = i;
+    assert(o.foo === i);
+}
diff --git a/Source/JavaScriptCore/ChangeLog b/Source/JavaScriptCore/ChangeLog
index 2e82865..c491f5d 100644
--- a/Source/JavaScriptCore/ChangeLog
+++ b/Source/JavaScriptCore/ChangeLog
@@ -1,3 +1,45 @@
+2019-10-14  Saam Barati  <sbarati@apple.com>
+
+        Canonicalize how we prepare the prototype chain for inline caching
+        https://bugs.webkit.org/show_bug.cgi?id=202827
+        <rdar://problem/56193919>
+
+        Reviewed by Yusuke Suzuki.
+
+        This patch canonicalizes how we prepare the prototype chain for caching. Both
+        in the poly proto chain, and the generateConditions*, we were flattening
+        dictionaries as we walk the prototype chain. We now unify that into one
+        function called `preparePrototypeChainForCaching`. In that, we flatten
+        dictionaries as we traverse the prototype chain, and note if any objects
+        are poly proto.
+        
+        My patch in r250540 made it so we now flatten uncacheable dictionaries (this
+        was the intention all along, but it was a perf bug that we didn't do this). That
+        revealed that the inline caching code could use a stale PropertyOffset when
+        flattening an uncacheable dictionary. This patch makes it so we universally
+        try just defer caching to later if we encounter a situation where we flatten
+        a dictionary that could be a property holder.
+
+        * bytecode/ObjectPropertyConditionSet.cpp:
+        (JSC::generateConditionsForPrototypeEquivalenceConcurrently):
+        (JSC::generateConditionsForPropertyMissConcurrently):
+        (JSC::generateConditionsForPropertySetterMissConcurrently):
+        (JSC::preparePrototypeChainForCaching):
+        * bytecode/ObjectPropertyConditionSet.h:
+        * bytecode/PolyProtoAccessChain.cpp:
+        (JSC::PolyProtoAccessChain::create):
+        * bytecode/PolyProtoAccessChain.h:
+        (JSC::PolyProtoAccessChain::slotBaseStructure const):
+        * jit/Repatch.cpp:
+        (JSC::tryCacheGetByID):
+        (JSC::tryCachePutByID):
+        (JSC::tryCacheInByID):
+        (JSC::tryCacheInstanceOf):
+        * llint/LLIntSlowPaths.cpp:
+        (JSC::LLInt::setupGetByIdPrototypeCache):
+        * runtime/StructureRareData.cpp:
+        (JSC::StructureRareData::setObjectToStringValue):
+
 2019-10-11  Devin Rousso  <drousso@apple.com>
 
         Web Inspector: Debugger: support pattern blackboxing
diff --git a/Source/JavaScriptCore/bytecode/ObjectPropertyConditionSet.cpp b/Source/JavaScriptCore/bytecode/ObjectPropertyConditionSet.cpp
index a3fa474..4751161 100644
--- a/Source/JavaScriptCore/bytecode/ObjectPropertyConditionSet.cpp
+++ b/Source/JavaScriptCore/bytecode/ObjectPropertyConditionSet.cpp
@@ -267,14 +267,9 @@
     return result;
 }
 
-enum Concurrency {
-    MainThread,
-    Concurrent
-};
 template<typename Functor>
 ObjectPropertyConditionSet generateConditions(
-    VM& vm, JSGlobalObject* globalObject, Structure* structure, JSObject* prototype, const Functor& functor,
-    Concurrency concurrency = MainThread)
+    VM& vm, JSGlobalObject* globalObject, Structure* structure, JSObject* prototype, const Functor& functor)
 {
     Vector<ObjectPropertyCondition> conditions;
     
@@ -314,21 +309,9 @@
         structure = object->structure(vm);
         
         if (structure->isDictionary()) {
-            if (concurrency == MainThread) {
-                if (structure->hasBeenFlattenedBefore()) {
-                    if (ObjectPropertyConditionSetInternal::verbose)
-                        dataLog("Dictionary has been flattened before, so invalid.\n");
-                    return ObjectPropertyConditionSet::invalid();
-                }
-
-                if (ObjectPropertyConditionSetInternal::verbose)
-                    dataLog("Flattening ", pointerDump(structure));
-                structure->flattenDictionaryStructure(vm, object);
-            } else {
-                if (ObjectPropertyConditionSetInternal::verbose)
-                    dataLog("Cannot flatten dictionary when not on main thread, so invalid.\n");
-                return ObjectPropertyConditionSet::invalid();
-            }
+            if (ObjectPropertyConditionSetInternal::verbose)
+                dataLog("Cannot cache dictionary.\n");
+            return ObjectPropertyConditionSet::invalid();
         }
 
         if (!functor(conditions, object)) {
@@ -487,7 +470,7 @@
                 return false;
             conditions.append(result);
             return true;
-        }, Concurrent);
+        });
 }
 
 ObjectPropertyConditionSet generateConditionsForPropertyMissConcurrently(
@@ -501,7 +484,7 @@
                 return false;
             conditions.append(result);
             return true;
-        }, Concurrent);
+        });
 }
 
 ObjectPropertyConditionSet generateConditionsForPropertySetterMissConcurrently(
@@ -516,7 +499,7 @@
                 return false;
             conditions.append(result);
             return true;
-        }, Concurrent);
+        });
 }
 
 ObjectPropertyCondition generateConditionForSelfEquivalence(
@@ -525,5 +508,84 @@
     return generateCondition(vm, owner, object, uid, PropertyCondition::Equivalence);
 }
 
+// Current might be null. Structure can't be null.
+static Optional<PrototypeChainCachingStatus> preparePrototypeChainForCaching(JSGlobalObject* globalObject, JSCell* current, Structure* structure, JSObject* target)
+{
+    ASSERT(structure);
+    VM& vm = globalObject->vm();
+
+    bool found = false;
+    bool usesPolyProto = false;
+    bool flattenedDictionary = false;
+
+    while (true) {
+        if (structure->isDictionary()) {
+            if (!current)
+                return WTF::nullopt;
+
+            ASSERT(structure->isObject());
+            if (structure->hasBeenFlattenedBefore())
+                return WTF::nullopt;
+
+            structure->flattenDictionaryStructure(vm, asObject(current));
+            flattenedDictionary = true;
+        }
+
+        if (!structure->propertyAccessesAreCacheable())
+            return WTF::nullopt;
+
+        if (structure->isProxy())
+            return WTF::nullopt;
+
+        if (current && current == target) {
+            found = true;
+            break;
+        }
+
+        // We only have poly proto if we need to access our prototype via
+        // the poly proto protocol. If the slot base is the only poly proto
+        // thing in the chain, and we have a cache hit on it, then we're not
+        // poly proto.
+        JSValue prototype;
+        if (structure->hasPolyProto()) {
+            if (!current)
+                return WTF::nullopt;
+            usesPolyProto = true;
+            prototype = structure->prototypeForLookup(globalObject, current);
+        } else
+            prototype = structure->prototypeForLookup(globalObject);
+
+        if (prototype.isNull())
+            break;
+        current = asObject(prototype);
+        structure = current->structure(vm);
+    }
+
+    if (!found && !!target)
+        return WTF::nullopt;
+
+    PrototypeChainCachingStatus result;
+    result.usesPolyProto = usesPolyProto;
+    result.flattenedDictionary = flattenedDictionary;
+
+    return result;
+}
+
+Optional<PrototypeChainCachingStatus> preparePrototypeChainForCaching(JSGlobalObject* globalObject, JSCell* base, JSObject* target)
+{
+    return preparePrototypeChainForCaching(globalObject, base, base->structure(globalObject->vm()), target);
+}
+
+Optional<PrototypeChainCachingStatus> preparePrototypeChainForCaching(JSGlobalObject* globalObject, JSCell* base, const PropertySlot& slot)
+{
+    JSObject* target = slot.isUnset() ? nullptr : slot.slotBase();
+    return preparePrototypeChainForCaching(globalObject, base, target);
+}
+
+Optional<PrototypeChainCachingStatus> preparePrototypeChainForCaching(JSGlobalObject* globalObject, Structure* baseStructure, JSObject* target)
+{
+    return preparePrototypeChainForCaching(globalObject, nullptr, baseStructure, target);
+}
+
 } // namespace JSC
 
diff --git a/Source/JavaScriptCore/bytecode/ObjectPropertyConditionSet.h b/Source/JavaScriptCore/bytecode/ObjectPropertyConditionSet.h
index 10cf844..f535787 100644
--- a/Source/JavaScriptCore/bytecode/ObjectPropertyConditionSet.h
+++ b/Source/JavaScriptCore/bytecode/ObjectPropertyConditionSet.h
@@ -189,4 +189,13 @@
 ObjectPropertyConditionSet generateConditionsForPropertySetterMissConcurrently(
     VM&, JSGlobalObject*, Structure* headStructure, UniquedStringImpl* uid);
 
+struct PrototypeChainCachingStatus {
+    bool usesPolyProto;
+    bool flattenedDictionary;
+};
+
+Optional<PrototypeChainCachingStatus> preparePrototypeChainForCaching(JSGlobalObject*, JSCell* base, const PropertySlot&);
+Optional<PrototypeChainCachingStatus> preparePrototypeChainForCaching(JSGlobalObject*, JSCell* base, JSObject* target);
+Optional<PrototypeChainCachingStatus> preparePrototypeChainForCaching(JSGlobalObject*, Structure* base, JSObject* target);
+
 } // namespace JSC
diff --git a/Source/JavaScriptCore/bytecode/PolyProtoAccessChain.cpp b/Source/JavaScriptCore/bytecode/PolyProtoAccessChain.cpp
index 3cf776e..8572380 100644
--- a/Source/JavaScriptCore/bytecode/PolyProtoAccessChain.cpp
+++ b/Source/JavaScriptCore/bytecode/PolyProtoAccessChain.cpp
@@ -31,33 +31,26 @@
 
 namespace JSC {
 
-std::unique_ptr<PolyProtoAccessChain> PolyProtoAccessChain::create(JSGlobalObject* globalObject, JSCell* base, const PropertySlot& slot, bool& usesPolyProto)
+std::unique_ptr<PolyProtoAccessChain> PolyProtoAccessChain::create(JSGlobalObject* globalObject, JSCell* base, const PropertySlot& slot)
 {
     JSObject* target = slot.isUnset() ? nullptr : slot.slotBase();
-    return create(globalObject, base, target, usesPolyProto);
+    return create(globalObject, base, target);
 }
 
-std::unique_ptr<PolyProtoAccessChain> PolyProtoAccessChain::create(JSGlobalObject* globalObject, JSCell* base, JSObject* target, bool& usesPolyProto)
+std::unique_ptr<PolyProtoAccessChain> PolyProtoAccessChain::create(JSGlobalObject* globalObject, JSCell* base, JSObject* target)
 {
     JSCell* current = base;
     VM& vm = base->vm();
 
     bool found = false;
 
-    usesPolyProto = false;
-
     std::unique_ptr<PolyProtoAccessChain> result(new PolyProtoAccessChain());
 
     for (unsigned iterationNumber = 0; true; ++iterationNumber) {
         Structure* structure = current->structure(vm);
 
-        if (structure->isDictionary()) {
-            ASSERT(structure->isObject());
-            if (structure->hasBeenFlattenedBefore())
-                return nullptr;
-
-            structure->flattenDictionaryStructure(vm, asObject(current));
-        }
+        if (structure->isDictionary())
+            return nullptr;
 
         if (!structure->propertyAccessesAreCacheable())
             return nullptr;
@@ -77,12 +70,6 @@
             break;
         }
 
-        // We only have poly proto if we need to access our prototype via
-        // the poly proto protocol. If the slot base is the only poly proto
-        // thing in the chain, and we have a cache hit on it, then we're not
-        // poly proto.
-        usesPolyProto |= structure->hasPolyProto();
-
         JSValue prototype = structure->prototypeForLookup(globalObject, current);
         if (prototype.isNull())
             break;
diff --git a/Source/JavaScriptCore/bytecode/PolyProtoAccessChain.h b/Source/JavaScriptCore/bytecode/PolyProtoAccessChain.h
index a1ecb94..68b24be 100644
--- a/Source/JavaScriptCore/bytecode/PolyProtoAccessChain.h
+++ b/Source/JavaScriptCore/bytecode/PolyProtoAccessChain.h
@@ -42,8 +42,8 @@
     PolyProtoAccessChain(PolyProtoAccessChain&) = default;
 
     // Returns nullptr when invalid.
-    static std::unique_ptr<PolyProtoAccessChain> create(JSGlobalObject*, JSCell* base, const PropertySlot&, bool& usesPolyProto);
-    static std::unique_ptr<PolyProtoAccessChain> create(JSGlobalObject*, JSCell* base, JSObject* target, bool& usesPolyProto);
+    static std::unique_ptr<PolyProtoAccessChain> create(JSGlobalObject*, JSCell* base, const PropertySlot&);
+    static std::unique_ptr<PolyProtoAccessChain> create(JSGlobalObject*, JSCell* base, JSObject* target);
 
     std::unique_ptr<PolyProtoAccessChain> clone()
     {
@@ -73,6 +73,13 @@
         }
     }
 
+    Structure* slotBaseStructure(Structure* baseStructure) const
+    {
+        if (m_chain.size())
+            return m_chain.last();
+        return baseStructure;
+    }
+
 private:
     PolyProtoAccessChain() = default;
 
diff --git a/Source/JavaScriptCore/jit/Repatch.cpp b/Source/JavaScriptCore/jit/Repatch.cpp
index f7173c7..0304b78 100644
--- a/Source/JavaScriptCore/jit/Repatch.cpp
+++ b/Source/JavaScriptCore/jit/Repatch.cpp
@@ -290,6 +290,7 @@
                     if (structure->hasBeenFlattenedBefore())
                         return GiveUpOnCache;
                     structure->flattenDictionaryStructure(vm, jsCast<JSObject*>(baseCell));
+                    return RetryCacheLater; // We may have changed property offsets.
                 }
 
                 if (slot.isUnset() && structure->typeInfo().getOwnPropertySlotIsImpureForPropertyAbsence())
@@ -298,14 +299,21 @@
                 // If a kind is GetByIDKind::Direct, we do not need to investigate prototype chains further.
                 // Cacheability just depends on the head structure.
                 if (kind != GetByIDKind::Direct) {
-                    bool usesPolyProto;
-                    prototypeAccessChain = PolyProtoAccessChain::create(exec->lexicalGlobalObject(), baseCell, slot, usesPolyProto);
-                    if (!prototypeAccessChain) {
-                        // It's invalid to access this prototype property.
+                    auto cacheStatus = preparePrototypeChainForCaching(exec->lexicalGlobalObject(), baseCell, slot);
+                    if (!cacheStatus)
                         return GiveUpOnCache;
+
+                    if (cacheStatus->flattenedDictionary) {
+                        // Property offsets may have changed due to flattening. We'll cache later.
+                        return RetryCacheLater;
                     }
 
-                    if (!usesPolyProto) {
+                    if (cacheStatus->usesPolyProto) {
+                        prototypeAccessChain = PolyProtoAccessChain::create(exec->lexicalGlobalObject(), baseCell, slot);
+                        if (!prototypeAccessChain)
+                            return GiveUpOnCache;
+                        RELEASE_ASSERT(slot.isCacheableCustom() || prototypeAccessChain->slotBaseStructure(structure)->get(vm, propertyName) == offset);
+                    } else {
                         // We use ObjectPropertyConditionSet instead for faster accesses.
                         prototypeAccessChain = nullptr;
 
@@ -318,6 +326,7 @@
                             conditionSet = generateConditionsForPrototypePropertyHit(
                                 vm, codeBlock, exec, structure, slot.slotBase(),
                                 propertyName.impl());
+                            RELEASE_ASSERT(!conditionSet.isValid() || conditionSet.slotBaseCondition().offset() == offset);
                         } else {
                             conditionSet = generateConditionsForPrototypePropertyHitCustom(
                                 vm, codeBlock, exec, structure, slot.slotBase(),
@@ -328,8 +337,6 @@
                             return GiveUpOnCache;
                     }
                 }
-
-                offset = slot.isUnset() ? invalidOffset : slot.cachedOffset();
             }
 
             JSFunction* getter = nullptr;
@@ -496,12 +503,11 @@
                     if (structure->hasBeenFlattenedBefore())
                         return GiveUpOnCache;
                     structure->flattenDictionaryStructure(vm, jsCast<JSObject*>(baseValue));
+                    return RetryCacheLater;
                 }
 
                 PropertyOffset offset;
-                Structure* newStructure =
-                    Structure::addPropertyTransitionToExistingStructureConcurrently(
-                        structure, ident.impl(), 0, offset);
+                Structure* newStructure = Structure::addPropertyTransitionToExistingStructureConcurrently(structure, ident.impl(), static_cast<unsigned>(PropertyAttribute::None), offset);
                 if (!newStructure || !newStructure->propertyAccessesAreCacheable())
                     return GiveUpOnCache;
 
@@ -512,22 +518,21 @@
                 std::unique_ptr<PolyProtoAccessChain> prototypeAccessChain;
                 ObjectPropertyConditionSet conditionSet;
                 if (putKind == NotDirect) {
-                    bool usesPolyProto;
-                    prototypeAccessChain = PolyProtoAccessChain::create(exec->lexicalGlobalObject(), baseCell, nullptr, usesPolyProto);
-                    if (!prototypeAccessChain) {
-                        // It's invalid to access this prototype property.
+                    auto cacheStatus = preparePrototypeChainForCaching(exec->lexicalGlobalObject(), baseCell, nullptr);
+                    if (!cacheStatus)
                         return GiveUpOnCache;
-                    }
 
-                    if (!usesPolyProto) {
+                    if (cacheStatus->usesPolyProto) {
+                        prototypeAccessChain = PolyProtoAccessChain::create(exec->lexicalGlobalObject(), baseCell, nullptr);
+                        if (!prototypeAccessChain)
+                            return GiveUpOnCache;
+                    } else {
                         prototypeAccessChain = nullptr;
-                        conditionSet =
-                            generateConditionsForPropertySetterMiss(
-                                vm, codeBlock, exec, newStructure, ident.impl());
+                        conditionSet = generateConditionsForPropertySetterMiss(
+                            vm, codeBlock, exec, newStructure, ident.impl());
                         if (!conditionSet.isValid())
                             return GiveUpOnCache;
                     }
-
                 }
 
                 newCase = AccessCase::create(vm, codeBlock, offset, structure, newStructure, conditionSet, WTFMove(prototypeAccessChain));
@@ -538,18 +543,18 @@
                 std::unique_ptr<PolyProtoAccessChain> prototypeAccessChain;
 
                 if (slot.base() != baseValue) {
-                    bool usesPolyProto;
-                    prototypeAccessChain = PolyProtoAccessChain::create(exec->lexicalGlobalObject(), baseCell, slot.base(), usesPolyProto);
-                    if (!prototypeAccessChain) {
-                        // It's invalid to access this prototype property.
+                    auto cacheStatus = preparePrototypeChainForCaching(exec->lexicalGlobalObject(), baseCell, slot.base());
+                    if (!cacheStatus)
                         return GiveUpOnCache;
-                    }
 
-                    if (!usesPolyProto) {
+                    if (cacheStatus->usesPolyProto) {
+                        prototypeAccessChain = PolyProtoAccessChain::create(exec->lexicalGlobalObject(), baseCell, slot.base());
+                        if (!prototypeAccessChain)
+                            return GiveUpOnCache;
+                    } else {
                         prototypeAccessChain = nullptr;
-                        conditionSet =
-                            generateConditionsForPrototypePropertyHitCustom(
-                                vm, codeBlock, exec, structure, slot.base(), ident.impl(), static_cast<unsigned>(PropertyAttribute::None));
+                        conditionSet = generateConditionsForPrototypePropertyHitCustom(
+                            vm, codeBlock, exec, structure, slot.base(), ident.impl(), static_cast<unsigned>(PropertyAttribute::None));
                         if (!conditionSet.isValid())
                             return GiveUpOnCache;
                     }
@@ -564,18 +569,21 @@
                 PropertyOffset offset = slot.cachedOffset();
 
                 if (slot.base() != baseValue) {
-                    bool usesPolyProto;
-                    prototypeAccessChain = PolyProtoAccessChain::create(exec->lexicalGlobalObject(), baseCell, slot.base(), usesPolyProto);
-                    if (!prototypeAccessChain) {
-                        // It's invalid to access this prototype property.
+                    auto cacheStatus = preparePrototypeChainForCaching(exec->lexicalGlobalObject(), baseCell, slot.base());
+                    if (!cacheStatus)
                         return GiveUpOnCache;
-                    }
+                    if (cacheStatus->flattenedDictionary)
+                        return RetryCacheLater;
 
-                    if (!usesPolyProto) {
+                    if (cacheStatus->usesPolyProto) {
+                        prototypeAccessChain = PolyProtoAccessChain::create(exec->lexicalGlobalObject(), baseCell, slot.base());
+                        if (!prototypeAccessChain)
+                            return GiveUpOnCache;
+                        offset = prototypeAccessChain->slotBaseStructure(baseCell->structure(vm))->get(vm, ident.impl());
+                    } else {
                         prototypeAccessChain = nullptr;
-                        conditionSet =
-                            generateConditionsForPrototypePropertyHit(
-                                vm, codeBlock, exec, structure, slot.base(), ident.impl());
+                        conditionSet = generateConditionsForPrototypePropertyHit(
+                            vm, codeBlock, exec, structure, slot.base(), ident.impl());
                         if (!conditionSet.isValid())
                             return GiveUpOnCache;
 
@@ -584,7 +592,6 @@
 
                         offset = conditionSet.slotBaseCondition().offset();
                     }
-
                 }
 
                 newCase = GetterSetterAccessCase::create(
@@ -667,34 +674,43 @@
             }
 
             if (slot.slotBase() != base) {
-                bool usesPolyProto;
-                prototypeAccessChain = PolyProtoAccessChain::create(exec->lexicalGlobalObject(), base, slot, usesPolyProto);
-                if (!prototypeAccessChain) {
-                    // It's invalid to access this prototype property.
+                auto cacheStatus = preparePrototypeChainForCaching(exec->lexicalGlobalObject(), base, slot);
+                if (!cacheStatus)
                     return GiveUpOnCache;
-                }
-                if (!usesPolyProto) {
+                if (cacheStatus->flattenedDictionary)
+                    return RetryCacheLater;
+
+                if (cacheStatus->usesPolyProto) {
+                    prototypeAccessChain = PolyProtoAccessChain::create(exec->lexicalGlobalObject(), base, slot);
+                    if (!prototypeAccessChain)
+                        return GiveUpOnCache;
+                    RELEASE_ASSERT(slot.isCacheableCustom() || prototypeAccessChain->slotBaseStructure(structure)->get(vm, ident.impl()) == slot.cachedOffset());
+                } else {
                     prototypeAccessChain = nullptr;
                     conditionSet = generateConditionsForPrototypePropertyHit(
                         vm, codeBlock, exec, structure, slot.slotBase(), ident.impl());
+                    if (!conditionSet.isValid())
+                        return GiveUpOnCache;
+                    RELEASE_ASSERT(slot.isCacheableCustom() || conditionSet.slotBaseCondition().offset() == slot.cachedOffset());
                 }
             }
         } else {
-            bool usesPolyProto;
-            prototypeAccessChain = PolyProtoAccessChain::create(exec->lexicalGlobalObject(), base, slot, usesPolyProto);
-            if (!prototypeAccessChain) {
-                // It's invalid to access this prototype property.
+            auto cacheStatus = preparePrototypeChainForCaching(exec->lexicalGlobalObject(), base, nullptr);
+            if (!cacheStatus)
                 return GiveUpOnCache;
-            }
 
-            if (!usesPolyProto) {
+            if (cacheStatus->usesPolyProto) {
+                prototypeAccessChain = PolyProtoAccessChain::create(exec->lexicalGlobalObject(), base, slot);
+                if (!prototypeAccessChain)
+                    return GiveUpOnCache;
+            } else {
                 prototypeAccessChain = nullptr;
                 conditionSet = generateConditionsForPropertyMiss(
                     vm, codeBlock, exec, structure, ident.impl());
+                if (!conditionSet.isValid())
+                    return GiveUpOnCache;
             }
         }
-        if (!conditionSet.isValid())
-            return GiveUpOnCache;
 
         LOG_IC((ICEvent::InAddAccessCase, structure->classInfo(), ident, slot.slotBase() == base));
 
@@ -754,7 +770,7 @@
             } else if (structure->prototypeQueriesAreCacheable()) {
                 // FIXME: Teach this to do poly proto.
                 // https://bugs.webkit.org/show_bug.cgi?id=185663
-
+                preparePrototypeChainForCaching(exec->lexicalGlobalObject(), value, wasFound ? prototype : nullptr);
                 ObjectPropertyConditionSet conditionSet = generateConditionsForInstanceOf(
                     vm, codeBlock, exec, structure, prototype, wasFound);
 
diff --git a/Source/JavaScriptCore/llint/LLIntSlowPaths.cpp b/Source/JavaScriptCore/llint/LLIntSlowPaths.cpp
index 34e6517..eb18b41 100644
--- a/Source/JavaScriptCore/llint/LLIntSlowPaths.cpp
+++ b/Source/JavaScriptCore/llint/LLIntSlowPaths.cpp
@@ -709,6 +709,8 @@
         structure->flattenDictionaryStructure(vm, jsCast<JSObject*>(baseCell));
     }
 
+    preparePrototypeChainForCaching(exec->lexicalGlobalObject(), baseCell, slot);
+
     ObjectPropertyConditionSet conditions;
     if (slot.isUnset())
         conditions = generateConditionsForPropertyMiss(vm, codeBlock, exec, structure, ident.impl());
diff --git a/Source/JavaScriptCore/runtime/StructureRareData.cpp b/Source/JavaScriptCore/runtime/StructureRareData.cpp
index d3cf183..e9b94b4 100644
--- a/Source/JavaScriptCore/runtime/StructureRareData.cpp
+++ b/Source/JavaScriptCore/runtime/StructureRareData.cpp
@@ -107,11 +107,13 @@
 
         // This will not create a condition for the current structure but that is good because we know the Symbol.toStringTag
         // is not on the ownStructure so we will transisition if one is added and this cache will no longer be used.
+        preparePrototypeChainForCaching(exec->lexicalGlobalObject(), ownStructure, toStringTagSymbolSlot.slotBase());
         conditionSet = generateConditionsForPrototypePropertyHit(vm, this, exec, ownStructure, toStringTagSymbolSlot.slotBase(), vm.propertyNames->toStringTagSymbol.impl());
         ASSERT(!conditionSet.isValid() || conditionSet.hasOneSlotBaseCondition());
-    } else if (toStringTagSymbolSlot.isUnset())
+    } else if (toStringTagSymbolSlot.isUnset()) {
+        preparePrototypeChainForCaching(exec->lexicalGlobalObject(), ownStructure, nullptr);
         conditionSet = generateConditionsForPropertyMiss(vm, this, exec, ownStructure, vm.propertyNames->toStringTagSymbol.impl());
-    else
+    } else
         return;
 
     if (!conditionSet.isValid()) {
diff --git a/Tools/ChangeLog b/Tools/ChangeLog
index cce65f4..03d5cb4 100644
--- a/Tools/ChangeLog
+++ b/Tools/ChangeLog
@@ -1,3 +1,13 @@
+2019-10-14  Saam Barati  <sbarati@apple.com>
+
+        Canonicalize how we prepare the prototype chain for inline caching
+        https://bugs.webkit.org/show_bug.cgi?id=202827
+        <rdar://problem/56193919>
+
+        Reviewed by Yusuke Suzuki.
+
+        * Scripts/run-jsc-stress-tests:
+
 2019-10-14  Nikolas Zimmermann  <nzimmermann@igalia.com>
 
         Update my nickname.
diff --git a/Tools/Scripts/run-jsc-stress-tests b/Tools/Scripts/run-jsc-stress-tests
index d1b4daf..54dfc0b 100755
--- a/Tools/Scripts/run-jsc-stress-tests
+++ b/Tools/Scripts/run-jsc-stress-tests
@@ -488,7 +488,7 @@
 
 # We force all tests to use a smaller (1.5M) stack so that stack overflow tests can run faster.
 BASE_OPTIONS = ["--useFTLJIT=false", "--useFunctionDotArguments=true", "--validateExceptionChecks=true", "--useDollarVM=true", "--maxPerThreadStackUsage=1572864"]
-EAGER_OPTIONS = ["--thresholdForJITAfterWarmUp=10", "--thresholdForJITSoon=10", "--thresholdForOptimizeAfterWarmUp=20", "--thresholdForOptimizeAfterLongWarmUp=20", "--thresholdForOptimizeSoon=20", "--thresholdForFTLOptimizeAfterWarmUp=20", "--thresholdForFTLOptimizeSoon=20", "--thresholdForOMGOptimizeAfterWarmUp=20", "--thresholdForOMGOptimizeSoon=20", "--maximumEvalCacheableSourceLength=150000", "--useEagerCodeBlockJettisonTiming=true"]
+EAGER_OPTIONS = ["--thresholdForJITAfterWarmUp=10", "--thresholdForJITSoon=10", "--thresholdForOptimizeAfterWarmUp=20", "--thresholdForOptimizeAfterLongWarmUp=20", "--thresholdForOptimizeSoon=20", "--thresholdForFTLOptimizeAfterWarmUp=20", "--thresholdForFTLOptimizeSoon=20", "--thresholdForOMGOptimizeAfterWarmUp=20", "--thresholdForOMGOptimizeSoon=20", "--maximumEvalCacheableSourceLength=150000", "--useEagerCodeBlockJettisonTiming=true", "--repatchBufferingCountdown=0"]
 # NOTE: Tests rely on this using scribbleFreeCells.
 NO_CJIT_OPTIONS = ["--useConcurrentJIT=false", "--thresholdForJITAfterWarmUp=100", "--scribbleFreeCells=true"]
 B3O1_OPTIONS = ["--defaultB3OptLevel=1"]