// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#include "common.h"
#include "threadstatics.h"

#ifndef DACCESS_COMPILE
InFlightTLSData::InFlightTLSData(TLSIndex index) : pNext(NULL), tlsIndex(index), hTLSData(0) { }
InFlightTLSData::~InFlightTLSData()
    {
        if (!IsHandleNullUnchecked(hTLSData))
        {
            DestroyTypedHandle(hTLSData);
        }
    }
#endif


struct ThreadLocalLoaderAllocator
{
    ThreadLocalLoaderAllocator* pNext; // Points at the next thread local loader allocator
    LoaderAllocator* pLoaderAllocator; // The loader allocator that has a TLS used in this thread
};
typedef DPTR(ThreadLocalLoaderAllocator) PTR_ThreadLocalLoaderAllocator;

#ifndef DACCESS_COMPILE
static TLSIndexToMethodTableMap *g_pThreadStaticCollectibleTypeIndices;
static TLSIndexToMethodTableMap *g_pThreadStaticNonCollectibleTypeIndices;
static PTR_MethodTable g_pMethodTablesForDirectThreadLocalData[offsetof(ThreadLocalData, ExtendedDirectThreadLocalTLSData) - offsetof(ThreadLocalData, pThread) + EXTENDED_DIRECT_THREAD_LOCAL_SIZE];

static uint32_t g_NextTLSSlot = 1;
static uint32_t g_NextNonCollectibleTlsSlot = NUMBER_OF_TLSOFFSETS_NOT_USED_IN_NONCOLLECTIBLE_ARRAY;
static uint32_t g_directThreadLocalTLSBytesAvailable = EXTENDED_DIRECT_THREAD_LOCAL_SIZE;

static CrstStatic g_TLSCrst;
#endif

// This can be used for out of thread access to TLS data.
PTR_VOID GetThreadLocalStaticBaseNoCreate(Thread* pThread, TLSIndex index)
{
    CONTRACTL
    {
        NOTHROW;
        GC_NOTRIGGER;
        MODE_ANY;
    }
    CONTRACTL_END;

    TADDR pTLSBaseAddress = (TADDR)NULL;

    // The only lock we take here is this spin lock, which is safe to take even when the GC is running.
    // The only time it isn't safe is if target thread is suspended by an OS primitive without
    // going through the suspend thread routine of the runtime itself.
    BEGIN_CONTRACT_VIOLATION(TakesLockViolation);
#ifndef DACCESS_COMPILE
    // Since this api can be used from a different thread, we need a lock to keep it all safe
    SpinLockHolder spinLock(&pThread->m_TlsSpinLock);
#endif

    PTR_ThreadLocalData pThreadLocalData = pThread->GetThreadLocalDataPtr();
    if (pThreadLocalData != NULL)
    {
        if (index.GetTLSIndexType() == TLSIndexType::NonCollectible)
        {
            PTR_ArrayBase tlsArray = (PTR_ArrayBase)pThreadLocalData->pNonCollectibleTlsArrayData;
            if (pThreadLocalData->cNonCollectibleTlsData > index.GetIndexOffset())
            {
                size_t arrayIndex = index.GetIndexOffset() - NUMBER_OF_TLSOFFSETS_NOT_USED_IN_NONCOLLECTIBLE_ARRAY;
                TADDR arrayTargetAddress = dac_cast<TADDR>(tlsArray) + offsetof(PtrArray, m_Array);
#ifdef DACCESS_COMPILE
                __ArrayDPtr<_UNCHECKED_OBJECTREF> targetArray = dac_cast< __ArrayDPtr<_UNCHECKED_OBJECTREF> >(arrayTargetAddress);
#else
                _UNCHECKED_OBJECTREF* targetArray = reinterpret_cast<_UNCHECKED_OBJECTREF*>(arrayTargetAddress);
#endif
                pTLSBaseAddress = dac_cast<TADDR>(targetArray[arrayIndex]);
            }
        }
        else if (index.GetTLSIndexType() == TLSIndexType::DirectOnThreadLocalData)
        {
            pTLSBaseAddress = dac_cast<TADDR>((dac_cast<TADDR>(pThreadLocalData)) + index.GetIndexOffset());
        }
        else
        {
            int32_t cCollectibleTlsData = pThreadLocalData->cCollectibleTlsData;
            if (cCollectibleTlsData > index.GetIndexOffset())
            {
                TADDR pCollectibleTlsArrayData = dac_cast<TADDR>(pThreadLocalData->pCollectibleTlsArrayData);
                pCollectibleTlsArrayData += index.GetIndexOffset() * sizeof(TADDR);
                OBJECTHANDLE objHandle = *dac_cast<DPTR(OBJECTHANDLE)>(pCollectibleTlsArrayData);
                if (!IsHandleNullUnchecked(objHandle))
                {
                    pTLSBaseAddress = dac_cast<TADDR>(ObjectFromHandleUnchecked(objHandle));
                }
            }
        }
        if (pTLSBaseAddress == (TADDR)NULL)
        {
            // Maybe it is in the InFlightData
            PTR_InFlightTLSData pInFlightData = pThreadLocalData->pInFlightData;
            while (pInFlightData != NULL)
            {
                if (pInFlightData->tlsIndex == index)
                {
                    pTLSBaseAddress = dac_cast<TADDR>(ObjectFromHandleUnchecked(pInFlightData->hTLSData));
                    break;
                }
                pInFlightData = pInFlightData->pNext;
            }
        }
    }

    END_CONTRACT_VIOLATION;

    return dac_cast<PTR_VOID>(pTLSBaseAddress);
}

#ifndef DACCESS_COMPILE
int32_t IndexOffsetToDirectThreadLocalIndex(int32_t indexOffset)
{
    LIMITED_METHOD_CONTRACT;

    int32_t adjustedIndexOffset = indexOffset + OFFSETOF__CORINFO_Array__data;
    _ASSERTE(((uint32_t)adjustedIndexOffset) >= offsetof(ThreadLocalData, pThread));
    int32_t directThreadLocalIndex = adjustedIndexOffset - offsetof(ThreadLocalData, pThread);
    _ASSERTE(((uint32_t)directThreadLocalIndex) < (sizeof(g_pMethodTablesForDirectThreadLocalData) / sizeof(g_pMethodTablesForDirectThreadLocalData[0])));
    _ASSERTE(directThreadLocalIndex >= 0);
    return directThreadLocalIndex;
}
#endif // DACCESS_COMPILE

