All prototypes should call didBecomePrototype()
[WebKit-https.git] / Source / JavaScriptCore / runtime / Structure.cpp
index dce51b5..293c464 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008, 2009, 2013, 2014 Apple Inc. All rights reserved.
+ * Copyright (C) 2008-2019 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -26,6 +26,7 @@
 #include "config.h"
 #include "Structure.h"
 
+#include "BuiltinNames.h"
 #include "CodeBlock.h"
 #include "DumpContext.h"
 #include "JSCInlines.h"
 #include "StructureRareDataInlines.h"
 #include "WeakGCMapInlines.h"
 #include <wtf/CommaPrinter.h>
+#include <wtf/NeverDestroyed.h>
 #include <wtf/ProcessID.h>
-#include <wtf/RefCountedLeakCounter.h>
 #include <wtf/RefPtr.h>
 #include <wtf/Threading.h>
 
 #define DUMP_STRUCTURE_ID_STATISTICS 0
 
-#ifndef NDEBUG
-#define DO_PROPERTYMAP_CONSTENCY_CHECK 0
-#else
-#define DO_PROPERTYMAP_CONSTENCY_CHECK 0
-#endif
-
-using namespace std;
-using namespace WTF;
-
 namespace JSC {
 
 #if DUMP_STRUCTURE_ID_STATISTICS
 static HashSet<Structure*>& liveStructureSet = *(new HashSet<Structure*>);
 #endif
 
-bool StructureTransitionTable::contains(AtomicStringImpl* rep, unsigned attributes) const
+class SingleSlotTransitionWeakOwner final : public WeakHandleOwner {
+    void finalize(Handle<Unknown>, void* context) override
+    {
+        StructureTransitionTable* table = reinterpret_cast<StructureTransitionTable*>(context);
+        ASSERT(table->isUsingSingleSlot());
+        WeakSet::deallocate(table->weakImpl());
+        table->m_data = StructureTransitionTable::UsingSingleSlotFlag;
+    }
+};
+
+static SingleSlotTransitionWeakOwner& singleSlotTransitionWeakOwner()
+{
+    static NeverDestroyed<SingleSlotTransitionWeakOwner> owner;
+    return owner;
+}
+
+inline Structure* StructureTransitionTable::singleTransition() const
+{
+    ASSERT(isUsingSingleSlot());
+    if (WeakImpl* impl = this->weakImpl()) {
+        if (impl->state() == WeakImpl::Live)
+            return jsCast<Structure*>(impl->jsValue().asCell());
+    }
+    return nullptr;
+}
+
+inline void StructureTransitionTable::setSingleTransition(Structure* structure)
+{
+    ASSERT(isUsingSingleSlot());
+    if (WeakImpl* impl = this->weakImpl())
+        WeakSet::deallocate(impl);
+    WeakImpl* impl = WeakSet::allocate(structure, &singleSlotTransitionWeakOwner(), this);
+    m_data = bitwise_cast<intptr_t>(impl) | UsingSingleSlotFlag;
+}
+
+bool StructureTransitionTable::contains(UniquedStringImpl* rep, unsigned attributes) const
 {
     if (isUsingSingleSlot()) {
         Structure* transition = singleTransition();
@@ -69,7 +96,7 @@ bool StructureTransitionTable::contains(AtomicStringImpl* rep, unsigned attribut
     return map()->get(std::make_pair(rep, attributes));
 }
 
-inline Structure* StructureTransitionTable::get(AtomicStringImpl* rep, unsigned attributes) const
+inline Structure* StructureTransitionTable::get(UniquedStringImpl* rep, unsigned attributes) const
 {
     if (isUsingSingleSlot()) {
         Structure* transition = singleTransition();
@@ -78,14 +105,14 @@ inline Structure* StructureTransitionTable::get(AtomicStringImpl* rep, unsigned
     return map()->get(std::make_pair(rep, attributes));
 }
 
-inline void StructureTransitionTable::add(VM& vm, Structure* structure)
+void StructureTransitionTable::add(VM& vm, Structure* structure)
 {
     if (isUsingSingleSlot()) {
         Structure* existingTransition = singleTransition();
 
         // This handles the first transition being added.
         if (!existingTransition) {
-            setSingleTransition(vm, structure);
+            setSingleTransition(structure);
             return;
         }
 
@@ -128,9 +155,9 @@ void Structure::dumpStatistics()
                 break;
         }
 
-        if (structure->propertyTable()) {
+        if (PropertyTable* table = structure->propertyTableOrNull()) {
             ++numberWithPropertyMaps;
-            totalPropertyMapsSize += structure->propertyTable()->sizeInMemory();
+            totalPropertyMapsSize += table->sizeInMemory();
         }
     }
 
@@ -152,97 +179,116 @@ Structure::Structure(VM& vm, JSGlobalObject* globalObject, JSValue prototype, co
     : JSCell(vm, vm.structureStructure.get())
     , m_blob(vm.heap.structureIDTable().allocateID(this), indexingType, typeInfo)
     , m_outOfLineTypeFlags(typeInfo.outOfLineTypeFlags())
+    , m_inlineCapacity(inlineCapacity)
+    , m_bitField(0)
     , m_globalObject(vm, this, globalObject, WriteBarrier<JSGlobalObject>::MayBeNull)
     , m_prototype(vm, this, prototype)
     , m_classInfo(classInfo)
     , m_transitionWatchpointSet(IsWatched)
     , m_offset(invalidOffset)
-    , m_inlineCapacity(inlineCapacity)
-    , m_bitField(0)
+    , m_propertyHash(0)
 {
     setDictionaryKind(NoneDictionaryKind);
     setIsPinnedPropertyTable(false);
     setHasGetterSetterProperties(classInfo->hasStaticSetterOrReadonlyProperties());
     setHasCustomGetterSetterProperties(false);
     setHasReadOnlyOrGetterSetterPropertiesExcludingProto(classInfo->hasStaticSetterOrReadonlyProperties());
-    setHasNonEnumerableProperties(false);
+    setHasUnderscoreProtoPropertyExcludingOriginalProto(false);
+    setIsQuickPropertyAccessAllowedForEnumeration(true);
     setAttributesInPrevious(0);
-    setPreventExtensions(false);
+    setDidPreventExtensions(false);
     setDidTransition(false);
-    setStaticFunctionsReified(false);
-    setHasRareData(false);
+    setStaticPropertiesReified(false);
+    setTransitionWatchpointIsLikelyToBeFired(false);
+    setHasBeenDictionary(false);
+    setIsAddingPropertyForTransition(false);
  
     ASSERT(inlineCapacity <= JSFinalObject::maxInlineCapacity());
     ASSERT(static_cast<PropertyOffset>(inlineCapacity) < firstOutOfLineOffset);
     ASSERT(!hasRareData());
     ASSERT(hasReadOnlyOrGetterSetterPropertiesExcludingProto() || !m_classInfo->hasStaticSetterOrReadonlyProperties());
     ASSERT(hasGetterSetterProperties() || !m_classInfo->hasStaticSetterOrReadonlyProperties());
+    ASSERT(!this->typeInfo().overridesGetCallData() || m_classInfo->methodTable.getCallData != &JSCell::getCallData);
 }
 
-const ClassInfo Structure::s_info = { "Structure", 0, 0, CREATE_METHOD_TABLE(Structure) };
+const ClassInfo Structure::s_info = { "Structure", nullptr, nullptr, nullptr, CREATE_METHOD_TABLE(Structure) };
 
 Structure::Structure(VM& vm)
     : JSCell(CreatingEarlyCell)
+    , m_inlineCapacity(0)
+    , m_bitField(0)
     , m_prototype(vm, this, jsNull())
     , m_classInfo(info())
     , m_transitionWatchpointSet(IsWatched)
     , m_offset(invalidOffset)
-    , m_inlineCapacity(0)
-    , m_bitField(0)
+    , m_propertyHash(0)
 {
     setDictionaryKind(NoneDictionaryKind);
     setIsPinnedPropertyTable(false);
     setHasGetterSetterProperties(m_classInfo->hasStaticSetterOrReadonlyProperties());
     setHasCustomGetterSetterProperties(false);
     setHasReadOnlyOrGetterSetterPropertiesExcludingProto(m_classInfo->hasStaticSetterOrReadonlyProperties());
-    setHasNonEnumerableProperties(false);
+    setHasUnderscoreProtoPropertyExcludingOriginalProto(false);
+    setIsQuickPropertyAccessAllowedForEnumeration(true);
     setAttributesInPrevious(0);
-    setPreventExtensions(false);
+    setDidPreventExtensions(false);
     setDidTransition(false);
-    setStaticFunctionsReified(false);
-    setHasRareData(false);
+    setStaticPropertiesReified(false);
+    setTransitionWatchpointIsLikelyToBeFired(false);
+    setHasBeenDictionary(false);
+    setIsAddingPropertyForTransition(false);
  
-    TypeInfo typeInfo = TypeInfo(CellType, StructureIsImmortal);
+    TypeInfo typeInfo = TypeInfo(CellType, StructureFlags);
     m_blob = StructureIDBlob(vm.heap.structureIDTable().allocateID(this), 0, typeInfo);
     m_outOfLineTypeFlags = typeInfo.outOfLineTypeFlags();
 
     ASSERT(hasReadOnlyOrGetterSetterPropertiesExcludingProto() || !m_classInfo->hasStaticSetterOrReadonlyProperties());
     ASSERT(hasGetterSetterProperties() || !m_classInfo->hasStaticSetterOrReadonlyProperties());
+    ASSERT(!this->typeInfo().overridesGetCallData() || m_classInfo->methodTable.getCallData != &JSCell::getCallData);
 }
 
-Structure::Structure(VM& vm, Structure* previous)
+Structure::Structure(VM& vm, Structure* previous, DeferredStructureTransitionWatchpointFire* deferred)
     : JSCell(vm, vm.structureStructure.get())
-    , m_prototype(vm, this, previous->storedPrototype())
+    , m_inlineCapacity(previous->m_inlineCapacity)
+    , m_bitField(0)
+    , m_prototype(vm, this, previous->m_prototype.get())
     , m_classInfo(previous->m_classInfo)
     , m_transitionWatchpointSet(IsWatched)
     , m_offset(invalidOffset)
-    , m_inlineCapacity(previous->m_inlineCapacity)
-    , m_bitField(0)
+    , m_propertyHash(previous->m_propertyHash)
 {
     setDictionaryKind(previous->dictionaryKind());
-    setIsPinnedPropertyTable(previous->hasBeenFlattenedBefore());
+    setIsPinnedPropertyTable(false);
+    setHasBeenFlattenedBefore(previous->hasBeenFlattenedBefore());
     setHasGetterSetterProperties(previous->hasGetterSetterProperties());
     setHasCustomGetterSetterProperties(previous->hasCustomGetterSetterProperties());
     setHasReadOnlyOrGetterSetterPropertiesExcludingProto(previous->hasReadOnlyOrGetterSetterPropertiesExcludingProto());
-    setHasNonEnumerableProperties(previous->hasNonEnumerableProperties());
+    setHasUnderscoreProtoPropertyExcludingOriginalProto(previous->hasUnderscoreProtoPropertyExcludingOriginalProto());
+    setIsQuickPropertyAccessAllowedForEnumeration(previous->isQuickPropertyAccessAllowedForEnumeration());
     setAttributesInPrevious(0);
-    setPreventExtensions(previous->preventExtensions());
+    setDidPreventExtensions(previous->didPreventExtensions());
     setDidTransition(true);
-    setStaticFunctionsReified(previous->staticFunctionsReified());
-    setHasRareData(false);
+    setStaticPropertiesReified(previous->staticPropertiesReified());
+    setHasBeenDictionary(previous->hasBeenDictionary());
+    setIsAddingPropertyForTransition(false);
  
     TypeInfo typeInfo = previous->typeInfo();
-    m_blob = StructureIDBlob(vm.heap.structureIDTable().allocateID(this), previous->indexingTypeIncludingHistory(), typeInfo);
+    m_blob = StructureIDBlob(vm.heap.structureIDTable().allocateID(this), previous->indexingModeIncludingHistory(), typeInfo);
     m_outOfLineTypeFlags = typeInfo.outOfLineTypeFlags();
 
     ASSERT(!previous->typeInfo().structureIsImmortal());
     setPreviousID(vm, previous);
 
-    previous->didTransitionFromThisStructure();
+    previous->didTransitionFromThisStructure(deferred);
+    
+    // Copy this bit now, in case previous was being watched.
+    setTransitionWatchpointIsLikelyToBeFired(previous->transitionWatchpointIsLikelyToBeFired());
+
     if (previous->m_globalObject)
         m_globalObject.set(vm, this, previous->m_globalObject.get());
     ASSERT(hasReadOnlyOrGetterSetterPropertiesExcludingProto() || !m_classInfo->hasStaticSetterOrReadonlyProperties());
     ASSERT(hasGetterSetterProperties() || !m_classInfo->hasStaticSetterOrReadonlyProperties());
+    ASSERT(!this->typeInfo().overridesGetCallData() || m_classInfo->methodTable.getCallData != &JSCell::getCallData);
 }
 
 Structure::~Structure()
@@ -257,6 +303,29 @@ void Structure::destroy(JSCell* cell)
     static_cast<Structure*>(cell)->Structure::~Structure();
 }
 
+Structure* Structure::create(PolyProtoTag, VM& vm, JSGlobalObject* globalObject, JSObject* prototype, const TypeInfo& typeInfo, const ClassInfo* classInfo, IndexingType indexingType, unsigned inlineCapacity)
+{
+    Structure* result = create(vm, globalObject, prototype, typeInfo, classInfo, indexingType, inlineCapacity);
+
+    unsigned oldOutOfLineCapacity = result->outOfLineCapacity();
+    result->addPropertyWithoutTransition(
+        vm, vm.propertyNames->builtinNames().polyProtoName(), static_cast<unsigned>(PropertyAttribute::DontEnum),
+        [&] (const GCSafeConcurrentJSLocker&, PropertyOffset offset, PropertyOffset newLastOffset) {
+            RELEASE_ASSERT(Structure::outOfLineCapacity(newLastOffset) == oldOutOfLineCapacity);
+            RELEASE_ASSERT(offset == knownPolyProtoOffset);
+            RELEASE_ASSERT(isInlineOffset(knownPolyProtoOffset));
+            result->m_prototype.setWithoutWriteBarrier(JSValue());
+            result->setLastOffset(newLastOffset);
+        });
+
+    return result;
+}
+
+bool Structure::isValidPrototype(JSValue prototype)
+{
+    return prototype.isNull() || (prototype.isObject() && prototype.getObject()->mayBePrototype());
+}
+
 void Structure::findStructuresAndMapForMaterialization(Vector<Structure*, 8>& structures, Structure*& structure, PropertyTable*& table)
 {
     ASSERT(structures.isEmpty());
@@ -265,7 +334,7 @@ void Structure::findStructuresAndMapForMaterialization(Vector<Structure*, 8>& st
     for (structure = this; structure; structure = structure->previousID()) {
         structure->m_lock.lock();
         
-        table = structure->propertyTable().get();
+        table = structure->propertyTableOrNull();
         if (table) {
             // Leave the structure locked, so that the caller can do things to it atomically
             // before it loses its property table.
@@ -280,43 +349,57 @@ void Structure::findStructuresAndMapForMaterialization(Vector<Structure*, 8>& st
     ASSERT(!table);
 }
 
-void Structure::materializePropertyMap(VM& vm)
+PropertyTable* Structure::materializePropertyTable(VM& vm, bool setPropertyTable)
 {
-    ASSERT(structure()->classInfo() == info());
-    ASSERT(!propertyTable());
-
+    ASSERT(structure(vm)->classInfo() == info());
+    ASSERT(!isAddingPropertyForTransition());
+    
+    DeferGC deferGC(vm.heap);
+    
     Vector<Structure*, 8> structures;
     Structure* structure;
     PropertyTable* table;
     
     findStructuresAndMapForMaterialization(structures, structure, table);
     
+    unsigned capacity = numberOfSlotsForLastOffset(m_offset, m_inlineCapacity);
     if (table) {
-        table = table->copy(vm, numberOfSlotsForLastOffset(m_offset, m_inlineCapacity));
+        table = table->copy(vm, capacity);
         structure->m_lock.unlock();
-    }
+    } else
+        table = PropertyTable::create(vm, capacity);
     
     // Must hold the lock on this structure, since we will be modifying this structure's
     // property map. We don't want getConcurrently() to see the property map in a half-baked
     // state.
-    GCSafeConcurrentJITLocker locker(m_lock, vm.heap);
-    if (!table)
-        createPropertyMap(locker, vm, numberOfSlotsForLastOffset(m_offset, m_inlineCapacity));
-    else
-        propertyTable().set(vm, this, table);
+    GCSafeConcurrentJSLocker locker(m_lock, vm.heap);
+    if (setPropertyTable)
+        this->setPropertyTable(vm, table);
 
     for (size_t i = structures.size(); i--;) {
         structure = structures[i];
         if (!structure->m_nameInPrevious)
             continue;
         PropertyMapEntry entry(structure->m_nameInPrevious.get(), structure->m_offset, structure->attributesInPrevious());
-        propertyTable()->add(entry, m_offset, PropertyTable::PropertyOffsetMustNotChange);
+        table->add(entry, m_offset, PropertyTable::PropertyOffsetMustNotChange);
     }
     
-    checkOffsetConsistency();
+    checkOffsetConsistency(
+        table,
+        [&] () {
+            dataLog("Detected in materializePropertyTable.\n");
+            dataLog("Found structure = ", RawPointer(structure), "\n");
+            dataLog("structures = ");
+            CommaPrinter comma;
+            for (Structure* structure : structures)
+                dataLog(comma, RawPointer(structure));
+            dataLog("\n");
+        });
+    
+    return table;
 }
 
-Structure* Structure::addPropertyTransitionToExistingStructureImpl(Structure* structure, AtomicStringImpl* uid, unsigned attributes, PropertyOffset& offset)
+Structure* Structure::addPropertyTransitionToExistingStructureImpl(Structure* structure, UniquedStringImpl* uid, unsigned attributes, PropertyOffset& offset)
 {
     ASSERT(!structure->isDictionary());
     ASSERT(structure->isObject());
@@ -336,32 +419,20 @@ Structure* Structure::addPropertyTransitionToExistingStructure(Structure* struct
     return addPropertyTransitionToExistingStructureImpl(structure, propertyName.uid(), attributes, offset);
 }
 
-Structure* Structure::addPropertyTransitionToExistingStructureConcurrently(Structure* structure, AtomicStringImpl* uid, unsigned attributes, PropertyOffset& offset)
+Structure* Structure::addPropertyTransitionToExistingStructureConcurrently(Structure* structure, UniquedStringImpl* uid, unsigned attributes, PropertyOffset& offset)
 {
-    ConcurrentJITLocker locker(structure->m_lock);
+    ConcurrentJSLocker locker(structure->m_lock);
     return addPropertyTransitionToExistingStructureImpl(structure, uid, attributes, offset);
 }
 
-bool Structure::anyObjectInChainMayInterceptIndexedAccesses() const
+bool Structure::holesMustForwardToPrototype(VM& vm, JSObject* base) const
 {
-    for (const Structure* current = this; ;) {
-        if (current->mayInterceptIndexedAccesses())
-            return true;
-        
-        JSValue prototype = current->storedPrototype();
-        if (prototype.isNull())
-            return false;
-        
-        current = asObject(prototype)->structure();
-    }
-}
+    ASSERT(base->structure(vm) == this);
 
-bool Structure::holesMustForwardToPrototype(VM& vm) const
-{
     if (this->mayInterceptIndexedAccesses())
         return true;
 
-    JSValue prototype = this->storedPrototype();
+    JSValue prototype = this->storedPrototype(base);
     if (!prototype.isObject())
         return false;
     JSObject* object = asObject(prototype);
@@ -370,7 +441,7 @@ bool Structure::holesMustForwardToPrototype(VM& vm) const
         Structure& structure = *object->structure(vm);
         if (hasIndexedProperties(object->indexingType()) || structure.mayInterceptIndexedAccesses())
             return true;
-        prototype = structure.storedPrototype();
+        prototype = structure.storedPrototype(object);
         if (!prototype.isObject())
             return false;
         object = asObject(prototype);
@@ -380,21 +451,18 @@ bool Structure::holesMustForwardToPrototype(VM& vm) const
     return false;
 }
 
-bool Structure::needsSlowPutIndexing() const
+Structure* Structure::addPropertyTransition(VM& vm, Structure* structure, PropertyName propertyName, unsigned attributes, PropertyOffset& offset)
 {
-    return anyObjectInChainMayInterceptIndexedAccesses()
-        || globalObject()->isHavingABadTime();
-}
+    Structure* newStructure = addPropertyTransitionToExistingStructure(
+        structure, propertyName, attributes, offset);
+    if (newStructure)
+        return newStructure;
 
-NonPropertyTransition Structure::suggestedArrayStorageTransition() const
-{
-    if (needsSlowPutIndexing())
-        return AllocateSlowPutArrayStorage;
-    
-    return AllocateArrayStorage;
+    return addNewPropertyTransition(
+        vm, structure, propertyName, attributes, offset, PutPropertySlot::UnknownContext);
 }
 
-Structure* Structure::addPropertyTransition(VM& vm, Structure* structure, PropertyName propertyName, unsigned attributes, PropertyOffset& offset, PutPropertySlot::Context context)
+Structure* Structure::addNewPropertyTransition(VM& vm, Structure* structure, PropertyName propertyName, unsigned attributes, PropertyOffset& offset, PutPropertySlot::Context context, DeferredStructureTransitionWatchpointFire* deferred)
 {
     ASSERT(!structure->isDictionary());
     ASSERT(structure->isObject());
@@ -406,25 +474,48 @@ Structure* Structure::addPropertyTransition(VM& vm, Structure* structure, Proper
     else
         maxTransitionLength = s_maxTransitionLength;
     if (structure->transitionCount() > maxTransitionLength) {
-        Structure* transition = toCacheableDictionaryTransition(vm, structure);
+        ASSERT(!isCopyOnWrite(structure->indexingMode()));
+        Structure* transition = toCacheableDictionaryTransition(vm, structure, deferred);
         ASSERT(structure != transition);
         offset = transition->add(vm, propertyName, attributes);
         return transition;
     }
     
-    Structure* transition = create(vm, structure);
+    Structure* transition = create(vm, structure, deferred);
 
     transition->m_cachedPrototypeChain.setMayBeNull(vm, transition, structure->m_cachedPrototypeChain.get());
+    
+    // While we are adding the property, rematerializing the property table is super weird: we already
+    // have a m_nameInPrevious and attributesInPrevious but the m_offset is still wrong. If the
+    // materialization algorithm runs, it'll build a property table that already has the property but
+    // at a bogus offset. Rather than try to teach the materialization code how to create a table under
+    // those conditions, we just tell the GC not to blow the table away during this period of time.
+    // Holding the lock ensures that we either do this before the GC starts scanning the structure, in
+    // which case the GC will not blow the table away, or we do it after the GC already ran in which
+    // case all is well.  If it wasn't for the lock, the GC would have TOCTOU: if could read
+    // isAddingPropertyForTransition before we set it to true, and then blow the table away after.
+    {
+        ConcurrentJSLocker locker(transition->m_lock);
+        transition->setIsAddingPropertyForTransition(true);
+    }
+
+    transition->m_blob.setIndexingModeIncludingHistory(structure->indexingModeIncludingHistory() & ~CopyOnWrite);
     transition->m_nameInPrevious = propertyName.uid();
     transition->setAttributesInPrevious(attributes);
-    transition->propertyTable().set(vm, transition, structure->takePropertyTableOrCloneIfPinned(vm));
+    transition->setPropertyTable(vm, structure->takePropertyTableOrCloneIfPinned(vm));
     transition->m_offset = structure->m_offset;
 
     offset = transition->add(vm, propertyName, attributes);
 
+    // Now that everything is fine with the new structure's bookkeeping, the GC is free to blow the
+    // table away if it wants. We can now rebuild it fine.
+    WTF::storeStoreFence();
+    transition->setIsAddingPropertyForTransition(false);
+
     checkOffset(transition->m_offset, transition->inlineCapacity());
     {
-        ConcurrentJITLocker locker(structure->m_lock);
+        ConcurrentJSLocker locker(structure->m_lock);
+        DeferGC deferGC(vm.heap);
         structure->m_transitionTable.add(vm, transition);
     }
     transition->checkOffsetConsistency();
@@ -434,6 +525,18 @@ Structure* Structure::addPropertyTransition(VM& vm, Structure* structure, Proper
 
 Structure* Structure::removePropertyTransition(VM& vm, Structure* structure, PropertyName propertyName, PropertyOffset& offset)
 {
+    // NOTE: There are some good reasons why this goes directly to uncacheable dictionary rather than
+    // caching the removal. We can fix all of these things, but we must remember to do so, if we ever try
+    // to optimize this case.
+    //
+    // - Cached transitions usually steal the property table, and assume that this is possible because they
+    //   can just rebuild the table by looking at past transitions. That code assumes that the table only
+    //   grew and never shrank. To support removals, we'd have to change the property table materialization
+    //   code to handle deletions. Also, we have logic to get the list of properties on a structure that
+    //   lacks a property table by just looking back through the set of transitions since the last
+    //   structure that had a pinned table. That logic would also have to be changed to handle cached
+    //   removals.
+    //
     ASSERT(!structure->isUncacheableDictionary());
 
     Structure* transition = toUncacheableDictionaryTransition(vm, structure);
@@ -444,38 +547,36 @@ Structure* Structure::removePropertyTransition(VM& vm, Structure* structure, Pro
     return transition;
 }
 
-Structure* Structure::changePrototypeTransition(VM& vm, Structure* structure, JSValue prototype)
+Structure* Structure::changePrototypeTransition(VM& vm, Structure* structure, JSValue prototype, DeferredStructureTransitionWatchpointFire& deferred)
 {
-    Structure* transition = create(vm, structure);
+    ASSERT(isValidPrototype(prototype));
+
+    DeferGC deferGC(vm.heap);
+    Structure* transition = create(vm, structure, &deferred);
 
     transition->m_prototype.set(vm, transition, prototype);
 
-    DeferGC deferGC(vm.heap);
-    structure->materializePropertyMapIfNecessary(vm, deferGC);
-    transition->propertyTable().set(vm, transition, structure->copyPropertyTableForPinning(vm));
+    PropertyTable* table = structure->copyPropertyTableForPinning(vm);
+    transition->pin(holdLock(transition->m_lock), vm, table);
     transition->m_offset = structure->m_offset;
-    transition->pin();
-
+    
     transition->checkOffsetConsistency();
     return transition;
 }
 
 Structure* Structure::attributeChangeTransition(VM& vm, Structure* structure, PropertyName propertyName, unsigned attributes)
 {
-    DeferGC deferGC(vm.heap);
     if (!structure->isUncacheableDictionary()) {
         Structure* transition = create(vm, structure);
 
-        structure->materializePropertyMapIfNecessary(vm, deferGC);
-        transition->propertyTable().set(vm, transition, structure->copyPropertyTableForPinning(vm));
+        PropertyTable* table = structure->copyPropertyTableForPinning(vm);
+        transition->pin(holdLock(transition->m_lock), vm, table);
         transition->m_offset = structure->m_offset;
-        transition->pin();
         
         structure = transition;
     }
 
-    ASSERT(structure->propertyTable());
-    PropertyMapEntry* entry = structure->propertyTable()->get(propertyName.uid());
+    PropertyMapEntry* entry = structure->ensurePropertyTable(vm)->get(propertyName.uid());
     ASSERT(entry);
     entry->attributes = attributes;
 
@@ -483,26 +584,26 @@ Structure* Structure::attributeChangeTransition(VM& vm, Structure* structure, Pr
     return structure;
 }
 
-Structure* Structure::toDictionaryTransition(VM& vm, Structure* structure, DictionaryKind kind)
+Structure* Structure::toDictionaryTransition(VM& vm, Structure* structure, DictionaryKind kind, DeferredStructureTransitionWatchpointFire* deferred)
 {
     ASSERT(!structure->isUncacheableDictionary());
+    DeferGC deferGC(vm.heap);
     
-    Structure* transition = create(vm, structure);
+    Structure* transition = create(vm, structure, deferred);
 
-    DeferGC deferGC(vm.heap);
-    structure->materializePropertyMapIfNecessary(vm, deferGC);
-    transition->propertyTable().set(vm, transition, structure->copyPropertyTableForPinning(vm));
+    PropertyTable* table = structure->copyPropertyTableForPinning(vm);
+    transition->pin(holdLock(transition->m_lock), vm, table);
     transition->m_offset = structure->m_offset;
     transition->setDictionaryKind(kind);
-    transition->pin();
-
+    transition->setHasBeenDictionary(true);
+    
     transition->checkOffsetConsistency();
     return transition;
 }
 
-Structure* Structure::toCacheableDictionaryTransition(VM& vm, Structure* structure)
+Structure* Structure::toCacheableDictionaryTransition(VM& vm, Structure* structure, DeferredStructureTransitionWatchpointFire* deferred)
 {
-    return toDictionaryTransition(vm, structure, CachedDictionaryKind);
+    return toDictionaryTransition(vm, structure, CachedDictionaryKind, deferred);
 }
 
 Structure* Structure::toUncacheableDictionaryTransition(VM& vm, Structure* structure)
@@ -510,111 +611,93 @@ Structure* Structure::toUncacheableDictionaryTransition(VM& vm, Structure* struc
     return toDictionaryTransition(vm, structure, UncachedDictionaryKind);
 }
 
-// In future we may want to cache this transition.
 Structure* Structure::sealTransition(VM& vm, Structure* structure)
 {
-    Structure* transition = preventExtensionsTransition(vm, structure);
-
-    if (transition->propertyTable()) {
-        PropertyTable::iterator end = transition->propertyTable()->end();
-        for (PropertyTable::iterator iter = transition->propertyTable()->begin(); iter != end; ++iter)
-            iter->attributes |= DontDelete;
-    }
-
-    transition->checkOffsetConsistency();
-    return transition;
+    return nonPropertyTransition(vm, structure, NonPropertyTransition::Seal);
 }
 
-// In future we may want to cache this transition.
 Structure* Structure::freezeTransition(VM& vm, Structure* structure)
 {
-    Structure* transition = preventExtensionsTransition(vm, structure);
-
-    if (transition->propertyTable()) {
-        PropertyTable::iterator iter = transition->propertyTable()->begin();
-        PropertyTable::iterator end = transition->propertyTable()->end();
-        if (iter != end)
-            transition->setHasReadOnlyOrGetterSetterPropertiesExcludingProto(true);
-        for (; iter != end; ++iter)
-            iter->attributes |= iter->attributes & Accessor ? DontDelete : (DontDelete | ReadOnly);
-    }
-
-    ASSERT(transition->hasReadOnlyOrGetterSetterPropertiesExcludingProto() || !transition->classInfo()->hasStaticSetterOrReadonlyProperties());
-    ASSERT(transition->hasGetterSetterProperties() || !transition->classInfo()->hasStaticSetterOrReadonlyProperties());
-    transition->checkOffsetConsistency();
-    return transition;
+    return nonPropertyTransition(vm, structure, NonPropertyTransition::Freeze);
 }
 
-// In future we may want to cache this transition.
 Structure* Structure::preventExtensionsTransition(VM& vm, Structure* structure)
 {
-    Structure* transition = create(vm, structure);
-
-    // Don't set m_offset, as one can not transition to this.
-
-    DeferGC deferGC(vm.heap);
-    structure->materializePropertyMapIfNecessary(vm, deferGC);
-    transition->propertyTable().set(vm, transition, structure->copyPropertyTableForPinning(vm));
-    transition->m_offset = structure->m_offset;
-    transition->setPreventExtensions(true);
-    transition->pin();
-
-    transition->checkOffsetConsistency();
-    return transition;
+    return nonPropertyTransition(vm, structure, NonPropertyTransition::PreventExtensions);
 }
 
 PropertyTable* Structure::takePropertyTableOrCloneIfPinned(VM& vm)
 {
-    DeferGC deferGC(vm.heap);
-    materializePropertyMapIfNecessaryForPinning(vm, deferGC);
-    
-    if (isPinnedPropertyTable())
-        return propertyTable()->copy(vm, propertyTable()->size() + 1);
-    
-    // Hold the lock while stealing the table - so that getConcurrently() on another thread
-    // will either have to bypass this structure, or will get to use the property table
-    // before it is stolen.
-    ConcurrentJITLocker locker(m_lock);
-    PropertyTable* takenPropertyTable = propertyTable().get();
-    propertyTable().clear();
-    return takenPropertyTable;
+    // This must always return a property table. It can't return null.
+    PropertyTable* result = propertyTableOrNull();
+    if (result) {
+        if (isPinnedPropertyTable())
+            return result->copy(vm, result->size() + 1);
+        ConcurrentJSLocker locker(m_lock);
+        setPropertyTable(vm, nullptr);
+        return result;
+    }
+    bool setPropertyTable = false;
+    return materializePropertyTable(vm, setPropertyTable);
 }
 
-Structure* Structure::nonPropertyTransition(VM& vm, Structure* structure, NonPropertyTransition transitionKind)
+Structure* Structure::nonPropertyTransitionSlow(VM& vm, Structure* structure, NonPropertyTransition transitionKind)
 {
     unsigned attributes = toAttributes(transitionKind);
-    IndexingType indexingType = newIndexingType(structure->indexingTypeIncludingHistory(), transitionKind);
-    
-    if (JSGlobalObject* globalObject = structure->m_globalObject.get()) {
-        if (globalObject->isOriginalArrayStructure(structure)) {
-            Structure* result = globalObject->originalArrayStructureForIndexingType(indexingType);
-            if (result->indexingTypeIncludingHistory() == indexingType) {
-                structure->didTransitionFromThisStructure();
-                return result;
-            }
-        }
-    }
+    IndexingType indexingModeIncludingHistory = newIndexingType(structure->indexingModeIncludingHistory(), transitionKind);
     
     Structure* existingTransition;
     if (!structure->isDictionary() && (existingTransition = structure->m_transitionTable.get(0, attributes))) {
         ASSERT(existingTransition->attributesInPrevious() == attributes);
-        ASSERT(existingTransition->indexingTypeIncludingHistory() == indexingType);
+        ASSERT(existingTransition->indexingModeIncludingHistory() == indexingModeIncludingHistory);
         return existingTransition;
     }
     
+    DeferGC deferGC(vm.heap);
+    
     Structure* transition = create(vm, structure);
     transition->setAttributesInPrevious(attributes);
-    transition->m_blob.setIndexingType(indexingType);
-    transition->propertyTable().set(vm, transition, structure->takePropertyTableOrCloneIfPinned(vm));
-    transition->m_offset = structure->m_offset;
-    checkOffset(transition->m_offset, transition->inlineCapacity());
+    transition->m_blob.setIndexingModeIncludingHistory(indexingModeIncludingHistory);
     
-    if (structure->isDictionary())
-        transition->pin();
-    else {
-        ConcurrentJITLocker locker(structure->m_lock);
+    if (preventsExtensions(transitionKind))
+        transition->setDidPreventExtensions(true);
+    
+    if (setsDontDeleteOnAllProperties(transitionKind)
+        || setsReadOnlyOnNonAccessorProperties(transitionKind)) {
+        // We pin the property table on transitions that do wholesale editing of the property
+        // table, since our logic for walking the property transition chain to rematerialize the
+        // table doesn't know how to take into account such wholesale edits.
+
+        PropertyTable* table = structure->copyPropertyTableForPinning(vm);
+        transition->pinForCaching(holdLock(transition->m_lock), vm, table);
+        transition->m_offset = structure->m_offset;
+        
+        table = transition->propertyTableOrNull();
+        RELEASE_ASSERT(table);
+        for (auto& entry : *table) {
+            if (setsDontDeleteOnAllProperties(transitionKind))
+                entry.attributes |= static_cast<unsigned>(PropertyAttribute::DontDelete);
+            if (setsReadOnlyOnNonAccessorProperties(transitionKind) && !(entry.attributes & PropertyAttribute::Accessor))
+                entry.attributes |= static_cast<unsigned>(PropertyAttribute::ReadOnly);
+        }
+    } else {
+        transition->setPropertyTable(vm, structure->takePropertyTableOrCloneIfPinned(vm));
+        transition->m_offset = structure->m_offset;
+        checkOffset(transition->m_offset, transition->inlineCapacity());
+    }
+    
+    if (setsReadOnlyOnNonAccessorProperties(transitionKind)
+        && !transition->propertyTableOrNull()->isEmpty())
+        transition->setHasReadOnlyOrGetterSetterPropertiesExcludingProto(true);
+    
+    if (structure->isDictionary()) {
+        PropertyTable* table = transition->ensurePropertyTable(vm);
+        transition->pin(holdLock(transition->m_lock), vm, table);
+    } else {
+        auto locker = holdLock(structure->m_lock);
         structure->m_transitionTable.add(vm, transition);
     }
+
     transition->checkOffsetConsistency();
     return transition;
 }
@@ -622,17 +705,16 @@ Structure* Structure::nonPropertyTransition(VM& vm, Structure* structure, NonPro
 // In future we may want to cache this property.
 bool Structure::isSealed(VM& vm)
 {
-    if (isExtensible())
+    if (isStructureExtensible())
         return false;
 
-    DeferGC deferGC(vm.heap);
-    materializePropertyMapIfNecessary(vm, deferGC);
-    if (!propertyTable())
+    PropertyTable* table = ensurePropertyTableIfNotEmpty(vm);
+    if (!table)
         return true;
-
-    PropertyTable::iterator end = propertyTable()->end();
-    for (PropertyTable::iterator iter = propertyTable()->begin(); iter != end; ++iter) {
-        if ((iter->attributes & DontDelete) != DontDelete)
+    
+    PropertyTable::iterator end = table->end();
+    for (PropertyTable::iterator iter = table->begin(); iter != end; ++iter) {
+        if ((iter->attributes & PropertyAttribute::DontDelete) != static_cast<unsigned>(PropertyAttribute::DontDelete))
             return false;
     }
     return true;
@@ -641,19 +723,18 @@ bool Structure::isSealed(VM& vm)
 // In future we may want to cache this property.
 bool Structure::isFrozen(VM& vm)
 {
-    if (isExtensible())
+    if (isStructureExtensible())
         return false;
 
-    DeferGC deferGC(vm.heap);
-    materializePropertyMapIfNecessary(vm, deferGC);
-    if (!propertyTable())
+    PropertyTable* table = ensurePropertyTableIfNotEmpty(vm);
+    if (!table)
         return true;
-
-    PropertyTable::iterator end = propertyTable()->end();
-    for (PropertyTable::iterator iter = propertyTable()->begin(); iter != end; ++iter) {
-        if (!(iter->attributes & DontDelete))
+    
+    PropertyTable::iterator end = table->end();
+    for (PropertyTable::iterator iter = table->begin(); iter != end; ++iter) {
+        if (!(iter->attributes & PropertyAttribute::DontDelete))
             return false;
-        if (!(iter->attributes & (ReadOnly | Accessor)))
+        if (!(iter->attributes & (PropertyAttribute::ReadOnly | PropertyAttribute::Accessor)))
             return false;
     }
     return true;
@@ -663,21 +744,27 @@ Structure* Structure::flattenDictionaryStructure(VM& vm, JSObject* object)
 {
     checkOffsetConsistency();
     ASSERT(isDictionary());
+    
+    GCSafeConcurrentJSLocker locker(m_lock, vm.heap);
+    
+    object->setStructureIDDirectly(nuke(id()));
+    WTF::storeStoreFence();
 
     size_t beforeOutOfLineCapacity = this->outOfLineCapacity();
     if (isUncacheableDictionary()) {
-        ASSERT(propertyTable());
+        PropertyTable* table = propertyTableOrNull();
+        ASSERT(table);
 
-        size_t propertyCount = propertyTable()->size();
+        size_t propertyCount = table->size();
 
         // Holds our values compacted by insertion order.
         Vector<JSValue> values(propertyCount);
 
         // Copies out our values from their hashed locations, compacting property table offsets as we go.
         unsigned i = 0;
-        PropertyTable::iterator end = propertyTable()->end();
+        PropertyTable::iterator end = table->end();
         m_offset = invalidOffset;
-        for (PropertyTable::iterator iter = propertyTable()->begin(); iter != end; ++iter, ++i) {
+        for (PropertyTable::iterator iter = table->begin(); iter != end; ++iter, ++i) {
             values[i] = object->getDirect(iter->offset);
             m_offset = iter->offset = offsetForPropertyNumber(i, m_inlineCapacity);
         }
@@ -686,7 +773,20 @@ Structure* Structure::flattenDictionaryStructure(VM& vm, JSObject* object)
         for (unsigned i = 0; i < propertyCount; i++)
             object->putDirect(vm, offsetForPropertyNumber(i, m_inlineCapacity), values[i]);
 
-        propertyTable()->clearDeletedOffsets();
+        table->clearDeletedOffsets();
+
+        // We need to zero our unused property space; otherwise the GC might see a
+        // stale pointer when we add properties in the future.
+        memset(
+            object->inlineStorageUnsafe() + inlineSize(),
+            0,
+            (inlineCapacity() - inlineSize()) * sizeof(EncodedJSValue));
+
+        Butterfly* butterfly = object->butterfly();
+        size_t preCapacity = butterfly->indexingHeader()->preCapacity(this);
+        void* base = butterfly->base(preCapacity, beforeOutOfLineCapacity);
+        void* startOfPropertyStorageSlots = reinterpret_cast<EncodedJSValue*>(base) + preCapacity;
+        memset(startOfPropertyStorageSlots, 0, (beforeOutOfLineCapacity - outOfLineSize()) * sizeof(EncodedJSValue));
         checkOffsetConsistency();
     }
 
@@ -695,69 +795,64 @@ Structure* Structure::flattenDictionaryStructure(VM& vm, JSObject* object)
 
     size_t afterOutOfLineCapacity = this->outOfLineCapacity();
 
-    if (beforeOutOfLineCapacity != afterOutOfLineCapacity) {
+    if (object->butterfly() && beforeOutOfLineCapacity != afterOutOfLineCapacity) {
         ASSERT(beforeOutOfLineCapacity > afterOutOfLineCapacity);
         // If the object had a Butterfly but after flattening/compacting we no longer have need of it,
         // we need to zero it out because the collector depends on the Structure to know the size for copying.
-        if (object->butterfly() && !afterOutOfLineCapacity && !this->hasIndexingHeader(object))
-            object->setStructureAndButterfly(vm, this, 0);
+        if (!afterOutOfLineCapacity && !this->hasIndexingHeader(object))
+            object->setButterfly(vm, nullptr);
         // If the object was down-sized to the point where the base of the Butterfly is no longer within the 
         // first CopiedBlock::blockSize bytes, we'll get the wrong answer if we try to mask the base back to 
         // the CopiedBlock header. To prevent this case we need to memmove the Butterfly down.
-        else if (object->butterfly())
-            object->shiftButterflyAfterFlattening(vm, beforeOutOfLineCapacity, afterOutOfLineCapacity);
+        else
+            object->shiftButterflyAfterFlattening(locker, vm, this, afterOutOfLineCapacity);
     }
-
-    return this;
-}
-
-PropertyOffset Structure::addPropertyWithoutTransition(VM& vm, PropertyName propertyName, unsigned attributes)
-{
-    DeferGC deferGC(vm.heap);
-    materializePropertyMapIfNecessaryForPinning(vm, deferGC);
     
-    pin();
+    WTF::storeStoreFence();
+    object->setStructureIDDirectly(id());
 
-    return add(vm, propertyName, attributes);
+    // We need to do a writebarrier here because the GC thread might be scanning the butterfly while
+    // we are shuffling properties around. See: https://bugs.webkit.org/show_bug.cgi?id=166989
+    vm.heap.writeBarrier(object);
+
+    return this;
 }
 
-PropertyOffset Structure::removePropertyWithoutTransition(VM& vm, PropertyName propertyName)
+void Structure::pin(const AbstractLocker&, VM& vm, PropertyTable* table)
 {
-    ASSERT(isUncacheableDictionary());
-
-    DeferGC deferGC(vm.heap);
-    materializePropertyMapIfNecessaryForPinning(vm, deferGC);
-
-    pin();
-    return remove(propertyName);
+    setIsPinnedPropertyTable(true);
+    setPropertyTable(vm, table);
+    clearPreviousID();
+    m_nameInPrevious = nullptr;
 }
 
-void Structure::pin()
+void Structure::pinForCaching(const AbstractLocker&, VM& vm, PropertyTable* table)
 {
-    ASSERT(propertyTable());
     setIsPinnedPropertyTable(true);
-    clearPreviousID();
-    m_nameInPrevious.clear();
+    setPropertyTable(vm, table);
+    m_nameInPrevious = nullptr;
 }
 
 void Structure::allocateRareData(VM& vm)
 {
     ASSERT(!hasRareData());
-    StructureRareData* rareData = StructureRareData::create(vm, previous());
+    StructureRareData* rareData = StructureRareData::create(vm, previousID());
     WTF::storeStoreFence();
     m_previousOrRareData.set(vm, this, rareData);
-    WTF::storeStoreFence();
-    setHasRareData(true);
     ASSERT(hasRareData());
 }
 
 WatchpointSet* Structure::ensurePropertyReplacementWatchpointSet(VM& vm, PropertyOffset offset)
 {
     ASSERT(!isUncacheableDictionary());
+
+    // In some places it's convenient to call this with an invalid offset. So, we do the check here.
+    if (!isValidOffset(offset))
+        return nullptr;
     
     if (!hasRareData())
         allocateRareData(vm);
-    ConcurrentJITLocker locker(m_lock);
+    ConcurrentJSLocker locker(m_lock);
     StructureRareData* rareData = this->rareData();
     if (!rareData->m_replacementWatchpointSets) {
         rareData->m_replacementWatchpointSets =
@@ -774,16 +869,13 @@ void Structure::startWatchingPropertyForReplacements(VM& vm, PropertyName proper
 {
     ASSERT(!isUncacheableDictionary());
     
-    PropertyOffset offset = get(vm, propertyName);
-    if (!JSC::isValidOffset(offset))
-        return;
-    
-    startWatchingPropertyForReplacements(vm, offset);
+    startWatchingPropertyForReplacements(vm, get(vm, propertyName));
 }
 
 void Structure::didCachePropertyReplacement(VM& vm, PropertyOffset offset)
 {
-    ensurePropertyReplacementWatchpointSet(vm, offset)->fireAll("Did cache property replacement");
+    RELEASE_ASSERT(isValidOffset(offset));
+    ensurePropertyReplacementWatchpointSet(vm, offset)->fireAll(vm, "Did cache property replacement");
 }
 
 void Structure::startWatchingInternalProperties(VM& vm)
@@ -828,21 +920,15 @@ PropertyMapStatisticsExitLogger::~PropertyMapStatisticsExitLogger()
 
 #endif
 
-PropertyTable* Structure::copyPropertyTable(VM& vm)
-{
-    if (!propertyTable())
-        return 0;
-    return PropertyTable::clone(vm, *propertyTable().get());
-}
-
 PropertyTable* Structure::copyPropertyTableForPinning(VM& vm)
 {
-    if (propertyTable())
-        return PropertyTable::clone(vm, *propertyTable().get());
-    return PropertyTable::create(vm, numberOfSlotsForLastOffset(m_offset, m_inlineCapacity));
+    if (PropertyTable* table = propertyTableOrNull())
+        return PropertyTable::clone(vm, *table);
+    bool setPropertyTable = false;
+    return materializePropertyTable(vm, setPropertyTable);
 }
 
-PropertyOffset Structure::getConcurrently(AtomicStringImpl* uid, unsigned& attributes)
+PropertyOffset Structure::getConcurrently(UniquedStringImpl* uid, unsigned& attributes)
 {
     PropertyOffset result = invalidOffset;
     
@@ -874,108 +960,75 @@ Vector<PropertyMapEntry> Structure::getPropertiesConcurrently()
 
 PropertyOffset Structure::add(VM& vm, PropertyName propertyName, unsigned attributes)
 {
-    GCSafeConcurrentJITLocker locker(m_lock, vm.heap);
-    
-    ASSERT(!JSC::isValidOffset(get(vm, propertyName)));
-
-    checkConsistency();
-    if (attributes & DontEnum)
-        setHasNonEnumerableProperties(true);
-
-    AtomicStringImpl* rep = propertyName.uid();
-
-    if (!propertyTable())
-        createPropertyMap(locker, vm);
-
-    PropertyOffset newOffset = propertyTable()->nextOffset(m_inlineCapacity);
-
-    propertyTable()->add(PropertyMapEntry(rep, newOffset, attributes), m_offset, PropertyTable::PropertyOffsetMayChange);
-    
-    checkConsistency();
-    return newOffset;
+    return add<ShouldPin::No>(
+        vm, propertyName, attributes,
+        [this] (const GCSafeConcurrentJSLocker&, PropertyOffset, PropertyOffset newLastOffset) {
+            setLastOffset(newLastOffset);
+        });
 }
 
 PropertyOffset Structure::remove(PropertyName propertyName)
 {
-    ConcurrentJITLocker locker(m_lock);
-    
-    checkConsistency();
-
-    AtomicStringImpl* rep = propertyName.uid();
-
-    if (!propertyTable())
-        return invalidOffset;
-
-    PropertyTable::find_iterator position = propertyTable()->find(rep);
-    if (!position.first)
-        return invalidOffset;
-
-    PropertyOffset offset = position.first->offset;
-
-    propertyTable()->remove(position);
-    propertyTable()->addDeletedOffset(offset);
-
-    checkConsistency();
-    return offset;
-}
-
-void Structure::createPropertyMap(const GCSafeConcurrentJITLocker&, VM& vm, unsigned capacity)
-{
-    ASSERT(!propertyTable());
-
-    checkConsistency();
-    propertyTable().set(vm, this, PropertyTable::create(vm, capacity));
+    return remove(propertyName, [] (const ConcurrentJSLocker&, PropertyOffset) { });
 }
 
 void Structure::getPropertyNamesFromStructure(VM& vm, PropertyNameArray& propertyNames, EnumerationMode mode)
 {
-    DeferGC deferGC(vm.heap);
-    materializePropertyMapIfNecessary(vm, deferGC);
-    if (!propertyTable())
+    PropertyTable* table = ensurePropertyTableIfNotEmpty(vm);
+    if (!table)
         return;
-
+    
     bool knownUnique = propertyNames.canAddKnownUniqueForStructure();
-
-    PropertyTable::iterator end = propertyTable()->end();
-    for (PropertyTable::iterator iter = propertyTable()->begin(); iter != end; ++iter) {
-        ASSERT(hasNonEnumerableProperties() || !(iter->attributes & DontEnum));
-        if (!iter->key->isUnique() && (!(iter->attributes & DontEnum) || shouldIncludeDontEnumProperties(mode))) {
+    
+    PropertyTable::iterator end = table->end();
+    for (PropertyTable::iterator iter = table->begin(); iter != end; ++iter) {
+        ASSERT(!isQuickPropertyAccessAllowedForEnumeration() || !(iter->attributes & PropertyAttribute::DontEnum));
+        ASSERT(!isQuickPropertyAccessAllowedForEnumeration() || !iter->key->isSymbol());
+        if (!(iter->attributes & PropertyAttribute::DontEnum) || mode.includeDontEnumProperties()) {
+            if (iter->key->isSymbol() && !propertyNames.includeSymbolProperties())
+                continue;
             if (knownUnique)
-                propertyNames.addKnownUnique(iter->key);
+                propertyNames.addUnchecked(iter->key);
             else
                 propertyNames.add(iter->key);
         }
     }
 }
 
-namespace {
-
-class StructureFireDetail : public FireDetail {
-public:
-    StructureFireDetail(const Structure* structure)
-        : m_structure(structure)
-    {
-    }
-    
-    virtual void dump(PrintStream& out) const override
-    {
-        out.print("Structure transition from ", *m_structure);
-    }
+void StructureFireDetail::dump(PrintStream& out) const
+{
+    out.print("Structure transition from ", *m_structure);
+}
 
-private:
-    const Structure* m_structure;
-};
+DeferredStructureTransitionWatchpointFire::DeferredStructureTransitionWatchpointFire(VM& vm, Structure* structure)
+    : DeferredWatchpointFire(vm)
+    , m_structure(structure)
+{
+}
 
-} // anonymous namespace
+DeferredStructureTransitionWatchpointFire::~DeferredStructureTransitionWatchpointFire()
+{
+    fireAll();
+}
 
-void Structure::didTransitionFromThisStructure() const
+void DeferredStructureTransitionWatchpointFire::dump(PrintStream& out) const
 {
-    m_transitionWatchpointSet.fireAll(StructureFireDetail(this));
+    out.print("Structure transition from ", *m_structure);
 }
 
-JSValue Structure::prototypeForLookup(CodeBlock* codeBlock) const
+void Structure::didTransitionFromThisStructure(DeferredStructureTransitionWatchpointFire* deferred) const
 {
-    return prototypeForLookup(codeBlock->globalObject());
+    // If the structure is being watched, and this is the kind of structure that the DFG would
+    // like to watch, then make sure to note for all future versions of this structure that it's
+    // unwise to watch it.
+    if (m_transitionWatchpointSet.isBeingWatched())
+        const_cast<Structure*>(this)->setTransitionWatchpointIsLikelyToBeFired(true);
+
+    if (deferred) {
+        ASSERT(deferred->structure() == this);
+        m_transitionWatchpointSet.fireAll(*vm(), deferred);
+    } else
+        m_transitionWatchpointSet.fireAll(*vm(), StructureFireDetail(this));
 }
 
 void Structure::visitChildren(JSCell* cell, SlotVisitor& visitor)
@@ -983,75 +1036,65 @@ void Structure::visitChildren(JSCell* cell, SlotVisitor& visitor)
     Structure* thisObject = jsCast<Structure*>(cell);
     ASSERT_GC_OBJECT_INHERITS(thisObject, info());
 
-    JSCell::visitChildren(thisObject, visitor);
-    visitor.append(&thisObject->m_globalObject);
+    Base::visitChildren(thisObject, visitor);
+    
+    ConcurrentJSLocker locker(thisObject->m_lock);
+    
+    visitor.append(thisObject->m_globalObject);
     if (!thisObject->isObject())
         thisObject->m_cachedPrototypeChain.clear();
     else {
-        visitor.append(&thisObject->m_prototype);
-        visitor.append(&thisObject->m_cachedPrototypeChain);
+        visitor.append(thisObject->m_prototype);
+        visitor.append(thisObject->m_cachedPrototypeChain);
     }
-    visitor.append(&thisObject->m_previousOrRareData);
-
-    if (thisObject->isPinnedPropertyTable()) {
-        ASSERT(thisObject->m_propertyTableUnsafe);
-        visitor.append(&thisObject->m_propertyTableUnsafe);
-    } else if (thisObject->m_propertyTableUnsafe)
+    visitor.append(thisObject->m_previousOrRareData);
+
+    if (thisObject->isPinnedPropertyTable() || thisObject->isAddingPropertyForTransition()) {
+        // NOTE: This can interleave in pin(), in which case it may see a null property table.
+        // That's fine, because then the barrier will fire and we will scan this again.
+        visitor.append(thisObject->m_propertyTableUnsafe);
+    } else if (visitor.isBuildingHeapSnapshot())
+        visitor.append(thisObject->m_propertyTableUnsafe);
+    else if (thisObject->m_propertyTableUnsafe)
         thisObject->m_propertyTableUnsafe.clear();
 }
 
-bool Structure::prototypeChainMayInterceptStoreTo(VM& vm, PropertyName propertyName)
+bool Structure::isCheapDuringGC(VM& vm)
 {
-    unsigned i = propertyName.asIndex();
-    if (i != PropertyName::NotAnIndex)
-        return anyObjectInChainMayInterceptIndexedAccesses();
+    // FIXME: We could make this even safer by returning false if this structure's property table
+    // has any large property names.
+    // https://bugs.webkit.org/show_bug.cgi?id=157334
     
-    for (Structure* current = this; ;) {
-        JSValue prototype = current->storedPrototype();
-        if (prototype.isNull())
-            return false;
-        
-        current = prototype.asCell()->structure(vm);
-        
-        unsigned attributes;
-        PropertyOffset offset = current->get(vm, propertyName, attributes);
-        if (!JSC::isValidOffset(offset))
-            continue;
-        
-        if (attributes & (ReadOnly | Accessor))
-            return true;
-        
-        return false;
-    }
+    return (!m_globalObject || vm.heap.isMarked(m_globalObject.get()))
+        && (hasPolyProto() || !storedPrototypeObject() || vm.heap.isMarked(storedPrototypeObject()));
 }
 
-PassRefPtr<StructureShape> Structure::toStructureShape(JSValue value)
+bool Structure::markIfCheap(SlotVisitor& visitor)
 {
-    RefPtr<StructureShape> baseShape = StructureShape::create();
-    RefPtr<StructureShape> curShape = baseShape;
+    VM& vm = visitor.vm();
+    if (!isCheapDuringGC(vm))
+        return vm.heap.isMarked(this);
+    
+    visitor.appendUnbarriered(this);
+    return true;
+}
+
+Ref<StructureShape> Structure::toStructureShape(JSValue value, bool& sawPolyProtoStructure)
+{
+    Ref<StructureShape> baseShape = StructureShape::create();
+    RefPtr<StructureShape> curShape = baseShape.ptr();
     Structure* curStructure = this;
     JSValue curValue = value;
+    sawPolyProtoStructure = false;
     while (curStructure) {
-        Vector<Structure*, 8> structures;
-        Structure* structure;
-        PropertyTable* table;
-
-        curStructure->findStructuresAndMapForMaterialization(structures, structure, table);
-        if (table) {
-            PropertyTable::iterator iter = table->begin();
-            PropertyTable::iterator end = table->end();
-            for (; iter != end; ++iter)
-                curShape->addProperty(iter->key);
-            
-            structure->m_lock.unlock();
-        }
-        for (unsigned i = structures.size(); i--;) {
-            Structure* structure = structures[i];
-            if (structure->m_nameInPrevious)
-                curShape->addProperty(structure->m_nameInPrevious.get());
-        }
+        sawPolyProtoStructure |= curStructure->hasPolyProto();
+        curStructure->forEachPropertyConcurrently(
+            [&] (const PropertyMapEntry& entry) -> bool {
+                if (!PropertyName(entry.key).isPrivateName())
+                    curShape->addProperty(*entry.key);
+                return true;
+            });
 
-        
         if (JSObject* curObject = curValue.getObject())
             curShape->setConstructorName(JSObject::calculatedClassName(curObject));
         else
@@ -1062,24 +1105,22 @@ PassRefPtr<StructureShape> Structure::toStructureShape(JSValue value)
 
         curShape->markAsFinal();
 
-        if (curStructure->storedPrototypeStructure()) {
-            RefPtr<StructureShape> newShape = StructureShape::create();
-            curShape->setProto(newShape);
-            curShape = newShape;
-            curValue = curStructure->storedPrototype();
-        }
+        if (!curValue.isObject())
+            break;
 
-        curStructure = curStructure->storedPrototypeStructure();
+        JSObject* object = asObject(curValue);
+        JSObject* prototypeObject = object->structure()->storedPrototypeObject(object);
+        if (!prototypeObject)
+            break;
+
+        auto newShape = StructureShape::create();
+        curShape->setProto(newShape.copyRef());
+        curShape = WTFMove(newShape);
+        curValue = prototypeObject;
+        curStructure = prototypeObject->structure();
     }
     
-    return baseShape.release();
-}
-
-bool Structure::canUseForAllocationsOf(Structure* other)
-{
-    return inlineCapacity() == other->inlineCapacity()
-        && storedPrototype() == other->storedPrototype()
-        && objectInitializationBlob() == other->objectInitializationBlob();
+    return baseShape;
 }
 
 void Structure::dump(PrintStream& out) const
@@ -1094,10 +1135,30 @@ void Structure::dump(PrintStream& out) const
             return true;
         });
     
-    out.print("}, ", IndexingTypeDump(indexingType()));
+    out.print("}, ", IndexingTypeDump(indexingMode()));
     
-    if (m_prototype.get().isCell())
+    if (hasPolyProto())
+        out.print(", PolyProto offset:", knownPolyProtoOffset);
+    else if (m_prototype.get().isCell())
         out.print(", Proto:", RawPointer(m_prototype.get().asCell()));
+
+    switch (dictionaryKind()) {
+    case NoneDictionaryKind:
+        if (hasBeenDictionary())
+            out.print(", Has been dictionary");
+        break;
+    case CachedDictionaryKind:
+        out.print(", Dictionary");
+        break;
+    case UncachedDictionaryKind:
+        out.print(", UncacheableDictionary");
+        break;
+    }
+
+    if (transitionWatchpointSetIsStillValid())
+        out.print(", Leaf");
+    else if (transitionWatchpointIsLikelyToBeFired())
+        out.print(", Shady leaf");
     
     out.print("]");
 }
@@ -1120,91 +1181,6 @@ void Structure::dumpContextHeader(PrintStream& out)
     out.print("Structures:");
 }
 
-#if DO_PROPERTYMAP_CONSTENCY_CHECK
-
-void PropertyTable::checkConsistency()
-{
-    ASSERT(m_indexSize >= PropertyTable::MinimumTableSize);
-    ASSERT(m_indexMask);
-    ASSERT(m_indexSize == m_indexMask + 1);
-    ASSERT(!(m_indexSize & m_indexMask));
-
-    ASSERT(m_keyCount <= m_indexSize / 2);
-    ASSERT(m_keyCount + m_deletedCount <= m_indexSize / 2);
-    ASSERT(m_deletedCount <= m_indexSize / 4);
-
-    unsigned indexCount = 0;
-    unsigned deletedIndexCount = 0;
-    for (unsigned a = 0; a != m_indexSize; ++a) {
-        unsigned entryIndex = m_index[a];
-        if (entryIndex == PropertyTable::EmptyEntryIndex)
-            continue;
-        if (entryIndex == deletedEntryIndex()) {
-            ++deletedIndexCount;
-            continue;
-        }
-        ASSERT(entryIndex < deletedEntryIndex());
-        ASSERT(entryIndex - 1 <= usedCount());
-        ++indexCount;
-
-        for (unsigned b = a + 1; b != m_indexSize; ++b)
-            ASSERT(m_index[b] != entryIndex);
-    }
-    ASSERT(indexCount == m_keyCount);
-    ASSERT(deletedIndexCount == m_deletedCount);
-
-    ASSERT(!table()[deletedEntryIndex() - 1].key);
-
-    unsigned nonEmptyEntryCount = 0;
-    for (unsigned c = 0; c < usedCount(); ++c) {
-        StringImpl* rep = table()[c].key;
-        if (rep == PROPERTY_MAP_DELETED_ENTRY_KEY)
-            continue;
-        ++nonEmptyEntryCount;
-        unsigned i = rep->existingHash();
-        unsigned k = 0;
-        unsigned entryIndex;
-        while (1) {
-            entryIndex = m_index[i & m_indexMask];
-            ASSERT(entryIndex != PropertyTable::EmptyEntryIndex);
-            if (rep == table()[entryIndex - 1].key)
-                break;
-            if (k == 0)
-                k = 1 | doubleHash(rep->existingHash());
-            i += k;
-        }
-        ASSERT(entryIndex == c + 1);
-    }
-
-    ASSERT(nonEmptyEntryCount == m_keyCount);
-}
-
-void Structure::checkConsistency()
-{
-    checkOffsetConsistency();
-
-    if (!propertyTable())
-        return;
-
-    if (!hasNonEnumerableProperties()) {
-        PropertyTable::iterator end = propertyTable()->end();
-        for (PropertyTable::iterator iter = propertyTable()->begin(); iter != end; ++iter) {
-            ASSERT(!(iter->attributes & DontEnum));
-        }
-    }
-
-    propertyTable()->checkConsistency();
-}
-
-#else
-
-inline void Structure::checkConsistency()
-{
-    checkOffsetConsistency();
-}
-
-#endif // DO_PROPERTYMAP_CONSTENCY_CHECK
-
 bool ClassInfo::hasStaticSetterOrReadonlyProperties() const
 {
     for (const ClassInfo* ci = this; ci; ci = ci->parentClass) {
@@ -1233,13 +1209,7 @@ JSPropertyNameEnumerator* Structure::cachedPropertyNameEnumerator() const
 
 bool Structure::canCachePropertyNameEnumerator() const
 {
-    if (isDictionary())
-        return false;
-
-    if (hasIndexedProperties(indexingType()))
-        return false;
-
-    if (typeInfo().overridesGetPropertyNames())
+    if (!this->canCacheOwnKeys())
         return false;
 
     StructureChain* structureChain = m_cachedPrototypeChain.get();
@@ -1247,18 +1217,19 @@ bool Structure::canCachePropertyNameEnumerator() const
     WriteBarrier<Structure>* structure = structureChain->head();
     while (true) {
         if (!structure->get())
-            break;
-        if (structure->get()->typeInfo().overridesGetPropertyNames())
+            return true;
+        if (!structure->get()->canCacheOwnKeys())
             return false;
         structure++;
     }
-    
+
+    ASSERT_NOT_REACHED();
     return true;
 }
     
-bool Structure::canAccessPropertiesQuickly() const
+bool Structure::canAccessPropertiesQuicklyForEnumeration() const
 {
-    if (hasNonEnumerableProperties())
+    if (!isQuickPropertyAccessAllowedForEnumeration())
         return false;
     if (hasGetterSetterProperties())
         return false;