| //------------------------------------------------------------------------------------------------------- |
| // 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 |
| |
| #define TypeWithAuxSlotTag(_t) \ |
| (reinterpret_cast<Type*>(reinterpret_cast<size_t>(_t) | InlineCacheAuxSlotTypeTag)) |
| #define TypeWithoutAuxSlotTag(_t) \ |
| (reinterpret_cast<Js::Type*>(reinterpret_cast<size_t>(_t) & ~InlineCacheAuxSlotTypeTag)) |
| #define TypeHasAuxSlotTag(_t) \ |
| (!!(reinterpret_cast<size_t>(_t) & InlineCacheAuxSlotTypeTag)) |
| |
| #if defined(_M_IX86_OR_ARM32) |
| #define PolymorphicInlineCacheShift 5 // On 32 bit architectures, the least 5 significant bits of a DynamicTypePointer is 0 |
| #else |
| #define PolymorphicInlineCacheShift 6 // On 64 bit architectures, the least 6 significant bits of a DynamicTypePointer is 0 |
| #endif |
| |
| // forward decl |
| class JITType; |
| struct InlineCacheData; |
| template <class TAllocator> class JITTypeHolderBase; |
| typedef JITTypeHolderBase<void> JITTypeHolder; |
| typedef JITTypeHolderBase<Recycler> RecyclerJITTypeHolder; |
| |
| namespace Js |
| { |
| enum CacheType : byte |
| { |
| CacheType_None, |
| CacheType_Local, |
| CacheType_Proto, |
| CacheType_LocalWithoutProperty, |
| CacheType_Getter, |
| CacheType_Setter, |
| CacheType_TypeProperty, |
| }; |
| |
| enum SlotType : byte |
| { |
| SlotType_None, |
| SlotType_Inline, |
| SlotType_Aux, |
| }; |
| |
| struct PropertyCacheOperationInfo |
| { |
| PropertyCacheOperationInfo() |
| : cacheType(CacheType_None), slotType(SlotType_None), isPolymorphic(false) |
| { |
| } |
| |
| CacheType cacheType; |
| SlotType slotType; |
| bool isPolymorphic; |
| }; |
| |
| struct JitTimeInlineCache; |
| struct InlineCache |
| { |
| static const int CacheLayoutSelectorBitCount = 1; |
| static const int RequiredAuxSlotCapacityBitCount = 15; |
| static const bool IsPolymorphic = false; |
| |
| InlineCache() {} |
| |
| union |
| { |
| // Invariants: |
| // - Type* fields do not overlap. |
| // - "next" field is non-null iff the cache is linked in a list of proto-caches |
| // (see ScriptContext::RegisterProtoInlineCache and ScriptContext::InvalidateProtoCaches). |
| |
| struct s_local |
| { |
| Type* type; |
| |
| // PatchPutValue caches here the type the object has before a new property is added. |
| // If this type is hit again we can immediately change the object's type to "type" |
| // and store the value into the slot "slotIndex". |
| Type* typeWithoutProperty; |
| |
| union |
| { |
| struct |
| { |
| uint16 isLocal : 1; |
| uint16 requiredAuxSlotCapacity : 15; // Maximum auxiliary slot capacity (for a path type) must be < 2^16 |
| }; |
| struct |
| { |
| uint16 rawUInt16; // Required for access from JIT-ed code |
| }; |
| }; |
| uint16 slotIndex; |
| } local; |
| |
| struct s_proto |
| { |
| uint16 isProto : 1; |
| uint16 isMissing : 1; |
| uint16 unused : 14; |
| uint16 slotIndex; |
| |
| // It's OK for the type in proto layout to overlap with typeWithoutProperty in the local layout, because |
| // we only use typeWithoutProperty on field stores, which can never have a proto layout. |
| Type* type; |
| |
| DynamicObject* prototypeObject; |
| } proto; |
| |
| struct s_accessor |
| { |
| DynamicObject *object; |
| |
| union |
| { |
| struct { |
| uint16 isAccessor : 1; |
| uint16 flags : 2; |
| uint16 isOnProto : 1; |
| uint16 unused : 12; |
| }; |
| uint16 rawUInt16; |
| }; |
| uint16 slotIndex; |
| |
| Type * type; |
| } accessor; |
| |
| CompileAssert(sizeof(s_local) == sizeof(s_proto)); |
| CompileAssert(sizeof(s_local) == sizeof(s_accessor)); |
| } u; |
| |
| InlineCache** invalidationListSlotPtr; |
| |
| bool IsEmpty() const |
| { |
| return u.local.type == nullptr; |
| } |
| |
| bool IsLocal() const |
| { |
| return u.local.isLocal; |
| } |
| |
| bool IsProto() const |
| { |
| return u.proto.isProto; |
| } |
| |
| DynamicObject * GetPrototypeObject() const |
| { |
| Assert(IsProto()); |
| return u.proto.prototypeObject; |
| } |
| |
| DynamicObject * GetAccessorObject() const |
| { |
| Assert(IsAccessor()); |
| return u.accessor.object; |
| } |
| |
| bool IsAccessor() const |
| { |
| return u.accessor.isAccessor; |
| } |
| |
| bool IsAccessorOnProto() const |
| { |
| return IsAccessor() && u.accessor.isOnProto; |
| } |
| |
| bool IsGetterAccessor() const |
| { |
| return IsAccessor() && !!(u.accessor.flags & InlineCacheGetterFlag); |
| } |
| |
| bool IsGetterAccessorOnProto() const |
| { |
| return IsGetterAccessor() && u.accessor.isOnProto; |
| } |
| |
| bool IsSetterAccessor() const |
| { |
| return IsAccessor() && !!(u.accessor.flags & InlineCacheSetterFlag); |
| } |
| |
| bool IsSetterAccessorOnProto() const |
| { |
| return IsSetterAccessor() && u.accessor.isOnProto; |
| } |
| |
| Type* GetRawType() const |
| { |
| return IsLocal() ? u.local.type : (IsProto() ? u.proto.type : (IsAccessor() ? u.accessor.type : nullptr)); |
| } |
| |
| Type* GetType() const |
| { |
| return TypeWithoutAuxSlotTag(GetRawType()); |
| } |
| |
| template<bool isAccessor> |
| bool HasDifferentType(const bool isProto, const Type * type, const Type * typeWithoutProperty) const; |
| |
| bool HasType_Flags(const Type * type) const |
| { |
| return u.accessor.type == type || u.accessor.type == TypeWithAuxSlotTag(type); |
| } |
| |
| bool HasDifferentType(const Type * type) const |
| { |
| return !IsEmpty() && GetType() != type; |
| } |
| |
| bool RemoveFromInvalidationList() |
| { |
| if (this->invalidationListSlotPtr == nullptr) |
| { |
| return false; |
| } |
| |
| *this->invalidationListSlotPtr = nullptr; |
| this->invalidationListSlotPtr = nullptr; |
| return true; |
| } |
| |
| #ifdef ENABLE_DEBUG_CONFIG_OPTIONS |
| const char16 *LayoutString() const |
| { |
| if (IsEmpty()) |
| { |
| return _u("Empty"); |
| } |
| if (IsLocal()) |
| { |
| return _u("Local"); |
| } |
| if (IsAccessor()) |
| { |
| return _u("Accessor"); |
| } |
| return _u("Proto"); |
| } |
| #endif |
| |
| public: |
| void CacheLocal( |
| Type *const type, |
| const PropertyId propertyId, |
| const PropertyIndex propertyIndex, |
| const bool isInlineSlot, |
| Type *const typeWithoutProperty, |
| int requiredAuxSlotCapacity, |
| ScriptContext *const requestContext); |
| |
| void CacheProto( |
| DynamicObject *const prototypeObjectWithProperty, |
| const PropertyId propertyId, |
| const PropertyIndex propertyIndex, |
| const bool isInlineSlot, |
| const bool isMissing, |
| Type *const type, |
| ScriptContext *const requestContext); |
| |
| void CacheMissing( |
| DynamicObject *const missingPropertyHolder, |
| const PropertyId propertyId, |
| const PropertyIndex propertyIndex, |
| const bool isInlineSlot, |
| Type *const type, |
| ScriptContext *const requestContext); |
| |
| void CacheAccessor( |
| const bool isGetter, |
| const PropertyId propertyId, |
| const PropertyIndex propertyIndex, |
| const bool isInlineSlot, |
| Type *const type, |
| DynamicObject *const object, |
| const bool isOnProto, |
| ScriptContext *const requestContext); |
| |
| template< |
| bool CheckLocal, |
| bool CheckProto, |
| bool CheckAccessor, |
| bool CheckMissing, |
| bool ReturnOperationInfo> |
| bool TryGetProperty( |
| Var const instance, |
| RecyclableObject *const propertyObject, |
| const PropertyId propertyId, |
| Var *const propertyValue, |
| ScriptContext *const requestContext, |
| PropertyCacheOperationInfo *const operationInfo); |
| |
| template< |
| bool CheckLocal, |
| bool CheckLocalTypeWithoutProperty, |
| bool CheckAccessor, |
| bool ReturnOperationInfo> |
| bool TrySetProperty( |
| RecyclableObject *const object, |
| const PropertyId propertyId, |
| Var propertyValue, |
| ScriptContext *const requestContext, |
| PropertyCacheOperationInfo *const operationInfo, |
| const PropertyOperationFlags propertyOperationFlags = PropertyOperation_None); |
| |
| bool PretendTryGetProperty(Type *const type, PropertyCacheOperationInfo * operationInfo) const; |
| bool PretendTrySetProperty(Type *const type, Type *const oldType, PropertyCacheOperationInfo * operationInfo) const; |
| |
| void Clear(); |
| template <class TAllocator> |
| InlineCache *Clone(TAllocator *const allocator); |
| InlineCache *Clone(Js::PropertyId propertyId, ScriptContext* scriptContext); |
| void CopyTo(PropertyId propertyId, ScriptContext * scriptContext, InlineCache * const clone); |
| bool TryGetFixedMethodFromCache(Js::FunctionBody* functionBody, uint cacheId, Js::JavascriptFunction** pFixedMethod); |
| |
| bool GetGetterSetter(Type *const type, RecyclableObject **callee); |
| bool GetCallApplyTarget(RecyclableObject* obj, RecyclableObject **callee); |
| |
| static uint GetGetterFlagMask() |
| { |
| // First bit is marked for isAccessor in the accessor cache layout. |
| return InlineCacheGetterFlag << 1; |
| } |
| |
| static uint GetSetterFlagMask() |
| { |
| // First bit is marked for isAccessor in the accessor cache layout. |
| return InlineCacheSetterFlag << 1; |
| } |
| |
| static uint GetGetterSetterFlagMask() |
| { |
| // First bit is marked for isAccessor in the accessor cache layout. |
| return (InlineCacheGetterFlag | InlineCacheSetterFlag) << 1; |
| } |
| |
| bool NeedsToBeRegisteredForProtoInvalidation() const; |
| bool NeedsToBeRegisteredForStoreFieldInvalidation() const; |
| |
| #if DEBUG |
| bool ConfirmCacheMiss(const Type * oldType, const PropertyValueInfo* info) const; |
| bool NeedsToBeRegisteredForInvalidation() const; |
| static void VerifyRegistrationForInvalidation(const InlineCache* cache, ScriptContext* scriptContext, Js::PropertyId propertyId); |
| #endif |
| |
| #if DBG_DUMP |
| void Dump(); |
| #endif |
| }; |
| |
| #if defined(_M_IX86_OR_ARM32) |
| CompileAssert(sizeof(InlineCache) == 0x10); |
| #else |
| CompileAssert(sizeof(InlineCache) == 0x20); |
| #endif |
| |
| CompileAssert(sizeof(InlineCache) == sizeof(InlineCacheAllocator::CacheLayout)); |
| CompileAssert(offsetof(InlineCache, invalidationListSlotPtr) == offsetof(InlineCacheAllocator::CacheLayout, strongRef)); |
| |
| class PolymorphicInlineCache _ABSTRACT : public FinalizableObject |
| { |
| #ifdef INLINE_CACHE_STATS |
| friend class Js::ScriptContext; |
| #endif |
| |
| public: |
| static const bool IsPolymorphic = true; |
| |
| protected: |
| FieldNoBarrier(InlineCache *) inlineCaches; |
| Field(uint16) size; |
| Field(bool) ignoreForEquivalentObjTypeSpec; |
| Field(bool) cloneForJitTimeUse; |
| |
| Field(int32) inlineCachesFillInfo; |
| |
| PolymorphicInlineCache(InlineCache * inlineCaches, uint16 size) |
| : inlineCaches(inlineCaches), size(size), ignoreForEquivalentObjTypeSpec(false), cloneForJitTimeUse(true), inlineCachesFillInfo(0) |
| { |
| } |
| |
| public: |
| |
| bool CanAllocateBigger() { return GetSize() < MaxPolymorphicInlineCacheSize; } |
| static uint16 GetNextSize(uint16 currentSize) |
| { |
| if (currentSize == MaxPolymorphicInlineCacheSize) |
| { |
| return 0; |
| } |
| else if (currentSize == MinPropertyStringInlineCacheSize) |
| { |
| CompileAssert(MinPropertyStringInlineCacheSize < MinPolymorphicInlineCacheSize); |
| return MinPolymorphicInlineCacheSize; |
| } |
| else |
| { |
| Assert(currentSize >= MinPolymorphicInlineCacheSize && currentSize <= (MaxPolymorphicInlineCacheSize / 2)); |
| return currentSize * 2; |
| } |
| } |
| |
| template<bool isAccessor> |
| bool HasDifferentType(const bool isProto, const Type * type, const Type * typeWithoutProperty) const; |
| bool HasType_Flags(const Type * type) const; |
| |
| InlineCache * GetInlineCaches() const { return inlineCaches; } |
| uint16 GetSize() const { return size; } |
| bool GetIgnoreForEquivalentObjTypeSpec() const { return this->ignoreForEquivalentObjTypeSpec; } |
| void SetIgnoreForEquivalentObjTypeSpec(bool value) { this->ignoreForEquivalentObjTypeSpec = value; } |
| bool GetCloneForJitTimeUse() const { return this->cloneForJitTimeUse; } |
| void SetCloneForJitTimeUse(bool value) { this->cloneForJitTimeUse = value; } |
| uint32 GetInlineCachesFillInfo() { return this->inlineCachesFillInfo; } |
| void UpdateInlineCachesFillInfo(uint32 index, bool set); |
| bool IsFull(); |
| void Clear(Type * type); |
| |
| virtual void Dispose(bool isShutdown) override { }; |
| virtual void Mark(Recycler *recycler) override { AssertMsg(false, "Mark called on object that isn't TrackableObject"); } |
| |
| void CacheLocal( |
| Type *const type, |
| const PropertyId propertyId, |
| const PropertyIndex propertyIndex, |
| const bool isInlineSlot, |
| Type *const typeWithoutProperty, |
| int requiredAuxSlotCapacity, |
| ScriptContext *const requestContext); |
| |
| void CacheProto( |
| DynamicObject *const prototypeObjectWithProperty, |
| const PropertyId propertyId, |
| const PropertyIndex propertyIndex, |
| const bool isInlineSlot, |
| const bool isMissing, |
| Type *const type, |
| ScriptContext *const requestContext); |
| |
| void CacheAccessor( |
| const bool isGetter, |
| const PropertyId propertyId, |
| const PropertyIndex propertyIndex, |
| const bool isInlineSlot, |
| Type *const type, |
| DynamicObject *const object, |
| const bool isOnProto, |
| ScriptContext *const requestContext); |
| |
| template< |
| bool CheckLocal, |
| bool CheckProto, |
| bool CheckAccessor, |
| bool CheckMissing, |
| bool IsInlineCacheAvailable, |
| bool ReturnOperationInfo> |
| bool TryGetProperty( |
| Var const instance, |
| RecyclableObject *const propertyObject, |
| const PropertyId propertyId, |
| Var *const propertyValue, |
| ScriptContext *const requestContext, |
| PropertyCacheOperationInfo *const operationInfo, |
| InlineCache *const inlineCacheToPopulate); |
| |
| template< |
| bool CheckLocal, |
| bool CheckLocalTypeWithoutProperty, |
| bool CheckAccessor, |
| bool ReturnOperationInfo, |
| bool PopulateInlineCache> |
| bool TrySetProperty( |
| RecyclableObject *const object, |
| const PropertyId propertyId, |
| Var propertyValue, |
| ScriptContext *const requestContext, |
| PropertyCacheOperationInfo *const operationInfo, |
| InlineCache *const inlineCacheToPopulate, |
| const PropertyOperationFlags propertyOperationFlags = PropertyOperation_None); |
| |
| bool PretendTryGetProperty(Type *const type, PropertyCacheOperationInfo * operationInfo); |
| bool PretendTrySetProperty(Type *const type, Type *const oldType, PropertyCacheOperationInfo * operationInfo); |
| |
| void CopyTo(PropertyId propertyId, ScriptContext* scriptContext, PolymorphicInlineCache *const clone); |
| |
| #if DBG_DUMP |
| void Dump(); |
| #endif |
| |
| uint GetInlineCacheIndexForType(const Type * type) const |
| { |
| return (((size_t)type) >> PolymorphicInlineCacheShift) & (GetSize() - 1); |
| } |
| |
| #ifdef INLINE_CACHE_STATS |
| virtual void PrintStats(InlineCacheData *data) const = 0; |
| virtual ScriptContext* GetScriptContext() const = 0; |
| #endif |
| |
| static uint32 GetOffsetOfSize() { return offsetof(Js::PolymorphicInlineCache, size); } |
| static uint32 GetOffsetOfInlineCaches() { return offsetof(Js::PolymorphicInlineCache, inlineCaches); } |
| |
| private: |
| uint GetNextInlineCacheIndex(uint index) const |
| { |
| if (++index == GetSize()) |
| { |
| index = 0; |
| } |
| return index; |
| } |
| |
| template<bool CheckLocal, bool CheckProto, bool CheckAccessor> |
| void CloneInlineCacheToEmptySlotInCollision(Type *const type, uint index); |
| |
| #ifdef CLONE_INLINECACHE_TO_EMPTYSLOT |
| template <typename TDelegate> |
| bool CheckClonedInlineCache(uint inlineCacheIndex, TDelegate mapper); |
| #endif |
| #if INTRUSIVE_TESTTRACE_PolymorphicInlineCache |
| uint GetEntryCount() |
| { |
| uint count = 0; |
| for (uint i = 0; i < size; ++i) |
| { |
| if (!inlineCaches[i].IsEmpty()) |
| { |
| count++; |
| } |
| } |
| return count; |
| } |
| #endif |
| }; |
| |
| |
| class FunctionBodyPolymorphicInlineCache sealed : public PolymorphicInlineCache |
| { |
| private: |
| FunctionBody * functionBody; |
| |
| // DList chaining all polymorphic inline caches of a FunctionBody together. |
| // Since PolymorphicInlineCache is a leaf object, these references do not keep |
| // the polymorphic inline caches alive. When a PolymorphicInlineCache is finalized, |
| // it removes itself from the list and deletes its inline cache array. |
| // We maintain this linked list of polymorphic caches because when we allocate a larger cache, |
| // the old one might still be used by some code on the stack. Consequently, we can't release |
| // the inline cache array back to the arena allocator. |
| FunctionBodyPolymorphicInlineCache * next; |
| FunctionBodyPolymorphicInlineCache * prev; |
| |
| FunctionBodyPolymorphicInlineCache(InlineCache * inlineCaches, uint16 size, FunctionBody * functionBody) |
| : PolymorphicInlineCache(inlineCaches, size), functionBody(functionBody), next(nullptr), prev(nullptr) |
| { |
| Assert((size == 0 && inlineCaches == nullptr) || |
| (inlineCaches != nullptr && size >= MinPolymorphicInlineCacheSize && size <= MaxPolymorphicInlineCacheSize)); |
| } |
| public: |
| static FunctionBodyPolymorphicInlineCache * New(uint16 size, FunctionBody * functionBody); |
| |
| #ifdef INLINE_CACHE_STATS |
| virtual void PrintStats(InlineCacheData *data) const override; |
| virtual ScriptContext* GetScriptContext() const override; |
| #endif |
| |
| virtual void Finalize(bool isShutdown) override; |
| }; |
| |
| class ScriptContextPolymorphicInlineCache sealed : public PolymorphicInlineCache |
| { |
| private: |
| Field(JavascriptLibrary*) javascriptLibrary; |
| |
| ScriptContextPolymorphicInlineCache(InlineCache * inlineCaches, uint16 size, JavascriptLibrary * javascriptLibrary) |
| : PolymorphicInlineCache(inlineCaches, size), javascriptLibrary(javascriptLibrary) |
| { |
| Assert((size == 0 && inlineCaches == nullptr) || |
| (inlineCaches != nullptr && size >= MinPropertyStringInlineCacheSize && size <= MaxPolymorphicInlineCacheSize)); |
| } |
| |
| public: |
| static ScriptContextPolymorphicInlineCache * New(uint16 size, JavascriptLibrary * javascriptLibrary); |
| |
| #ifdef INLINE_CACHE_STATS |
| virtual void PrintStats(InlineCacheData *data) const override; |
| virtual ScriptContext* GetScriptContext() const override; |
| #endif |
| |
| virtual void Finalize(bool isShutdown) override; |
| }; |
| |
| #if ENABLE_NATIVE_CODEGEN |
| class EquivalentTypeSet |
| { |
| private: |
| Field(bool) sortedAndDuplicatesRemoved; |
| Field(uint16) count; |
| Field(RecyclerJITTypeHolder *) types; |
| |
| public: |
| EquivalentTypeSet(RecyclerJITTypeHolder * types, uint16 count); |
| |
| uint16 GetCount() const |
| { |
| return this->count; |
| } |
| |
| JITTypeHolder GetFirstType() const; |
| |
| JITTypeHolder GetType(uint16 index) const; |
| |
| bool GetSortedAndDuplicatesRemoved() const |
| { |
| return this->sortedAndDuplicatesRemoved; |
| } |
| bool Contains(const JITTypeHolder type, uint16 * pIndex = nullptr); |
| |
| static bool AreIdentical(EquivalentTypeSet * left, EquivalentTypeSet * right); |
| static bool IsSubsetOf(EquivalentTypeSet * left, EquivalentTypeSet * right); |
| void SortAndRemoveDuplicates(); |
| }; |
| #endif |
| enum class CtorCacheGuardValues : intptr_t |
| { |
| TagFlag = 0x01, |
| |
| Invalid = 0x00, |
| Special = TagFlag |
| }; |
| ENUM_CLASS_HELPERS(CtorCacheGuardValues, intptr_t); |
| |
| #define MaxCachedSlotCount 65535 |
| |
| struct ConstructorCache |
| { |
| friend class JavascriptFunction; |
| |
| struct GuardStruct |
| { |
| Field(CtorCacheGuardValues) value; |
| }; |
| |
| struct ContentStruct |
| { |
| Field(DynamicType*) type; |
| Field(ScriptContext*) scriptContext; |
| // In a pinch we could eliminate this and store type pending sharing in the type field as long |
| // as the guard value flags fit below the object alignment boundary. However, this wouldn't |
| // keep the type alive, so it would only work if we zeroed constructor caches before GC. |
| Field(DynamicType*) pendingType; |
| |
| // We cache only types whose slotCount < 64K to ensure the slotCount field doesn't look like a pointer to the recycler. |
| Field(int) slotCount; |
| |
| // This layout (i.e. one-byte bit fields first, then the one-byte updateAfterCtor, and then the two byte inlineSlotCount) is |
| // chosen intentionally to make sure the whole four bytes never look like a pointer and create a false reference pinning something |
| // in recycler heap. The isPopulated bit is always set when the cache holds any data - even if it got invalidated. |
| Field(bool) isPopulated : 1; |
| Field(bool) isPolymorphic : 1; |
| Field(bool) typeUpdatePending : 1; |
| Field(bool) ctorHasNoExplicitReturnValue : 1; |
| Field(bool) skipDefaultNewObject : 1; |
| // This field indicates that the type stored in this cache is the final type after constructor. |
| Field(bool) typeIsFinal : 1; |
| // This field indicates that the constructor cache has been invalidated due to a constructor's prototype property change. |
| // We use this flag to determine if we should mark the cache as polymorphic and not attempt subsequent optimizations. |
| // The cache may also be invalidated due to a guard invalidation resulting from some property change (e.g. in proto chain), |
| // in which case we won't deem the cache polymorphic. |
| Field(bool) hasPrototypeChanged : 1; |
| |
| Field(uint8) callCount; |
| |
| // Separate from the bit field below for convenient compare from the JIT-ed code. Doesn't currently increase the size. |
| // If size becomes an issue, we could merge back into the bit field and use a TEST instead of CMP. |
| Field(bool) updateAfterCtor; |
| |
| Field(int16) inlineSlotCount; |
| }; |
| |
| union |
| { |
| Field(GuardStruct) guard; |
| Field(ContentStruct) content; |
| }; |
| |
| CompileAssert(offsetof(GuardStruct, value) == offsetof(ContentStruct, type)); |
| CompileAssert(sizeof(((GuardStruct*)nullptr)->value) == sizeof(((ContentStruct*)nullptr)->type)); |
| CompileAssert(static_cast<intptr_t>(CtorCacheGuardValues::Invalid) == static_cast<intptr_t>(NULL)); |
| |
| static ConstructorCache DefaultInstance; |
| |
| public: |
| ConstructorCache() |
| { |
| this->content.type = nullptr; |
| this->content.scriptContext = nullptr; |
| this->content.slotCount = 0; |
| this->content.inlineSlotCount = 0; |
| this->content.updateAfterCtor = false; |
| this->content.ctorHasNoExplicitReturnValue = false; |
| this->content.skipDefaultNewObject = false; |
| this->content.isPopulated = false; |
| this->content.isPolymorphic = false; |
| this->content.typeUpdatePending = false; |
| this->content.typeIsFinal = false; |
| this->content.hasPrototypeChanged = false; |
| this->content.callCount = 0; |
| Assert(IsConsistent()); |
| } |
| |
| ConstructorCache(ConstructorCache const * other) |
| { |
| Assert(other != nullptr); |
| this->content.type = other->content.type; |
| this->content.scriptContext = other->content.scriptContext; |
| this->content.slotCount = other->content.slotCount; |
| this->content.inlineSlotCount = other->content.inlineSlotCount; |
| this->content.updateAfterCtor = other->content.updateAfterCtor; |
| this->content.ctorHasNoExplicitReturnValue = other->content.ctorHasNoExplicitReturnValue; |
| this->content.skipDefaultNewObject = other->content.skipDefaultNewObject; |
| this->content.isPopulated = other->content.isPopulated; |
| this->content.isPolymorphic = other->content.isPolymorphic; |
| this->content.typeUpdatePending = other->content.typeUpdatePending; |
| this->content.typeIsFinal = other->content.typeIsFinal; |
| this->content.hasPrototypeChanged = other->content.hasPrototypeChanged; |
| this->content.callCount = other->content.callCount; |
| Assert(IsConsistent()); |
| } |
| |
| static size_t const GetOffsetOfGuardValue() { return offsetof(Js::ConstructorCache, guard.value); } |
| static size_t const GetSizeOfGuardValue() { return sizeof(((Js::ConstructorCache*)nullptr)->guard.value); } |
| |
| void Populate(DynamicType* type, ScriptContext* scriptContext, bool ctorHasNoExplicitReturnValue, bool updateAfterCtor) |
| { |
| Assert(scriptContext == type->GetScriptContext()); |
| Assert(type->GetIsShared()); |
| Assert(IsConsistent()); |
| Assert(!this->content.isPopulated || this->content.isPolymorphic); |
| Assert(type->GetTypeHandler()->GetSlotCapacity() <= MaxCachedSlotCount); |
| this->content.isPopulated = true; |
| this->content.type = type; |
| this->content.scriptContext = scriptContext; |
| this->content.slotCount = type->GetTypeHandler()->GetSlotCapacity(); |
| this->content.inlineSlotCount = type->GetTypeHandler()->GetInlineSlotCapacity(); |
| this->content.ctorHasNoExplicitReturnValue = ctorHasNoExplicitReturnValue; |
| this->content.updateAfterCtor = updateAfterCtor; |
| Assert(IsConsistent()); |
| } |
| |
| void PopulateForSkipDefaultNewObject(ScriptContext* scriptContext) |
| { |
| Assert(IsConsistent()); |
| Assert(!this->content.isPopulated); |
| this->content.isPopulated = true; |
| this->guard.value = CtorCacheGuardValues::Special; |
| this->content.scriptContext = scriptContext; |
| this->content.skipDefaultNewObject = true; |
| Assert(IsConsistent()); |
| } |
| |
| bool TryUpdateAfterConstructor(DynamicType* type, ScriptContext* scriptContext) |
| { |
| Assert(scriptContext == type->GetScriptContext()); |
| Assert(type->GetTypeHandler()->GetMayBecomeShared()); |
| Assert(IsConsistent()); |
| Assert(this->content.isPopulated); |
| Assert(this->content.scriptContext == scriptContext); |
| Assert(!this->content.typeUpdatePending); |
| Assert(this->content.ctorHasNoExplicitReturnValue); |
| |
| if (type->GetTypeHandler()->GetSlotCapacity() > MaxCachedSlotCount) |
| { |
| return false; |
| } |
| |
| if (type->GetIsShared()) |
| { |
| this->content.type = type; |
| this->content.typeIsFinal = true; |
| this->content.pendingType = nullptr; |
| } |
| else |
| { |
| AssertMsg(false, "No one calls this part of the code?"); |
| this->guard.value = CtorCacheGuardValues::Special; |
| this->content.pendingType = type; |
| this->content.typeUpdatePending = true; |
| } |
| this->content.slotCount = type->GetTypeHandler()->GetSlotCapacity(); |
| this->content.inlineSlotCount = type->GetTypeHandler()->GetInlineSlotCapacity(); |
| Assert(IsConsistent()); |
| return true; |
| } |
| |
| void UpdateInlineSlotCount() |
| { |
| Assert(IsConsistent()); |
| Assert(this->content.isPopulated); |
| Assert(IsEnabled() || NeedsTypeUpdate()); |
| DynamicType* type = this->content.typeUpdatePending ? this->content.pendingType : this->content.type; |
| DynamicTypeHandler* typeHandler = type->GetTypeHandler(); |
| // Inline slot capacity should never grow as a result of shrinking. |
| Assert(typeHandler->GetInlineSlotCapacity() <= this->content.inlineSlotCount); |
| // Slot capacity should never grow as a result of shrinking. |
| Assert(typeHandler->GetSlotCapacity() <= this->content.slotCount); |
| this->content.slotCount = typeHandler->GetSlotCapacity(); |
| this->content.inlineSlotCount = typeHandler->GetInlineSlotCapacity(); |
| Assert(IsConsistent()); |
| } |
| |
| void EnableAfterTypeUpdate() |
| { |
| Assert(IsConsistent()); |
| Assert(this->content.isPopulated); |
| Assert(!IsEnabled()); |
| Assert(this->guard.value == CtorCacheGuardValues::Special); |
| Assert(this->content.typeUpdatePending); |
| Assert(this->content.slotCount == this->content.pendingType->GetTypeHandler()->GetSlotCapacity()); |
| Assert(this->content.inlineSlotCount == this->content.pendingType->GetTypeHandler()->GetInlineSlotCapacity()); |
| Assert(this->content.pendingType->GetIsShared()); |
| this->content.type = this->content.pendingType; |
| this->content.typeIsFinal = true; |
| this->content.pendingType = nullptr; |
| this->content.typeUpdatePending = false; |
| Assert(IsConsistent()); |
| } |
| |
| intptr_t GetRawGuardValue() const |
| { |
| return static_cast<intptr_t>(this->guard.value); |
| } |
| |
| DynamicType* GetGuardValueAsType() const |
| { |
| return reinterpret_cast<DynamicType*>(this->guard.value & ~CtorCacheGuardValues::TagFlag); |
| } |
| |
| DynamicType* GetType() const |
| { |
| Assert(static_cast<intptr_t>(this->guard.value & CtorCacheGuardValues::TagFlag) == 0); |
| return this->content.type; |
| } |
| |
| DynamicType* GetPendingType() const |
| { |
| return this->content.pendingType; |
| } |
| |
| ScriptContext* GetScriptContext() const |
| { |
| return this->content.scriptContext; |
| } |
| |
| int GetSlotCount() const |
| { |
| return this->content.slotCount; |
| } |
| |
| int16 GetInlineSlotCount() const |
| { |
| return this->content.inlineSlotCount; |
| } |
| |
| static bool IsDefault(const ConstructorCache* constructorCache) |
| { |
| return constructorCache == &ConstructorCache::DefaultInstance; |
| } |
| |
| bool IsDefault() const |
| { |
| return IsDefault(this); |
| } |
| |
| bool IsPopulated() const |
| { |
| Assert(IsConsistent()); |
| return this->content.isPopulated; |
| } |
| |
| bool IsEmpty() const |
| { |
| Assert(IsConsistent()); |
| return !this->content.isPopulated; |
| } |
| |
| bool IsPolymorphic() const |
| { |
| Assert(IsConsistent()); |
| return this->content.isPolymorphic; |
| } |
| |
| bool GetSkipDefaultNewObject() const |
| { |
| return this->content.skipDefaultNewObject; |
| } |
| |
| bool GetCtorHasNoExplicitReturnValue() const |
| { |
| return this->content.ctorHasNoExplicitReturnValue; |
| } |
| |
| bool GetUpdateCacheAfterCtor() const |
| { |
| return this->content.updateAfterCtor; |
| } |
| |
| bool GetTypeUpdatePending() const |
| { |
| return this->content.typeUpdatePending; |
| } |
| |
| bool IsEnabled() const |
| { |
| return GetGuardValueAsType() != nullptr; |
| } |
| |
| bool IsInvalidated() const |
| { |
| return this->guard.value == CtorCacheGuardValues::Invalid && this->content.isPopulated; |
| } |
| |
| bool NeedsTypeUpdate() const |
| { |
| return this->guard.value == CtorCacheGuardValues::Special && this->content.typeUpdatePending; |
| } |
| |
| uint8 CallCount() const |
| { |
| return content.callCount; |
| } |
| |
| void IncCallCount() |
| { |
| ++content.callCount; |
| Assert(content.callCount != 0); |
| } |
| |
| bool NeedsUpdateAfterCtor() const |
| { |
| return this->content.updateAfterCtor; |
| } |
| |
| bool IsNormal() const |
| { |
| return this->guard.value != CtorCacheGuardValues::Invalid && static_cast<intptr_t>(this->guard.value & CtorCacheGuardValues::TagFlag) == 0; |
| } |
| |
| bool SkipDefaultNewObject() const |
| { |
| return this->guard.value == CtorCacheGuardValues::Special && this->content.skipDefaultNewObject; |
| } |
| |
| bool IsSetUpForJit() const |
| { |
| return GetRawGuardValue() != NULL && !IsPolymorphic() && !NeedsUpdateAfterCtor() && (IsNormal() || SkipDefaultNewObject()); |
| } |
| |
| void ClearUpdateAfterCtor() |
| { |
| Assert(IsConsistent()); |
| Assert(this->content.isPopulated); |
| Assert(this->content.updateAfterCtor); |
| this->content.updateAfterCtor = false; |
| Assert(IsConsistent()); |
| } |
| |
| static ConstructorCache* EnsureValidInstance(ConstructorCache* currentCache, ScriptContext* scriptContext); |
| |
| const void* GetAddressOfGuardValue() const |
| { |
| return reinterpret_cast<const void*>(&this->guard.value); |
| } |
| |
| static uint32 GetOffsetOfUpdateAfterCtor() |
| { |
| return offsetof(ConstructorCache, content.updateAfterCtor); |
| } |
| |
| void InvalidateAsGuard() |
| { |
| Assert(!IsDefault(this)); |
| this->guard.value = CtorCacheGuardValues::Invalid; |
| // Make sure we don't leak the types. |
| Assert(this->content.type == nullptr); |
| Assert(this->content.pendingType == nullptr); |
| Assert(IsInvalidated()); |
| Assert(IsConsistent()); |
| } |
| |
| #if DBG |
| bool IsConsistent() const |
| { |
| return this->guard.value == CtorCacheGuardValues::Invalid || |
| (this->content.isPopulated && ( |
| (this->guard.value == CtorCacheGuardValues::Special && !this->content.updateAfterCtor && this->content.skipDefaultNewObject && !this->content.typeUpdatePending && this->content.slotCount == 0 && this->content.inlineSlotCount == 0 && this->content.pendingType == nullptr) || |
| (this->guard.value == CtorCacheGuardValues::Special && !this->content.updateAfterCtor && this->content.typeUpdatePending && !this->content.skipDefaultNewObject && this->content.pendingType != nullptr) || |
| ((this->guard.value & CtorCacheGuardValues::TagFlag) == CtorCacheGuardValues::Invalid && !this->content.skipDefaultNewObject && !this->content.typeUpdatePending && this->content.pendingType == nullptr))); |
| } |
| #endif |
| |
| #if DBG_DUMP |
| void Dump() const; |
| #endif |
| |
| private: |
| void InvalidateOnPrototypeChange(); |
| }; |
| |
| // Caches the result of an instanceof operator over a type and a function |
| struct IsInstInlineCache |
| { |
| Type * type; // The type of object operand an inline cache caches a result for |
| JavascriptFunction * function; // The function operand an inline cache caches a result for |
| JavascriptBoolean * result; // The result of doing (object instanceof function) where object->type == this->type |
| IsInstInlineCache * next; // Used to link together caches that have the same function operand |
| |
| public: |
| bool IsEmpty() const { return type == nullptr; } |
| bool TryGetResult(Var instance, JavascriptFunction * function, JavascriptBoolean ** result); |
| void Cache(Type * instanceType, JavascriptFunction * function, JavascriptBoolean * result, ScriptContext * scriptContext); |
| void Unregister(ScriptContext * scriptContext); |
| void Clear(); |
| |
| static uint32 OffsetOfFunction(); |
| static uint32 OffsetOfResult(); |
| static uint32 OffsetOfType(); |
| |
| private: |
| void Set(Type * instanceType, JavascriptFunction * function, JavascriptBoolean * result); |
| }; |
| |
| // Two-entry Type-indexed circular cache |
| // cache IsConcatSpreadable() result unless user-defined [@@isConcatSpreadable] exists |
| class IsConcatSpreadableCache |
| { |
| Type *type0, *type1; |
| int lastAccess; |
| BOOL result0, result1; |
| |
| public: |
| IsConcatSpreadableCache() : |
| type0(nullptr), |
| type1(nullptr), |
| result0(FALSE), |
| result1(FALSE), |
| lastAccess(1) |
| { |
| } |
| |
| bool TryGetIsConcatSpreadable(Type *type, _Out_ BOOL *result) |
| { |
| Assert(type != nullptr); |
| Assert(result != nullptr); |
| |
| *result = FALSE; |
| if (type0 == type) |
| { |
| *result = result0; |
| lastAccess = 0; |
| return true; |
| } |
| |
| if (type1 == type) |
| { |
| *result = result1; |
| lastAccess = 1; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void CacheIsConcatSpreadable(Type *type, BOOL result) |
| { |
| Assert(type != nullptr); |
| |
| if (lastAccess == 0) |
| { |
| type1 = type; |
| result1 = result; |
| lastAccess = 1; |
| } |
| else |
| { |
| type0 = type; |
| result0 = result; |
| lastAccess = 0; |
| } |
| } |
| |
| void Invalidate() |
| { |
| type0 = nullptr; |
| type1 = nullptr; |
| } |
| }; |
| |
| #if defined(_M_IX86_OR_ARM32) |
| CompileAssert(sizeof(IsInstInlineCache) == 0x10); |
| #else |
| CompileAssert(sizeof(IsInstInlineCache) == 0x20); |
| #endif |
| } |