#ifndef DACCESS_COMPILE
PTR_MethodTable LookupMethodTableForThreadStaticKnownToBeAllocated(TLSIndex index)
{
    CONTRACTL
    {
        NOTHROW;
        GC_NOTRIGGER;
        MODE_PREEMPTIVE;
    }
    CONTRACTL_END;

    if (index.GetTLSIndexType() == TLSIndexType::NonCollectible)
    {
        return g_pThreadStaticNonCollectibleTypeIndices->LookupTlsIndexKnownToBeAllocated(index);
    }
    else if (index.GetTLSIndexType() == TLSIndexType::DirectOnThreadLocalData)
    {
        return VolatileLoadWithoutBarrier(&g_pMethodTablesForDirectThreadLocalData[IndexOffsetToDirectThreadLocalIndex(index.GetIndexOffset())]);
    }
    else
    {
        return g_pThreadStaticCollectibleTypeIndices->LookupTlsIndexKnownToBeAllocated(index);
    }
}
#endif // DACCESS_COMPILE

#ifndef DACCESS_COMPILE
PTR_MethodTable LookupMethodTableAndFlagForThreadStatic(TLSIndex index, bool *pIsGCStatic, bool *pIsCollectible)
{
    CONTRACTL
    {
        NOTHROW;
        GC_NOTRIGGER;
        MODE_COOPERATIVE;
    }
    CONTRACTL_END;

    // This method is called without the g_TLSCrst lock being held
    PTR_MethodTable retVal;
    if (index.GetTLSIndexType() == TLSIndexType::NonCollectible)
    {
        retVal = g_pThreadStaticNonCollectibleTypeIndices->Lookup(index, pIsGCStatic, pIsCollectible);
    }
    else if (index.GetTLSIndexType() == TLSIndexType::DirectOnThreadLocalData)
    {
        *pIsGCStatic = false;
        *pIsCollectible = false;
        retVal = VolatileLoadWithoutBarrier(&g_pMethodTablesForDirectThreadLocalData[IndexOffsetToDirectThreadLocalIndex(index.GetIndexOffset())]);
    }
    else
    {
        retVal = g_pThreadStaticCollectibleTypeIndices->Lookup(index, pIsGCStatic, pIsCollectible);
    }
    return retVal;
}
#endif // DACCESS_COMPILE

#ifndef DACCESS_COMPILE
void ScanThreadStaticRoots(Thread* pThread, promote_func* fn, ScanContext* sc)
{
    CONTRACTL
    {
        NOTHROW;
        GC_NOTRIGGER;
        MODE_COOPERATIVE;
    }
    CONTRACTL_END;

    if (pThread->m_ThreadLocalDataPtr == NULL)
        return;

    ThreadLocalData *pThreadLocalData = pThread->m_ThreadLocalDataPtr;

    // Report non-collectible object array
    fn(&pThreadLocalData->pNonCollectibleTlsArrayData, sc, 0);
}
#endif // DACCESS_COMPILE

#ifndef DACCESS_COMPILE

void TLSIndexToMethodTableMap::Set(TLSIndex index, PTR_MethodTable pMT, bool isGCStatic)
{
    CONTRACTL
    {
        THROWS;
        GC_NOTRIGGER;
        MODE_COOPERATIVE;
    }
    CONTRACTL_END;

    if (index.GetIndexOffset() >= m_maxIndex)
    {
        int32_t newSize = max(m_maxIndex, 16);
        while (index.GetIndexOffset() >= newSize)
        {
            newSize *= 2;
        }
        TADDR *newMap = new TADDR[newSize];
        memset(newMap, 0, sizeof(TADDR) * newSize);
        if (pMap != NULL)
        {
            memcpy(newMap, pMap, m_maxIndex * sizeof(TADDR));
            // Don't delete the old map in case some other thread is reading from it, this won't waste significant amounts of memory, since this map cannot grow indefinitely
        }
        VolatileStore(&pMap, newMap); // Use a volatile store, so that the memcpy is guaranteed to be visible before the new pMap is visible on other threads.  This is paired with VolatileLoad in the Lookup functions although it probably could be a VolatileLoadWithoutBarrier and take advantage of data dependency.
        VolatileStore(&m_maxIndex, newSize); // Use a volatile store so that any thread that reads the size will also have a consistent view of a map at least as big as newSize. This is paired with VolatileLoad in the Lookup functions.
    }

    TADDR rawValue = dac_cast<TADDR>(pMT);
    if (isGCStatic)
    {
        rawValue |= IsGCFlag();
    }
    if (pMT->Collectible())
    {
        rawValue |= IsCollectibleFlag();
        m_collectibleEntries++;
    }
    _ASSERTE(pMap[index.GetIndexOffset()] == 0 || IsClearedValue(pMap[index.GetIndexOffset()]));
    // This VolatileStore does not have a clear consumer-producer relationship. Notably, the MethodTable is always considered to have been
    // fully published by the time its pointer is stored in the map, so there is no need for a memory barrier to ensure visibility of the MethodTable's contents.
    // However, we have had issues with race safety in this codebase, and we fixed them by adding several VolatileStore which we believe are actually critical
    // but this is a scenario which AI indicated should also have a VolatileStore. We've chosen to add the VolatileStore as a defensive measure, as this is very
    // rarely run code.
    VolatileStore(&pMap[index.GetIndexOffset()], rawValue); 
}

void TLSIndexToMethodTableMap::Clear(TLSIndex index, uint8_t whenCleared)
{
    CONTRACTL
    {
        NOTHROW;
        GC_NOTRIGGER;
        MODE_COOPERATIVE;
    }
    CONTRACTL_END;

    _ASSERTE(index.GetIndexOffset() < m_maxIndex);
    TADDR rawValue = pMap[index.GetIndexOffset()];
    _ASSERTE(rawValue & IsCollectibleFlag());
    if (rawValue & IsCollectibleFlag())
    {
        m_collectibleEntries--;
    }
    VolatileStore(&pMap[index.GetIndexOffset()], (TADDR)((whenCleared << 2) | 0x3));
    _ASSERTE(GetClearedMarker(pMap[index.GetIndexOffset()]) == whenCleared);
    _ASSERTE(IsClearedValue(pMap[index.GetIndexOffset()]));
}

