| /* | 
 |  * Copyright (C) 2020-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. ``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 | 
 |  * 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 "DeleteByStatus.h" | 
 |  | 
 | #include "CacheableIdentifierInlines.h" | 
 | #include "CodeBlock.h" | 
 | #include "ICStatusUtils.h" | 
 | #include "PolymorphicAccess.h" | 
 | #include "StructureStubInfo.h" | 
 | #include <wtf/ListDump.h> | 
 |  | 
 | namespace JSC { | 
 |  | 
 | bool DeleteByStatus::appendVariant(const DeleteByVariant& variant) | 
 | { | 
 |     return appendICStatusVariant(m_variants, variant); | 
 | } | 
 |  | 
 | void DeleteByStatus::shrinkToFit() | 
 | { | 
 |     m_variants.shrinkToFit(); | 
 | } | 
 |  | 
 | DeleteByStatus DeleteByStatus::computeForBaseline(CodeBlock* baselineBlock, ICStatusMap& map, BytecodeIndex bytecodeIndex, ExitFlag didExit) | 
 | { | 
 |     ConcurrentJSLocker locker(baselineBlock->m_lock); | 
 |  | 
 |     DeleteByStatus result; | 
 |  | 
 | #if ENABLE(DFG_JIT) | 
 |     result = computeForStubInfoWithoutExitSiteFeedback( | 
 |         locker, baselineBlock, map.get(CodeOrigin(bytecodeIndex)).stubInfo); | 
 |  | 
 |     if (didExit) | 
 |         return result.slowVersion(); | 
 | #else | 
 |     UNUSED_PARAM(map); | 
 |     UNUSED_PARAM(didExit); | 
 |     UNUSED_PARAM(bytecodeIndex); | 
 | #endif | 
 |  | 
 |     return result; | 
 | } | 
 |  | 
 | #if ENABLE(JIT) | 
 | DeleteByStatus::DeleteByStatus(StubInfoSummary summary, StructureStubInfo& stubInfo) | 
 | { | 
 |     switch (summary) { | 
 |     case StubInfoSummary::NoInformation: | 
 |         m_state = NoInformation; | 
 |         return; | 
 |     case StubInfoSummary::Simple: | 
 |     case StubInfoSummary::MakesCalls: | 
 |     case StubInfoSummary::TakesSlowPathAndMakesCalls: | 
 |         RELEASE_ASSERT_NOT_REACHED(); | 
 |         return; | 
 |     case StubInfoSummary::TakesSlowPath: | 
 |         m_state = stubInfo.tookSlowPath ? ObservedTakesSlowPath : LikelyTakesSlowPath; | 
 |         return; | 
 |     } | 
 |     RELEASE_ASSERT_NOT_REACHED(); | 
 | } | 
 |  | 
 | DeleteByStatus DeleteByStatus::computeForStubInfoWithoutExitSiteFeedback( | 
 |     const ConcurrentJSLocker&, CodeBlock* block, StructureStubInfo* stubInfo) | 
 | { | 
 |     StubInfoSummary summary = StructureStubInfo::summary(block->vm(), stubInfo); | 
 |     if (!isInlineable(summary)) | 
 |         return DeleteByStatus(summary, *stubInfo); | 
 |  | 
 |     DeleteByStatus result; | 
 |     result.m_state = Simple; | 
 |     switch (stubInfo->cacheType()) { | 
 |     case CacheType::Unset: | 
 |         return DeleteByStatus(NoInformation); | 
 |  | 
 |     case CacheType::Stub: { | 
 |         PolymorphicAccess* list = stubInfo->m_stub.get(); | 
 |  | 
 |         for (unsigned listIndex = 0; listIndex < list->size(); ++listIndex) { | 
 |             const AccessCase& access = list->at(listIndex); | 
 |             ASSERT(!access.viaProxy()); | 
 |  | 
 |             Structure* structure = access.structure(); | 
 |             ASSERT(structure); | 
 |  | 
 |             switch (access.type()) { | 
 |             case AccessCase::DeleteMiss: | 
 |             case AccessCase::DeleteNonConfigurable: { | 
 |                 DeleteByVariant variant(access.identifier(), access.type() == AccessCase::DeleteMiss ? true : false, structure, nullptr, invalidOffset); | 
 |                 if (!result.appendVariant(variant)) | 
 |                     return DeleteByStatus(JSC::slowVersion(summary), *stubInfo); | 
 |                 break; | 
 |             } | 
 |             case AccessCase::Delete: { | 
 |                 PropertyOffset offset; | 
 |                 Structure* newStructure = Structure::removePropertyTransitionFromExistingStructureConcurrently(structure, access.identifier().uid(), offset); | 
 |                 if (!newStructure) | 
 |                     return DeleteByStatus(JSC::slowVersion(summary), *stubInfo); | 
 |                 ASSERT_UNUSED(offset, offset == access.offset());                 | 
 |                 DeleteByVariant variant(access.identifier(), true, structure, newStructure, access.offset()); | 
 |  | 
 |                 if (!result.appendVariant(variant)) | 
 |                     return DeleteByStatus(JSC::slowVersion(summary), *stubInfo); | 
 |                 break; | 
 |             } | 
 |             default: | 
 |                 ASSERT_NOT_REACHED(); | 
 |                 return DeleteByStatus(JSC::slowVersion(summary), *stubInfo); | 
 |             } | 
 |         } | 
 |  | 
 |         result.shrinkToFit(); | 
 |         return result; | 
 |     } | 
 |  | 
 |     default: | 
 |         return DeleteByStatus(JSC::slowVersion(summary), *stubInfo); | 
 |     } | 
 |  | 
 |     RELEASE_ASSERT_NOT_REACHED(); | 
 |     return DeleteByStatus(); | 
 | } | 
 |  | 
 | DeleteByStatus DeleteByStatus::computeFor( | 
 |     CodeBlock* baselineBlock, ICStatusMap& baselineMap, | 
 |     ICStatusContextStack& contextStack, CodeOrigin codeOrigin) | 
 | { | 
 |     BytecodeIndex bytecodeIndex = codeOrigin.bytecodeIndex(); | 
 |     ExitFlag didExit = hasBadCacheExitSite(baselineBlock, bytecodeIndex); | 
 |  | 
 |     for (ICStatusContext* context : contextStack) { | 
 |         ICStatus status = context->get(codeOrigin); | 
 |  | 
 |         auto bless = [&] (const DeleteByStatus& result) -> DeleteByStatus { | 
 |             if (!context->isInlined(codeOrigin)) { | 
 |                 DeleteByStatus baselineResult = computeForBaseline( | 
 |                     baselineBlock, baselineMap, bytecodeIndex, didExit); | 
 |                 baselineResult.merge(result); | 
 |                 return baselineResult; | 
 |             } | 
 |             if (didExit.isSet(ExitFromInlined)) | 
 |                 return result.slowVersion(); | 
 |             return result; | 
 |         }; | 
 |  | 
 |         if (status.stubInfo) { | 
 |             DeleteByStatus result; | 
 |             { | 
 |                 ConcurrentJSLocker locker(context->optimizedCodeBlock->m_lock); | 
 |                 result = computeForStubInfoWithoutExitSiteFeedback( | 
 |                     locker, context->optimizedCodeBlock, status.stubInfo); | 
 |             } | 
 |             if (result.isSet()) | 
 |                 return bless(result); | 
 |         } | 
 |  | 
 |         if (status.deleteStatus) | 
 |             return bless(*status.deleteStatus); | 
 |     } | 
 |  | 
 |     return computeForBaseline(baselineBlock, baselineMap, bytecodeIndex, didExit); | 
 | } | 
 |  | 
 | #endif // ENABLE(JIT) | 
 |  | 
 | DeleteByStatus DeleteByStatus::slowVersion() const | 
 | { | 
 |     if (observedSlowPath()) | 
 |         return DeleteByStatus(ObservedTakesSlowPath); | 
 |     return DeleteByStatus(LikelyTakesSlowPath); | 
 | } | 
 |  | 
 | void DeleteByStatus::merge(const DeleteByStatus& other) | 
 | { | 
 |     if (other.m_state == NoInformation) | 
 |         return; | 
 |  | 
 |     auto mergeSlow = [&] () { | 
 |         if (observedSlowPath() || other.observedSlowPath()) | 
 |             *this = DeleteByStatus(ObservedTakesSlowPath); | 
 |         else | 
 |             *this = DeleteByStatus(LikelyTakesSlowPath); | 
 |     }; | 
 |  | 
 |     switch (m_state) { | 
 |     case NoInformation: | 
 |         *this = other; | 
 |         return; | 
 |  | 
 |     case Simple: | 
 |         if (m_state != other.m_state) | 
 |             return mergeSlow(); | 
 |  | 
 |         for (auto& otherVariant : other.m_variants) { | 
 |             if (!appendVariant(otherVariant)) | 
 |                 return mergeSlow(); | 
 |         } | 
 |         shrinkToFit(); | 
 |         return; | 
 |  | 
 |     case LikelyTakesSlowPath: | 
 |     case ObservedTakesSlowPath: | 
 |         return mergeSlow(); | 
 |     } | 
 |  | 
 |     RELEASE_ASSERT_NOT_REACHED(); | 
 | } | 
 |  | 
 | void DeleteByStatus::filter(const StructureSet& set) | 
 | { | 
 |     if (m_state != Simple) | 
 |         return; | 
 |     m_variants.removeAllMatching( | 
 |         [&] (auto& variant) -> bool { | 
 |             return !set.contains(variant.oldStructure()); | 
 |         }); | 
 |     if (m_variants.isEmpty()) | 
 |         m_state = NoInformation; | 
 | } | 
 |  | 
 | CacheableIdentifier DeleteByStatus::singleIdentifier() const | 
 | { | 
 |     return singleIdentifierForICStatus(m_variants); | 
 | } | 
 |  | 
 | template<typename Visitor> | 
 | void DeleteByStatus::visitAggregateImpl(Visitor& visitor) | 
 | { | 
 |     for (DeleteByVariant& variant : m_variants) | 
 |         variant.visitAggregate(visitor); | 
 | } | 
 |  | 
 | DEFINE_VISIT_AGGREGATE(DeleteByStatus); | 
 |  | 
 | template<typename Visitor> | 
 | void DeleteByStatus::markIfCheap(Visitor& visitor) | 
 | { | 
 |     for (DeleteByVariant& variant : m_variants) | 
 |         variant.markIfCheap(visitor); | 
 | } | 
 |  | 
 | template void DeleteByStatus::markIfCheap(AbstractSlotVisitor&); | 
 | template void DeleteByStatus::markIfCheap(SlotVisitor&); | 
 |  | 
 | bool DeleteByStatus::finalize(VM& vm) | 
 | { | 
 |     for (auto& variant : m_variants) { | 
 |         if (!variant.finalize(vm)) | 
 |             return false; | 
 |     } | 
 |     return true; | 
 | } | 
 |  | 
 | void DeleteByStatus::dump(PrintStream& out) const | 
 | { | 
 |     out.print("("); | 
 |     switch (m_state) { | 
 |     case NoInformation: | 
 |         out.print("NoInformation"); | 
 |         break; | 
 |     case Simple: | 
 |         out.print("Simple"); | 
 |         break; | 
 |     case LikelyTakesSlowPath: | 
 |         out.print("LikelyTakesSlowPath"); | 
 |         break; | 
 |     case ObservedTakesSlowPath: | 
 |         out.print("ObservedTakesSlowPath"); | 
 |         break; | 
 |     } | 
 |     out.print(", ", listDump(m_variants), ")"); | 
 | } | 
 |  | 
 | } // namespace JSC |