|  | /* | 
|  | * Copyright (C) 2013-2021 Apple Inc. All rights reserved. | 
|  | * | 
|  | * Redistribution and use in source and binary forms, with or without | 
|  | * modification, are permitted provided that the following conditions | 
|  | * are met: | 
|  | * 1. Redistributions of source code must retain the above copyright | 
|  | *    notice, this list of conditions and the following disclaimer. | 
|  | * 2. Redistributions in binary form must reproduce the above copyright | 
|  | *    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 INC. AND ITS CONTRIBUTORS ``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 INC. OR ITS 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 PROFITS; OR BUSINESS | 
|  | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN | 
|  | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | 
|  | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF | 
|  | * THE POSSIBILITY OF SUCH DAMAGE. | 
|  | */ | 
|  |  | 
|  | #include "config.h" | 
|  | #include "StructureRareData.h" | 
|  |  | 
|  | #include "AdaptiveInferredPropertyValueWatchpointBase.h" | 
|  | #include "CacheableIdentifierInlines.h" | 
|  | #include "CachedSpecialPropertyAdaptiveStructureWatchpoint.h" | 
|  | #include "JSImmutableButterfly.h" | 
|  | #include "JSObjectInlines.h" | 
|  | #include "JSPropertyNameEnumerator.h" | 
|  | #include "JSString.h" | 
|  | #include "ObjectPropertyConditionSet.h" | 
|  | #include "StructureChain.h" | 
|  | #include "StructureInlines.h" | 
|  | #include "StructureRareDataInlines.h" | 
|  |  | 
|  | namespace JSC { | 
|  |  | 
|  | const ClassInfo StructureRareData::s_info = { "StructureRareData"_s, nullptr, nullptr, nullptr, CREATE_METHOD_TABLE(StructureRareData) }; | 
|  |  | 
|  | Structure* StructureRareData::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) | 
|  | { | 
|  | return Structure::create(vm, globalObject, prototype, TypeInfo(CellType, StructureFlags), info()); | 
|  | } | 
|  |  | 
|  | StructureRareData* StructureRareData::create(VM& vm, Structure* previous) | 
|  | { | 
|  | StructureRareData* rareData = new (NotNull, allocateCell<StructureRareData>(vm)) StructureRareData(vm, previous); | 
|  | rareData->finishCreation(vm); | 
|  | return rareData; | 
|  | } | 
|  |  | 
|  | void StructureRareData::destroy(JSCell* cell) | 
|  | { | 
|  | static_cast<StructureRareData*>(cell)->StructureRareData::~StructureRareData(); | 
|  | } | 
|  |  | 
|  | StructureRareData::StructureRareData(VM& vm, Structure* previous) | 
|  | : JSCell(vm, vm.structureRareDataStructure.get()) | 
|  | , m_previous(vm, this, previous, WriteBarrierStructureID::MayBeNull) | 
|  | , m_maxOffset(invalidOffset) | 
|  | , m_transitionOffset(invalidOffset) | 
|  | { | 
|  | } | 
|  |  | 
|  | template<typename Visitor> | 
|  | void StructureRareData::visitChildrenImpl(JSCell* cell, Visitor& visitor) | 
|  | { | 
|  | StructureRareData* thisObject = jsCast<StructureRareData*>(cell); | 
|  | ASSERT_GC_OBJECT_INHERITS(thisObject, info()); | 
|  |  | 
|  | Base::visitChildren(thisObject, visitor); | 
|  | visitor.append(thisObject->m_previous); | 
|  | if (thisObject->m_specialPropertyCache) { | 
|  | for (unsigned index = 0; index < numberOfCachedSpecialPropertyKeys; ++index) | 
|  | visitor.appendUnbarriered(thisObject->cachedSpecialProperty(static_cast<CachedSpecialPropertyKey>(index))); | 
|  | } | 
|  | visitor.appendUnbarriered(thisObject->cachedPropertyNameEnumerator()); | 
|  | for (unsigned index = 0; index < numberOfCachedPropertyNames; ++index) { | 
|  | auto* cached = thisObject->m_cachedPropertyNames[index].unvalidatedGet(); | 
|  | if (cached != cachedPropertyNamesSentinel()) | 
|  | visitor.appendUnbarriered(cached); | 
|  | } | 
|  | } | 
|  |  | 
|  | DEFINE_VISIT_CHILDREN(StructureRareData); | 
|  |  | 
|  | // ----------- Cached special properties helper watchpoint classes ----------- | 
|  |  | 
|  | class CachedSpecialPropertyAdaptiveInferredPropertyValueWatchpoint final : public AdaptiveInferredPropertyValueWatchpointBase { | 
|  | public: | 
|  | typedef AdaptiveInferredPropertyValueWatchpointBase Base; | 
|  | CachedSpecialPropertyAdaptiveInferredPropertyValueWatchpoint(const ObjectPropertyCondition&, StructureRareData*); | 
|  |  | 
|  | private: | 
|  | bool isValid() const final; | 
|  | void handleFire(VM&, const FireDetail&) final; | 
|  |  | 
|  | StructureRareData* m_structureRareData; | 
|  | }; | 
|  |  | 
|  | SpecialPropertyCacheEntry::~SpecialPropertyCacheEntry() = default; | 
|  |  | 
|  | SpecialPropertyCache& StructureRareData::ensureSpecialPropertyCacheSlow() | 
|  | { | 
|  | ASSERT(!isCompilationThread() && !Thread::mayBeGCThread()); | 
|  | ASSERT(!m_specialPropertyCache); | 
|  | auto cache = makeUnique<SpecialPropertyCache>(); | 
|  | WTF::storeStoreFence(); // Expose valid struct for concurrent threads including concurrent compilers. | 
|  | m_specialPropertyCache = WTFMove(cache); | 
|  | return *m_specialPropertyCache.get(); | 
|  | } | 
|  |  | 
|  | inline void StructureRareData::giveUpOnSpecialPropertyCache(CachedSpecialPropertyKey key) | 
|  | { | 
|  | ensureSpecialPropertyCache().m_cache[static_cast<unsigned>(key)].m_value.setWithoutWriteBarrier(JSCell::seenMultipleCalleeObjects()); | 
|  | } | 
|  |  | 
|  | void StructureRareData::cacheSpecialPropertySlow(JSGlobalObject* globalObject, VM& vm, Structure* ownStructure, JSValue value, CachedSpecialPropertyKey key, const PropertySlot& slot) | 
|  | { | 
|  | UniquedStringImpl* uid = nullptr; | 
|  | switch (key) { | 
|  | case CachedSpecialPropertyKey::ToStringTag: | 
|  | uid = vm.propertyNames->toStringTagSymbol.impl(); | 
|  | break; | 
|  | case CachedSpecialPropertyKey::ToString: | 
|  | uid = vm.propertyNames->toString.impl(); | 
|  | break; | 
|  | case CachedSpecialPropertyKey::ValueOf: | 
|  | uid = vm.propertyNames->valueOf.impl(); | 
|  | break; | 
|  | case CachedSpecialPropertyKey::ToPrimitive: | 
|  | uid = vm.propertyNames->toPrimitiveSymbol.impl(); | 
|  | break; | 
|  | case CachedSpecialPropertyKey::ToJSON: | 
|  | uid = vm.propertyNames->toJSON.impl(); | 
|  | break; | 
|  | } | 
|  |  | 
|  | if (!ownStructure->propertyAccessesAreCacheable() || ownStructure->isProxy()) { | 
|  | giveUpOnSpecialPropertyCache(key); | 
|  | return; | 
|  | } | 
|  |  | 
|  | ObjectPropertyConditionSet conditionSet; | 
|  | if (slot.isValue()) { | 
|  | // We don't handle the own property case of special properties (toString, valueOf, @@toPrimitive, @@toStringTag) because we would never know if a new | 
|  | // object transitioning to the same structure had the same value stored in that property. | 
|  | // Additionally, this is a super unlikely case anyway. | 
|  | if (!slot.isCacheable() || slot.slotBase()->structure() == ownStructure) | 
|  | return; | 
|  |  | 
|  | // This will not create a condition for the current structure but that is good because we know that property | 
|  | // is not on the ownStructure so we will transisition if one is added and this cache will no longer be used. | 
|  | auto cacheStatus = prepareChainForCaching(globalObject, ownStructure, uid, slot.slotBase()); | 
|  | if (!cacheStatus) { | 
|  | giveUpOnSpecialPropertyCache(key); | 
|  | return; | 
|  | } | 
|  | conditionSet = generateConditionsForPrototypePropertyHit(vm, this, globalObject, ownStructure, slot.slotBase(), uid); | 
|  | ASSERT(!conditionSet.isValid() || conditionSet.hasOneSlotBaseCondition()); | 
|  | } else if (slot.isUnset()) { | 
|  | if (!ownStructure->propertyAccessesAreCacheableForAbsence()) { | 
|  | giveUpOnSpecialPropertyCache(key); | 
|  | return; | 
|  | } | 
|  |  | 
|  | auto cacheStatus = prepareChainForCaching(globalObject, ownStructure, uid, nullptr); | 
|  | if (!cacheStatus) { | 
|  | giveUpOnSpecialPropertyCache(key); | 
|  | return; | 
|  | } | 
|  | conditionSet = generateConditionsForPropertyMiss(vm, this, globalObject, ownStructure, uid); | 
|  | } else | 
|  | return; | 
|  |  | 
|  | if (!conditionSet.isValid()) { | 
|  | giveUpOnSpecialPropertyCache(key); | 
|  | return; | 
|  | } | 
|  |  | 
|  | ObjectPropertyCondition equivCondition; | 
|  | for (const ObjectPropertyCondition& condition : conditionSet) { | 
|  | if (condition.condition().kind() == PropertyCondition::Presence) { | 
|  | ASSERT(isValidOffset(condition.offset())); | 
|  | condition.object()->structure()->startWatchingPropertyForReplacements(vm, condition.offset()); | 
|  | equivCondition = condition.attemptToMakeEquivalenceWithoutBarrier(); | 
|  |  | 
|  | // The equivalence condition won't be watchable if we have already seen a replacement. | 
|  | if (!equivCondition.isWatchable(PropertyCondition::MakeNoChanges)) { | 
|  | giveUpOnSpecialPropertyCache(key); | 
|  | return; | 
|  | } | 
|  | } else if (!condition.isWatchable(PropertyCondition::MakeNoChanges)) { | 
|  | giveUpOnSpecialPropertyCache(key); | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | ASSERT(conditionSet.structuresEnsureValidity()); | 
|  | auto& cache = ensureSpecialPropertyCache().m_cache[static_cast<unsigned>(key)]; | 
|  | for (ObjectPropertyCondition condition : conditionSet) { | 
|  | if (condition.condition().kind() == PropertyCondition::Presence) { | 
|  | cache.m_equivalenceWatchpoint = makeUnique<CachedSpecialPropertyAdaptiveInferredPropertyValueWatchpoint>(equivCondition, this); | 
|  | cache.m_equivalenceWatchpoint->install(vm); | 
|  | } else | 
|  | cache.m_missWatchpoints.add(condition, this)->install(vm); | 
|  | } | 
|  | cache.m_value.set(vm, this, value); | 
|  | } | 
|  |  | 
|  | void StructureRareData::clearCachedSpecialProperty(CachedSpecialPropertyKey key) | 
|  | { | 
|  | auto* objectToStringCache = m_specialPropertyCache.get(); | 
|  | if (!objectToStringCache) | 
|  | return; | 
|  | auto& cache = objectToStringCache->m_cache[static_cast<unsigned>(key)]; | 
|  | cache.m_missWatchpoints.clear(); | 
|  | cache.m_equivalenceWatchpoint.reset(); | 
|  | if (cache.m_value.get() != JSCell::seenMultipleCalleeObjects()) | 
|  | cache.m_value.clear(); | 
|  | } | 
|  |  | 
|  | void StructureRareData::finalizeUnconditionally(VM& vm) | 
|  | { | 
|  | if (m_specialPropertyCache) { | 
|  | auto clearCacheIfInvalidated = [&](CachedSpecialPropertyKey key) { | 
|  | auto& cache = m_specialPropertyCache->m_cache[static_cast<unsigned>(key)]; | 
|  | if (cache.m_equivalenceWatchpoint) { | 
|  | if (!cache.m_equivalenceWatchpoint->key().isStillLive(vm)) { | 
|  | clearCachedSpecialProperty(key); | 
|  | return; | 
|  | } | 
|  | } | 
|  | for (auto* watchpoint : cache.m_missWatchpoints) { | 
|  | if (!watchpoint->key().isStillLive(vm)) { | 
|  | clearCachedSpecialProperty(key); | 
|  | return; | 
|  | } | 
|  | } | 
|  | }; | 
|  |  | 
|  | for (unsigned index = 0; index < numberOfCachedSpecialPropertyKeys; ++index) | 
|  | clearCacheIfInvalidated(static_cast<CachedSpecialPropertyKey>(index)); | 
|  | } | 
|  | } | 
|  |  | 
|  | // ------------- Methods for Object.prototype.toString() helper watchpoint classes -------------- | 
|  |  | 
|  | CachedSpecialPropertyAdaptiveInferredPropertyValueWatchpoint::CachedSpecialPropertyAdaptiveInferredPropertyValueWatchpoint(const ObjectPropertyCondition& key, StructureRareData* structureRareData) | 
|  | : Base(key) | 
|  | , m_structureRareData(structureRareData) | 
|  | { | 
|  | } | 
|  |  | 
|  | bool CachedSpecialPropertyAdaptiveInferredPropertyValueWatchpoint::isValid() const | 
|  | { | 
|  | return m_structureRareData->isLive(); | 
|  | } | 
|  |  | 
|  | void CachedSpecialPropertyAdaptiveInferredPropertyValueWatchpoint::handleFire(VM& vm, const FireDetail&) | 
|  | { | 
|  | CachedSpecialPropertyKey key = CachedSpecialPropertyKey::ToStringTag; | 
|  | if (this->key().uid() == vm.propertyNames->toStringTagSymbol.impl()) | 
|  | key = CachedSpecialPropertyKey::ToStringTag; | 
|  | else if (this->key().uid() == vm.propertyNames->toString.impl()) | 
|  | key = CachedSpecialPropertyKey::ToString; | 
|  | else if (this->key().uid() == vm.propertyNames->valueOf.impl()) | 
|  | key = CachedSpecialPropertyKey::ValueOf; | 
|  | else if (this->key().uid() == vm.propertyNames->toJSON.impl()) | 
|  | key = CachedSpecialPropertyKey::ToJSON; | 
|  | else { | 
|  | ASSERT(this->key().uid() == vm.propertyNames->toPrimitiveSymbol.impl()); | 
|  | key = CachedSpecialPropertyKey::ToPrimitive; | 
|  | } | 
|  | m_structureRareData->clearCachedSpecialProperty(key); | 
|  | } | 
|  |  | 
|  | } // namespace JSC |