bool TLSIndexToMethodTableMap::FindClearedIndex(TLSIndex* pIndex)
{
    CONTRACTL
    {
        NOTHROW;
        GC_NOTRIGGER;
        MODE_COOPERATIVE;
    }
    CONTRACTL_END;

    for (const auto& entry : *this)
    {
        if (entry.IsClearedValue)
        {
            *pIndex = entry.TlsIndex;
            return true;
        }
    }
    return false;
}

void InitializeThreadStaticData()
{
    CONTRACTL
    {
        THROWS;
        GC_TRIGGERS;
        MODE_PREEMPTIVE;
    }
    CONTRACTL_END;

    g_pThreadStaticCollectibleTypeIndices = new TLSIndexToMethodTableMap(TLSIndexType::Collectible);
    g_pThreadStaticNonCollectibleTypeIndices = new TLSIndexToMethodTableMap(TLSIndexType::NonCollectible);
    CoreLibBinder::GetClass(CLASS__DIRECTONTHREADLOCALDATA);
    CoreLibBinder::GetClass(CLASS__THREAD_BLOCKING_INFO);
    g_TLSCrst.Init(CrstThreadLocalStorageLock, CRST_UNSAFE_ANYMODE);
}

void InitializeCurrentThreadsStaticData(Thread* pThread)
{
    LIMITED_METHOD_CONTRACT;

    t_ThreadStatics.pThread = pThread;
    t_ThreadStatics.pThread->m_ThreadLocalDataPtr = &t_ThreadStatics;
    t_ThreadStatics.pThread->m_TlsSpinLock.Init(LOCK_TLSDATA, FALSE);
}

void AllocateThreadStaticBoxes(MethodTable *pMT, PTRARRAYREF *ppRef)
{
    CONTRACTL
    {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
        PRECONDITION(pMT->HasBoxedThreadStatics());
        INJECT_FAULT(COMPlusThrowOM(););
    }
    CONTRACTL_END;

    FieldDesc *pField = pMT->HasGenericsStaticsInfo() ?
        pMT->GetGenericsStaticFieldDescs() : (pMT->GetApproxFieldDescListRaw() + pMT->GetNumIntroducedInstanceFields());

    // Move pField to point to the list of thread statics
    pField += pMT->GetNumStaticFields() - pMT->GetNumThreadStaticFields();

    FieldDesc *pFieldEnd = pField + pMT->GetNumThreadStaticFields();

    while (pField < pFieldEnd)
    {
        _ASSERTE(pField->IsThreadStatic());

        // We only care about thread statics which are value types
        if (pField->IsByValue())
        {
            TypeHandle  th = pField->GetFieldTypeHandleThrowing();
            MethodTable* pFieldMT = th.GetMethodTable();

            OBJECTREF obj = MethodTable::AllocateStaticBox(pFieldMT, pMT->HasFixedAddressVTStatics());
            uint8_t *pBase = (uint8_t*)OBJECTREFToObject(*ppRef);
            SetObjectReference((OBJECTREF*)(pBase + pField->GetOffset()), obj);
        }

        pField++;
    }
}

void FreeLoaderAllocatorHandlesForTLSData(Thread *pThread)
{
    CONTRACTL {
        NOTHROW;
        GC_NOTRIGGER;
        MODE_COOPERATIVE;
    }
    CONTRACTL_END;

    if (pThread->cLoaderHandles > 0)
    {
        CrstHolder ch(&g_TLSCrst);
#ifdef _DEBUG
        bool allRemainingIndicesAreNotValid = false;
#endif
        for (const auto& entry : g_pThreadStaticCollectibleTypeIndices->CollectibleEntries())
        {
            _ASSERTE((entry.TlsIndex.GetIndexOffset() >= pThread->cLoaderHandles) || !allRemainingIndicesAreNotValid);
            if (entry.TlsIndex.GetIndexOffset() >= pThread->cLoaderHandles)
            {
#ifndef _DEBUG
                break;
#else
                allRemainingIndicesAreNotValid = true;
#endif
            }
            else
            {
                if (pThread->pLoaderHandles[entry.TlsIndex.GetIndexOffset()] != (LOADERHANDLE)NULL)
                {
                    LoaderAllocator *pLoaderAllocator = entry.pMT->GetLoaderAllocator();
                    if (pLoaderAllocator->IsExposedObjectLive())
                        pLoaderAllocator->FreeHandle(pThread->pLoaderHandles[entry.TlsIndex.GetIndexOffset()]);
                    pThread->pLoaderHandles[entry.TlsIndex.GetIndexOffset()] = (LOADERHANDLE)NULL;
                }
            }
        }

        pThread->cLoaderHandles = -1; // Sentinel value indicating that there are no LoaderHandles and the thread is permanently dead.
        if (pThread->pLoaderHandles != NULL)
        {
            delete[] pThread->pLoaderHandles;
            pThread->pLoaderHandles = NULL;
        }
    }
}

void AssertThreadStaticDataFreed()
{
    LIMITED_METHOD_CONTRACT;

    ThreadLocalData *pThreadLocalData = &t_ThreadStatics;

    _ASSERTE(pThreadLocalData->pThread == NULL);
    _ASSERTE(pThreadLocalData->pCollectibleTlsArrayData == NULL);
    _ASSERTE(pThreadLocalData->cCollectibleTlsData == 0);
    _ASSERTE(pThreadLocalData->pNonCollectibleTlsArrayData == NULL);
    _ASSERTE(pThreadLocalData->cNonCollectibleTlsData == 0);
    _ASSERTE(pThreadLocalData->pInFlightData == NULL);
}

