|  | /* | 
|  | * Copyright (C) 2012-2019 Apple Inc. All rights reserved. | 
|  | * | 
|  | * Redistribution and use in source and binary forms, with or without | 
|  | * modification, are permitted provided that the following conditions | 
|  | * 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 "PutByIdStatus.h" | 
|  |  | 
|  | #include "BytecodeStructs.h" | 
|  | #include "CodeBlock.h" | 
|  | #include "ComplexGetStatus.h" | 
|  | #include "GetterSetterAccessCase.h" | 
|  | #include "ICStatusUtils.h" | 
|  | #include "PolymorphicAccess.h" | 
|  | #include "StructureInlines.h" | 
|  | #include "StructureStubInfo.h" | 
|  | #include <wtf/ListDump.h> | 
|  |  | 
|  | namespace JSC { | 
|  |  | 
|  | bool PutByIdStatus::appendVariant(const PutByIdVariant& variant) | 
|  | { | 
|  | return appendICStatusVariant(m_variants, variant); | 
|  | } | 
|  |  | 
|  | PutByIdStatus PutByIdStatus::computeFromLLInt(CodeBlock* profiledBlock, BytecodeIndex bytecodeIndex, UniquedStringImpl* uid) | 
|  | { | 
|  | VM& vm = profiledBlock->vm(); | 
|  |  | 
|  | auto instruction = profiledBlock->instructions().at(bytecodeIndex.offset()); | 
|  |  | 
|  | // We are not yet using `computeFromLLInt` in any place for `put_private_name`. | 
|  | // We can add support for it if this is required in future changes, since we have | 
|  | // IC implemented for this operation on LLInt. | 
|  | ASSERT(!instruction->is<OpPutPrivateName>()); | 
|  |  | 
|  | auto bytecode = instruction->as<OpPutById>(); | 
|  | auto& metadata = bytecode.metadata(profiledBlock); | 
|  |  | 
|  | StructureID structureID = metadata.m_oldStructureID; | 
|  | if (!structureID) | 
|  | return PutByIdStatus(NoInformation); | 
|  |  | 
|  | Structure* structure = vm.heap.structureIDTable().get(structureID); | 
|  |  | 
|  | StructureID newStructureID = metadata.m_newStructureID; | 
|  | if (!newStructureID) { | 
|  | PropertyOffset offset = structure->getConcurrently(uid); | 
|  | if (!isValidOffset(offset)) | 
|  | return PutByIdStatus(NoInformation); | 
|  |  | 
|  | return PutByIdVariant::replace(structure, offset); | 
|  | } | 
|  |  | 
|  | Structure* newStructure = vm.heap.structureIDTable().get(newStructureID); | 
|  |  | 
|  | ASSERT(structure->transitionWatchpointSetHasBeenInvalidated()); | 
|  |  | 
|  | PropertyOffset offset = newStructure->getConcurrently(uid); | 
|  | if (!isValidOffset(offset)) | 
|  | return PutByIdStatus(NoInformation); | 
|  |  | 
|  | ObjectPropertyConditionSet conditionSet; | 
|  | if (!(bytecode.m_flags.isDirect())) { | 
|  | conditionSet = | 
|  | generateConditionsForPropertySetterMissConcurrently( | 
|  | vm, profiledBlock->globalObject(), structure, uid); | 
|  | if (!conditionSet.isValid()) | 
|  | return PutByIdStatus(NoInformation); | 
|  | } | 
|  |  | 
|  | return PutByIdVariant::transition( | 
|  | structure, newStructure, conditionSet, offset); | 
|  | } | 
|  |  | 
|  | #if ENABLE(JIT) | 
|  | PutByIdStatus PutByIdStatus::computeFor(CodeBlock* profiledBlock, ICStatusMap& map, BytecodeIndex bytecodeIndex, UniquedStringImpl* uid, ExitFlag didExit, CallLinkStatus::ExitSiteData callExitSiteData) | 
|  | { | 
|  | ConcurrentJSLocker locker(profiledBlock->m_lock); | 
|  |  | 
|  | UNUSED_PARAM(profiledBlock); | 
|  | UNUSED_PARAM(bytecodeIndex); | 
|  | UNUSED_PARAM(uid); | 
|  | #if ENABLE(DFG_JIT) | 
|  | if (didExit) | 
|  | return PutByIdStatus(TakesSlowPath); | 
|  |  | 
|  | StructureStubInfo* stubInfo = map.get(CodeOrigin(bytecodeIndex)).stubInfo; | 
|  | PutByIdStatus result = computeForStubInfo( | 
|  | locker, profiledBlock, stubInfo, uid, callExitSiteData); | 
|  | if (!result) | 
|  | return computeFromLLInt(profiledBlock, bytecodeIndex, uid); | 
|  |  | 
|  | return result; | 
|  | #else // ENABLE(JIT) | 
|  | UNUSED_PARAM(map); | 
|  | UNUSED_PARAM(didExit); | 
|  | UNUSED_PARAM(callExitSiteData); | 
|  | return PutByIdStatus(NoInformation); | 
|  | #endif // ENABLE(JIT) | 
|  | } | 
|  |  | 
|  | PutByIdStatus PutByIdStatus::computeForStubInfo(const ConcurrentJSLocker& locker, CodeBlock* baselineBlock, StructureStubInfo* stubInfo, CodeOrigin codeOrigin, UniquedStringImpl* uid) | 
|  | { | 
|  | return computeForStubInfo( | 
|  | locker, baselineBlock, stubInfo, uid, | 
|  | CallLinkStatus::computeExitSiteData(baselineBlock, codeOrigin.bytecodeIndex())); | 
|  | } | 
|  |  | 
|  | PutByIdStatus PutByIdStatus::computeForStubInfo( | 
|  | const ConcurrentJSLocker& locker, CodeBlock* profiledBlock, StructureStubInfo* stubInfo, | 
|  | UniquedStringImpl* uid, CallLinkStatus::ExitSiteData callExitSiteData) | 
|  | { | 
|  | StubInfoSummary summary = StructureStubInfo::summary(profiledBlock->vm(), stubInfo); | 
|  | if (!isInlineable(summary)) | 
|  | return PutByIdStatus(summary); | 
|  |  | 
|  | switch (stubInfo->cacheType()) { | 
|  | case CacheType::Unset: | 
|  | // This means that we attempted to cache but failed for some reason. | 
|  | return PutByIdStatus(JSC::slowVersion(summary)); | 
|  |  | 
|  | case CacheType::PutByIdReplace: { | 
|  | PropertyOffset offset = | 
|  | stubInfo->u.byIdSelf.baseObjectStructure->getConcurrently(uid); | 
|  | if (isValidOffset(offset)) { | 
|  | return PutByIdVariant::replace( | 
|  | stubInfo->u.byIdSelf.baseObjectStructure.get(), offset); | 
|  | } | 
|  | return PutByIdStatus(JSC::slowVersion(summary)); | 
|  | } | 
|  |  | 
|  | case CacheType::Stub: { | 
|  | PolymorphicAccess* list = stubInfo->u.stub; | 
|  |  | 
|  | PutByIdStatus result; | 
|  | result.m_state = Simple; | 
|  |  | 
|  | for (unsigned i = 0; i < list->size(); ++i) { | 
|  | const AccessCase& access = list->at(i); | 
|  | if (access.viaProxy()) | 
|  | return PutByIdStatus(JSC::slowVersion(summary)); | 
|  | if (access.usesPolyProto()) | 
|  | return PutByIdStatus(JSC::slowVersion(summary)); | 
|  |  | 
|  | PutByIdVariant variant; | 
|  |  | 
|  | switch (access.type()) { | 
|  | case AccessCase::Replace: { | 
|  | Structure* structure = access.structure(); | 
|  | PropertyOffset offset = structure->getConcurrently(uid); | 
|  | if (!isValidOffset(offset)) | 
|  | return PutByIdStatus(JSC::slowVersion(summary)); | 
|  | variant = PutByIdVariant::replace( | 
|  | structure, offset); | 
|  | break; | 
|  | } | 
|  |  | 
|  | case AccessCase::Transition: { | 
|  | PropertyOffset offset = | 
|  | access.newStructure()->getConcurrently(uid); | 
|  | if (!isValidOffset(offset)) | 
|  | return PutByIdStatus(JSC::slowVersion(summary)); | 
|  | ObjectPropertyConditionSet conditionSet = access.conditionSet(); | 
|  | if (!conditionSet.structuresEnsureValidity()) | 
|  | return PutByIdStatus(JSC::slowVersion(summary)); | 
|  | variant = PutByIdVariant::transition( | 
|  | access.structure(), access.newStructure(), conditionSet, offset); | 
|  | break; | 
|  | } | 
|  |  | 
|  | case AccessCase::Setter: { | 
|  | Structure* structure = access.structure(); | 
|  |  | 
|  | ComplexGetStatus complexGetStatus = ComplexGetStatus::computeFor( | 
|  | structure, access.conditionSet(), uid); | 
|  |  | 
|  | switch (complexGetStatus.kind()) { | 
|  | case ComplexGetStatus::ShouldSkip: | 
|  | continue; | 
|  |  | 
|  | case ComplexGetStatus::TakesSlowPath: | 
|  | return PutByIdStatus(JSC::slowVersion(summary)); | 
|  |  | 
|  | case ComplexGetStatus::Inlineable: { | 
|  | std::unique_ptr<CallLinkStatus> callLinkStatus = | 
|  | makeUnique<CallLinkStatus>(); | 
|  | if (CallLinkInfo* callLinkInfo = access.as<GetterSetterAccessCase>().callLinkInfo()) { | 
|  | *callLinkStatus = CallLinkStatus::computeFor( | 
|  | locker, profiledBlock, *callLinkInfo, callExitSiteData); | 
|  | } | 
|  |  | 
|  | variant = PutByIdVariant::setter( | 
|  | structure, complexGetStatus.offset(), complexGetStatus.conditionSet(), | 
|  | WTFMove(callLinkStatus)); | 
|  | } } | 
|  | break; | 
|  | } | 
|  |  | 
|  | case AccessCase::CustomValueSetter: | 
|  | case AccessCase::CustomAccessorSetter: | 
|  | return PutByIdStatus(MakesCalls); | 
|  |  | 
|  | default: | 
|  | return PutByIdStatus(JSC::slowVersion(summary)); | 
|  | } | 
|  |  | 
|  | if (!result.appendVariant(variant)) | 
|  | return PutByIdStatus(JSC::slowVersion(summary)); | 
|  | } | 
|  |  | 
|  | return result; | 
|  | } | 
|  |  | 
|  | default: | 
|  | return PutByIdStatus(JSC::slowVersion(summary)); | 
|  | } | 
|  | } | 
|  |  | 
|  | PutByIdStatus PutByIdStatus::computeFor(CodeBlock* baselineBlock, ICStatusMap& baselineMap, ICStatusContextStack& contextStack, CodeOrigin codeOrigin, UniquedStringImpl* uid) | 
|  | { | 
|  | BytecodeIndex bytecodeIndex = codeOrigin.bytecodeIndex(); | 
|  | CallLinkStatus::ExitSiteData callExitSiteData = CallLinkStatus::computeExitSiteData(baselineBlock, bytecodeIndex); | 
|  | ExitFlag didExit = hasBadCacheExitSite(baselineBlock, bytecodeIndex); | 
|  |  | 
|  | for (ICStatusContext* context : contextStack) { | 
|  | ICStatus status = context->get(codeOrigin); | 
|  |  | 
|  | auto bless = [&] (const PutByIdStatus& result) -> PutByIdStatus { | 
|  | if (!context->isInlined(codeOrigin)) { | 
|  | PutByIdStatus baselineResult = computeFor( | 
|  | baselineBlock, baselineMap, bytecodeIndex, uid, didExit, | 
|  | callExitSiteData); | 
|  | baselineResult.merge(result); | 
|  | return baselineResult; | 
|  | } | 
|  | if (didExit.isSet(ExitFromInlined)) | 
|  | return result.slowVersion(); | 
|  | return result; | 
|  | }; | 
|  |  | 
|  | if (status.stubInfo) { | 
|  | PutByIdStatus result; | 
|  | { | 
|  | ConcurrentJSLocker locker(context->optimizedCodeBlock->m_lock); | 
|  | result = computeForStubInfo( | 
|  | locker, context->optimizedCodeBlock, status.stubInfo, uid, callExitSiteData); | 
|  | } | 
|  | if (result.isSet()) | 
|  | return bless(result); | 
|  | } | 
|  |  | 
|  | if (status.putStatus) | 
|  | return bless(*status.putStatus); | 
|  | } | 
|  |  | 
|  | return computeFor(baselineBlock, baselineMap, bytecodeIndex, uid, didExit, callExitSiteData); | 
|  | } | 
|  |  | 
|  | PutByIdStatus PutByIdStatus::computeFor(JSGlobalObject* globalObject, const StructureSet& set, UniquedStringImpl* uid, bool isDirect, PrivateFieldPutKind privateFieldPutKind) | 
|  | { | 
|  | if (parseIndex(*uid)) | 
|  | return PutByIdStatus(TakesSlowPath); | 
|  |  | 
|  | if (set.isEmpty()) | 
|  | return PutByIdStatus(); | 
|  |  | 
|  | VM& vm = globalObject->vm(); | 
|  | PutByIdStatus result; | 
|  | result.m_state = Simple; | 
|  | for (unsigned i = 0; i < set.size(); ++i) { | 
|  | Structure* structure = set[i]; | 
|  |  | 
|  | if (structure->typeInfo().overridesGetOwnPropertySlot() && structure->typeInfo().type() != GlobalObjectType) | 
|  | return PutByIdStatus(TakesSlowPath); | 
|  |  | 
|  | if (!structure->propertyAccessesAreCacheable()) | 
|  | return PutByIdStatus(TakesSlowPath); | 
|  |  | 
|  | unsigned attributes; | 
|  | PropertyOffset offset = structure->getConcurrently(uid, attributes); | 
|  | if (isValidOffset(offset)) { | 
|  | // We can't have a valid offset for structures on `PutPrivateNameById` define mode | 
|  | // since it means we are redefining a private field. In such case, we need to take | 
|  | // slow path to throw exception. | 
|  | if (privateFieldPutKind.isDefine()) | 
|  | return PutByIdStatus(TakesSlowPath); | 
|  |  | 
|  | if (attributes & PropertyAttribute::CustomAccessorOrValue) | 
|  | return PutByIdStatus(MakesCalls); | 
|  |  | 
|  | if (attributes & (PropertyAttribute::Accessor | PropertyAttribute::ReadOnly)) | 
|  | return PutByIdStatus(TakesSlowPath); | 
|  |  | 
|  | WatchpointSet* replaceSet = structure->propertyReplacementWatchpointSet(offset); | 
|  | if (!replaceSet || replaceSet->isStillValid()) { | 
|  | // When this executes, it'll create, and fire, this replacement watchpoint set. | 
|  | // That means that  this has probably never executed or that something fishy is | 
|  | // going on. Also, we cannot create or fire the watchpoint set from the concurrent | 
|  | // JIT thread, so even if we wanted to do this, we'd need to have a lazy thingy. | 
|  | // So, better leave this alone and take slow path. | 
|  | return PutByIdStatus(TakesSlowPath); | 
|  | } | 
|  |  | 
|  | PutByIdVariant variant = | 
|  | PutByIdVariant::replace(structure, offset); | 
|  | if (!result.appendVariant(variant)) | 
|  | return PutByIdStatus(TakesSlowPath); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | // We can have a case with PutPrivateNameById in set mode and it | 
|  | // should never cause a structure transition because it means we are | 
|  | // trying to store in a not installed private field. We need to take | 
|  | // slow path to throw excpetion if it ever gets executed. | 
|  | if (privateFieldPutKind.isSet()) | 
|  | return PutByIdStatus(TakesSlowPath); | 
|  |  | 
|  | // Our hypothesis is that we're doing a transition. Before we prove that this is really | 
|  | // true, we want to do some sanity checks. | 
|  |  | 
|  | // Don't cache put transitions on dictionaries. | 
|  | if (structure->isDictionary()) | 
|  | return PutByIdStatus(TakesSlowPath); | 
|  |  | 
|  | // If the structure corresponds to something that isn't an object, then give up, since | 
|  | // we don't want to be adding properties to strings. | 
|  | if (!structure->typeInfo().isObject()) | 
|  | return PutByIdStatus(TakesSlowPath); | 
|  |  | 
|  | ObjectPropertyConditionSet conditionSet; | 
|  | if (!isDirect) { | 
|  | ASSERT(privateFieldPutKind.isNone()); | 
|  | conditionSet = generateConditionsForPropertySetterMissConcurrently( | 
|  | vm, globalObject, structure, uid); | 
|  | if (!conditionSet.isValid()) | 
|  | return PutByIdStatus(TakesSlowPath); | 
|  | } | 
|  |  | 
|  | // We only optimize if there is already a structure that the transition is cached to. | 
|  | Structure* transition = | 
|  | Structure::addPropertyTransitionToExistingStructureConcurrently(structure, uid, 0, offset); | 
|  | if (!transition) | 
|  | return PutByIdStatus(TakesSlowPath); | 
|  | ASSERT(isValidOffset(offset)); | 
|  |  | 
|  | bool didAppend = result.appendVariant( | 
|  | PutByIdVariant::transition( | 
|  | structure, transition, conditionSet, offset)); | 
|  | if (!didAppend) | 
|  | return PutByIdStatus(TakesSlowPath); | 
|  | } | 
|  |  | 
|  | return result; | 
|  | } | 
|  | #endif | 
|  |  | 
|  | bool PutByIdStatus::makesCalls() const | 
|  | { | 
|  | if (m_state == MakesCalls) | 
|  | return true; | 
|  |  | 
|  | if (m_state != Simple) | 
|  | return false; | 
|  |  | 
|  | for (unsigned i = m_variants.size(); i--;) { | 
|  | if (m_variants[i].makesCalls()) | 
|  | return true; | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | PutByIdStatus PutByIdStatus::slowVersion() const | 
|  | { | 
|  | return PutByIdStatus(makesCalls() ? MakesCalls : TakesSlowPath); | 
|  | } | 
|  |  | 
|  | void PutByIdStatus::markIfCheap(SlotVisitor& visitor) | 
|  | { | 
|  | for (PutByIdVariant& variant : m_variants) | 
|  | variant.markIfCheap(visitor); | 
|  | } | 
|  |  | 
|  | bool PutByIdStatus::finalize(VM& vm) | 
|  | { | 
|  | for (PutByIdVariant& variant : m_variants) { | 
|  | if (!variant.finalize(vm)) | 
|  | return false; | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | void PutByIdStatus::merge(const PutByIdStatus& other) | 
|  | { | 
|  | if (other.m_state == NoInformation) | 
|  | return; | 
|  |  | 
|  | auto mergeSlow = [&] () { | 
|  | *this = PutByIdStatus((makesCalls() || other.makesCalls()) ? MakesCalls : TakesSlowPath); | 
|  | }; | 
|  |  | 
|  | switch (m_state) { | 
|  | case NoInformation: | 
|  | *this = other; | 
|  | return; | 
|  |  | 
|  | case Simple: | 
|  | if (other.m_state != Simple) | 
|  | return mergeSlow(); | 
|  |  | 
|  | for (const PutByIdVariant& other : other.m_variants) { | 
|  | if (!appendVariant(other)) | 
|  | return mergeSlow(); | 
|  | } | 
|  | return; | 
|  |  | 
|  | case TakesSlowPath: | 
|  | case MakesCalls: | 
|  | return mergeSlow(); | 
|  | } | 
|  |  | 
|  | RELEASE_ASSERT_NOT_REACHED(); | 
|  | } | 
|  |  | 
|  | void PutByIdStatus::filter(const StructureSet& set) | 
|  | { | 
|  | if (m_state != Simple) | 
|  | return; | 
|  | filterICStatusVariants(m_variants, set); | 
|  | for (PutByIdVariant& variant : m_variants) | 
|  | variant.fixTransitionToReplaceIfNecessary(); | 
|  | if (m_variants.isEmpty()) | 
|  | m_state = NoInformation; | 
|  | } | 
|  |  | 
|  | void PutByIdStatus::dump(PrintStream& out) const | 
|  | { | 
|  | switch (m_state) { | 
|  | case NoInformation: | 
|  | out.print("(NoInformation)"); | 
|  | return; | 
|  |  | 
|  | case Simple: | 
|  | out.print("(", listDump(m_variants), ")"); | 
|  | return; | 
|  |  | 
|  | case TakesSlowPath: | 
|  | out.print("(TakesSlowPath)"); | 
|  | return; | 
|  | case MakesCalls: | 
|  | out.print("(MakesCalls)"); | 
|  | return; | 
|  | } | 
|  |  | 
|  | RELEASE_ASSERT_NOT_REACHED(); | 
|  | } | 
|  |  | 
|  | } // namespace JSC | 
|  |  |