blob: bdd49334d548e63772625b128f8d5694a72fcca9 [file]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#pragma once
namespace Js
{
template<
bool CheckLocal,
bool CheckProto,
bool CheckAccessor,
bool CheckMissing,
bool ReturnOperationInfo>
bool InlineCache::TryGetProperty(
Var const instance,
RecyclableObject *const propertyObject,
const PropertyId propertyId,
Var *const propertyValue,
ScriptContext *const requestContext,
PropertyCacheOperationInfo *const operationInfo)
{
CompileAssert(CheckLocal || CheckProto || CheckAccessor);
Assert(!ReturnOperationInfo || operationInfo);
CompileAssert(!ReturnOperationInfo || (CheckLocal && CheckProto && CheckAccessor));
Assert(instance);
Assert(propertyObject);
Assert(propertyId != Constants::NoProperty);
Assert(propertyValue);
Assert(requestContext);
DebugOnly(VerifyRegistrationForInvalidation(this, requestContext, propertyId));
Type *const type = propertyObject->GetType();
if (CheckLocal && type == u.local.type)
{
Assert(propertyObject->GetScriptContext() == requestContext); // we never cache a type from another script context
*propertyValue = DynamicObject::FromVar(propertyObject)->GetInlineSlot(u.local.slotIndex);
Assert(*propertyValue == JavascriptOperators::GetProperty(propertyObject, propertyId, requestContext) ||
(RootObjectBase::Is(propertyObject) && *propertyValue == JavascriptOperators::GetRootProperty(propertyObject, propertyId, requestContext)));
if (ReturnOperationInfo)
{
operationInfo->cacheType = CacheType_Local;
operationInfo->slotType = SlotType_Inline;
}
return true;
}
if (CheckLocal && TypeWithAuxSlotTag(type) == u.local.type)
{
Assert(propertyObject->GetScriptContext() == requestContext); // we never cache a type from another script context
*propertyValue = DynamicObject::FromVar(propertyObject)->GetAuxSlot(u.local.slotIndex);
Assert(*propertyValue == JavascriptOperators::GetProperty(propertyObject, propertyId, requestContext) ||
(RootObjectBase::Is(propertyObject) && *propertyValue == JavascriptOperators::GetRootProperty(propertyObject, propertyId, requestContext)));
if (ReturnOperationInfo)
{
operationInfo->cacheType = CacheType_Local;
operationInfo->slotType = SlotType_Aux;
}
return true;
}
if (CheckProto && type == u.proto.type && !this->u.proto.isMissing)
{
Assert(u.proto.prototypeObject->GetScriptContext() == requestContext); // we never cache a type from another script context
*propertyValue = u.proto.prototypeObject->GetInlineSlot(u.proto.slotIndex);
Assert(*propertyValue == JavascriptOperators::GetProperty(propertyObject, propertyId, requestContext) ||
(RootObjectBase::Is(propertyObject) && *propertyValue == JavascriptOperators::GetRootProperty(propertyObject, propertyId, requestContext)));
if (ReturnOperationInfo)
{
operationInfo->cacheType = CacheType_Proto;
operationInfo->slotType = SlotType_Inline;
}
return true;
}
if (CheckProto && TypeWithAuxSlotTag(type) == u.proto.type && !this->u.proto.isMissing)
{
Assert(u.proto.prototypeObject->GetScriptContext() == requestContext); // we never cache a type from another script context
*propertyValue = u.proto.prototypeObject->GetAuxSlot(u.proto.slotIndex);
Assert(*propertyValue == JavascriptOperators::GetProperty(propertyObject, propertyId, requestContext) ||
(RootObjectBase::Is(propertyObject) && *propertyValue == JavascriptOperators::GetRootProperty(propertyObject, propertyId, requestContext)));
if (ReturnOperationInfo)
{
operationInfo->cacheType = CacheType_Proto;
operationInfo->slotType = SlotType_Aux;
}
return true;
}
if (CheckAccessor && type == u.accessor.type)
{
Assert(propertyObject->GetScriptContext() == requestContext); // we never cache a type from another script context
Assert(u.accessor.flags & InlineCacheGetterFlag);
RecyclableObject *const function = RecyclableObject::FromVar(u.accessor.object->GetInlineSlot(u.accessor.slotIndex));
*propertyValue = JavascriptOperators::CallGetter(function, instance, requestContext);
// Can't assert because the getter could have a side effect
#ifdef CHKGETTER
Assert(JavascriptOperators::Equal(*propertyValue, JavascriptOperators::GetProperty(propertyObject, propertyId, requestContext), requestContext));
#endif
if (ReturnOperationInfo)
{
operationInfo->cacheType = CacheType_Getter;
operationInfo->slotType = SlotType_Inline;
}
return true;
}
if (CheckAccessor && TypeWithAuxSlotTag(type) == u.accessor.type)
{
Assert(propertyObject->GetScriptContext() == requestContext); // we never cache a type from another script context
Assert(u.accessor.flags & InlineCacheGetterFlag);
RecyclableObject *const function = RecyclableObject::FromVar(u.accessor.object->GetAuxSlot(u.accessor.slotIndex));
*propertyValue = JavascriptOperators::CallGetter(function, instance, requestContext);
// Can't assert because the getter could have a side effect
#ifdef CHKGETTER
Assert(JavascriptOperators::Equal(*propertyValue, JavascriptOperators::GetProperty(propertyObject, propertyId, requestContext), requestContext));
#endif
if (ReturnOperationInfo)
{
operationInfo->cacheType = CacheType_Getter;
operationInfo->slotType = SlotType_Aux;
}
return true;
}
if (CheckMissing && type == u.proto.type && this->u.proto.isMissing)
{
Assert(u.proto.prototypeObject->GetScriptContext() == requestContext); // we never cache a type from another script context
*propertyValue = u.proto.prototypeObject->GetInlineSlot(u.proto.slotIndex);
Assert(*propertyValue == JavascriptOperators::GetProperty(propertyObject, propertyId, requestContext) ||
(RootObjectBase::Is(propertyObject) && *propertyValue == JavascriptOperators::GetRootProperty(propertyObject, propertyId, requestContext)));
#ifdef MISSING_PROPERTY_STATS
if (PHASE_STATS1(MissingPropertyCachePhase))
{
requestContext->RecordMissingPropertyHit();
}
#endif
if (ReturnOperationInfo)
{
operationInfo->cacheType = CacheType_Proto;
operationInfo->slotType = SlotType_Inline;
}
return true;
}
if (CheckMissing && TypeWithAuxSlotTag(type) == u.proto.type && this->u.proto.isMissing)
{
Assert(u.proto.prototypeObject->GetScriptContext() == requestContext); // we never cache a type from another script context
*propertyValue = u.proto.prototypeObject->GetAuxSlot(u.proto.slotIndex);
Assert(*propertyValue == JavascriptOperators::GetProperty(propertyObject, propertyId, requestContext) ||
(RootObjectBase::Is(propertyObject) && *propertyValue == JavascriptOperators::GetRootProperty(propertyObject, propertyId, requestContext)));
#ifdef MISSING_PROPERTY_STATS
if (PHASE_STATS1(MissingPropertyCachePhase))
{
requestContext->RecordMissingPropertyHit();
}
#endif
if (ReturnOperationInfo)
{
operationInfo->cacheType = CacheType_Proto;
operationInfo->slotType = SlotType_Aux;
}
return true;
}
return false;
}
template<
bool CheckLocal,
bool CheckLocalTypeWithoutProperty,
bool CheckAccessor,
bool ReturnOperationInfo>
bool InlineCache::TrySetProperty(
RecyclableObject *const object,
const PropertyId propertyId,
Var propertyValue,
ScriptContext *const requestContext,
PropertyCacheOperationInfo *const operationInfo,
const PropertyOperationFlags propertyOperationFlags)
{
CompileAssert(CheckLocal || CheckLocalTypeWithoutProperty || CheckAccessor);
Assert(!ReturnOperationInfo || operationInfo);
CompileAssert(!ReturnOperationInfo || (CheckLocal && CheckLocalTypeWithoutProperty && CheckAccessor));
Assert(object);
Assert(propertyId != Constants::NoProperty);
Assert(requestContext);
DebugOnly(VerifyRegistrationForInvalidation(this, requestContext, propertyId));
#if DBG
const bool isRoot = (propertyOperationFlags & PropertyOperation_Root) != 0;
bool canSetField; // To verify if we can set a field on the object
Var setterValue = nullptr;
{
// We need to disable implicit call to ensure the check doesn't cause unwanted side effects in debug code
// Save old disableImplicitFlags and implicitCallFlags and disable implicit call and exception
ThreadContext * threadContext = requestContext->GetThreadContext();
DisableImplicitFlags disableImplicitFlags = *threadContext->GetAddressOfDisableImplicitFlags();
Js::ImplicitCallFlags implicitCallFlags = threadContext->GetImplicitCallFlags();
threadContext->ClearImplicitCallFlags();
*threadContext->GetAddressOfDisableImplicitFlags() = DisableImplicitCallAndExceptionFlag;
DescriptorFlags flags = DescriptorFlags::None;
canSetField = !JavascriptOperators::CheckPrototypesForAccessorOrNonWritablePropertySlow(object, propertyId, &setterValue, &flags, isRoot, requestContext);
if (threadContext->GetImplicitCallFlags() != Js::ImplicitCall_None)
{
canSetField = true; // If there was an implicit call, inconclusive. Disable debug check.
setterValue = nullptr;
}
else
if ((flags & Accessor) == Accessor)
{
Assert(setterValue != nullptr);
}
// Restore old disableImplicitFlags and implicitCallFlags
*threadContext->GetAddressOfDisableImplicitFlags() = disableImplicitFlags;
threadContext->SetImplicitCallFlags(implicitCallFlags);
}
#endif
Type *const type = object->GetType();
if (CheckLocal && type == u.local.type)
{
Assert(object->GetScriptContext() == requestContext); // we never cache a type from another script context
Assert(isRoot || object->GetPropertyIndex(propertyId) == DynamicObject::FromVar(object)->GetTypeHandler()->InlineOrAuxSlotIndexToPropertyIndex(u.local.slotIndex, true));
Assert(!isRoot || RootObjectBase::FromVar(object)->GetRootPropertyIndex(propertyId) == DynamicObject::FromVar(object)->GetTypeHandler()->InlineOrAuxSlotIndexToPropertyIndex(u.local.slotIndex, true));
Assert(object->CanStorePropertyValueDirectly(propertyId, isRoot));
DynamicObject::FromVar(object)->SetInlineSlot(SetSlotArgumentsRoot(propertyId, isRoot, u.local.slotIndex, propertyValue));
if (ReturnOperationInfo)
{
operationInfo->cacheType = CacheType_Local;
operationInfo->slotType = SlotType_Inline;
}
Assert(canSetField);
return true;
}
if (CheckLocal && TypeWithAuxSlotTag(type) == u.local.type)
{
Assert(object->GetScriptContext() == requestContext); // we never cache a type from another script context
Assert(isRoot || object->GetPropertyIndex(propertyId) == DynamicObject::FromVar(object)->GetTypeHandler()->InlineOrAuxSlotIndexToPropertyIndex(u.local.slotIndex, false));
Assert(!isRoot || RootObjectBase::FromVar(object)->GetRootPropertyIndex(propertyId) == DynamicObject::FromVar(object)->GetTypeHandler()->InlineOrAuxSlotIndexToPropertyIndex(u.local.slotIndex, false));
Assert(object->CanStorePropertyValueDirectly(propertyId, isRoot));
DynamicObject::FromVar(object)->SetAuxSlot(SetSlotArgumentsRoot(propertyId, isRoot, u.local.slotIndex, propertyValue));
if (ReturnOperationInfo)
{
operationInfo->cacheType = CacheType_Local;
operationInfo->slotType = SlotType_Aux;
}
Assert(canSetField);
return true;
}
if (CheckLocalTypeWithoutProperty && type == u.local.typeWithoutProperty)
{
// CAREFUL! CheckIfPrototypeChainHasOnlyWritableDataProperties may do allocation that triggers GC and
// clears this cache, so save any info that is needed from the cache before calling those functions.
Type *const typeWithProperty = u.local.type;
const PropertyIndex propertyIndex = u.local.slotIndex;
#if DBG
uint16 newAuxSlotCapacity = u.local.requiredAuxSlotCapacity;
#endif
Assert(object->GetScriptContext() == requestContext); // we never cache a type from another script context
Assert(typeWithProperty);
Assert(DynamicType::Is(typeWithProperty->GetTypeId()));
Assert(((DynamicType*)typeWithProperty)->GetIsShared());
Assert(((DynamicType*)typeWithProperty)->GetTypeHandler()->IsPathTypeHandler());
AssertMsg(!((DynamicType*)u.local.typeWithoutProperty)->GetTypeHandler()->GetIsPrototype(), "Why did we cache a property add for a prototype?");
Assert(((DynamicType*)typeWithProperty)->GetTypeHandler()->CanStorePropertyValueDirectly((const DynamicObject*)object, propertyId, isRoot));
DynamicObject *const dynamicObject = DynamicObject::FromVar(object);
// If we're adding a property to an inlined slot, we should never need to adjust auxiliary slot array size.
Assert(newAuxSlotCapacity == 0);
dynamicObject->type = typeWithProperty;
Assert(isRoot || object->GetPropertyIndex(propertyId) == DynamicObject::FromVar(object)->GetTypeHandler()->InlineOrAuxSlotIndexToPropertyIndex(propertyIndex, true));
Assert(!isRoot || RootObjectBase::FromVar(object)->GetRootPropertyIndex(propertyId) == DynamicObject::FromVar(object)->GetTypeHandler()->InlineOrAuxSlotIndexToPropertyIndex(propertyIndex, true));
dynamicObject->SetInlineSlot(SetSlotArgumentsRoot(propertyId, isRoot, propertyIndex, propertyValue));
if (ReturnOperationInfo)
{
operationInfo->cacheType = CacheType_LocalWithoutProperty;
operationInfo->slotType = SlotType_Inline;
}
Assert(canSetField);
return true;
}
if (CheckLocalTypeWithoutProperty && TypeWithAuxSlotTag(type) == u.local.typeWithoutProperty)
{
// CAREFUL! CheckIfPrototypeChainHasOnlyWritableDataProperties or AdjustSlots may do allocation that triggers GC and
// clears this cache, so save any info that is needed from the cache before calling those functions.
Type *const typeWithProperty = TypeWithoutAuxSlotTag(u.local.type);
const PropertyIndex propertyIndex = u.local.slotIndex;
uint16 newAuxSlotCapacity = u.local.requiredAuxSlotCapacity;
Assert(object->GetScriptContext() == requestContext); // we never cache a type from another script context
Assert(typeWithProperty);
Assert(DynamicType::Is(typeWithProperty->GetTypeId()));
Assert(((DynamicType*)typeWithProperty)->GetIsShared());
Assert(((DynamicType*)typeWithProperty)->GetTypeHandler()->IsPathTypeHandler());
AssertMsg(!((DynamicType*)TypeWithoutAuxSlotTag(u.local.typeWithoutProperty))->GetTypeHandler()->GetIsPrototype(), "Why did we cache a property add for a prototype?");
Assert(((DynamicType*)typeWithProperty)->GetTypeHandler()->CanStorePropertyValueDirectly((const DynamicObject*)object, propertyId, isRoot));
DynamicObject *const dynamicObject = DynamicObject::FromVar(object);
if (newAuxSlotCapacity > 0)
{
DynamicTypeHandler::AdjustSlots(
dynamicObject,
static_cast<DynamicType *>(typeWithProperty)->GetTypeHandler()->GetInlineSlotCapacity(),
newAuxSlotCapacity);
}
dynamicObject->type = typeWithProperty;
Assert(isRoot || object->GetPropertyIndex(propertyId) == DynamicObject::FromVar(object)->GetTypeHandler()->InlineOrAuxSlotIndexToPropertyIndex(propertyIndex, false));
Assert(!isRoot || RootObjectBase::FromVar(object)->GetRootPropertyIndex(propertyId) == DynamicObject::FromVar(object)->GetTypeHandler()->InlineOrAuxSlotIndexToPropertyIndex(propertyIndex, false));
dynamicObject->SetAuxSlot(SetSlotArgumentsRoot(propertyId, isRoot, propertyIndex, propertyValue));
if (ReturnOperationInfo)
{
operationInfo->cacheType = CacheType_LocalWithoutProperty;
operationInfo->slotType = SlotType_Aux;
}
Assert(canSetField);
return true;
}
if (CheckAccessor && type == u.accessor.type)
{
Assert(object->GetScriptContext() == requestContext); // we never cache a type from another script context
Assert(u.accessor.flags & InlineCacheSetterFlag);
RecyclableObject *const function = RecyclableObject::FromVar(u.accessor.object->GetInlineSlot(u.accessor.slotIndex));
Assert(setterValue == nullptr || setterValue == function);
Js::JavascriptOperators::CallSetter(function, object, propertyValue, requestContext);
if (ReturnOperationInfo)
{
operationInfo->cacheType = CacheType_Setter;
operationInfo->slotType = SlotType_Inline;
}
return true;
}
if (CheckAccessor && TypeWithAuxSlotTag(type) == u.accessor.type)
{
Assert(object->GetScriptContext() == requestContext); // we never cache a type from another script context
Assert(u.accessor.flags & InlineCacheSetterFlag);
RecyclableObject *const function = RecyclableObject::FromVar(u.accessor.object->GetAuxSlot(u.accessor.slotIndex));
Assert(setterValue == nullptr || setterValue == function);
Js::JavascriptOperators::CallSetter(function, object, propertyValue, requestContext);
if (ReturnOperationInfo)
{
operationInfo->cacheType = CacheType_Setter;
operationInfo->slotType = SlotType_Aux;
}
return true;
}
return false;
}
template<
bool CheckLocal,
bool CheckProto,
bool CheckAccessor>
void PolymorphicInlineCache::CloneInlineCacheToEmptySlotInCollision(Type * const type, uint inlineCacheIndex)
{
if (CheckLocal && (inlineCaches[inlineCacheIndex].u.local.type == type || inlineCaches[inlineCacheIndex].u.local.type == TypeWithAuxSlotTag(type)))
{
return;
}
if (CheckProto && (inlineCaches[inlineCacheIndex].u.proto.type == type || inlineCaches[inlineCacheIndex].u.proto.type == TypeWithAuxSlotTag(type)))
{
return;
}
if (CheckAccessor && (inlineCaches[inlineCacheIndex].u.accessor.type == type || inlineCaches[inlineCacheIndex].u.accessor.type == TypeWithAuxSlotTag(type)))
{
return;
}
if (this->IsFull())
{
// If the cache is full, we won't find an empty slot to move the contents of the colliding inline cache to.
return;
}
// Collision is with a cache having a different type.
uint tryInlineCacheIndex = GetNextInlineCacheIndex(inlineCacheIndex);
// Iterate over the inline caches in the polymorphic cache, stop when:
// 1. an empty inline cache is found, or
// 2. a cache already populated with the incoming type is found, or
// 3. all the inline caches have been looked at.
while (!inlineCaches[tryInlineCacheIndex].IsEmpty() && tryInlineCacheIndex != inlineCacheIndex)
{
if (CheckLocal && (inlineCaches[tryInlineCacheIndex].u.local.type == type || inlineCaches[tryInlineCacheIndex].u.local.type == TypeWithAuxSlotTag(type)))
{
break;
}
if (CheckProto && (inlineCaches[tryInlineCacheIndex].u.proto.type == type || inlineCaches[tryInlineCacheIndex].u.proto.type == TypeWithAuxSlotTag(type)))
{
Assert(GetInlineCacheIndexForType(inlineCaches[tryInlineCacheIndex].u.proto.type) == inlineCacheIndex);
break;
}
if (CheckAccessor && (inlineCaches[tryInlineCacheIndex].u.accessor.type == type || inlineCaches[tryInlineCacheIndex].u.accessor.type == TypeWithAuxSlotTag(type)))
{
Assert(GetInlineCacheIndexForType(inlineCaches[tryInlineCacheIndex].u.accessor.type) == inlineCacheIndex);
break;
}
tryInlineCacheIndex = GetNextInlineCacheIndex(tryInlineCacheIndex);
}
if (tryInlineCacheIndex != inlineCacheIndex)
{
if (inlineCaches[inlineCacheIndex].invalidationListSlotPtr != nullptr)
{
Assert(*(inlineCaches[inlineCacheIndex].invalidationListSlotPtr) == &inlineCaches[inlineCacheIndex]);
if (inlineCaches[tryInlineCacheIndex].invalidationListSlotPtr != nullptr)
{
Assert(*(inlineCaches[tryInlineCacheIndex].invalidationListSlotPtr) == &inlineCaches[tryInlineCacheIndex]);
}
else
{
inlineCaches[tryInlineCacheIndex].invalidationListSlotPtr = inlineCaches[inlineCacheIndex].invalidationListSlotPtr;
*(inlineCaches[tryInlineCacheIndex].invalidationListSlotPtr) = &inlineCaches[tryInlineCacheIndex];
inlineCaches[inlineCacheIndex].invalidationListSlotPtr = nullptr;
}
}
inlineCaches[tryInlineCacheIndex].u = inlineCaches[inlineCacheIndex].u;
UpdateInlineCachesFillInfo(tryInlineCacheIndex, true /*set*/);
// Let's clear the cache slot on which we had the collision. We might have stolen the invalidationListSlotPtr,
// so it may not pass VerifyRegistrationForInvalidation. Besides, it will be repopulated with the incoming data,
// and registered for invalidation, if necessary.
inlineCaches[inlineCacheIndex].Clear();
Assert((this->inlineCachesFillInfo & (1 << inlineCacheIndex)) != 0);
UpdateInlineCachesFillInfo(inlineCacheIndex, false /*set*/);
}
}
#ifdef CLONE_INLINECACHE_TO_EMPTYSLOT
template <typename TDelegate>
bool PolymorphicInlineCache::CheckClonedInlineCache(uint inlineCacheIndex, TDelegate mapper)
{
bool success = false;
uint tryInlineCacheIndex = GetNextInlineCacheIndex(inlineCacheIndex);
do
{
if (inlineCaches[tryInlineCacheIndex].IsEmpty())
{
break;
}
success = mapper(tryInlineCacheIndex);
if (success)
{
Assert(inlineCaches[inlineCacheIndex].invalidationListSlotPtr == nullptr || *inlineCaches[inlineCacheIndex].invalidationListSlotPtr == &inlineCaches[inlineCacheIndex]);
Assert(inlineCaches[tryInlineCacheIndex].invalidationListSlotPtr == nullptr || *inlineCaches[tryInlineCacheIndex].invalidationListSlotPtr == &inlineCaches[tryInlineCacheIndex]);
// Swap inline caches, including their invalidationListSlotPtrs.
InlineCache temp = inlineCaches[tryInlineCacheIndex];
inlineCaches[tryInlineCacheIndex] = inlineCaches[inlineCacheIndex];
inlineCaches[inlineCacheIndex] = temp;
// Fix up invalidationListSlotPtrs to point to their owners.
if (inlineCaches[inlineCacheIndex].invalidationListSlotPtr != nullptr)
{
*inlineCaches[inlineCacheIndex].invalidationListSlotPtr = &inlineCaches[inlineCacheIndex];
}
if (inlineCaches[tryInlineCacheIndex].invalidationListSlotPtr != nullptr)
{
*inlineCaches[tryInlineCacheIndex].invalidationListSlotPtr = &inlineCaches[tryInlineCacheIndex];
}
break;
}
tryInlineCacheIndex = GetNextInlineCacheIndex(tryInlineCacheIndex);
} while (tryInlineCacheIndex != inlineCacheIndex);
return success;
}
#endif
template<
bool CheckLocal,
bool CheckProto,
bool CheckAccessor,
bool CheckMissing,
bool IsInlineCacheAvailable,
bool ReturnOperationInfo>
bool PolymorphicInlineCache::TryGetProperty(
Var const instance,
RecyclableObject *const propertyObject,
const PropertyId propertyId,
Var *const propertyValue,
ScriptContext *const requestContext,
PropertyCacheOperationInfo *const operationInfo,
InlineCache *const inlineCacheToPopulate)
{
Assert(!IsInlineCacheAvailable || inlineCacheToPopulate);
Assert(!ReturnOperationInfo || operationInfo);
Type * const type = propertyObject->GetType();
uint inlineCacheIndex = GetInlineCacheIndexForType(type);
InlineCache *cache = &inlineCaches[inlineCacheIndex];
#ifdef INLINE_CACHE_STATS
bool isEmpty = false;
if (PHASE_STATS1(Js::PolymorphicInlineCachePhase))
{
isEmpty = cache->IsEmpty();
}
#endif
bool result = cache->TryGetProperty<CheckLocal, CheckProto, CheckAccessor, CheckMissing, ReturnOperationInfo>(
instance, propertyObject, propertyId, propertyValue, requestContext, operationInfo);
#ifdef CLONE_INLINECACHE_TO_EMPTYSLOT
if (!result && !cache->IsEmpty())
{
result = CheckClonedInlineCache(inlineCacheIndex, [&](uint tryInlineCacheIndex) -> bool
{
cache = &inlineCaches[tryInlineCacheIndex];
return cache->TryGetProperty<CheckLocal, CheckProto, CheckAccessor, CheckMissing, ReturnOperationInfo>(
instance, propertyObject, propertyId, propertyValue, requestContext, operationInfo);
});
}
#endif
if (IsInlineCacheAvailable && result)
{
cache->CopyTo(propertyId, requestContext, inlineCacheToPopulate);
}
#ifdef INLINE_CACHE_STATS
if (PHASE_STATS1(Js::PolymorphicInlineCachePhase))
{
bool collision = !result && !isEmpty;
GetScriptContext()->LogCacheUsage(this, /*isGet*/ true, propertyId, result, collision);
}
#endif
return result;
}
template<
bool CheckLocal,
bool CheckLocalTypeWithoutProperty,
bool CheckAccessor,
bool IsInlineCacheAvailable,
bool ReturnOperationInfo>
bool PolymorphicInlineCache::TrySetProperty(
RecyclableObject *const object,
const PropertyId propertyId,
Var propertyValue,
ScriptContext *const requestContext,
PropertyCacheOperationInfo *const operationInfo,
InlineCache *const inlineCacheToPopulate,
const PropertyOperationFlags propertyOperationFlags)
{
Assert(!IsInlineCacheAvailable || inlineCacheToPopulate);
Assert(!ReturnOperationInfo || operationInfo);
Type * const type = object->GetType();
uint inlineCacheIndex = GetInlineCacheIndexForType(type);
InlineCache *cache = &inlineCaches[inlineCacheIndex];
#ifdef INLINE_CACHE_STATS
bool isEmpty = false;
if (PHASE_STATS1(Js::PolymorphicInlineCachePhase))
{
isEmpty = cache->IsEmpty();
}
#endif
bool result = cache->TrySetProperty<CheckLocal, CheckLocalTypeWithoutProperty, CheckAccessor, ReturnOperationInfo>(
object, propertyId, propertyValue, requestContext, operationInfo, propertyOperationFlags);
#ifdef CLONE_INLINECACHE_TO_EMPTYSLOT
if (!result && !cache->IsEmpty())
{
result = CheckClonedInlineCache(inlineCacheIndex, [&](uint tryInlineCacheIndex) -> bool
{
cache = &inlineCaches[tryInlineCacheIndex];
return cache->TrySetProperty<CheckLocal, CheckLocalTypeWithoutProperty, CheckAccessor, ReturnOperationInfo>(
object, propertyId, propertyValue, requestContext, operationInfo, propertyOperationFlags);
});
}
#endif
if (IsInlineCacheAvailable && result)
{
cache->CopyTo(propertyId, requestContext, inlineCacheToPopulate);
}
#ifdef INLINE_CACHE_STATS
if (PHASE_STATS1(Js::PolymorphicInlineCachePhase))
{
bool collision = !result && !isEmpty;
GetScriptContext()->LogCacheUsage(this, /*isGet*/ false, propertyId, result, collision);
}
#endif
return result;
}
}