void FreeThreadStaticData(Thread* pThread)
{
    CONTRACTL {
        NOTHROW;
        GC_NOTRIGGER;
        MODE_COOPERATIVE;
    }
    CONTRACTL_END;

    InFlightTLSData* pOldInFlightData = nullptr;

    int32_t oldCollectibleTlsDataCount = 0;
    DPTR(OBJECTHANDLE) pOldCollectibleTlsArrayData = nullptr;

    {
        SpinLockHolder spinLock(&pThread->m_TlsSpinLock);

        ThreadLocalData *pThreadLocalData = &t_ThreadStatics;

        pOldCollectibleTlsArrayData = pThreadLocalData->pCollectibleTlsArrayData;
        oldCollectibleTlsDataCount = pThreadLocalData->cCollectibleTlsData;

        pThreadLocalData->pCollectibleTlsArrayData = NULL;
        pThreadLocalData->cCollectibleTlsData = 0;
        pThreadLocalData->pNonCollectibleTlsArrayData = NULL;
        pThreadLocalData->cNonCollectibleTlsData = 0;

        pOldInFlightData = pThreadLocalData->pInFlightData;
        pThreadLocalData->pInFlightData = NULL;
        _ASSERTE(pThreadLocalData->pThread == pThread);
        pThreadLocalData->pThread = NULL;
    }

    for (int32_t iTlsSlot = 0; iTlsSlot < oldCollectibleTlsDataCount; ++iTlsSlot)
    {
        if (!IsHandleNullUnchecked(pOldCollectibleTlsArrayData[iTlsSlot]))
        {
            DestroyLongWeakHandle(pOldCollectibleTlsArrayData[iTlsSlot]);
        }
    }

    delete[] (uint8_t*)pOldCollectibleTlsArrayData;

    while (pOldInFlightData != NULL)
    {
        InFlightTLSData* pInFlightData = pOldInFlightData;
        pOldInFlightData = pInFlightData->pNext;
        delete pInFlightData;
    }
}

void SetTLSBaseValue(TADDR *ppTLSBaseAddress, TADDR pTLSBaseAddress, bool useGCBarrierInsteadOfHandleStore)
{
    CONTRACTL
    {
        NOTHROW;
        GC_NOTRIGGER;
        MODE_COOPERATIVE;
    }
    CONTRACTL_END;

    if (useGCBarrierInsteadOfHandleStore)
    {
        SetObjectReference((OBJECTREF *)ppTLSBaseAddress, (OBJECTREF)ObjectToOBJECTREF((Object*)pTLSBaseAddress));
    }
    else
    {
        OBJECTHANDLE objHandle = (OBJECTHANDLE)ppTLSBaseAddress;
        StoreObjectInHandle(objHandle, (OBJECTREF)ObjectToOBJECTREF((Object*)pTLSBaseAddress));
    }
}

