//-------------------------------------------------------------------------------------------------------
// 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;
            }

            Assert(*this->invalidationListSlotPtr == this);

            *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();
        void RemoveFromInvalidationListAndClear(ThreadContext* threadContext);
        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
}
