X-Git-Url: https://git.saurik.com/apple/javascriptcore.git/blobdiff_plain/14957cd040308e3eeec43d26bae5d76da13fcd85..refs/heads/master:/runtime/Structure.cpp diff --git a/runtime/Structure.cpp b/runtime/Structure.cpp index a3fda54..3985805 100644 --- a/runtime/Structure.cpp +++ b/runtime/Structure.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008, 2009 Apple Inc. All rights reserved. + * Copyright (C) 2008, 2009, 2013, 2014 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -10,10 +10,10 @@ * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * - * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR @@ -26,18 +26,22 @@ #include "config.h" #include "Structure.h" -#include "Identifier.h" +#include "CodeBlock.h" +#include "DumpContext.h" +#include "JSCInlines.h" #include "JSObject.h" -#include "JSPropertyNameIterator.h" +#include "JSPropertyNameEnumerator.h" #include "Lookup.h" +#include "PropertyMapHashTable.h" #include "PropertyNameArray.h" #include "StructureChain.h" +#include "StructureRareDataInlines.h" +#include "WeakGCMapInlines.h" +#include +#include #include #include - -#if ENABLE(JSC_MULTIPLE_THREADS) #include -#endif #define DUMP_STRUCTURE_ID_STATISTICS 0 @@ -50,77 +54,45 @@ using namespace std; using namespace WTF; -#if DUMP_PROPERTYMAP_STATS - -int numProbes; -int numCollisions; -int numRehashes; -int numRemoves; - -#endif - namespace JSC { #if DUMP_STRUCTURE_ID_STATISTICS static HashSet& liveStructureSet = *(new HashSet); #endif -bool StructureTransitionTable::contains(StringImpl* rep, unsigned attributes) const +bool StructureTransitionTable::contains(UniquedStringImpl* rep, unsigned attributes) const { if (isUsingSingleSlot()) { Structure* transition = singleTransition(); - return transition && transition->m_nameInPrevious == rep && transition->m_attributesInPrevious == attributes; + return transition && transition->m_nameInPrevious == rep && transition->attributesInPrevious() == attributes; } - return map()->contains(make_pair(rep, attributes)); + return map()->get(std::make_pair(rep, attributes)); } -inline Structure* StructureTransitionTable::get(StringImpl* rep, unsigned attributes) const +Structure* StructureTransitionTable::get(UniquedStringImpl* rep, unsigned attributes) const { if (isUsingSingleSlot()) { Structure* transition = singleTransition(); - return (transition && transition->m_nameInPrevious == rep && transition->m_attributesInPrevious == attributes) ? transition : 0; - } - return map()->get(make_pair(rep, attributes)); -} - -inline void StructureTransitionTable::remove(Structure* structure) -{ - if (isUsingSingleSlot()) { - // If more than one transition had been added, then we wouldn't be in - // single slot mode (even despecifying a from a specific value triggers - // map mode). - // As such, the passed structure *must* be the existing transition. - ASSERT(singleTransition() == structure); - clearSingleTransition(); - } else { - // Check whether a mapping exists for structure's key, and whether the - // entry is structure (the latter check may fail if we initially had a - // transition with a specific value, and this has been despecified). - - // Newer versions of the STL have an std::make_pair function that takes rvalue references. - // When either of the parameters are bitfields, the C++ compiler will try to bind them as lvalues, which is invalid. To work around this, use unary "+" to make the parameter an rvalue. - // See https://bugs.webkit.org/show_bug.cgi?id=59261 for more details - TransitionMap::iterator entry = map()->find(make_pair(structure->m_nameInPrevious, +structure->m_attributesInPrevious)); - if (entry != map()->end() && structure == entry.get().second) - map()->remove(entry); + return (transition && transition->m_nameInPrevious == rep && transition->attributesInPrevious() == attributes) ? transition : 0; } + return map()->get(std::make_pair(rep, attributes)); } -inline void StructureTransitionTable::add(JSGlobalData& globalData, Structure* structure) +void StructureTransitionTable::add(VM& vm, Structure* structure) { if (isUsingSingleSlot()) { Structure* existingTransition = singleTransition(); // This handles the first transition being added. if (!existingTransition) { - setSingleTransition(globalData, structure); + setSingleTransition(vm, structure); return; } // This handles the second transition being added // (or the first transition being despecified!) - setMap(new TransitionMap()); - add(globalData, existingTransition); + setMap(new TransitionMap(vm)); + add(vm, existingTransition); } // Add the structure to the map. @@ -128,13 +100,7 @@ inline void StructureTransitionTable::add(JSGlobalData& globalData, Structure* s // Newer versions of the STL have an std::make_pair function that takes rvalue references. // When either of the parameters are bitfields, the C++ compiler will try to bind them as lvalues, which is invalid. To work around this, use unary "+" to make the parameter an rvalue. // See https://bugs.webkit.org/show_bug.cgi?id=59261 for more details - std::pair result = map()->add(globalData, make_pair(structure->m_nameInPrevious, +structure->m_attributesInPrevious), structure); - if (!result.second) { - // There already is an entry! - we should only hit this when despecifying. - ASSERT(result.first.get().second->m_specificValueInPrevious); - ASSERT(!structure->m_specificValueInPrevious); - map()->set(result.first, structure); - } + map()->set(std::make_pair(structure->m_nameInPrevious.get(), +structure->attributesInPrevious()), structure); } void Structure::dumpStatistics() @@ -153,7 +119,7 @@ void Structure::dumpStatistics() switch (structure->m_transitionTable.size()) { case 0: ++numberLeaf; - if (!structure->m_previous) + if (!structure->previousID()) ++numberSingletons; break; @@ -162,380 +128,510 @@ void Structure::dumpStatistics() break; } - if (structure->m_propertyTable) { + if (structure->propertyTable()) { ++numberWithPropertyMaps; - totalPropertyMapsSize += structure->m_propertyTable->sizeInMemory(); + totalPropertyMapsSize += structure->propertyTable()->sizeInMemory(); } } - printf("Number of live Structures: %d\n", liveStructureSet.size()); - printf("Number of Structures using the single item optimization for transition map: %d\n", numberUsingSingleSlot); - printf("Number of Structures that are leaf nodes: %d\n", numberLeaf); - printf("Number of Structures that singletons: %d\n", numberSingletons); - printf("Number of Structures with PropertyMaps: %d\n", numberWithPropertyMaps); + dataLogF("Number of live Structures: %d\n", liveStructureSet.size()); + dataLogF("Number of Structures using the single item optimization for transition map: %d\n", numberUsingSingleSlot); + dataLogF("Number of Structures that are leaf nodes: %d\n", numberLeaf); + dataLogF("Number of Structures that singletons: %d\n", numberSingletons); + dataLogF("Number of Structures with PropertyMaps: %d\n", numberWithPropertyMaps); - printf("Size of a single Structures: %d\n", static_cast(sizeof(Structure))); - printf("Size of sum of all property maps: %d\n", totalPropertyMapsSize); - printf("Size of average of all property maps: %f\n", static_cast(totalPropertyMapsSize) / static_cast(liveStructureSet.size())); + dataLogF("Size of a single Structures: %d\n", static_cast(sizeof(Structure))); + dataLogF("Size of sum of all property maps: %d\n", totalPropertyMapsSize); + dataLogF("Size of average of all property maps: %f\n", static_cast(totalPropertyMapsSize) / static_cast(liveStructureSet.size())); #else - printf("Dumping Structure statistics is not enabled.\n"); + dataLogF("Dumping Structure statistics is not enabled.\n"); #endif } -Structure::Structure(JSGlobalData& globalData, JSValue prototype, const TypeInfo& typeInfo, unsigned anonymousSlotCount, const ClassInfo* classInfo) - : JSCell(globalData, globalData.structureStructure.get()) - , m_typeInfo(typeInfo) - , m_prototype(globalData, this, prototype) +Structure::Structure(VM& vm, JSGlobalObject* globalObject, JSValue prototype, const TypeInfo& typeInfo, const ClassInfo* classInfo, IndexingType indexingType, unsigned inlineCapacity) + : JSCell(vm, vm.structureStructure.get()) + , m_blob(vm.heap.structureIDTable().allocateID(this), indexingType, typeInfo) + , m_outOfLineTypeFlags(typeInfo.outOfLineTypeFlags()) + , m_globalObject(vm, this, globalObject, WriteBarrier::MayBeNull) + , m_prototype(vm, this, prototype) , m_classInfo(classInfo) - , m_propertyStorageCapacity(typeInfo.isFinal() ? JSFinalObject_inlineStorageCapacity : JSNonFinalObject_inlineStorageCapacity) - , m_offset(noOffset) - , m_dictionaryKind(NoneDictionaryKind) - , m_isPinnedPropertyTable(false) - , m_hasGetterSetterProperties(false) - , m_hasNonEnumerableProperties(false) - , m_attributesInPrevious(0) - , m_specificFunctionThrashCount(0) - , m_anonymousSlotCount(anonymousSlotCount) - , m_preventExtensions(false) - , m_didTransition(false) -{ - ASSERT(m_prototype); - ASSERT(m_prototype.isObject() || m_prototype.isNull()); -} - -const ClassInfo Structure::s_info = { "Structure", 0, 0, 0 }; - -Structure::Structure(JSGlobalData& globalData) - : JSCell(globalData, this, CreatingEarlyCell) - , m_typeInfo(CompoundType, OverridesVisitChildren) - , m_prototype(globalData, this, jsNull()) - , m_classInfo(&s_info) - , m_propertyStorageCapacity(0) - , m_offset(noOffset) - , m_dictionaryKind(NoneDictionaryKind) - , m_isPinnedPropertyTable(false) - , m_hasGetterSetterProperties(false) - , m_hasNonEnumerableProperties(false) - , m_attributesInPrevious(0) - , m_specificFunctionThrashCount(0) - , m_anonymousSlotCount(0) - , m_preventExtensions(false) - , m_didTransition(false) -{ - ASSERT(m_prototype); - ASSERT(m_prototype.isNull()); - ASSERT(!globalData.structureStructure); -} - -Structure::Structure(JSGlobalData& globalData, const Structure* previous) - : JSCell(globalData, globalData.structureStructure.get()) - , m_typeInfo(previous->typeInfo()) - , m_prototype(globalData, this, previous->storedPrototype()) - , m_classInfo(previous->m_classInfo) - , m_propertyStorageCapacity(previous->m_propertyStorageCapacity) - , m_offset(noOffset) - , m_dictionaryKind(NoneDictionaryKind) - , m_isPinnedPropertyTable(false) - , m_hasGetterSetterProperties(previous->m_hasGetterSetterProperties) - , m_hasNonEnumerableProperties(previous->m_hasNonEnumerableProperties) - , m_attributesInPrevious(0) - , m_specificFunctionThrashCount(previous->m_specificFunctionThrashCount) - , m_anonymousSlotCount(previous->anonymousSlotCount()) - , m_preventExtensions(previous->m_preventExtensions) - , m_didTransition(true) -{ - ASSERT(m_prototype); - ASSERT(m_prototype.isObject() || m_prototype.isNull()); + , m_transitionWatchpointSet(IsWatched) + , m_offset(invalidOffset) + , m_inlineCapacity(inlineCapacity) + , m_bitField(0) +{ + setDictionaryKind(NoneDictionaryKind); + setIsPinnedPropertyTable(false); + setHasGetterSetterProperties(classInfo->hasStaticSetterOrReadonlyProperties()); + setHasCustomGetterSetterProperties(false); + setHasReadOnlyOrGetterSetterPropertiesExcludingProto(classInfo->hasStaticSetterOrReadonlyProperties()); + setHasNonEnumerableProperties(false); + setAttributesInPrevious(0); + setPreventExtensions(false); + setDidTransition(false); + setStaticFunctionsReified(false); + setHasRareData(false); + + ASSERT(inlineCapacity <= JSFinalObject::maxInlineCapacity()); + ASSERT(static_cast(inlineCapacity) < firstOutOfLineOffset); + ASSERT(!hasRareData()); + ASSERT(hasReadOnlyOrGetterSetterPropertiesExcludingProto() || !m_classInfo->hasStaticSetterOrReadonlyProperties()); + ASSERT(hasGetterSetterProperties() || !m_classInfo->hasStaticSetterOrReadonlyProperties()); } -Structure::~Structure() +const ClassInfo Structure::s_info = { "Structure", 0, 0, CREATE_METHOD_TABLE(Structure) }; + +Structure::Structure(VM& vm) + : JSCell(CreatingEarlyCell) + , m_prototype(vm, this, jsNull()) + , m_classInfo(info()) + , m_transitionWatchpointSet(IsWatched) + , m_offset(invalidOffset) + , m_inlineCapacity(0) + , m_bitField(0) { + setDictionaryKind(NoneDictionaryKind); + setIsPinnedPropertyTable(false); + setHasGetterSetterProperties(m_classInfo->hasStaticSetterOrReadonlyProperties()); + setHasCustomGetterSetterProperties(false); + setHasReadOnlyOrGetterSetterPropertiesExcludingProto(m_classInfo->hasStaticSetterOrReadonlyProperties()); + setHasNonEnumerableProperties(false); + setAttributesInPrevious(0); + setPreventExtensions(false); + setDidTransition(false); + setStaticFunctionsReified(false); + setHasRareData(false); + + 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()); } -void Structure::materializePropertyMap(JSGlobalData& globalData) +Structure::Structure(VM& vm, Structure* previous) + : JSCell(vm, vm.structureStructure.get()) + , m_prototype(vm, this, previous->storedPrototype()) + , m_classInfo(previous->m_classInfo) + , m_transitionWatchpointSet(IsWatched) + , m_offset(invalidOffset) + , m_inlineCapacity(previous->m_inlineCapacity) + , m_bitField(0) { - ASSERT(structure()->classInfo() == &s_info); - ASSERT(!m_propertyTable); + setDictionaryKind(previous->dictionaryKind()); + setIsPinnedPropertyTable(previous->hasBeenFlattenedBefore()); + setHasGetterSetterProperties(previous->hasGetterSetterProperties()); + setHasCustomGetterSetterProperties(previous->hasCustomGetterSetterProperties()); + setHasReadOnlyOrGetterSetterPropertiesExcludingProto(previous->hasReadOnlyOrGetterSetterPropertiesExcludingProto()); + setHasNonEnumerableProperties(previous->hasNonEnumerableProperties()); + setAttributesInPrevious(0); + setPreventExtensions(previous->preventExtensions()); + setDidTransition(true); + setStaticFunctionsReified(previous->staticFunctionsReified()); + setHasRareData(false); + + TypeInfo typeInfo = previous->typeInfo(); + m_blob = StructureIDBlob(vm.heap.structureIDTable().allocateID(this), previous->indexingTypeIncludingHistory(), typeInfo); + m_outOfLineTypeFlags = typeInfo.outOfLineTypeFlags(); + + ASSERT(!previous->typeInfo().structureIsImmortal()); + setPreviousID(vm, previous); + + previous->didTransitionFromThisStructure(); + if (previous->m_globalObject) + m_globalObject.set(vm, this, previous->m_globalObject.get()); + ASSERT(hasReadOnlyOrGetterSetterPropertiesExcludingProto() || !m_classInfo->hasStaticSetterOrReadonlyProperties()); + ASSERT(hasGetterSetterProperties() || !m_classInfo->hasStaticSetterOrReadonlyProperties()); +} - Vector structures; - structures.append(this); +Structure::~Structure() +{ + if (typeInfo().structureIsImmortal()) + return; + Heap::heap(this)->structureIDTable().deallocateID(this, m_blob.structureID()); +} - Structure* structure = this; +void Structure::destroy(JSCell* cell) +{ + static_cast(cell)->Structure::~Structure(); +} - // Search for the last Structure with a property table. - while ((structure = structure->previousID())) { - if (structure->m_isPinnedPropertyTable) { - ASSERT(structure->m_propertyTable); - ASSERT(!structure->m_previous); +void Structure::findStructuresAndMapForMaterialization(Vector& structures, Structure*& structure, PropertyTable*& table) +{ + ASSERT(structures.isEmpty()); + table = 0; - m_propertyTable = structure->m_propertyTable->copy(globalData, 0, m_offset + 1); - break; + for (structure = this; structure; structure = structure->previousID()) { + structure->m_lock.lock(); + + table = structure->propertyTable().get(); + if (table) { + // Leave the structure locked, so that the caller can do things to it atomically + // before it loses its property table. + return; } - + structures.append(structure); + structure->m_lock.unlock(); } + + ASSERT(!structure); + ASSERT(!table); +} - if (!m_propertyTable) - createPropertyMap(m_offset + 1); +void Structure::materializePropertyMap(VM& vm) +{ + ASSERT(structure()->classInfo() == info()); + ASSERT(!propertyTable()); - for (ptrdiff_t i = structures.size() - 2; i >= 0; --i) { + Vector structures; + Structure* structure; + PropertyTable* table; + + findStructuresAndMapForMaterialization(structures, structure, table); + + if (table) { + table = table->copy(vm, numberOfSlotsForLastOffset(m_offset, m_inlineCapacity)); + structure->m_lock.unlock(); + } + + // 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); + + for (size_t i = structures.size(); i--;) { structure = structures[i]; - PropertyMapEntry entry(globalData, this, structure->m_nameInPrevious.get(), m_anonymousSlotCount + structure->m_offset, structure->m_attributesInPrevious, structure->m_specificValueInPrevious.get()); - m_propertyTable->add(entry); + if (!structure->m_nameInPrevious) + continue; + PropertyMapEntry entry(structure->m_nameInPrevious.get(), structure->m_offset, structure->attributesInPrevious()); + propertyTable()->add(entry, m_offset, PropertyTable::PropertyOffsetMustNotChange); } + + checkOffsetConsistency(); } -void Structure::growPropertyStorageCapacity() +Structure* Structure::addPropertyTransitionToExistingStructureImpl(Structure* structure, UniquedStringImpl* uid, unsigned attributes, PropertyOffset& offset) { - if (isUsingInlineStorage()) - m_propertyStorageCapacity = JSObject::baseExternalStorageCapacity; - else - m_propertyStorageCapacity *= 2; + ASSERT(!structure->isDictionary()); + ASSERT(structure->isObject()); + + if (Structure* existingTransition = structure->m_transitionTable.get(uid, attributes)) { + validateOffset(existingTransition->m_offset, existingTransition->inlineCapacity()); + offset = existingTransition->m_offset; + return existingTransition; + } + + return 0; } -void Structure::despecifyDictionaryFunction(JSGlobalData& globalData, const Identifier& propertyName) +Structure* Structure::addPropertyTransitionToExistingStructure(Structure* structure, PropertyName propertyName, unsigned attributes, PropertyOffset& offset) { - StringImpl* rep = propertyName.impl(); - - materializePropertyMapIfNecessary(globalData); + ASSERT(!isCompilationThread()); + return addPropertyTransitionToExistingStructureImpl(structure, propertyName.uid(), attributes, offset); +} - ASSERT(isDictionary()); - ASSERT(m_propertyTable); +Structure* Structure::addPropertyTransitionToExistingStructureConcurrently(Structure* structure, UniquedStringImpl* uid, unsigned attributes, PropertyOffset& offset) +{ + ConcurrentJITLocker locker(structure->m_lock); + return addPropertyTransitionToExistingStructureImpl(structure, uid, attributes, offset); +} - PropertyMapEntry* entry = m_propertyTable->find(rep).first; - ASSERT(entry); - entry->specificValue.clear(); +bool Structure::anyObjectInChainMayInterceptIndexedAccesses() const +{ + for (const Structure* current = this; ;) { + if (current->mayInterceptIndexedAccesses()) + return true; + + JSValue prototype = current->storedPrototype(); + if (prototype.isNull()) + return false; + + current = asObject(prototype)->structure(); + } } -Structure* Structure::addPropertyTransitionToExistingStructure(Structure* structure, const Identifier& propertyName, unsigned attributes, JSCell* specificValue, size_t& offset) +bool Structure::holesMustForwardToPrototype(VM& vm) const { - ASSERT(!structure->isDictionary()); - ASSERT(structure->typeInfo().type() == ObjectType); - - if (Structure* existingTransition = structure->m_transitionTable.get(propertyName.impl(), attributes)) { - JSCell* specificValueInPrevious = existingTransition->m_specificValueInPrevious.get(); - if (specificValueInPrevious && specificValueInPrevious != specificValue) - return 0; - ASSERT(existingTransition->m_offset != noOffset); - offset = existingTransition->m_offset + existingTransition->m_anonymousSlotCount; - ASSERT(offset >= structure->m_anonymousSlotCount); - ASSERT(structure->m_anonymousSlotCount == existingTransition->m_anonymousSlotCount); - return existingTransition; + if (this->mayInterceptIndexedAccesses()) + return true; + + JSValue prototype = this->storedPrototype(); + if (!prototype.isObject()) + return false; + JSObject* object = asObject(prototype); + + while (true) { + Structure& structure = *object->structure(vm); + if (hasIndexedProperties(object->indexingType()) || structure.mayInterceptIndexedAccesses()) + return true; + prototype = structure.storedPrototype(); + if (!prototype.isObject()) + return false; + object = asObject(prototype); } - return 0; + RELEASE_ASSERT_NOT_REACHED(); + return false; +} + +bool Structure::needsSlowPutIndexing() const +{ + return anyObjectInChainMayInterceptIndexedAccesses() + || globalObject()->isHavingABadTime(); } -Structure* Structure::addPropertyTransition(JSGlobalData& globalData, Structure* structure, const Identifier& propertyName, unsigned attributes, JSCell* specificValue, size_t& offset) +NonPropertyTransition Structure::suggestedArrayStorageTransition() const { - // If we have a specific function, we may have got to this point if there is - // already a transition with the correct property name and attributes, but - // specialized to a different function. In this case we just want to give up - // and despecialize the transition. - // In this case we clear the value of specificFunction which will result - // in us adding a non-specific transition, and any subsequent lookup in - // Structure::addPropertyTransitionToExistingStructure will just use that. - if (specificValue && structure->m_transitionTable.contains(propertyName.impl(), attributes)) - specificValue = 0; + if (needsSlowPutIndexing()) + return AllocateSlowPutArrayStorage; + + return AllocateArrayStorage; +} +Structure* Structure::addPropertyTransition(VM& vm, Structure* structure, PropertyName propertyName, unsigned attributes, PropertyOffset& offset, PutPropertySlot::Context context) +{ ASSERT(!structure->isDictionary()); - ASSERT(structure->typeInfo().type() == ObjectType); - ASSERT(!Structure::addPropertyTransitionToExistingStructure(structure, propertyName, attributes, specificValue, offset)); + ASSERT(structure->isObject()); + ASSERT(!Structure::addPropertyTransitionToExistingStructure(structure, propertyName, attributes, offset)); - if (structure->m_specificFunctionThrashCount == maxSpecificFunctionThrashCount) - specificValue = 0; - - if (structure->transitionCount() > s_maxTransitionLength) { - Structure* transition = toCacheableDictionaryTransition(globalData, structure); + int maxTransitionLength; + if (context == PutPropertySlot::PutById) + maxTransitionLength = s_maxTransitionLengthForNonEvalPutById; + else + maxTransitionLength = s_maxTransitionLength; + if (structure->transitionCount() > maxTransitionLength) { + Structure* transition = toCacheableDictionaryTransition(vm, structure); ASSERT(structure != transition); - offset = transition->putSpecificValue(globalData, propertyName, attributes, specificValue); - ASSERT(offset >= structure->m_anonymousSlotCount); - ASSERT(structure->m_anonymousSlotCount == transition->m_anonymousSlotCount); - if (transition->propertyStorageSize() > transition->propertyStorageCapacity()) - transition->growPropertyStorageCapacity(); + offset = transition->add(vm, propertyName, attributes); return transition; } + + Structure* transition = create(vm, structure); - Structure* transition = create(globalData, structure); + transition->m_cachedPrototypeChain.setMayBeNull(vm, transition, structure->m_cachedPrototypeChain.get()); + transition->m_nameInPrevious = propertyName.uid(); + transition->setAttributesInPrevious(attributes); + transition->propertyTable().set(vm, transition, structure->takePropertyTableOrCloneIfPinned(vm)); + transition->m_offset = structure->m_offset; - transition->m_cachedPrototypeChain.setMayBeNull(globalData, transition, structure->m_cachedPrototypeChain.get()); - transition->m_previous.set(globalData, transition, structure); - transition->m_nameInPrevious = propertyName.impl(); - transition->m_attributesInPrevious = attributes; - transition->m_specificValueInPrevious.setMayBeNull(globalData, transition, specificValue); + offset = transition->add(vm, propertyName, attributes); - if (structure->m_propertyTable) { - if (structure->m_isPinnedPropertyTable) - transition->m_propertyTable = structure->m_propertyTable->copy(globalData, 0, structure->m_propertyTable->size() + 1); - else - transition->m_propertyTable = structure->m_propertyTable.release(); - } else { - if (structure->m_previous) - transition->materializePropertyMap(globalData); - else - transition->createPropertyMap(); + checkOffset(transition->m_offset, transition->inlineCapacity()); + { + ConcurrentJITLocker locker(structure->m_lock); + structure->m_transitionTable.add(vm, transition); } - - offset = transition->putSpecificValue(globalData, propertyName, attributes, specificValue); - ASSERT(offset >= structure->m_anonymousSlotCount); - ASSERT(structure->m_anonymousSlotCount == transition->m_anonymousSlotCount); - if (transition->propertyStorageSize() > transition->propertyStorageCapacity()) - transition->growPropertyStorageCapacity(); - - transition->m_offset = offset - structure->m_anonymousSlotCount; - ASSERT(structure->anonymousSlotCount() == transition->anonymousSlotCount()); - structure->m_transitionTable.add(globalData, transition); + transition->checkOffsetConsistency(); + structure->checkOffsetConsistency(); return transition; } -Structure* Structure::removePropertyTransition(JSGlobalData& globalData, Structure* structure, const Identifier& propertyName, size_t& offset) +Structure* Structure::removePropertyTransition(VM& vm, Structure* structure, PropertyName propertyName, PropertyOffset& offset) { ASSERT(!structure->isUncacheableDictionary()); - Structure* transition = toUncacheableDictionaryTransition(globalData, structure); + Structure* transition = toUncacheableDictionaryTransition(vm, structure); offset = transition->remove(propertyName); - ASSERT(offset >= structure->m_anonymousSlotCount); - ASSERT(structure->m_anonymousSlotCount == transition->m_anonymousSlotCount); + transition->checkOffsetConsistency(); return transition; } -Structure* Structure::changePrototypeTransition(JSGlobalData& globalData, Structure* structure, JSValue prototype) +Structure* Structure::changePrototypeTransition(VM& vm, Structure* structure, JSValue prototype) { - Structure* transition = create(globalData, structure); + Structure* transition = create(vm, structure); - transition->m_prototype.set(globalData, transition, prototype); + transition->m_prototype.set(vm, transition, prototype); - // 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->pin(); - structure->materializePropertyMapIfNecessary(globalData); - transition->m_propertyTable = structure->copyPropertyTable(globalData, transition); - transition->m_isPinnedPropertyTable = true; - - ASSERT(structure->anonymousSlotCount() == transition->anonymousSlotCount()); + transition->checkOffsetConsistency(); return transition; } -Structure* Structure::despecifyFunctionTransition(JSGlobalData& globalData, Structure* structure, const Identifier& replaceFunction) +Structure* Structure::attributeChangeTransition(VM& vm, Structure* structure, PropertyName propertyName, unsigned attributes) { - ASSERT(structure->m_specificFunctionThrashCount < maxSpecificFunctionThrashCount); - Structure* transition = create(globalData, structure); - - ++transition->m_specificFunctionThrashCount; - - // Don't set m_offset, as one can not transition to this. - - structure->materializePropertyMapIfNecessary(globalData); - transition->m_propertyTable = structure->copyPropertyTable(globalData, transition); - transition->m_isPinnedPropertyTable = true; - - if (transition->m_specificFunctionThrashCount == maxSpecificFunctionThrashCount) - transition->despecifyAllFunctions(globalData); - else { - bool removed = transition->despecifyFunction(globalData, replaceFunction); - ASSERT_UNUSED(removed, removed); + DeferGC deferGC(vm.heap); + if (!structure->isUncacheableDictionary()) { + Structure* transition = create(vm, structure); + + structure->materializePropertyMapIfNecessary(vm, deferGC); + transition->propertyTable().set(vm, transition, structure->copyPropertyTableForPinning(vm)); + transition->m_offset = structure->m_offset; + transition->pin(); + + structure = transition; } - - ASSERT(structure->anonymousSlotCount() == transition->anonymousSlotCount()); - return transition; -} - -Structure* Structure::getterSetterTransition(JSGlobalData& globalData, Structure* structure) -{ - Structure* transition = create(globalData, structure); - // Don't set m_offset, as one can not transition to this. + ASSERT(structure->propertyTable()); + PropertyMapEntry* entry = structure->propertyTable()->get(propertyName.uid()); + ASSERT(entry); + entry->attributes = attributes; - structure->materializePropertyMapIfNecessary(globalData); - transition->m_propertyTable = structure->copyPropertyTable(globalData, transition); - transition->m_isPinnedPropertyTable = true; - - ASSERT(structure->anonymousSlotCount() == transition->anonymousSlotCount()); - return transition; + structure->checkOffsetConsistency(); + return structure; } -Structure* Structure::toDictionaryTransition(JSGlobalData& globalData, Structure* structure, DictionaryKind kind) +Structure* Structure::toDictionaryTransition(VM& vm, Structure* structure, DictionaryKind kind) { ASSERT(!structure->isUncacheableDictionary()); - Structure* transition = create(globalData, structure); + Structure* transition = create(vm, structure); - structure->materializePropertyMapIfNecessary(globalData); - transition->m_propertyTable = structure->copyPropertyTable(globalData, transition); - transition->m_isPinnedPropertyTable = true; - transition->m_dictionaryKind = kind; - - ASSERT(structure->anonymousSlotCount() == transition->anonymousSlotCount()); + DeferGC deferGC(vm.heap); + structure->materializePropertyMapIfNecessary(vm, deferGC); + transition->propertyTable().set(vm, transition, structure->copyPropertyTableForPinning(vm)); + transition->m_offset = structure->m_offset; + transition->setDictionaryKind(kind); + transition->pin(); + + transition->checkOffsetConsistency(); return transition; } -Structure* Structure::toCacheableDictionaryTransition(JSGlobalData& globalData, Structure* structure) +Structure* Structure::toCacheableDictionaryTransition(VM& vm, Structure* structure) { - return toDictionaryTransition(globalData, structure, CachedDictionaryKind); + return toDictionaryTransition(vm, structure, CachedDictionaryKind); } -Structure* Structure::toUncacheableDictionaryTransition(JSGlobalData& globalData, Structure* structure) +Structure* Structure::toUncacheableDictionaryTransition(VM& vm, Structure* structure) { - return toDictionaryTransition(globalData, structure, UncachedDictionaryKind); + return toDictionaryTransition(vm, structure, UncachedDictionaryKind); } // In future we may want to cache this transition. -Structure* Structure::sealTransition(JSGlobalData& globalData, Structure* structure) +Structure* Structure::sealTransition(VM& vm, Structure* structure) { - Structure* transition = preventExtensionsTransition(globalData, structure); + Structure* transition = preventExtensionsTransition(vm, structure); - if (transition->m_propertyTable) { - PropertyTable::iterator end = transition->m_propertyTable->end(); - for (PropertyTable::iterator iter = transition->m_propertyTable->begin(); iter != end; ++iter) + 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; } // In future we may want to cache this transition. -Structure* Structure::freezeTransition(JSGlobalData& globalData, Structure* structure) +Structure* Structure::freezeTransition(VM& vm, Structure* structure) { - Structure* transition = preventExtensionsTransition(globalData, structure); - - if (transition->m_propertyTable) { - PropertyTable::iterator end = transition->m_propertyTable->end(); - for (PropertyTable::iterator iter = transition->m_propertyTable->begin(); iter != end; ++iter) - iter->attributes |= (DontDelete | ReadOnly); + 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; } // In future we may want to cache this transition. -Structure* Structure::preventExtensionsTransition(JSGlobalData& globalData, Structure* structure) +Structure* Structure::preventExtensionsTransition(VM& vm, Structure* structure) { - Structure* transition = create(globalData, structure); + Structure* transition = create(vm, structure); // Don't set m_offset, as one can not transition to this. - structure->materializePropertyMapIfNecessary(globalData); - transition->m_propertyTable = structure->copyPropertyTable(globalData, transition); - transition->m_isPinnedPropertyTable = true; - transition->m_preventExtensions = true; + 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(); - ASSERT(structure->anonymousSlotCount() == transition->anonymousSlotCount()); + transition->checkOffsetConsistency(); + return transition; +} + +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; +} + +Structure* Structure::nonPropertyTransition(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; + } + } + } + + Structure* existingTransition; + if (!structure->isDictionary() && (existingTransition = structure->m_transitionTable.get(0, attributes))) { + ASSERT(existingTransition->attributesInPrevious() == attributes); + ASSERT(existingTransition->indexingTypeIncludingHistory() == indexingType); + return existingTransition; + } + + 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()); + + if (structure->isDictionary()) + transition->pin(); + else { + ConcurrentJITLocker locker(structure->m_lock); + structure->m_transitionTable.add(vm, transition); + } + transition->checkOffsetConsistency(); return transition; } // In future we may want to cache this property. -bool Structure::isSealed(JSGlobalData& globalData) +bool Structure::isSealed(VM& vm) { if (isExtensible()) return false; - materializePropertyMapIfNecessary(globalData); - if (!m_propertyTable) + DeferGC deferGC(vm.heap); + materializePropertyMapIfNecessary(vm, deferGC); + if (!propertyTable()) return true; - PropertyTable::iterator end = m_propertyTable->end(); - for (PropertyTable::iterator iter = m_propertyTable->begin(); iter != end; ++iter) { + PropertyTable::iterator end = propertyTable()->end(); + for (PropertyTable::iterator iter = propertyTable()->begin(); iter != end; ++iter) { if ((iter->attributes & DontDelete) != DontDelete) return false; } @@ -543,232 +639,309 @@ bool Structure::isSealed(JSGlobalData& globalData) } // In future we may want to cache this property. -bool Structure::isFrozen(JSGlobalData& globalData) +bool Structure::isFrozen(VM& vm) { if (isExtensible()) return false; - materializePropertyMapIfNecessary(globalData); - if (!m_propertyTable) + DeferGC deferGC(vm.heap); + materializePropertyMapIfNecessary(vm, deferGC); + if (!propertyTable()) return true; - PropertyTable::iterator end = m_propertyTable->end(); - for (PropertyTable::iterator iter = m_propertyTable->begin(); iter != end; ++iter) { - if ((iter->attributes & (DontDelete | ReadOnly)) != (DontDelete | ReadOnly)) + PropertyTable::iterator end = propertyTable()->end(); + for (PropertyTable::iterator iter = propertyTable()->begin(); iter != end; ++iter) { + if (!(iter->attributes & DontDelete)) + return false; + if (!(iter->attributes & (ReadOnly | Accessor))) return false; } return true; } -Structure* Structure::flattenDictionaryStructure(JSGlobalData& globalData, JSObject* object) +Structure* Structure::flattenDictionaryStructure(VM& vm, JSObject* object) { + checkOffsetConsistency(); ASSERT(isDictionary()); + + size_t beforeOutOfLineCapacity = this->outOfLineCapacity(); if (isUncacheableDictionary()) { - ASSERT(m_propertyTable); + ASSERT(propertyTable()); - unsigned anonymousSlotCount = m_anonymousSlotCount; - size_t propertyCount = m_propertyTable->size(); + size_t propertyCount = propertyTable()->size(); + + // Holds our values compacted by insertion order. Vector values(propertyCount); + // Copies out our values from their hashed locations, compacting property table offsets as we go. unsigned i = 0; - PropertyTable::iterator end = m_propertyTable->end(); - for (PropertyTable::iterator iter = m_propertyTable->begin(); iter != end; ++iter, ++i) { - values[i] = object->getDirectOffset(iter->offset); - // Update property table to have the new property offsets - iter->offset = anonymousSlotCount + i; + PropertyTable::iterator end = propertyTable()->end(); + m_offset = invalidOffset; + for (PropertyTable::iterator iter = propertyTable()->begin(); iter != end; ++iter, ++i) { + values[i] = object->getDirect(iter->offset); + m_offset = iter->offset = offsetForPropertyNumber(i, m_inlineCapacity); } - // Copy the original property values into their final locations + // Copies in our values to their compacted locations. for (unsigned i = 0; i < propertyCount; i++) - object->putDirectOffset(globalData, anonymousSlotCount + i, values[i]); + object->putDirect(vm, offsetForPropertyNumber(i, m_inlineCapacity), values[i]); + + propertyTable()->clearDeletedOffsets(); + checkOffsetConsistency(); + } - m_propertyTable->clearDeletedOffsets(); + setDictionaryKind(NoneDictionaryKind); + setHasBeenFlattenedBefore(true); + + size_t afterOutOfLineCapacity = this->outOfLineCapacity(); + + if (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 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); } - m_dictionaryKind = NoneDictionaryKind; return this; } -size_t Structure::addPropertyWithoutTransition(JSGlobalData& globalData, const Identifier& propertyName, unsigned attributes, JSCell* specificValue) +PropertyOffset Structure::addPropertyWithoutTransition(VM& vm, PropertyName propertyName, unsigned attributes) { - ASSERT(!m_enumerationCache); + DeferGC deferGC(vm.heap); + materializePropertyMapIfNecessaryForPinning(vm, deferGC); + + pin(); - if (m_specificFunctionThrashCount == maxSpecificFunctionThrashCount) - specificValue = 0; + return add(vm, propertyName, attributes); +} - materializePropertyMapIfNecessary(globalData); +PropertyOffset Structure::removePropertyWithoutTransition(VM& vm, PropertyName propertyName) +{ + ASSERT(isUncacheableDictionary()); - m_isPinnedPropertyTable = true; + DeferGC deferGC(vm.heap); + materializePropertyMapIfNecessaryForPinning(vm, deferGC); - size_t offset = putSpecificValue(globalData, propertyName, attributes, specificValue); - ASSERT(offset >= m_anonymousSlotCount); - if (propertyStorageSize() > propertyStorageCapacity()) - growPropertyStorageCapacity(); - return offset; + pin(); + return remove(propertyName); } -size_t Structure::removePropertyWithoutTransition(JSGlobalData& globalData, const Identifier& propertyName) +void Structure::pin() { - ASSERT(isUncacheableDictionary()); - ASSERT(!m_enumerationCache); + ASSERT(propertyTable()); + setIsPinnedPropertyTable(true); + clearPreviousID(); + m_nameInPrevious = nullptr; +} - materializePropertyMapIfNecessary(globalData); +void Structure::allocateRareData(VM& vm) +{ + ASSERT(!hasRareData()); + StructureRareData* rareData = StructureRareData::create(vm, previous()); + WTF::storeStoreFence(); + m_previousOrRareData.set(vm, this, rareData); + WTF::storeStoreFence(); + setHasRareData(true); + ASSERT(hasRareData()); +} - m_isPinnedPropertyTable = true; - size_t offset = remove(propertyName); - ASSERT(offset >= m_anonymousSlotCount); - return offset; +WatchpointSet* Structure::ensurePropertyReplacementWatchpointSet(VM& vm, PropertyOffset offset) +{ + ASSERT(!isUncacheableDictionary()); + + if (!hasRareData()) + allocateRareData(vm); + ConcurrentJITLocker locker(m_lock); + StructureRareData* rareData = this->rareData(); + if (!rareData->m_replacementWatchpointSets) { + rareData->m_replacementWatchpointSets = + std::make_unique(); + WTF::storeStoreFence(); + } + auto result = rareData->m_replacementWatchpointSets->add(offset, nullptr); + if (result.isNewEntry) + result.iterator->value = adoptRef(new WatchpointSet(IsWatched)); + return result.iterator->value.get(); +} + +void Structure::startWatchingPropertyForReplacements(VM& vm, PropertyName propertyName) +{ + ASSERT(!isUncacheableDictionary()); + + PropertyOffset offset = get(vm, propertyName); + if (!JSC::isValidOffset(offset)) + return; + + startWatchingPropertyForReplacements(vm, offset); +} + +void Structure::didCachePropertyReplacement(VM& vm, PropertyOffset offset) +{ + ensurePropertyReplacementWatchpointSet(vm, offset)->fireAll("Did cache property replacement"); +} + +void Structure::startWatchingInternalProperties(VM& vm) +{ + if (!isUncacheableDictionary()) { + startWatchingPropertyForReplacements(vm, vm.propertyNames->toString); + startWatchingPropertyForReplacements(vm, vm.propertyNames->valueOf); + } + setDidWatchInternalProperties(true); } #if DUMP_PROPERTYMAP_STATS +PropertyMapHashTableStats* propertyMapHashTableStats = 0; + struct PropertyMapStatisticsExitLogger { + PropertyMapStatisticsExitLogger(); ~PropertyMapStatisticsExitLogger(); }; -static PropertyMapStatisticsExitLogger logger; +DEFINE_GLOBAL_FOR_LOGGING(PropertyMapStatisticsExitLogger, logger, ); -PropertyMapStatisticsExitLogger::~PropertyMapStatisticsExitLogger() +PropertyMapStatisticsExitLogger::PropertyMapStatisticsExitLogger() { - printf("\nJSC::PropertyMap statistics\n\n"); - printf("%d probes\n", numProbes); - printf("%d collisions (%.1f%%)\n", numCollisions, 100.0 * numCollisions / numProbes); - printf("%d rehashes\n", numRehashes); - printf("%d removes\n", numRemoves); + propertyMapHashTableStats = adoptPtr(new PropertyMapHashTableStats()).leakPtr(); } -#endif - -#if !DO_PROPERTYMAP_CONSTENCY_CHECK - -inline void Structure::checkConsistency() +PropertyMapStatisticsExitLogger::~PropertyMapStatisticsExitLogger() { + unsigned finds = propertyMapHashTableStats->numFinds; + unsigned collisions = propertyMapHashTableStats->numCollisions; + dataLogF("\nJSC::PropertyMap statistics for process %d\n\n", getCurrentProcessID()); + dataLogF("%d finds\n", finds); + dataLogF("%d collisions (%.1f%%)\n", collisions, 100.0 * collisions / finds); + dataLogF("%d lookups\n", propertyMapHashTableStats->numLookups.load()); + dataLogF("%d lookup probings\n", propertyMapHashTableStats->numLookupProbing.load()); + dataLogF("%d adds\n", propertyMapHashTableStats->numAdds.load()); + dataLogF("%d removes\n", propertyMapHashTableStats->numRemoves.load()); + dataLogF("%d rehashes\n", propertyMapHashTableStats->numRehashes.load()); + dataLogF("%d reinserts\n", propertyMapHashTableStats->numReinserts.load()); } #endif -PassOwnPtr Structure::copyPropertyTable(JSGlobalData& globalData, Structure* owner) +PropertyTable* Structure::copyPropertyTable(VM& vm) { - return adoptPtr(m_propertyTable ? new PropertyTable(globalData, owner, *m_propertyTable) : 0); + if (!propertyTable()) + return 0; + return PropertyTable::clone(vm, *propertyTable().get()); } -size_t Structure::get(JSGlobalData& globalData, StringImpl* propertyName, unsigned& attributes, JSCell*& specificValue) +PropertyTable* Structure::copyPropertyTableForPinning(VM& vm) { - materializePropertyMapIfNecessary(globalData); - if (!m_propertyTable) - return WTF::notFound; - - PropertyMapEntry* entry = m_propertyTable->find(propertyName).first; - if (!entry) - return WTF::notFound; - - attributes = entry->attributes; - specificValue = entry->specificValue.get(); - ASSERT(entry->offset >= m_anonymousSlotCount); - return entry->offset; + if (propertyTable()) + return PropertyTable::clone(vm, *propertyTable().get()); + return PropertyTable::create(vm, numberOfSlotsForLastOffset(m_offset, m_inlineCapacity)); } -bool Structure::despecifyFunction(JSGlobalData& globalData, const Identifier& propertyName) +PropertyOffset Structure::getConcurrently(UniquedStringImpl* uid, unsigned& attributes) { - materializePropertyMapIfNecessary(globalData); - if (!m_propertyTable) - return false; - - ASSERT(!propertyName.isNull()); - PropertyMapEntry* entry = m_propertyTable->find(propertyName.impl()).first; - if (!entry) - return false; - - ASSERT(entry->specificValue); - entry->specificValue.clear(); - return true; + PropertyOffset result = invalidOffset; + + forEachPropertyConcurrently( + [&] (const PropertyMapEntry& candidate) -> bool { + if (candidate.key != uid) + return true; + + result = candidate.offset; + attributes = candidate.attributes; + return false; + }); + + return result; } -void Structure::despecifyAllFunctions(JSGlobalData& globalData) +Vector Structure::getPropertiesConcurrently() { - materializePropertyMapIfNecessary(globalData); - if (!m_propertyTable) - return; + Vector result; - PropertyTable::iterator end = m_propertyTable->end(); - for (PropertyTable::iterator iter = m_propertyTable->begin(); iter != end; ++iter) - iter->specificValue.clear(); + forEachPropertyConcurrently( + [&] (const PropertyMapEntry& entry) -> bool { + result.append(entry); + return true; + }); + + return result; } -size_t Structure::putSpecificValue(JSGlobalData& globalData, const Identifier& propertyName, unsigned attributes, JSCell* specificValue) +PropertyOffset Structure::add(VM& vm, PropertyName propertyName, unsigned attributes) { - ASSERT(!propertyName.isNull()); - ASSERT(get(globalData, propertyName) == notFound); + GCSafeConcurrentJITLocker locker(m_lock, vm.heap); + + ASSERT(!JSC::isValidOffset(get(vm, propertyName))); checkConsistency(); if (attributes & DontEnum) - m_hasNonEnumerableProperties = true; + setHasNonEnumerableProperties(true); - StringImpl* rep = propertyName.impl(); + auto rep = propertyName.uid(); - if (!m_propertyTable) - createPropertyMap(); + if (!propertyTable()) + createPropertyMap(locker, vm); - unsigned newOffset; - - if (m_propertyTable->hasDeletedOffset()) - newOffset = m_propertyTable->getDeletedOffset(); - else - newOffset = m_propertyTable->size() + m_anonymousSlotCount; - ASSERT(newOffset >= m_anonymousSlotCount); - - m_propertyTable->add(PropertyMapEntry(globalData, this, rep, newOffset, attributes, specificValue)); + PropertyOffset newOffset = propertyTable()->nextOffset(m_inlineCapacity); + propertyTable()->add(PropertyMapEntry(rep, newOffset, attributes), m_offset, PropertyTable::PropertyOffsetMayChange); + checkConsistency(); return newOffset; } -size_t Structure::remove(const Identifier& propertyName) +PropertyOffset Structure::remove(PropertyName propertyName) { - ASSERT(!propertyName.isNull()); - + ConcurrentJITLocker locker(m_lock); + checkConsistency(); - StringImpl* rep = propertyName.impl(); + auto rep = propertyName.uid(); - if (!m_propertyTable) - return notFound; + if (!propertyTable()) + return invalidOffset; - PropertyTable::find_iterator position = m_propertyTable->find(rep); + PropertyTable::find_iterator position = propertyTable()->find(rep); if (!position.first) - return notFound; + return invalidOffset; - size_t offset = position.first->offset; - ASSERT(offset >= m_anonymousSlotCount); + PropertyOffset offset = position.first->offset; - m_propertyTable->remove(position); - m_propertyTable->addDeletedOffset(offset); + propertyTable()->remove(position); + propertyTable()->addDeletedOffset(offset); checkConsistency(); return offset; } -void Structure::createPropertyMap(unsigned capacity) +void Structure::createPropertyMap(const GCSafeConcurrentJITLocker&, VM& vm, unsigned capacity) { - ASSERT(!m_propertyTable); + ASSERT(!propertyTable()); checkConsistency(); - m_propertyTable = adoptPtr(new PropertyTable(capacity)); - checkConsistency(); + propertyTable().set(vm, this, PropertyTable::create(vm, capacity)); } -void Structure::getPropertyNames(JSGlobalData& globalData, PropertyNameArray& propertyNames, EnumerationMode mode) +void Structure::getPropertyNamesFromStructure(VM& vm, PropertyNameArray& propertyNames, EnumerationMode mode) { - materializePropertyMapIfNecessary(globalData); - if (!m_propertyTable) + DeferGC deferGC(vm.heap); + materializePropertyMapIfNecessary(vm, deferGC); + if (!propertyTable()) return; - bool knownUnique = !propertyNames.size(); + bool knownUnique = propertyNames.canAddKnownUniqueForStructure(); - PropertyTable::iterator end = m_propertyTable->end(); - for (PropertyTable::iterator iter = m_propertyTable->begin(); iter != end; ++iter) { - ASSERT(m_hasNonEnumerableProperties || !(iter->attributes & DontEnum)); - if (!(iter->attributes & DontEnum) || (mode == IncludeDontEnumProperties)) { + PropertyTable::iterator end = propertyTable()->end(); + for (PropertyTable::iterator iter = propertyTable()->begin(); iter != end; ++iter) { + ASSERT(hasNonEnumerableProperties() || !(iter->attributes & DontEnum)); + if (!(iter->attributes & DontEnum) || mode.includeDontEnumProperties()) { + if (iter->key->isSymbol() && !mode.includeSymbolProperties()) + continue; if (knownUnique) propertyNames.addKnownUnique(iter->key); else @@ -777,28 +950,174 @@ void Structure::getPropertyNames(JSGlobalData& globalData, PropertyNameArray& pr } } -void Structure::visitChildren(SlotVisitor& visitor) -{ - ASSERT_GC_OBJECT_INHERITS(this, &s_info); - ASSERT(structure()->typeInfo().overridesVisitChildren()); - JSCell::visitChildren(visitor); - if (m_prototype) - visitor.append(&m_prototype); - if (m_cachedPrototypeChain) - visitor.append(&m_cachedPrototypeChain); - if (m_previous) - visitor.append(&m_previous); - if (m_specificValueInPrevious) - visitor.append(&m_specificValueInPrevious); - if (m_enumerationCache) - visitor.append(&m_enumerationCache); - if (m_propertyTable) { - PropertyTable::iterator end = m_propertyTable->end(); - for (PropertyTable::iterator ptr = m_propertyTable->begin(); ptr != end; ++ptr) { - if (ptr->specificValue) - visitor.append(&ptr->specificValue); +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); + } + +private: + const Structure* m_structure; +}; + +} // anonymous namespace + +void Structure::didTransitionFromThisStructure() const +{ + m_transitionWatchpointSet.fireAll(StructureFireDetail(this)); +} + +JSValue Structure::prototypeForLookup(CodeBlock* codeBlock) const +{ + return prototypeForLookup(codeBlock->globalObject()); +} + +void Structure::visitChildren(JSCell* cell, SlotVisitor& visitor) +{ + Structure* thisObject = jsCast(cell); + ASSERT_GC_OBJECT_INHERITS(thisObject, info()); + + JSCell::visitChildren(thisObject, visitor); + 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_previousOrRareData); + + if (thisObject->isPinnedPropertyTable()) { + ASSERT(thisObject->m_propertyTableUnsafe); + visitor.append(&thisObject->m_propertyTableUnsafe); + } else if (thisObject->m_propertyTableUnsafe) + thisObject->m_propertyTableUnsafe.clear(); +} + +bool Structure::prototypeChainMayInterceptStoreTo(VM& vm, PropertyName propertyName) +{ + if (parseIndex(propertyName)) + return anyObjectInChainMayInterceptIndexedAccesses(); + + 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; + } +} + +PassRefPtr Structure::toStructureShape(JSValue value) +{ + RefPtr baseShape = StructureShape::create(); + RefPtr curShape = baseShape; + Structure* curStructure = this; + JSValue curValue = value; + while (curStructure) { + Vector 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); + } + + if (JSObject* curObject = curValue.getObject()) + curShape->setConstructorName(JSObject::calculatedClassName(curObject)); + else + curShape->setConstructorName(curStructure->classInfo()->className); + + if (curStructure->isDictionary()) + curShape->enterDictionaryMode(); + + curShape->markAsFinal(); + + if (curStructure->storedPrototypeStructure()) { + RefPtr newShape = StructureShape::create(); + curShape->setProto(newShape); + curShape = newShape.release(); + curValue = curStructure->storedPrototype(); } + + curStructure = curStructure->storedPrototypeStructure(); } + + return baseShape.release(); +} + +bool Structure::canUseForAllocationsOf(Structure* other) +{ + return inlineCapacity() == other->inlineCapacity() + && storedPrototype() == other->storedPrototype() + && objectInitializationBlob() == other->objectInitializationBlob(); +} + +void Structure::dump(PrintStream& out) const +{ + out.print(RawPointer(this), ":[", classInfo()->className, ", {"); + + CommaPrinter comma; + + const_cast(this)->forEachPropertyConcurrently( + [&] (const PropertyMapEntry& entry) -> bool { + out.print(comma, entry.key, ":", static_cast(entry.offset)); + return true; + }); + + out.print("}, ", IndexingTypeDump(indexingType())); + + if (m_prototype.get().isCell()) + out.print(", Proto:", RawPointer(m_prototype.get().asCell())); + + out.print("]"); +} + +void Structure::dumpInContext(PrintStream& out, DumpContext* context) const +{ + if (context) + context->structures.dumpBrief(this, out); + else + dump(out); +} + +void Structure::dumpBrief(PrintStream& out, const CString& string) const +{ + out.print("%", string, ":", classInfo()->className); +} + +void Structure::dumpContextHeader(PrintStream& out) +{ + out.print("Structures:"); } #if DO_PROPERTYMAP_CONSTENCY_CHECK @@ -842,7 +1161,7 @@ void PropertyTable::checkConsistency() if (rep == PROPERTY_MAP_DELETED_ENTRY_KEY) continue; ++nonEmptyEntryCount; - unsigned i = rep->existingHash(); + unsigned i = IdentifierRepHash::hash(rep); unsigned k = 0; unsigned entryIndex; while (1) { @@ -851,7 +1170,7 @@ void PropertyTable::checkConsistency() if (rep == table()[entryIndex - 1].key) break; if (k == 0) - k = 1 | doubleHash(rep->existingHash()); + k = 1 | doubleHash(IdentifierRepHash::hash(rep)); i += k; } ASSERT(entryIndex == c + 1); @@ -862,20 +1181,90 @@ void PropertyTable::checkConsistency() void Structure::checkConsistency() { - if (!m_propertyTable) + checkOffsetConsistency(); + + if (!propertyTable()) return; - if (!m_hasNonEnumerableProperties) { - PropertyTable::iterator end = m_propertyTable->end(); - for (PropertyTable::iterator iter = m_propertyTable->begin(); iter != end; ++iter) { + if (!hasNonEnumerableProperties()) { + PropertyTable::iterator end = propertyTable()->end(); + for (PropertyTable::iterator iter = propertyTable()->begin(); iter != end; ++iter) { ASSERT(!(iter->attributes & DontEnum)); - ASSERT(iter->offset >= m_anonymousSlotCount); } } - m_propertyTable->checkConsistency(); + 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) { + if (const HashTable* table = ci->staticPropHashTable) { + if (table->hasSetterOrReadonlyProperties) + return true; + } + } + return false; +} + +void Structure::setCachedPropertyNameEnumerator(VM& vm, JSPropertyNameEnumerator* enumerator) +{ + ASSERT(!isDictionary()); + if (!hasRareData()) + allocateRareData(vm); + rareData()->setCachedPropertyNameEnumerator(vm, enumerator); +} + +JSPropertyNameEnumerator* Structure::cachedPropertyNameEnumerator() const +{ + if (!hasRareData()) + return nullptr; + return rareData()->cachedPropertyNameEnumerator(); +} + +bool Structure::canCachePropertyNameEnumerator() const +{ + if (isDictionary()) + return false; + + if (hasIndexedProperties(indexingType())) + return false; + + if (typeInfo().overridesGetPropertyNames()) + return false; + + StructureChain* structureChain = m_cachedPrototypeChain.get(); + ASSERT(structureChain); + WriteBarrier* structure = structureChain->head(); + while (true) { + if (!structure->get()) + break; + if (structure->get()->typeInfo().overridesGetPropertyNames()) + return false; + structure++; + } + + return true; +} + +bool Structure::canAccessPropertiesQuickly() const +{ + if (hasNonEnumerableProperties()) + return false; + if (hasGetterSetterProperties()) + return false; + if (isUncacheableDictionary()) + return false; + return true; +} + } // namespace JSC