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

Code before:
#include <limits>
#include <functional>

struct InterpDispatchCacheEntry
{
    // MethodTable of the calling object
    void* pMT;
    size_t dispatchToken;
    // Resolved target MethodDesc
    MethodDesc* pTargetMD;
    // Used for linking dead entries for cleanup during GC
    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;


Review comment (performance):
Consider adding a static assertion to verify that DISPATCH_CACHE_BITS equals CALL_STUB_CACHE_NUM_BITS, since the hash function in DispatchToken::GetHash assumes a 12-bit cache. This ensures consistency between the interpreter dispatch cache and the VSD cache hash function, and would catch any future changes that inadvertently modify only one of these constants.
```suggestion

static_assert(DISPATCH_CACHE_BITS == CALL_STUB_CACHE_NUM_BITS, "Interpreter dispatch cache size must match VSD dispatch cache size");
```

Code after:
#include <limits>
#include <functional>

struct InterpDispatchCacheEntry
{
    // MethodTable of the calling object
    MethodTable* pMT;
    size_t dispatchToken;
    // Resolved target MethodDesc
    MethodDesc* pTargetMD;
    // Used for linking dead entries for cleanup during GC
    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)
    {