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

Code before:
                        {
                            targetMethod = NonVirtualEntry2MethodDesc(actualTarget);
                        }

                        PTR_InterpByteCodeStart targetIp;
                        if ((targetMethod) && (targetIp = targetMethod->GetInterpreterCode()) != NULL)
                        {
                            pFrame->ip = ip;
                            InterpMethod* pTargetMethod = targetIp->Method;
                            if (frameNeedsTailcallUpdate)
                            {
                                UpdateFrameForTailCall(pFrame, targetIp, LOCAL_VAR_ADDR(callArgsOffset + INTERP_STACK_SLOT_SIZE, int8_t));
                                frameNeedsTailcallUpdate = false;
                            }
                            else
                            {
                                // Shift args down by one slot to remove the delegate obj pointer.
                                // We need to preserve alignment of arguments that require 16-byte alignment.
                                // The sizeOfArgsUpto16ByteAlignment is the size of all the target method args starting at the first argument up to (but not including) the first argument that requires 16-byte alignment.
                                if (sizeOfArgsUpto16ByteAlignment != 0)
                                {
                                    memmove(LOCAL_VAR_ADDR(callArgsOffset, int8_t), LOCAL_VAR_ADDR(callArgsOffset + INTERP_STACK_SLOT_SIZE, int8_t), sizeOfArgsUpto16ByteAlignment);
                                }

                                if (sizeOfArgsUpto16ByteAlignment != pTargetMethod->argsSize)
                                {
                                    // There are arguments that require 16-byte alignment
                                    size_t firstAlignedTargetArgDstOffset = ALIGN_UP(sizeOfArgsUpto16ByteAlignment, INTERP_STACK_ALIGNMENT);
                                    size_t firstAlignedTargetArgSrcOffset = ALIGN_UP(INTERP_STACK_SLOT_SIZE + sizeOfArgsUpto16ByteAlignment, INTERP_STACK_ALIGNMENT);
                                    memmove(LOCAL_VAR_ADDR(callArgsOffset + firstAlignedTargetArgDstOffset, int8_t), LOCAL_VAR_ADDR(callArgsOffset + firstAlignedTargetArgSrcOffset, int8_t), pTargetMethod->argsSize - sizeOfArgsUpto16ByteAlignment);
                                }

                                // Allocate child frame.
                                InterpMethodContextFrame *pChildFrame = pFrame->pNext;
                                if (!pChildFrame)
                                {
                                    pChildFrame = (InterpMethodContextFrame*)alloca(sizeof(InterpMethodContextFrame));
                                    pChildFrame->pNext = NULL;
                                    pFrame->pNext = pChildFrame;
                                    SAVE_THE_LOWEST_SP;
                                }
                                pChildFrame->ReInit(pFrame, targetIp, returnValueAddress, LOCAL_VAR_ADDR(callArgsOffset, int8_t));
                                pFrame = pChildFrame;
                            }
                            // Set execution state for the new frame
                            pMethod = pFrame->startIp->Method;
                            _ASSERTE(pMethod->CheckIntegrity());
                            stack = pFrame->pStack;
                            ip = pFrame->startIp->GetByteCodes();
                            pThreadContext->pStackPointer = stack + pMethod->allocaSize;
                            break;

Review comment (performance):
Using `0` as the “uninitialized” sentinel for the cached `dispatchToken` (and its hash) is ambiguous: `DispatchToken::CreateDispatchToken(slotNumber)` can legitimately produce token value `0` (e.g., TYPE_ID_THIS_CLASS + slot 0), and `DispatchToken::GetHash()` can also return `0`. In those cases the cache slots will never be considered initialized and will be recomputed every call. Use a non-colliding sentinel encoding (e.g., store `token+1` / `hash+1`, or store a dedicated struct/pointer) so caching works for all callvirt sites.
```suggestion
                    size_t storedDispatchTokenPlus1 = (size_t)VolatileLoadWithoutBarrier(&pMethod->pDataItems[ip[4]]);
                    size_t dispatchToken;
                    if (storedDispatchTokenPlus1 == 0)
                    {
                        dispatchToken = CreateDispatchTokenForMethod(pMD);
                        VolatileStoreWithoutBarrier(&pMethod->pDataItems[ip[4]], (void*)(dispatchToken + 1));
                    }
                    else
                    {
                        dispatchToken = storedDispatchTokenPlus1 - 1;
                    }
                    // The token hash is cached in the data item immediately following the dispatchToken
                    size_t storedDispatchTokenHashPlus1 = (size_t)VolatileLoadWithoutBarrier(&pMethod->pDataItems[ip[4] + 1]);
                    size_t dispatchTokenHash;
                    if (storedDispatchTokenHashPlus1 == 0)
                    {
                        dispatchTokenHash = DispatchToken::From_SIZE_T(dispatchToken).GetHash();
                        VolatileStoreWithoutBarrier(&pMethod->pDataItems[ip[4] + 1], (void*)(dispatchTokenHash + 1));
                    }
                    else
                    {
                        dispatchTokenHash = storedDispatchTokenHashPlus1 - 1;
                    }
```

Code after:
                    returnOffset = ip[1];
                    callArgsOffset = ip[2];
                    methodSlot = ip[3];

                    MethodDesc *pMD = (MethodDesc*)pMethod->pDataItems[methodSlot];

                    OBJECTREF *pThisArg = LOCAL_VAR_ADDR(callArgsOffset, OBJECTREF);
                    NULL_CHECK(*pThisArg);

                    MethodTable *pObjMT = (*pThisArg)->GetMethodTable();

                    // Interpreter-FIXME: It would be nice to have these caches initialized at compile time instead
                    // Obtain the cached dispatch token or initialize it
                    size_t dispatchToken = (size_t)VolatileLoadWithoutBarrier(&pMethod->pDataItems[ip[4]]);
                    if (dispatchToken == 0)
                    {
                        dispatchToken = CreateDispatchTokenForMethod(pMD);
                        VolatileStoreWithoutBarrier(&pMethod->pDataItems[ip[4]], (void*)dispatchToken);
                    }
                    // The token hash is cached in the data item immediately following the dispatchToken
                    size_t dispatchTokenHash = (size_t)VolatileLoadWithoutBarrier(&pMethod->pDataItems[ip[4] + 1]);
                    if (dispatchTokenHash == 0)
                    {
                        dispatchTokenHash = DispatchToken::From_SIZE_T(dispatchToken).GetHash();
                        VolatileStoreWithoutBarrier(&pMethod->pDataItems[ip[4] + 1], (void*)dispatchTokenHash);
                    }

                    // Try cache lookup first
                    targetMethod = g_InterpDispatchCache.Lookup(dispatchToken, pObjMT, (uint16_t)dispatchTokenHash);

                    if (targetMethod == NULL)
                    {
                        // miss, resolve the virtual method and cache it
                        targetMethod = CallWithSEHWrapper(
                            [&pMD, &pThisArg]() {
                                return pMD->GetMethodDescOfVirtualizedCode(pThisArg, pMD->GetMethodTable());
                            });
                        g_InterpDispatchCache.Insert(dispatchToken, pObjMT, targetMethod, (uint16_t)dispatchTokenHash);
                    }

                    ip += 5;
                    goto CALL_INTERP_METHOD;
                }

                case INTOP_CALLI_TAIL:
                case INTOP_CALLI:
                {
                    frameNeedsTailcallUpdate = (*ip == INTOP_CALLI_TAIL);
                    returnOffset = ip[1];
                    int8_t* returnValueAddress = LOCAL_VAR_ADDR(returnOffset, int8_t);
                    callArgsOffset = ip[2];