void* GetThreadLocalStaticBase(TLSIndex index)
{
    CONTRACTL
    {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    }
    CONTRACTL_END;

    bool isGCStatic;
    bool isCollectible;
    bool staticIsNonCollectible = false;
    MethodTable *pMT = LookupMethodTableAndFlagForThreadStatic(index, &isGCStatic, &isCollectible);

    struct
    {
        TADDR *ppTLSBaseAddress = NULL;
        TADDR pTLSBaseAddress = (TADDR)NULL;
    } gcBaseAddresses;
    GCPROTECT_BEGININTERIOR(gcBaseAddresses);

    if (index.GetTLSIndexType() == TLSIndexType::NonCollectible)
    {
        PTRARRAYREF tlsArray = (PTRARRAYREF)UNCHECKED_OBJECTREF_TO_OBJECTREF(t_ThreadStatics.pNonCollectibleTlsArrayData);
        if (t_ThreadStatics.cNonCollectibleTlsData <= index.GetIndexOffset())
        {
            GCPROTECT_BEGIN(tlsArray);
            PTRARRAYREF tlsArrayNew = (PTRARRAYREF)AllocateObjectArray(index.GetIndexOffset() + 8, g_pObjectClass);
            if (tlsArray != NULL)
            {
                for (DWORD i = 0; i < tlsArray->GetNumComponents(); i++)
                {
                    tlsArrayNew->SetAt(i, tlsArray->GetAt(i));
                }
            }
            t_ThreadStatics.pNonCollectibleTlsArrayData = OBJECTREF_TO_UNCHECKED_OBJECTREF(tlsArrayNew);
            tlsArray = tlsArrayNew;
            t_ThreadStatics.cNonCollectibleTlsData = tlsArrayNew->GetNumComponents() + NUMBER_OF_TLSOFFSETS_NOT_USED_IN_NONCOLLECTIBLE_ARRAY;
            GCPROTECT_END();
        }
        gcBaseAddresses.ppTLSBaseAddress = (TADDR*)(tlsArray->GetDataPtr() + (index.GetIndexOffset() - NUMBER_OF_TLSOFFSETS_NOT_USED_IN_NONCOLLECTIBLE_ARRAY)) ;
        staticIsNonCollectible = true;
        gcBaseAddresses.pTLSBaseAddress = *gcBaseAddresses.ppTLSBaseAddress;
    }
    else if (index.GetTLSIndexType() == TLSIndexType::DirectOnThreadLocalData)
    {
        // All of the current cases are non GC static, non-collectible
        _ASSERTE(!isGCStatic);
        _ASSERTE(!isCollectible);
        gcBaseAddresses.pTLSBaseAddress = ((TADDR)&t_ThreadStatics) + index.GetIndexOffset();
    }
    else
    {
        int32_t cCollectibleTlsData = t_ThreadStatics.cCollectibleTlsData;
        if (cCollectibleTlsData <= index.GetIndexOffset())
        {
            // Grow the underlying TLS array
            SpinLockHolder spinLock(&t_ThreadStatics.pThread->m_TlsSpinLock);
            int32_t newcCollectibleTlsData = index.GetIndexOffset() + 8; // Leave a bit of margin
            OBJECTHANDLE* pNewTLSArrayData = new OBJECTHANDLE[newcCollectibleTlsData];
            memset(pNewTLSArrayData, 0, newcCollectibleTlsData * sizeof(OBJECTHANDLE));
            if (cCollectibleTlsData > 0)
                memcpy(pNewTLSArrayData, (void*)t_ThreadStatics.pCollectibleTlsArrayData, cCollectibleTlsData * sizeof(OBJECTHANDLE));
            OBJECTHANDLE* pOldArray = (OBJECTHANDLE*)t_ThreadStatics.pCollectibleTlsArrayData;
            t_ThreadStatics.pCollectibleTlsArrayData = pNewTLSArrayData;
            cCollectibleTlsData = newcCollectibleTlsData;
            t_ThreadStatics.cCollectibleTlsData = cCollectibleTlsData;
            delete[] pOldArray;
        }

        _ASSERTE(t_ThreadStatics.pThread->cLoaderHandles != -1); // Check sentinel value indicating that there are no LoaderHandles, the thread has gone through termination and is permanently dead.

        if (isCollectible && t_ThreadStatics.pThread->cLoaderHandles <= index.GetIndexOffset())
        {
            // Grow the underlying TLS array
            SpinLockHolder spinLock(&t_ThreadStatics.pThread->m_TlsSpinLock);
            int32_t cNewTLSLoaderHandles = index.GetIndexOffset() + 8; // Leave a bit of margin
            size_t cbNewTLSLoaderHandles = sizeof(LOADERHANDLE) * cNewTLSLoaderHandles;
            LOADERHANDLE* pNewTLSLoaderHandles = new LOADERHANDLE[cNewTLSLoaderHandles];
            memset(pNewTLSLoaderHandles, 0, cbNewTLSLoaderHandles);
            if (cCollectibleTlsData > 0)
                memcpy(pNewTLSLoaderHandles, (void*)t_ThreadStatics.pThread->pLoaderHandles, t_ThreadStatics.pThread->cLoaderHandles * sizeof(LOADERHANDLE));

            LOADERHANDLE* pOldArray = t_ThreadStatics.pThread->pLoaderHandles;
            t_ThreadStatics.pThread->pLoaderHandles = pNewTLSLoaderHandles;
            t_ThreadStatics.pThread->cLoaderHandles = cNewTLSLoaderHandles;
            delete[] pOldArray;
        }

        OBJECTHANDLE* pCollectibleTlsArrayData = t_ThreadStatics.pCollectibleTlsArrayData;
        pCollectibleTlsArrayData += index.GetIndexOffset();
        OBJECTHANDLE objHandle = *pCollectibleTlsArrayData;
        if (IsHandleNullUnchecked(objHandle))
        {
            objHandle = GetAppDomain()->CreateLongWeakHandle(NULL);
            *pCollectibleTlsArrayData = objHandle;
        }
        gcBaseAddresses.ppTLSBaseAddress = reinterpret_cast<TADDR*>(objHandle);
        gcBaseAddresses.pTLSBaseAddress = dac_cast<TADDR>(OBJECTREFToObject(ObjectFromHandle(objHandle)));
    }

    if (gcBaseAddresses.pTLSBaseAddress == (TADDR)NULL)
    {
        // Maybe it is in the InFlightData
        InFlightTLSData* pInFlightData = t_ThreadStatics.pInFlightData;
        InFlightTLSData** ppOldNextPtr = &t_ThreadStatics.pInFlightData;
        while (pInFlightData != NULL)
        {
            if (pInFlightData->tlsIndex == index)
            {
                gcBaseAddresses.pTLSBaseAddress = dac_cast<TADDR>(OBJECTREFToObject(ObjectFromHandle(pInFlightData->hTLSData)));
                if (pMT->IsClassInited())
                {
                    {
                        SpinLockHolder spinLock(&t_ThreadStatics.pThread->m_TlsSpinLock);
                        SetTLSBaseValue(gcBaseAddresses.ppTLSBaseAddress, gcBaseAddresses.pTLSBaseAddress, staticIsNonCollectible);
                        *ppOldNextPtr = pInFlightData->pNext;
                    }
                    delete pInFlightData;
                }
                break;
            }
            ppOldNextPtr = &pInFlightData->pNext;
            pInFlightData = pInFlightData->pNext;
        }
        if (gcBaseAddresses.pTLSBaseAddress == (TADDR)NULL)
        {
            // Now we need to actually allocate the TLS data block
            struct 
            {
                PTRARRAYREF ptrRef;
                OBJECTREF tlsEntry;
            } gc;
            memset(&gc, 0, sizeof(gc));
            GCPROTECT_BEGIN(gc);
            if (isGCStatic)
            {
                gc.ptrRef = (PTRARRAYREF)AllocateObjectArray(pMT->GetClass()->GetNumHandleThreadStatics(), g_pObjectClass);
                if (pMT->HasBoxedThreadStatics())
                {
                    AllocateThreadStaticBoxes(pMT, &gc.ptrRef);
                }
                gc.tlsEntry = (OBJECTREF)gc.ptrRef;
            }
            else
            {
#ifndef TARGET_64BIT
                // On non 64 bit platforms, the static data may need to be 8 byte aligned to allow for good performance
                // for doubles and 64bit ints, come as close as possible, by simply allocating the data as a double array
                gc.tlsEntry = AllocatePrimitiveArray(ELEMENT_TYPE_R8, static_cast<DWORD>(AlignUp(pMT->GetClass()->GetNonGCThreadStaticFieldBytes(), 8)/8));
#else
                gc.tlsEntry = AllocatePrimitiveArray(ELEMENT_TYPE_I1, static_cast<DWORD>(pMT->GetClass()->GetNonGCThreadStaticFieldBytes()));
#endif
            }

            NewHolder<InFlightTLSData> pNewInFlightData = NULL;
            if (!pMT->IsClassInited() && pInFlightData == NULL)
            {
                pNewInFlightData = new InFlightTLSData(index);
                HandleType handleType = staticIsNonCollectible ? HNDTYPE_STRONG : HNDTYPE_WEAK_LONG;
                pNewInFlightData->hTLSData = GetAppDomain()->CreateTypedHandle(gc.tlsEntry, handleType);
                pInFlightData = pNewInFlightData;
            }

            if (isCollectible)
            {
                LOADERHANDLE *pLoaderHandle = t_ThreadStatics.pThread->pLoaderHandles + index.GetIndexOffset();
                // Note, that this can fail, but if it succeeds we don't have a holder in place to clean it up if future operations fail
                // Add such a holder if we ever add a possibly failing operation after this
                *pLoaderHandle = pMT->GetLoaderAllocator()->AllocateHandle(gc.tlsEntry);
            }

            // After this, we cannot fail
            pNewInFlightData.SuppressRelease();

            {
                GCX_FORBID();
                gcBaseAddresses.pTLSBaseAddress = (TADDR)OBJECTREFToObject(gc.tlsEntry);
                if (pInFlightData == NULL)
                {
                    SetTLSBaseValue(gcBaseAddresses.ppTLSBaseAddress, gcBaseAddresses.pTLSBaseAddress, staticIsNonCollectible);
                }
                else
                {
                    SpinLockHolder spinLock(&t_ThreadStatics.pThread->m_TlsSpinLock);
                    pInFlightData->pNext = t_ThreadStatics.pInFlightData;
                    StoreObjectInHandle(pInFlightData->hTLSData, gc.tlsEntry);
                    t_ThreadStatics.pInFlightData = pInFlightData;
                }
            }
            GCPROTECT_END();
        }
    }
    GCPROTECT_END();
    _ASSERTE(gcBaseAddresses.pTLSBaseAddress != (TADDR)NULL);
    return reinterpret_cast<void*>(gcBaseAddresses.pTLSBaseAddress);
}

