bool GetByIdVariant::attemptToMerge(const GetByIdVariant& other) { if (m_offset != other.m_offset) return false; if (m_callLinkStatus || other.m_callLinkStatus) return false; if (!canMergeIntrinsicStructures(other)) return false; if (m_conditionSet.isEmpty() != other.m_conditionSet.isEmpty()) return false; ObjectPropertyConditionSet mergedConditionSet; if (!m_conditionSet.isEmpty()) { mergedConditionSet = m_conditionSet.mergedWith(other.m_conditionSet); if (!mergedConditionSet.isValid() || !mergedConditionSet.hasOneSlotBaseCondition()) return false; } m_conditionSet = mergedConditionSet; m_structureSet.merge(other.m_structureSet); return true; }
ObjectPropertyConditionSet generateConditionsForInstanceOf( VM& vm, JSCell* owner, ExecState* exec, Structure* headStructure, JSObject* prototype, bool shouldHit) { bool didHit = false; if (ObjectPropertyConditionSetInternal::verbose) dataLog("Searching for prototype ", JSValue(prototype), " starting with structure ", RawPointer(headStructure), " with shouldHit = ", shouldHit, "\n"); ObjectPropertyConditionSet result = generateConditions( vm, exec->lexicalGlobalObject(), headStructure, shouldHit ? prototype : nullptr, [&] (Vector<ObjectPropertyCondition>& conditions, JSObject* object) -> bool { if (ObjectPropertyConditionSetInternal::verbose) dataLog("Encountered object: ", RawPointer(object), "\n"); if (object == prototype) { RELEASE_ASSERT(shouldHit); didHit = true; return true; } Structure* structure = object->structure(vm); if (structure->hasPolyProto()) return false; conditions.append( ObjectPropertyCondition::hasPrototype( vm, owner, object, structure->storedPrototypeObject())); return true; }); if (result.isValid()) { if (ObjectPropertyConditionSetInternal::verbose) dataLog("didHit = ", didHit, ", shouldHit = ", shouldHit, "\n"); RELEASE_ASSERT(didHit == shouldHit); } return result; }
void StructureRareData::setObjectToStringValue(ExecState* exec, VM& vm, Structure* ownStructure, JSString* value, PropertySlot toStringTagSymbolSlot) { if (m_giveUpOnObjectToStringValueCache) return; ObjectPropertyConditionSet conditionSet; if (toStringTagSymbolSlot.isValue()) { // We don't handle the own property case of Symbol.toStringTag because we would never know if a new // object transitioning to the same structure had the same value stored in Symbol.toStringTag. // Additionally, this is a super unlikely case anyway. if (!toStringTagSymbolSlot.isCacheable() || toStringTagSymbolSlot.slotBase()->structure(vm) == ownStructure) return; // This will not create a condition for the current structure but that is good because we know the Symbol.toStringTag // is not on the ownStructure so we will transisition if one is added and this cache will no longer be used. conditionSet = generateConditionsForPrototypePropertyHit(vm, this, exec, ownStructure, toStringTagSymbolSlot.slotBase(), vm.propertyNames->toStringTagSymbol.impl()); ASSERT(!conditionSet.isValid() || conditionSet.hasOneSlotBaseCondition()); } else if (toStringTagSymbolSlot.isUnset()) conditionSet = generateConditionsForPropertyMiss(vm, this, exec, ownStructure, vm.propertyNames->toStringTagSymbol.impl()); else return; if (!conditionSet.isValid()) { m_giveUpOnObjectToStringValueCache = true; return; } ObjectPropertyCondition equivCondition; for (const ObjectPropertyCondition& condition : conditionSet) { if (condition.condition().kind() == PropertyCondition::Presence) { ASSERT(isValidOffset(condition.offset())); condition.object()->structure(vm)->startWatchingPropertyForReplacements(vm, condition.offset()); equivCondition = condition.attemptToMakeEquivalenceWithoutBarrier(); // The equivalence condition won't be watchable if we have already seen a replacement. if (!equivCondition.isWatchable()) { m_giveUpOnObjectToStringValueCache = true; return; } } else if (!condition.isWatchable()) { m_giveUpOnObjectToStringValueCache = true; return; } } ASSERT(conditionSet.structuresEnsureValidity()); for (ObjectPropertyCondition condition : conditionSet) { if (condition.condition().kind() == PropertyCondition::Presence) { m_objectToStringAdaptiveInferredValueWatchpoint = std::make_unique<ObjectToStringAdaptiveInferredPropertyValueWatchpoint>(equivCondition, this); m_objectToStringAdaptiveInferredValueWatchpoint->install(); } else m_objectToStringAdaptiveWatchpointSet.add(condition, this)->install(); } m_objectToStringValue.set(vm, this, value); }
ObjectPropertyConditionSet ObjectPropertyConditionSet::mergedWith( const ObjectPropertyConditionSet& other) const { if (!isValid() || !other.isValid()) return invalid(); Vector<ObjectPropertyCondition> result; if (!isEmpty()) result.appendVector(m_data->vector); for (const ObjectPropertyCondition& newCondition : other) { bool foundMatch = false; for (const ObjectPropertyCondition& existingCondition : *this) { if (newCondition == existingCondition) { foundMatch = true; continue; } if (!newCondition.isCompatibleWith(existingCondition)) return invalid(); } if (!foundMatch) result.append(newCondition); } return create(result); }
PutByIdStatus PutByIdStatus::computeFromLLInt(CodeBlock* profiledBlock, unsigned bytecodeIndex, UniquedStringImpl* uid) { UNUSED_PARAM(profiledBlock); UNUSED_PARAM(bytecodeIndex); UNUSED_PARAM(uid); VM& vm = *profiledBlock->vm(); Instruction* instruction = profiledBlock->instructions().begin() + bytecodeIndex; StructureID structureID = instruction[4].u.structureID; if (!structureID) return PutByIdStatus(NoInformation); Structure* structure = vm.heap.structureIDTable().get(structureID); StructureID newStructureID = instruction[6].u.structureID; if (!newStructureID) { PropertyOffset offset = structure->getConcurrently(uid); if (!isValidOffset(offset)) return PutByIdStatus(NoInformation); return PutByIdVariant::replace(structure, offset, structure->inferredTypeDescriptorFor(uid)); } Structure* newStructure = vm.heap.structureIDTable().get(newStructureID); ASSERT(structure->transitionWatchpointSetHasBeenInvalidated()); PropertyOffset offset = newStructure->getConcurrently(uid); if (!isValidOffset(offset)) return PutByIdStatus(NoInformation); ObjectPropertyConditionSet conditionSet; if (!(instruction[8].u.putByIdFlags & PutByIdIsDirect)) { conditionSet = generateConditionsForPropertySetterMissConcurrently( *profiledBlock->vm(), profiledBlock->globalObject(), structure, uid); if (!conditionSet.isValid()) return PutByIdStatus(NoInformation); } return PutByIdVariant::transition( structure, newStructure, conditionSet, offset, newStructure->inferredTypeDescriptorFor(uid)); }
GetByIdVariant::GetByIdVariant( const StructureSet& structureSet, PropertyOffset offset, const ObjectPropertyConditionSet& conditionSet, std::unique_ptr<CallLinkStatus> callLinkStatus, JSFunction* intrinsicFunction) : m_structureSet(structureSet) , m_conditionSet(conditionSet) , m_offset(offset) , m_callLinkStatus(WTFMove(callLinkStatus)) , m_intrinsicFunction(intrinsicFunction) { if (!structureSet.size()) { ASSERT(offset == invalidOffset); ASSERT(conditionSet.isEmpty()); } if (intrinsicFunction) ASSERT(intrinsic() != NoIntrinsic); }
PutByIdStatus PutByIdStatus::computeFor(JSGlobalObject* globalObject, const StructureSet& set, UniquedStringImpl* uid, bool isDirect) { if (parseIndex(*uid)) return PutByIdStatus(TakesSlowPath); if (set.isEmpty()) return PutByIdStatus(); 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)) { if (attributes & CustomAccessor) return PutByIdStatus(MakesCalls); if (attributes & (Accessor | 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, structure->inferredTypeDescriptorFor(uid)); if (!result.appendVariant(variant)) return PutByIdStatus(TakesSlowPath); continue; } // 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) { conditionSet = generateConditionsForPropertySetterMissConcurrently( globalObject->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, transition->inferredTypeDescriptorFor(uid))); if (!didAppend) return PutByIdStatus(TakesSlowPath); } return result; }
PutByIdStatus PutByIdStatus::computeForStubInfo( const ConcurrentJITLocker& locker, CodeBlock* profiledBlock, StructureStubInfo* stubInfo, UniquedStringImpl* uid, CallLinkStatus::ExitSiteData callExitSiteData) { if (!stubInfo || !stubInfo->everConsidered) return PutByIdStatus(); if (stubInfo->tookSlowPath) return PutByIdStatus(TakesSlowPath); switch (stubInfo->cacheType) { case CacheType::Unset: // This means that we attempted to cache but failed for some reason. return PutByIdStatus(TakesSlowPath); case CacheType::PutByIdReplace: { PropertyOffset offset = stubInfo->u.byIdSelf.baseObjectStructure->getConcurrently(uid); if (isValidOffset(offset)) { return PutByIdVariant::replace( stubInfo->u.byIdSelf.baseObjectStructure.get(), offset, InferredType::Top); } return PutByIdStatus(TakesSlowPath); } case CacheType::Stub: { PolymorphicAccess* list = stubInfo->u.stub; PutByIdStatus result; result.m_state = Simple; State slowPathState = TakesSlowPath; for (unsigned i = 0; i < list->size(); ++i) { const AccessCase& access = list->at(i); if (access.doesCalls()) slowPathState = MakesCalls; } for (unsigned i = 0; i < list->size(); ++i) { const AccessCase& access = list->at(i); if (access.viaProxy()) return PutByIdStatus(slowPathState); PutByIdVariant variant; switch (access.type()) { case AccessCase::Replace: { Structure* structure = access.structure(); PropertyOffset offset = structure->getConcurrently(uid); if (!isValidOffset(offset)) return PutByIdStatus(slowPathState); variant = PutByIdVariant::replace( structure, offset, structure->inferredTypeDescriptorFor(uid)); break; } case AccessCase::Transition: { PropertyOffset offset = access.newStructure()->getConcurrently(uid); if (!isValidOffset(offset)) return PutByIdStatus(slowPathState); ObjectPropertyConditionSet conditionSet = access.conditionSet(); if (!conditionSet.structuresEnsureValidity()) return PutByIdStatus(slowPathState); variant = PutByIdVariant::transition( access.structure(), access.newStructure(), conditionSet, offset, access.newStructure()->inferredTypeDescriptorFor(uid)); 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(slowPathState); case ComplexGetStatus::Inlineable: { CallLinkInfo* callLinkInfo = access.callLinkInfo(); ASSERT(callLinkInfo); std::unique_ptr<CallLinkStatus> callLinkStatus = std::make_unique<CallLinkStatus>( CallLinkStatus::computeFor( locker, profiledBlock, *callLinkInfo, callExitSiteData)); variant = PutByIdVariant::setter( structure, complexGetStatus.offset(), complexGetStatus.conditionSet(), WTFMove(callLinkStatus)); } } break; } case AccessCase::CustomSetter: return PutByIdStatus(MakesCalls); default: return PutByIdStatus(slowPathState); } if (!result.appendVariant(variant)) return PutByIdStatus(slowPathState); } return result; } default: return PutByIdStatus(TakesSlowPath); } }