Repository: dotnet/runtime
File: src/coreclr/vm/interpexec.cpp (C++)

Code before:
                // Given the EE is suspended, we can free the entry without worrying about races
                delete pEntry;
            }
        }
    }
};

// Global interpreter dispatch cache instance
static InterpDispatchCache g_InterpDispatchCache;

// Called during GC, when we are guaranteed no entry is being used by any thread.
void InterpDispatchCache_ReclaimAll()
{
    g_InterpDispatchCache.ReclaimDeadEntries();
}

// Clear entries that reference types/methods from the given LoaderAllocator.
// Called during collectible assembly unload when the EE is suspended.
void InterpDispatchCache_ClearForLoaderAllocator(LoaderAllocator* pLoaderAllocator)
{
    g_InterpDispatchCache.ClearEntriesForLoaderAllocator(pLoaderAllocator);
}

static size_t CreateDispatchTokenForMethod(MethodDesc* pMD)
{
    WRAPPER_NO_CONTRACT;

    uint32_t slotNumber = pMD->GetSlot();

    if (pMD->IsInterface())
    {
        // For interface methods, get the interface's TypeID
        MethodTable* pInterfaceMT = pMD->GetMethodTable();
        uint32_t typeID = pInterfaceMT->GetTypeID();
        return DispatchToken::CreateDispatchToken(typeID, slotNumber).To_SIZE_T();
    }
    else
    {
        // For non-interface virtual methods, use TYPE_ID_THIS_CLASS
        return DispatchToken::CreateDispatchToken(slotNumber).To_SIZE_T();
    }
}

#ifdef TARGET_WASM
// Unused on WASM
#define SAVE_THE_LOWEST_SP do {} while (0)
#else
// Save the lowest SP in the current method so that we can identify it by that during stackwalk
#define SAVE_THE_LOWEST_SP pInterpreterFrame->SetInterpExecMethodSP((TADDR)GetCurrentSP())
#endif // !TARGET_WASM


Review comment (bug):
```suggestion
    CONTRACTL
    {
        NOTHROW;
        GC_NOTRIGGER;
        MODE_COOPERATIVE;

        // Should only be called during a GC suspension
        PRECONDITION(Debug_IsLockedViaThreadSuspension());
    }
    CONTRACTL_END;
        
    g_InterpDispatchCache.ReclaimDeadEntries();
```

I think the above contract is correct.

Code after:
            {
                VolatileStoreWithoutBarrier(&m_cache[i], (InterpDispatchCacheEntry*)nullptr);
                // Given the EE is suspended, we can free the entry without worrying about races
                delete pEntry;
            }
        }
    }
};

// Global interpreter dispatch cache instance
static InterpDispatchCache g_InterpDispatchCache;

// Called during GC, when we are guaranteed no entry is being used by any thread.
void InterpDispatchCache_ReclaimAll()
{
    CONTRACTL
    {
        NOTHROW;
        GC_NOTRIGGER;
        MODE_COOPERATIVE;
        // Should only be called during a GC suspension
        PRECONDITION(Debug_IsLockedViaThreadSuspension());
    }
    CONTRACTL_END;

    g_InterpDispatchCache.ReclaimDeadEntries();
}

// Clear entries that reference types/methods from the given LoaderAllocator.
// Called during collectible assembly unload when the EE is suspended.
void InterpDispatchCache_ClearForLoaderAllocator(LoaderAllocator* pLoaderAllocator)
{
    g_InterpDispatchCache.ClearEntriesForLoaderAllocator(pLoaderAllocator);
}

static size_t CreateDispatchTokenForMethod(MethodDesc* pMD)
{
    WRAPPER_NO_CONTRACT;

    uint32_t slotNumber = pMD->GetSlot();

    if (pMD->IsInterface())
    {
        // For interface methods, get the interface's TypeID
        MethodTable* pInterfaceMT = pMD->GetMethodTable();
        uint32_t typeID = pInterfaceMT->GetTypeID();
        return DispatchToken::CreateDispatchToken(typeID, slotNumber).To_SIZE_T();
    }
    else
    {
        // For non-interface virtual methods, use TYPE_ID_THIS_CLASS