void GetTLSIndexForThreadStatic(MethodTable* pMT, bool gcStatic, TLSIndex* pIndex, uint32_t bytesNeeded)
{
    CONTRACTL
    {
        THROWS;
        GC_NOTRIGGER;
        MODE_ANY;
    }
    CONTRACTL_END;

    GCX_COOP();
    CrstHolder ch(&g_TLSCrst);
    if (pIndex->IsAllocated())
    {
        return;
    }

    TLSIndex newTLSIndex = TLSIndex::Unallocated();

    if (!pMT->Collectible())
    {
        bool usedDirectOnThreadLocalDataPath = false;

        if (!gcStatic && ((pMT == CoreLibBinder::GetExistingClass(CLASS__THREAD_BLOCKING_INFO)) || (pMT == CoreLibBinder::GetExistingClass(CLASS__DIRECTONTHREADLOCALDATA)) || ((g_directThreadLocalTLSBytesAvailable >= bytesNeeded) && (!pMT->HasClassConstructor() || pMT->IsClassInited()))))
        {
            if (pMT == CoreLibBinder::GetExistingClass(CLASS__THREAD_BLOCKING_INFO))
            {
                newTLSIndex = TLSIndex(TLSIndexType::DirectOnThreadLocalData, offsetof(ThreadLocalData, ThreadBlockingInfo_First) - OFFSETOF__CORINFO_Array__data);
                usedDirectOnThreadLocalDataPath = true;
            }
            else if (pMT == CoreLibBinder::GetExistingClass(CLASS__DIRECTONTHREADLOCALDATA))
            {
                newTLSIndex = TLSIndex(TLSIndexType::DirectOnThreadLocalData, offsetof(ThreadLocalData, pThread) - OFFSETOF__CORINFO_Array__data);
                usedDirectOnThreadLocalDataPath = true;
            }
            else
            {
                // This is a top down bump allocator that aligns data at the largest alignment that might be needed
                uint32_t newBytesAvailable = g_directThreadLocalTLSBytesAvailable - bytesNeeded;
                uint32_t indexOffsetWithoutAlignment = offsetof(ThreadLocalData, ExtendedDirectThreadLocalTLSData) - OFFSETOF__CORINFO_Array__data + newBytesAvailable;
                uint32_t alignment;
                if (bytesNeeded >= 8)
                    alignment = 8;
                if (bytesNeeded >= 4)
                    alignment = 4;
                else if (bytesNeeded >= 2)
                    alignment = 2;
                else 
                    alignment = 1;
                
                uint32_t actualIndexOffset = AlignDown(indexOffsetWithoutAlignment, alignment);
                uint32_t alignmentAdjust = indexOffsetWithoutAlignment - actualIndexOffset;
                if (alignmentAdjust <= newBytesAvailable)
                {
                    g_directThreadLocalTLSBytesAvailable = newBytesAvailable - alignmentAdjust;
                    newTLSIndex = TLSIndex(TLSIndexType::DirectOnThreadLocalData, actualIndexOffset);
                }
                usedDirectOnThreadLocalDataPath = true;
            }
            if (usedDirectOnThreadLocalDataPath)
                VolatileStore(&g_pMethodTablesForDirectThreadLocalData[IndexOffsetToDirectThreadLocalIndex(newTLSIndex.GetIndexOffset())], pMT);
        }

        if (!usedDirectOnThreadLocalDataPath)
        {
            uint32_t tlsRawIndex = g_NextNonCollectibleTlsSlot++;
            newTLSIndex = TLSIndex(TLSIndexType::NonCollectible, tlsRawIndex);
            g_pThreadStaticNonCollectibleTypeIndices->Set(newTLSIndex, pMT, gcStatic);
        }
    }
    else
    {
        if (!g_pThreadStaticCollectibleTypeIndices->FindClearedIndex(&newTLSIndex))
        {
            uint32_t tlsRawIndex = g_NextTLSSlot;
            newTLSIndex = TLSIndex(TLSIndexType::Collectible, tlsRawIndex);
            g_NextTLSSlot += 1;
        }

        g_pThreadStaticCollectibleTypeIndices->Set(newTLSIndex, pMT, gcStatic);
        pMT->GetLoaderAllocator()->GetTLSIndexList().Append(newTLSIndex);
    }

    pIndex->VolatileStore(newTLSIndex); // Use a volatile store so that any other thread that sees the allocated index will also see the writes throughout this path.
}

void FreeTLSIndicesForLoaderAllocator(LoaderAllocator *pLoaderAllocator)
{
    CONTRACTL
    {
        GC_TRIGGERS;
        NOTHROW;
        MODE_COOPERATIVE;
        CAN_TAKE_LOCK;
    }
    CONTRACTL_END;

    CrstHolder ch(&g_TLSCrst);

    const auto& tlsIndicesToCleanup = pLoaderAllocator->GetTLSIndexList();
    COUNT_T current = 0;
    COUNT_T end = tlsIndicesToCleanup.GetCount();

    while (current != end)
    {
        g_pThreadStaticCollectibleTypeIndices->Clear(tlsIndicesToCleanup[current], 0);
        ++current;
    }
}

static void* GetTlsIndexObjectAddress();

