/* * Copyright (C) 2012-2020 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 "GetByStatus.h" #include "BytecodeStructs.h" #include "CacheableIdentifierInlines.h" #include "CodeBlock.h" #include "ComplexGetStatus.h" #include "GetterSetterAccessCase.h" #include "ICStatusUtils.h" #include "IntrinsicGetterAccessCase.h" #include "ModuleNamespaceAccessCase.h" #include "PolymorphicAccess.h" #include "StructureStubInfo.h" #include namespace JSC { bool GetByStatus::appendVariant(const GetByIdVariant& variant) { return appendICStatusVariant(m_variants, variant); } GetByStatus GetByStatus::computeFromLLInt(CodeBlock* profiledBlock, BytecodeIndex bytecodeIndex) { VM& vm = profiledBlock->vm(); auto instruction = profiledBlock->instructions().at(bytecodeIndex.offset()); StructureID structureID; const Identifier* identifier = nullptr; switch (instruction->opcodeID()) { case op_get_by_id: { auto& metadata = instruction->as().metadata(profiledBlock); // FIXME: We should not just bail if we see a get_by_id_proto_load. // https://bugs.webkit.org/show_bug.cgi?id=158039 if (metadata.m_modeMetadata.mode != GetByIdMode::Default) return GetByStatus(NoInformation, false); structureID = metadata.m_modeMetadata.defaultMode.structureID; identifier = &(profiledBlock->identifier(instruction->as().m_property)); break; } case op_get_by_id_direct: structureID = instruction->as().metadata(profiledBlock).m_structureID; identifier = &(profiledBlock->identifier(instruction->as().m_property)); break; case op_try_get_by_id: { // FIXME: We should not just bail if we see a try_get_by_id. // https://bugs.webkit.org/show_bug.cgi?id=158039 return GetByStatus(NoInformation, false); } case op_get_by_val: return GetByStatus(NoInformation, false); case op_iterator_open: { ASSERT(bytecodeIndex.checkpoint() == OpIteratorOpen::getNext); auto& metadata = instruction->as().metadata(profiledBlock); // FIXME: We should not just bail if we see a get_by_id_proto_load. // https://bugs.webkit.org/show_bug.cgi?id=158039 if (metadata.m_modeMetadata.mode != GetByIdMode::Default) return GetByStatus(NoInformation, false); structureID = metadata.m_modeMetadata.defaultMode.structureID; identifier = &vm.propertyNames->next; break; } case op_iterator_next: { auto& metadata = instruction->as().metadata(profiledBlock); if (bytecodeIndex.checkpoint() == OpIteratorNext::getDone) { if (metadata.m_doneModeMetadata.mode != GetByIdMode::Default) return GetByStatus(NoInformation, false); structureID = metadata.m_doneModeMetadata.defaultMode.structureID; identifier = &vm.propertyNames->done; } else { ASSERT(bytecodeIndex.checkpoint() == OpIteratorNext::getValue); if (metadata.m_valueModeMetadata.mode != GetByIdMode::Default) return GetByStatus(NoInformation, false); structureID = metadata.m_valueModeMetadata.defaultMode.structureID; identifier = &vm.propertyNames->value; } break; } case op_get_private_name: // FIXME: Consider using LLInt caches or IC information to populate GetByStatus // https://bugs.webkit.org/show_bug.cgi?id=217245 return GetByStatus(NoInformation, false); default: { ASSERT_NOT_REACHED(); return GetByStatus(NoInformation, false); } } if (!structureID) return GetByStatus(NoInformation, false); Structure* structure = vm.heap.structureIDTable().get(structureID); if (structure->takesSlowPathInDFGForImpureProperty()) return GetByStatus(NoInformation, false); unsigned attributes; PropertyOffset offset = structure->getConcurrently(identifier->impl(), attributes); if (!isValidOffset(offset)) return GetByStatus(NoInformation, false); if (attributes & PropertyAttribute::CustomAccessorOrValue) return GetByStatus(NoInformation, false); GetByStatus result(Simple, false); result.appendVariant(GetByIdVariant(nullptr, StructureSet(structure), offset)); return result; } GetByStatus GetByStatus::computeFor(CodeBlock* profiledBlock, ICStatusMap& map, BytecodeIndex bytecodeIndex, ExitFlag didExit, CallLinkStatus::ExitSiteData callExitSiteData) { ConcurrentJSLocker locker(profiledBlock->m_lock); GetByStatus result; #if ENABLE(DFG_JIT) result = computeForStubInfoWithoutExitSiteFeedback( locker, profiledBlock, map.get(CodeOrigin(bytecodeIndex)).stubInfo, callExitSiteData); if (didExit) return result.slowVersion(); #else UNUSED_PARAM(map); UNUSED_PARAM(didExit); UNUSED_PARAM(callExitSiteData); #endif if (!result) return computeFromLLInt(profiledBlock, bytecodeIndex); return result; } #if ENABLE(JIT) GetByStatus::GetByStatus(StubInfoSummary summary, StructureStubInfo& stubInfo) : m_wasSeenInJIT(true) { switch (summary) { case StubInfoSummary::NoInformation: m_state = NoInformation; return; case StubInfoSummary::Simple: case StubInfoSummary::MakesCalls: RELEASE_ASSERT_NOT_REACHED(); return; case StubInfoSummary::TakesSlowPath: m_state = stubInfo.tookSlowPath ? ObservedTakesSlowPath : LikelyTakesSlowPath; return; case StubInfoSummary::TakesSlowPathAndMakesCalls: m_state = stubInfo.tookSlowPath ? ObservedSlowPathAndMakesCalls : MakesCalls; return; } RELEASE_ASSERT_NOT_REACHED(); } GetByStatus::GetByStatus(const ModuleNamespaceAccessCase& accessCase) : m_moduleNamespaceData(Box::create(ModuleNamespaceData { accessCase.moduleNamespaceObject(), accessCase.moduleEnvironment(), accessCase.scopeOffset(), accessCase.identifier() })) , m_state(ModuleNamespace) , m_wasSeenInJIT(true) { } GetByStatus GetByStatus::computeForStubInfoWithoutExitSiteFeedback( const ConcurrentJSLocker& locker, CodeBlock* profiledBlock, StructureStubInfo* stubInfo, CallLinkStatus::ExitSiteData callExitSiteData) { StubInfoSummary summary = StructureStubInfo::summary(profiledBlock->vm(), stubInfo); if (!isInlineable(summary)) return GetByStatus(summary, *stubInfo); // Finally figure out if we can derive an access strategy. GetByStatus result; result.m_state = Simple; result.m_wasSeenInJIT = true; // This is interesting for bytecode dumping only. switch (stubInfo->cacheType()) { case CacheType::Unset: return GetByStatus(NoInformation); case CacheType::GetByIdSelf: { Structure* structure = stubInfo->u.byIdSelf.baseObjectStructure.get(); if (structure->takesSlowPathInDFGForImpureProperty()) return GetByStatus(JSC::slowVersion(summary), *stubInfo); CacheableIdentifier identifier = stubInfo->identifier(); UniquedStringImpl* uid = identifier.uid(); RELEASE_ASSERT(uid); GetByIdVariant variant(WTFMove(identifier)); unsigned attributes; variant.m_offset = structure->getConcurrently(uid, attributes); if (!isValidOffset(variant.m_offset)) return GetByStatus(JSC::slowVersion(summary), *stubInfo); if (attributes & PropertyAttribute::CustomAccessorOrValue) return GetByStatus(JSC::slowVersion(summary), *stubInfo); variant.m_structureSet.add(structure); bool didAppend = result.appendVariant(variant); ASSERT_UNUSED(didAppend, didAppend); return result; } case CacheType::Stub: { PolymorphicAccess* list = stubInfo->u.stub; if (list->size() == 1) { const AccessCase& access = list->at(0); switch (access.type()) { case AccessCase::ModuleNamespaceLoad: return GetByStatus(access.as()); default: break; } } for (unsigned listIndex = 0; listIndex < list->size(); ++listIndex) { const AccessCase& access = list->at(listIndex); if (access.viaProxy()) return GetByStatus(JSC::slowVersion(summary), *stubInfo); if (access.usesPolyProto()) return GetByStatus(JSC::slowVersion(summary), *stubInfo); if (!access.requiresIdentifierNameMatch()) { // FIXME: We could use this for indexed loads in the future. This is pretty solid profiling // information, and probably better than ArrayProfile when it's available. // https://bugs.webkit.org/show_bug.cgi?id=204215 return GetByStatus(JSC::slowVersion(summary), *stubInfo); } Structure* structure = access.structure(); if (!structure) { // The null structure cases arise due to array.length and string.length. We have no way // of creating a GetByIdVariant for those, and we don't really have to since the DFG // handles those cases in FixupPhase using value profiling. That's a bit awkward - we // shouldn't have to use value profiling to discover something that the AccessCase // could have told us. But, it works well enough. So, our only concern here is to not // crash on null structure. return GetByStatus(JSC::slowVersion(summary), *stubInfo); } ComplexGetStatus complexGetStatus = ComplexGetStatus::computeFor( structure, access.conditionSet(), access.uid()); switch (complexGetStatus.kind()) { case ComplexGetStatus::ShouldSkip: continue; case ComplexGetStatus::TakesSlowPath: return GetByStatus(JSC::slowVersion(summary), *stubInfo); case ComplexGetStatus::Inlineable: { std::unique_ptr callLinkStatus; JSFunction* intrinsicFunction = nullptr; FunctionPtr customAccessorGetter; std::unique_ptr domAttribute; bool haveDOMAttribute = false; switch (access.type()) { case AccessCase::Load: case AccessCase::GetGetter: case AccessCase::Miss: { break; } case AccessCase::IntrinsicGetter: { intrinsicFunction = access.as().intrinsicFunction(); break; } case AccessCase::Getter: { callLinkStatus = makeUnique(); if (CallLinkInfo* callLinkInfo = access.as().callLinkInfo()) { *callLinkStatus = CallLinkStatus::computeFor( locker, profiledBlock, *callLinkInfo, callExitSiteData); } break; } case AccessCase::CustomAccessorGetter: { customAccessorGetter = access.as().customAccessor(); if (!access.as().domAttribute()) return GetByStatus(JSC::slowVersion(summary), *stubInfo); domAttribute = WTF::makeUnique(*access.as().domAttribute()); haveDOMAttribute = true; result.m_state = Custom; break; } default: { // FIXME: It would be totally sweet to support more of these at some point in the // future. https://bugs.webkit.org/show_bug.cgi?id=133052 return GetByStatus(JSC::slowVersion(summary), *stubInfo); } } ASSERT((AccessCase::Miss == access.type() || access.isCustom()) == (access.offset() == invalidOffset)); GetByIdVariant variant(access.identifier(), StructureSet(structure), complexGetStatus.offset(), complexGetStatus.conditionSet(), WTFMove(callLinkStatus), intrinsicFunction, customAccessorGetter, WTFMove(domAttribute)); if (!result.appendVariant(variant)) return GetByStatus(JSC::slowVersion(summary), *stubInfo); if (haveDOMAttribute) { // Give up when custom accesses are not merged into one. if (result.numVariants() != 1) return GetByStatus(JSC::slowVersion(summary), *stubInfo); } else { // Give up when custom access and simple access are mixed. if (result.m_state == Custom) return GetByStatus(JSC::slowVersion(summary), *stubInfo); } break; } } } return result; } default: return GetByStatus(JSC::slowVersion(summary), *stubInfo); } RELEASE_ASSERT_NOT_REACHED(); return GetByStatus(); } GetByStatus GetByStatus::computeFor( CodeBlock* profiledBlock, ICStatusMap& baselineMap, ICStatusContextStack& icContextStack, CodeOrigin codeOrigin) { BytecodeIndex bytecodeIndex = codeOrigin.bytecodeIndex(); CallLinkStatus::ExitSiteData callExitSiteData = CallLinkStatus::computeExitSiteData(profiledBlock, bytecodeIndex); ExitFlag didExit = hasBadCacheExitSite(profiledBlock, bytecodeIndex); for (ICStatusContext* context : icContextStack) { ICStatus status = context->get(codeOrigin); auto bless = [&] (const GetByStatus& result) -> GetByStatus { if (!context->isInlined(codeOrigin)) { // Merge with baseline result, which also happens to contain exit data for both // inlined and not-inlined. GetByStatus baselineResult = computeFor( profiledBlock, baselineMap, bytecodeIndex, didExit, callExitSiteData); baselineResult.merge(result); return baselineResult; } if (didExit.isSet(ExitFromInlined)) return result.slowVersion(); return result; }; if (status.stubInfo) { GetByStatus result; { ConcurrentJSLocker locker(context->optimizedCodeBlock->m_lock); result = computeForStubInfoWithoutExitSiteFeedback( locker, context->optimizedCodeBlock, status.stubInfo, callExitSiteData); } if (result.isSet()) return bless(result); } if (status.getStatus) return bless(*status.getStatus); } return computeFor(profiledBlock, baselineMap, bytecodeIndex, didExit, callExitSiteData); } GetByStatus GetByStatus::computeFor(const StructureSet& set, UniquedStringImpl* uid) { // For now we only handle the super simple self access case. We could handle the // prototype case in the future. // // Note that this code is also used for GetByIdDirect since this function only looks // into direct properties. When supporting prototype chains, we should split this for // GetById and GetByIdDirect. if (set.isEmpty()) return GetByStatus(); if (parseIndex(*uid)) return GetByStatus(LikelyTakesSlowPath); GetByStatus result; result.m_state = Simple; result.m_wasSeenInJIT = false; for (unsigned i = 0; i < set.size(); ++i) { Structure* structure = set[i]; if (structure->typeInfo().overridesGetOwnPropertySlot() && structure->typeInfo().type() != GlobalObjectType) return GetByStatus(LikelyTakesSlowPath); if (!structure->propertyAccessesAreCacheable()) return GetByStatus(LikelyTakesSlowPath); unsigned attributes; PropertyOffset offset = structure->getConcurrently(uid, attributes); if (!isValidOffset(offset)) return GetByStatus(LikelyTakesSlowPath); // It's probably a prototype lookup. Give up on life for now, even though we could totally be way smarter about it. if (attributes & PropertyAttribute::Accessor) return GetByStatus(MakesCalls); // We could be smarter here, like strength-reducing this to a Call. if (attributes & PropertyAttribute::CustomAccessorOrValue) return GetByStatus(LikelyTakesSlowPath); if (!result.appendVariant(GetByIdVariant(nullptr, structure, offset))) return GetByStatus(LikelyTakesSlowPath); } return result; } #endif // ENABLE(JIT) bool GetByStatus::makesCalls() const { switch (m_state) { case NoInformation: case LikelyTakesSlowPath: case ObservedTakesSlowPath: case Custom: case ModuleNamespace: return false; case Simple: for (unsigned i = m_variants.size(); i--;) { if (m_variants[i].callLinkStatus()) return true; } return false; case MakesCalls: case ObservedSlowPathAndMakesCalls: return true; } RELEASE_ASSERT_NOT_REACHED(); return false; } GetByStatus GetByStatus::slowVersion() const { if (observedStructureStubInfoSlowPath()) return GetByStatus(makesCalls() ? ObservedSlowPathAndMakesCalls : ObservedTakesSlowPath, wasSeenInJIT()); return GetByStatus(makesCalls() ? MakesCalls : LikelyTakesSlowPath, wasSeenInJIT()); } void GetByStatus::merge(const GetByStatus& other) { if (other.m_state == NoInformation) return; auto mergeSlow = [&] () { if (observedStructureStubInfoSlowPath() || other.observedStructureStubInfoSlowPath()) *this = GetByStatus((makesCalls() || other.makesCalls()) ? ObservedSlowPathAndMakesCalls : ObservedTakesSlowPath); else *this = GetByStatus((makesCalls() || other.makesCalls()) ? MakesCalls : LikelyTakesSlowPath); }; switch (m_state) { case NoInformation: *this = other; return; case Simple: case Custom: if (m_state != other.m_state) return mergeSlow(); for (const GetByIdVariant& otherVariant : other.m_variants) { if (!appendVariant(otherVariant)) return mergeSlow(); } return; case ModuleNamespace: if (other.m_state != ModuleNamespace) return mergeSlow(); if (m_moduleNamespaceData->m_moduleNamespaceObject != other.m_moduleNamespaceData->m_moduleNamespaceObject) return mergeSlow(); if (m_moduleNamespaceData->m_moduleEnvironment != other.m_moduleNamespaceData->m_moduleEnvironment) return mergeSlow(); if (m_moduleNamespaceData->m_scopeOffset != other.m_moduleNamespaceData->m_scopeOffset) return mergeSlow(); return; case LikelyTakesSlowPath: case ObservedTakesSlowPath: case MakesCalls: case ObservedSlowPathAndMakesCalls: return mergeSlow(); } RELEASE_ASSERT_NOT_REACHED(); } void GetByStatus::filter(const StructureSet& set) { if (m_state != Simple) return; filterICStatusVariants(m_variants, set); if (m_variants.isEmpty()) m_state = NoInformation; } void GetByStatus::visitAggregate(SlotVisitor& visitor) { if (isModuleNamespace()) m_moduleNamespaceData->m_identifier.visitAggregate(visitor); for (GetByIdVariant& variant : m_variants) variant.visitAggregate(visitor); } void GetByStatus::markIfCheap(SlotVisitor& visitor) { for (GetByIdVariant& variant : m_variants) variant.markIfCheap(visitor); } bool GetByStatus::finalize(VM& vm) { for (GetByIdVariant& variant : m_variants) { if (!variant.finalize(vm)) return false; } if (isModuleNamespace()) { if (m_moduleNamespaceData->m_moduleNamespaceObject && !vm.heap.isMarked(m_moduleNamespaceData->m_moduleNamespaceObject)) return false; if (m_moduleNamespaceData->m_moduleEnvironment && !vm.heap.isMarked(m_moduleNamespaceData->m_moduleEnvironment)) return false; } return true; } CacheableIdentifier GetByStatus::singleIdentifier() const { if (isModuleNamespace()) return m_moduleNamespaceData->m_identifier; if (m_variants.isEmpty()) return nullptr; CacheableIdentifier result = m_variants.first().identifier(); if (!result) return nullptr; for (size_t i = 1; i < m_variants.size(); ++i) { CacheableIdentifier identifier = m_variants[i].identifier(); if (!identifier) return nullptr; if (identifier != result) return nullptr; } return result; } void GetByStatus::dump(PrintStream& out) const { out.print("("); switch (m_state) { case NoInformation: out.print("NoInformation"); break; case Simple: out.print("Simple"); break; case Custom: out.print("Custom"); break; case ModuleNamespace: out.print("ModuleNamespace"); break; case LikelyTakesSlowPath: out.print("LikelyTakesSlowPath"); break; case ObservedTakesSlowPath: out.print("ObservedTakesSlowPath"); break; case MakesCalls: out.print("MakesCalls"); break; case ObservedSlowPathAndMakesCalls: out.print("ObservedSlowPathAndMakesCalls"); break; } out.print(", ", listDump(m_variants), ", seenInJIT = ", m_wasSeenInJIT, ")"); } } // namespace JSC