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

Code before:
    InterpDispatchCacheEntry* pNextDead;
};

#define DISPATCH_CACHE_BITS 12
#define DISPATCH_CACHE_SIZE (1 << DISPATCH_CACHE_BITS)
#define DISPATCH_CACHE_MASK (DISPATCH_CACHE_SIZE - 1)

// A simple hash table that caches virtual method dispatch results.
// Maps (DispatchToken, MethodTable*) to MethodDesc*.
struct InterpDispatchCache
{
    InterpDispatchCacheEntry* m_cache[DISPATCH_CACHE_SIZE];
    // List of dead entries to be freed at GC time
    InterpDispatchCacheEntry* m_pDeadList;

    MethodDesc* Lookup(size_t dispatchToken, void* pMT)
    {
        LIMITED_METHOD_CONTRACT;

        size_t idx = Hash(dispatchToken, pMT);

        InterpDispatchCacheEntry* pEntry = m_cache[idx];
        // Data dependency ensures field reads are ordered after loading of `pEntry`
        // The entry struct is immutable once created, so these reads are safe
        if (pEntry != nullptr && pEntry->pMT == pMT && pEntry->dispatchToken == dispatchToken)
            return pEntry->pTargetMD;

        return NULL;
    }

    void Insert(size_t dispatchToken, void* pMT, MethodDesc* pTargetMD)
    {
        LIMITED_METHOD_CONTRACT;

        size_t idx = Hash(dispatchToken, pMT);

        InterpDispatchCacheEntry* pNewEntry = new (nothrow) InterpDispatchCacheEntry();
        if (pNewEntry == nullptr)
            return;

        // Except pNextDead, these fields never change
        pNewEntry->pMT = pMT;
        pNewEntry->dispatchToken = dispatchToken;
        pNewEntry->pTargetMD = pTargetMD;
        pNewEntry->pNextDead = nullptr;

        // CAS has release semantics, so the fields of the entry have correct
        // values once the entry is published. If CAS succeeds, we own the old
        // entry for freeing
        InterpDispatchCacheEntry* pOldEntry = InterlockedExchangeT(&m_cache[idx], pNewEntry);


Review comment (performance):
The Lookup method reads m_cache[idx] without acquire semantics, but the Insert method publishes entries using InterlockedExchangeT which has release semantics. To ensure proper memory ordering and guarantee that the fields of pEntry are visible after the pointer is loaded, consider using VolatileLoad to read the cache entry, similar to how VSD's DispatchCache::GetCacheEntry uses VolatileLoad. This ensures the data dependency chain is correctly established across all compiler optimizations and architectures.

Code after:
    InterpDispatchCacheEntry* pNextDead;

    InterpDispatchCacheEntry(MethodTable* pMT, size_t dispatchToken, MethodDesc* pTargetMD)
    {
        this->pMT = pMT;
        this->dispatchToken = dispatchToken;
        this->pTargetMD = pTargetMD;
        pNextDead = nullptr;
    }
};

#define DISPATCH_CACHE_BITS 12
#define DISPATCH_CACHE_SIZE (1 << DISPATCH_CACHE_BITS)
#define DISPATCH_CACHE_MASK (DISPATCH_CACHE_SIZE - 1)

// A simple hash table that caches virtual method dispatch results.
// Maps (DispatchToken, MethodTable*) to MethodDesc*.
struct InterpDispatchCache
{
    InterpDispatchCacheEntry* m_cache[DISPATCH_CACHE_SIZE];
    // List of dead entries to be freed at GC time
    InterpDispatchCacheEntry* m_pDeadList;

    MethodDesc* Lookup(size_t dispatchToken, void* pMT, uint16_t dispatchTokenHash)
    {
        LIMITED_METHOD_CONTRACT;

        size_t idx = Hash(dispatchToken, pMT, dispatchTokenHash);

        InterpDispatchCacheEntry* pEntry = VolatileLoadWithoutBarrier(&m_cache[idx]);
        // Data dependency ensures field reads are ordered after loading of `pEntry`
        // The entry struct is immutable once created, so these reads are safe
        if (pEntry != nullptr && pEntry->pMT == pMT && pEntry->dispatchToken == dispatchToken)
            return pEntry->pTargetMD;

        return NULL;
    }

    void Insert(size_t dispatchToken, MethodTable* pMT, MethodDesc* pTargetMD, uint16_t dispatchTokenHash)
    {
        LIMITED_METHOD_CONTRACT;

        size_t idx = Hash(dispatchToken, pMT, dispatchTokenHash);

        InterpDispatchCacheEntry* pNewEntry = new (nothrow) InterpDispatchCacheEntry(pMT, dispatchToken, pTargetMD);
        if (pNewEntry == nullptr)
            return;

        // CAS has release semantics, so the fields of the entry have correct
        // values once the entry is published. If CAS succeeds, we own the old
        // entry for freeing