#if !defined(TARGET_APPLE) && defined(TARGET_UNIX) && !defined(TARGET_ANDROID) && (defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64))
extern "C" size_t GetTLSResolverAddress();

// Check if the resolver address retrieval code is expected. We verify the exact
// code sequence for all the instructions. However, for two instructions adrp/ldr,
// we make sure that the instruction's opcode and registers matches.
// If the resolver address retrieval code is correct, we invoke it to determine if
// it is a static or dynamic resolver. TLS optimization is enabled only for for static
// resolver. That's because for static resolver, the TP offset is same for all threads.
// For dynamic resolver, TP offset returned is for the current thread and will be
// different for the other threads.
static bool IsValidTLSResolver()
{
#define READ_CODE(p, code)                                      \
    code = (p[3] << 24) | (p[2] << 16) | (p[1] << 8) | p[0];    \
    p += 4;

    uint32_t code;
    uint8_t* p = reinterpret_cast<uint8_t*>(&GetTLSResolverAddress);

    // stp     x29, x30, [sp, #-32]!
    READ_CODE(p, code)
    if (code != 0xA9BE7BFD)
    {
        return false;
    }

    // mov     x29, sp
    READ_CODE(p, code)
    if (code != 0x910003FD)
    {
        return false;
    }

    // adrp    x0, <address>
    // 28:24 have 0x10 for adrp
    // 4:0 should have x0
    READ_CODE(p, code)
    if ((code & 0x9F00001F) != 0x90000000)
    {
        return false;
    }

    // ldr     x0, [x0, <offet>]
    READ_CODE(p, code)
    // 31:24 have 0xf9 for ldr
    // 23:22 have 01
    // 9:5 have 0 for x0
    // 4:0 have 1 for x1
    if ((code & 0xFFC003FF) != 0xF9400001)
    {
        return false;
    }

    // mov     x0, x1
    READ_CODE(p, code)
    if (code != 0xAA0103E0)
    {
        return false;
    }

    // ldp     x29, x30, [sp], #32
    READ_CODE(p, code)
    if (code != 0xA8C27BFD)
    {
        return false;
    }

    // ret
    READ_CODE(p, code)
    if (code != 0xD65F03C0)
    {
        return false;
    }

    // Now invoke the code to retrieve the resolver address
    // and verify if that is as expected.
    uint32_t* resolverAddress = reinterpret_cast<uint32_t*>(GetTLSResolverAddress());
    int ip = 0;
    if ((resolverAddress[ip] == 0xd503201f) || (resolverAddress[ip] == 0xd503241f))
    {
        // nop might not be present in older resolver, so skip it.

        // nop or hint 32
        ip++;
    }

    if (
        // ldr x0, [x0, #8]
        (resolverAddress[ip] == 0xf9400400) &&
        // ret
        (resolverAddress[ip + 1] == 0xd65f03c0)
    )
    {
        return true;
    }

    return false;
}
#endif // !TARGET_APPLE && TARGET_UNIX && !TARGET_ANDROID && (TARGET_ARM64 || TARGET_LOONGARCH64)

bool CanJITOptimizeTLSAccess()
{
    LIMITED_METHOD_CONTRACT;
    if (g_pConfig->DisableOptimizedThreadStaticAccess())
    {
        return false;
    }

    bool optimizeThreadStaticAccess = false;
#if defined(TARGET_ARM)
    // Optimization is disabled for linux/windows arm
#elif !defined(TARGET_WINDOWS) && defined(TARGET_X86)
    // Optimization is disabled for linux/x86
#elif defined(TARGET_LINUX_MUSL) && defined(TARGET_ARM64)
    // Optimization is disabled for linux musl arm64
#elif defined(TARGET_FREEBSD) && defined(TARGET_ARM64)
    // Optimization is disabled for FreeBSD/arm64
#elif defined(TARGET_ANDROID)
    // Optimation is disabled for Android until emulated TLS is supported.
#elif !defined(TARGET_APPLE) && defined(TARGET_UNIX) && defined(TARGET_ARM64)
    bool tlsResolverValid = IsValidTLSResolver();
    if (tlsResolverValid)
    {
        optimizeThreadStaticAccess = true;
#ifdef _DEBUG
        if (CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_AssertNotStaticTlsResolver) != 0)
        {
            _ASSERTE(!"Detected static resolver in use when not expected");
        }
#endif // _DEBUG
    }
#elif defined(TARGET_LOONGARCH64)
    // Optimization is enabled for linux/loongarch64 only for static resolver.
    // For static resolver, the TP offset is same for all threads.
    // For dynamic resolver, TP offset returned is for the current thread and
    // will be different for the other threads.
    uint32_t* resolverAddress = reinterpret_cast<uint32_t*>(GetTLSResolverAddress());

    if (
        // ld.d a0, a0, 8
        (resolverAddress[0] == 0x28c02084) &&
        // ret
        (resolverAddress[1] == 0x4c000020)
    )
    {
        optimizeThreadStaticAccess = true;
#ifdef _DEBUG
        if (CLRConfig::GetConfigValue(CLRConfig::EXTERNAL_AssertNotStaticTlsResolver) != 0)
        {
            _ASSERTE(!"Detected static resolver in use when not expected");
        }
#endif
    }
#else
    optimizeThreadStaticAccess = true;
#if !defined(TARGET_APPLE) && defined(TARGET_UNIX) && defined(TARGET_AMD64)
    // For linux/x64, check if compiled coreclr as .so file and not single file.
    // For single file, the `tls_index` might not be accurate.
    // Do not perform this optimization in such case.
    optimizeThreadStaticAccess = GetTlsIndexObjectAddress() != nullptr;
#endif // !TARGET_APPLE && TARGET_UNIX && TARGET_AMD64
#endif

    return optimizeThreadStaticAccess;
}

#ifndef _MSC_VER
extern "C" void* __tls_get_addr(void* ti);
#endif // !_MSC_VER

#if defined(TARGET_WINDOWS)
EXTERN_C uint32_t _tls_index;
/*********************************************************************/
static uint32_t ThreadLocalOffset(void* p)
{
    LIMITED_METHOD_CONTRACT;

    PTEB Teb = NtCurrentTeb();
    uint8_t** pTls = (uint8_t**)Teb->ThreadLocalStoragePointer;
    uint8_t* pOurTls = pTls[_tls_index];
    return (uint32_t)((uint8_t*)p - pOurTls);
}
#elif defined(TARGET_APPLE)
extern "C" void* GetThreadVarsAddress();

static void* GetThreadVarsSectionAddressFromDesc(uint8_t* p)
{
    LIMITED_METHOD_CONTRACT;

    _ASSERT(p[0] == 0x48 && p[1] == 0x8d && p[2] == 0x3d);

    // At this point, `p` contains the instruction pointer and is pointing to the above opcodes.
    // These opcodes are patched by the dynamic linker.
    // Move beyond the opcodes that we have already checked above.
    p += 3;

    // The descriptor address is located at *p at this point.
    // (p + 4) below skips the descriptor address bytes embedded in the instruction and
    // add it to the `instruction pointer` to find out the address.
    return *(uint32_t*)p + (p + 4);
}

static void* GetThreadVarsSectionAddress()
{
    LIMITED_METHOD_CONTRACT;

#ifdef TARGET_AMD64
    // On x64, the address is related to rip, so, disassemble the function,
    // read the offset, and then relative to the IP, find the final address of
    // __thread_vars section.
    uint8_t* p = reinterpret_cast<uint8_t*>(&GetThreadVarsAddress);
    return GetThreadVarsSectionAddressFromDesc(p);
#else
    return GetThreadVarsAddress();
#endif // TARGET_AMD64
}

#else

// Linux

#ifdef TARGET_AMD64

extern "C" void* GetTlsIndexObjectDescOffset();

static void* GetThreadStaticDescriptor(uint8_t* p)
{
    LIMITED_METHOD_CONTRACT;

    if (!(p[0] == 0x66 && p[1] == 0x48 && p[2] == 0x8d && p[3] == 0x3d))
    {
        // The optimization is disabled if coreclr is not compiled in .so format.
        _ASSERTE(false && "Unexpected code sequence");
        return nullptr;
    }

    // At this point, `p` contains the instruction pointer and is pointing to the above opcodes.
    // These opcodes are patched by the dynamic linker.
    // Move beyond the opcodes that we have already checked above.
    p += 4;

    // The descriptor address is located at *p at this point. Read that and add
    // it to the instruction pointer to locate the address of `ti` that will be used
    // to pass to __tls_get_addr during execution.
    // (p + 4) below skips the descriptor address bytes embedded in the instruction and
    // add it to the `instruction pointer` to find out the address.
    return *(uint32_t*)p + (p + 4);
}

static void* GetTlsIndexObjectAddress()
{
    LIMITED_METHOD_CONTRACT;

    uint8_t* p = reinterpret_cast<uint8_t*>(&GetTlsIndexObjectDescOffset);
    return GetThreadStaticDescriptor(p);
}

#elif !defined(TARGET_ANDROID) && defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)

extern "C" size_t GetThreadStaticsVariableOffset();

#endif // !TARGET_ANDROID && TARGET_ARM64 || TARGET_LOONGARCH64 || TARGET_RISCV64
#endif // TARGET_WINDOWS

void GetThreadLocalStaticBlocksInfo(CORINFO_THREAD_STATIC_BLOCKS_INFO* pInfo)
{

#if !defined(TARGET_ANDROID)
    STANDARD_VM_CONTRACT;
    size_t threadStaticBaseOffset = 0;

#if defined(TARGET_WINDOWS)
    pInfo->tlsIndex.addr = (void*)static_cast<uintptr_t>(_tls_index);
    pInfo->tlsIndex.accessType = IAT_VALUE;

    pInfo->offsetOfThreadLocalStoragePointer = offsetof(_TEB, ThreadLocalStoragePointer);
    threadStaticBaseOffset = ThreadLocalOffset(&t_ThreadStatics);

#elif defined(TARGET_APPLE)

    pInfo->threadVarsSection = GetThreadVarsSectionAddress();

#elif defined(TARGET_AMD64)

    // For Linux/x64, get the address of tls_get_addr system method and the base address
    // of struct that we will pass to it.
    pInfo->tlsGetAddrFtnPtr = reinterpret_cast<void*>(&__tls_get_addr);
    pInfo->tlsIndexObject = GetTlsIndexObjectAddress();

#elif defined(TARGET_ARM64) || defined(TARGET_LOONGARCH64) || defined(TARGET_RISCV64)

    // For Linux arm64/loongarch64/riscv64, just get the offset of thread static variable, and during execution,
    // this offset, arm64 taken from trpid_elp0 system register gives back the thread variable address.
    // this offset, loongarch64 taken from $tp register gives back the thread variable address.
    threadStaticBaseOffset = GetThreadStaticsVariableOffset();

#else
    _ASSERTE_MSG(false, "Unsupported scenario of optimizing TLS access on Linux Arm32/x86 and Android");
#endif // TARGET_WINDOWS

    pInfo->offsetOfMaxThreadStaticBlocks = (uint32_t)(threadStaticBaseOffset + offsetof(ThreadLocalData, cNonCollectibleTlsData));
    pInfo->offsetOfThreadStaticBlocks = (uint32_t)(threadStaticBaseOffset + offsetof(ThreadLocalData, pNonCollectibleTlsArrayData));
    pInfo->offsetOfBaseOfThreadLocalData = (uint32_t)threadStaticBaseOffset;
#endif // !TARGET_ANDROID
}
#endif // !DACCESS_COMPILE

#ifdef DACCESS_COMPILE
void EnumThreadMemoryRegions(ThreadLocalData *pThreadLocalData, CLRDataEnumMemoryFlags flags)
{
    SUPPORTS_DAC;
    DacEnumMemoryRegion(dac_cast<TADDR>(pThreadLocalData->pCollectibleTlsArrayData), pThreadLocalData->cCollectibleTlsData, flags);
    PTR_InFlightTLSData pInFlightData = pThreadLocalData->pInFlightData;
    while (pInFlightData != NULL)
    {
        DacEnumMemoryRegion(dac_cast<TADDR>(pInFlightData), sizeof(InFlightTLSData), flags);
        pInFlightData = pInFlightData->pNext;
    }
}
#endif // DACCESS_COMPILE
