// 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"

#ifdef FEATURE_INTERPRETER

#include "interpreter.h"
#include "interpreter.hpp"
#include "cgencpu.h"
#include "stublink.h"
#include "openum.h"
#include "fcall.h"
#include "frames.h"
#include "gcheaputilities.h"
#include <float.h>
#include "jitinterface.h"
#include "safemath.h"
#include "exceptmacros.h"
#include "runtimeexceptionkind.h"
#include "runtimehandles.h"
#include "vars.hpp"
#include "cycletimer.h"

inline CORINFO_CALLINFO_FLAGS combine(CORINFO_CALLINFO_FLAGS flag1, CORINFO_CALLINFO_FLAGS flag2)
{
    return (CORINFO_CALLINFO_FLAGS) (flag1 | flag2);
}

static CorInfoType asCorInfoType(CORINFO_CLASS_HANDLE clsHnd)
{
    TypeHandle typeHnd(clsHnd);
    return CEEInfo::asCorInfoType(typeHnd.GetInternalCorElementType(), typeHnd, NULL);
}

InterpreterMethodInfo::InterpreterMethodInfo(CEEInfo* comp, CORINFO_METHOD_INFO* methInfo)
    : m_method(methInfo->ftn),
      m_module(methInfo->scope),
      m_ILCode(methInfo->ILCode),
      m_ILCodeEnd(methInfo->ILCode + methInfo->ILCodeSize),
      m_maxStack(methInfo->maxStack),
#if INTERP_PROFILE
      m_totIlInstructionsExeced(0),
      m_maxIlInstructionsExeced(0),
#endif
      m_ehClauseCount(methInfo->EHcount),
      m_varArgHandleArgNum(NO_VA_ARGNUM),
      m_numArgs(methInfo->args.numArgs),
      m_numLocals(methInfo->locals.numArgs),
      m_flags(0),
      m_argDescs(NULL),
      m_returnType(methInfo->args.retType),
      m_invocations(0),
      m_methodCache(NULL)
{
    // Overflow sanity check. (Can ILCodeSize ever be zero?)
    _ASSERTE(m_ILCode <= m_ILCodeEnd);

    // Does the calling convention indicate an implicit "this" (first arg) or generic type context arg (last arg)?
    SetFlag<Flag_hasThisArg>((methInfo->args.callConv & CORINFO_CALLCONV_HASTHIS) != 0);
    if (GetFlag<Flag_hasThisArg>())
    {
        GCX_PREEMP();
        CORINFO_CLASS_HANDLE methClass = comp->getMethodClass(methInfo->ftn);
        DWORD attribs = comp->getClassAttribs(methClass);
        SetFlag<Flag_thisArgIsObjPtr>((attribs & CORINFO_FLG_VALUECLASS) == 0);
    }

#if INTERP_PROFILE || defined(_DEBUG)
    {
        const char* clsName;
#if defined(_DEBUG)
        m_methName = ::eeGetMethodFullName(comp, methInfo->ftn, &clsName);
#else
        m_methName = comp->getMethodName(methInfo->ftn, &clsName);
#endif
        char* myClsName = new char[strlen(clsName) + 1];
        strcpy(myClsName, clsName);
        m_clsName = myClsName;
    }
#endif // INTERP_PROFILE

    // Do we have a ret buff?  If its a struct or refany, then *maybe*, depending on architecture...
    bool hasRetBuff = (methInfo->args.retType == CORINFO_TYPE_VALUECLASS || methInfo->args.retType == CORINFO_TYPE_REFANY);
#if defined(FEATURE_HFA)
    // ... unless its an HFA type (and not varargs)...
    if (hasRetBuff && (comp->getHFAType(methInfo->args.retTypeClass) != CORINFO_HFA_ELEM_NONE) && methInfo->args.getCallConv() != CORINFO_CALLCONV_VARARG)
    {
        hasRetBuff = false;
    }
#endif

#if defined(UNIX_AMD64_ABI) || defined(HOST_LOONGARCH64) || defined(HOST_RISCV64)
    // ...or it fits into two registers.
    if (hasRetBuff && getClassSize(methInfo->args.retTypeClass) <= 2 * sizeof(void*))
    {
        hasRetBuff = false;
    }
#elif defined(HOST_ARM) || defined(HOST_AMD64)|| defined(HOST_ARM64)
    // ...or it fits into one register.
    if (hasRetBuff && getClassSize(methInfo->args.retTypeClass) <= sizeof(void*))
    {
        hasRetBuff = false;
    }
#endif
    SetFlag<Flag_hasRetBuffArg>(hasRetBuff);

    MetaSig sig(reinterpret_cast<MethodDesc*>(methInfo->ftn));
    SetFlag<Flag_hasGenericsContextArg>((methInfo->args.callConv & CORINFO_CALLCONV_PARAMTYPE) != 0);
    SetFlag<Flag_isVarArg>((methInfo->args.callConv & CORINFO_CALLCONV_VARARG) != 0);
    SetFlag<Flag_typeHasGenericArgs>(methInfo->args.sigInst.classInstCount > 0);
    SetFlag<Flag_methHasGenericArgs>(methInfo->args.sigInst.methInstCount > 0);
    _ASSERTE_MSG(!GetFlag<Flag_hasGenericsContextArg>()
                 || ((GetFlag<Flag_typeHasGenericArgs>() && !(GetFlag<Flag_hasThisArg>() && GetFlag<Flag_thisArgIsObjPtr>())) || GetFlag<Flag_methHasGenericArgs>()),
                 "If the method takes a generic parameter, is a static method of generic class (or meth of a value class), and/or itself takes generic parameters");

    if (GetFlag<Flag_hasThisArg>())
    {
        m_numArgs++;
    }
    if (GetFlag<Flag_hasRetBuffArg>())
    {
        m_numArgs++;
    }
    if (GetFlag<Flag_isVarArg>())
    {
        m_numArgs++;
    }
    if (GetFlag<Flag_hasGenericsContextArg>())
    {
        m_numArgs++;
    }
    if (m_numArgs == 0)
    {
        m_argDescs = NULL;
    }
    else
    {
        m_argDescs = new ArgDesc[m_numArgs];
    }

    // Now we'll do the locals.
    m_localDescs = new LocalDesc[m_numLocals];
    // Allocate space for the pinning reference bits (lazily).
    m_localIsPinningRefBits = NULL;

    // Now look at each local.
    CORINFO_ARG_LIST_HANDLE localsPtr = methInfo->locals.args;
    CORINFO_CLASS_HANDLE vcTypeRet;
    unsigned curLargeStructOffset = 0;
    for (unsigned k = 0; k < methInfo->locals.numArgs; k++)
    {
        // TODO: if this optimization succeeds, the switch below on localType
        // can become much simpler.
        m_localDescs[k].m_offset = 0;
#ifdef _DEBUG
        vcTypeRet = NULL;
#endif
        CorInfoTypeWithMod localTypWithMod = comp->getArgType(&methInfo->locals, localsPtr, &vcTypeRet);
        // If the local vars is a pinning reference, set the bit to indicate this.
        if ((localTypWithMod & CORINFO_TYPE_MOD_PINNED) != 0)
        {
            SetPinningBit(k);
        }

        CorInfoType localType = strip(localTypWithMod);
        switch (localType)
        {
        case CORINFO_TYPE_VALUECLASS:
        case CORINFO_TYPE_REFANY: // Just a special case: vcTypeRet is handle for TypedReference in this case...
            {
                InterpreterType tp = InterpreterType(comp, vcTypeRet);
                unsigned size = static_cast<unsigned>(tp.Size(comp));
                size = max(size, sizeof(void*));
                m_localDescs[k].m_type = tp;
                if (tp.IsLargeStruct(comp))
                {
                    m_localDescs[k].m_offset = curLargeStructOffset;
                    curLargeStructOffset += size;
                }
            }
            break;

        case CORINFO_TYPE_VAR:
            NYI_INTERP("argument of generic parameter type");  // Should not happen;
            break;

        default:
            m_localDescs[k].m_type = InterpreterType(localType);
            break;
        }
        m_localDescs[k].m_typeStackNormal = m_localDescs[k].m_type.StackNormalize();
        localsPtr = comp->getArgNext(localsPtr);
    }
    m_largeStructLocalSize = curLargeStructOffset;
}

void InterpreterMethodInfo::InitArgInfo(CEEInfo* comp, CORINFO_METHOD_INFO* methInfo, short* argOffsets_)
{
    unsigned numSigArgsPlusThis = methInfo->args.numArgs;
    if (GetFlag<Flag_hasThisArg>())
    {
        numSigArgsPlusThis++;
    }

    // The m_argDescs array is constructed in the following "canonical" order:
    // 1. 'this' pointer
    // 2. signature arguments
    // 3. return buffer
    // 4. type parameter -or- vararg cookie
    //
    // argOffsets_ is passed in this order, and serves to establish the offsets to arguments
    // when the interpreter is invoked using the native calling convention (i.e., not directly).
    //
    // When the interpreter is invoked directly, the arguments will appear in the same order
    // and form as arguments passed to MethodDesc::CallDescr().  This ordering is as follows:
    // 1. 'this' pointer
    // 2. return buffer
    // 3. signature arguments
    //
    // MethodDesc::CallDescr() does not support generic parameters or varargs functions.

    _ASSERTE_MSG((methInfo->args.callConv & (CORINFO_CALLCONV_EXPLICITTHIS)) == 0,
        "Don't yet handle EXPLICITTHIS calling convention modifier.");
    switch (methInfo->args.callConv & CORINFO_CALLCONV_MASK)
    {
    case CORINFO_CALLCONV_DEFAULT:
    case CORINFO_CALLCONV_VARARG:
        {
            unsigned k = 0;
            ARG_SLOT* directOffset = NULL;
            short directRetBuffOffset = 0;
            short directVarArgOffset = 0;
            short directTypeParamOffset = 0;

            // If there's a "this" argument, handle it.
            if (GetFlag<Flag_hasThisArg>())
            {
                m_argDescs[k].m_type = InterpreterType(CORINFO_TYPE_UNDEF);
#ifdef FEATURE_INSTANTIATINGSTUB_AS_IL
                MethodDesc *pMD = reinterpret_cast<MethodDesc*>(methInfo->ftn);
                // The signature of the ILStubs may be misleading.
                // If a StubTarget is ever set, we'll find the correct type by inspecting the
                // target, rather than the stub.
                if (pMD->IsILStub())
                {

                    if (pMD->AsDynamicMethodDesc()->IsUnboxingILStub())
                    {
                        // This is an unboxing stub where the thisptr is passed as a boxed VT.
                        m_argDescs[k].m_type = InterpreterType(CORINFO_TYPE_CLASS);
                    }
                    else
                    {
                        MethodDesc *pTargetMD = pMD->AsDynamicMethodDesc()->GetILStubResolver()->GetStubTargetMethodDesc();
                        if (pTargetMD != NULL)
                        {
                            if (pTargetMD->GetMethodTable()->IsValueType())
                            {
                                m_argDescs[k].m_type = InterpreterType(CORINFO_TYPE_BYREF);
                            }
                            else
                            {
                                m_argDescs[k].m_type = InterpreterType(CORINFO_TYPE_CLASS);
                            }

                        }
                    }
                }

#endif // FEATURE_INSTANTIATINGSTUB_AS_IL
                if (m_argDescs[k].m_type == InterpreterType(CORINFO_TYPE_UNDEF))
                {
                    CORINFO_CLASS_HANDLE cls = comp->getMethodClass(methInfo->ftn);
                    DWORD attribs = comp->getClassAttribs(cls);
                    if (attribs & CORINFO_FLG_VALUECLASS)
                    {
                        m_argDescs[k].m_type = InterpreterType(CORINFO_TYPE_BYREF);
                    }
                    else
                    {
                        m_argDescs[k].m_type = InterpreterType(CORINFO_TYPE_CLASS);
                    }
                }
                m_argDescs[k].m_typeStackNormal = m_argDescs[k].m_type;
                m_argDescs[k].m_nativeOffset = argOffsets_[k];
                m_argDescs[k].m_directOffset = static_cast<short>(reinterpret_cast<intptr_t>(ArgSlotEndiannessFixup(directOffset, sizeof(void*))));
                directOffset++;
                k++;
            }

            // If there is a return buffer, it will appear next in the arguments list for a direct call.
            // Reserve its offset now, for use after the explicit arguments.
#if defined(HOST_ARM)
            // On ARM, for direct calls we always treat HFA return types as having ret buffs.
            // So figure out if we have an HFA return type.
            bool hasHFARetType =
                methInfo->args.retType == CORINFO_TYPE_VALUECLASS
                && (comp->getHFAType(methInfo->args.retTypeClass) != CORINFO_HFA_ELEM_NONE)
                && methInfo->args.getCallConv() != CORINFO_CALLCONV_VARARG;
#endif // defined(HOST_ARM)

            if (GetFlag<Flag_hasRetBuffArg>()
#if defined(HOST_ARM)
                // On ARM, for direct calls we always treat HFA return types as having ret buffs.
                || hasHFARetType
#endif // defined(HOST_ARM)
                )
            {
                directRetBuffOffset = static_cast<short>(reinterpret_cast<intptr_t>(ArgSlotEndiannessFixup(directOffset, sizeof(void*))));
                directOffset++;
            }
#if defined(HOST_AMD64)
            if (GetFlag<Flag_isVarArg>())
            {
                directVarArgOffset = static_cast<short>(reinterpret_cast<intptr_t>(ArgSlotEndiannessFixup(directOffset, sizeof(void*))));
                directOffset++;
            }
            if (GetFlag<Flag_hasGenericsContextArg>())
            {
                directTypeParamOffset = static_cast<short>(reinterpret_cast<intptr_t>(ArgSlotEndiannessFixup(directOffset, sizeof(void*))));
                directOffset++;
            }
#endif

            // Now record the argument types for the rest of the arguments.
            InterpreterType it;
            CORINFO_CLASS_HANDLE vcTypeRet;
            CORINFO_ARG_LIST_HANDLE argPtr = methInfo->args.args;
            for (; k < numSigArgsPlusThis; k++)
            {
                CorInfoTypeWithMod argTypWithMod = comp->getArgType(&methInfo->args, argPtr, &vcTypeRet);
                CorInfoType argType = strip(argTypWithMod);
                switch (argType)
                {
                case CORINFO_TYPE_VALUECLASS:
                case CORINFO_TYPE_REFANY: // Just a special case: vcTypeRet is handle for TypedReference in this case...
                    it = InterpreterType(comp, vcTypeRet);
                    break;
                default:
                    // Everything else is just encoded as a shifted CorInfoType.
                    it = InterpreterType(argType);
                    break;
                }
                m_argDescs[k].m_type = it;
                m_argDescs[k].m_typeStackNormal = it.StackNormalize();
                m_argDescs[k].m_nativeOffset = argOffsets_[k];
                // When invoking the interpreter directly, large value types are always passed by reference.
                if (it.IsLargeStruct(comp))
                {
                    m_argDescs[k].m_directOffset = static_cast<short>(reinterpret_cast<intptr_t>(ArgSlotEndiannessFixup(directOffset, sizeof(void*))));
                }
                else
                {
                    m_argDescs[k].m_directOffset = static_cast<short>(reinterpret_cast<intptr_t>(ArgSlotEndiannessFixup(directOffset, it.Size(comp))));
                }
                argPtr = comp->getArgNext(argPtr);
                directOffset++;
            }

            if (GetFlag<Flag_hasRetBuffArg>())
            {
                // The generic type context is an unmanaged pointer (native int).
                m_argDescs[k].m_type = InterpreterType(CORINFO_TYPE_BYREF);
                m_argDescs[k].m_typeStackNormal = m_argDescs[k].m_type;
                m_argDescs[k].m_nativeOffset = argOffsets_[k];
                m_argDescs[k].m_directOffset = directRetBuffOffset;
                k++;
            }

            if (GetFlag<Flag_hasGenericsContextArg>())
            {
                // The vararg cookie is an unmanaged pointer (native int).
                m_argDescs[k].m_type = InterpreterType(CORINFO_TYPE_NATIVEINT);
                m_argDescs[k].m_typeStackNormal = m_argDescs[k].m_type;
                m_argDescs[k].m_nativeOffset = argOffsets_[k];
                m_argDescs[k].m_directOffset = directTypeParamOffset;
                directOffset++;
                k++;
            }
            if (GetFlag<Flag_isVarArg>())
            {
                // The generic type context is an unmanaged pointer (native int).
                m_argDescs[k].m_type = InterpreterType(CORINFO_TYPE_NATIVEINT);
                m_argDescs[k].m_typeStackNormal = m_argDescs[k].m_type;
                m_argDescs[k].m_nativeOffset = argOffsets_[k];
                m_argDescs[k].m_directOffset = directVarArgOffset;
                k++;
            }
        }
        break;

    case IMAGE_CEE_CS_CALLCONV_C:
        NYI_INTERP("InterpreterMethodInfo::InitArgInfo -- IMAGE_CEE_CS_CALLCONV_C");
        break;

    case IMAGE_CEE_CS_CALLCONV_STDCALL:
        NYI_INTERP("InterpreterMethodInfo::InitArgInfo -- IMAGE_CEE_CS_CALLCONV_STDCALL");
        break;

    case IMAGE_CEE_CS_CALLCONV_THISCALL:
        NYI_INTERP("InterpreterMethodInfo::InitArgInfo -- IMAGE_CEE_CS_CALLCONV_THISCALL");
        break;

    case IMAGE_CEE_CS_CALLCONV_FASTCALL:
        NYI_INTERP("InterpreterMethodInfo::InitArgInfo -- IMAGE_CEE_CS_CALLCONV_FASTCALL");
        break;

    case CORINFO_CALLCONV_FIELD:
        NYI_INTERP("InterpreterMethodInfo::InitArgInfo -- CORINFO_CALLCONV_FIELD");
        break;

    case CORINFO_CALLCONV_LOCAL_SIG:
        NYI_INTERP("InterpreterMethodInfo::InitArgInfo -- CORINFO_CALLCONV_LOCAL_SIG");
        break;

    case CORINFO_CALLCONV_PROPERTY:
        NYI_INTERP("InterpreterMethodInfo::InitArgInfo -- CORINFO_CALLCONV_PROPERTY");
        break;

    case CORINFO_CALLCONV_UNMANAGED:
        NYI_INTERP("InterpreterMethodInfo::InitArgInfo -- CORINFO_CALLCONV_UNMANAGED");
        break;

    case CORINFO_CALLCONV_NATIVEVARARG:
        NYI_INTERP("InterpreterMethodInfo::InitArgInfo -- CORINFO_CALLCONV_NATIVEVARARG");
        break;

    default:
        _ASSERTE_ALL_BUILDS(false); // shouldn't get here
    }
}

InterpreterMethodInfo::~InterpreterMethodInfo()
{
    if (m_methodCache != NULL)
    {
        delete reinterpret_cast<ILOffsetToItemCache*>(m_methodCache);
    }
}

void InterpreterMethodInfo::AllocPinningBitsIfNeeded()
{
    if (m_localIsPinningRefBits != NULL)
        return;

    unsigned numChars = (m_numLocals + 7) / 8;
    m_localIsPinningRefBits = new char[numChars];
    for (unsigned i = 0; i < numChars; i++)
    {
        m_localIsPinningRefBits[i] = char(0);
    }
}


void InterpreterMethodInfo::SetPinningBit(unsigned locNum)
{
    _ASSERTE_MSG(locNum < m_numLocals, "Precondition");
    AllocPinningBitsIfNeeded();

    unsigned ind = locNum / 8;
    unsigned bitNum = locNum - (ind * 8);
    m_localIsPinningRefBits[ind] |= (1 << bitNum);
}

bool InterpreterMethodInfo::GetPinningBit(unsigned locNum)
{
    _ASSERTE_MSG(locNum < m_numLocals, "Precondition");
    if (m_localIsPinningRefBits == NULL)
        return false;

    unsigned ind = locNum / 8;
    unsigned bitNum = locNum - (ind * 8);
    return (m_localIsPinningRefBits[ind] & (1 << bitNum)) != 0;
}

void Interpreter::ArgState::AddArg(unsigned canonIndex, short numSlots, bool noReg, bool twoSlotAlign)
{
#if defined(HOST_AMD64)
    _ASSERTE(!noReg);
    _ASSERTE(!twoSlotAlign);
    AddArgAmd64(canonIndex, numSlots, /*isFloatingType*/false);
    return;
#else // !HOST_AMD64
#if defined(HOST_X86) || defined(HOST_ARM64)
    _ASSERTE(!twoSlotAlign); // Shouldn't use this flag on x86 (it wouldn't work right in the stack, at least).
#endif
    // If the argument requires two-slot alignment, make sure we have it.  This is the
    // ARM model: both in regs and on the stack.
    if (twoSlotAlign)
    {
        if (!noReg && numRegArgs < NumberOfIntegerRegArgs())
        {
            if ((numRegArgs % 2) != 0)
            {
                numRegArgs++;
            }
        }
        else
        {
            if ((callerArgStackSlots % 2) != 0)
            {
                callerArgStackSlots++;
            }
        }
    }

#if defined(HOST_ARM64)
    // On ARM64 we're not going to place an argument 'partially' on the stack
    // if all slots fits into registers, they go into registers, otherwise they go into stack.
    if (!noReg && numRegArgs+numSlots <= NumberOfIntegerRegArgs())
#else
    if (!noReg && numRegArgs < NumberOfIntegerRegArgs())
#endif
    {
        argIsReg[canonIndex] = ARS_IntReg;
        argOffsets[canonIndex] = numRegArgs * sizeof(void*);
        numRegArgs += numSlots;
        // If we overflowed the regs, we consume some stack arg space.
        if (numRegArgs > NumberOfIntegerRegArgs())
        {
            callerArgStackSlots += (numRegArgs - NumberOfIntegerRegArgs());
        }
    }
    else
    {
#if defined(HOST_X86)
        // On X86, stack args are pushed in order.  We will add the total size of the arguments to this offset,
        // so we set this to a negative number relative to the SP before the first arg push.
        callerArgStackSlots += numSlots;
        ClrSafeInt<short> offset(-callerArgStackSlots);
#elif defined(HOST_ARM) || defined(HOST_ARM64)
        // On ARM, args are pushed in *reverse* order.  So we will create an offset relative to the address
        // of the first stack arg; later, we will add the size of the non-stack arguments.
        ClrSafeInt<short> offset(callerArgStackSlots);
#elif defined(HOST_LOONGARCH64)
        callerArgStackSlots += numSlots;
        ClrSafeInt<short> offset(-callerArgStackSlots);
#elif defined(HOST_RISCV64)
        callerArgStackSlots += numSlots;
        ClrSafeInt<short> offset(-callerArgStackSlots);
#endif
        offset *= static_cast<short>(sizeof(void*));
        _ASSERTE(!offset.IsOverflow());
        argOffsets[canonIndex] = offset.Value();
#if defined(HOST_ARM) || defined(HOST_ARM64)
        callerArgStackSlots += numSlots;
#endif
    }
#endif // !HOST_AMD64
}

#if defined(HOST_AMD64)

#if defined(UNIX_AMD64_ABI)
void Interpreter::ArgState::AddArgAmd64(unsigned canonIndex, unsigned short numSlots, bool isFloatingType)
{
    int regSlots = numFPRegArgSlots + numRegArgs;
    if (isFloatingType && numFPRegArgSlots + 1 < MaxNumFPRegArgSlots)
    {
        _ASSERTE(numSlots == 1);
        argIsReg[canonIndex]   = ARS_FloatReg;
        argOffsets[canonIndex] = regSlots * sizeof(void*);
        fpArgsUsed |= (0x1 << regSlots);
        numFPRegArgSlots += 1;
        return;
    }
    else if (numSlots < 3 && (numRegArgs + numSlots <= NumberOfIntegerRegArgs()))
    {
        argIsReg[canonIndex]   = ARS_IntReg;
        argOffsets[canonIndex] = regSlots * sizeof(void*);
        numRegArgs += numSlots;
    }
    else
    {
        argIsReg[canonIndex] = ARS_NotReg;
        ClrSafeInt<short> offset(callerArgStackSlots * sizeof(void*));
        _ASSERTE(!offset.IsOverflow());
        argOffsets[canonIndex] = offset.Value();
        callerArgStackSlots += numSlots;
    }
}
#else
// Windows AMD64 calling convention allows any type that can be contained in 64 bits to be passed in registers,
// if not contained or they are of a size not a power of 2, then they are passed by reference on the stack.
// RCX, RDX, R8, R9 are the int arg registers. XMM0-3 overlap with the integer registers and are used
// for floating point arguments.
void Interpreter::ArgState::AddArgAmd64(unsigned canonIndex, unsigned short numSlots, bool isFloatingType)
{
    // If floating type and there are slots use a float reg slot.
    if (isFloatingType && (numFPRegArgSlots < MaxNumFPRegArgSlots))
    {
        _ASSERTE(numSlots == 1);
        argIsReg[canonIndex] = ARS_FloatReg;
        argOffsets[canonIndex] = numFPRegArgSlots * sizeof(void*);
        fpArgsUsed |= (0x1 << (numFPRegArgSlots + 1));
        numFPRegArgSlots += 1;
        numRegArgs += 1; // Increment int reg count due to shadowing.
        return;
    }

    // If we have an integer/aligned-struct arg or a reference of a struct that got copied on
    // to the stack, it would go into a register or a stack slot.
    if (numRegArgs != NumberOfIntegerRegArgs())
    {
        argIsReg[canonIndex] = ARS_IntReg;
        argOffsets[canonIndex] = numRegArgs * sizeof(void*);
        numRegArgs += 1;
        numFPRegArgSlots += 1; // Increment FP reg count due to shadowing.
    }
    else
    {
        argIsReg[canonIndex] = ARS_NotReg;
        ClrSafeInt<short> offset(callerArgStackSlots * sizeof(void*));
        _ASSERTE(!offset.IsOverflow());
        argOffsets[canonIndex] = offset.Value();
        callerArgStackSlots += 1;
    }
}
#endif //UNIX_AMD64_ABI
#endif

void Interpreter::ArgState::AddFPArg(unsigned canonIndex, unsigned short numSlots, bool twoSlotAlign)
{
#if defined(HOST_AMD64)
    _ASSERTE(!twoSlotAlign);
    _ASSERTE(numSlots == 1);
    AddArgAmd64(canonIndex, numSlots, /*isFloatingType*/ true);
#elif defined(HOST_X86)
    _ASSERTE(false);  // Don't call this on x86; we pass all FP on the stack.
#elif defined(HOST_ARM)
    // We require "numSlots" alignment.
    _ASSERTE(numFPRegArgSlots + numSlots <= MaxNumFPRegArgSlots);
    argIsReg[canonIndex] = ARS_FloatReg;

    if (twoSlotAlign)
    {
        // If we require two slot alignment, the number of slots must be a multiple of two.
        _ASSERTE((numSlots % 2) == 0);

        // Skip a slot if necessary.
        if ((numFPRegArgSlots % 2) != 0)
        {
            numFPRegArgSlots++;
        }
        // We always use new slots for two slot aligned args precision...
        argOffsets[canonIndex] = numFPRegArgSlots * sizeof(void*);
        for (unsigned short i = 0; i < numSlots/2; i++)
        {
            fpArgsUsed |= (0x3 << (numFPRegArgSlots + i));
        }
        numFPRegArgSlots += numSlots;
    }
    else
    {
        if (numSlots == 1)
        {
            // A single-precision (float) argument.  We must do "back-filling" where possible, searching
            // for previous unused registers.
            unsigned slot = 0;
            while (slot < 32 && (fpArgsUsed & (1 << slot))) slot++;
            _ASSERTE(slot < 32); // Search succeeded.
            _ASSERTE(slot <= numFPRegArgSlots);  // No bits at or above numFPRegArgSlots are set (regs used).
            argOffsets[canonIndex] = slot * sizeof(void*);
            fpArgsUsed |= (0x1 << slot);
            if (slot == numFPRegArgSlots)
                numFPRegArgSlots += numSlots;
        }
        else
        {
            // We can always allocate at after the last used slot.
            argOffsets[numFPRegArgSlots] = numFPRegArgSlots * sizeof(void*);
            for (unsigned i = 0; i < numSlots; i++)
            {
                fpArgsUsed |= (0x1 << (numFPRegArgSlots + i));
            }
            numFPRegArgSlots += numSlots;
        }
    }
#elif defined(HOST_ARM64)

    _ASSERTE(numFPRegArgSlots + numSlots <= MaxNumFPRegArgSlots);
    _ASSERTE(!twoSlotAlign);
    argIsReg[canonIndex] = ARS_FloatReg;

    argOffsets[canonIndex] = numFPRegArgSlots * sizeof(void*);
    for (unsigned i = 0; i < numSlots; i++)
    {
        fpArgsUsed |= (0x1 << (numFPRegArgSlots + i));
    }
    numFPRegArgSlots += numSlots;

#elif defined(HOST_LOONGARCH64)

    assert(numFPRegArgSlots + numSlots <= MaxNumFPRegArgSlots);
    assert(!twoSlotAlign);
    argIsReg[canonIndex] = ARS_FloatReg;

    argOffsets[canonIndex] = numFPRegArgSlots * sizeof(void*);
    for (unsigned i = 0; i < numSlots; i++)
    {
        fpArgsUsed |= (0x1 << (numFPRegArgSlots + i));
    }
    numFPRegArgSlots += numSlots;
#elif defined(HOST_RISCV64)
    assert(numFPRegArgSlots + numSlots <= MaxNumFPRegArgSlots);
    assert(!twoSlotAlign);
    argIsReg[canonIndex] = ARS_FloatReg;

    argOffsets[canonIndex] = numFPRegArgSlots * sizeof(void*);
    for (unsigned i = 0; i < numSlots; i++)
    {
        fpArgsUsed |= (0x1 << (numFPRegArgSlots + i));
    }
    numFPRegArgSlots += numSlots;
#else
#error "Unsupported architecture"
#endif
}


// static
CorJitResult Interpreter::GenerateInterpreterStub(CEEInfo* comp,
                                                  CORINFO_METHOD_INFO* info,
                                                  /*OUT*/ BYTE **nativeEntry,
                                                  /*OUT*/ ULONG *nativeSizeOfCode,
                                                  InterpreterMethodInfo** ppInterpMethodInfo,
                                                  bool jmpCall)
{
    //
    // First, ensure that the compiler-specific statics are initialized.
    //

    InitializeCompilerStatics(comp);

    //
    // Next, use switches and IL scanning to determine whether to interpret this method.
    //

#if INTERP_TRACING
#define TRACE_SKIPPED(cls, meth, reason)                                                \
    if (s_DumpInterpreterStubsFlag.val(CLRConfig::INTERNAL_DumpInterpreterStubs)) {     \
        fprintf(GetLogFile(), "Skipping %s:%s (%s).\n", cls, meth, reason);             \
    }
#else
#define TRACE_SKIPPED(cls, meth, reason)
#endif


    // If jmpCall, we only need to do computations involving method info.
    if (!jmpCall)
    {
        const char* clsName;
        const char* methName = comp->getMethodName(info->ftn, &clsName);
        if (   !s_InterpretMeths.contains(methName, clsName, info->args.pSig)
            || s_InterpretMethsExclude.contains(methName, clsName, info->args.pSig))
        {
            TRACE_SKIPPED(clsName, methName, "not in set of methods to interpret");
            return CORJIT_SKIPPED;
        }

        unsigned methHash = comp->getMethodHash(info->ftn);
        if (   methHash < s_InterpretMethHashMin.val(CLRConfig::INTERNAL_InterpreterMethHashMin)
            || methHash > s_InterpretMethHashMax.val(CLRConfig::INTERNAL_InterpreterMethHashMax))
        {
            TRACE_SKIPPED(clsName, methName, "hash not within range to interpret");
            return CORJIT_SKIPPED;
        }

        MethodDesc* pMD = reinterpret_cast<MethodDesc*>(info->ftn);

#if !INTERP_ILSTUBS
        if (pMD->IsILStub())
        {
            TRACE_SKIPPED(clsName, methName, "interop stubs not supported");
            return CORJIT_SKIPPED;
        }
        else
#endif // !INTERP_ILSTUBS

        if (!s_InterpreterDoLoopMethods && MethodMayHaveLoop(info->ILCode, info->ILCodeSize))
        {
            TRACE_SKIPPED(clsName, methName, "has loop, not interpreting loop methods.");
            return CORJIT_SKIPPED;
        }

        s_interpreterStubNum++;

#if INTERP_TRACING
        if (s_interpreterStubNum < s_InterpreterStubMin.val(CLRConfig::INTERNAL_InterpreterStubMin)
                || s_interpreterStubNum > s_InterpreterStubMax.val(CLRConfig::INTERNAL_InterpreterStubMax))
        {
            TRACE_SKIPPED(clsName, methName, "stub num not in range, not interpreting.");
            return CORJIT_SKIPPED;
        }

        if (s_DumpInterpreterStubsFlag.val(CLRConfig::INTERNAL_DumpInterpreterStubs))
        {
            unsigned hash = comp->getMethodHash(info->ftn);
            fprintf(GetLogFile(), "Generating interpretation stub (# %d = 0x%x, hash = 0x%x) for %s:%s.\n",
                    s_interpreterStubNum, s_interpreterStubNum, hash, clsName, methName);
            fflush(GetLogFile());
        }
#endif
    }

    //
    // Finally, generate an interpreter entry-point stub.
    //

    // @TODO: this structure clearly needs some sort of lifetime management.  It is the moral equivalent
    // of compiled code, and should be associated with an app domain.  In addition, when I get to it, we should
    // delete it when/if we actually compile the method.  (Actually, that's complicated, since there may be
    // VSD stubs still bound to the interpreter stub.  The check there will get to the jitted code, but we want
    // to eventually clean those up at some safe point...)
    InterpreterMethodInfo* interpMethInfo = new InterpreterMethodInfo(comp, info);
    if (ppInterpMethodInfo != nullptr)
    {
        *ppInterpMethodInfo = interpMethInfo;
    }
    interpMethInfo->m_stubNum = s_interpreterStubNum;
    MethodDesc* methodDesc = reinterpret_cast<MethodDesc*>(info->ftn);
    if (!jmpCall)
    {
        interpMethInfo = RecordInterpreterMethodInfoForMethodHandle(info->ftn, interpMethInfo);
    }

#if FEATURE_INTERPRETER_DEADSIMPLE_OPT
    unsigned offsetOfLd;
    if (IsDeadSimpleGetter(comp, methodDesc, &offsetOfLd))
    {
        interpMethInfo->SetFlag<InterpreterMethodInfo::Flag_methIsDeadSimpleGetter>(true);
        if (offsetOfLd == ILOffsetOfLdFldInDeadSimpleInstanceGetterDbg)
        {
            interpMethInfo->SetFlag<InterpreterMethodInfo::Flag_methIsDeadSimpleGetterIsDbgForm>(true);
        }
        else
        {
            _ASSERTE(offsetOfLd == ILOffsetOfLdFldInDeadSimpleInstanceGetterOpt);
        }
    }
#endif // FEATURE_INTERPRETER_DEADSIMPLE_OPT

    // Used to initialize the arg offset information.
    Stub* stub = NULL;

    // We assume that the stack contains (with addresses growing upwards, assuming a downwards-growing stack):
    //
    //    [Non-reg arg N-1]
    //    ...
    //    [Non-reg arg <# of reg args>]
    //    [return PC]
    //
    // Then push the register args to get:
    //
    //    [Non-reg arg N-1]
    //    ...
    //    [Non-reg arg <# of reg args>]
    //    [return PC]
    //    [reg arg <# of reg args>-1]
    //    ...
    //    [reg arg 0]
    //
    // Pass the address of this argument array, and the MethodDesc pointer for the method, as arguments to
    // Interpret.
    //
    // So the structure of the code will look like this (in the non-ILstub case):
    //
#if defined(HOST_X86) || defined(HOST_AMD64)
    // push ebp
    // mov ebp, esp
    // [if there are register arguments in ecx or edx, push them]
    // ecx := addr of InterpretMethodInfo for the method to be interpreted.
    // edx = esp  /*pointer to argument structure*/
    // call to Interpreter::InterpretMethod
    // [if we pushed register arguments, increment esp by the right amount.]
    // pop ebp
    // ret <n>  ; where <n> is the number of argument stack slots in the call to the stub.
#elif defined (HOST_ARM)
    // TODO.
#endif

    // TODO: much of the interpreter stub code should be is shareable.  In the non-IL stub case,
    // at least, we could have a small per-method stub that puts the address of the method-specific
    // InterpreterMethodInfo into eax, and then branches to a shared part.  Probably we would want to
    // always push all integer args on x86, as we do already on ARM.  On ARM, we'd need several versions
    // of the shared stub, for different numbers of floating point register args, cross different kinds of
    // HFA return values.  But these could still be shared, and the per-method stub would decide which of
    // these to target.
    //
    // In the IL stub case, which uses eax, it would be problematic to do this sharing.

    StubLinkerCPU sl;
    MethodDesc* pMD = reinterpret_cast<MethodDesc*>(info->ftn);
    if (!jmpCall)
    {
        sl.Init();
#if defined(HOST_X86) || defined(HOST_AMD64)
#if defined(HOST_X86)
        sl.X86EmitPushReg(kEBP);
        sl.X86EmitMovRegReg(kEBP, static_cast<X86Reg>(kESP_Unsafe));
#endif
#elif defined(HOST_ARM)
        // On ARM we use R12 as a "scratch" register -- callee-trashed, not used
        // for arguments.
        ThumbReg r11 = ThumbReg(11);
        ThumbReg r12 = ThumbReg(12);

#elif defined(HOST_ARM64)
        // x8 through x15 are scratch registers on ARM64.
        IntReg x8 = IntReg(8);
        IntReg x9 = IntReg(9);
#error unsupported platform
#endif
    }

    MetaSig sig(methodDesc);

    unsigned totalArgs = info->args.numArgs;
    unsigned sigArgsPlusThis = totalArgs;
    bool hasThis = false;
    bool hasRetBuff = false;
    bool isVarArg = false;
    bool hasGenericsContextArg = false;

    // Below, we will increment "totalArgs" for any of the "this" argument,
    // a ret buff argument, and/or a generics context argument.
    //
    // There will be four arrays allocated below, each with this increased "totalArgs" elements:
    // argOffsets, argIsReg, argPerm, and, later, m_argDescs.
    //
    // They will be indexed in the order (0-based, [] indicating optional)
    //
    //    [this] sigArgs [retBuff] [VASigCookie] [genCtxt]
    //
    // We will call this "canonical order".  It is architecture-independent, and
    // does not necessarily correspond to the architecture-dependent physical order
    // in which the registers are actually passed.  (That's actually the purpose of
    // "argPerm": to record the correspondence between canonical order and physical
    // order.)  We could have chosen any order for the first three of these, but it's
    // simplest to let m_argDescs have all the passed IL arguments passed contiguously
    // at the beginning, allowing it to be indexed by IL argument number.

    int genericsContextArgIndex = 0;
    int retBuffArgIndex = 0;
    int vaSigCookieIndex = 0;

    if (sig.HasThis())
    {
        _ASSERTE(info->args.callConv & CORINFO_CALLCONV_HASTHIS);
        hasThis = true;
        totalArgs++; sigArgsPlusThis++;
    }

    if (methodDesc->HasRetBuffArg())
    {
        hasRetBuff = true;
        retBuffArgIndex = totalArgs;
        totalArgs++;
    }

    if (sig.GetCallingConventionInfo() & CORINFO_CALLCONV_VARARG)
    {
        isVarArg = true;
        vaSigCookieIndex = totalArgs;
        totalArgs++;
    }

    if (sig.GetCallingConventionInfo() & CORINFO_CALLCONV_PARAMTYPE)
    {
        _ASSERTE(info->args.callConv & CORINFO_CALLCONV_PARAMTYPE);
        hasGenericsContextArg = true;
        genericsContextArgIndex = totalArgs;
        totalArgs++;
    }

    // The non-this sig args have indices starting after these.

    // We will first encode the arg offsets as *negative* offsets from the address above the first
    // stack arg, and later add in the total size of the stack args to get a positive offset.
    // The first sigArgsPlusThis elements are the offsets of the IL-addressable arguments.  After that,
    // there may be up to two more: generics context arg, if present, and return buff pointer, if present.
    // (Note that the latter is actually passed after the "this" pointer, or else first if no "this" pointer
    // is present.  We re-arrange to preserve the easy IL-addressability.)
    ArgState argState(totalArgs);

    // This is the permutation that translates from an index in the argOffsets/argIsReg arrays to
    // the platform-specific order in which the arguments are passed.
    unsigned* argPerm = new unsigned[totalArgs];

    // The number of register argument slots we end up pushing.
    unsigned short regArgsFound = 0;

    unsigned physArgIndex = 0;

#if defined(HOST_ARM)
    // The stub linker has a weird little limitation: all stubs it's used
    // for on ARM push some callee-saved register, so the unwind info
    // code was written assuming at least one would be pushed.  I don't know how to
    // fix it, so I'm meeting this requirement, by pushing one callee-save.
#define STUB_LINK_EMIT_PROLOG_REQUIRES_CALLEE_SAVE_PUSH 1

#if STUB_LINK_EMIT_PROLOG_REQUIRES_CALLEE_SAVE_PUSH
    const int NumberOfCalleeSaveRegsToPush = 1;
#else
    const int NumberOfCalleeSaveRegsToPush = 0;
#endif
    // The "1" here is for the return address.
    const int NumberOfFixedPushes = 1 + NumberOfCalleeSaveRegsToPush;
#elif defined(HOST_ARM64)
    // FP, LR
    const int NumberOfFixedPushes = 2;
#endif

#if defined(FEATURE_HFA)
#if defined(HOST_ARM) || defined(HOST_ARM64)
    // On ARM, a non-retBuffArg method that returns a struct type might be an HFA return.  Figure
    // that out.
    unsigned HFARetTypeSize = 0;
#endif
#if defined(HOST_ARM64)
    unsigned cHFAVars   = 0;
#endif
    if (info->args.retType == CORINFO_TYPE_VALUECLASS
        && (comp->getHFAType(info->args.retTypeClass) != CORINFO_HFA_ELEM_NONE)
        && info->args.getCallConv() != CORINFO_CALLCONV_VARARG)
    {
        HFARetTypeSize = getClassSize(info->args.retTypeClass);
#if defined(HOST_ARM)
        // Round up to a double boundary;
        HFARetTypeSize = ((HFARetTypeSize+ sizeof(double) - 1) / sizeof(double)) * sizeof(double);
#elif defined(HOST_ARM64)
        // We don't need to round it up to double. Unlike ARM, whether it's a float or a double each field will
        // occupy one slot. We'll handle the stack alignment in the prolog where we have all the information about
        // what is going to be pushed on the stack.
        // Instead on ARM64 we'll need to know how many slots we'll need.
        // for instance a VT with two float fields will have the same size as a VT with 1 double field. (ARM64TODO: Verify it)
        // It works on ARM because the overlapping layout of the floating point registers
        // but it won't work on ARM64.
        cHFAVars = (comp->getHFAType(info->args.retTypeClass) == CORINFO_HFA_ELEM_FLOAT) ? HFARetTypeSize/sizeof(float) : HFARetTypeSize/sizeof(double);
#endif
    }

#endif // defined(FEATURE_HFA)

    _ASSERTE_MSG((info->args.callConv & (CORINFO_CALLCONV_EXPLICITTHIS)) == 0,
        "Don't yet handle EXPLICITTHIS calling convention modifier.");

    switch (info->args.callConv & CORINFO_CALLCONV_MASK)
    {
    case CORINFO_CALLCONV_DEFAULT:
    case CORINFO_CALLCONV_VARARG:
        {
            unsigned firstSigArgIndex = 0;
            if (hasThis)
            {
                argPerm[0] = physArgIndex; physArgIndex++;
                argState.AddArg(0);
                firstSigArgIndex++;
            }

            if (hasRetBuff)
            {
                argPerm[retBuffArgIndex] = physArgIndex; physArgIndex++;
                argState.AddArg(retBuffArgIndex);
            }

            if (isVarArg)
            {
                argPerm[vaSigCookieIndex] = physArgIndex; physArgIndex++;
                interpMethInfo->m_varArgHandleArgNum = vaSigCookieIndex;
                argState.AddArg(vaSigCookieIndex);
            }

#if defined(HOST_ARM) || defined(HOST_AMD64) || defined(HOST_ARM64)
            // Generics context comes before args on ARM.  Would be better if I factored this out as a call,
            // to avoid large swatches of duplicate code.
            if (hasGenericsContextArg)
            {
                argPerm[genericsContextArgIndex] = physArgIndex; physArgIndex++;
                argState.AddArg(genericsContextArgIndex);
            }
#endif // HOST_ARM || HOST_AMD64 || HOST_ARM64

            CORINFO_ARG_LIST_HANDLE argPtr = info->args.args;
            // Some arguments are have been passed in registers, some in memory. We must generate code that
            // moves the register arguments to memory, and determines a pointer into the stack from which all
            // the arguments can be accessed, according to the offsets in "argOffsets."
            //
            // In the first pass over the arguments, we will label and count the register arguments, and
            // initialize entries in "argOffsets" for the non-register arguments -- relative to the SP at the
            // time of the call. Then when we have counted the number of register arguments, we will adjust
            // the offsets for the non-register arguments to account for those. Then, in the second pass, we
            // will push the register arguments on the stack, and capture the final stack pointer value as
            // the argument vector pointer.
            CORINFO_CLASS_HANDLE vcTypeRet;
            // This iteration starts at the first signature argument, and iterates over all the
            // canonical indices for the signature arguments.
            for (unsigned k = firstSigArgIndex; k < sigArgsPlusThis; k++)
            {
                argPerm[k] = physArgIndex; physArgIndex++;

                CorInfoTypeWithMod argTypWithMod = comp->getArgType(&info->args, argPtr, &vcTypeRet);
                CorInfoType argType = strip(argTypWithMod);
                switch (argType)
                {
                case CORINFO_TYPE_UNDEF:
                case CORINFO_TYPE_VOID:
                case CORINFO_TYPE_VAR:
                    _ASSERTE_ALL_BUILDS(false);  // Should not happen;
                    break;

                    // One integer slot arguments:
                case CORINFO_TYPE_BOOL:
                case CORINFO_TYPE_CHAR:
                case CORINFO_TYPE_BYTE:
                case CORINFO_TYPE_UBYTE:
                case CORINFO_TYPE_SHORT:
                case CORINFO_TYPE_USHORT:
                case CORINFO_TYPE_INT:
                case CORINFO_TYPE_UINT:
                case CORINFO_TYPE_NATIVEINT:
                case CORINFO_TYPE_NATIVEUINT:
                case CORINFO_TYPE_BYREF:
                case CORINFO_TYPE_CLASS:
                case CORINFO_TYPE_STRING:
                case CORINFO_TYPE_PTR:
                    argState.AddArg(k);
                    break;

                    // Two integer slot arguments.
                case CORINFO_TYPE_LONG:
                case CORINFO_TYPE_ULONG:
#if defined(HOST_X86)
                    // Longs are always passed on the stack -- with no obvious alignment.
                    argState.AddArg(k, 2, /*noReg*/true);
#elif defined(HOST_ARM)
                    // LONGS have 2-reg alignment; inc reg if necessary.
                    argState.AddArg(k, 2, /*noReg*/false, /*twoSlotAlign*/true);
#elif defined(HOST_AMD64) || defined(HOST_ARM64) || defined(HOST_LOONGARCH64) || defined(HOST_RISCV64)
                    argState.AddArg(k);
#else
#error unknown platform
#endif
                    break;

                    // One float slot args:
                case CORINFO_TYPE_FLOAT:
#if defined(HOST_X86)
                    argState.AddArg(k, 1, /*noReg*/true);
#elif defined(HOST_ARM)
                    argState.AddFPArg(k, 1, /*twoSlotAlign*/false);
#elif defined(HOST_AMD64) || defined(HOST_ARM64) || defined(HOST_LOONGARCH64) || defined(HOST_RISCV64)
                    argState.AddFPArg(k, 1, false);
#else
#error unknown platform
#endif
                    break;

                    // Two float slot args
                case CORINFO_TYPE_DOUBLE:
#if defined(HOST_X86)
                    argState.AddArg(k, 2, /*noReg*/true);
#elif defined(HOST_ARM)
                    argState.AddFPArg(k, 2, /*twoSlotAlign*/true);
#elif defined(HOST_AMD64) || defined(HOST_ARM64) || defined(HOST_LOONGARCH64) || defined(HOST_RISCV64)
                    argState.AddFPArg(k, 1, false);
#else
#error unknown platform
#endif
                    break;

                    // Value class args:
                case CORINFO_TYPE_VALUECLASS:
                case CORINFO_TYPE_REFANY:
                    {
                        unsigned sz = getClassSize(vcTypeRet);
                        unsigned szSlots = max(1, sz / sizeof(void*));
#if defined(HOST_X86)
                        argState.AddArg(k, static_cast<short>(szSlots), /*noReg*/true);
#elif defined(HOST_AMD64)
                        argState.AddArg(k, static_cast<short>(szSlots));
#elif defined(HOST_ARM) || defined(HOST_ARM64)
                        // TODO: handle Vector64, Vector128 types
                        CorInfoHFAElemType hfaType = comp->getHFAType(vcTypeRet);
                        if (CorInfoTypeIsFloatingPoint(hfaType))
                        {
                            argState.AddFPArg(k, szSlots,
#if defined(HOST_ARM)
                                    /*twoSlotAlign*/ (hfaType == CORINFO_HFA_ELEM_DOUBLE)
#elif defined(HOST_ARM64)
                                    /*twoSlotAlign*/ false // unlike ARM32 FP args always consume 1 slot on ARM64
#endif
                                    );
                        }
                        else
                        {
                            unsigned align = comp->getClassAlignmentRequirement(vcTypeRet, FALSE);
                            argState.AddArg(k, static_cast<short>(szSlots), /*noReg*/false,
#if defined(HOST_ARM)
                                    /*twoSlotAlign*/ (align == 8)
#elif defined(HOST_ARM64)
                                    /*twoSlotAlign*/ false
#endif
                                    );
                        }
#elif defined(HOST_LOONGARCH64)
                        argState.AddArg(k, static_cast<short>(szSlots));
#elif defined(HOST_RISCV64)
                        argState.AddArg(k, static_cast<short>(szSlots));
#else
#error unknown platform
#endif
                    }
                    break;


                default:
                    _ASSERTE_MSG(false, "should not reach here, unknown arg type");
                }
                argPtr = comp->getArgNext(argPtr);
            }

#if defined(HOST_X86)
            // Generics context comes last on HOST_X86.  Would be better if I factored this out as a call,
            // to avoid large swatches of duplicate code.
            if (hasGenericsContextArg)
            {
                argPerm[genericsContextArgIndex] = physArgIndex; physArgIndex++;
                argState.AddArg(genericsContextArgIndex);
            }

            // Now we have counted the number of register arguments, so we can update the offsets for the
            // non-register arguments.  "+ 2" below is to account for the return address from the call, and
            // pushing of EBP.
            unsigned short stackArgBaseOffset = (argState.numRegArgs + 2 + argState.callerArgStackSlots) * sizeof(void*);
            unsigned       intRegArgBaseOffset = 0;

#elif defined(HOST_ARM)

            // We're choosing to always push all arg regs on ARM -- this is the only option
            // that ThumbEmitProlog currently gives.
            argState.numRegArgs = 4;

            // On ARM, we push the (integer) arg regs before we push the return address, so we don't add an
            // extra constant.  And the offset is the address of the last pushed argument, which is the first
            // stack argument in signature order.

            // Round up to a double boundary...
            unsigned       fpStackSlots = ((argState.numFPRegArgSlots + 1) / 2) * 2;
            unsigned       intRegArgBaseOffset = (fpStackSlots + NumberOfFixedPushes) * sizeof(void*);
            unsigned short stackArgBaseOffset = intRegArgBaseOffset + (argState.numRegArgs) * sizeof(void*);
#elif defined(HOST_ARM64)

            // See StubLinkerCPU::EmitProlog for the layout of the stack
            unsigned       intRegArgBaseOffset = (argState.numFPRegArgSlots) * sizeof(void*);
            unsigned short stackArgBaseOffset = (unsigned short) ((argState.numRegArgs + argState.numFPRegArgSlots) * sizeof(void*));
#elif defined(UNIX_AMD64_ABI)
            unsigned       intRegArgBaseOffset = 0;
            unsigned short stackArgBaseOffset = (2 + argState.numRegArgs + argState.numFPRegArgSlots) * sizeof(void*);
#elif defined(HOST_AMD64)
            unsigned short stackArgBaseOffset = (argState.numRegArgs) * sizeof(void*);
#elif defined(HOST_LOONGARCH64)
            // See StubLinkerCPU::EmitProlog for the layout of the stack
            unsigned       intRegArgBaseOffset = (argState.numFPRegArgSlots) * sizeof(void*);
            unsigned short stackArgBaseOffset = (unsigned short) ((argState.numRegArgs + argState.numFPRegArgSlots) * sizeof(void*));
#elif defined(HOST_RISCV64)
            unsigned       intRegArgBaseOffset = (argState.numFPRegArgSlots) * sizeof(void*);
            unsigned short stackArgBaseOffset = (unsigned short) ((argState.numRegArgs + argState.numFPRegArgSlots) * sizeof(void*));
#else
#error unsupported platform
#endif

#if defined(HOST_ARM)
            WORD regArgMask = 0;
#endif // defined(HOST_ARM)
            // argPerm maps from an index into the argOffsets/argIsReg arrays to
            // the order that the arguments are passed.
            unsigned* argPermInverse = new unsigned[totalArgs];
            for (unsigned t = 0; t < totalArgs; t++)
            {
                argPermInverse[argPerm[t]] = t;
            }

            for (unsigned kk = 0; kk < totalArgs; kk++)
            {
                // Let "k" be the index of the kk'th input in the argOffsets and argIsReg arrays.
                // To compute "k" we need to invert argPerm permutation -- determine the "k" such
                // that argPerm[k] == kk.
                unsigned k = argPermInverse[kk];

                _ASSERTE(k < totalArgs);

                if (argState.argIsReg[k] == ArgState::ARS_IntReg)
                {
                    regArgsFound++;
                    // If any int reg args are used on ARM, we push them all (in ThumbEmitProlog)
#if defined(HOST_X86)
                    if (regArgsFound == 1)
                    {
                        if (!jmpCall) { sl.X86EmitPushReg(kECX); }
                        argState.argOffsets[k] = (argState.numRegArgs - regArgsFound)*sizeof(void*);  // General form, good for general # of reg args.
                    }
                    else
                    {
                        _ASSERTE(regArgsFound == 2);
                        if (!jmpCall) { sl.X86EmitPushReg(kEDX); }
                        argState.argOffsets[k] = (argState.numRegArgs - regArgsFound)*sizeof(void*);
                    }
#elif defined(HOST_ARM) || defined(HOST_ARM64) || defined(UNIX_AMD64_ABI)
                    argState.argOffsets[k] += intRegArgBaseOffset;
#elif defined(HOST_AMD64)
                    // First home the register arguments in the stack space allocated by the caller.
                    // Refer to Stack Allocation on x64 [http://msdn.microsoft.com/en-US/library/ew5tede7(v=vs.80).aspx]
                    X86Reg argRegs[] = { kECX, kEDX, kR8, kR9 };
                    if (!jmpCall) { sl.X86EmitIndexRegStoreRSP(regArgsFound * sizeof(void*), argRegs[regArgsFound - 1]); }
                    argState.argOffsets[k] = (regArgsFound - 1) * sizeof(void*);
#elif defined(HOST_LOONGARCH64)
                    argState.argOffsets[k] += intRegArgBaseOffset;
#elif defined(HOST_RISCV64)
                    argState.argOffsets[k] += intRegArgBaseOffset;
#else
#error unsupported platform
#endif
                }
#if defined(HOST_AMD64) && !defined(UNIX_AMD64_ABI)
                else if (argState.argIsReg[k] == ArgState::ARS_FloatReg)
                {
                    // Increment regArgsFound since float/int arguments have overlapping registers.
                    regArgsFound++;
                    // Home the float arguments.
                    X86Reg argRegs[] = { kXMM0, kXMM1, kXMM2, kXMM3 };
                    if (!jmpCall) { sl.X64EmitMovSDToMem(argRegs[regArgsFound - 1], static_cast<X86Reg>(kESP_Unsafe), regArgsFound * sizeof(void*)); }
                    argState.argOffsets[k] = (regArgsFound - 1) * sizeof(void*);
                }
#endif
                else if (argState.argIsReg[k] == ArgState::ARS_NotReg)
                {
                    argState.argOffsets[k] += stackArgBaseOffset;
                }
                // So far, x86 doesn't have any FP reg args, and ARM and ARM64 puts them at offset 0, so no
                // adjustment is necessary (yet) for arguments passed in those registers.
            }
            delete[] argPermInverse;
        }
        break;

    case IMAGE_CEE_CS_CALLCONV_C:
        NYI_INTERP("GenerateInterpreterStub -- IMAGE_CEE_CS_CALLCONV_C");
        break;

    case IMAGE_CEE_CS_CALLCONV_STDCALL:
        NYI_INTERP("GenerateInterpreterStub -- IMAGE_CEE_CS_CALLCONV_STDCALL");
        break;

    case IMAGE_CEE_CS_CALLCONV_THISCALL:
        NYI_INTERP("GenerateInterpreterStub -- IMAGE_CEE_CS_CALLCONV_THISCALL");
        break;

    case IMAGE_CEE_CS_CALLCONV_FASTCALL:
        NYI_INTERP("GenerateInterpreterStub -- IMAGE_CEE_CS_CALLCONV_FASTCALL");
        break;

    case CORINFO_CALLCONV_FIELD:
        NYI_INTERP("GenerateInterpreterStub -- CORINFO_CALLCONV_FIELD");
        break;

    case CORINFO_CALLCONV_LOCAL_SIG:
        NYI_INTERP("GenerateInterpreterStub -- CORINFO_CALLCONV_LOCAL_SIG");
        break;

    case CORINFO_CALLCONV_PROPERTY:
        NYI_INTERP("GenerateInterpreterStub -- CORINFO_CALLCONV_PROPERTY");
        break;

    case CORINFO_CALLCONV_UNMANAGED:
        NYI_INTERP("GenerateInterpreterStub -- CORINFO_CALLCONV_UNMANAGED");
        break;

    case CORINFO_CALLCONV_NATIVEVARARG:
        NYI_INTERP("GenerateInterpreterStub -- CORINFO_CALLCONV_NATIVEVARARG");
        break;

    default:
        _ASSERTE_ALL_BUILDS(false); // shouldn't get here
    }

    delete[] argPerm;

    PCODE interpretMethodFunc;
    if (!jmpCall)
    {
        switch (info->args.retType)
        {
        case CORINFO_TYPE_FLOAT:
            interpretMethodFunc = reinterpret_cast<PCODE>(&InterpretMethodFloat);
            break;
        case CORINFO_TYPE_DOUBLE:
            interpretMethodFunc = reinterpret_cast<PCODE>(&InterpretMethodDouble);
            break;
        default:
            interpretMethodFunc = reinterpret_cast<PCODE>(&InterpretMethod);
            break;
        }
        // The argument registers have been pushed by now, so we can use them.
#if defined(HOST_X86)
        // First arg is pointer to the base of the ILargs arr -- i.e., the current stack value.
        sl.X86EmitMovRegReg(kEDX, static_cast<X86Reg>(kESP_Unsafe));
        // InterpretMethod uses F_CALL_CONV == __fastcall; pass 2 args in regs.
#if INTERP_ILSTUBS
        if (pMD->IsILStub())
        {
            // Third argument is stubcontext, in eax.
            sl.X86EmitPushReg(kEAX);
        }
        else
#endif
        {
            // For a non-ILStub method, push NULL as the StubContext argument.
            sl.X86EmitZeroOutReg(kECX);
            sl.X86EmitPushReg(kECX);
        }
        // sl.X86EmitAddReg(kECX, reinterpret_cast<UINT>(interpMethInfo));
        sl.X86EmitRegLoad(kECX, reinterpret_cast<UINT>(interpMethInfo));
        sl.X86EmitCall(sl.NewExternalCodeLabel(interpretMethodFunc), 0);
        // Now we will deallocate the stack slots we pushed to hold register arguments.
        if (argState.numRegArgs > 0)
        {
            sl.X86EmitAddEsp(argState.numRegArgs * sizeof(void*));
        }
        sl.X86EmitPopReg(kEBP);
        sl.X86EmitReturn(static_cast<WORD>(argState.callerArgStackSlots * sizeof(void*)));
#elif defined(UNIX_AMD64_ABI)
        bool hasTowRetSlots = info->args.retType == CORINFO_TYPE_VALUECLASS &&
            getClassSize(info->args.retTypeClass) == 16;

        int fixedTwoSlotSize = 16;

        int argSize = (argState.numFPRegArgSlots +  argState.numRegArgs) * sizeof(void*);

        int stackSize = argSize + fixedTwoSlotSize; // Fixed two slot for possible "retbuf", access address by "m_ilArgs-16"

        if (stackSize % 16 == 0) { // for $rsp align requirement
            stackSize += 8;
        }

        sl.X86EmitSubEsp(stackSize);

        X86Reg intArgsRegs[] = {ARGUMENT_kREG1, ARGUMENT_kREG2, kRDX, kRCX, kR8, kR9};
        int    indexGP       = 0;
        int    indexFP       = 0;
        for (int i = 0; i < argState.numRegArgs + argState.numFPRegArgSlots; i++)
        {
            int offs = i * sizeof(void*) + 16;
            if (argState.fpArgsUsed & (1 << i))
            {
                sl.X64EmitMovSDToMem(static_cast<X86Reg>(indexFP), static_cast<X86Reg>(kESP_Unsafe), offs);
                indexFP++;
            }
            else
            {
                sl.X86EmitIndexRegStoreRSP(offs, intArgsRegs[indexGP]);
                indexGP++;
            }
        }

        // Pass "ilArgs", i.e. just the point where registers have been homed, as 2nd arg.
        sl.X86EmitIndexLeaRSP(ARGUMENT_kREG2, static_cast<X86Reg>(kESP_Unsafe), fixedTwoSlotSize);

        // If we have IL stubs pass the stub context in R10 or else pass NULL.
#if INTERP_ILSTUBS
        if (pMD->IsILStub())
        {
            sl.X86EmitMovRegReg(kRDX, kR10);
        }
        else
#endif
        {
            // For a non-ILStub method, push NULL as the StubContext argument.
            sl.X86EmitZeroOutReg(ARGUMENT_kREG1);
            sl.X86EmitMovRegReg(kRDX, ARGUMENT_kREG1);
        }
        sl.X86EmitRegLoad(ARGUMENT_kREG1, reinterpret_cast<UINT_PTR>(interpMethInfo));

        sl.X86EmitCall(sl.NewExternalCodeLabel(interpretMethodFunc), 0);
        if (hasTowRetSlots) {
            sl.X86EmitEspOffset(0x8b, kRAX, 0);
            sl.X86EmitEspOffset(0x8b, kRDX, 8);
        }
        sl.X86EmitAddEsp(stackSize);
        sl.X86EmitReturn(0);
#elif defined(HOST_AMD64)
        // Pass "ilArgs", i.e. just the point where registers have been homed, as 2nd arg
        sl.X86EmitIndexLeaRSP(ARGUMENT_kREG2, static_cast<X86Reg>(kESP_Unsafe), 8);

        // Allocate space for homing callee's (InterpretMethod's) arguments.
        // Calling convention requires a default allocation space of 4,
        // but to double align the stack frame, we'd allocate 5.
        int interpMethodArgSize = 5 * sizeof(void*);
        sl.X86EmitSubEsp(interpMethodArgSize);

        // If we have IL stubs pass the stub context in R10 or else pass NULL.
#if INTERP_ILSTUBS
        if (pMD->IsILStub())
        {
            sl.X86EmitMovRegReg(kR8, kR10);
        }
        else
#endif
        {
            // For a non-ILStub method, push NULL as the StubContext argument.
            sl.X86EmitZeroOutReg(ARGUMENT_kREG1);
            sl.X86EmitMovRegReg(kR8, ARGUMENT_kREG1);
        }
        sl.X86EmitRegLoad(ARGUMENT_kREG1, reinterpret_cast<UINT_PTR>(interpMethInfo));
        sl.X86EmitCall(sl.NewExternalCodeLabel(interpretMethodFunc), 0);
        sl.X86EmitAddEsp(interpMethodArgSize);
        sl.X86EmitReturn(0);
#elif defined(HOST_ARM)

        // We have to maintain 8-byte stack alignment.  So if the number of
        // slots we would normally push is not a multiple of two, add a random
        // register.  (We will not pop this register, but rather, increment
        // sp by an amount that includes it.)
        bool oddPushes = (((argState.numRegArgs + NumberOfFixedPushes) % 2) != 0);

        UINT stackFrameSize = 0;
        if (oddPushes) stackFrameSize = sizeof(void*);
        // Now, if any FP regs are used as arguments, we will copy those to the stack; reserve space for that here.
        // (We push doubles to keep the stack aligned...)
        unsigned short doublesToPush = (argState.numFPRegArgSlots + 1)/2;
        stackFrameSize += (doublesToPush*2*sizeof(void*));

        // The last argument here causes this to generate code to push all int arg regs.
        sl.ThumbEmitProlog(/*cCalleeSavedRegs*/NumberOfCalleeSaveRegsToPush, /*cbStackFrame*/stackFrameSize, /*fPushArgRegs*/TRUE);

        // Now we will generate code to copy the floating point registers to the stack frame.
        if (doublesToPush > 0)
        {
            sl.ThumbEmitStoreMultipleVFPDoubleReg(ThumbVFPDoubleReg(0), thumbRegSp, doublesToPush*2);
        }

#if INTERP_ILSTUBS
        if (pMD->IsILStub())
        {
            // Third argument is stubcontext, in r12.
            sl.ThumbEmitMovRegReg(ThumbReg(2), ThumbReg(12));
        }
        else
#endif
        {
            // For a non-ILStub method, push NULL as the third StubContext argument.
            sl.ThumbEmitMovConstant(ThumbReg(2), 0);
        }
        // Second arg is pointer to the base of the ILargs arr -- i.e., the current stack value.
        sl.ThumbEmitMovRegReg(ThumbReg(1), thumbRegSp);

        // First arg is the pointer to the interpMethInfo structure.
        sl.ThumbEmitMovConstant(ThumbReg(0), reinterpret_cast<int>(interpMethInfo));

        // If there's an HFA return, add space for that.
        if (HFARetTypeSize > 0)
        {
            sl.ThumbEmitSubSp(HFARetTypeSize);
        }

        // Now we can call the right method.
        // No "direct call" instruction, so load into register first.  Can use R3.
        sl.ThumbEmitMovConstant(ThumbReg(3), static_cast<int>(interpretMethodFunc));
        sl.ThumbEmitCallRegister(ThumbReg(3));

        // If there's an HFA return, copy to FP regs, and deallocate the stack space.
        if (HFARetTypeSize > 0)
        {
            sl.ThumbEmitLoadMultipleVFPDoubleReg(ThumbVFPDoubleReg(0), thumbRegSp, HFARetTypeSize/sizeof(void*));
            sl.ThumbEmitAddSp(HFARetTypeSize);
        }

        sl.ThumbEmitEpilog();

#elif defined(HOST_ARM64)

        UINT stackFrameSize = argState.numFPRegArgSlots;

        sl.EmitProlog(argState.numRegArgs, argState.numFPRegArgSlots, 0 /*cCalleeSavedRegs*/, static_cast<unsigned short>(cHFAVars*sizeof(void*)));

#if INTERP_ILSTUBS
        if (pMD->IsILStub())
        {
            // Third argument is stubcontext, in x12 (METHODDESC_REGISTER)
            sl.EmitMovReg(IntReg(2), IntReg(12));
        }
        else
#endif
        {
            // For a non-ILStub method, push NULL as the third stubContext argument
            sl.EmitMovConstant(IntReg(2), 0);
        }

        // Second arg is pointer to the basei of the ILArgs -- i.e., the current stack value
        sl.EmitAddImm(IntReg(1), RegSp, sl.GetSavedRegArgsOffset());

        // First arg is the pointer to the interpMethodInfo structure
#if INTERP_ILSTUBS
        if (!pMD->IsILStub())
#endif
        {
            // interpMethodInfo is already in x8, so copy it from x8
            sl.EmitMovReg(IntReg(0), IntReg(8));
        }
#if INTERP_ILSTUBS
        else
        {
            // We didn't do the short-circuiting, therefore interpMethInfo is
            // not stored in a register (x8) before. so do it now.
            sl.EmitMovConstant(IntReg(0), reinterpret_cast<UINT64>(interpMethInfo));
        }
#endif

        sl.EmitCallLabel(sl.NewExternalCodeLabel((LPVOID)interpretMethodFunc), FALSE, FALSE);

        // If there's an HFA return, copy to FP regs
        if (cHFAVars > 0)
        {
            for (unsigned i=0; i<=(cHFAVars/2)*2;i+=2)
                sl.EmitLoadStoreRegPairImm(StubLinkerCPU::eLOAD, VecReg(i), VecReg(i+1), RegSp, i*sizeof(void*));
            if ((cHFAVars % 2) == 1)
                sl.EmitLoadStoreRegImm(StubLinkerCPU::eLOAD,VecReg(cHFAVars-1), RegSp, cHFAVars*sizeof(void*));

        }

        sl.EmitEpilog();

#elif defined(HOST_LOONGARCH64)
        assert(!"unimplemented on LOONGARCH yet");
#elif defined(HOST_RISCV64)
        assert(!"unimplemented on RISCV64 yet");
#else
#error unsupported platform
#endif
        stub = sl.Link(SystemDomain::GetGlobalLoaderAllocator()->GetStubHeap());

        *nativeSizeOfCode = static_cast<ULONG>(stub->GetNumCodeBytes());
        // TODO: manage reference count of interpreter stubs.  Look for examples...
        *nativeEntry = dac_cast<BYTE*>(stub->GetEntryPoint());
    }

    // Initialize the arg offset information.
    interpMethInfo->InitArgInfo(comp, info, argState.argOffsets);

#ifdef _DEBUG
    AddInterpMethInfo(interpMethInfo);
#endif // _DEBUG
    if (!jmpCall)
    {
        // Remember the mapping between code address and MethodDesc*.
        RecordInterpreterStubForMethodDesc(info->ftn, *nativeEntry);
    }

    return CORJIT_OK;
#undef TRACE_SKIPPED
}

size_t Interpreter::GetFrameSize(InterpreterMethodInfo* interpMethInfo)
{
    size_t sz = interpMethInfo->LocalMemSize();
#if COMBINE_OPSTACK_VAL_TYPE
    sz += (interpMethInfo->m_maxStack * sizeof(OpStackValAndType));
#else
    sz += (interpMethInfo->m_maxStack * (sizeof(INT64) + sizeof(InterpreterType*)));
#endif
    return sz;
}

// static
ARG_SLOT Interpreter::ExecuteMethodWrapper(struct InterpreterMethodInfo* interpMethInfo, bool directCall, BYTE* ilArgs, void* stubContext, _Out_ bool* pDoJmpCall, CORINFO_RESOLVED_TOKEN* pResolvedToken)
{
#define INTERP_DYNAMIC_CONTRACTS 1
#if INTERP_DYNAMIC_CONTRACTS
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;
#else
    // Dynamic contract occupies too much stack.
    STATIC_CONTRACT_THROWS;
    STATIC_CONTRACT_GC_TRIGGERS;
    STATIC_CONTRACT_MODE_COOPERATIVE;
#endif

    size_t sizeWithGS = GetFrameSize(interpMethInfo) + sizeof(GSCookie);
    BYTE* frameMemoryGS = static_cast<BYTE*>(_alloca(sizeWithGS));

    ARG_SLOT retVal = 0;
    unsigned jmpCallToken = 0;

    Interpreter interp(interpMethInfo, directCall, ilArgs, stubContext, frameMemoryGS);

    // Make sure we can do a GC Scan properly.
    FrameWithCookie<InterpreterFrame> interpFrame(&interp);

    // Update the interpretation count.
    InterlockedIncrement(reinterpret_cast<LONG *>(&interpMethInfo->m_invocations));

    // Need to wait until this point to do this JITting, since it may trigger a GC.
    JitMethodIfAppropriate(interpMethInfo);

    // Pass buffers to get jmpCall flag and the token, if necessary.
    interp.ExecuteMethod(&retVal, pDoJmpCall, &jmpCallToken);

    if (*pDoJmpCall)
    {
        GCX_PREEMP();
        interp.ResolveToken(pResolvedToken, jmpCallToken, CORINFO_TOKENKIND_Method InterpTracingArg(RTK_Call));
    }

    interpFrame.Pop();
    return retVal;
}

// TODO: Add GSCookie checks

// static
inline ARG_SLOT Interpreter::InterpretMethodBody(struct InterpreterMethodInfo* interpMethInfo, bool directCall, BYTE* ilArgs, void* stubContext)
{
#if INTERP_DYNAMIC_CONTRACTS
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;
#else
    // Dynamic contract occupies too much stack.
    STATIC_CONTRACT_THROWS;
    STATIC_CONTRACT_GC_TRIGGERS;
    STATIC_CONTRACT_MODE_COOPERATIVE;
#endif

    CEEInfo* jitInfo = NULL;
    for (bool doJmpCall = true; doJmpCall; )
    {
        unsigned jmpCallToken = 0;
        CORINFO_RESOLVED_TOKEN methTokPtr;
        ARG_SLOT retVal = ExecuteMethodWrapper(interpMethInfo, directCall, ilArgs, stubContext, &doJmpCall, &methTokPtr);
        // Clear any allocated jitInfo.
        delete jitInfo;

        // Nothing to do if the recent method asks not to do a jmpCall.
        if (!doJmpCall)
        {
            return retVal;
        }

        // The recently executed method wants us to perform a jmpCall.
        MethodDesc* pMD = GetMethod(methTokPtr.hMethod);
        interpMethInfo = MethodHandleToInterpreterMethInfoPtr(CORINFO_METHOD_HANDLE(pMD));

        // Allocate a new jitInfo and also a new interpMethInfo.
        if (interpMethInfo == NULL)
        {
            _ASSERTE(doJmpCall);
            jitInfo = new CEEInfo(pMD, true);

            CORINFO_METHOD_INFO methInfo;

            GCX_PREEMP();
            jitInfo->getMethodInfo(CORINFO_METHOD_HANDLE(pMD), &methInfo);
            GenerateInterpreterStub(jitInfo, &methInfo, NULL, 0, &interpMethInfo, true);
        }
    }
    UNREACHABLE();
}

void Interpreter::JitMethodIfAppropriate(InterpreterMethodInfo* interpMethInfo, bool force)
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    unsigned int MaxInterpretCount = s_InterpreterJITThreshold.val(CLRConfig::INTERNAL_InterpreterJITThreshold);
    bool scheduleTieringBackgroundWork = false;
    TieredCompilationManager *tieredCompilationManager = GetAppDomain()->GetTieredCompilationManager();

    if (force || interpMethInfo->m_invocations > MaxInterpretCount)
    {
        GCX_PREEMP();
        MethodDesc *md = reinterpret_cast<MethodDesc *>(interpMethInfo->m_method);
        PCODE stub = md->GetNativeCode();

        if (InterpretationStubToMethodInfo(stub) == md)
        {
#if INTERP_TRACING
            if (s_TraceInterpreterJITTransitionFlag.val(CLRConfig::INTERNAL_TraceInterpreterJITTransition))
            {
                fprintf(GetLogFile(), "JITting method %s:%s.\n", md->m_pszDebugClassName, md->m_pszDebugMethodName);
            }
#endif // INTERP_TRACING
            CORJIT_FLAGS jitFlags(CORJIT_FLAGS::CORJIT_FLAG_MAKEFINALCODE);
            NewHolder<COR_ILMETHOD_DECODER> pDecoder(NULL);
            // Dynamic methods (e.g., IL stubs) do not have an IL decoder but may
            // require additional flags.  Ordinary methods require the opposite.
            if (md->IsDynamicMethod())
            {
                jitFlags.Add(md->AsDynamicMethodDesc()->GetILStubResolver()->GetJitFlags());
            }
            else
            {
                COR_ILMETHOD_DECODER::DecoderStatus status;
                pDecoder = new COR_ILMETHOD_DECODER(md->GetILHeader(TRUE),
                                                    md->GetMDImport(),
                                                    &status);
            }
            // This used to be a synchronous jit and could be made so again if desired,
            // but using ASP .NET MusicStore as an example scenario the performance is
            // better doing the JIT asynchronously. Given the not-on-by-default nature of the
            // interpreter I didn't wring my hands too much trying to determine the ideal
            // policy.
#ifdef FEATURE_TIERED_COMPILATION
            CodeVersionManager::LockHolder _lockHolder;
            NativeCodeVersion activeCodeVersion = md->GetCodeVersionManager()->GetActiveILCodeVersion(md).GetActiveNativeCodeVersion(md);
            ILCodeVersion ilCodeVersion = activeCodeVersion.GetILCodeVersion();
            if (!activeCodeVersion.IsFinalTier() &&
                !ilCodeVersion.HasAnyOptimizedNativeCodeVersion(activeCodeVersion))
            {
                tieredCompilationManager->AsyncPromoteToTier1(activeCodeVersion, &scheduleTieringBackgroundWork);
            }
#else
#error FEATURE_INTERPRETER depends on FEATURE_TIERED_COMPILATION now
#endif
        }
    }

    if (scheduleTieringBackgroundWork)
    {
        tieredCompilationManager->TryScheduleBackgroundWorkerWithoutGCTrigger_Locked();
    }
}

// static
HCIMPL3(float, InterpretMethodFloat, struct InterpreterMethodInfo* interpMethInfo, BYTE* ilArgs, void* stubContext)
{
    FCALL_CONTRACT;

    ARG_SLOT retVal = 0;

    HELPER_METHOD_FRAME_BEGIN_RET_ATTRIB(Frame::FRAME_ATTR_EXACT_DEPTH|Frame::FRAME_ATTR_CAPTURE_DEPTH_2);
    retVal = (ARG_SLOT)Interpreter::InterpretMethodBody(interpMethInfo, false, ilArgs, stubContext);
    HELPER_METHOD_FRAME_END();

    return *reinterpret_cast<float*>(ArgSlotEndiannessFixup(&retVal, sizeof(float)));
}
HCIMPLEND

// static
HCIMPL3(double, InterpretMethodDouble, struct InterpreterMethodInfo* interpMethInfo, BYTE* ilArgs, void* stubContext)
{
    FCALL_CONTRACT;

    ARG_SLOT retVal = 0;

    HELPER_METHOD_FRAME_BEGIN_RET_ATTRIB(Frame::FRAME_ATTR_EXACT_DEPTH|Frame::FRAME_ATTR_CAPTURE_DEPTH_2);
    retVal = Interpreter::InterpretMethodBody(interpMethInfo, false, ilArgs, stubContext);
    HELPER_METHOD_FRAME_END();

    return *reinterpret_cast<double*>(ArgSlotEndiannessFixup(&retVal, sizeof(double)));
}
HCIMPLEND

// static
HCIMPL3(INT64, InterpretMethod, struct InterpreterMethodInfo* interpMethInfo, BYTE* ilArgs, void* stubContext)
{
    FCALL_CONTRACT;

    ARG_SLOT retVal = 0;

    HELPER_METHOD_FRAME_BEGIN_RET_ATTRIB(Frame::FRAME_ATTR_EXACT_DEPTH|Frame::FRAME_ATTR_CAPTURE_DEPTH_2);
    retVal = Interpreter::InterpretMethodBody(interpMethInfo, false, ilArgs, stubContext);
    HELPER_METHOD_FRAME_END();

    return static_cast<INT64>(retVal);
}
HCIMPLEND

bool Interpreter::IsInCalleesFrames(void* stackPtr)
{
    // We assume a downwards_growing stack.
    return stackPtr < (m_localVarMemory - sizeof(GSCookie));
}

// I want an enumeration with values for the second byte of 2-byte opcodes.
enum OPCODE_2BYTE {
#define OPDEF(c,s,pop,push,args,type,l,s1,s2,ctrl) TWOBYTE_##c = unsigned(s2),
#include "opcode.def"
#undef OPDEF
};

// Optimize the interpreter loop for speed.
#ifdef _MSC_VER
#pragma optimize("t", on)
#endif

// Duplicating code from JitHelpers for MonEnter,MonExit,MonEnter_Static,
// MonExit_Static because it sets up helper frame for the JIT.
static void MonitorEnter(Object* obj, BYTE* pbLockTaken)
{

    OBJECTREF objRef = ObjectToOBJECTREF(obj);


    if (objRef == NULL)
        COMPlusThrow(kArgumentNullException);

    GCPROTECT_BEGININTERIOR(pbLockTaken);

    if (GET_THREAD()->CatchAtSafePointOpportunistic())
    {
        GET_THREAD()->PulseGCMode();
    }
    objRef->EnterObjMonitor();

    if (pbLockTaken != 0) *pbLockTaken = 1;

    GCPROTECT_END();
}

static void MonitorExit(Object* obj, BYTE* pbLockTaken)
{
    OBJECTREF objRef = ObjectToOBJECTREF(obj);

    if (objRef == NULL)
        COMPlusThrow(kArgumentNullException);

    if (!objRef->LeaveObjMonitor())
        COMPlusThrow(kSynchronizationLockException);

    if (pbLockTaken != 0) *pbLockTaken = 0;

    if (GET_THREAD()->IsAbortRequested()) {
        GET_THREAD()->HandleThreadAbort();
    }
}

static void MonitorEnterStatic(AwareLock *lock, BYTE* pbLockTaken)
{
    lock->Enter();
    MONHELPER_STATE(*pbLockTaken = 1;)
}

static void MonitorExitStatic(AwareLock *lock, BYTE* pbLockTaken)
{
    // Error, yield or contention
    if (!lock->Leave())
        COMPlusThrow(kSynchronizationLockException);

    if (GET_THREAD()->IsAbortRequested()) {
        GET_THREAD()->HandleThreadAbort();
    }
}


AwareLock* Interpreter::GetMonitorForStaticMethod()
{
    MethodDesc* pMD = reinterpret_cast<MethodDesc*>(m_methInfo->m_method);
    CORINFO_LOOKUP_KIND kind;
    {
        GCX_PREEMP();
        m_interpCeeInfo.getLocationOfThisType(m_methInfo->m_method, &kind);
    }
    if (!kind.needsRuntimeLookup)
    {
        OBJECTREF ref = pMD->GetMethodTable()->GetManagedClassObject();
        return (AwareLock*) ref->GetSyncBlock()->GetMonitor();
    }
    else
    {
        CORINFO_CLASS_HANDLE classHnd = nullptr;
        switch (kind.runtimeLookupKind)
        {
        case CORINFO_LOOKUP_CLASSPARAM:
            {
                CORINFO_CONTEXT_HANDLE ctxHnd = GetPreciseGenericsContext();
                _ASSERTE_MSG((((size_t)ctxHnd & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_CLASS), "Precise context not class context");
                classHnd = (CORINFO_CLASS_HANDLE) ((size_t)ctxHnd & ~CORINFO_CONTEXTFLAGS_CLASS);
            }
            break;
        case CORINFO_LOOKUP_METHODPARAM:
            {
                CORINFO_CONTEXT_HANDLE ctxHnd = GetPreciseGenericsContext();
                _ASSERTE_MSG((((size_t)ctxHnd & CORINFO_CONTEXTFLAGS_MASK) == CORINFO_CONTEXTFLAGS_METHOD), "Precise context not method context");
                MethodDesc* pMD = (MethodDesc*) (CORINFO_METHOD_HANDLE) ((size_t)ctxHnd & ~CORINFO_CONTEXTFLAGS_METHOD);
                classHnd = (CORINFO_CLASS_HANDLE) pMD->GetMethodTable();
            }
            break;
        default:
            NYI_INTERP("Unknown lookup for synchronized methods");
            break;
        }
        MethodTable* pMT = GetMethodTableFromClsHnd(classHnd);
        OBJECTREF ref = pMT->GetManagedClassObject();
        _ASSERTE(ref);
        return (AwareLock*) ref->GetSyncBlock()->GetMonitor();
    }
}

void Interpreter::DoMonitorEnterWork()
{
    MethodDesc* pMD = reinterpret_cast<MethodDesc*>(m_methInfo->m_method);
    if (pMD->IsSynchronized())
    {
        if (pMD->IsStatic())
        {
            AwareLock* lock = GetMonitorForStaticMethod();
            MonitorEnterStatic(lock, &m_monAcquired);
        }
        else
        {
            MonitorEnter((Object*) m_thisArg, &m_monAcquired);
        }
    }
}

void Interpreter::DoMonitorExitWork()
{
    MethodDesc* pMD = reinterpret_cast<MethodDesc*>(m_methInfo->m_method);
    if (pMD->IsSynchronized())
    {
        if (pMD->IsStatic())
        {
            AwareLock* lock = GetMonitorForStaticMethod();
            MonitorExitStatic(lock, &m_monAcquired);
        }
        else
        {
            MonitorExit((Object*) m_thisArg, &m_monAcquired);
        }
    }
}


void Interpreter::ExecuteMethod(ARG_SLOT* retVal, _Out_ bool* pDoJmpCall, _Out_ unsigned* pJmpCallToken)
{
#if INTERP_DYNAMIC_CONTRACTS
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;
#else
    // Dynamic contract occupies too much stack.
    STATIC_CONTRACT_THROWS;
    STATIC_CONTRACT_GC_TRIGGERS;
    STATIC_CONTRACT_MODE_COOPERATIVE;
#endif

    *pDoJmpCall = false;

    // Normally I'd prefer to declare these in small case-block scopes, but most C++ compilers
    // do not realize that their lifetimes do not overlap, so that makes for a large stack frame.
    // So I avoid that by outside declarations (sigh).
    char offsetc, valc;
    unsigned char argNumc;
    unsigned short argNums;
    INT32 vali;
    INT64 vall;
    InterpreterType it;
    size_t sz;

    unsigned short ops;

    // Make sure that the .cctor for the current method's class has been run.
    MethodDesc* pMD = reinterpret_cast<MethodDesc*>(m_methInfo->m_method);
    EnsureClassInit(pMD->GetMethodTable());

#if INTERP_TRACING
    const char* methName = eeGetMethodFullName(m_methInfo->m_method);
    unsigned ilOffset = 0;

    unsigned curInvocation = InterlockedIncrement(&s_totalInvocations);
    if (s_TraceInterpreterEntriesFlag.val(CLRConfig::INTERNAL_TraceInterpreterEntries))
    {
        fprintf(GetLogFile(), "Entering method #%d (= 0x%x): %s.\n", curInvocation, curInvocation, methName);
        fprintf(GetLogFile(), " arguments:\n");
        PrintArgs();
    }
#endif // INTERP_TRACING

#if LOOPS_VIA_INSTRS
    unsigned instrs = 0;
#else
#if INTERP_PROFILE
    unsigned instrs = 0;
#endif
#endif

EvalLoop:
    GCX_ASSERT_COOP();
    // Catch any exceptions raised.
    EX_TRY {
        // Optional features...
#define INTERPRETER_CHECK_LARGE_STRUCT_STACK_HEIGHT 1

#if INTERP_ILCYCLE_PROFILE
    m_instr = CEE_COUNT; // Flag to indicate first instruction.
    m_exemptCycles = 0;
#endif // INTERP_ILCYCLE_PROFILE

    DoMonitorEnterWork();

    INTERPLOG("START %d, %s\n", m_methInfo->m_stubNum, methName);
    for (;;)
    {
        // TODO: verify that m_ILCodePtr is legal, and we haven't walked off the end of the IL array? (i.e., bad IL).
        // Note that ExecuteBranch() should be called for every branch. That checks that we aren't either before or
        // after the IL range. Here, we would only need to check that we haven't gone past the end (not before the beginning)
        // because everything that doesn't call ExecuteBranch() should only add to m_ILCodePtr.

#if INTERP_TRACING
        ilOffset = CurOffset();
#endif // _DEBUG
#if INTERP_TRACING
        if (s_TraceInterpreterOstackFlag.val(CLRConfig::INTERNAL_TraceInterpreterOstack))
        {
            PrintOStack();
        }
#if INTERPRETER_CHECK_LARGE_STRUCT_STACK_HEIGHT
        _ASSERTE_MSG(LargeStructStackHeightIsValid(), "Large structure stack height invariant violated."); // Check the large struct stack invariant.
#endif
        if (s_TraceInterpreterILFlag.val(CLRConfig::INTERNAL_TraceInterpreterIL))
        {
            fprintf(GetLogFile(), "  %#4x: %s\n", ilOffset, ILOp(m_ILCodePtr));
            fflush(GetLogFile());
        }
#endif // INTERP_TRACING
#if LOOPS_VIA_INSTRS
        instrs++;
#else
#if INTERP_PROFILE
        instrs++;
#endif
#endif

#if INTERP_ILINSTR_PROFILE
#if INTERP_ILCYCLE_PROFILE
        UpdateCycleCount();
#endif // INTERP_ILCYCLE_PROFILE

        InterlockedIncrement(&s_ILInstrExecs[*m_ILCodePtr]);
#endif // INTERP_ILINSTR_PROFILE

        switch (*m_ILCodePtr)
        {
        case CEE_NOP:
            m_ILCodePtr++;
            continue;
        case CEE_BREAK:     // TODO: interact with the debugger?
            m_ILCodePtr++;
            continue;
        case CEE_LDARG_0:
            LdArg(0);
            break;
        case CEE_LDARG_1:
            LdArg(1);
            break;
        case CEE_LDARG_2:
            LdArg(2);
            break;
        case CEE_LDARG_3:
            LdArg(3);
            break;
        case CEE_LDLOC_0:
            LdLoc(0);
            m_ILCodePtr++;
            continue;
        case CEE_LDLOC_1:
            LdLoc(1);
            break;
        case CEE_LDLOC_2:
            LdLoc(2);
            break;
        case CEE_LDLOC_3:
            LdLoc(3);
            break;
        case CEE_STLOC_0:
            StLoc(0);
            break;
        case CEE_STLOC_1:
            StLoc(1);
            break;
        case CEE_STLOC_2:
            StLoc(2);
            break;
        case CEE_STLOC_3:
            StLoc(3);
            break;
        case CEE_LDARG_S:
            m_ILCodePtr++;
            argNumc = *m_ILCodePtr;
            LdArg(argNumc);
            break;
        case CEE_LDARGA_S:
            m_ILCodePtr++;
            argNumc = *m_ILCodePtr;
            LdArgA(argNumc);
            break;
        case CEE_STARG_S:
            m_ILCodePtr++;
            argNumc = *m_ILCodePtr;
            StArg(argNumc);
            break;
        case CEE_LDLOC_S:
            argNumc = *(m_ILCodePtr + 1);
            LdLoc(argNumc);
            m_ILCodePtr += 2;
            continue;
        case CEE_LDLOCA_S:
            m_ILCodePtr++;
            argNumc = *m_ILCodePtr;
            LdLocA(argNumc);
            break;
        case CEE_STLOC_S:
            argNumc = *(m_ILCodePtr + 1);
            StLoc(argNumc);
            m_ILCodePtr += 2;
            continue;
        case CEE_LDNULL:
            LdNull();
            break;
        case CEE_LDC_I4_M1:
            LdIcon(-1);
            break;
        case CEE_LDC_I4_0:
            LdIcon(0);
            break;
        case CEE_LDC_I4_1:
            LdIcon(1);
            m_ILCodePtr++;
            continue;
        case CEE_LDC_I4_2:
            LdIcon(2);
            break;
        case CEE_LDC_I4_3:
            LdIcon(3);
            break;
        case CEE_LDC_I4_4:
            LdIcon(4);
            break;
        case CEE_LDC_I4_5:
            LdIcon(5);
            break;
        case CEE_LDC_I4_6:
            LdIcon(6);
            break;
        case CEE_LDC_I4_7:
            LdIcon(7);
            break;
        case CEE_LDC_I4_8:
            LdIcon(8);
            break;
        case CEE_LDC_I4_S:
            valc = getI1(m_ILCodePtr + 1);
            LdIcon(valc);
            m_ILCodePtr += 2;
            continue;
        case CEE_LDC_I4:
            vali = getI4LittleEndian(m_ILCodePtr + 1);
            LdIcon(vali);
            m_ILCodePtr += 5;
            continue;
        case CEE_LDC_I8:
            vall = getI8LittleEndian(m_ILCodePtr + 1);
            LdLcon(vall);
            m_ILCodePtr += 9;
            continue;
        case CEE_LDC_R4:
            // We use I4 here because we just care about the bit pattern.
            // LdR4Con will push the right InterpreterType.
            vali = getI4LittleEndian(m_ILCodePtr + 1);
            LdR4con(vali);
            m_ILCodePtr += 5;
            continue;
        case CEE_LDC_R8:
            // We use I4 here because we just care about the bit pattern.
            // LdR8Con will push the right InterpreterType.
            vall = getI8LittleEndian(m_ILCodePtr + 1);
            LdR8con(vall);
            m_ILCodePtr += 9;
            continue;
        case CEE_DUP:
            _ASSERTE(m_curStackHt > 0);
            it = OpStackTypeGet(m_curStackHt - 1);
            OpStackTypeSet(m_curStackHt, it);
            if (it.IsLargeStruct(&m_interpCeeInfo))
            {
                sz = it.Size(&m_interpCeeInfo);
                void* dest = LargeStructOperandStackPush(sz);
                memcpy(dest, OpStackGet<void*>(m_curStackHt - 1), sz);
                OpStackSet<void*>(m_curStackHt, dest);
            }
            else
            {
                OpStackSet<INT64>(m_curStackHt, OpStackGet<INT64>(m_curStackHt - 1));
            }
            m_curStackHt++;
            break;
        case CEE_POP:
            _ASSERTE(m_curStackHt > 0);
            m_curStackHt--;
            it = OpStackTypeGet(m_curStackHt);
            if (it.IsLargeStruct(&m_interpCeeInfo))
            {
                LargeStructOperandStackPop(it.Size(&m_interpCeeInfo), OpStackGet<void*>(m_curStackHt));
            }
            break;

        case CEE_JMP:
            *pJmpCallToken = getU4LittleEndian(m_ILCodePtr + sizeof(BYTE));
            *pDoJmpCall = true;
            goto ExitEvalLoop;

        case CEE_CALL:
            DoCall(/*virtualCall*/false);
#if INTERP_TRACING
            if (s_TraceInterpreterILFlag.val(CLRConfig::INTERNAL_TraceInterpreterIL))
            {
                fprintf(GetLogFile(), "  Returning to method %s, stub num %d.\n", methName, m_methInfo->m_stubNum);
            }
#endif // INTERP_TRACING
            continue;

        case CEE_CALLVIRT:
            DoCall(/*virtualCall*/true);
#if INTERP_TRACING
            if (s_TraceInterpreterILFlag.val(CLRConfig::INTERNAL_TraceInterpreterIL))
            {
                fprintf(GetLogFile(), "  Returning to method %s, stub num %d.\n", methName, m_methInfo->m_stubNum);
            }
#endif // INTERP_TRACING
            continue;

            // HARD
        case CEE_CALLI:
            CallI();
            continue;

        case CEE_RET:
            if (m_methInfo->m_returnType == CORINFO_TYPE_VOID)
            {
                _ASSERTE(m_curStackHt == 0);
            }
            else
            {
                _ASSERTE(m_curStackHt == 1);
                InterpreterType retValIt = OpStackTypeGet(0);
                bool looseInt = s_InterpreterLooseRules &&
                    CorInfoTypeIsIntegral(m_methInfo->m_returnType) &&
                    (CorInfoTypeIsIntegral(retValIt.ToCorInfoType()) || CorInfoTypeIsPointer(retValIt.ToCorInfoType())) &&
                    (m_methInfo->m_returnType != retValIt.ToCorInfoType());

                bool looseFloat = s_InterpreterLooseRules &&
                    CorInfoTypeIsFloatingPoint(m_methInfo->m_returnType) &&
                    CorInfoTypeIsFloatingPoint(retValIt.ToCorInfoType()) &&
                    (m_methInfo->m_returnType != retValIt.ToCorInfoType());

                // Make sure that the return value "matches" (which allows certain relaxations) the declared return type.
                _ASSERTE((m_methInfo->m_returnType == CORINFO_TYPE_VALUECLASS && retValIt.ToCorInfoType() == CORINFO_TYPE_VALUECLASS) ||
                       (m_methInfo->m_returnType == CORINFO_TYPE_REFANY && retValIt.ToCorInfoType() == CORINFO_TYPE_VALUECLASS) ||
                       (m_methInfo->m_returnType == CORINFO_TYPE_REFANY && retValIt.ToCorInfoType() == CORINFO_TYPE_REFANY) ||
                       (looseInt || looseFloat) ||
                      InterpreterType(m_methInfo->m_returnType).StackNormalize().Matches(retValIt, &m_interpCeeInfo));

                size_t sz = retValIt.Size(&m_interpCeeInfo);
#if defined(FEATURE_HFA)
                CorInfoHFAElemType cit = CORINFO_HFA_ELEM_NONE;
                {
                    GCX_PREEMP();
                    if(m_methInfo->m_returnType == CORINFO_TYPE_VALUECLASS)
                        cit = m_interpCeeInfo.getHFAType(retValIt.ToClassHandle());
                }
#endif
                if (m_methInfo->GetFlag<InterpreterMethodInfo::Flag_hasRetBuffArg>())
                {
                    _ASSERTE((m_methInfo->m_returnType == CORINFO_TYPE_VALUECLASS && retValIt.ToCorInfoType() == CORINFO_TYPE_VALUECLASS) ||
                       (m_methInfo->m_returnType == CORINFO_TYPE_REFANY && retValIt.ToCorInfoType() == CORINFO_TYPE_VALUECLASS) ||
                       (m_methInfo->m_returnType == CORINFO_TYPE_REFANY && retValIt.ToCorInfoType() == CORINFO_TYPE_REFANY));
                    if (retValIt.ToCorInfoType() == CORINFO_TYPE_REFANY)
                    {
                        InterpreterType typedRefIT = GetTypedRefIT(&m_interpCeeInfo);
                        TypedByRef* ptr = OpStackGet<TypedByRef*>(0);
                        *((TypedByRef*) m_retBufArg) = *ptr;
                    }
                    else if (retValIt.IsLargeStruct(&m_interpCeeInfo))
                    {
                        MethodTable* clsMt = GetMethodTableFromClsHnd(retValIt.ToClassHandle());
                        // The ostack value is a pointer to the struct value.
                        CopyValueClassUnchecked(m_retBufArg, OpStackGet<void*>(0), clsMt);
                    }
                    else
                    {
                        MethodTable* clsMt = GetMethodTableFromClsHnd(retValIt.ToClassHandle());
                        // The ostack value *is* the struct value.
                        CopyValueClassUnchecked(m_retBufArg, OpStackGetAddr(0, sz), clsMt);
                    }
                }
#if defined(FEATURE_HFA)
                // Is it an HFA?
                else if (m_methInfo->m_returnType == CORINFO_TYPE_VALUECLASS
                         && (cit != CORINFO_HFA_ELEM_NONE)
                         && (MetaSig(reinterpret_cast<MethodDesc*>(m_methInfo->m_method)).GetCallingConventionInfo() & CORINFO_CALLCONV_VARARG) == 0)
                {
                    if (retValIt.IsLargeStruct(&m_interpCeeInfo))
                    {
                        // The ostack value is a pointer to the struct value.
                        memcpy(GetHFARetBuffAddr(static_cast<unsigned>(sz)), OpStackGet<void*>(0), sz);
                    }
                    else
                    {
                        // The ostack value *is* the struct value.
                        memcpy(GetHFARetBuffAddr(static_cast<unsigned>(sz)), OpStackGetAddr(0, sz), sz);
                    }
                }
#elif defined(UNIX_AMD64_ABI)
                // Is it an struct contained in $rax and $rdx
                else if (m_methInfo->m_returnType == CORINFO_TYPE_VALUECLASS
                         && sz == 16)
                {
                    //The Fixed Two slot return buffer address
                    memcpy(m_ilArgs-16, OpStackGet<void*>(0), sz);
                }
#endif
                else if (CorInfoTypeIsFloatingPoint(m_methInfo->m_returnType) &&
                    CorInfoTypeIsFloatingPoint(retValIt.ToCorInfoType()))
                {
                    double val = (sz <= sizeof(INT32)) ? OpStackGet<float>(0) : OpStackGet<double>(0);
                    if (m_methInfo->m_returnType == CORINFO_TYPE_DOUBLE)
                    {
                        memcpy(retVal, &val, sizeof(double));
                    }
                    else
                    {
                        float val2 = (float) val;
                        memcpy(retVal, &val2, sizeof(float));
                    }
                }
                else
                {
                    if (sz <= sizeof(INT32))
                    {
                        *retVal = OpStackGet<INT32>(0);
                    }
                    else
                    {
                        // If looseInt is true, we are relying on auto-downcast in case *retVal
                        // is small (but this is guaranteed not to happen by def'n of ARG_SLOT.)
                        //
                        // Note structs of size 5, 6, 7 may be returned as 8 byte ints.
                        _ASSERTE(sz <= sizeof(INT64));
                        *retVal = OpStackGet<INT64>(0);
                    }
                }
            }


#if INTERP_PROFILE
            // We're not capturing instructions executed in a method that terminates via exception,
            // but that's OK...
            m_methInfo->RecordExecInstrs(instrs);
#endif
#if INTERP_TRACING
            // We keep this live until we leave.
            delete methName;
#endif // INTERP_TRACING

#if INTERP_ILCYCLE_PROFILE
            // Finish off accounting for the "RET" before we return
            UpdateCycleCount();
#endif // INTERP_ILCYCLE_PROFILE

            goto ExitEvalLoop;

        case CEE_BR_S:
            m_ILCodePtr++;
            offsetc = *m_ILCodePtr;
            // The offset is wrt the beginning of the following instruction, so the +1 is to get to that
            // m_ILCodePtr value before adding the offset.
            ExecuteBranch(m_ILCodePtr + offsetc + 1);
            continue; // Skip the default m_ILCodePtr++ at bottom of loop.

        case CEE_LEAVE_S:
            // LEAVE empties the operand stack.
            m_curStackHt = 0;
            m_largeStructOperandStackHt = 0;
            offsetc = getI1(m_ILCodePtr + 1);

            {
                // The offset is wrt the beginning of the following instruction, so the +2 is to get to that
                // m_ILCodePtr value before adding the offset.
                BYTE* leaveTarget = m_ILCodePtr + offsetc + 2;
                unsigned leaveOffset = CurOffset();
                m_leaveInfoStack.Push(LeaveInfo(leaveOffset, leaveTarget));
                if (!SearchForCoveringFinally())
                {
                    m_leaveInfoStack.Pop();
                    ExecuteBranch(leaveTarget);
                }
            }
            continue; // Skip the default m_ILCodePtr++ at bottom of loop.

            // Abstract the next pair out to something common with templates.
        case CEE_BRFALSE_S:
            BrOnValue<false, 1>();
            continue;

        case CEE_BRTRUE_S:
            BrOnValue<true, 1>();
            continue;

        case CEE_BEQ_S:
            BrOnComparison<CO_EQ, false, 1>();
            continue;
        case CEE_BGE_S:
            _ASSERTE(m_curStackHt >= 2);
            // ECMA spec gives different semantics for different operand types:
            switch (OpStackTypeGet(m_curStackHt-1).ToCorInfoType())
            {
            case CORINFO_TYPE_FLOAT:
            case CORINFO_TYPE_DOUBLE:
                BrOnComparison<CO_LT_UN, true, 1>();
                break;
            default:
                BrOnComparison<CO_LT, true, 1>();
                break;
            }
            continue;
        case CEE_BGT_S:
            BrOnComparison<CO_GT, false, 1>();
            continue;
        case CEE_BLE_S:
            _ASSERTE(m_curStackHt >= 2);
            // ECMA spec gives different semantics for different operand types:
            switch (OpStackTypeGet(m_curStackHt-1).ToCorInfoType())
            {
            case CORINFO_TYPE_FLOAT:
            case CORINFO_TYPE_DOUBLE:
                BrOnComparison<CO_GT_UN, true, 1>();
                break;
            default:
                BrOnComparison<CO_GT, true, 1>();
                break;
            }
            continue;
        case CEE_BLT_S:
            BrOnComparison<CO_LT, false, 1>();
            continue;
        case CEE_BNE_UN_S:
            BrOnComparison<CO_EQ, true, 1>();
            continue;
        case CEE_BGE_UN_S:
            _ASSERTE(m_curStackHt >= 2);
            // ECMA spec gives different semantics for different operand types:
            switch (OpStackTypeGet(m_curStackHt-1).ToCorInfoType())
            {
            case CORINFO_TYPE_FLOAT:
            case CORINFO_TYPE_DOUBLE:
                BrOnComparison<CO_LT, true, 1>();
                break;
            default:
                BrOnComparison<CO_LT_UN, true, 1>();
                break;
            }
            continue;
        case CEE_BGT_UN_S:
            BrOnComparison<CO_GT_UN, false, 1>();
            continue;
        case CEE_BLE_UN_S:
            _ASSERTE(m_curStackHt >= 2);
            // ECMA spec gives different semantics for different operand types:
            switch (OpStackTypeGet(m_curStackHt-1).ToCorInfoType())
            {
            case CORINFO_TYPE_FLOAT:
            case CORINFO_TYPE_DOUBLE:
                BrOnComparison<CO_GT, true, 1>();
                break;
            default:
                BrOnComparison<CO_GT_UN, true, 1>();
                break;
            }
            continue;
        case CEE_BLT_UN_S:
            BrOnComparison<CO_LT_UN, false, 1>();
            continue;

        case CEE_BR:
            m_ILCodePtr++;
            vali = getI4LittleEndian(m_ILCodePtr);
            vali += 4; // +4 for the length of the offset.
            ExecuteBranch(m_ILCodePtr + vali);
            if (vali < 0)
            {
                // Backwards branch -- enable caching.
                BackwardsBranchActions(vali);
            }

            continue;

        case CEE_LEAVE:
            // LEAVE empties the operand stack.
            m_curStackHt = 0;
            m_largeStructOperandStackHt = 0;
            vali = getI4LittleEndian(m_ILCodePtr + 1);

            {
                // The offset is wrt the beginning of the following instruction, so the +5 is to get to that
                // m_ILCodePtr value before adding the offset.
                BYTE* leaveTarget =  m_ILCodePtr + (vali + 5);
                unsigned leaveOffset = CurOffset();
                m_leaveInfoStack.Push(LeaveInfo(leaveOffset, leaveTarget));
                if (!SearchForCoveringFinally())
                {
                    (void)m_leaveInfoStack.Pop();
                    if (vali < 0)
                    {
                        // Backwards branch -- enable caching.
                        BackwardsBranchActions(vali);
                    }
                    ExecuteBranch(leaveTarget);
                }
            }
            continue; // Skip the default m_ILCodePtr++ at bottom of loop.

        case CEE_BRFALSE:
            BrOnValue<false, 4>();
            continue;
        case CEE_BRTRUE:
            BrOnValue<true, 4>();
            continue;

        case CEE_BEQ:
            BrOnComparison<CO_EQ, false, 4>();
            continue;
        case CEE_BGE:
            _ASSERTE(m_curStackHt >= 2);
            // ECMA spec gives different semantics for different operand types:
            switch (OpStackTypeGet(m_curStackHt-1).ToCorInfoType())
            {
            case CORINFO_TYPE_FLOAT:
            case CORINFO_TYPE_DOUBLE:
                BrOnComparison<CO_LT_UN, true, 4>();
                break;
            default:
                BrOnComparison<CO_LT, true, 4>();
                break;
            }
            continue;
        case CEE_BGT:
            BrOnComparison<CO_GT, false, 4>();
            continue;
        case CEE_BLE:
            _ASSERTE(m_curStackHt >= 2);
            // ECMA spec gives different semantics for different operand types:
            switch (OpStackTypeGet(m_curStackHt-1).ToCorInfoType())
            {
            case CORINFO_TYPE_FLOAT:
            case CORINFO_TYPE_DOUBLE:
                BrOnComparison<CO_GT_UN, true, 4>();
                break;
            default:
                BrOnComparison<CO_GT, true, 4>();
                break;
            }
            continue;
        case CEE_BLT:
            BrOnComparison<CO_LT, false, 4>();
            continue;
        case CEE_BNE_UN:
            BrOnComparison<CO_EQ, true, 4>();
            continue;
        case CEE_BGE_UN:
            _ASSERTE(m_curStackHt >= 2);
            // ECMA spec gives different semantics for different operand types:
            switch (OpStackTypeGet(m_curStackHt-1).ToCorInfoType())
            {
            case CORINFO_TYPE_FLOAT:
            case CORINFO_TYPE_DOUBLE:
                BrOnComparison<CO_LT, true, 4>();
                break;
            default:
                BrOnComparison<CO_LT_UN, true, 4>();
                break;
            }
            continue;
        case CEE_BGT_UN:
            BrOnComparison<CO_GT_UN, false, 4>();
            continue;
        case CEE_BLE_UN:
             _ASSERTE(m_curStackHt >= 2);
            // ECMA spec gives different semantics for different operand types:
            switch (OpStackTypeGet(m_curStackHt-1).ToCorInfoType())
            {
            case CORINFO_TYPE_FLOAT:
            case CORINFO_TYPE_DOUBLE:
                BrOnComparison<CO_GT, true, 4>();
                break;
            default:
                BrOnComparison<CO_GT_UN, true, 4>();
                break;
            }
            continue;
        case CEE_BLT_UN:
            BrOnComparison<CO_LT_UN, false, 4>();
            continue;

        case CEE_SWITCH:
            {
                _ASSERTE(m_curStackHt > 0);
                m_curStackHt--;
#if defined(_DEBUG) || defined(HOST_AMD64)
                CorInfoType cit = OpStackTypeGet(m_curStackHt).ToCorInfoType();
#endif // _DEBUG || HOST_AMD64
#ifdef _DEBUG
                _ASSERTE(cit == CORINFO_TYPE_INT || cit == CORINFO_TYPE_UINT || cit == CORINFO_TYPE_NATIVEINT);
#endif // _DEBUG
#if defined(HOST_AMD64)
                UINT32 val = (cit == CORINFO_TYPE_NATIVEINT) ? (INT32) OpStackGet<NativeInt>(m_curStackHt)
                                                             : OpStackGet<INT32>(m_curStackHt);
#else
                UINT32 val = OpStackGet<INT32>(m_curStackHt);
#endif
                UINT32 n = getU4LittleEndian(m_ILCodePtr + 1);
                UINT32 instrSize = 1 + (n + 1)*4;
                if (val < n)
                {
                    vali = getI4LittleEndian(m_ILCodePtr + (5 + val * 4));
                    ExecuteBranch(m_ILCodePtr + instrSize + vali);
                }
                else
                {
                    m_ILCodePtr += instrSize;
                }
            }
            continue;

        case CEE_LDIND_I1:
            LdIndShort<INT8, /*isUnsigned*/false>();
            break;
        case CEE_LDIND_U1:
            LdIndShort<UINT8, /*isUnsigned*/true>();
            break;
        case CEE_LDIND_I2:
            LdIndShort<INT16, /*isUnsigned*/false>();
            break;
        case CEE_LDIND_U2:
            LdIndShort<UINT16, /*isUnsigned*/true>();
            break;
        case CEE_LDIND_I4:
            LdInd<INT32, CORINFO_TYPE_INT>();
            break;
        case CEE_LDIND_U4:
            LdInd<UINT32, CORINFO_TYPE_INT>();
            break;
        case CEE_LDIND_I8:
            LdInd<INT64, CORINFO_TYPE_LONG>();
            break;
        case CEE_LDIND_I:
            LdInd<NativeInt, CORINFO_TYPE_NATIVEINT>();
            break;
        case CEE_LDIND_R4:
            LdInd<float, CORINFO_TYPE_FLOAT>();
            break;
        case CEE_LDIND_R8:
            LdInd<double, CORINFO_TYPE_DOUBLE>();
            break;
        case CEE_LDIND_REF:
            LdInd<Object*, CORINFO_TYPE_CLASS>();
            break;
        case CEE_STIND_REF:
            StInd_Ref();
            break;
        case CEE_STIND_I1:
            StInd<INT8>();
            break;
        case CEE_STIND_I2:
            StInd<INT16>();
            break;
        case CEE_STIND_I4:
            StInd<INT32>();
            break;
        case CEE_STIND_I8:
            StInd<INT64>();
            break;
        case CEE_STIND_R4:
            StInd<float>();
            break;
        case CEE_STIND_R8:
            StInd<double>();
            break;
        case CEE_ADD:
            BinaryArithOp<BA_Add>();
            m_ILCodePtr++;
            continue;
        case CEE_SUB:
            BinaryArithOp<BA_Sub>();
            break;
        case CEE_MUL:
            BinaryArithOp<BA_Mul>();
            break;
        case CEE_DIV:
            BinaryArithOp<BA_Div>();
            break;
        case CEE_DIV_UN:
            BinaryIntOp<BIO_DivUn>();
            break;
        case CEE_REM:
            BinaryArithOp<BA_Rem>();
            break;
        case CEE_REM_UN:
            BinaryIntOp<BIO_RemUn>();
            break;
        case CEE_AND:
            BinaryIntOp<BIO_And>();
            break;
        case CEE_OR:
            BinaryIntOp<BIO_Or>();
            break;
        case CEE_XOR:
            BinaryIntOp<BIO_Xor>();
            break;
        case CEE_SHL:
            ShiftOp<CEE_SHL>();
            break;
        case CEE_SHR:
            ShiftOp<CEE_SHR>();
            break;
        case CEE_SHR_UN:
            ShiftOp<CEE_SHR_UN>();
            break;
        case CEE_NEG:
            Neg();
            break;
        case CEE_NOT:
            Not();
            break;
        case CEE_CONV_I1:
            Conv<INT8, /*TIsUnsigned*/false, /*TCanHoldPtr*/false, /*TIsShort*/true, CORINFO_TYPE_INT>();
            break;
        case CEE_CONV_I2:
            Conv<INT16, /*TIsUnsigned*/false, /*TCanHoldPtr*/false, /*TIsShort*/true, CORINFO_TYPE_INT>();
            break;
        case CEE_CONV_I4:
            Conv<INT32, /*TIsUnsigned*/false, /*TCanHoldPtr*/false, /*TIsShort*/false, CORINFO_TYPE_INT>();
            break;
        case CEE_CONV_I8:
            Conv<INT64, /*TIsUnsigned*/false, /*TCanHoldPtr*/true, /*TIsShort*/false, CORINFO_TYPE_LONG>();
            break;
        case CEE_CONV_R4:
            Conv<float, /*TIsUnsigned*/false, /*TCanHoldPtr*/false, /*TIsShort*/false, CORINFO_TYPE_FLOAT>();
            break;
        case CEE_CONV_R8:
            Conv<double, /*TIsUnsigned*/false, /*TCanHoldPtr*/false, /*TIsShort*/false, CORINFO_TYPE_DOUBLE>();
            break;
        case CEE_CONV_U4:
            Conv<UINT32, /*TIsUnsigned*/true, /*TCanHoldPtr*/false, /*TIsShort*/false, CORINFO_TYPE_INT>();
            break;
        case CEE_CONV_U8:
            Conv<UINT64, /*TIsUnsigned*/true, /*TCanHoldPtr*/true, /*TIsShort*/false, CORINFO_TYPE_LONG>();
            break;

        case CEE_CPOBJ:
            CpObj();
            continue;
        case CEE_LDOBJ:
            LdObj();
            continue;
        case CEE_LDSTR:
            LdStr();
            continue;
        case CEE_NEWOBJ:
            NewObj();
#if INTERP_TRACING
            if (s_TraceInterpreterILFlag.val(CLRConfig::INTERNAL_TraceInterpreterIL))
            {
                fprintf(GetLogFile(), "  Returning to method %s, stub num %d.\n", methName, m_methInfo->m_stubNum);
            }
#endif // INTERP_TRACING
            continue;
        case CEE_CASTCLASS:
            CastClass();
            continue;
        case CEE_ISINST:
            IsInst();
            continue;
        case CEE_CONV_R_UN:
            ConvRUn();
            break;
        case CEE_UNBOX:
            Unbox();
            continue;
        case CEE_THROW:
            Throw();
            break;
        case CEE_LDFLD:
            LdFld();
            continue;
        case CEE_LDFLDA:
            LdFldA();
            continue;
        case CEE_STFLD:
            StFld();
            continue;
        case CEE_LDSFLD:
            LdSFld();
            continue;
        case CEE_LDSFLDA:
            LdSFldA();
            continue;
        case CEE_STSFLD:
            StSFld();
            continue;
        case CEE_STOBJ:
            StObj();
            continue;
        case CEE_CONV_OVF_I1_UN:
            ConvOvfUn<INT8, SCHAR_MIN, SCHAR_MAX, /*TCanHoldPtr*/false, CORINFO_TYPE_INT>();
            break;
        case CEE_CONV_OVF_I2_UN:
            ConvOvfUn<INT16, SHRT_MIN, SHRT_MAX, /*TCanHoldPtr*/false, CORINFO_TYPE_INT>();
            break;
        case CEE_CONV_OVF_I4_UN:
            ConvOvfUn<INT32, INT_MIN, INT_MAX, /*TCanHoldPtr*/false, CORINFO_TYPE_INT>();
            break;
        case CEE_CONV_OVF_I8_UN:
            ConvOvfUn<INT64, _I64_MIN, _I64_MAX, /*TCanHoldPtr*/true, CORINFO_TYPE_LONG>();
            break;
        case CEE_CONV_OVF_U1_UN:
            ConvOvfUn<UINT8, 0, UCHAR_MAX, /*TCanHoldPtr*/false, CORINFO_TYPE_INT>();
            break;
        case CEE_CONV_OVF_U2_UN:
            ConvOvfUn<UINT16, 0, USHRT_MAX, /*TCanHoldPtr*/false, CORINFO_TYPE_INT>();
            break;
        case CEE_CONV_OVF_U4_UN:
            ConvOvfUn<UINT32, 0, UINT_MAX, /*TCanHoldPtr*/false, CORINFO_TYPE_INT>();
            break;
        case CEE_CONV_OVF_U8_UN:
            ConvOvfUn<UINT64, 0, _UI64_MAX, /*TCanHoldPtr*/true, CORINFO_TYPE_LONG>();
            break;
        case CEE_CONV_OVF_I_UN:
            if (sizeof(NativeInt) == 4)
            {
                ConvOvfUn<NativeInt, INT_MIN, INT_MAX, /*TCanHoldPtr*/true, CORINFO_TYPE_NATIVEINT>();
            }
            else
            {
                _ASSERTE(sizeof(NativeInt) == 8);
                ConvOvfUn<NativeInt, _I64_MIN, _I64_MAX, /*TCanHoldPtr*/true, CORINFO_TYPE_NATIVEINT>();
            }
            break;
        case CEE_CONV_OVF_U_UN:
            if (sizeof(NativeUInt) == 4)
            {
                ConvOvfUn<NativeUInt, 0, UINT_MAX, /*TCanHoldPtr*/true, CORINFO_TYPE_NATIVEINT>();
            }
            else
            {
                _ASSERTE(sizeof(NativeUInt) == 8);
                ConvOvfUn<NativeUInt, 0, _UI64_MAX, /*TCanHoldPtr*/true, CORINFO_TYPE_NATIVEINT>();
            }
            break;
        case CEE_BOX:
            Box();
            continue;
        case CEE_NEWARR:
            NewArr();
            continue;
        case CEE_LDLEN:
            LdLen();
            break;
        case CEE_LDELEMA:
            LdElem</*takeAddr*/true>();
            continue;
        case CEE_LDELEM_I1:
            LdElemWithType<INT8, false, CORINFO_TYPE_INT>();
            break;
        case CEE_LDELEM_U1:
            LdElemWithType<UINT8, false, CORINFO_TYPE_INT>();
            break;
        case CEE_LDELEM_I2:
            LdElemWithType<INT16, false, CORINFO_TYPE_INT>();
            break;
        case CEE_LDELEM_U2:
            LdElemWithType<UINT16, false, CORINFO_TYPE_INT>();
            break;
        case CEE_LDELEM_I4:
            LdElemWithType<INT32, false, CORINFO_TYPE_INT>();
            break;
        case CEE_LDELEM_U4:
            LdElemWithType<UINT32, false, CORINFO_TYPE_INT>();
            break;
        case CEE_LDELEM_I8:
            LdElemWithType<INT64, false, CORINFO_TYPE_LONG>();
            break;
            // Note that the ECMA spec defines a "LDELEM_U8", but it is the same instruction number as LDELEM_I8 (since
            // when loading to the widest width, signed/unsigned doesn't matter).
        case CEE_LDELEM_I:
            LdElemWithType<NativeInt, false, CORINFO_TYPE_NATIVEINT>();
            break;
        case CEE_LDELEM_R4:
            LdElemWithType<float, false, CORINFO_TYPE_FLOAT>();
            break;
        case CEE_LDELEM_R8:
            LdElemWithType<double, false, CORINFO_TYPE_DOUBLE>();
            break;
        case CEE_LDELEM_REF:
            LdElemWithType<Object*, true, CORINFO_TYPE_CLASS>();
            break;
        case CEE_STELEM_I:
            StElemWithType<NativeInt, false>();
            break;
        case CEE_STELEM_I1:
            StElemWithType<INT8, false>();
            break;
        case CEE_STELEM_I2:
            StElemWithType<INT16, false>();
            break;
        case CEE_STELEM_I4:
            StElemWithType<INT32, false>();
            break;
        case CEE_STELEM_I8:
            StElemWithType<INT64, false>();
            break;
        case CEE_STELEM_R4:
            StElemWithType<float, false>();
            break;
        case CEE_STELEM_R8:
            StElemWithType<double, false>();
            break;
        case CEE_STELEM_REF:
            StElemWithType<Object*, true>();
            break;
        case CEE_LDELEM:
            LdElem</*takeAddr*/false>();
            continue;
        case CEE_STELEM:
            StElem();
            continue;
        case CEE_UNBOX_ANY:
            UnboxAny();
            continue;
        case CEE_CONV_OVF_I1:
            ConvOvf<INT8, SCHAR_MIN, SCHAR_MAX, /*TCanHoldPtr*/false, CORINFO_TYPE_INT>();
            break;
        case CEE_CONV_OVF_U1:
            ConvOvf<UINT8, 0, UCHAR_MAX, /*TCanHoldPtr*/false, CORINFO_TYPE_INT>();
            break;
        case CEE_CONV_OVF_I2:
            ConvOvf<INT16, SHRT_MIN, SHRT_MAX, /*TCanHoldPtr*/false, CORINFO_TYPE_INT>();
            break;
        case CEE_CONV_OVF_U2:
            ConvOvf<UINT16, 0, USHRT_MAX, /*TCanHoldPtr*/false, CORINFO_TYPE_INT>();
            break;
        case CEE_CONV_OVF_I4:
            ConvOvf<INT32, INT_MIN, INT_MAX, /*TCanHoldPtr*/false, CORINFO_TYPE_INT>();
            break;
        case CEE_CONV_OVF_U4:
            ConvOvf<UINT32, 0, UINT_MAX, /*TCanHoldPtr*/false, CORINFO_TYPE_INT>();
            break;
        case CEE_CONV_OVF_I8:
            ConvOvf<INT64, _I64_MIN, _I64_MAX, /*TCanHoldPtr*/true, CORINFO_TYPE_LONG>();
            break;
        case CEE_CONV_OVF_U8:
            ConvOvf<UINT64, 0, _UI64_MAX, /*TCanHoldPtr*/true, CORINFO_TYPE_LONG>();
            break;
        case CEE_REFANYVAL:
            RefanyVal();
            continue;
        case CEE_CKFINITE:
            CkFinite();
            break;
        case CEE_MKREFANY:
            MkRefany();
            continue;
        case CEE_LDTOKEN:
            LdToken();
            continue;
        case CEE_CONV_U2:
            Conv<UINT16, /*TIsUnsigned*/true, /*TCanHoldPtr*/false, /*TIsShort*/true, CORINFO_TYPE_INT>();
            break;
        case CEE_CONV_U1:
            Conv<UINT8, /*TIsUnsigned*/true, /*TCanHoldPtr*/false, /*TIsShort*/true, CORINFO_TYPE_INT>();
            break;
        case CEE_CONV_I:
            Conv<NativeInt, /*TIsUnsigned*/false, /*TCanHoldPtr*/true, /*TIsShort*/false, CORINFO_TYPE_NATIVEINT>();
            break;
        case CEE_CONV_OVF_I:
            if (sizeof(NativeInt) == 4)
            {
                ConvOvf<NativeInt, INT_MIN, INT_MAX, /*TCanHoldPtr*/true, CORINFO_TYPE_NATIVEINT>();
            }
            else
            {
                _ASSERTE(sizeof(NativeInt) == 8);
                ConvOvf<NativeInt, _I64_MIN, _I64_MAX, /*TCanHoldPtr*/true, CORINFO_TYPE_NATIVEINT>();
            }
            break;
        case CEE_CONV_OVF_U:
            if (sizeof(NativeUInt) == 4)
            {
                ConvOvf<NativeUInt, 0, UINT_MAX, /*TCanHoldPtr*/true, CORINFO_TYPE_NATIVEINT>();
            }
            else
            {
                _ASSERTE(sizeof(NativeUInt) == 8);
                ConvOvf<NativeUInt, 0, _UI64_MAX, /*TCanHoldPtr*/true, CORINFO_TYPE_NATIVEINT>();
            }
            break;
        case CEE_ADD_OVF:
            BinaryArithOvfOp<BA_Add, /*asUnsigned*/false>();
            break;
        case CEE_ADD_OVF_UN:
            BinaryArithOvfOp<BA_Add, /*asUnsigned*/true>();
            break;
        case CEE_MUL_OVF:
            BinaryArithOvfOp<BA_Mul, /*asUnsigned*/false>();
            break;
        case CEE_MUL_OVF_UN:
            BinaryArithOvfOp<BA_Mul, /*asUnsigned*/true>();
            break;
        case CEE_SUB_OVF:
            BinaryArithOvfOp<BA_Sub, /*asUnsigned*/false>();
            break;
        case CEE_SUB_OVF_UN:
            BinaryArithOvfOp<BA_Sub, /*asUnsigned*/true>();
            break;
        case CEE_ENDFINALLY:
            // We have just ended a finally.
            // If we were called during exception dispatch,
            // rethrow the exception on our way out.
            if (m_leaveInfoStack.IsEmpty())
            {
                Object* finallyException = NULL;

                {
                    GCX_FORBID();
                    _ASSERTE(m_inFlightException != NULL);
                    finallyException = m_inFlightException;
                    INTERPLOG("endfinally handling for %s, %p, %p\n", methName, m_methInfo, finallyException);
                    m_inFlightException = NULL;
                }

                COMPlusThrow(ObjectToOBJECTREF(finallyException));
                UNREACHABLE();
            }
            // Otherwise, see if there's another finally block to
            // execute as part of processing the current LEAVE...
            else if (!SearchForCoveringFinally())
            {
                // No, there isn't -- go to the leave target.
                _ASSERTE(!m_leaveInfoStack.IsEmpty());
                LeaveInfo li = m_leaveInfoStack.Pop();
                ExecuteBranch(li.m_target);
            }
            // Yes, there, is, and SearchForCoveringFinally set us up to start executing it.
            continue; // Skip the default m_ILCodePtr++ at bottom of loop.

        case CEE_STIND_I:
            StInd<NativeInt>();
            break;
        case CEE_CONV_U:
            Conv<NativeUInt, /*TIsUnsigned*/true, /*TCanHoldPtr*/true, /*TIsShort*/false, CORINFO_TYPE_NATIVEINT>();
            break;
        case CEE_PREFIX7:
            NYI_INTERP("Unimplemented opcode: CEE_PREFIX7");
            break;
        case CEE_PREFIX6:
            NYI_INTERP("Unimplemented opcode: CEE_PREFIX6");
            break;
        case CEE_PREFIX5:
            NYI_INTERP("Unimplemented opcode: CEE_PREFIX5");
            break;
        case CEE_PREFIX4:
            NYI_INTERP("Unimplemented opcode: CEE_PREFIX4");
            break;
        case CEE_PREFIX3:
            NYI_INTERP("Unimplemented opcode: CEE_PREFIX3");
            break;
        case CEE_PREFIX2:
            NYI_INTERP("Unimplemented opcode: CEE_PREFIX2");
            break;
        case CEE_PREFIX1:
            // This is the prefix for all the 2-byte opcodes.
            // Figure out the second byte of the 2-byte opcode.
            ops = *(m_ILCodePtr + 1);
#if INTERP_ILINSTR_PROFILE
            // Take one away from PREFIX1, which we won't count.
            InterlockedDecrement(&s_ILInstrExecs[CEE_PREFIX1]);
            // Credit instead to the 2-byte instruction index.
            InterlockedIncrement(&s_ILInstr2ByteExecs[ops]);
#endif // INTERP_ILINSTR_PROFILE
            switch (ops)
            {
            case TWOBYTE_CEE_ARGLIST:
                // NYI_INTERP("Unimplemented opcode: TWOBYTE_CEE_ARGLIST");
                _ASSERTE(m_methInfo->m_varArgHandleArgNum != NO_VA_ARGNUM);
                LdArgA(m_methInfo->m_varArgHandleArgNum);
                m_ILCodePtr += 2;
                break;

            case TWOBYTE_CEE_CEQ:
                CompareOp<CO_EQ>();
                m_ILCodePtr += 2;
                break;
            case TWOBYTE_CEE_CGT:
                CompareOp<CO_GT>();
                m_ILCodePtr += 2;
                break;
            case TWOBYTE_CEE_CGT_UN:
                CompareOp<CO_GT_UN>();
                m_ILCodePtr += 2;
                break;
            case TWOBYTE_CEE_CLT:
                CompareOp<CO_LT>();
                m_ILCodePtr += 2;
                break;
            case TWOBYTE_CEE_CLT_UN:
                CompareOp<CO_LT_UN>();
                m_ILCodePtr += 2;
                break;

            case TWOBYTE_CEE_LDARG:
                m_ILCodePtr += 2;
                argNums = getU2LittleEndian(m_ILCodePtr);
                LdArg(argNums);
                m_ILCodePtr += 2;
                break;
            case TWOBYTE_CEE_LDARGA:
                m_ILCodePtr += 2;
                argNums = getU2LittleEndian(m_ILCodePtr);
                LdArgA(argNums);
                m_ILCodePtr += 2;
                break;
            case TWOBYTE_CEE_STARG:
                m_ILCodePtr += 2;
                argNums = getU2LittleEndian(m_ILCodePtr);
                StArg(argNums);
                m_ILCodePtr += 2;
                break;

            case TWOBYTE_CEE_LDLOC:
                m_ILCodePtr += 2;
                argNums = getU2LittleEndian(m_ILCodePtr);
                LdLoc(argNums);
                m_ILCodePtr += 2;
                break;
            case TWOBYTE_CEE_LDLOCA:
                m_ILCodePtr += 2;
                argNums = getU2LittleEndian(m_ILCodePtr);
                LdLocA(argNums);
                m_ILCodePtr += 2;
                break;
            case TWOBYTE_CEE_STLOC:
                m_ILCodePtr += 2;
                argNums = getU2LittleEndian(m_ILCodePtr);
                StLoc(argNums);
                m_ILCodePtr += 2;
                break;

            case TWOBYTE_CEE_CONSTRAINED:
                RecordConstrainedCall();
                break;

            case TWOBYTE_CEE_VOLATILE:
                // Set a flag that causes a memory barrier to be associated with the next load or store.
                m_volatileFlag = true;
                m_ILCodePtr += 2;
                break;

            case TWOBYTE_CEE_LDFTN:
                LdFtn();
                break;

            case TWOBYTE_CEE_INITOBJ:
                InitObj();
                break;

            case TWOBYTE_CEE_LOCALLOC:
                LocAlloc();
                m_ILCodePtr += 2;
                break;

            case TWOBYTE_CEE_LDVIRTFTN:
                LdVirtFtn();
                break;

            case TWOBYTE_CEE_SIZEOF:
                Sizeof();
                break;

            case TWOBYTE_CEE_RETHROW:
                Rethrow();
                break;

            case TWOBYTE_CEE_READONLY:
                m_readonlyFlag = true;
                m_ILCodePtr += 2;
                // A comment in importer.cpp indicates that READONLY may also apply to calls.  We'll see.
                _ASSERTE_MSG(*m_ILCodePtr == CEE_LDELEMA, "According to the ECMA spec, READONLY may only precede LDELEMA");
                break;

            case TWOBYTE_CEE_INITBLK:
                InitBlk();
                break;

            case TWOBYTE_CEE_CPBLK:
                CpBlk();
                break;

            case TWOBYTE_CEE_ENDFILTER:
                EndFilter();
                break;

            case TWOBYTE_CEE_UNALIGNED:
                // Nothing to do here.
                m_ILCodePtr += 3;
                break;

            case TWOBYTE_CEE_TAILCALL:
                // TODO: Needs revisiting when implementing tail call.
                // NYI_INTERP("Unimplemented opcode: TWOBYTE_CEE_TAILCALL");
                m_ILCodePtr += 2;
                break;

            case TWOBYTE_CEE_REFANYTYPE:
                RefanyType();
                break;

            default:
                UNREACHABLE();
                break;
            }
            continue;

        case CEE_PREFIXREF:
            NYI_INTERP("Unimplemented opcode: CEE_PREFIXREF");
            m_ILCodePtr++;
            continue;

        default:
            UNREACHABLE();
            continue;
        }
        m_ILCodePtr++;
    }
ExitEvalLoop:;
        INTERPLOG("DONE %d, %s\n", m_methInfo->m_stubNum, m_methInfo->m_methName);
    }
    EX_CATCH
    {
        INTERPLOG("EXCEPTION %d (throw), %s\n", m_methInfo->m_stubNum, m_methInfo->m_methName);

        bool handleException = false;
        OBJECTREF orThrowable = NULL;
        GCX_COOP_NO_DTOR();

        orThrowable = GET_THROWABLE();

        if (m_filterNextScan != 0)
        {
            // We are in the middle of a filter scan and an exception is thrown inside
            // a filter. We are supposed to swallow it and assume the filter did not
            // handle the exception.
            m_curStackHt = 0;
            m_largeStructOperandStackHt = 0;
            LdIcon(0);
            EndFilter();
            handleException = true;
        }
        else
        {
            // orThrowable must be protected.  MethodHandlesException() will place orThrowable
            // into the operand stack (a permanently protected area) if it returns true.
            GCPROTECT_BEGIN(orThrowable);
            handleException = MethodHandlesException(orThrowable);
            GCPROTECT_END();
        }

        if (handleException)
        {
            GetThread()->SafeSetThrowables(orThrowable
                DEBUG_ARG(ThreadExceptionState::STEC_CurrentTrackerEqualNullOkForInterpreter));
            goto EvalLoop;
        }
        else
        {
            INTERPLOG("EXCEPTION %d (rethrow), %s\n", m_methInfo->m_stubNum, m_methInfo->m_methName);
            EX_RETHROW;
        }
    }
    EX_END_CATCH(RethrowTransientExceptions)
}

#ifdef _MSC_VER
#pragma optimize("", on)
#endif

void Interpreter::EndFilter()
{
    unsigned handles = OpStackGet<unsigned>(0);
    // If the filter decides to handle the exception, then go to the handler offset.
    if (handles)
    {
        // We decided to handle the exception, so give all EH entries a chance to
        // handle future exceptions. Clear scan.
        m_filterNextScan = 0;
        ExecuteBranch(m_methInfo->m_ILCode + m_filterHandlerOffset);
    }
    // The filter decided not to handle the exception, ask if there is some other filter
    // lined up to try to handle it or some other catch/finally handlers will handle it.
    // If no one handles the exception, rethrow and be done with it.
    else
    {
        bool handlesEx = false;
        {
            OBJECTREF orThrowable = ObjectToOBJECTREF(m_inFlightException);
            GCPROTECT_BEGIN(orThrowable);
            handlesEx = MethodHandlesException(orThrowable);
            GCPROTECT_END();
        }
        if (!handlesEx)
        {
            // Just clear scan before rethrowing to give any EH entry a chance to handle
            // the "rethrow".
            m_filterNextScan = 0;
            Object* filterException = NULL;
            {
                GCX_FORBID();
                _ASSERTE(m_inFlightException != NULL);
                filterException = m_inFlightException;
                INTERPLOG("endfilter handling for %s, %p, %p\n", m_methInfo->m_methName, m_methInfo, filterException);
                m_inFlightException = NULL;
            }

            COMPlusThrow(ObjectToOBJECTREF(filterException));
            UNREACHABLE();
        }
        else
        {
            // Let it do another round of filter:end-filter or handler block.
            // During the next end filter, we will reuse m_filterNextScan and
            // continue searching where we left off. Note however, while searching,
            // any of the filters could throw an exception. But this is supposed to
            // be swallowed and endfilter should be called with a value of 0 on the
            // stack.
        }
    }
}

bool Interpreter::MethodHandlesException(OBJECTREF orThrowable)
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    bool handlesEx = false;

    if (orThrowable != NULL)
    {
        // Don't catch ThreadAbort and other uncatchable exceptions
        if (!IsUncatchable(&orThrowable))
        {
            // Does the current method catch this?  The clauses are defined by offsets, so get that.
            // However, if we are in the middle of a filter scan, make sure we get the offset of the
            // excepting code, rather than the offset of the filter body.
            DWORD curOffset = (m_filterNextScan != 0) ? m_filterExcILOffset : CurOffset();
            TypeHandle orThrowableTH = TypeHandle(orThrowable->GetMethodTable());

            GCPROTECT_BEGIN(orThrowable);
            GCX_PREEMP();

            // Perform a filter scan or regular walk of the EH Table. Filter scan is performed when
            // we are evaluating a series of filters to handle the exception until the first handler
            // (filter's or otherwise) that will handle the exception.
            for (unsigned XTnum = m_filterNextScan; XTnum < m_methInfo->m_ehClauseCount; XTnum++)
            {
                CORINFO_EH_CLAUSE clause;
                m_interpCeeInfo.getEHinfo(m_methInfo->m_method, XTnum, &clause);
                _ASSERTE(clause.HandlerLength != (unsigned)-1); // @DEPRECATED

                // First, is the current offset in the try block?
                if (clause.TryOffset <= curOffset && curOffset < clause.TryOffset + clause.TryLength)
                {
                    unsigned handlerOffset = 0;
                    // CORINFO_EH_CLAUSE_NONE represents 'catch' blocks
                    if (clause.Flags == CORINFO_EH_CLAUSE_NONE)
                    {
                        // Now, does the catch block handle the thrown exception type?
                        CORINFO_CLASS_HANDLE excType = FindClass(clause.ClassToken InterpTracingArg(RTK_CheckHandlesException));
                        if (ExceptionIsOfRightType(TypeHandle::FromPtr(excType), orThrowableTH))
                        {
                            GCX_COOP();
                            // Push the exception object onto the operand stack.
                            OpStackSet<OBJECTREF>(0, orThrowable);
                            OpStackTypeSet(0, InterpreterType(CORINFO_TYPE_CLASS));
                            m_curStackHt = 1;
                            m_largeStructOperandStackHt = 0;
                            handlerOffset = clause.HandlerOffset;
                            handlesEx = true;
                            m_filterNextScan = 0;
                        }
                        else
                        {
                            GCX_COOP();
                            // Handle a wrapped exception.
                            OBJECTREF orUnwrapped = PossiblyUnwrapThrowable(orThrowable, GetMethodDesc()->GetAssembly());
                            if (ExceptionIsOfRightType(TypeHandle::FromPtr(excType), orUnwrapped->GetTypeHandle()))
                            {
                                // Push the exception object onto the operand stack.
                                OpStackSet<OBJECTREF>(0, orUnwrapped);
                                OpStackTypeSet(0, InterpreterType(CORINFO_TYPE_CLASS));
                                m_curStackHt = 1;
                                m_largeStructOperandStackHt = 0;
                                handlerOffset = clause.HandlerOffset;
                                handlesEx = true;
                                m_filterNextScan = 0;
                            }
                        }
                    }
                    else if (clause.Flags == CORINFO_EH_CLAUSE_FILTER)
                    {
                        GCX_COOP();
                        // Push the exception object onto the operand stack.
                        OpStackSet<OBJECTREF>(0, orThrowable);
                        OpStackTypeSet(0, InterpreterType(CORINFO_TYPE_CLASS));
                        m_curStackHt = 1;
                        m_largeStructOperandStackHt = 0;
                        handlerOffset = clause.FilterOffset;
                        m_inFlightException = OBJECTREFToObject(orThrowable);
                        handlesEx = true;
                        m_filterHandlerOffset = clause.HandlerOffset;
                        m_filterNextScan = XTnum + 1;
                        m_filterExcILOffset = curOffset;
                    }
                    else if (clause.Flags == CORINFO_EH_CLAUSE_FAULT ||
                            clause.Flags == CORINFO_EH_CLAUSE_FINALLY)
                    {
                        GCX_COOP();
                        // Save the exception object to rethrow.
                        m_inFlightException = OBJECTREFToObject(orThrowable);
                        // Empty the operand stack.
                        m_curStackHt = 0;
                        m_largeStructOperandStackHt = 0;
                        handlerOffset = clause.HandlerOffset;
                        handlesEx = true;
                        m_filterNextScan = 0;
                    }

                    // Reset the interpreter loop in preparation of calling the handler.
                    if (handlesEx)
                    {
                        // Set the IL offset of the handler.
                        ExecuteBranch(m_methInfo->m_ILCode + handlerOffset);

                        // If an exception occurs while attempting to leave a protected scope,
                        // we empty the 'leave' info stack upon entering the handler.
                        while (!m_leaveInfoStack.IsEmpty())
                        {
                            m_leaveInfoStack.Pop();
                        }

                        // Some things are set up before a call, and must be cleared on an exception caught be the caller.
                        // A method that returns a struct allocates local space for the return value, and "registers" that
                        // space and the type so that it's scanned if a GC happens.  "Unregister" it if we throw an exception
                        // in the call, and handle it in the caller.  (If it's not handled by the caller, the Interpreter is
                        // deallocated, so it's value doesn't matter.)
                        m_structRetValITPtr = NULL;
                        m_callThisArg = NULL;
                        m_argsSize = 0;

                        break;
                    }
                }
            }
            GCPROTECT_END();
        }
        if (!handlesEx)
        {
            DoMonitorExitWork();
        }
    }
    return handlesEx;
}

static unsigned OpFormatExtraSize(opcode_format_t format) {
    switch (format)
    {
    case InlineNone:
        return 0;
    case InlineVar:
        return 2;
    case InlineI:
    case InlineBrTarget:
    case InlineMethod:
    case InlineField:
    case InlineType:
    case InlineString:
    case InlineSig:
    case InlineRVA:
    case InlineTok:
    case ShortInlineR:
        return 4;

    case InlineR:
    case InlineI8:
        return 8;

    case InlineSwitch:
        return 0;  // We'll handle this specially.

    case ShortInlineVar:
    case ShortInlineI:
    case ShortInlineBrTarget:
        return 1;

    default:
        _ASSERTE(false);
        return 0;
    }
}



static unsigned opSizes1Byte[CEE_COUNT];
static bool     opSizes1ByteInit = false;

static void OpSizes1ByteInit()
{
    if (opSizes1ByteInit) return;
#define OPDEF(name, stringname, stackpop, stackpush, params, kind, len, byte1, byte2, ctrl) \
    opSizes1Byte[name] = len + OpFormatExtraSize(params);
#include "opcode.def"
#undef  OPDEF
    opSizes1ByteInit = true;
};

// static
bool Interpreter::MethodMayHaveLoop(BYTE* ilCode, unsigned codeSize)
{
    OpSizes1ByteInit();
    int delta;
    BYTE* ilCodeLim = ilCode + codeSize;
    while (ilCode < ilCodeLim)
    {
        unsigned op = *ilCode;
        switch (op)
        {
        case CEE_BR_S: case CEE_BRFALSE_S: case CEE_BRTRUE_S:
        case CEE_BEQ_S: case CEE_BGE_S: case CEE_BGT_S: case CEE_BLE_S: case CEE_BLT_S:
        case CEE_BNE_UN_S: case CEE_BGE_UN_S: case CEE_BGT_UN_S: case CEE_BLE_UN_S: case CEE_BLT_UN_S:
        case CEE_LEAVE_S:
            delta = getI1(ilCode + 1);
            if (delta < 0) return true;
            ilCode += 2;
            break;

        case CEE_BR: case CEE_BRFALSE: case CEE_BRTRUE:
        case CEE_BEQ: case CEE_BGE: case CEE_BGT: case CEE_BLE: case CEE_BLT:
        case CEE_BNE_UN: case CEE_BGE_UN: case CEE_BGT_UN: case CEE_BLE_UN: case CEE_BLT_UN:
        case CEE_LEAVE:
            delta = getI4LittleEndian(ilCode + 1);
            if (delta < 0) return true;
            ilCode += 5;
            break;

        case CEE_SWITCH:
            {
                UINT32 n = getU4LittleEndian(ilCode + 1);
                UINT32 instrSize = 1 + (n + 1)*4;
                for (unsigned i = 0; i < n; i++) {
                    delta = getI4LittleEndian(ilCode + (5 + i * 4));
                    if (delta < 0) return true;
                }
                ilCode += instrSize;
                break;
            }

        case CEE_PREFIX1:
            op = *(ilCode + 1) + 0x100;
            _ASSERTE(op < CEE_COUNT);  // Bounds check for below.
            // deliberate fall-through here.
            __fallthrough;
        default:
            // For the rest of the 1-byte instructions, we'll use a table-driven approach.
            ilCode += opSizes1Byte[op];
            break;
        }
    }
    return false;

}

void Interpreter::BackwardsBranchActions(int offset)
{
    // TODO: Figure out how to do a GC poll.
}

bool Interpreter::SearchForCoveringFinally()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_ANY;
    } CONTRACTL_END;

    _ASSERTE_MSG(!m_leaveInfoStack.IsEmpty(), "precondition");

    LeaveInfo& li = m_leaveInfoStack.PeekRef();

    GCX_PREEMP();

    for (unsigned XTnum = li.m_nextEHIndex; XTnum < m_methInfo->m_ehClauseCount; XTnum++)
    {
        CORINFO_EH_CLAUSE clause;
        m_interpCeeInfo.getEHinfo(m_methInfo->m_method, XTnum, &clause);
        _ASSERTE(clause.HandlerLength != (unsigned)-1); // @DEPRECATED

        // First, is the offset of the leave instruction in the try block?
        unsigned tryEndOffset = clause.TryOffset + clause.TryLength;
        if (clause.TryOffset <= li.m_offset && li.m_offset < tryEndOffset)
        {
            // Yes: is it a finally, and is its target outside the try block?
            size_t targOffset = (li.m_target - m_methInfo->m_ILCode);
            if (clause.Flags == CORINFO_EH_CLAUSE_FINALLY
                && !(clause.TryOffset <= targOffset && targOffset < tryEndOffset))
            {
                m_ILCodePtr = m_methInfo->m_ILCode + clause.HandlerOffset;
                li.m_nextEHIndex = XTnum + 1;
                return true;
            }
        }
    }

    // Caller will handle popping the leave info stack.
    return false;
}

// static
void Interpreter::GCScanRoots(promote_func* pf, ScanContext* sc, void* interp0)
{
    Interpreter* interp = reinterpret_cast<Interpreter*>(interp0);
    interp->GCScanRoots(pf, sc);
}

void Interpreter::GCScanRoots(promote_func* pf, ScanContext* sc)
{
    // Report inbound arguments, if the interpreter has not been invoked directly.
    // (In the latter case, the arguments are reported by the calling method.)
    if (!m_directCall)
    {
        for (unsigned i = 0; i < m_methInfo->m_numArgs; i++)
        {
            GCScanRootAtLoc(reinterpret_cast<Object**>(GetArgAddr(i)), GetArgType(i), pf, sc);
        }
    }

    if (m_methInfo->GetFlag<InterpreterMethodInfo::Flag_hasThisArg>())
    {
        if (m_methInfo->GetFlag<InterpreterMethodInfo::Flag_thisArgIsObjPtr>())
        {
            GCScanRootAtLoc(&m_thisArg, InterpreterType(CORINFO_TYPE_CLASS), pf, sc);
        }
        else
        {
            GCScanRootAtLoc(&m_thisArg, InterpreterType(CORINFO_TYPE_BYREF), pf, sc);
        }
    }

    // This is the "this" argument passed in to DoCallWork.  (Note that we treat this as a byref; it
    // might be, for a struct instance method, and this covers the object pointer case as well.)
    GCScanRootAtLoc(reinterpret_cast<Object**>(&m_callThisArg), InterpreterType(CORINFO_TYPE_BYREF), pf, sc);

    // Scan the exception object that we'll rethrow at the end of the finally block.
    GCScanRootAtLoc(reinterpret_cast<Object**>(&m_inFlightException), InterpreterType(CORINFO_TYPE_CLASS), pf, sc);

    // A retBufArg, may, in some cases, be a byref into the heap.
    if (m_retBufArg != NULL)
    {
        GCScanRootAtLoc(reinterpret_cast<Object**>(&m_retBufArg), InterpreterType(CORINFO_TYPE_BYREF), pf, sc);
    }

    if (m_structRetValITPtr != NULL)
    {
        GCScanRootAtLoc(reinterpret_cast<Object**>(m_structRetValTempSpace), *m_structRetValITPtr, pf, sc);
    }

    // We'll conservatively assume that we might have a security object.
    GCScanRootAtLoc(reinterpret_cast<Object**>(&m_securityObject), InterpreterType(CORINFO_TYPE_CLASS), pf, sc);

    // Do locals.
    for (unsigned i = 0; i < m_methInfo->m_numLocals; i++)
    {
        InterpreterType it = m_methInfo->m_localDescs[i].m_type;
        void* localPtr = NULL;
        if (it.IsLargeStruct(&m_interpCeeInfo))
        {
            void* structPtr = ArgSlotEndiannessFixup(reinterpret_cast<ARG_SLOT*>(FixedSizeLocalSlot(i)), sizeof(void**));
            localPtr = *reinterpret_cast<void**>(structPtr);
        }
        else
        {
            localPtr = ArgSlotEndiannessFixup(reinterpret_cast<ARG_SLOT*>(FixedSizeLocalSlot(i)), it.Size(&m_interpCeeInfo));
        }
        GCScanRootAtLoc(reinterpret_cast<Object**>(localPtr), it, pf, sc, m_methInfo->GetPinningBit(i));
    }

    // Do current ostack.
    for (unsigned i = 0; i < m_curStackHt; i++)
    {
        InterpreterType it = OpStackTypeGet(i);
        if (it.IsLargeStruct(&m_interpCeeInfo))
        {
            Object** structPtr = reinterpret_cast<Object**>(OpStackGet<void*>(i));
            // If the ostack value is a pointer to a local var value, don't scan, since we already
            // scanned the variable value above.
            if (!IsInLargeStructLocalArea(structPtr))
            {
                GCScanRootAtLoc(structPtr, it, pf, sc);
            }
        }
        else
        {
            void* stackPtr = OpStackGetAddr(i, it.Size(&m_interpCeeInfo));
            GCScanRootAtLoc(reinterpret_cast<Object**>(stackPtr), it, pf, sc);
        }
    }

    // Any outgoing arguments for a call in progress.
    for (unsigned i = 0; i < m_argsSize; i++)
    {
        // If a call has a large struct argument, we'll have pushed a pointer to the entry for that argument on the
        // largeStructStack of the current Interpreter.  That will be scanned by the code above, so just skip it.
        InterpreterType undef(CORINFO_TYPE_UNDEF);
        InterpreterType it = m_argTypes[i];
        if (it != undef && !it.IsLargeStruct(&m_interpCeeInfo))
        {
            BYTE* argPtr = ArgSlotEndiannessFixup(&m_args[i], it.Size(&m_interpCeeInfo));
            GCScanRootAtLoc(reinterpret_cast<Object**>(argPtr), it, pf, sc);
        }
    }
}

void Interpreter::GCScanRootAtLoc(Object** loc, InterpreterType it, promote_func* pf, ScanContext* sc, bool pinningRef)
{
    switch (it.ToCorInfoType())
    {
    case CORINFO_TYPE_CLASS:
    case CORINFO_TYPE_STRING:
        {
            DWORD flags = 0;
            if (pinningRef) flags |= GC_CALL_PINNED;
            (*pf)(loc, sc, flags);
        }
        break;

    case CORINFO_TYPE_BYREF:
    case CORINFO_TYPE_REFANY:
        {
            DWORD flags = GC_CALL_INTERIOR;
            if (pinningRef) flags |= GC_CALL_PINNED;
            (*pf)(loc, sc, flags);
        }
        break;

    case CORINFO_TYPE_VALUECLASS:
        _ASSERTE(!pinningRef);
        GCScanValueClassRootAtLoc(loc, it.ToClassHandle(), pf, sc);
        break;

    default:
        _ASSERTE(!pinningRef);
        break;
    }
}

void Interpreter::GCScanValueClassRootAtLoc(Object** loc, CORINFO_CLASS_HANDLE valueClsHnd, promote_func* pf, ScanContext* sc)
{
    MethodTable* valClsMT = GetMethodTableFromClsHnd(valueClsHnd);
    ReportPointersFromValueType(pf, sc, valClsMT, loc);
}

// Returns "true" iff "cit" is "stack-normal": all integer types with byte size less than 4
// are folded to CORINFO_TYPE_INT; all remaining unsigned types are folded to their signed counterparts.
bool IsStackNormalType(CorInfoType cit)
{
    LIMITED_METHOD_CONTRACT;

    switch (cit)
    {
    case CORINFO_TYPE_UNDEF:
    case CORINFO_TYPE_VOID:
    case CORINFO_TYPE_BOOL:
    case CORINFO_TYPE_CHAR:
    case CORINFO_TYPE_BYTE:
    case CORINFO_TYPE_UBYTE:
    case CORINFO_TYPE_SHORT:
    case CORINFO_TYPE_USHORT:
    case CORINFO_TYPE_UINT:
    case CORINFO_TYPE_NATIVEUINT:
    case CORINFO_TYPE_ULONG:
    case CORINFO_TYPE_VAR:
    case CORINFO_TYPE_STRING:
    case CORINFO_TYPE_PTR:
        return false;

    case CORINFO_TYPE_INT:
    case CORINFO_TYPE_NATIVEINT:
    case CORINFO_TYPE_BYREF:
    case CORINFO_TYPE_CLASS:
    case CORINFO_TYPE_LONG:
    case CORINFO_TYPE_VALUECLASS:
    case CORINFO_TYPE_REFANY:
        // I chose to consider both float and double stack-normal; together these comprise
        // the "F" type of the ECMA spec.  This means I have to consider these to freely
        // interconvert.
    case CORINFO_TYPE_FLOAT:
    case CORINFO_TYPE_DOUBLE:
        return true;

    default:
        UNREACHABLE();
    }
}

CorInfoType CorInfoTypeStackNormalize(CorInfoType cit)
{
    LIMITED_METHOD_CONTRACT;

    switch (cit)
    {
    case CORINFO_TYPE_UNDEF:
        return CORINFO_TYPE_UNDEF;

    case CORINFO_TYPE_VOID:
    case CORINFO_TYPE_VAR:
        _ASSERTE_MSG(false, "Type that cannot be on the ostack.");
        return CORINFO_TYPE_UNDEF;

    case CORINFO_TYPE_BOOL:
    case CORINFO_TYPE_CHAR:
    case CORINFO_TYPE_BYTE:
    case CORINFO_TYPE_UBYTE:
    case CORINFO_TYPE_SHORT:
    case CORINFO_TYPE_USHORT:
    case CORINFO_TYPE_UINT:
        return CORINFO_TYPE_INT;

    case CORINFO_TYPE_NATIVEUINT:
    case CORINFO_TYPE_PTR:
        return CORINFO_TYPE_NATIVEINT;

    case CORINFO_TYPE_ULONG:
        return CORINFO_TYPE_LONG;

    case CORINFO_TYPE_STRING:
        return CORINFO_TYPE_CLASS;

    case CORINFO_TYPE_INT:
    case CORINFO_TYPE_NATIVEINT:
    case CORINFO_TYPE_BYREF:
    case CORINFO_TYPE_CLASS:
    case CORINFO_TYPE_LONG:
    case CORINFO_TYPE_VALUECLASS:
    case CORINFO_TYPE_REFANY:
        // I chose to consider both float and double stack-normal; together these comprise
        // the "F" type of the ECMA spec.  This means I have to consider these to freely
        // interconvert.
    case CORINFO_TYPE_FLOAT:
    case CORINFO_TYPE_DOUBLE:
        _ASSERTE(IsStackNormalType(cit));
        return cit;

    default:
        UNREACHABLE();
    }
}

InterpreterType InterpreterType::StackNormalize() const
{
    LIMITED_METHOD_CONTRACT;

    switch (ToCorInfoType())
    {
    case CORINFO_TYPE_BOOL:
    case CORINFO_TYPE_CHAR:
    case CORINFO_TYPE_BYTE:
    case CORINFO_TYPE_UBYTE:
    case CORINFO_TYPE_SHORT:
    case CORINFO_TYPE_USHORT:
    case CORINFO_TYPE_UINT:
        return InterpreterType(CORINFO_TYPE_INT);

    case CORINFO_TYPE_NATIVEUINT:
    case CORINFO_TYPE_PTR:
        return InterpreterType(CORINFO_TYPE_NATIVEINT);

    case CORINFO_TYPE_ULONG:
        return InterpreterType(CORINFO_TYPE_LONG);

    case CORINFO_TYPE_STRING:
        return InterpreterType(CORINFO_TYPE_CLASS);

    case CORINFO_TYPE_INT:
    case CORINFO_TYPE_NATIVEINT:
    case CORINFO_TYPE_BYREF:
    case CORINFO_TYPE_CLASS:
    case CORINFO_TYPE_LONG:
    case CORINFO_TYPE_VALUECLASS:
    case CORINFO_TYPE_REFANY:
    case CORINFO_TYPE_FLOAT:
    case CORINFO_TYPE_DOUBLE:
        return *const_cast<InterpreterType*>(this);

    case CORINFO_TYPE_UNDEF:
    case CORINFO_TYPE_VOID:
    case CORINFO_TYPE_VAR:
    default:
        _ASSERTE_MSG(false, "should not reach here");
        return *const_cast<InterpreterType*>(this);
    }
}

#ifdef _DEBUG
bool InterpreterType::MatchesWork(const InterpreterType it2, CEEInfo* info) const
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    if (*this == it2) return true;

    // Otherwise...
    CorInfoType cit1 = ToCorInfoType();
    CorInfoType cit2 = it2.ToCorInfoType();

    GCX_PREEMP();

    // An approximation: valueclasses of the same size match.
    if (cit1 == CORINFO_TYPE_VALUECLASS &&
        cit2 == CORINFO_TYPE_VALUECLASS &&
        Size(info) == it2.Size(info))
    {
        return true;
    }

    // NativeInt matches byref.  (In unsafe code).
    if ((cit1 == CORINFO_TYPE_BYREF && cit2 == CORINFO_TYPE_NATIVEINT))
        return true;

    // apparently the VM may do the optimization of reporting the return type of a method that
    // returns a struct of a single nativeint field *as* nativeint; and similarly with at least some other primitive types.
    // So weaken this check to allow that.
    // (The check is actually a little weaker still, since I don't want to crack the return type and make sure
    // that it has only a single nativeint member -- so I just ensure that the total size is correct).
    switch (cit1)
    {
    case CORINFO_TYPE_NATIVEINT:
    case CORINFO_TYPE_NATIVEUINT:
        _ASSERTE(sizeof(NativeInt) == sizeof(NativeUInt));
        if (it2.Size(info) == sizeof(NativeInt))
            return true;
        break;

    case CORINFO_TYPE_INT:
    case CORINFO_TYPE_UINT:
        _ASSERTE(sizeof(INT32) == sizeof(UINT32));
        if (it2.Size(info) == sizeof(INT32))
            return true;
        break;

    default:
        break;
    }

    // See if the second is a value type synonym for a primitive.
    if (cit2 == CORINFO_TYPE_VALUECLASS)
    {
        CorInfoType cit2prim = info->getTypeForPrimitiveValueClass(it2.ToClassHandle());
        if (cit2prim != CORINFO_TYPE_UNDEF)
        {
            InterpreterType it2prim(cit2prim);
            if (*this == it2prim.StackNormalize())
                return true;
        }
    }

    // Otherwise...
    return false;
}
#endif // _DEBUG

// Static
size_t CorInfoTypeSizeArray[] =
{
    /*CORINFO_TYPE_UNDEF           = 0x0*/0,
    /*CORINFO_TYPE_VOID            = 0x1*/0,
    /*CORINFO_TYPE_BOOL            = 0x2*/1,
    /*CORINFO_TYPE_CHAR            = 0x3*/2,
    /*CORINFO_TYPE_BYTE            = 0x4*/1,
    /*CORINFO_TYPE_UBYTE           = 0x5*/1,
    /*CORINFO_TYPE_SHORT           = 0x6*/2,
    /*CORINFO_TYPE_USHORT          = 0x7*/2,
    /*CORINFO_TYPE_INT             = 0x8*/4,
    /*CORINFO_TYPE_UINT            = 0x9*/4,
    /*CORINFO_TYPE_LONG            = 0xa*/8,
    /*CORINFO_TYPE_ULONG           = 0xb*/8,
    /*CORINFO_TYPE_NATIVEINT       = 0xc*/sizeof(void*),
    /*CORINFO_TYPE_NATIVEUINT      = 0xd*/sizeof(void*),
    /*CORINFO_TYPE_FLOAT           = 0xe*/4,
    /*CORINFO_TYPE_DOUBLE          = 0xf*/8,
    /*CORINFO_TYPE_STRING          = 0x10*/sizeof(void*),
    /*CORINFO_TYPE_PTR             = 0x11*/sizeof(void*),
    /*CORINFO_TYPE_BYREF           = 0x12*/sizeof(void*),
    /*CORINFO_TYPE_VALUECLASS      = 0x13*/0,
    /*CORINFO_TYPE_CLASS           = 0x14*/sizeof(void*),
    /*CORINFO_TYPE_REFANY          = 0x15*/sizeof(void*)*2,
    /*CORINFO_TYPE_VAR             = 0x16*/0,
};

bool CorInfoTypeIsUnsigned(CorInfoType cit)
{
    LIMITED_METHOD_CONTRACT;

    switch (cit)
    {
    case CORINFO_TYPE_UINT:
    case CORINFO_TYPE_NATIVEUINT:
    case CORINFO_TYPE_ULONG:
    case CORINFO_TYPE_UBYTE:
    case CORINFO_TYPE_USHORT:
    case CORINFO_TYPE_CHAR:
        return true;

    default:
        return false;
    }
}

bool CorInfoTypeIsIntegral(CorInfoType cit)
{
    LIMITED_METHOD_CONTRACT;

    switch (cit)
    {
    case CORINFO_TYPE_UINT:
    case CORINFO_TYPE_NATIVEUINT:
    case CORINFO_TYPE_ULONG:
    case CORINFO_TYPE_UBYTE:
    case CORINFO_TYPE_USHORT:
    case CORINFO_TYPE_INT:
    case CORINFO_TYPE_NATIVEINT:
    case CORINFO_TYPE_LONG:
    case CORINFO_TYPE_BYTE:
    case CORINFO_TYPE_BOOL:
    case CORINFO_TYPE_SHORT:
        return true;

    default:
        return false;
    }
}

bool CorInfoTypeIsFloatingPoint(CorInfoType cit)
{
    return cit == CORINFO_TYPE_FLOAT || cit == CORINFO_TYPE_DOUBLE;
}

bool CorInfoTypeIsFloatingPoint(CorInfoHFAElemType cihet)
{
    return cihet == CORINFO_HFA_ELEM_FLOAT || cihet == CORINFO_HFA_ELEM_DOUBLE;
}

bool CorElemTypeIsUnsigned(CorElementType cet)
{
    LIMITED_METHOD_CONTRACT;

    switch (cet)
    {
    case ELEMENT_TYPE_U1:
    case ELEMENT_TYPE_U2:
    case ELEMENT_TYPE_U4:
    case ELEMENT_TYPE_U8:
    case ELEMENT_TYPE_U:
        return true;

    default:
        return false;
    }
}

bool CorInfoTypeIsPointer(CorInfoType cit)
{
    LIMITED_METHOD_CONTRACT;
    switch (cit)
    {
    case CORINFO_TYPE_PTR:
    case CORINFO_TYPE_BYREF:
    case CORINFO_TYPE_NATIVEINT:
    case CORINFO_TYPE_NATIVEUINT:
        return true;

        // It seems like the ECMA spec doesn't allow this, but (at least) the managed C++
        // compiler expects the explicitly-sized pointer type of the platform pointer size to work:
    case CORINFO_TYPE_INT:
    case CORINFO_TYPE_UINT:
        return sizeof(NativeInt) == sizeof(INT32);
    case CORINFO_TYPE_LONG:
    case CORINFO_TYPE_ULONG:
        return sizeof(NativeInt) == sizeof(INT64);

    default:
        return false;
    }
}

void Interpreter::LdArg(int argNum)
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    LdFromMemAddr(GetArgAddr(argNum), GetArgType(argNum));
}

void Interpreter::LdArgA(int argNum)
{
    CONTRACTL {
        NOTHROW;
        GC_NOTRIGGER;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_BYREF));
    OpStackSet<void*>(m_curStackHt, reinterpret_cast<void*>(GetArgAddr(argNum)));
    m_curStackHt++;
}

void Interpreter::StArg(int argNum)
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    StToLocalMemAddr(GetArgAddr(argNum), GetArgType(argNum));
}


void Interpreter::LdLocA(int locNum)
{
    CONTRACTL {
        NOTHROW;
        GC_NOTRIGGER;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    InterpreterType tp = m_methInfo->m_localDescs[locNum].m_type;
    void* addr;
    if (tp.IsLargeStruct(&m_interpCeeInfo))
    {
        void* structPtr = ArgSlotEndiannessFixup(reinterpret_cast<ARG_SLOT*>(FixedSizeLocalSlot(locNum)), sizeof(void**));
        addr = *reinterpret_cast<void**>(structPtr);
    }
    else
    {
        addr = ArgSlotEndiannessFixup(reinterpret_cast<ARG_SLOT*>(FixedSizeLocalSlot(locNum)), tp.Size(&m_interpCeeInfo));
    }
    // The "addr" above, while a byref, is never a heap pointer, so we're robust if
    // any of these were to cause a GC.
    OpStackSet<void*>(m_curStackHt, addr);
    OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_BYREF));
    m_curStackHt++;
}

void Interpreter::LdIcon(INT32 c)
{
    CONTRACTL {
        NOTHROW;
        GC_NOTRIGGER;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_INT));
    OpStackSet<INT32>(m_curStackHt, c);
    m_curStackHt++;
}

void Interpreter::LdR4con(INT32 c)
{
    CONTRACTL {
        NOTHROW;
        GC_NOTRIGGER;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_FLOAT));
    OpStackSet<INT32>(m_curStackHt, c);
    m_curStackHt++;
}

void Interpreter::LdLcon(INT64 c)
{
    CONTRACTL {
        NOTHROW;
        GC_NOTRIGGER;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_LONG));
    OpStackSet<INT64>(m_curStackHt, c);
    m_curStackHt++;
}

void Interpreter::LdR8con(INT64 c)
{
    CONTRACTL {
        NOTHROW;
        GC_NOTRIGGER;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_DOUBLE));
    OpStackSet<INT64>(m_curStackHt, c);
    m_curStackHt++;
}

void Interpreter::LdNull()
{
    CONTRACTL {
        NOTHROW;
        GC_NOTRIGGER;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_CLASS));
    OpStackSet<void*>(m_curStackHt, NULL);
    m_curStackHt++;
}

template<typename T, CorInfoType cit>
void Interpreter::LdInd()
{
    _ASSERTE(TOSIsPtr());
    _ASSERTE(IsStackNormalType(cit));
    unsigned curStackInd = m_curStackHt-1;
    T* ptr = OpStackGet<T*>(curStackInd);
    ThrowOnInvalidPointer(ptr);
    OpStackSet<T>(curStackInd, *ptr);
    OpStackTypeSet(curStackInd, InterpreterType(cit));
    BarrierIfVolatile();
}

template<typename T, bool isUnsigned>
void Interpreter::LdIndShort()
{
    _ASSERTE(TOSIsPtr());
    _ASSERTE(sizeof(T) < 4);
    unsigned curStackInd = m_curStackHt-1;
    T* ptr = OpStackGet<T*>(curStackInd);
    ThrowOnInvalidPointer(ptr);
    if (isUnsigned)
    {
        OpStackSet<UINT32>(curStackInd, *ptr);
    }
    else
    {
        OpStackSet<INT32>(curStackInd, *ptr);
    }
    // All short integers are normalized to INT as their stack type.
    OpStackTypeSet(curStackInd, InterpreterType(CORINFO_TYPE_INT));
    BarrierIfVolatile();
}

template<typename T>
void Interpreter::StInd()
{
    _ASSERTE(m_curStackHt >= 2);
    _ASSERTE(CorInfoTypeIsPointer(OpStackTypeGet(m_curStackHt-2).ToCorInfoType()));
    BarrierIfVolatile();
    unsigned stackInd0 = m_curStackHt-2;
    unsigned stackInd1 = m_curStackHt-1;
    T val = OpStackGet<T>(stackInd1);
    T* ptr = OpStackGet<T*>(stackInd0);
    ThrowOnInvalidPointer(ptr);
    *ptr = val;
    m_curStackHt -= 2;

#if INTERP_TRACING
    if (s_TraceInterpreterILFlag.val(CLRConfig::INTERNAL_TraceInterpreterIL) &&
        IsInLocalArea(ptr))
    {
        PrintLocals();
    }
#endif // INTERP_TRACING
}

void Interpreter::StInd_Ref()
{
    _ASSERTE(m_curStackHt >= 2);
    _ASSERTE(CorInfoTypeIsPointer(OpStackTypeGet(m_curStackHt-2).ToCorInfoType()));
    BarrierIfVolatile();
    unsigned stackInd0 = m_curStackHt-2;
    unsigned stackInd1 = m_curStackHt-1;
    OBJECTREF val = ObjectToOBJECTREF(OpStackGet<Object*>(stackInd1));
    OBJECTREF* ptr = OpStackGet<OBJECTREF*>(stackInd0);
    ThrowOnInvalidPointer(ptr);
    SetObjectReference(ptr, val);
    m_curStackHt -= 2;

#if INTERP_TRACING
    if (s_TraceInterpreterILFlag.val(CLRConfig::INTERNAL_TraceInterpreterIL) &&
        IsInLocalArea(ptr))
    {
        PrintLocals();
    }
#endif // INTERP_TRACING
}


template<int op>
void Interpreter::BinaryArithOp()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    _ASSERTE(m_curStackHt >= 2);
    unsigned op1idx = m_curStackHt - 2;
    unsigned op2idx = m_curStackHt - 1;
    InterpreterType t1 = OpStackTypeGet(op1idx);
    _ASSERTE(IsStackNormalType(t1.ToCorInfoType()));
    // Looking at the generated code, it does seem to save some instructions to use the "shifted
    // types," though the effect on end-to-end time is variable.  So I'll leave it set.
    InterpreterType t2 = OpStackTypeGet(op2idx);
    _ASSERTE(IsStackNormalType(t2.ToCorInfoType()));

    // In all cases belows, since "op" is compile-time constant, "if" chains on it should fold away.
    switch (t1.ToCorInfoTypeShifted())
    {
    case CORINFO_TYPE_SHIFTED_INT:
        if (t1 == t2)
        {
            // Int op Int = Int
            INT32 val1 = OpStackGet<INT32>(op1idx);
            INT32 val2 = OpStackGet<INT32>(op2idx);
            BinaryArithOpWork<op, INT32, /*IsIntType*/true, CORINFO_TYPE_INT, /*TypeIsUnchanged*/true>(val1, val2);
        }
        else
        {
            CorInfoTypeShifted cits2 = t2.ToCorInfoTypeShifted();
            if (cits2 == CORINFO_TYPE_SHIFTED_NATIVEINT)
            {
                // Int op NativeInt = NativeInt
                NativeInt val1 = static_cast<NativeInt>(OpStackGet<INT32>(op1idx));
                NativeInt val2 = OpStackGet<NativeInt>(op2idx);
                BinaryArithOpWork<op, NativeInt, /*IsIntType*/true, CORINFO_TYPE_NATIVEINT, /*TypeIsUnchanged*/false>(val1, val2);
            }
            else if (s_InterpreterLooseRules && cits2 == CORINFO_TYPE_SHIFTED_LONG)
            {
                // Int op Long = Long
                INT64 val1 = static_cast<INT64>(OpStackGet<INT32>(op1idx));
                INT64 val2 = OpStackGet<INT64>(op2idx);
                BinaryArithOpWork<op, INT64, /*IsIntType*/true, CORINFO_TYPE_LONG, /*TypeIsUnchanged*/false>(val1, val2);
            }
            else if (cits2 == CORINFO_TYPE_SHIFTED_BYREF)
            {
                if (op == BA_Add || (s_InterpreterLooseRules && op == BA_Sub))
                {
                    // Int + ByRef = ByRef
                    NativeInt val1 = static_cast<NativeInt>(OpStackGet<INT32>(op1idx));
                    NativeInt val2 = OpStackGet<NativeInt>(op2idx);
                    BinaryArithOpWork<op, NativeInt, /*IsIntType*/true, CORINFO_TYPE_BYREF, /*TypeIsUnchanged*/false>(val1, val2);
                }
                else
                {
                    VerificationError("Operation not permitted on int and managed pointer.");
                }
            }
            else
            {
                VerificationError("Binary arithmetic operation type mismatch (int and ?)");
            }
        }
        break;

    case CORINFO_TYPE_SHIFTED_NATIVEINT:
        {
            NativeInt val1 = OpStackGet<NativeInt>(op1idx);
            if (t1 == t2)
            {
                // NativeInt op NativeInt = NativeInt
                NativeInt val2 = OpStackGet<NativeInt>(op2idx);
                BinaryArithOpWork<op, NativeInt, /*IsIntType*/true, CORINFO_TYPE_NATIVEINT, /*TypeIsUnchanged*/true>(val1, val2);
            }
            else
            {
                CorInfoTypeShifted cits2 = t2.ToCorInfoTypeShifted();
                if (cits2 == CORINFO_TYPE_SHIFTED_INT)
                {
                    // NativeInt op Int = NativeInt
                    NativeInt val2 = static_cast<NativeInt>(OpStackGet<INT32>(op2idx));
                    BinaryArithOpWork<op, NativeInt, /*IsIntType*/true, CORINFO_TYPE_NATIVEINT, /*TypeIsUnchanged*/true>(val1, val2);
                }
                // CLI spec does not allow adding a native int and an int64. So use loose rules.
                else if (s_InterpreterLooseRules && cits2 == CORINFO_TYPE_SHIFTED_LONG)
                {
                    // NativeInt op Int = NativeInt
                    NativeInt val2 = static_cast<NativeInt>(OpStackGet<INT64>(op2idx));
                    BinaryArithOpWork<op, NativeInt, /*IsIntType*/true, CORINFO_TYPE_NATIVEINT, /*TypeIsUnchanged*/true>(val1, val2);
                }
                else if (cits2 == CORINFO_TYPE_SHIFTED_BYREF)
                {
                    if (op == BA_Add || (s_InterpreterLooseRules && op == BA_Sub))
                    {
                        // NativeInt + ByRef = ByRef
                        NativeInt val2 = OpStackGet<NativeInt>(op2idx);
                        BinaryArithOpWork<op, NativeInt, /*IsIntType*/true, CORINFO_TYPE_BYREF, /*TypeIsUnchanged*/false>(val1, val2);
                    }
                    else
                    {
                        VerificationError("Operation not permitted on native int and managed pointer.");
                    }
                }
                else
                {
                    VerificationError("Binary arithmetic operation type mismatch (native int and ?)");
                }
            }
        }
        break;

    case CORINFO_TYPE_SHIFTED_LONG:
        {
            bool looseLong = false;
#if defined(HOST_AMD64)
            looseLong = (s_InterpreterLooseRules && (t2.ToCorInfoType() == CORINFO_TYPE_NATIVEINT ||
                    t2.ToCorInfoType() == CORINFO_TYPE_BYREF));
#endif
            if (t1 == t2 || looseLong)
            {
                // Long op Long = Long
                INT64 val1 = OpStackGet<INT64>(op1idx);
                INT64 val2 = OpStackGet<INT64>(op2idx);
                BinaryArithOpWork<op, INT64, /*IsIntType*/true, CORINFO_TYPE_LONG, /*TypeIsUnchanged*/true>(val1, val2);
            }
            else
            {
                VerificationError("Binary arithmetic operation type mismatch (long and ?)");
            }
        }
        break;

    case CORINFO_TYPE_SHIFTED_FLOAT:
        {
            if (t1 == t2)
            {
                // Float op Float = Float
                float val1 = OpStackGet<float>(op1idx);
                float val2 = OpStackGet<float>(op2idx);
                BinaryArithOpWork<op, float, /*IsIntType*/false, CORINFO_TYPE_FLOAT, /*TypeIsUnchanged*/true>(val1, val2);
            }
            else
            {
                CorInfoTypeShifted cits2 = t2.ToCorInfoTypeShifted();
                if (cits2 == CORINFO_TYPE_SHIFTED_DOUBLE)
                {
                    // Float op Double = Double
                    double val1 = static_cast<double>(OpStackGet<float>(op1idx));
                    double val2 = OpStackGet<double>(op2idx);
                    BinaryArithOpWork<op, double, /*IsIntType*/false, CORINFO_TYPE_DOUBLE, /*TypeIsUnchanged*/false>(val1, val2);
                }
                else
                {
                    VerificationError("Binary arithmetic operation type mismatch (float and ?)");
                }
            }
        }
        break;

    case CORINFO_TYPE_SHIFTED_DOUBLE:
        {
            if (t1 == t2)
            {
                // Double op Double = Double
                double val1 = OpStackGet<double>(op1idx);
                double val2 = OpStackGet<double>(op2idx);
                BinaryArithOpWork<op, double, /*IsIntType*/false, CORINFO_TYPE_DOUBLE, /*TypeIsUnchanged*/true>(val1, val2);
            }
            else
            {
                CorInfoTypeShifted cits2 = t2.ToCorInfoTypeShifted();
                if (cits2 == CORINFO_TYPE_SHIFTED_FLOAT)
                {
                    // Double op Float = Double
                    double val1 = OpStackGet<double>(op1idx);
                    double val2 = static_cast<double>(OpStackGet<float>(op2idx));
                    BinaryArithOpWork<op, double, /*IsIntType*/false, CORINFO_TYPE_DOUBLE, /*TypeIsUnchanged*/true>(val1, val2);
                }
                else
                {
                    VerificationError("Binary arithmetic operation type mismatch (double and ?)");
                }
            }
        }
        break;

    case CORINFO_TYPE_SHIFTED_BYREF:
        {
            NativeInt val1 = OpStackGet<NativeInt>(op1idx);
            CorInfoTypeShifted cits2 = t2.ToCorInfoTypeShifted();
            if (cits2 == CORINFO_TYPE_SHIFTED_INT)
            {
                if (op == BA_Add || op == BA_Sub)
                {
                    // ByRef +- Int = ByRef
                    NativeInt val2 = static_cast<NativeInt>(OpStackGet<INT32>(op2idx));
                    BinaryArithOpWork<op, NativeInt, /*IsIntType*/true, CORINFO_TYPE_BYREF, /*TypeIsUnchanged*/true>(val1, val2);
                }
                else
                {
                    VerificationError("May only add/subtract managed pointer and integral value.");
                }
            }
            else if (cits2 == CORINFO_TYPE_SHIFTED_NATIVEINT)
            {
                if (op == BA_Add || op == BA_Sub)
                {
                    // ByRef +- NativeInt = ByRef
                    NativeInt val2 = OpStackGet<NativeInt>(op2idx);
                    BinaryArithOpWork<op, NativeInt, /*IsIntType*/true, CORINFO_TYPE_BYREF, /*TypeIsUnchanged*/true>(val1, val2);
                }
                else
                {
                    VerificationError("May only add/subtract managed pointer and integral value.");
                }
            }
            else if (cits2 == CORINFO_TYPE_SHIFTED_BYREF)
            {
                if (op == BA_Sub)
                {
                    // ByRef - ByRef = NativeInt
                    NativeInt val2 = OpStackGet<NativeInt>(op2idx);
                    BinaryArithOpWork<op, NativeInt, /*IsIntType*/true, CORINFO_TYPE_NATIVEINT, /*TypeIsUnchanged*/false>(val1, val2);
                }
                else
                {
                    VerificationError("May only subtract managed pointer values.");
                }
            }
            // CLI spec does not allow adding a native int and an int64. So use loose rules.
            else if (s_InterpreterLooseRules && cits2 == CORINFO_TYPE_SHIFTED_LONG)
            {
                // NativeInt op Int = NativeInt
                NativeInt val2 = static_cast<NativeInt>(OpStackGet<INT64>(op2idx));
                BinaryArithOpWork<op, NativeInt, /*IsIntType*/true, CORINFO_TYPE_NATIVEINT, /*TypeIsUnchanged*/true>(val1, val2);
            }
            else
            {
                VerificationError("Binary arithmetic operation not permitted on byref");
            }
        }
        break;

    case CORINFO_TYPE_SHIFTED_CLASS:
        VerificationError("Can't do binary arithmetic on object references.");
        break;

    default:
        _ASSERTE_MSG(false, "Non-stack-normal type on stack.");
    }

    // In all cases:
    m_curStackHt--;
}

template<int op, bool asUnsigned>
void Interpreter::BinaryArithOvfOp()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    _ASSERTE(m_curStackHt >= 2);
    unsigned op1idx = m_curStackHt - 2;
    unsigned op2idx = m_curStackHt - 1;

    InterpreterType t1 = OpStackTypeGet(op1idx);
    CorInfoType cit1 = t1.ToCorInfoType();
    _ASSERTE(IsStackNormalType(cit1));

    InterpreterType t2 = OpStackTypeGet(op2idx);
    CorInfoType cit2 = t2.ToCorInfoType();
    _ASSERTE(IsStackNormalType(cit2));

    // In all cases belows, since "op" is compile-time constant, "if" chains on it should fold away.
    switch (cit1)
    {
    case CORINFO_TYPE_INT:
        if (cit2 == CORINFO_TYPE_INT)
        {
            if (asUnsigned)
            {
                // UnsignedInt op UnsignedInt = UnsignedInt
                UINT32 val1 = OpStackGet<UINT32>(op1idx);
                UINT32 val2 = OpStackGet<UINT32>(op2idx);
                BinaryArithOvfOpWork<op, UINT32, CORINFO_TYPE_INT, /*TypeIsUnchanged*/true>(val1, val2);
            }
            else
            {
                // Int op Int = Int
                INT32 val1 = OpStackGet<INT32>(op1idx);
                INT32 val2 = OpStackGet<INT32>(op2idx);
                BinaryArithOvfOpWork<op, INT32, CORINFO_TYPE_INT, /*TypeIsUnchanged*/true>(val1, val2);
            }
        }
        else if (cit2 == CORINFO_TYPE_NATIVEINT)
        {
            if (asUnsigned)
            {
                // UnsignedInt op UnsignedNativeInt = UnsignedNativeInt
                NativeUInt val1 = static_cast<NativeUInt>(OpStackGet<UINT32>(op1idx));
                NativeUInt val2 = OpStackGet<NativeUInt>(op2idx);
                BinaryArithOvfOpWork<op, NativeUInt, CORINFO_TYPE_NATIVEINT, /*TypeIsUnchanged*/false>(val1, val2);
            }
            else
            {
                // Int op NativeInt = NativeInt
                NativeInt val1 = static_cast<NativeInt>(OpStackGet<INT32>(op1idx));
                NativeInt val2 = OpStackGet<NativeInt>(op2idx);
                BinaryArithOvfOpWork<op, NativeInt, CORINFO_TYPE_NATIVEINT, /*TypeIsUnchanged*/false>(val1, val2);
            }
        }
        else if (cit2 == CORINFO_TYPE_BYREF)
        {
            if (asUnsigned && op == BA_Add)
            {
                // UnsignedInt + ByRef = ByRef
                NativeUInt val1 = static_cast<NativeUInt>(OpStackGet<UINT32>(op1idx));
                NativeUInt val2 = OpStackGet<NativeUInt>(op2idx);
                BinaryArithOvfOpWork<op, NativeUInt, CORINFO_TYPE_BYREF, /*TypeIsUnchanged*/false>(val1, val2);
            }
            else
            {
                VerificationError("Illegal arithmetic overflow operation for int and byref.");
            }
        }
        else
        {
            VerificationError("Binary arithmetic overflow operation type mismatch (int and ?)");
        }
        break;

    case CORINFO_TYPE_NATIVEINT:
        if (cit2 == CORINFO_TYPE_INT)
        {
            if (asUnsigned)
            {
                // UnsignedNativeInt op UnsignedInt = UnsignedNativeInt
                NativeUInt val1 = OpStackGet<NativeUInt>(op1idx);
                NativeUInt val2 = static_cast<NativeUInt>(OpStackGet<UINT32>(op2idx));
                BinaryArithOvfOpWork<op, NativeUInt, CORINFO_TYPE_NATIVEINT, /*TypeIsUnchanged*/true>(val1, val2);
            }
            else
            {
                // NativeInt op Int = NativeInt
                NativeInt val1 = OpStackGet<NativeInt>(op1idx);
                NativeInt val2 = static_cast<NativeInt>(OpStackGet<INT32>(op2idx));
                BinaryArithOvfOpWork<op, NativeInt, CORINFO_TYPE_NATIVEINT, /*TypeIsUnchanged*/true>(val1, val2);
            }
        }
        else if (cit2 == CORINFO_TYPE_NATIVEINT)
        {
            if (asUnsigned)
            {
                // UnsignedNativeInt op UnsignedNativeInt = UnsignedNativeInt
                NativeUInt val1 = OpStackGet<NativeUInt>(op1idx);
                NativeUInt val2 = OpStackGet<NativeUInt>(op2idx);
                BinaryArithOvfOpWork<op, NativeUInt, CORINFO_TYPE_NATIVEINT, /*TypeIsUnchanged*/true>(val1, val2);
            }
            else
            {
                // NativeInt op NativeInt = NativeInt
                NativeInt val1 = OpStackGet<NativeInt>(op1idx);
                NativeInt val2 = OpStackGet<NativeInt>(op2idx);
                BinaryArithOvfOpWork<op, NativeInt, CORINFO_TYPE_NATIVEINT, /*TypeIsUnchanged*/true>(val1, val2);
            }
        }
        else if (cit2 == CORINFO_TYPE_BYREF)
        {
            if (asUnsigned && op == BA_Add)
            {
                // UnsignedNativeInt op ByRef = ByRef
                NativeUInt val1 = OpStackGet<UINT32>(op1idx);
                NativeUInt val2 = OpStackGet<NativeUInt>(op2idx);
                BinaryArithOvfOpWork<op, NativeUInt, CORINFO_TYPE_BYREF, /*TypeIsUnchanged*/false>(val1, val2);
            }
            else
            {
                VerificationError("Illegal arithmetic overflow operation for native int and byref.");
            }
        }
        else
        {
            VerificationError("Binary arithmetic overflow operation type mismatch (native int and ?)");
        }
        break;

    case CORINFO_TYPE_LONG:
        if (cit2 == CORINFO_TYPE_LONG || (s_InterpreterLooseRules && cit2 == CORINFO_TYPE_NATIVEINT))
        {
            if (asUnsigned)
            {
                // UnsignedLong op UnsignedLong = UnsignedLong
                UINT64 val1 = OpStackGet<UINT64>(op1idx);
                UINT64 val2 = OpStackGet<UINT64>(op2idx);
                BinaryArithOvfOpWork<op, UINT64, CORINFO_TYPE_LONG, /*TypeIsUnchanged*/true>(val1, val2);
            }
            else
            {
                // Long op Long = Long
                INT64 val1 = OpStackGet<INT64>(op1idx);
                INT64 val2 = OpStackGet<INT64>(op2idx);
                BinaryArithOvfOpWork<op, INT64, CORINFO_TYPE_LONG, /*TypeIsUnchanged*/true>(val1, val2);
            }
        }
        else
        {
            VerificationError("Binary arithmetic overflow operation type mismatch (long and ?)");
        }
        break;

    case CORINFO_TYPE_BYREF:
        if (asUnsigned && (op == BA_Add || op == BA_Sub))
        {
            NativeUInt val1 = OpStackGet<NativeUInt>(op1idx);
            if (cit2 == CORINFO_TYPE_INT)
            {
                // ByRef +- UnsignedInt = ByRef
                NativeUInt val2 = static_cast<NativeUInt>(OpStackGet<INT32>(op2idx));
                BinaryArithOvfOpWork<op, NativeUInt, CORINFO_TYPE_BYREF, /*TypeIsUnchanged*/true>(val1, val2);
            }
            else if (cit2 == CORINFO_TYPE_NATIVEINT)
            {
                // ByRef +- UnsignedNativeInt = ByRef
                NativeUInt val2 = OpStackGet<NativeUInt>(op2idx);
                BinaryArithOvfOpWork<op, NativeUInt, CORINFO_TYPE_BYREF, /*TypeIsUnchanged*/true>(val1, val2);
            }
            else if (cit2 == CORINFO_TYPE_BYREF)
            {
                if (op == BA_Sub)
                {
                    // ByRef - ByRef = UnsignedNativeInt
                    NativeUInt val2 = OpStackGet<NativeUInt>(op2idx);
                    BinaryArithOvfOpWork<op, NativeUInt, CORINFO_TYPE_NATIVEINT, /*TypeIsUnchanged*/false>(val1, val2);
                }
                else
                {
                    VerificationError("Illegal arithmetic overflow operation for byref and byref: may only subtract managed pointer values.");
                }
            }
            else
            {
                VerificationError("Binary arithmetic overflow operation not permitted on byref");
            }
        }
        else
        {
            if (!asUnsigned)
            {
                VerificationError("Signed binary arithmetic overflow operation not permitted on managed pointer values.");
            }
            else
            {
                _ASSERTE_MSG(op == BA_Mul, "Must be an overflow operation; tested for Add || Sub above.");
                VerificationError("Cannot multiply managed pointer values.");
            }
        }
        break;

    default:
        _ASSERTE_MSG(false, "Non-stack-normal type on stack.");
    }

    // In all cases:
    m_curStackHt--;
}

template<int op, typename T, CorInfoType cit, bool TypeIsUnchanged>
void Interpreter::BinaryArithOvfOpWork(T val1, T val2)
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    ClrSafeInt<T> res;
    ClrSafeInt<T> safeV1(val1);
    ClrSafeInt<T> safeV2(val2);
    if (op == BA_Add)
    {
        res = safeV1 + safeV2;
    }
    else if (op == BA_Sub)
    {
        res = safeV1 - safeV2;
    }
    else if (op == BA_Mul)
    {
        res = safeV1 * safeV2;
    }
    else
    {
        _ASSERTE_MSG(false, "op should be one of the overflow ops...");
    }

    if (res.IsOverflow())
    {
        ThrowOverflowException();
    }

    unsigned residx = m_curStackHt - 2;
    OpStackSet<T>(residx, res.Value());
    if (!TypeIsUnchanged)
    {
        OpStackTypeSet(residx, InterpreterType(cit));
    }
}

template<int op>
void Interpreter::BinaryIntOp()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    _ASSERTE(m_curStackHt >= 2);
    unsigned op1idx = m_curStackHt - 2;
    unsigned op2idx = m_curStackHt - 1;

    InterpreterType t1 = OpStackTypeGet(op1idx);
    CorInfoType cit1 = t1.ToCorInfoType();
    _ASSERTE(IsStackNormalType(cit1));

    InterpreterType t2 = OpStackTypeGet(op2idx);
    CorInfoType cit2 = t2.ToCorInfoType();
    _ASSERTE(IsStackNormalType(cit2));

    // In all cases belows, since "op" is compile-time constant, "if" chains on it should fold away.
    switch (cit1)
    {
    case CORINFO_TYPE_INT:
        if (cit2 == CORINFO_TYPE_INT)
        {
            // Int op Int = Int
            UINT32 val1 = OpStackGet<UINT32>(op1idx);
            UINT32 val2 = OpStackGet<UINT32>(op2idx);
            BinaryIntOpWork<op, UINT32, CORINFO_TYPE_INT, /*TypeIsUnchanged*/true>(val1, val2);
        }
        else if (cit2 == CORINFO_TYPE_NATIVEINT)
        {
            // Int op NativeInt = NativeInt
            NativeUInt val1 = static_cast<NativeUInt>(OpStackGet<INT32>(op1idx));
            NativeUInt val2 = OpStackGet<NativeUInt>(op2idx);
            BinaryIntOpWork<op, NativeUInt, CORINFO_TYPE_NATIVEINT, /*TypeIsUnchanged*/false>(val1, val2);
        }
        else if (s_InterpreterLooseRules && cit2 == CORINFO_TYPE_BYREF)
        {
            // Int op NativeUInt = NativeUInt
            NativeUInt val1 = static_cast<NativeUInt>(OpStackGet<INT32>(op1idx));
            NativeUInt val2 = OpStackGet<NativeUInt>(op2idx);
            BinaryIntOpWork<op, NativeUInt, CORINFO_TYPE_BYREF, /*TypeIsUnchanged*/false>(val1, val2);
        }
        else
        {
            VerificationError("Binary arithmetic operation type mismatch (int and ?)");
        }
        break;

    case CORINFO_TYPE_NATIVEINT:
        if (cit2 == CORINFO_TYPE_NATIVEINT)
        {
            // NativeInt op NativeInt = NativeInt
            NativeUInt val1 = OpStackGet<NativeUInt>(op1idx);
            NativeUInt val2 = OpStackGet<NativeUInt>(op2idx);
            BinaryIntOpWork<op, NativeUInt, CORINFO_TYPE_NATIVEINT, /*TypeIsUnchanged*/true>(val1, val2);
        }
        else if (cit2 == CORINFO_TYPE_INT)
        {
            // NativeInt op Int = NativeInt
            NativeUInt val1 = OpStackGet<NativeUInt>(op1idx);
            NativeUInt val2 = static_cast<NativeUInt>(OpStackGet<INT32>(op2idx));
            BinaryIntOpWork<op, NativeUInt, CORINFO_TYPE_NATIVEINT, /*TypeIsUnchanged*/true>(val1, val2);
        }
        // CLI spec does not allow adding a native int and an int64. So use loose rules.
        else if (s_InterpreterLooseRules && cit2 == CORINFO_TYPE_LONG)
        {
            // NativeInt op Int = NativeInt
            NativeUInt val1 = OpStackGet<NativeUInt>(op1idx);
            NativeUInt val2 = static_cast<NativeUInt>(OpStackGet<INT64>(op2idx));
            BinaryIntOpWork<op, NativeUInt, CORINFO_TYPE_NATIVEINT, /*TypeIsUnchanged*/true>(val1, val2);
        }
        else
        {
            VerificationError("Binary arithmetic operation type mismatch (native int and ?)");
        }
        break;

    case CORINFO_TYPE_LONG:
        if (cit2 == CORINFO_TYPE_LONG || (s_InterpreterLooseRules && cit2 == CORINFO_TYPE_NATIVEINT))
        {
            // Long op Long = Long
            UINT64 val1 = OpStackGet<UINT64>(op1idx);
            UINT64 val2 = OpStackGet<UINT64>(op2idx);
            BinaryIntOpWork<op, UINT64, CORINFO_TYPE_LONG, /*TypeIsUnchanged*/true>(val1, val2);
        }
        else
        {
            VerificationError("Binary arithmetic operation type mismatch (long and ?)");
        }
        break;

    default:
        VerificationError("Illegal operation for non-integral data type.");
    }

    // In all cases:
    m_curStackHt--;
}

template<int op, typename T, CorInfoType cit, bool TypeIsUnchanged>
void Interpreter::BinaryIntOpWork(T val1, T val2)
{
    T res;
    if (op == BIO_And)
    {
        res = val1 & val2;
    }
    else if (op == BIO_Or)
    {
        res = val1 | val2;
    }
    else if (op == BIO_Xor)
    {
        res = val1 ^ val2;
    }
    else
    {
        _ASSERTE(op == BIO_DivUn || op == BIO_RemUn);
        if (val2 == 0)
        {
            ThrowDivideByZero();
        }
        else if (val2 == static_cast<T>(-1) && val1 == static_cast<T>(((UINT64)1) << (sizeof(T)*8 - 1))) // min int / -1 is not representable.
        {
            ThrowSysArithException();
        }
        // Otherwise...
        if (op == BIO_DivUn)
        {
            res = val1 / val2;
        }
        else
        {
            res = val1 % val2;
        }
    }

    unsigned residx = m_curStackHt - 2;
    OpStackSet<T>(residx, res);
    if (!TypeIsUnchanged)
    {
        OpStackTypeSet(residx, InterpreterType(cit));
    }
}

template<int op>
void Interpreter::ShiftOp()
{
    CONTRACTL {
        NOTHROW;
        GC_NOTRIGGER;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    _ASSERTE(m_curStackHt >= 2);
    unsigned op1idx = m_curStackHt - 2;
    unsigned op2idx = m_curStackHt - 1;

    InterpreterType t1 = OpStackTypeGet(op1idx);
    CorInfoType cit1 = t1.ToCorInfoType();
    _ASSERTE(IsStackNormalType(cit1));

    InterpreterType t2 = OpStackTypeGet(op2idx);
    CorInfoType cit2 = t2.ToCorInfoType();
    _ASSERTE(IsStackNormalType(cit2));

    // In all cases belows, since "op" is compile-time constant, "if" chains on it should fold away.
    switch (cit1)
    {
    case CORINFO_TYPE_INT:
        ShiftOpWork<op, INT32, UINT32>(op1idx, cit2);
        break;

    case CORINFO_TYPE_NATIVEINT:
        ShiftOpWork<op, NativeInt, NativeUInt>(op1idx, cit2);
        break;

    case CORINFO_TYPE_LONG:
        ShiftOpWork<op, INT64, UINT64>(op1idx, cit2);
        break;

    default:
        VerificationError("Illegal value type for shift operation.");
        break;
    }

    m_curStackHt--;
}

template<int op, typename T, typename UT>
void Interpreter::ShiftOpWork(unsigned op1idx, CorInfoType cit2)
{
    T val = OpStackGet<T>(op1idx);
    unsigned op2idx = op1idx + 1;
    T res = 0;

    if (cit2 == CORINFO_TYPE_INT)
    {
        INT32 shiftAmt = OpStackGet<INT32>(op2idx);
        if (op == CEE_SHL)
        {
            res = val << shiftAmt; // TODO: Check that C++ semantics matches IL.
        }
        else if (op == CEE_SHR)
        {
            res = val >> shiftAmt;
        }
        else
        {
            _ASSERTE(op == CEE_SHR_UN);
            res = (static_cast<UT>(val)) >> shiftAmt;
        }
    }
    else if (cit2 == CORINFO_TYPE_NATIVEINT)
    {
        NativeInt shiftAmt = OpStackGet<NativeInt>(op2idx);
        if (op == CEE_SHL)
        {
            res = val << shiftAmt; // TODO: Check that C++ semantics matches IL.
        }
        else if (op == CEE_SHR)
        {
            res = val >> shiftAmt;
        }
        else
        {
            _ASSERTE(op == CEE_SHR_UN);
            res = (static_cast<UT>(val)) >> shiftAmt;
        }
    }
    else
    {
        VerificationError("Operand type mismatch for shift operator.");
    }
    OpStackSet<T>(op1idx, res);
}


void Interpreter::Neg()
{
    CONTRACTL {
        NOTHROW;
        GC_NOTRIGGER;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    _ASSERTE(m_curStackHt >= 1);
    unsigned opidx = m_curStackHt - 1;

    InterpreterType t1 = OpStackTypeGet(opidx);
    CorInfoType cit1 = t1.ToCorInfoType();
    _ASSERTE(IsStackNormalType(cit1));

    switch (cit1)
    {
    case CORINFO_TYPE_INT:
        OpStackSet<INT32>(opidx, -OpStackGet<INT32>(opidx));
        break;

    case CORINFO_TYPE_NATIVEINT:
        OpStackSet<NativeInt>(opidx, -OpStackGet<NativeInt>(opidx));
        break;

    case CORINFO_TYPE_LONG:
        OpStackSet<INT64>(opidx, -OpStackGet<INT64>(opidx));
        break;

    case CORINFO_TYPE_FLOAT:
        OpStackSet<float>(opidx, -OpStackGet<float>(opidx));
        break;

    case CORINFO_TYPE_DOUBLE:
        OpStackSet<double>(opidx, -OpStackGet<double>(opidx));
        break;

    default:
        VerificationError("Illegal operand type for Neg operation.");
    }
}

void Interpreter::Not()
{
    CONTRACTL {
        NOTHROW;
        GC_NOTRIGGER;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    _ASSERTE(m_curStackHt >= 1);
    unsigned opidx = m_curStackHt - 1;

    InterpreterType t1 = OpStackTypeGet(opidx);
    CorInfoType cit1 = t1.ToCorInfoType();
    _ASSERTE(IsStackNormalType(cit1));

    switch (cit1)
    {
    case CORINFO_TYPE_INT:
        OpStackSet<INT32>(opidx, ~OpStackGet<INT32>(opidx));
        break;

    case CORINFO_TYPE_NATIVEINT:
        OpStackSet<NativeInt>(opidx, ~OpStackGet<NativeInt>(opidx));
        break;

    case CORINFO_TYPE_LONG:
        OpStackSet<INT64>(opidx, ~OpStackGet<INT64>(opidx));
        break;

    default:
        VerificationError("Illegal operand type for Not operation.");
    }
}

template<typename T, bool TIsUnsigned, bool TCanHoldPtr, bool TIsShort, CorInfoType cit>
void Interpreter::Conv()
{
    CONTRACTL {
        NOTHROW;
        GC_NOTRIGGER;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    _ASSERTE(m_curStackHt >= 1);
    unsigned opidx = m_curStackHt - 1;

    InterpreterType t1 = OpStackTypeGet(opidx);
    CorInfoType cit1 = t1.ToCorInfoType();
    _ASSERTE(IsStackNormalType(cit1));

    T val;
    switch (cit1)
    {
    case CORINFO_TYPE_INT:
        if (TIsUnsigned)
        {
            // Must convert the 32 bit value to unsigned first, so that we zero-extend if necessary.
            val = static_cast<T>(static_cast<UINT32>(OpStackGet<INT32>(opidx)));
        }
        else
        {
            val = static_cast<T>(OpStackGet<INT32>(opidx));
        }
        break;

    case CORINFO_TYPE_NATIVEINT:
        if (TIsUnsigned)
        {
            // NativeInt might be 32 bits, so convert to unsigned before possibly widening.
            val = static_cast<T>(static_cast<NativeUInt>(OpStackGet<NativeInt>(opidx)));
        }
        else
        {
            val = static_cast<T>(OpStackGet<NativeInt>(opidx));
        }
        break;

    case CORINFO_TYPE_LONG:
        val = static_cast<T>(OpStackGet<INT64>(opidx));
        break;

        // TODO: Make sure that the C++ conversions do the right thing (truncate to zero...)
    case CORINFO_TYPE_FLOAT:
        val = static_cast<T>(OpStackGet<float>(opidx));
        break;

    case CORINFO_TYPE_DOUBLE:
        val = static_cast<T>(OpStackGet<double>(opidx));
        break;

    case CORINFO_TYPE_BYREF:
    case CORINFO_TYPE_CLASS:
    case CORINFO_TYPE_STRING:
        if (!TCanHoldPtr && !s_InterpreterLooseRules)
        {
            VerificationError("Conversion of pointer value to type that can't hold its value.");
        }

        // Otherwise...
        // (Must first convert to NativeInt, because the compiler believes this might be applied for T =
        // float or double.  It won't, by the test above, and the extra cast shouldn't generate any code...)
        val = static_cast<T>(reinterpret_cast<NativeInt>(OpStackGet<void*>(opidx)));
        break;

    default:
        VerificationError("Illegal operand type for conv.* operation.");
        UNREACHABLE();
    }

    if (TIsShort)
    {
        OpStackSet<INT32>(opidx, static_cast<INT32>(val));
    }
    else
    {
        OpStackSet<T>(opidx, val);
    }

    OpStackTypeSet(opidx, InterpreterType(cit));
}


void Interpreter::ConvRUn()
{
    CONTRACTL {
        NOTHROW;
        GC_NOTRIGGER;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    _ASSERTE(m_curStackHt >= 1);
    unsigned opidx = m_curStackHt - 1;

    InterpreterType t1 = OpStackTypeGet(opidx);
    CorInfoType cit1 = t1.ToCorInfoType();
    _ASSERTE(IsStackNormalType(cit1));

    switch (cit1)
    {
    case CORINFO_TYPE_INT:
        OpStackSet<double>(opidx, static_cast<double>(OpStackGet<UINT32>(opidx)));
        break;

    case CORINFO_TYPE_NATIVEINT:
        OpStackSet<double>(opidx, static_cast<double>(OpStackGet<NativeUInt>(opidx)));
        break;

    case CORINFO_TYPE_LONG:
        OpStackSet<double>(opidx, static_cast<double>(OpStackGet<UINT64>(opidx)));
        break;

    case CORINFO_TYPE_DOUBLE:
        return;

    default:
        VerificationError("Illegal operand type for conv.r.un operation.");
    }

    OpStackTypeSet(opidx, InterpreterType(CORINFO_TYPE_DOUBLE));
}

template<typename T, INT64 TMin, UINT64 TMax, bool TCanHoldPtr, CorInfoType cit>
void Interpreter::ConvOvf()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    _ASSERTE(m_curStackHt >= 1);
    unsigned opidx = m_curStackHt - 1;

    InterpreterType t1 = OpStackTypeGet(opidx);
    CorInfoType cit1 = t1.ToCorInfoType();
    _ASSERTE(IsStackNormalType(cit1));

    switch (cit1)
    {
    case CORINFO_TYPE_INT:
        {
            INT32 i4 = OpStackGet<INT32>(opidx);
            if (!FitsIn<T>(i4))
            {
                ThrowOverflowException();
            }
            OpStackSet<T>(opidx, static_cast<T>(i4));
        }
        break;

    case CORINFO_TYPE_NATIVEINT:
        {
            NativeInt i = OpStackGet<NativeInt>(opidx);
            if (!FitsIn<T>(i))
            {
                ThrowOverflowException();
            }
            OpStackSet<T>(opidx, static_cast<T>(i));
        }
        break;

    case CORINFO_TYPE_LONG:
        {
            INT64 i8 = OpStackGet<INT64>(opidx);
            if (!FitsIn<T>(i8))
            {
                ThrowOverflowException();
            }
            OpStackSet<T>(opidx, static_cast<T>(i8));
        }
        break;

        // Make sure that the C++ conversions do the right thing (truncate to zero...)
    case CORINFO_TYPE_FLOAT:
        {
            float f = OpStackGet<float>(opidx);
            if (!FloatFitsInIntType<TMin, TMax>(f))
            {
                ThrowOverflowException();
            }
            OpStackSet<T>(opidx, static_cast<T>(f));
        }
        break;

    case CORINFO_TYPE_DOUBLE:
         {
            double d = OpStackGet<double>(opidx);
            if (!DoubleFitsInIntType<TMin, TMax>(d))
            {
                ThrowOverflowException();
            }
            OpStackSet<T>(opidx, static_cast<T>(d));
        }
        break;

    case CORINFO_TYPE_BYREF:
    case CORINFO_TYPE_CLASS:
    case CORINFO_TYPE_STRING:
        if (!TCanHoldPtr)
        {
            VerificationError("Conversion of pointer value to type that can't hold its value.");
        }

        // Otherwise...
        // (Must first convert to NativeInt, because the compiler believes this might be applied for T =
        // float or double.  It won't, by the test above, and the extra cast shouldn't generate any code...
        OpStackSet<T>(opidx, static_cast<T>(reinterpret_cast<NativeInt>(OpStackGet<void*>(opidx))));
        break;

    default:
        VerificationError("Illegal operand type for conv.ovf.* operation.");
    }

    _ASSERTE_MSG(IsStackNormalType(cit), "Precondition.");
    OpStackTypeSet(opidx, InterpreterType(cit));
}

template<typename T, INT64 TMin, UINT64 TMax, bool TCanHoldPtr, CorInfoType cit>
void Interpreter::ConvOvfUn()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    _ASSERTE(m_curStackHt >= 1);
    unsigned opidx = m_curStackHt - 1;

    InterpreterType t1 = OpStackTypeGet(opidx);
    CorInfoType cit1 = t1.ToCorInfoType();
    _ASSERTE(IsStackNormalType(cit1));

    switch (cit1)
    {
    case CORINFO_TYPE_INT:
        {
            UINT32 ui4 = OpStackGet<UINT32>(opidx);
            if (!FitsIn<T>(ui4))
            {
                ThrowOverflowException();
            }
            OpStackSet<T>(opidx, static_cast<T>(ui4));
        }
        break;

    case CORINFO_TYPE_NATIVEINT:
        {
            NativeUInt ui = OpStackGet<NativeUInt>(opidx);
            if (!FitsIn<T>(ui))
            {
                ThrowOverflowException();
            }
            OpStackSet<T>(opidx, static_cast<T>(ui));
        }
        break;

    case CORINFO_TYPE_LONG:
        {
            UINT64 ui8 = OpStackGet<UINT64>(opidx);
            if (!FitsIn<T>(ui8))
            {
                ThrowOverflowException();
            }
            OpStackSet<T>(opidx, static_cast<T>(ui8));
        }
        break;

        // Make sure that the C++ conversions do the right thing (truncate to zero...)
    case CORINFO_TYPE_FLOAT:
        {
            float f = OpStackGet<float>(opidx);
            if (!FloatFitsInIntType<TMin, TMax>(f))
            {
                ThrowOverflowException();
            }
            OpStackSet<T>(opidx, static_cast<T>(f));
        }
        break;

    case CORINFO_TYPE_DOUBLE:
         {
            double d = OpStackGet<double>(opidx);
            if (!DoubleFitsInIntType<TMin, TMax>(d))
            {
                ThrowOverflowException();
            }
            OpStackSet<T>(opidx, static_cast<T>(d));
        }
        break;

    case CORINFO_TYPE_BYREF:
    case CORINFO_TYPE_CLASS:
    case CORINFO_TYPE_STRING:
        if (!TCanHoldPtr)
        {
            VerificationError("Conversion of pointer value to type that can't hold its value.");
        }

        // Otherwise...
        // (Must first convert to NativeInt, because the compiler believes this might be applied for T =
        // float or double.  It won't, by the test above, and the extra cast shouldn't generate any code...
        OpStackSet<T>(opidx, static_cast<T>(reinterpret_cast<NativeInt>(OpStackGet<void*>(opidx))));
        break;

    default:
        VerificationError("Illegal operand type for conv.ovf.*.un operation.");
    }

    _ASSERTE_MSG(IsStackNormalType(cit), "Precondition.");
    OpStackTypeSet(opidx, InterpreterType(cit));
}

void Interpreter::LdObj()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    BarrierIfVolatile();

    _ASSERTE(m_curStackHt > 0);
    unsigned ind = m_curStackHt - 1;

#ifdef _DEBUG
    CorInfoType cit = OpStackTypeGet(ind).ToCorInfoType();
    _ASSERTE_MSG(IsValidPointerType(cit), "Expect pointer on stack");
#endif // _DEBUG

#if INTERP_TRACING
    InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_LdObj]);
#endif // INTERP_TRACING

    // TODO: GetTypeFromToken also uses GCX_PREEMP(); can we merge it with the getClassAttribs() block below, and do it just once?
    CORINFO_CLASS_HANDLE clsHnd = GetTypeFromToken(m_ILCodePtr + 1, CORINFO_TOKENKIND_Class  InterpTracingArg(RTK_LdObj));
    DWORD clsAttribs;
    {
        GCX_PREEMP();
        clsAttribs = m_interpCeeInfo.getClassAttribs(clsHnd);
    }

    void* src = OpStackGet<void*>(ind);
    ThrowOnInvalidPointer(src);

    if (clsAttribs & CORINFO_FLG_VALUECLASS)
    {
        LdObjValueClassWork(clsHnd, ind, src);
    }
    else
    {
        OpStackSet<void*>(ind, *reinterpret_cast<void**>(src));
        OpStackTypeSet(ind, InterpreterType(CORINFO_TYPE_CLASS));
    }
    m_ILCodePtr += 5;
}

void Interpreter::LdObjValueClassWork(CORINFO_CLASS_HANDLE valueClsHnd, unsigned ind, void* src)
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    // "src" is a byref, which may be into an object.  GCPROTECT for the call below.
    GCPROTECT_BEGININTERIOR(src);

    InterpreterType it = InterpreterType(&m_interpCeeInfo, valueClsHnd);
    size_t sz = it.Size(&m_interpCeeInfo);
    // Note that the memcpy's below are permissible because the destination is in the operand stack.
    if (sz > sizeof(INT64))
    {
        void* dest = LargeStructOperandStackPush(sz);
        memcpy(dest, src, sz);
        OpStackSet<void*>(ind, dest);
    }
    else
    {
        OpStackSet<INT64>(ind, GetSmallStructValue(src, sz));
    }

    OpStackTypeSet(ind, it.StackNormalize());

    GCPROTECT_END();
}

CORINFO_CLASS_HANDLE Interpreter::GetTypeFromToken(BYTE* codePtr, CorInfoTokenKind tokKind  InterpTracingArg(ResolveTokenKind rtk))
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    GCX_PREEMP();

    CORINFO_RESOLVED_TOKEN typeTok;
    ResolveToken(&typeTok, getU4LittleEndian(codePtr), tokKind InterpTracingArg(rtk));
    return typeTok.hClass;
}

bool Interpreter::IsValidPointerType(CorInfoType cit)
{
    bool isValid = (cit == CORINFO_TYPE_NATIVEINT || cit == CORINFO_TYPE_BYREF);
#if defined(HOST_AMD64)
    isValid = isValid || (s_InterpreterLooseRules && cit == CORINFO_TYPE_LONG);
#endif
    return isValid;
}

void Interpreter::CpObj()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    _ASSERTE(m_curStackHt >= 2);
    unsigned destInd = m_curStackHt - 2;
    unsigned srcInd  = m_curStackHt - 1;

#ifdef _DEBUG
    // Check that src and dest are both pointer types.
    CorInfoType cit = OpStackTypeGet(destInd).ToCorInfoType();
    _ASSERTE_MSG(IsValidPointerType(cit), "Expect pointer on stack for dest of cpobj");

    cit = OpStackTypeGet(srcInd).ToCorInfoType();
    _ASSERTE_MSG(IsValidPointerType(cit), "Expect pointer on stack for src of cpobj");
#endif // _DEBUG

#if INTERP_TRACING
    InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_CpObj]);
#endif // INTERP_TRACING

    CORINFO_CLASS_HANDLE clsHnd = GetTypeFromToken(m_ILCodePtr + 1, CORINFO_TOKENKIND_Class  InterpTracingArg(RTK_CpObj));
    DWORD clsAttribs;
    {
        GCX_PREEMP();
        clsAttribs = m_interpCeeInfo.getClassAttribs(clsHnd);
    }

    void* dest = OpStackGet<void*>(destInd);
    void* src  = OpStackGet<void*>(srcInd);

    ThrowOnInvalidPointer(dest);
    ThrowOnInvalidPointer(src);

    // dest and src are vulnerable byrefs.
    GCX_FORBID();

    if (clsAttribs & CORINFO_FLG_VALUECLASS)
    {
        CopyValueClassUnchecked(dest, src, GetMethodTableFromClsHnd(clsHnd));
    }
    else
    {
        OBJECTREF val = *reinterpret_cast<OBJECTREF*>(src);
        SetObjectReference(reinterpret_cast<OBJECTREF*>(dest), val);
    }
    m_curStackHt -= 2;
    m_ILCodePtr += 5;
}

void Interpreter::StObj()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    _ASSERTE(m_curStackHt >= 2);
    unsigned destInd = m_curStackHt - 2;
    unsigned valInd  = m_curStackHt - 1;

#ifdef _DEBUG
    // Check that dest is a pointer type.
    CorInfoType cit = OpStackTypeGet(destInd).ToCorInfoType();
    _ASSERTE_MSG(IsValidPointerType(cit), "Expect pointer on stack for dest of stobj");
#endif // _DEBUG

#if INTERP_TRACING
    InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_StObj]);
#endif // INTERP_TRACING

    CORINFO_CLASS_HANDLE clsHnd = GetTypeFromToken(m_ILCodePtr + 1, CORINFO_TOKENKIND_Class  InterpTracingArg(RTK_StObj));
    DWORD clsAttribs;
    {
        GCX_PREEMP();
        clsAttribs = m_interpCeeInfo.getClassAttribs(clsHnd);
    }

    if (clsAttribs & CORINFO_FLG_VALUECLASS)
    {
        MethodTable* clsMT = GetMethodTableFromClsHnd(clsHnd);
        size_t sz;
        {
            GCX_PREEMP();
            sz = getClassSize(clsHnd);
        }

        // Note that "dest" might be a pointer into the heap.  It is therefore important
        // to calculate it *after* any PREEMP transitions at which we might do a GC.
        void* dest = OpStackGet<void*>(destInd);
        ThrowOnInvalidPointer(dest);

#ifdef _DEBUG
        // Try and validate types
        InterpreterType vit = OpStackTypeGet(valInd);
        CorInfoType vitc = vit.ToCorInfoType();

        if (vitc == CORINFO_TYPE_VALUECLASS)
        {
            CORINFO_CLASS_HANDLE vClsHnd = vit.ToClassHandle();
            const bool isClass = (vClsHnd == clsHnd);
            const bool isPrim = (vitc == CorInfoTypeStackNormalize(GetTypeForPrimitiveValueClass(clsHnd)));
            bool isShared = false;

            // If operand type is shared we need a more complex check;
            // the IL type may not be shared
            if (!isPrim && !isClass)
            {
                DWORD vClsAttribs;
                {
                    GCX_PREEMP();
                    vClsAttribs = m_interpCeeInfo.getClassAttribs(vClsHnd);
                }

                if ((vClsAttribs & CORINFO_FLG_SHAREDINST) != 0)
                {
                    MethodTable* clsMT2 = clsMT->GetCanonicalMethodTable();
                    if (((CORINFO_CLASS_HANDLE) clsMT2) == vClsHnd)
                    {
                        isShared = true;
                    }
                }
            }

            _ASSERTE(isClass || isPrim || isShared);
        }
        else
        {
            const bool isSz = s_InterpreterLooseRules && sz <= sizeof(dest);
            _ASSERTE(isSz);
        }

#endif // _DEBUG

        GCX_FORBID();

        if (sz > sizeof(INT64))
        {
            // Large struct case -- ostack entry is pointer.
            void* src = OpStackGet<void*>(valInd);
            CopyValueClassUnchecked(dest, src, clsMT);
            LargeStructOperandStackPop(sz, src);
        }
        else
        {
            // The ostack entry contains the struct value.
            CopyValueClassUnchecked(dest, OpStackGetAddr(valInd, sz), clsMT);
        }
    }
    else
    {
        // The ostack entry is an object reference.
        _ASSERTE(OpStackTypeGet(valInd).ToCorInfoType() == CORINFO_TYPE_CLASS);

        // Note that "dest" might be a pointer into the heap.  It is therefore important
        // to calculate it *after* any PREEMP transitions at which we might do a GC.  (Thus,
        // we have to duplicate this code with the case above.
        void* dest = OpStackGet<void*>(destInd);
        ThrowOnInvalidPointer(dest);

        GCX_FORBID();

        OBJECTREF val = ObjectToOBJECTREF(OpStackGet<Object*>(valInd));
        SetObjectReference(reinterpret_cast<OBJECTREF*>(dest), val);
    }

    m_curStackHt -= 2;
    m_ILCodePtr += 5;

    BarrierIfVolatile();
}

void Interpreter::InitObj()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    _ASSERTE(m_curStackHt >= 1);
    unsigned destInd = m_curStackHt - 1;
#ifdef _DEBUG
    // Check that src and dest are both pointer types.
    CorInfoType cit = OpStackTypeGet(destInd).ToCorInfoType();
    _ASSERTE_MSG(IsValidPointerType(cit), "Expect pointer on stack");
#endif // _DEBUG

#if INTERP_TRACING
    InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_InitObj]);
#endif // INTERP_TRACING

    CORINFO_CLASS_HANDLE clsHnd = GetTypeFromToken(m_ILCodePtr + 2, CORINFO_TOKENKIND_Class  InterpTracingArg(RTK_InitObj));
    size_t valueClassSz = 0;

    DWORD clsAttribs;
    {
        GCX_PREEMP();
        clsAttribs = m_interpCeeInfo.getClassAttribs(clsHnd);
        if (clsAttribs & CORINFO_FLG_VALUECLASS)
        {
            valueClassSz = getClassSize(clsHnd);
        }
    }

    void* dest = OpStackGet<void*>(destInd);
    ThrowOnInvalidPointer(dest);

    // dest is a vulnerable byref.
    GCX_FORBID();

    if (clsAttribs & CORINFO_FLG_VALUECLASS)
    {
        memset(dest, 0, valueClassSz);
    }
    else
    {
        // The ostack entry is an object reference.
        SetObjectReference(reinterpret_cast<OBJECTREF*>(dest), NULL);
    }
    m_curStackHt -= 1;
    m_ILCodePtr += 6;
}

void Interpreter::LdStr()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    OBJECTHANDLE res = ConstructStringLiteral(m_methInfo->m_module, getU4LittleEndian(m_ILCodePtr + 1));
    {
        GCX_FORBID();
        OpStackSet<Object*>(m_curStackHt, *reinterpret_cast<Object**>(res));
        OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_CLASS));  // Stack-normal type for "string"
        m_curStackHt++;
    }
    m_ILCodePtr += 5;
}

void Interpreter::NewObj()
{
#if INTERP_DYNAMIC_CONTRACTS
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;
#else
    // Dynamic contract occupies too much stack.
    STATIC_CONTRACT_THROWS;
    STATIC_CONTRACT_GC_TRIGGERS;
    STATIC_CONTRACT_MODE_COOPERATIVE;
#endif

    unsigned ctorTok = getU4LittleEndian(m_ILCodePtr + 1);

#if INTERP_TRACING
    InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_NewObj]);
#endif // INTERP_TRACING

    CORINFO_CALL_INFO callInfo;
    CORINFO_RESOLVED_TOKEN methTok;

    {
        GCX_PREEMP();
        ResolveToken(&methTok, ctorTok, CORINFO_TOKENKIND_Ldtoken InterpTracingArg(RTK_NewObj));
        m_interpCeeInfo.getCallInfo(&methTok, NULL,
                                    m_methInfo->m_method,
                                    CORINFO_CALLINFO_FLAGS(0),
                                    &callInfo);
    }

    unsigned mflags = callInfo.methodFlags;

    if ((mflags & (CORINFO_FLG_STATIC|CORINFO_FLG_ABSTRACT)) != 0)
    {
        VerificationError("newobj on static or abstract method");
    }

    unsigned clsFlags = callInfo.classFlags;

#ifdef _DEBUG
    // What class are we allocating?
    const char* clsName;

    {
        GCX_PREEMP();
        clsName = m_interpCeeInfo.getClassName(methTok.hClass);
    }
#endif // _DEBUG

    // There are four cases:
    // 1) Value types (ordinary constructor, resulting VALUECLASS pushed)
    // 2) String (var-args constructor, result automatically pushed)
    // 3) MDArray (var-args constructor, resulting OBJECTREF pushed)
    // 4) Reference types (ordinary constructor, resulting OBJECTREF pushed)
    if (clsFlags & CORINFO_FLG_VALUECLASS)
    {
        void* tempDest;
        INT64 smallTempDest = 0;
        size_t sz = 0;
        {
            GCX_PREEMP();
            sz = getClassSize(methTok.hClass);
        }
        if (sz > sizeof(INT64))
        {
            // TODO: Make sure this is deleted in the face of exceptions.
            tempDest = new BYTE[sz];
        }
        else
        {
            tempDest = &smallTempDest;
        }
        memset(tempDest, 0, sz);
        InterpreterType structValRetIT(&m_interpCeeInfo, methTok.hClass);
        m_structRetValITPtr = &structValRetIT;
        m_structRetValTempSpace = tempDest;

        DoCallWork(/*virtCall*/false, tempDest, &methTok, &callInfo);

        if (sz > sizeof(INT64))
        {
            void* dest = LargeStructOperandStackPush(sz);
            memcpy(dest, tempDest, sz);
            delete[] reinterpret_cast<BYTE*>(tempDest);
            OpStackSet<void*>(m_curStackHt, dest);
        }
        else
        {
            OpStackSet<INT64>(m_curStackHt, GetSmallStructValue(tempDest, sz));
        }
        if (m_structRetValITPtr->IsStruct())
        {
            OpStackTypeSet(m_curStackHt, *m_structRetValITPtr);
        }
        else
        {
            // Must stack-normalize primitive types.
            OpStackTypeSet(m_curStackHt, m_structRetValITPtr->StackNormalize());
        }
        // "Unregister" the temp space for GC scanning...
        m_structRetValITPtr = NULL;
        m_curStackHt++;
    }
    else if ((clsFlags & CORINFO_FLG_VAROBJSIZE) && !(clsFlags & CORINFO_FLG_ARRAY))
    {
        // For a VAROBJSIZE class (currently == String), pass NULL as this to "pseudo-constructor."
        void* specialFlagArg = reinterpret_cast<void*>(0x1);  // Special value for "thisArg" argument of "DoCallWork": push NULL that's not on op stack.
        DoCallWork(/*virtCall*/false, specialFlagArg, &methTok, &callInfo);  // pushes result automatically
    }
    else
    {
        OBJECTREF thisArgObj = NULL;
        GCPROTECT_BEGIN(thisArgObj);

        if (clsFlags & CORINFO_FLG_ARRAY)
        {
            _ASSERTE(clsFlags & CORINFO_FLG_VAROBJSIZE);

            MethodDesc* methDesc = GetMethod(methTok.hMethod);

            PCCOR_SIGNATURE pSig;
            DWORD cbSigSize;
            methDesc->GetSig(&pSig, &cbSigSize);
            MetaSig msig(pSig, cbSigSize, methDesc->GetModule(), NULL);

            unsigned dwNumArgs = msig.NumFixedArgs();
            _ASSERTE(m_curStackHt >= dwNumArgs);
            m_curStackHt -= dwNumArgs;

            INT32* args = (INT32*)_alloca(dwNumArgs * sizeof(INT32));

            unsigned dwArg;
            for (dwArg = 0; dwArg < dwNumArgs; dwArg++)
            {
                unsigned stkInd = m_curStackHt + dwArg;
                bool loose = s_InterpreterLooseRules && (OpStackTypeGet(stkInd).ToCorInfoType() == CORINFO_TYPE_NATIVEINT);
                if (OpStackTypeGet(stkInd).ToCorInfoType() != CORINFO_TYPE_INT && !loose)
                {
                    VerificationError("MD array dimension bounds and sizes must be int.");
                }
                args[dwArg] = loose ? (INT32) OpStackGet<NativeInt>(stkInd) : OpStackGet<INT32>(stkInd);
            }

            thisArgObj = AllocateArrayEx(TypeHandle(methTok.hClass), args, dwNumArgs);
        }
        else
        {
            CorInfoHelpFunc newHelper;
            {
                GCX_PREEMP();
                bool sideEffect;
                newHelper = m_interpCeeInfo.getNewHelper(methTok.hClass, m_methInfo->m_method, &sideEffect);
            }

            MethodTable * pNewObjMT = GetMethodTableFromClsHnd(methTok.hClass);
            switch (newHelper)
            {
            case CORINFO_HELP_NEWFAST:
            default:
                thisArgObj = AllocateObject(pNewObjMT);
                break;
            }

            DoCallWork(/*virtCall*/false, OBJECTREFToObject(thisArgObj), &methTok, &callInfo);
        }

        {
            GCX_FORBID();
            OpStackSet<Object*>(m_curStackHt, OBJECTREFToObject(thisArgObj));
            OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_CLASS));
            m_curStackHt++;
        }
        GCPROTECT_END();  // For "thisArgObj"
    }

    m_ILCodePtr += 5;
}

void Interpreter::NewArr()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    _ASSERTE(m_curStackHt > 0);
    unsigned stkInd = m_curStackHt-1;
    CorInfoType cit = OpStackTypeGet(stkInd).ToCorInfoType();
    NativeInt sz = 0;
    switch (cit)
    {
    case CORINFO_TYPE_INT:
        sz = static_cast<NativeInt>(OpStackGet<INT32>(stkInd));
        break;
    case CORINFO_TYPE_NATIVEINT:
        sz = OpStackGet<NativeInt>(stkInd);
        break;
    default:
        VerificationError("Size operand of 'newarr' must be int or native int.");
    }

    unsigned elemTypeTok = getU4LittleEndian(m_ILCodePtr + 1);

    CORINFO_CLASS_HANDLE elemClsHnd;

#if INTERP_TRACING
    InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_NewArr]);
#endif // INTERP_TRACING

    CORINFO_RESOLVED_TOKEN elemTypeResolvedTok;

    {
        GCX_PREEMP();
        ResolveToken(&elemTypeResolvedTok, elemTypeTok, CORINFO_TOKENKIND_Newarr InterpTracingArg(RTK_NewArr));
        elemClsHnd = elemTypeResolvedTok.hClass;
    }

    {
        if (sz < 0)
        {
            COMPlusThrow(kOverflowException);
        }

#ifdef HOST_64BIT
        // Even though ECMA allows using a native int as the argument to newarr instruction
        // (therefore size is INT_PTR), ArrayBase::m_NumComponents is 32-bit, so even on 64-bit
        // platforms we can't create an array whose size exceeds 32 bits.
        if (sz > INT_MAX)
        {
            EX_THROW(EEMessageException, (kOverflowException, IDS_EE_ARRAY_DIMENSIONS_EXCEEDED));
        }
#endif

        TypeHandle th(elemClsHnd);
        MethodTable* pArrayMT = th.GetMethodTable();
        pArrayMT->CheckRunClassInitThrowing();

        INT32 size32 = (INT32)sz;
        Object* newarray = OBJECTREFToObject(AllocateSzArray(pArrayMT, size32));

        GCX_FORBID();
        OpStackTypeSet(stkInd, InterpreterType(CORINFO_TYPE_CLASS));
        OpStackSet<Object*>(stkInd, newarray);
    }

    m_ILCodePtr += 5;
}

void Interpreter::IsInst()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

#if INTERP_TRACING
    InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_IsInst]);
#endif // INTERP_TRACING

    CORINFO_CLASS_HANDLE cls = GetTypeFromToken(m_ILCodePtr + 1, CORINFO_TOKENKIND_Casting  InterpTracingArg(RTK_IsInst));

    _ASSERTE(m_curStackHt >= 1);
    unsigned idx = m_curStackHt - 1;
#ifdef _DEBUG
    CorInfoType cit = OpStackTypeGet(idx).ToCorInfoType();
    _ASSERTE(cit == CORINFO_TYPE_CLASS || cit == CORINFO_TYPE_STRING);
#endif // DEBUG

    Object * pObj = OpStackGet<Object*>(idx);
    if (pObj != NULL)
    {
        if (!ObjIsInstanceOf(pObj, TypeHandle(cls)))
            OpStackSet<Object*>(idx, NULL);
    }

    // Type stack stays unmodified.

    m_ILCodePtr += 5;
}

void Interpreter::CastClass()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

#if INTERP_TRACING
    InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_CastClass]);
#endif // INTERP_TRACING

    CORINFO_CLASS_HANDLE cls = GetTypeFromToken(m_ILCodePtr + 1, CORINFO_TOKENKIND_Casting  InterpTracingArg(RTK_CastClass));

    _ASSERTE(m_curStackHt >= 1);
    unsigned idx = m_curStackHt - 1;
#ifdef _DEBUG
    CorInfoType cit = OpStackTypeGet(idx).ToCorInfoType();
    _ASSERTE(cit == CORINFO_TYPE_CLASS || cit == CORINFO_TYPE_STRING);
#endif // _DEBUG

    Object * pObj = OpStackGet<Object*>(idx);
    if (pObj != NULL)
    {
        if (!ObjIsInstanceOf(pObj, TypeHandle(cls), TRUE))
        {
            UNREACHABLE(); //ObjIsInstanceOf will throw if cast can't be done
        }
    }


    // Type stack stays unmodified.

    m_ILCodePtr += 5;
}

void Interpreter::LocAlloc()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    _ASSERTE(m_curStackHt >= 1);
    unsigned idx = m_curStackHt - 1;
    CorInfoType cit = OpStackTypeGet(idx).ToCorInfoType();
    NativeUInt sz = 0;
    if (cit == CORINFO_TYPE_INT || cit == CORINFO_TYPE_UINT)
    {
        sz = static_cast<NativeUInt>(OpStackGet<UINT32>(idx));
    }
    else if (cit == CORINFO_TYPE_NATIVEINT || cit == CORINFO_TYPE_NATIVEUINT)
    {
        sz = OpStackGet<NativeUInt>(idx);
    }
    else if (s_InterpreterLooseRules && cit == CORINFO_TYPE_LONG)
    {
        sz = (NativeUInt) OpStackGet<INT64>(idx);
    }
    else
    {
        VerificationError("localloc requires int or nativeint argument.");
    }
    if (sz == 0)
    {
        OpStackSet<void*>(idx, NULL);
    }
    else
    {
        void* res = GetLocAllocData()->Alloc(sz);
        if (res == NULL) ThrowStackOverflow();
        OpStackSet<void*>(idx, res);
    }
    OpStackTypeSet(idx, InterpreterType(CORINFO_TYPE_NATIVEINT));
}

void Interpreter::MkRefany()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

#if INTERP_TRACING
    InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_MkRefAny]);
#endif // INTERP_TRACING

    CORINFO_CLASS_HANDLE cls = GetTypeFromToken(m_ILCodePtr + 1, CORINFO_TOKENKIND_Class  InterpTracingArg(RTK_MkRefAny));
    _ASSERTE(m_curStackHt >= 1);
    unsigned idx = m_curStackHt - 1;

    CorInfoType cit = OpStackTypeGet(idx).ToCorInfoType();
    if (!(cit == CORINFO_TYPE_BYREF || cit == CORINFO_TYPE_NATIVEINT))
        VerificationError("MkRefany requires byref or native int (pointer) on the stack.");

    void* ptr = OpStackGet<void*>(idx);

    InterpreterType typedRefIT = GetTypedRefIT(&m_interpCeeInfo);
    TypedByRef* tbr;
#if defined(HOST_AMD64)
    _ASSERTE(typedRefIT.IsLargeStruct(&m_interpCeeInfo));
    tbr = (TypedByRef*) LargeStructOperandStackPush(GetTypedRefSize(&m_interpCeeInfo));
    OpStackSet<void*>(idx, tbr);
#elif defined(HOST_X86) || defined(HOST_ARM)
    _ASSERTE(!typedRefIT.IsLargeStruct(&m_interpCeeInfo));
    tbr = OpStackGetAddr<TypedByRef>(idx);
#elif defined(HOST_ARM64)
    tbr = NULL;
    NYI_INTERP("Unimplemented code: MkRefAny");
#elif defined(HOST_LOONGARCH64)
    tbr = NULL;
    NYI_INTERP("Unimplemented code: MkRefAny on LOONGARCH");
#elif defined(HOST_RISCV64)
    tbr = NULL;
    NYI_INTERP("Unimplemented code: MkRefAny on RISCV64");
#else
#error "unsupported platform"
#endif
    tbr->data = ptr;
    tbr->type = TypeHandle(cls);
    OpStackTypeSet(idx, typedRefIT);

    m_ILCodePtr += 5;
}

void Interpreter::RefanyType()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    _ASSERTE(m_curStackHt > 0);
    unsigned idx = m_curStackHt - 1;

    if (OpStackTypeGet(idx) != GetTypedRefIT(&m_interpCeeInfo))
        VerificationError("RefAnyVal requires a TypedRef on the stack.");

    TypedByRef* ptbr = OpStackGet<TypedByRef*>(idx);
    LargeStructOperandStackPop(sizeof(TypedByRef), ptbr);

    TypeHandle* pth = &ptbr->type;

    {
        OBJECTREF classobj = TypeHandleToTypeRef(pth);
        GCX_FORBID();
        OpStackSet<Object*>(idx, OBJECTREFToObject(classobj));
        OpStackTypeSet(idx, InterpreterType(CORINFO_TYPE_CLASS));
    }
    m_ILCodePtr += 2;
}

// This (unfortunately) duplicates code in JIT_GetRuntimeTypeHandle, which
// isn't callable because it sets up a Helper Method Frame.
OBJECTREF Interpreter::TypeHandleToTypeRef(TypeHandle* pth)
{
    OBJECTREF typePtr = NULL;
    if (!pth->IsTypeDesc())
    {
        // Most common... and fastest case
        typePtr = pth->AsMethodTable()->GetManagedClassObjectIfExists();
        if (typePtr == NULL)
        {
            typePtr = pth->GetManagedClassObject();
        }
    }
    else
    {
        typePtr = pth->GetManagedClassObject();
    }
    return typePtr;
}

CorInfoType Interpreter::GetTypeForPrimitiveValueClass(CORINFO_CLASS_HANDLE clsHnd)
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    GCX_PREEMP();

    return m_interpCeeInfo.getTypeForPrimitiveValueClass(clsHnd);
}

void Interpreter::RefanyVal()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    _ASSERTE(m_curStackHt > 0);
    unsigned idx = m_curStackHt - 1;

    if (OpStackTypeGet(idx) != GetTypedRefIT(&m_interpCeeInfo))
        VerificationError("RefAnyVal requires a TypedRef on the stack.");

#if INTERP_TRACING
    InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_RefAnyVal]);
#endif // INTERP_TRACING

    CORINFO_CLASS_HANDLE cls = GetTypeFromToken(m_ILCodePtr + 1, CORINFO_TOKENKIND_Class  InterpTracingArg(RTK_RefAnyVal));
    TypeHandle expected(cls);

    TypedByRef* ptbr = OpStackGet<TypedByRef*>(idx);
    LargeStructOperandStackPop(sizeof(TypedByRef), ptbr);
    if (expected != ptbr->type) ThrowInvalidCastException();

    OpStackSet<void*>(idx, static_cast<void*>(ptbr->data));
    OpStackTypeSet(idx, InterpreterType(CORINFO_TYPE_BYREF));

    m_ILCodePtr += 5;
}

void Interpreter::CkFinite()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    _ASSERTE(m_curStackHt > 0);
    unsigned idx = m_curStackHt - 1;

    CorInfoType cit = OpStackTypeGet(idx).ToCorInfoType();
    double val = 0.0;

    switch (cit)
    {
    case CORINFO_TYPE_FLOAT:
        val = (double)OpStackGet<float>(idx);
        break;
    case CORINFO_TYPE_DOUBLE:
        val = OpStackGet<double>(idx);
        break;
    default:
        VerificationError("CkFinite requires a floating-point value on the stack.");
        break;
    }

    if (!_finite(val))
        ThrowSysArithException();
}

void Interpreter::LdToken()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    unsigned tokVal = getU4LittleEndian(m_ILCodePtr + 1);

#if INTERP_TRACING
    InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_LdToken]);
#endif // INTERP_TRACING


    CORINFO_RESOLVED_TOKEN tok;
    {
        GCX_PREEMP();
        ResolveToken(&tok, tokVal, CORINFO_TOKENKIND_Ldtoken InterpTracingArg(RTK_LdToken));
    }

    // To save duplication of the factored code at the bottom, I don't do GCX_FORBID for
    // these Object* values, but this comment documents the intent.
    if (tok.hMethod != NULL)
    {
        MethodDesc* pMethod = (MethodDesc*)tok.hMethod;
        Object* objPtr = OBJECTREFToObject((OBJECTREF)pMethod->GetStubMethodInfo());
        OpStackSet<Object*>(m_curStackHt, objPtr);
    }
    else if (tok.hField != NULL)
    {
        FieldDesc * pField = (FieldDesc *)tok.hField;
        Object* objPtr = OBJECTREFToObject((OBJECTREF)pField->GetStubFieldInfo());
        OpStackSet<Object*>(m_curStackHt, objPtr);
    }
    else
    {
        TypeHandle th(tok.hClass);
        Object* objPtr = OBJECTREFToObject(th.GetManagedClassObject());
        OpStackSet<Object*>(m_curStackHt, objPtr);
    }

    {
        GCX_FORBID();
        OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_CLASS));
        m_curStackHt++;
    }

    m_ILCodePtr += 5;
}

void Interpreter::LdFtn()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    unsigned tokVal = getU4LittleEndian(m_ILCodePtr + 2);

#if INTERP_TRACING
    InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_LdFtn]);
#endif // INTERP_TRACING

    CORINFO_RESOLVED_TOKEN tok;
    CORINFO_CALL_INFO callInfo;
    {
        GCX_PREEMP();
        ResolveToken(&tok, tokVal, CORINFO_TOKENKIND_Method InterpTracingArg(RTK_LdFtn));
        m_interpCeeInfo.getCallInfo(&tok, NULL, m_methInfo->m_method,
                                  combine(CORINFO_CALLINFO_SECURITYCHECKS,CORINFO_CALLINFO_LDFTN),
                                  &callInfo);
    }

    switch (callInfo.kind)
    {
    case CORINFO_CALL:
        {
            PCODE pCode = ((MethodDesc *)callInfo.hMethod)->GetMultiCallableAddrOfCode();
            OpStackSet<void*>(m_curStackHt, (void *)pCode);
            GetFunctionPointerStack()[m_curStackHt] = callInfo.hMethod;
        }
        break;
    case CORINFO_CALL_CODE_POINTER:
        NYI_INTERP("Indirect code pointer.");
        break;
    default:
        _ASSERTE_MSG(false, "Should not reach here: unknown call kind.");
        break;
    }
    OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_NATIVEINT));
    m_curStackHt++;
    m_ILCodePtr += 6;
}

void Interpreter::LdVirtFtn()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    _ASSERTE(m_curStackHt >= 1);
    unsigned ind = m_curStackHt - 1;

    unsigned tokVal = getU4LittleEndian(m_ILCodePtr + 2);

#if INTERP_TRACING
    InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_LdVirtFtn]);
#endif // INTERP_TRACING

    CORINFO_RESOLVED_TOKEN tok;
    CORINFO_CALL_INFO callInfo;
    CORINFO_CLASS_HANDLE classHnd;
    CORINFO_METHOD_HANDLE methodHnd;
    {
        GCX_PREEMP();
        ResolveToken(&tok, tokVal, CORINFO_TOKENKIND_Method InterpTracingArg(RTK_LdVirtFtn));
        m_interpCeeInfo.getCallInfo(&tok, NULL, m_methInfo->m_method,
                                    combine(CORINFO_CALLINFO_CALLVIRT,
                                            combine(CORINFO_CALLINFO_SECURITYCHECKS,
                                                    CORINFO_CALLINFO_LDFTN)),
                                    &callInfo);


        classHnd = tok.hClass;
        methodHnd = tok.hMethod;
    }

    MethodDesc * pMD = (MethodDesc *)methodHnd;
    PCODE pCode;
    if (pMD->IsVtableMethod())
    {
        Object* obj = OpStackGet<Object*>(ind);
        ThrowOnInvalidPointer(obj);

        OBJECTREF objRef = ObjectToOBJECTREF(obj);
        GCPROTECT_BEGIN(objRef);
        pCode = pMD->GetMultiCallableAddrOfVirtualizedCode(&objRef, TypeHandle(classHnd));
        GCPROTECT_END();

        pMD = Entry2MethodDesc(pCode, TypeHandle(classHnd).GetMethodTable());
    }
    else
    {
        pCode = pMD->GetMultiCallableAddrOfCode();
    }
    OpStackSet<void*>(ind, (void *)pCode);
    GetFunctionPointerStack()[ind] = (CORINFO_METHOD_HANDLE)pMD;

    OpStackTypeSet(ind, InterpreterType(CORINFO_TYPE_NATIVEINT));
    m_ILCodePtr += 6;
}

void Interpreter::Sizeof()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

#if INTERP_TRACING
    InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_Sizeof]);
#endif // INTERP_TRACING

    CORINFO_CLASS_HANDLE cls = GetTypeFromToken(m_ILCodePtr + 2, CORINFO_TOKENKIND_Class  InterpTracingArg(RTK_Sizeof));
    unsigned sz;
    {
        GCX_PREEMP();
        CorInfoType cit = ::asCorInfoType(cls);
        // For class types, the ECMA spec says to return the size of the object reference, not the referent
        // object.  Everything else should be a value type, for which we can just return the size as reported
        // by the EE.
        switch (cit)
        {
        case CORINFO_TYPE_CLASS:
            sz = sizeof(Object*);
            break;
        default:
            sz = getClassSize(cls);
            break;
        }
    }

    OpStackSet<UINT32>(m_curStackHt, sz);
    OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_INT));
    m_curStackHt++;
    m_ILCodePtr += 6;
}


// static:
bool Interpreter::s_initialized = false;
bool Interpreter::s_compilerStaticsInitialized = false;
size_t Interpreter::s_TypedRefSize;
CORINFO_CLASS_HANDLE Interpreter::s_TypedRefClsHnd;
InterpreterType Interpreter::s_TypedRefIT;

// Must call GetTypedRefIT
size_t Interpreter::GetTypedRefSize(CEEInfo* info)
{
    _ASSERTE_MSG(s_compilerStaticsInitialized, "Precondition");
    return s_TypedRefSize;
}

InterpreterType Interpreter::GetTypedRefIT(CEEInfo* info)
{
    _ASSERTE_MSG(s_compilerStaticsInitialized, "Precondition");
    return s_TypedRefIT;
}

CORINFO_CLASS_HANDLE Interpreter::GetTypedRefClsHnd(CEEInfo* info)
{
    _ASSERTE_MSG(s_compilerStaticsInitialized, "Precondition");
    return s_TypedRefClsHnd;
}

void Interpreter::Initialize()
{
    _ASSERTE(!s_initialized);

    s_InterpretMeths.ensureInit(CLRConfig::INTERNAL_Interpret);
    s_InterpretMethsExclude.ensureInit(CLRConfig::INTERNAL_InterpretExclude);
    s_InterpreterUseCaching = (s_InterpreterUseCachingFlag.val(CLRConfig::INTERNAL_InterpreterUseCaching) != 0);
    s_InterpreterLooseRules = (s_InterpreterLooseRulesFlag.val(CLRConfig::INTERNAL_InterpreterLooseRules) != 0);
    s_InterpreterDoLoopMethods = (s_InterpreterDoLoopMethodsFlag.val(CLRConfig::INTERNAL_InterpreterDoLoopMethods) != 0);

    // Initialize the lock used to protect method locks.
    // TODO: it would be better if this were a reader/writer lock.
    s_methodCacheLock.Init(CrstLeafLock, CRST_DEFAULT);

    // Similarly, initialize the lock used to protect the map from
    // interpreter stub addresses to their method descs.
    s_interpStubToMDMapLock.Init(CrstLeafLock, CRST_DEFAULT);

    s_initialized = true;

#if INTERP_ILINSTR_PROFILE
    SetILInstrCategories();
#endif // INTERP_ILINSTR_PROFILE
}

void Interpreter::InitializeCompilerStatics(CEEInfo* info)
{
    if (!s_compilerStaticsInitialized)
    {
        // TODO: I believe I need no synchronization around this on x86, but I do
        // on more permissive memory models.  (Why it's OK on x86: each thread executes this
        // before any access to the initialized static variables; if several threads do
        // so, they perform idempotent initializing writes to the statics.
        GCX_PREEMP();
        s_TypedRefClsHnd = info->getBuiltinClass(CLASSID_TYPED_BYREF);
        s_TypedRefIT = InterpreterType(info, s_TypedRefClsHnd);
        s_TypedRefSize = getClassSize(s_TypedRefClsHnd);
        s_compilerStaticsInitialized = true;
        // TODO: Need store-store memory barrier here.
    }
}

void Interpreter::Terminate()
{
    if (s_initialized)
    {
        s_methodCacheLock.Destroy();
        s_interpStubToMDMapLock.Destroy();
        s_initialized = false;
    }
}

#if INTERP_ILINSTR_PROFILE
void Interpreter::SetILInstrCategories()
{
    // Start with the indentity maps
    for (unsigned short instr = 0; instr < 512; instr++) s_ILInstrCategories[instr] = instr;
    // Now make exceptions.
    for (unsigned instr = CEE_LDARG_0; instr <= CEE_LDARG_3; instr++) s_ILInstrCategories[instr] = CEE_LDARG;
    s_ILInstrCategories[CEE_LDARG_S] = CEE_LDARG;

    for (unsigned instr = CEE_LDLOC_0; instr <= CEE_LDLOC_3; instr++) s_ILInstrCategories[instr] = CEE_LDLOC;
    s_ILInstrCategories[CEE_LDLOC_S] = CEE_LDLOC;

    for (unsigned instr = CEE_STLOC_0; instr <= CEE_STLOC_3; instr++) s_ILInstrCategories[instr] = CEE_STLOC;
    s_ILInstrCategories[CEE_STLOC_S] = CEE_STLOC;

    s_ILInstrCategories[CEE_LDLOCA_S] = CEE_LDLOCA;

    for (unsigned instr = CEE_LDC_I4_M1; instr <= CEE_LDC_I4_S; instr++) s_ILInstrCategories[instr] = CEE_LDC_I4;

    for (unsigned instr = CEE_BR_S; instr <= CEE_BLT_UN; instr++) s_ILInstrCategories[instr] = CEE_BR;

    for (unsigned instr = CEE_LDIND_I1; instr <= CEE_LDIND_REF; instr++) s_ILInstrCategories[instr] = CEE_LDIND_I;

    for (unsigned instr = CEE_STIND_REF; instr <= CEE_STIND_R8; instr++) s_ILInstrCategories[instr] = CEE_STIND_I;

    for (unsigned instr = CEE_ADD; instr <= CEE_REM_UN; instr++) s_ILInstrCategories[instr] = CEE_ADD;

    for (unsigned instr = CEE_AND; instr <= CEE_NOT; instr++) s_ILInstrCategories[instr] = CEE_AND;

    for (unsigned instr = CEE_CONV_I1; instr <= CEE_CONV_U8; instr++) s_ILInstrCategories[instr] = CEE_CONV_I;
    for (unsigned instr = CEE_CONV_OVF_I1_UN; instr <= CEE_CONV_OVF_U_UN; instr++) s_ILInstrCategories[instr] = CEE_CONV_I;

    for (unsigned instr = CEE_LDELEM_I1; instr <= CEE_LDELEM_REF; instr++) s_ILInstrCategories[instr] = CEE_LDELEM;
    for (unsigned instr = CEE_STELEM_I; instr <= CEE_STELEM_REF; instr++) s_ILInstrCategories[instr] = CEE_STELEM;

    for (unsigned instr = CEE_CONV_OVF_I1; instr <= CEE_CONV_OVF_U8; instr++) s_ILInstrCategories[instr] = CEE_CONV_I;
    for (unsigned instr = CEE_CONV_U2; instr <= CEE_CONV_U1; instr++) s_ILInstrCategories[instr] = CEE_CONV_I;
    for (unsigned instr = CEE_CONV_OVF_I; instr <= CEE_CONV_OVF_U; instr++) s_ILInstrCategories[instr] = CEE_CONV_I;

    for (unsigned instr = CEE_ADD_OVF; instr <= CEE_SUB_OVF; instr++) s_ILInstrCategories[instr] = CEE_ADD_OVF;

    s_ILInstrCategories[CEE_LEAVE_S] = CEE_LEAVE;
    s_ILInstrCategories[CEE_CONV_U] = CEE_CONV_I;
}
#endif // INTERP_ILINSTR_PROFILE


template<int op>
void Interpreter::CompareOp()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    _ASSERTE(m_curStackHt >= 2);
    unsigned op1idx = m_curStackHt - 2;
    INT32 res = CompareOpRes<op>(op1idx);
    OpStackSet<INT32>(op1idx, res);
    OpStackTypeSet(op1idx, InterpreterType(CORINFO_TYPE_INT));
    m_curStackHt--;
}

template<int op>
INT32 Interpreter::CompareOpRes(unsigned op1idx)
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    _ASSERTE(m_curStackHt >= op1idx + 2);
    unsigned op2idx = op1idx + 1;
    InterpreterType t1 = OpStackTypeGet(op1idx);
    CorInfoType cit1 = t1.ToCorInfoType();
    _ASSERTE(IsStackNormalType(cit1));
    InterpreterType t2 = OpStackTypeGet(op2idx);
    CorInfoType cit2 = t2.ToCorInfoType();
    _ASSERTE(IsStackNormalType(cit2));
    INT32 res = 0;

    switch (cit1)
    {
    case CORINFO_TYPE_INT:
        if (cit2 == CORINFO_TYPE_INT)
        {
            INT32 val1 = OpStackGet<INT32>(op1idx);
            INT32 val2 = OpStackGet<INT32>(op2idx);
            if (op == CO_EQ)
            {
                if (val1 == val2) res = 1;
            }
            else if (op == CO_GT)
            {
                if (val1 > val2) res  = 1;
            }
            else if (op == CO_GT_UN)
            {
                if (static_cast<UINT32>(val1) > static_cast<UINT32>(val2)) res = 1;
            }
            else if (op == CO_LT)
            {
                if (val1 < val2) res  = 1;
            }
            else
            {
                _ASSERTE(op == CO_LT_UN);
                if (static_cast<UINT32>(val1) < static_cast<UINT32>(val2)) res = 1;
            }
        }
        else if (cit2 == CORINFO_TYPE_NATIVEINT ||
                 (s_InterpreterLooseRules && cit2 == CORINFO_TYPE_BYREF) ||
                 (cit2 == CORINFO_TYPE_VALUECLASS
                  && CorInfoTypeStackNormalize(GetTypeForPrimitiveValueClass(t2.ToClassHandle())) == CORINFO_TYPE_NATIVEINT))
        {
            NativeInt val1 = OpStackGet<NativeInt>(op1idx);
            NativeInt val2 = OpStackGet<NativeInt>(op2idx);
            if (op == CO_EQ)
            {
                if (val1 == val2) res = 1;
            }
            else if (op == CO_GT)
            {
                if (val1 > val2) res  = 1;
            }
            else if (op == CO_GT_UN)
            {
                if (static_cast<NativeUInt>(val1) > static_cast<NativeUInt>(val2)) res = 1;
            }
            else if (op == CO_LT)
            {
                if (val1 < val2) res  = 1;
            }
            else
            {
                _ASSERTE(op == CO_LT_UN);
                if (static_cast<NativeUInt>(val1) < static_cast<NativeUInt>(val2)) res = 1;
            }
        }
        else if (cit2 == CORINFO_TYPE_VALUECLASS)
        {
            cit2 = GetTypeForPrimitiveValueClass(t2.ToClassHandle());
            INT32 val1 = OpStackGet<INT32>(op1idx);
            INT32 val2 = 0;
            if (CorInfoTypeStackNormalize(cit2) == CORINFO_TYPE_INT)
            {

                size_t sz = t2.Size(&m_interpCeeInfo);
                switch (sz)
                {
                case 1:
                    if (CorInfoTypeIsUnsigned(cit2))
                    {
                        val2 = OpStackGet<UINT8>(op2idx);
                    }
                    else
                    {
                        val2 = OpStackGet<INT8>(op2idx);
                    }
                    break;
                case 2:
                    if (CorInfoTypeIsUnsigned(cit2))
                    {
                        val2 = OpStackGet<UINT16>(op2idx);
                    }
                    else
                    {
                        val2 = OpStackGet<INT16>(op2idx);
                    }
                    break;
                case 4:
                    val2 = OpStackGet<INT32>(op2idx);
                    break;
                default:
                    UNREACHABLE();
                }
            }
            else
            {
                VerificationError("Can't compare with struct type.");
            }
            if (op == CO_EQ)
            {
                if (val1 == val2) res = 1;
            }
            else if (op == CO_GT)
            {
                if (val1 > val2) res  = 1;
            }
            else if (op == CO_GT_UN)
            {
                if (static_cast<UINT32>(val1) > static_cast<UINT32>(val2)) res = 1;
            }
            else if (op == CO_LT)
            {
                if (val1 < val2) res  = 1;
            }
            else
            {
                _ASSERTE(op == CO_LT_UN);
                if (static_cast<UINT32>(val1) < static_cast<UINT32>(val2)) res = 1;
            }
        }
        else
        {
            VerificationError("Binary comparison operation: type mismatch.");
        }
        break;
    case CORINFO_TYPE_NATIVEINT:
        if (cit2 == CORINFO_TYPE_NATIVEINT || cit2 == CORINFO_TYPE_INT
            || (s_InterpreterLooseRules && cit2 == CORINFO_TYPE_LONG)
            || (s_InterpreterLooseRules && cit2 == CORINFO_TYPE_BYREF)
            || (s_InterpreterLooseRules && cit2 == CORINFO_TYPE_CLASS && OpStackGet<void*>(op2idx) == 0))
        {
            NativeInt val1 = OpStackGet<NativeInt>(op1idx);
            NativeInt val2;
            if (cit2 == CORINFO_TYPE_NATIVEINT)
            {
                val2 = OpStackGet<NativeInt>(op2idx);
            }
            else if (cit2 == CORINFO_TYPE_INT)
            {
                val2 = static_cast<NativeInt>(OpStackGet<INT32>(op2idx));
            }
            else if (s_InterpreterLooseRules && cit2 == CORINFO_TYPE_LONG)
            {
                val2 = static_cast<NativeInt>(OpStackGet<INT64>(op2idx));
            }
            else if (cit2 == CORINFO_TYPE_CLASS)
            {
                _ASSERTE(OpStackGet<void*>(op2idx) == 0);
                val2 = 0;
            }
            else
            {
                _ASSERTE(s_InterpreterLooseRules && cit2 == CORINFO_TYPE_BYREF);
                val2 = reinterpret_cast<NativeInt>(OpStackGet<void*>(op2idx));
            }
            if (op == CO_EQ)
            {
                if (val1 == val2) res = 1;
            }
            else if (op == CO_GT)
            {
                if (val1 > val2) res  = 1;
            }
            else if (op == CO_GT_UN)
            {
                if (static_cast<NativeUInt>(val1) > static_cast<NativeUInt>(val2)) res = 1;
            }
            else if (op == CO_LT)
            {
                if (val1 < val2) res  = 1;
            }
            else
            {
                _ASSERTE(op == CO_LT_UN);
                if (static_cast<NativeUInt>(val1) < static_cast<NativeUInt>(val2)) res = 1;
            }
        }
        else
        {
            VerificationError("Binary comparison operation: type mismatch.");
        }
        break;
    case CORINFO_TYPE_LONG:
        {
            bool looseLong = false;
#if defined(HOST_AMD64)
            looseLong = s_InterpreterLooseRules && (cit2 == CORINFO_TYPE_NATIVEINT || cit2 == CORINFO_TYPE_BYREF);
#endif
            if (cit2 == CORINFO_TYPE_LONG || looseLong)
            {
                INT64 val1 = OpStackGet<INT64>(op1idx);
                INT64 val2 = OpStackGet<INT64>(op2idx);
                if (op == CO_EQ)
                {
                    if (val1 == val2) res = 1;
                }
                else if (op == CO_GT)
                {
                    if (val1 > val2) res  = 1;
                }
                else if (op == CO_GT_UN)
                {
                    if (static_cast<UINT64>(val1) > static_cast<UINT64>(val2)) res = 1;
                }
                else if (op == CO_LT)
                {
                    if (val1 < val2) res  = 1;
                }
                else
                {
                    _ASSERTE(op == CO_LT_UN);
                    if (static_cast<UINT64>(val1) < static_cast<UINT64>(val2)) res = 1;
                }
            }
            else
            {
                VerificationError("Binary comparison operation: type mismatch.");
            }
        }
        break;

    case CORINFO_TYPE_CLASS:
    case CORINFO_TYPE_STRING:
        if (cit2 == CORINFO_TYPE_CLASS || cit2 == CORINFO_TYPE_STRING)
        {
            GCX_FORBID();
            Object* val1 = OpStackGet<Object*>(op1idx);
            Object* val2 = OpStackGet<Object*>(op2idx);
            if (op == CO_EQ)
            {
                if (val1 == val2) res = 1;
            }
            else if (op == CO_GT_UN)
            {
                if (val1 != val2) res  = 1;
            }
            else
            {
                VerificationError("Binary comparison operation: type mismatch.");
            }
        }
        else
        {
            VerificationError("Binary comparison operation: type mismatch.");
        }
        break;


    case CORINFO_TYPE_FLOAT:
        {
            bool isDouble = (s_InterpreterLooseRules && cit2 == CORINFO_TYPE_DOUBLE);
            if (cit2 == CORINFO_TYPE_FLOAT || isDouble)
            {
                float val1 = OpStackGet<float>(op1idx);
                float val2 = (isDouble) ? (float) OpStackGet<double>(op2idx) : OpStackGet<float>(op2idx);
                if (op == CO_EQ)
                {
                    // I'm assuming IEEE math here, so that if at least one is a NAN, the comparison will fail...
                    if (val1 == val2) res = 1;
                }
                else if (op == CO_GT)
                {
                    // I'm assuming that C++ arithmetic does the right thing here with infinities and NANs.
                    if (val1 > val2) res  = 1;
                }
                else if (op == CO_GT_UN)
                {
                    // Check for NAN's here: if either is a NAN, they're unordered, so this comparison returns true.
                    if (_isnan(val1) || _isnan(val2)) res = 1;
                    else if (val1 > val2) res = 1;
                }
                else if (op == CO_LT)
                {
                    if (val1 < val2) res  = 1;
                }
                else
                {
                    _ASSERTE(op == CO_LT_UN);
                    // Check for NAN's here: if either is a NAN, they're unordered, so this comparison returns true.
                    if (_isnan(val1) || _isnan(val2)) res = 1;
                    else if (val1 < val2) res = 1;
                }
            }
            else
            {
                VerificationError("Binary comparison operation: type mismatch.");
            }
        }
        break;

    case CORINFO_TYPE_DOUBLE:
        {
            bool isFloat = (s_InterpreterLooseRules && cit2 == CORINFO_TYPE_FLOAT);
            if (cit2 == CORINFO_TYPE_DOUBLE || isFloat)
            {
                double val1 = OpStackGet<double>(op1idx);
                double val2 = (isFloat) ? (double) OpStackGet<float>(op2idx) : OpStackGet<double>(op2idx);
                if (op == CO_EQ)
                {
                    // I'm assuming IEEE math here, so that if at least one is a NAN, the comparison will fail...
                    if (val1 == val2) res = 1;
                }
                else if (op == CO_GT)
                {
                    // I'm assuming that C++ arithmetic does the right thing here with infinities and NANs.
                    if (val1 > val2) res  = 1;
                }
                else if (op == CO_GT_UN)
                {
                    // Check for NAN's here: if either is a NAN, they're unordered, so this comparison returns true.
                    if (_isnan(val1) || _isnan(val2)) res = 1;
                    else if (val1 > val2) res = 1;
                }
                else if (op == CO_LT)
                {
                    if (val1 < val2) res  = 1;
                }
                else
                {
                    _ASSERTE(op == CO_LT_UN);
                    // Check for NAN's here: if either is a NAN, they're unordered, so this comparison returns true.
                    if (_isnan(val1) || _isnan(val2)) res = 1;
                    else if (val1 < val2) res = 1;
                }
            }
            else
            {
                VerificationError("Binary comparison operation: type mismatch.");
            }
        }
        break;

    case CORINFO_TYPE_BYREF:
        if (cit2 == CORINFO_TYPE_BYREF || (s_InterpreterLooseRules && cit2 == CORINFO_TYPE_NATIVEINT))
        {
            NativeInt val1 = reinterpret_cast<NativeInt>(OpStackGet<void*>(op1idx));
            NativeInt val2;
            if (cit2 == CORINFO_TYPE_BYREF)
            {
                val2 = reinterpret_cast<NativeInt>(OpStackGet<void*>(op2idx));
            }
            else
            {
                _ASSERTE(s_InterpreterLooseRules && cit2 == CORINFO_TYPE_NATIVEINT);
                val2 = OpStackGet<NativeInt>(op2idx);
            }
            if (op == CO_EQ)
            {
                if (val1 == val2) res = 1;
            }
            else if (op == CO_GT)
            {
                if (val1 > val2) res  = 1;
            }
            else if (op == CO_GT_UN)
            {
                if (static_cast<NativeUInt>(val1) > static_cast<NativeUInt>(val2)) res = 1;
            }
            else if (op == CO_LT)
            {
                if (val1 < val2) res  = 1;
            }
            else
            {
                _ASSERTE(op == CO_LT_UN);
                if (static_cast<NativeUInt>(val1) < static_cast<NativeUInt>(val2)) res = 1;
            }
        }
        else
        {
            VerificationError("Binary comparison operation: type mismatch.");
        }
        break;

    case CORINFO_TYPE_VALUECLASS:
        {
            CorInfoType newCit1 = GetTypeForPrimitiveValueClass(t1.ToClassHandle());
            if (newCit1 == CORINFO_TYPE_UNDEF)
            {
                VerificationError("Can't compare a value class.");
            }
            else
            {
                NYI_INTERP("Must eliminate 'punning' value classes from the ostack.");
            }
        }
        break;

    default:
        _ASSERTE(false); // Should not be here if the type is stack-normal.
    }

    return res;
}

template<bool val, int targetLen>
void Interpreter::BrOnValue()
{
    _ASSERTE(targetLen == 1 || targetLen == 4);
    _ASSERTE(m_curStackHt > 0);
    unsigned stackInd = m_curStackHt - 1;
    InterpreterType it = OpStackTypeGet(stackInd);

    // It shouldn't be a value class, unless it's a punning name for a primitive integral type.
    if (it.ToCorInfoType() == CORINFO_TYPE_VALUECLASS)
    {
        GCX_PREEMP();
        CorInfoType cit = m_interpCeeInfo.getTypeForPrimitiveValueClass(it.ToClassHandle());
        if (CorInfoTypeIsIntegral(cit))
        {
            it = InterpreterType(cit);
        }
        else
        {
            VerificationError("Can't branch on the value of a value type that is not a primitive type.");
        }
    }

#ifdef _DEBUG
    switch (it.ToCorInfoType())
    {
    case CORINFO_TYPE_FLOAT:
    case CORINFO_TYPE_DOUBLE:
        VerificationError("Can't branch on the value of a float or double.");
        break;
    default:
        break;
    }
#endif // _DEBUG

    switch (it.SizeNotStruct())
    {
    case 4:
        {
            INT32 branchVal = OpStackGet<INT32>(stackInd);
            BrOnValueTakeBranch((branchVal != 0) == val, targetLen);
        }
        break;
    case 8:
        {
            INT64 branchVal = OpStackGet<INT64>(stackInd);
            BrOnValueTakeBranch((branchVal != 0) == val, targetLen);
        }
        break;

        // The value-class case handled above makes sizes 1 and 2 possible.
    case 1:
        {
            INT8 branchVal = OpStackGet<INT8>(stackInd);
            BrOnValueTakeBranch((branchVal != 0) == val, targetLen);
        }
        break;
    case 2:
        {
            INT16 branchVal = OpStackGet<INT16>(stackInd);
            BrOnValueTakeBranch((branchVal != 0) == val, targetLen);
        }
        break;
    default:
        UNREACHABLE();
        break;
    }
    m_curStackHt = stackInd;
}

// compOp is a member of the BranchComparisonOp enumeration.
template<int compOp, bool reverse, int targetLen>
void Interpreter::BrOnComparison()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    _ASSERTE(targetLen == 1 || targetLen == 4);
    _ASSERTE(m_curStackHt >= 2);
    unsigned v1Ind = m_curStackHt - 2;

    INT32 res = CompareOpRes<compOp>(v1Ind);
    if (reverse)
    {
        res = (res == 0) ? 1 : 0;
    }

    if  (res)
    {
        int offset;
        if (targetLen == 1)
        {
            // BYTE is unsigned...
            offset = getI1(m_ILCodePtr + 1);
        }
        else
        {
            offset = getI4LittleEndian(m_ILCodePtr + 1);
        }
        // 1 is the size of the current instruction; offset is relative to start of next.
        if (offset < 0)
        {
            // Backwards branch; enable caching.
            BackwardsBranchActions(offset);
        }
        ExecuteBranch(m_ILCodePtr + 1 + targetLen + offset);
    }
    else
    {
        m_ILCodePtr += targetLen + 1;
    }
    m_curStackHt -= 2;
}

void Interpreter::LdFld(FieldDesc* fldIn)
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    BarrierIfVolatile();

    FieldDesc* fld = fldIn;
    CORINFO_CLASS_HANDLE valClsHnd = NULL;
    DWORD fldOffset;
    {
        GCX_PREEMP();
        unsigned ilOffset = CurOffset();
        if (fld == NULL && s_InterpreterUseCaching)
        {
#if INTERP_TRACING
            InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_LdFld]);
#endif // INTERP_TRACING
            fld = GetCachedInstanceField(ilOffset);
        }
        if (fld == NULL)
        {
            unsigned tok = getU4LittleEndian(m_ILCodePtr + sizeof(BYTE));
            fld = FindField(tok  InterpTracingArg(RTK_LdFld));
            _ASSERTE(fld != NULL);

            fldOffset = fld->GetOffset();
            if (s_InterpreterUseCaching && fldOffset < FIELD_OFFSET_LAST_REAL_OFFSET)
                CacheInstanceField(ilOffset, fld);
        }
        else
        {
            fldOffset = fld->GetOffset();
        }
    }
    CorInfoType valCit = CEEInfo::asCorInfoType(fld->GetFieldType());

    // If "fldIn" is non-NULL, it's not a "real" LdFld -- the caller should handle updating the instruction pointer.
    if (fldIn == NULL)
        m_ILCodePtr += 5;  // Last use above, so update now.

    // We need to construct the interpreter type for a struct type before we try to do coordinated
    // pushes of the value and type on the opstacks -- these must be atomic wrt GC, and constructing
    // a struct InterpreterType transitions to preemptive mode.
    InterpreterType structValIT;
    if (valCit == CORINFO_TYPE_VALUECLASS)
    {
        GCX_PREEMP();
        valCit = m_interpCeeInfo.getFieldType(CORINFO_FIELD_HANDLE(fld), &valClsHnd, nullptr);
        structValIT = InterpreterType(&m_interpCeeInfo, valClsHnd);
    }

    UINT sz = fld->GetSize();

    // Live vars: valCit, structValIt
    _ASSERTE(m_curStackHt > 0);
    unsigned stackInd = m_curStackHt - 1;
    InterpreterType addrIt = OpStackTypeGet(stackInd);
    CorInfoType addrCit = addrIt.ToCorInfoType();
    bool isUnsigned;

    if (addrCit == CORINFO_TYPE_CLASS)
    {
        OBJECTREF obj = OBJECTREF(OpStackGet<Object*>(stackInd));
        ThrowOnInvalidPointer(OBJECTREFToObject(obj));
        if (valCit == CORINFO_TYPE_VALUECLASS)
        {
            void* srcPtr = fld->GetInstanceAddress(obj);

            // srcPtr is now vulnerable.
            GCX_FORBID();

            MethodTable* valClsMT = GetMethodTableFromClsHnd(valClsHnd);
            if (sz > sizeof(INT64))
            {
                // Large struct case: allocate space on the large struct operand stack.
                void* destPtr = LargeStructOperandStackPush(sz);
                OpStackSet<void*>(stackInd, destPtr);
                CopyValueClass(destPtr, srcPtr, valClsMT);
            }
            else
            {
                // Small struct case -- is inline in operand stack.
                OpStackSet<INT64>(stackInd, GetSmallStructValue(srcPtr, sz));
            }
        }
        else
        {
            BYTE* fldStart = dac_cast<PTR_BYTE>(OBJECTREFToObject(obj)) + sizeof(Object) + fldOffset;
            // fldStart is now a vulnerable byref
            GCX_FORBID();

            switch (sz)
            {
            case 1:
                isUnsigned = CorInfoTypeIsUnsigned(valCit);
                if (isUnsigned)
                {
                    OpStackSet<UINT32>(stackInd, *reinterpret_cast<UINT8*>(fldStart));
                }
                else
                {
                    OpStackSet<INT32>(stackInd, *reinterpret_cast<INT8*>(fldStart));
                }
                break;
            case 2:
                isUnsigned = CorInfoTypeIsUnsigned(valCit);
                if (isUnsigned)
                {
                    OpStackSet<UINT32>(stackInd, *reinterpret_cast<UINT16*>(fldStart));
                }
                else
                {
                    OpStackSet<INT32>(stackInd, *reinterpret_cast<INT16*>(fldStart));
                }
                break;
            case 4:
                OpStackSet<INT32>(stackInd, *reinterpret_cast<INT32*>(fldStart));
                break;
            case 8:
                OpStackSet<INT64>(stackInd, *reinterpret_cast<INT64*>(fldStart));
                break;
            default:
                _ASSERTE_MSG(false, "Should not reach here.");
                break;
            }
        }
    }
    else
    {
        INT8* ptr = NULL;
        if (addrCit == CORINFO_TYPE_VALUECLASS)
        {
            size_t addrSize = addrIt.Size(&m_interpCeeInfo);
            // The ECMA spec allows ldfld to be applied to "an instance of a value type."
            // We will take the address of the ostack entry.
            if (addrIt.IsLargeStruct(&m_interpCeeInfo))
            {
                ptr = reinterpret_cast<INT8*>(OpStackGet<void*>(stackInd));
                // This is delicate.  I'm going to pop the large struct off the large-struct stack
                // now, even though the field value we push may go back on the large object stack.
                // We rely on the fact that this instruction doesn't do any other pushing, and
                // we assume that LargeStructOperandStackPop does not actually deallocate any memory,
                // and we rely on memcpy properly handling possibly-overlapping regions being copied.
                // Finally (wow, this really *is* delicate), we rely on the property that the large-struct
                // stack pop operation doesn't deallocate memory (the size of the allocated memory for the
                // large-struct stack only grows in a method execution), and that if we push the field value
                // on the large struct stack below, the size of the pushed item is at most the size of the
                // popped item, so the stack won't grow (which would allow a dealloc/realloc).
                // (All in all, maybe it would be better to just copy the value elsewhere then pop...but
                // that wouldn't be very aggressive.)
                LargeStructOperandStackPop(addrSize, ptr);
            }
            else
            {
                ptr = reinterpret_cast<INT8*>(OpStackGetAddr(stackInd, addrSize));
            }
        }
        else
        {
            _ASSERTE(CorInfoTypeIsPointer(addrCit));
            ptr = OpStackGet<INT8*>(stackInd);
            ThrowOnInvalidPointer(ptr);
        }

        _ASSERTE(ptr != NULL);
        ptr += fldOffset;

        if (valCit == CORINFO_TYPE_VALUECLASS)
        {
            if (sz > sizeof(INT64))
            {
                // Large struct case.
                void* dstPtr = LargeStructOperandStackPush(sz);
                memcpy(dstPtr, ptr, sz);
                OpStackSet<void*>(stackInd, dstPtr);
            }
            else
            {
                // Small struct case -- is inline in operand stack.
                OpStackSet<INT64>(stackInd, GetSmallStructValue(ptr, sz));
            }
            OpStackTypeSet(stackInd, structValIT.StackNormalize());
            return;
        }
        // Otherwise...
        switch (sz)
        {
        case 1:
            isUnsigned = CorInfoTypeIsUnsigned(valCit);
            if (isUnsigned)
            {
                OpStackSet<UINT32>(stackInd, *reinterpret_cast<UINT8*>(ptr));
            }
            else
            {
                OpStackSet<INT32>(stackInd, *reinterpret_cast<INT8*>(ptr));
            }
            break;
        case 2:
            isUnsigned = CorInfoTypeIsUnsigned(valCit);
            if (isUnsigned)
            {
                OpStackSet<UINT32>(stackInd, *reinterpret_cast<UINT16*>(ptr));
            }
            else
            {
                OpStackSet<INT32>(stackInd, *reinterpret_cast<INT16*>(ptr));
            }
            break;
        case 4:
            OpStackSet<INT32>(stackInd, *reinterpret_cast<INT32*>(ptr));
            break;
        case 8:
            OpStackSet<INT64>(stackInd, *reinterpret_cast<INT64*>(ptr));
            break;
        }
    }
    if (valCit == CORINFO_TYPE_VALUECLASS)
    {
        OpStackTypeSet(stackInd, structValIT.StackNormalize());
    }
    else
    {
        OpStackTypeSet(stackInd, InterpreterType(valCit).StackNormalize());
    }
}

void Interpreter::LdFldA()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    unsigned tok = getU4LittleEndian(m_ILCodePtr + sizeof(BYTE));

#if INTERP_TRACING
    InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_LdFldA]);
#endif // INTERP_TRACING

    unsigned offset = CurOffset();
    m_ILCodePtr += 5;  // Last use above, so update now.

    FieldDesc* fld = NULL;
    if (s_InterpreterUseCaching) fld = GetCachedInstanceField(offset);
    if (fld == NULL)
    {
        GCX_PREEMP();
        fld = FindField(tok  InterpTracingArg(RTK_LdFldA));
        if (s_InterpreterUseCaching) CacheInstanceField(offset, fld);
    }
    _ASSERTE(m_curStackHt > 0);
    unsigned stackInd = m_curStackHt - 1;
    CorInfoType addrCit = OpStackTypeGet(stackInd).ToCorInfoType();
    if (addrCit == CORINFO_TYPE_BYREF || addrCit == CORINFO_TYPE_CLASS || addrCit == CORINFO_TYPE_NATIVEINT)
    {
        NativeInt ptr = OpStackGet<NativeInt>(stackInd);
        ThrowOnInvalidPointer((void*)ptr);
        // The "offset" below does not include the Object (i.e., the MethodTable pointer) for object pointers, so add that in first.
        if (addrCit == CORINFO_TYPE_CLASS) ptr += sizeof(Object);
        // Now add the offset.
        ptr += fld->GetOffset();
        OpStackSet<NativeInt>(stackInd, ptr);
        if (addrCit == CORINFO_TYPE_NATIVEINT)
        {
            OpStackTypeSet(stackInd, InterpreterType(CORINFO_TYPE_NATIVEINT));
        }
        else
        {
            OpStackTypeSet(stackInd, InterpreterType(CORINFO_TYPE_BYREF));
        }
    }
    else
    {
        VerificationError("LdfldA requires object reference, managed or unmanaged pointer type.");
    }
}

void Interpreter::StFld()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

#if INTERP_TRACING
    InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_StFld]);
#endif // INTERP_TRACING

    FieldDesc* fld = NULL;
    DWORD fldOffset;
    {
        unsigned ilOffset = CurOffset();
        if (s_InterpreterUseCaching) fld = GetCachedInstanceField(ilOffset);
        if (fld == NULL)
        {
            unsigned tok = getU4LittleEndian(m_ILCodePtr + sizeof(BYTE));
            GCX_PREEMP();
            fld = FindField(tok  InterpTracingArg(RTK_StFld));
            _ASSERTE(fld != NULL);
            fldOffset = fld->GetOffset();
            if (s_InterpreterUseCaching && fldOffset < FIELD_OFFSET_LAST_REAL_OFFSET)
                CacheInstanceField(ilOffset, fld);
        }
        else
        {
            fldOffset = fld->GetOffset();
        }
    }
    m_ILCodePtr += 5;  // Last use above, so update now.

    UINT sz = fld->GetSize();
    _ASSERTE(m_curStackHt >= 2);
    unsigned addrInd = m_curStackHt - 2;
    CorInfoType addrCit = OpStackTypeGet(addrInd).ToCorInfoType();
    unsigned valInd = m_curStackHt - 1;
    CorInfoType valCit = OpStackTypeGet(valInd).ToCorInfoType();
    _ASSERTE(IsStackNormalType(addrCit) && IsStackNormalType(valCit));

    m_curStackHt -= 2;

    if (addrCit == CORINFO_TYPE_CLASS)
    {
        OBJECTREF obj = OBJECTREF(OpStackGet<Object*>(addrInd));
        ThrowOnInvalidPointer(OBJECTREFToObject(obj));

        if (valCit == CORINFO_TYPE_CLASS)
        {
            fld->SetRefValue(obj, ObjectToOBJECTREF(OpStackGet<Object*>(valInd)));
        }
        else if (valCit == CORINFO_TYPE_VALUECLASS)
        {
            MethodTable* valClsMT = GetMethodTableFromClsHnd(OpStackTypeGet(valInd).ToClassHandle());
            void* destPtr = fld->GetInstanceAddress(obj);

            // destPtr is now a vulnerable byref, so can't do GC.
            GCX_FORBID();

            // I use GCSafeMemCpy below to ensure that write barriers happen for the case in which
            // the value class contains GC pointers.  We could do better...
            if (sz > sizeof(INT64))
            {
                // Large struct case: stack slot contains pointer...
                void* srcPtr = OpStackGet<void*>(valInd);
                CopyValueClassUnchecked(destPtr, srcPtr, valClsMT);
                LargeStructOperandStackPop(sz, srcPtr);
            }
            else
            {
                // Small struct case -- is inline in operand stack.
                CopyValueClassUnchecked(destPtr, OpStackGetAddr(valInd, sz), valClsMT);
            }
            BarrierIfVolatile();
            return;
        }
        else
        {
            BYTE* fldStart = dac_cast<PTR_BYTE>(OBJECTREFToObject(obj)) + sizeof(Object) + fldOffset;
            // fldStart is now a vulnerable byref
            GCX_FORBID();

            switch (sz)
            {
            case 1:
                *reinterpret_cast<INT8*>(fldStart) = OpStackGet<INT8>(valInd);
                break;
            case 2:
                *reinterpret_cast<INT16*>(fldStart) = OpStackGet<INT16>(valInd);
                break;
            case 4:
                *reinterpret_cast<INT32*>(fldStart) = OpStackGet<INT32>(valInd);
                break;
            case 8:
                *reinterpret_cast<INT64*>(fldStart) = OpStackGet<INT64>(valInd);
                break;
            }
        }
    }
    else
    {
        _ASSERTE(addrCit == CORINFO_TYPE_BYREF || addrCit == CORINFO_TYPE_NATIVEINT);

        INT8* destPtr = OpStackGet<INT8*>(addrInd);
        ThrowOnInvalidPointer(destPtr);
        destPtr += fldOffset;

        if (valCit == CORINFO_TYPE_VALUECLASS)
        {
            MethodTable* valClsMT = GetMethodTableFromClsHnd(OpStackTypeGet(valInd).ToClassHandle());
            // I use GCSafeMemCpy below to ensure that write barriers happen for the case in which
            // the value class contains GC pointers.  We could do better...
            if (sz > sizeof(INT64))
            {
                // Large struct case: stack slot contains pointer...
                void* srcPtr = OpStackGet<void*>(valInd);
                CopyValueClassUnchecked(destPtr, srcPtr, valClsMT);
                LargeStructOperandStackPop(sz, srcPtr);
            }
            else
            {
                // Small struct case -- is inline in operand stack.
                CopyValueClassUnchecked(destPtr, OpStackGetAddr(valInd, sz), valClsMT);
            }
            BarrierIfVolatile();
            return;
        }
        else if (valCit == CORINFO_TYPE_CLASS)
        {
            OBJECTREF val = ObjectToOBJECTREF(OpStackGet<Object*>(valInd));
            SetObjectReference(reinterpret_cast<OBJECTREF*>(destPtr), val);
        }
        else
        {
            switch (sz)
            {
            case 1:
                *reinterpret_cast<INT8*>(destPtr) = OpStackGet<INT8>(valInd);
                break;
            case 2:
                *reinterpret_cast<INT16*>(destPtr) = OpStackGet<INT16>(valInd);
                break;
            case 4:
                *reinterpret_cast<INT32*>(destPtr) = OpStackGet<INT32>(valInd);
                break;
            case 8:
                *reinterpret_cast<INT64*>(destPtr) = OpStackGet<INT64>(valInd);
                break;
            }
        }
    }
    BarrierIfVolatile();
}

bool Interpreter::StaticFldAddrWork(CORINFO_ACCESS_FLAGS accessFlgs, /*out (byref)*/void** pStaticFieldAddr, /*out*/InterpreterType* pit, /*out*/UINT* pFldSize, /*out*/bool* pManagedMem)
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    bool isCacheable = true;
    *pManagedMem = true;  // Default result.

    unsigned tok = getU4LittleEndian(m_ILCodePtr + sizeof(BYTE));
    m_ILCodePtr += 5;  // Above is last use of m_ILCodePtr in this method, so update now.

    FieldDesc* fld;
    CORINFO_FIELD_INFO fldInfo;
    CORINFO_RESOLVED_TOKEN fldTok;

    void* pFldAddr = NULL;
    {
        {
            GCX_PREEMP();

            ResolveToken(&fldTok, tok, CORINFO_TOKENKIND_Field InterpTracingArg(RTK_SFldAddr));
            fld = reinterpret_cast<FieldDesc*>(fldTok.hField);

            m_interpCeeInfo.getFieldInfo(&fldTok, m_methInfo->m_method, accessFlgs, &fldInfo);
        }

        EnsureClassInit(GetMethodTableFromClsHnd(fldTok.hClass));

        if ((fldInfo.fieldAccessor == CORINFO_FIELD_STATIC_TLS) || (fldInfo.fieldAccessor == CORINFO_FIELD_STATIC_TLS_MANAGED))
        {
            NYI_INTERP("Thread-local static.");
        }
        else if (fldInfo.fieldAccessor == CORINFO_FIELD_STATIC_SHARED_STATIC_HELPER
                 || fldInfo.fieldAccessor == CORINFO_FIELD_STATIC_GENERICS_STATIC_HELPER)
        {
            *pStaticFieldAddr = fld->GetCurrentStaticAddress();
            isCacheable = false;
        }
        else
        {
            *pStaticFieldAddr = fld->GetCurrentStaticAddress();
        }
    }
    if (fldInfo.structType != NULL && fldInfo.fieldType != CORINFO_TYPE_CLASS && fldInfo.fieldType != CORINFO_TYPE_PTR)
    {
        *pit = InterpreterType(&m_interpCeeInfo, fldInfo.structType);

        if ((fldInfo.fieldFlags & CORINFO_FLG_FIELD_UNMANAGED) == 0)
        {
            // For valuetypes in managed memory, the address returned contains a pointer into the heap, to a boxed version of the
            // static variable; return a pointer to the boxed struct.
            isCacheable = false;
        }
        else
        {
            *pManagedMem = false;
        }
    }
    else
    {
        *pit = InterpreterType(fldInfo.fieldType);
    }
    *pFldSize = fld->GetSize();

    return isCacheable;
}

void Interpreter::LdSFld()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    InterpreterType fldIt;
    UINT sz;
    bool managedMem;
    void* srcPtr = NULL;

    BarrierIfVolatile();

    GCPROTECT_BEGININTERIOR(srcPtr);

    StaticFldAddr(CORINFO_ACCESS_GET, &srcPtr, &fldIt, &sz, &managedMem);

    bool isUnsigned;

    if (fldIt.IsStruct())
    {
        // Large struct case.
        CORINFO_CLASS_HANDLE sh = fldIt.ToClassHandle();
        // This call is GC_TRIGGERS, so do it before we copy the value: no GC after this,
        // until the op stacks and ht are consistent.
        OpStackTypeSet(m_curStackHt, InterpreterType(&m_interpCeeInfo, sh).StackNormalize());
        if (fldIt.IsLargeStruct(&m_interpCeeInfo))
        {
            void* dstPtr = LargeStructOperandStackPush(sz);
            memcpy(dstPtr, srcPtr, sz);
            OpStackSet<void*>(m_curStackHt, dstPtr);
        }
        else
        {
            OpStackSet<INT64>(m_curStackHt, GetSmallStructValue(srcPtr, sz));
        }
    }
    else
    {
        CorInfoType valCit = fldIt.ToCorInfoType();
        switch (sz)
        {
        case 1:
            isUnsigned = CorInfoTypeIsUnsigned(valCit);
            if (isUnsigned)
            {
                OpStackSet<UINT32>(m_curStackHt, *reinterpret_cast<UINT8*>(srcPtr));
            }
            else
            {
                OpStackSet<INT32>(m_curStackHt, *reinterpret_cast<INT8*>(srcPtr));
            }
            break;
        case 2:
            isUnsigned = CorInfoTypeIsUnsigned(valCit);
            if (isUnsigned)
            {
                OpStackSet<UINT32>(m_curStackHt, *reinterpret_cast<UINT16*>(srcPtr));
            }
            else
            {
                OpStackSet<INT32>(m_curStackHt, *reinterpret_cast<INT16*>(srcPtr));
            }
            break;
        case 4:
            OpStackSet<INT32>(m_curStackHt, *reinterpret_cast<INT32*>(srcPtr));
            break;
        case 8:
            OpStackSet<INT64>(m_curStackHt, *reinterpret_cast<INT64*>(srcPtr));
            break;
        default:
            _ASSERTE_MSG(false, "LdSFld: this should have exhausted all the possible sizes.");
            break;
        }
        OpStackTypeSet(m_curStackHt, fldIt.StackNormalize());
    }
    m_curStackHt++;
    GCPROTECT_END();
}

void Interpreter::EnsureClassInit(MethodTable* pMT)
{
    if (!pMT->IsClassInited())
    {
        pMT->CheckRestore();
        // This is tantamount to a call, so exempt it from the cycle count.
#if INTERP_ILCYCLE_PROFILE
        unsigned __int64 startCycles;
        bool b = CycleTimer::GetThreadCyclesS(&startCycles); _ASSERTE(b);
#endif // INTERP_ILCYCLE_PROFILE

        pMT->CheckRunClassInitThrowing();

#if INTERP_ILCYCLE_PROFILE
        unsigned __int64 endCycles;
        b = CycleTimer::GetThreadCyclesS(&endCycles); _ASSERTE(b);
        m_exemptCycles += (endCycles - startCycles);
#endif // INTERP_ILCYCLE_PROFILE
    }
}

void Interpreter::LdSFldA()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    InterpreterType fldIt;
    UINT fldSz;
    bool managedMem;
    void* srcPtr = NULL;
    GCPROTECT_BEGININTERIOR(srcPtr);

    StaticFldAddr(CORINFO_ACCESS_ADDRESS, &srcPtr, &fldIt, &fldSz, &managedMem);

    OpStackSet<void*>(m_curStackHt, srcPtr);
    if (managedMem)
    {
        // Static variable in managed memory...
        OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_BYREF));
    }
    else
    {
        // RVA is in unmanaged memory.
        OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_NATIVEINT));
    }
    m_curStackHt++;

    GCPROTECT_END();
}

void Interpreter::StSFld()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;
    InterpreterType fldIt;
    UINT sz;
    bool managedMem;
    void* dstPtr = NULL;
    GCPROTECT_BEGININTERIOR(dstPtr);

    StaticFldAddr(CORINFO_ACCESS_SET, &dstPtr, &fldIt, &sz, &managedMem);

    m_curStackHt--;
    InterpreterType valIt = OpStackTypeGet(m_curStackHt);
    CorInfoType valCit = valIt.ToCorInfoType();

    if (valCit == CORINFO_TYPE_VALUECLASS)
    {
        MethodTable* valClsMT = GetMethodTableFromClsHnd(valIt.ToClassHandle());
        if (sz > sizeof(INT64))
        {
            // Large struct case: value in operand stack is indirect pointer.
            void* srcPtr = OpStackGet<void*>(m_curStackHt);
            CopyValueClassUnchecked(dstPtr, srcPtr, valClsMT);
            LargeStructOperandStackPop(sz, srcPtr);
        }
        else
        {
            // Struct value is inline in the operand stack.
            CopyValueClassUnchecked(dstPtr, OpStackGetAddr(m_curStackHt, sz), valClsMT);
        }
    }
    else if (valCit == CORINFO_TYPE_CLASS)
    {
        SetObjectReference(reinterpret_cast<OBJECTREF*>(dstPtr), ObjectToOBJECTREF(OpStackGet<Object*>(m_curStackHt)));
    }
    else
    {
        switch (sz)
        {
        case 1:
            *reinterpret_cast<UINT8*>(dstPtr) = OpStackGet<UINT8>(m_curStackHt);
            break;
        case 2:
            *reinterpret_cast<UINT16*>(dstPtr) = OpStackGet<UINT16>(m_curStackHt);
            break;
        case 4:
            *reinterpret_cast<UINT32*>(dstPtr) = OpStackGet<UINT32>(m_curStackHt);
            break;
        case 8:
            *reinterpret_cast<UINT64*>(dstPtr) = OpStackGet<UINT64>(m_curStackHt);
            break;
        default:
            _ASSERTE_MSG(false, "This should have exhausted all the possible sizes.");
            break;
        }
    }
    GCPROTECT_END();

    BarrierIfVolatile();
}

template<typename T, bool IsObjType, CorInfoType cit>
void Interpreter::LdElemWithType()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    _ASSERTE(m_curStackHt >= 2);
    unsigned arrInd = m_curStackHt - 2;
    unsigned indexInd = m_curStackHt - 1;

    _ASSERTE(OpStackTypeGet(arrInd).ToCorInfoType() == CORINFO_TYPE_CLASS);

    ArrayBase* a = OpStackGet<ArrayBase*>(arrInd);
    ThrowOnInvalidPointer(a);
    int len = a->GetNumComponents();

    CorInfoType indexCit = OpStackTypeGet(indexInd).ToCorInfoType();
    if (indexCit == CORINFO_TYPE_INT)
    {
        int index = OpStackGet<INT32>(indexInd);
        if (index < 0 || index >= len) ThrowArrayBoundsException();

        GCX_FORBID();

        if (IsObjType)
        {
            OBJECTREF res = reinterpret_cast<PtrArray*>(a)->GetAt(index);
            OpStackSet<OBJECTREF>(arrInd, res);
        }
        else
        {
            intptr_t res_ptr = reinterpret_cast<intptr_t>(reinterpret_cast<Array<T>*>(a)->GetDirectConstPointerToNonObjectElements());
            if (cit == CORINFO_TYPE_INT)
            {
                _ASSERTE(std::is_integral<T>::value);

                // Widen narrow types.
                int ires;
                switch (sizeof(T))
                {
                case 1:
                    ires = std::is_same<T, INT8>::value ?
                           static_cast<int>(reinterpret_cast<INT8*>(res_ptr)[index]) :
                           static_cast<int>(reinterpret_cast<UINT8*>(res_ptr)[index]);
                    break;
                case 2:
                    ires = std::is_same<T, INT16>::value ?
                           static_cast<int>(reinterpret_cast<INT16*>(res_ptr)[index]) :
                           static_cast<int>(reinterpret_cast<UINT16*>(res_ptr)[index]);
                    break;
                case 4:
                    ires = std::is_same<T, INT32>::value ?
                           static_cast<int>(reinterpret_cast<INT32*>(res_ptr)[index]) :
                           static_cast<int>(reinterpret_cast<UINT32*>(res_ptr)[index]);
                    break;
                default:
                    _ASSERTE_MSG(false, "This should have exhausted all the possible sizes.");
                    break;
                }

                OpStackSet<int>(arrInd, ires);
            }
            else
            {
                OpStackSet<T>(arrInd, ((T*) res_ptr)[index]);
            }
        }
    }
    else
    {
        _ASSERTE(indexCit == CORINFO_TYPE_NATIVEINT);
        NativeInt index = OpStackGet<NativeInt>(indexInd);
        if (index < 0 || index >= NativeInt(len)) ThrowArrayBoundsException();

        GCX_FORBID();

        if (IsObjType)
        {
            OBJECTREF res = reinterpret_cast<PtrArray*>(a)->GetAt(index);
            OpStackSet<OBJECTREF>(arrInd, res);
        }
        else
        {
            T res = reinterpret_cast<Array<T>*>(a)->GetDirectConstPointerToNonObjectElements()[index];
            OpStackSet<T>(arrInd, res);
        }
    }

    OpStackTypeSet(arrInd, InterpreterType(cit));
    m_curStackHt--;
}

template<typename T, bool IsObjType>
void Interpreter::StElemWithType()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;


    _ASSERTE(m_curStackHt >= 3);
    unsigned arrInd = m_curStackHt - 3;
    unsigned indexInd = m_curStackHt - 2;
    unsigned valInd = m_curStackHt - 1;

    _ASSERTE(OpStackTypeGet(arrInd).ToCorInfoType() == CORINFO_TYPE_CLASS);

    ArrayBase* a = OpStackGet<ArrayBase*>(arrInd);
    ThrowOnInvalidPointer(a);
    int len = a->GetNumComponents();

    CorInfoType indexCit = OpStackTypeGet(indexInd).ToCorInfoType();
    if (indexCit == CORINFO_TYPE_INT)
    {
        int index = OpStackGet<INT32>(indexInd);
        if (index < 0 || index >= len) ThrowArrayBoundsException();
        if (IsObjType)
        {
            struct _gc {
                OBJECTREF val;
                OBJECTREF a;
            } gc;
            gc.val = ObjectToOBJECTREF(OpStackGet<Object*>(valInd));
            gc.a = ObjectToOBJECTREF(a);
            GCPROTECT_BEGIN(gc);
            if (gc.val != NULL &&
                !ObjIsInstanceOf(OBJECTREFToObject(gc.val), reinterpret_cast<PtrArray*>(a)->GetArrayElementTypeHandle()))
                COMPlusThrow(kArrayTypeMismatchException);
            reinterpret_cast<PtrArray*>(OBJECTREFToObject(gc.a))->SetAt(index, gc.val);
            GCPROTECT_END();
        }
        else
        {
            GCX_FORBID();
            T val = OpStackGet<T>(valInd);
            reinterpret_cast<Array<T>*>(a)->GetDirectPointerToNonObjectElements()[index] = val;
        }
    }
    else
    {
        _ASSERTE(indexCit == CORINFO_TYPE_NATIVEINT);
        NativeInt index = OpStackGet<NativeInt>(indexInd);
        if (index < 0 || index >= NativeInt(len)) ThrowArrayBoundsException();
        if (IsObjType)
        {
            struct _gc {
                OBJECTREF val;
                OBJECTREF a;
            } gc;
            gc.val = ObjectToOBJECTREF(OpStackGet<Object*>(valInd));
            gc.a = ObjectToOBJECTREF(a);
            GCPROTECT_BEGIN(gc);
            if (gc.val != NULL &&
                !ObjIsInstanceOf(OBJECTREFToObject(gc.val), reinterpret_cast<PtrArray*>(a)->GetArrayElementTypeHandle()))
                COMPlusThrow(kArrayTypeMismatchException);
            reinterpret_cast<PtrArray*>(OBJECTREFToObject(gc.a))->SetAt(index, gc.val);
            GCPROTECT_END();
        }
        else
        {
            GCX_FORBID();
            T val = OpStackGet<T>(valInd);
            reinterpret_cast<Array<T>*>(a)->GetDirectPointerToNonObjectElements()[index] = val;
        }
    }

    m_curStackHt -= 3;
}

template<bool takeAddress>
void Interpreter::LdElem()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    _ASSERTE(m_curStackHt >= 2);
    unsigned arrInd = m_curStackHt - 2;
    unsigned indexInd = m_curStackHt - 1;

    unsigned elemTypeTok = getU4LittleEndian(m_ILCodePtr + 1);

#if INTERP_TRACING
    InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_LdElem]);
#endif // INTERP_TRACING

    unsigned ilOffset = CurOffset();
    CORINFO_CLASS_HANDLE clsHnd = NULL;
    if (s_InterpreterUseCaching) clsHnd = GetCachedClassHandle(ilOffset);

    if (clsHnd == NULL)
    {

        CORINFO_RESOLVED_TOKEN elemTypeResolvedTok;
        {
            GCX_PREEMP();
            ResolveToken(&elemTypeResolvedTok, elemTypeTok, CORINFO_TOKENKIND_Class InterpTracingArg(RTK_LdElem));
            clsHnd = elemTypeResolvedTok.hClass;
        }
        if (s_InterpreterUseCaching) CacheClassHandle(ilOffset, clsHnd);
    }

    CorInfoType elemCit = ::asCorInfoType(clsHnd);

    m_ILCodePtr += 5;


    InterpreterType elemIt;
    if (elemCit == CORINFO_TYPE_VALUECLASS)
    {
        elemIt = InterpreterType(&m_interpCeeInfo, clsHnd);
    }
    else
    {
        elemIt = InterpreterType(elemCit);
    }

    _ASSERTE(OpStackTypeGet(arrInd).ToCorInfoType() == CORINFO_TYPE_CLASS);


    ArrayBase* a = OpStackGet<ArrayBase*>(arrInd);
    ThrowOnInvalidPointer(a);
    int len = a->GetNumComponents();

    NativeInt index;
    {
        GCX_FORBID();

        CorInfoType indexCit = OpStackTypeGet(indexInd).ToCorInfoType();
        if (indexCit == CORINFO_TYPE_INT)
        {
            index = static_cast<NativeInt>(OpStackGet<INT32>(indexInd));
        }
        else
        {
            _ASSERTE(indexCit == CORINFO_TYPE_NATIVEINT);
            index = OpStackGet<NativeInt>(indexInd);
        }
    }
    if (index < 0 || index >= len) ThrowArrayBoundsException();

    bool throwTypeMismatch = NULL;
    {
        void* elemPtr = a->GetDataPtr() + a->GetComponentSize() * index;
        // elemPtr is now a vulnerable byref.
        GCX_FORBID();

        if (takeAddress)
        {
            // If the element type is a class type, may have to do a type check.
            if (elemCit == CORINFO_TYPE_CLASS)
            {
                // Unless there was a readonly prefix, which removes the need to
                // do the (dynamic) type check.
                if (m_readonlyFlag)
                {
                    // Consume the readonly prefix, and don't do the type check below.
                    m_readonlyFlag = false;
                }
                else
                {
                    PtrArray* pa = reinterpret_cast<PtrArray*>(a);
                    // The element array type must be exactly the referent type of the managed
                    // pointer we'll be creating.
                    if (pa->GetArrayElementTypeHandle() != TypeHandle(clsHnd))
                    {
                        throwTypeMismatch = true;
                    }
                }
            }
            if (!throwTypeMismatch)
            {
                // If we're not going to throw the exception, we can take the address.
                OpStackSet<void*>(arrInd, elemPtr);
                OpStackTypeSet(arrInd, InterpreterType(CORINFO_TYPE_BYREF));
                m_curStackHt--;
            }
        }
        else
        {
            m_curStackHt -= 2;
            LdFromMemAddr(elemPtr, elemIt);
            return;
        }
    }

    // If we're going to throw, we do the throw outside the GCX_FORBID region above, since it requires GC_TRIGGERS.
    if (throwTypeMismatch)
    {
        COMPlusThrow(kArrayTypeMismatchException);
    }
}

void Interpreter::StElem()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    _ASSERTE(m_curStackHt >= 3);
    unsigned arrInd = m_curStackHt - 3;
    unsigned indexInd = m_curStackHt - 2;
    unsigned valInd = m_curStackHt - 1;

    CorInfoType valCit = OpStackTypeGet(valInd).ToCorInfoType();

#if INTERP_TRACING
    InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_StElem]);
#endif // INTERP_TRACING

    CORINFO_CLASS_HANDLE typeFromTok = GetTypeFromToken(m_ILCodePtr + 1, CORINFO_TOKENKIND_Class  InterpTracingArg(RTK_StElem));

    m_ILCodePtr += 5;

    CorInfoType typeFromTokCit;
    {
        GCX_PREEMP();
        typeFromTokCit = ::asCorInfoType(typeFromTok);
    }
    size_t sz;

#ifdef _DEBUG
    InterpreterType typeFromTokIt;
#endif // _DEBUG

    if (typeFromTokCit == CORINFO_TYPE_VALUECLASS)
    {
        GCX_PREEMP();
        sz = getClassSize(typeFromTok);
#ifdef _DEBUG
        typeFromTokIt = InterpreterType(&m_interpCeeInfo, typeFromTok);
#endif // _DEBUG
    }
    else
    {
        sz = CorInfoTypeSize(typeFromTokCit);
#ifdef _DEBUG
        typeFromTokIt = InterpreterType(typeFromTokCit);
#endif // _DEBUG
    }

#ifdef _DEBUG
    // Instead of debug, I need to parameterize the interpreter at the top level over whether
    // to do checks corresponding to verification.
    if (typeFromTokIt.StackNormalize().ToCorInfoType() != valCit)
    {
        // This is obviously only a partial test of the required condition.
        VerificationError("Value in stelem does not have the required type.");
    }
#endif // _DEBUG

    _ASSERTE(OpStackTypeGet(arrInd).ToCorInfoType() == CORINFO_TYPE_CLASS);

    ArrayBase* a = OpStackGet<ArrayBase*>(arrInd);
    ThrowOnInvalidPointer(a);
    int len = a->GetNumComponents();

    CorInfoType indexCit = OpStackTypeGet(indexInd).ToCorInfoType();
    NativeInt index = 0;
    if (indexCit == CORINFO_TYPE_INT)
    {
        index = static_cast<NativeInt>(OpStackGet<INT32>(indexInd));
    }
    else
    {
        index = OpStackGet<NativeInt>(indexInd);
    }

    if (index < 0 || index >= len) ThrowArrayBoundsException();

    if (typeFromTokCit == CORINFO_TYPE_CLASS)
    {
        struct _gc {
            OBJECTREF val;
            OBJECTREF a;
        } gc;
        gc.val = ObjectToOBJECTREF(OpStackGet<Object*>(valInd));
        gc.a = ObjectToOBJECTREF(a);
        GCPROTECT_BEGIN(gc);
        if (gc.val != NULL &&
            !ObjIsInstanceOf(OBJECTREFToObject(gc.val), reinterpret_cast<PtrArray*>(a)->GetArrayElementTypeHandle()))
            COMPlusThrow(kArrayTypeMismatchException);
        reinterpret_cast<PtrArray*>(OBJECTREFToObject(gc.a))->SetAt(index, gc.val);
        GCPROTECT_END();
    }
    else
    {
        GCX_FORBID();

        void* destPtr = a->GetDataPtr() + index * sz;;

        if (typeFromTokCit == CORINFO_TYPE_VALUECLASS)
        {
            MethodTable* valClsMT = GetMethodTableFromClsHnd(OpStackTypeGet(valInd).ToClassHandle());
            // I use GCSafeMemCpy below to ensure that write barriers happen for the case in which
            // the value class contains GC pointers.  We could do better...
            if (sz > sizeof(UINT64))
            {
                // Large struct case: stack slot contains pointer...
                void* src = OpStackGet<void*>(valInd);
                CopyValueClassUnchecked(destPtr, src, valClsMT);
                LargeStructOperandStackPop(sz, src);
            }
            else
            {
                // Small struct case -- is inline in operand stack.
                CopyValueClassUnchecked(destPtr, OpStackGetAddr(valInd, sz), valClsMT);
            }
        }
        else
        {
            switch (sz)
            {
            case 1:
                *reinterpret_cast<INT8*>(destPtr) = OpStackGet<INT8>(valInd);
                break;
            case 2:
                *reinterpret_cast<INT16*>(destPtr) = OpStackGet<INT16>(valInd);
                break;
            case 4:
                *reinterpret_cast<INT32*>(destPtr) = OpStackGet<INT32>(valInd);
                break;
            case 8:
                *reinterpret_cast<INT64*>(destPtr) = OpStackGet<INT64>(valInd);
                break;
            }
        }
    }

    m_curStackHt -= 3;
}

void Interpreter::InitBlk()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    _ASSERTE(m_curStackHt >= 3);
    unsigned addrInd = m_curStackHt - 3;
    unsigned valInd  = m_curStackHt - 2;
    unsigned sizeInd = m_curStackHt - 1;

#ifdef _DEBUG
    CorInfoType addrCIT = OpStackTypeGet(addrInd).ToCorInfoType();
    bool addrValidType = (addrCIT == CORINFO_TYPE_NATIVEINT || addrCIT == CORINFO_TYPE_BYREF);
#if defined(HOST_AMD64)
    if (s_InterpreterLooseRules && addrCIT == CORINFO_TYPE_LONG)
        addrValidType = true;
#endif
    if (!addrValidType)
        VerificationError("Addr of InitBlk must be native int or &.");

    CorInfoType valCIT = OpStackTypeGet(valInd).ToCorInfoType();
    if (valCIT != CORINFO_TYPE_INT)
        VerificationError("Value of InitBlk must be int");

#endif // _DEBUG

    CorInfoType sizeCIT = OpStackTypeGet(sizeInd).ToCorInfoType();
    bool isLong = s_InterpreterLooseRules && (sizeCIT == CORINFO_TYPE_LONG);

#ifdef _DEBUG
    if (sizeCIT != CORINFO_TYPE_INT && !isLong)
        VerificationError("Size of InitBlk must be int");
#endif // _DEBUG

    void* addr = OpStackGet<void*>(addrInd);
    ThrowOnInvalidPointer(addr);
    GCX_FORBID(); // addr is a potentially vulnerable byref.
    INT8 val = OpStackGet<INT8>(valInd);
    size_t size = (size_t) ((isLong) ? OpStackGet<UINT64>(sizeInd) : OpStackGet<UINT32>(sizeInd));
    memset(addr, val, size);

    m_curStackHt = addrInd;
    m_ILCodePtr += 2;

    BarrierIfVolatile();
}

void Interpreter::CpBlk()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    _ASSERTE(m_curStackHt >= 3);
    unsigned destInd = m_curStackHt - 3;
    unsigned srcInd  = m_curStackHt - 2;
    unsigned sizeInd = m_curStackHt - 1;

#ifdef _DEBUG
    CorInfoType destCIT = OpStackTypeGet(destInd).ToCorInfoType();
    bool destValidType = (destCIT == CORINFO_TYPE_NATIVEINT || destCIT == CORINFO_TYPE_BYREF);
#if defined(HOST_AMD64)
    if (s_InterpreterLooseRules && destCIT == CORINFO_TYPE_LONG)
        destValidType = true;
#endif
    if (!destValidType)
    {
        VerificationError("Dest addr of CpBlk must be native int or &.");
    }
    CorInfoType srcCIT = OpStackTypeGet(srcInd).ToCorInfoType();
    bool srcValidType = (srcCIT == CORINFO_TYPE_NATIVEINT || srcCIT == CORINFO_TYPE_BYREF);
#if defined(HOST_AMD64)
    if (s_InterpreterLooseRules && srcCIT == CORINFO_TYPE_LONG)
        srcValidType = true;
#endif
    if (!srcValidType)
        VerificationError("Src addr of CpBlk must be native int or &.");
#endif // _DEBUG

    CorInfoType sizeCIT = OpStackTypeGet(sizeInd).ToCorInfoType();
    bool isLong = s_InterpreterLooseRules && (sizeCIT == CORINFO_TYPE_LONG);

#ifdef _DEBUG
    if (sizeCIT != CORINFO_TYPE_INT && !isLong)
        VerificationError("Size of CpBlk must be int");
#endif // _DEBUG


    void* destAddr = OpStackGet<void*>(destInd);
    void* srcAddr = OpStackGet<void*>(srcInd);
    ThrowOnInvalidPointer(destAddr);
    ThrowOnInvalidPointer(srcAddr);
    GCX_FORBID(); // destAddr & srcAddr are potentially vulnerable byrefs.
    size_t size = (size_t)((isLong) ? OpStackGet<UINT64>(sizeInd) : OpStackGet<UINT32>(sizeInd));
    memcpyNoGCRefs(destAddr, srcAddr, size);

    m_curStackHt = destInd;
    m_ILCodePtr += 2;

    BarrierIfVolatile();
}

void Interpreter::Box()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    _ASSERTE(m_curStackHt >= 1);
    unsigned ind = m_curStackHt - 1;

    DWORD boxTypeAttribs = 0;

#if INTERP_TRACING
    InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_Box]);
#endif // INTERP_TRACING

    CORINFO_CLASS_HANDLE boxTypeClsHnd = GetTypeFromToken(m_ILCodePtr + 1, CORINFO_TOKENKIND_Class  InterpTracingArg(RTK_Box));

    {
        GCX_PREEMP();
        boxTypeAttribs = m_interpCeeInfo.getClassAttribs(boxTypeClsHnd);
    }

    m_ILCodePtr += 5;

    if (boxTypeAttribs & CORINFO_FLG_VALUECLASS)
    {
        InterpreterType valIt = OpStackTypeGet(ind);

        void* valPtr;
        if (valIt.IsLargeStruct(&m_interpCeeInfo))
        {
            // Operand stack entry is pointer to the data.
            valPtr = OpStackGet<void*>(ind);
        }
        else
        {
            // Operand stack entry *is* the data.
            size_t classSize = getClassSize(boxTypeClsHnd);
            valPtr = OpStackGetAddr(ind, classSize);
        }

        TypeHandle th(boxTypeClsHnd);
        if (th.IsTypeDesc())
        {
            COMPlusThrow(kInvalidOperationException, W("InvalidOperation_TypeCannotBeBoxed"));
        }

        MethodTable* pMT = th.AsMethodTable();

        {
            Object* res = OBJECTREFToObject(pMT->Box(valPtr));

            GCX_FORBID();

            // If we're popping a large struct off the operand stack, make sure we clean up.
            if (valIt.IsLargeStruct(&m_interpCeeInfo))
            {
                LargeStructOperandStackPop(valIt.Size(&m_interpCeeInfo), valPtr);
            }
            OpStackSet<Object*>(ind, res);
            OpStackTypeSet(ind, InterpreterType(CORINFO_TYPE_CLASS));
        }
    }
}

void Interpreter::BoxStructRefAt(unsigned ind, CORINFO_CLASS_HANDLE valCls)
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    _ASSERTE_MSG(ind < m_curStackHt, "Precondition");
    {
        GCX_PREEMP();
        _ASSERTE_MSG(m_interpCeeInfo.getClassAttribs(valCls) & CORINFO_FLG_VALUECLASS, "Precondition");
    }
    _ASSERTE_MSG(OpStackTypeGet(ind).ToCorInfoType() == CORINFO_TYPE_BYREF, "Precondition");

    InterpreterType valIt = InterpreterType(&m_interpCeeInfo, valCls);

    void* valPtr = OpStackGet<void*>(ind);

    TypeHandle th(valCls);
    if (th.IsTypeDesc())
        COMPlusThrow(kInvalidOperationException,W("InvalidOperation_TypeCannotBeBoxed"));

    MethodTable* pMT = th.AsMethodTable();

    {
        Object* res = OBJECTREFToObject(pMT->Box(valPtr));

        GCX_FORBID();

        OpStackSet<Object*>(ind, res);
        OpStackTypeSet(ind, InterpreterType(CORINFO_TYPE_CLASS));
    }
}


void Interpreter::Unbox()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END

    _ASSERTE(m_curStackHt > 0);
    unsigned tos = m_curStackHt - 1;

#ifdef _DEBUG
    CorInfoType tosCIT = OpStackTypeGet(tos).ToCorInfoType();
    if (tosCIT != CORINFO_TYPE_CLASS)
        VerificationError("Unbox requires that TOS is an object pointer.");
#endif // _DEBUG

#if INTERP_TRACING
    InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_Unbox]);
#endif // INTERP_TRACING

    CORINFO_CLASS_HANDLE boxTypeClsHnd = GetTypeFromToken(m_ILCodePtr + 1, CORINFO_TOKENKIND_Class  InterpTracingArg(RTK_Unbox));

    CorInfoHelpFunc unboxHelper;

    {
        GCX_PREEMP();
        unboxHelper = m_interpCeeInfo.getUnBoxHelper(boxTypeClsHnd);
    }

    void* res = NULL;
    Object* obj = OpStackGet<Object*>(tos);

    switch (unboxHelper)
    {
    case CORINFO_HELP_UNBOX:
        {
            ThrowOnInvalidPointer(obj);

            MethodTable* pMT1 = (MethodTable*)boxTypeClsHnd;
            MethodTable* pMT2 = obj->GetMethodTable();

            if (pMT1->IsEquivalentTo(pMT2))
            {
                res = OpStackGet<Object*>(tos)->UnBox();
            }
            else
            {
                CorElementType type1 = pMT1->GetInternalCorElementType();
                CorElementType type2 = pMT2->GetInternalCorElementType();

                // we allow enums and their primitive type to be interchangeable
                if (type1 == type2)
                {
                    if ((pMT1->IsEnum() || pMT1->IsTruePrimitive()) &&
                        (pMT2->IsEnum() || pMT2->IsTruePrimitive()))
                    {
                        res = OpStackGet<Object*>(tos)->UnBox();
                    }
                }
            }

            if (res == NULL)
            {
                COMPlusThrow(kInvalidCastException);
            }
        }
        break;

    case CORINFO_HELP_UNBOX_NULLABLE:
        {
            // For "unbox Nullable<T>", we need to create a new object (maybe in some temporary local
            // space (that we reuse every time we hit this IL instruction?), that gets reported to the GC,
            // maybe in the GC heap itself). That object will contain an embedded Nullable<T>. Then, we need to
            // get a byref to the data within the object.

            NYI_INTERP("Unhandled 'unbox' of Nullable<T>.");
        }
        break;

    default:
        NYI_INTERP("Unhandled 'unbox' helper.");
    }

    {
        GCX_FORBID();
        OpStackSet<void*>(tos, res);
        OpStackTypeSet(tos, InterpreterType(CORINFO_TYPE_BYREF));
    }

    m_ILCodePtr += 5;
}


void Interpreter::Throw()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END

    _ASSERTE(m_curStackHt >= 1);

    // Note that we can't decrement the stack height here, since the operand stack
    // protects the thrown object.  Nor do we need to, since the ostack will be cleared on
    // any catch within this method.
    unsigned exInd = m_curStackHt - 1;

#ifdef _DEBUG
    CorInfoType exCIT = OpStackTypeGet(exInd).ToCorInfoType();
    if (exCIT != CORINFO_TYPE_CLASS)
    {
        VerificationError("Can only throw an object.");
    }
#endif // _DEBUG

    Object* obj = OpStackGet<Object*>(exInd);
    ThrowOnInvalidPointer(obj);

    OBJECTREF oref = ObjectToOBJECTREF(obj);
    if (!IsException(oref->GetMethodTable()))
    {
        GCPROTECT_BEGIN(oref);
        WrapNonCompliantException(&oref);
        GCPROTECT_END();
    }
    COMPlusThrow(oref);
}

void Interpreter::Rethrow()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END

    OBJECTREF throwable = GetThread()->LastThrownObject();
    COMPlusThrow(throwable);
}

void Interpreter::UnboxAny()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    _ASSERTE(m_curStackHt > 0);
    unsigned tos = m_curStackHt - 1;

    unsigned boxTypeTok = getU4LittleEndian(m_ILCodePtr + 1);
    m_ILCodePtr += 5;

#if INTERP_TRACING
    InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_UnboxAny]);
#endif // INTERP_TRACING

    CORINFO_RESOLVED_TOKEN boxTypeResolvedTok;
    CORINFO_CLASS_HANDLE boxTypeClsHnd;
    DWORD boxTypeAttribs = 0;

    {
        GCX_PREEMP();
        ResolveToken(&boxTypeResolvedTok, boxTypeTok, CORINFO_TOKENKIND_Class InterpTracingArg(RTK_UnboxAny));
        boxTypeClsHnd = boxTypeResolvedTok.hClass;
        boxTypeAttribs = m_interpCeeInfo.getClassAttribs(boxTypeClsHnd);
    }

    CorInfoType unboxCIT = OpStackTypeGet(tos).ToCorInfoType();
    if (unboxCIT != CORINFO_TYPE_CLASS)
        VerificationError("Type mismatch in UNBOXANY.");

    if ((boxTypeAttribs & CORINFO_FLG_VALUECLASS) == 0)
    {
        Object* obj = OpStackGet<Object*>(tos);
        if (obj != NULL && !ObjIsInstanceOf(obj, TypeHandle(boxTypeClsHnd), TRUE))
        {
            UNREACHABLE(); //ObjIsInstanceOf will throw if cast can't be done
        }
    }
    else
    {
        CorInfoHelpFunc unboxHelper;

        {
            GCX_PREEMP();
            unboxHelper = m_interpCeeInfo.getUnBoxHelper(boxTypeClsHnd);
        }

        // Important that this *not* be factored out with the identical statement in the "if" branch:
        // delay read from GC-protected operand stack until after COOP-->PREEMP transition above.
        Object* obj = OpStackGet<Object*>(tos);

        switch (unboxHelper)
        {
        case CORINFO_HELP_UNBOX:
            {
                ThrowOnInvalidPointer(obj);

                MethodTable* pMT1 = (MethodTable*)boxTypeClsHnd;
                MethodTable* pMT2 = obj->GetMethodTable();

                void* res = NULL;
                if (pMT1->IsEquivalentTo(pMT2))
                {
                    res = OpStackGet<Object*>(tos)->UnBox();
                }
                else
                {
                    if (pMT1->GetInternalCorElementType() == pMT2->GetInternalCorElementType() &&
                            (pMT1->IsEnum() || pMT1->IsTruePrimitive()) &&
                            (pMT2->IsEnum() || pMT2->IsTruePrimitive()))
                    {
                        res = OpStackGet<Object*>(tos)->UnBox();
                    }
                }

                if (res == NULL)
                {
                    COMPlusThrow(kInvalidCastException);
                }

                // As the ECMA spec says, the rest is like a "ldobj".
                LdObjValueClassWork(boxTypeClsHnd, tos, res);
            }
            break;

        case CORINFO_HELP_UNBOX_NULLABLE:
            {
                InterpreterType it = InterpreterType(&m_interpCeeInfo, boxTypeClsHnd);
                size_t sz = it.Size(&m_interpCeeInfo);
                if (sz > sizeof(INT64))
                {
                    void* destPtr = LargeStructOperandStackPush(sz);
                    if (!Nullable::UnBox(destPtr, ObjectToOBJECTREF(obj), (MethodTable*)boxTypeClsHnd))
                    {
                        COMPlusThrow(kInvalidCastException);
                    }
                    OpStackSet<void*>(tos, destPtr);
                }
                else
                {
                    INT64 dest = 0;
                    if (!Nullable::UnBox(&dest, ObjectToOBJECTREF(obj), (MethodTable*)boxTypeClsHnd))
                    {
                        COMPlusThrow(kInvalidCastException);
                    }
                    OpStackSet<INT64>(tos, dest);
                }
                OpStackTypeSet(tos, it.StackNormalize());
            }
            break;

        default:
            NYI_INTERP("Unhandled 'unbox.any' helper.");
        }
    }
}

void Interpreter::LdLen()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    _ASSERTE(m_curStackHt >= 1);
    unsigned arrInd = m_curStackHt - 1;

    _ASSERTE(OpStackTypeGet(arrInd).ToCorInfoType() == CORINFO_TYPE_CLASS);

    GCX_FORBID();

    ArrayBase* a = OpStackGet<ArrayBase*>(arrInd);
    ThrowOnInvalidPointer(a);
    int len = a->GetNumComponents();

    OpStackSet<NativeUInt>(arrInd, NativeUInt(len));
    // The ECMA spec says that the type of the length value is NATIVEUINT, but this
    // doesn't make any sense -- unsigned types are not stack-normalized.  So I'm
    // using NATIVEINT, to get the width right.
    OpStackTypeSet(arrInd, InterpreterType(CORINFO_TYPE_NATIVEINT));
}


void Interpreter::DoCall(bool virtualCall)
{
#if INTERP_DYNAMIC_CONTRACTS
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;
#else
    // Dynamic contract occupies too much stack.
    STATIC_CONTRACT_THROWS;
    STATIC_CONTRACT_GC_TRIGGERS;
    STATIC_CONTRACT_MODE_COOPERATIVE;
#endif

#if INTERP_TRACING
    InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_Call]);
#endif // INTERP_TRACING

    DoCallWork(virtualCall);

    m_ILCodePtr += 5;
}

CORINFO_CONTEXT_HANDLE InterpreterMethodInfo::GetPreciseGenericsContext(Object* thisArg, void* genericsCtxtArg)
{
    // If the caller has a generic argument, then we need to get the exact methodContext.
    // There are several possibilities that lead to a generic argument:
    //     1) Static method of generic class: generic argument is the method table of the class.
    //     2) generic method of a class: generic argument is the precise MethodDesc* of the method.
    if (GetFlag<InterpreterMethodInfo::Flag_hasGenericsContextArg>())
    {
        _ASSERTE(GetFlag<InterpreterMethodInfo::Flag_methHasGenericArgs>() || GetFlag<InterpreterMethodInfo::Flag_typeHasGenericArgs>());
        if (GetFlag<InterpreterMethodInfo::Flag_methHasGenericArgs>())
        {
            return MAKE_METHODCONTEXT(reinterpret_cast<CORINFO_METHOD_HANDLE>(genericsCtxtArg));
        }
        else
        {
            MethodTable* methodClass = reinterpret_cast<MethodDesc*>(m_method)->GetMethodTable();
            MethodTable* contextClass = reinterpret_cast<MethodTable*>(genericsCtxtArg)->GetMethodTableMatchingParentClass(methodClass);
            return MAKE_CLASSCONTEXT(contextClass);
        }
    }
    // TODO: This condition isn't quite right.  If the actual class is a subtype of the declaring type of the method,
    // then it might be in another module, the scope and context won't agree.
    else if (GetFlag<InterpreterMethodInfo::Flag_typeHasGenericArgs>()
             && !GetFlag<InterpreterMethodInfo::Flag_methHasGenericArgs>()
             && GetFlag<InterpreterMethodInfo::Flag_hasThisArg>()
             && GetFlag<InterpreterMethodInfo::Flag_thisArgIsObjPtr>() && thisArg != NULL)
    {
        MethodTable* methodClass = reinterpret_cast<MethodDesc*>(m_method)->GetMethodTable();
        MethodTable* contextClass = thisArg->GetMethodTable()->GetMethodTableMatchingParentClass(methodClass);
        return MAKE_CLASSCONTEXT(contextClass);
    }
    else
    {
        return MAKE_METHODCONTEXT(m_method);
    }
}

void Interpreter::DoCallWork(bool virtualCall, void* thisArg, CORINFO_RESOLVED_TOKEN* methTokPtr, CORINFO_CALL_INFO* callInfoPtr)
{
#if INTERP_DYNAMIC_CONTRACTS
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;
#else
    // Dynamic contract occupies too much stack.
    STATIC_CONTRACT_THROWS;
    STATIC_CONTRACT_GC_TRIGGERS;
    STATIC_CONTRACT_MODE_COOPERATIVE;
#endif

#if INTERP_ILCYCLE_PROFILE
#if 0
    // XXX
    unsigned __int64 callStartCycles;
    bool b = CycleTimer::GetThreadCyclesS(&callStartCycles); _ASSERTE(b);
    unsigned __int64 callStartExemptCycles = m_exemptCycles;
#endif
#endif // INTERP_ILCYCLE_PROFILE

#if INTERP_TRACING
    InterlockedIncrement(&s_totalInterpCalls);
#endif // INTERP_TRACING
    unsigned tok = getU4LittleEndian(m_ILCodePtr + sizeof(BYTE));

    // It's possible for an IL method to push a capital-F Frame.  If so, we pop it and save it;
    // we'll push it back on after our GCPROTECT frame is popped.
    Frame* ilPushedFrame = NULL;

    // We can't protect "thisArg" with a GCPROTECT, because this pushes a Frame, and there
    // exist managed methods that push (and pop) Frames -- so that the Frame chain does not return
    // to its original state after a call.  Therefore, we can't have a Frame on the stack over the duration
    // of a call.  (I assume that any method that calls a Frame-pushing IL method performs a matching
    // call to pop that Frame before the caller method completes.  If this were not true, if one method could push
    // a Frame, but defer the pop to its caller, then we could *never* use a Frame in the interpreter, and
    // our implementation plan would be doomed.)
    _ASSERTE(m_callThisArg == NULL);
    m_callThisArg = thisArg;

    // Have we already cached a MethodDescCallSite for this call? (We do this only in loops
    // in the current execution).
    unsigned iloffset = CurOffset();
    CallSiteCacheData* pCscd = NULL;
    if (s_InterpreterUseCaching) pCscd = GetCachedCallInfo(iloffset);

    // If this is true, then we should not cache this call site.
    bool doNotCache;

    CORINFO_RESOLVED_TOKEN methTok;
    CORINFO_CALL_INFO callInfo;
    MethodDesc* methToCall = NULL;
    CORINFO_CLASS_HANDLE exactClass = NULL;
    CORINFO_SIG_INFO_SMALL sigInfo;
    if (pCscd != NULL)
    {
        GCX_PREEMP();
        methToCall = pCscd->m_pMD;
        sigInfo = pCscd->m_sigInfo;

        doNotCache = true; // We already have a cache entry.
    }
    else
    {
        doNotCache = false;  // Until we determine otherwise.
        if (callInfoPtr == NULL)
        {
            GCX_PREEMP();

            // callInfoPtr and methTokPtr must either both be NULL, or neither.
            _ASSERTE(methTokPtr == NULL);

            methTokPtr = &methTok;
            ResolveToken(methTokPtr, tok, CORINFO_TOKENKIND_Method InterpTracingArg(RTK_Call));
            OPCODE opcode = (OPCODE)(*m_ILCodePtr);

            m_interpCeeInfo.getCallInfo(methTokPtr,
                                        m_constrainedFlag ? & m_constrainedResolvedToken : NULL,
                                        m_methInfo->m_method,
                                        //this is how impImportCall invokes getCallInfo
                                        combine(combine(CORINFO_CALLINFO_ALLOWINSTPARAM,
                                                        CORINFO_CALLINFO_SECURITYCHECKS),
                                                (opcode == CEE_CALLVIRT) ? CORINFO_CALLINFO_CALLVIRT
                                                                           : CORINFO_CALLINFO_NONE),
                                        &callInfo);
#if INTERP_ILCYCLE_PROFILE
#if 0
            if (virtualCall)
            {
                unsigned __int64 callEndCycles;
                b = CycleTimer::GetThreadCyclesS(&callEndCycles); _ASSERTE(b);
                unsigned __int64 delta = (callEndCycles - callStartCycles);
                delta -= (m_exemptCycles - callStartExemptCycles);
                s_callCycles += delta;
                s_calls++;
            }
#endif
#endif // INTERP_ILCYCLE_PROFILE

            callInfoPtr = &callInfo;

            _ASSERTE(!callInfoPtr->exactContextNeedsRuntimeLookup);

            methToCall = reinterpret_cast<MethodDesc*>(methTok.hMethod);
            exactClass = methTok.hClass;
        }
        else
        {
            // callInfoPtr and methTokPtr must either both be NULL, or neither.
            _ASSERTE(methTokPtr != NULL);

            _ASSERTE(!callInfoPtr->exactContextNeedsRuntimeLookup);

            methToCall = reinterpret_cast<MethodDesc*>(callInfoPtr->hMethod);
            exactClass = methTokPtr->hClass;
        }

        // We used to take the sigInfo from the callInfo here, but that isn't precise, since
        // we may have made "methToCall" more precise wrt generics than the method handle in
        // the callinfo.  So look up th emore precise signature.
        GCX_PREEMP();

        CORINFO_SIG_INFO sigInfoFull;
        m_interpCeeInfo.getMethodSig(CORINFO_METHOD_HANDLE(methToCall), &sigInfoFull, nullptr);
        sigInfo.retTypeClass = sigInfoFull.retTypeClass;
        sigInfo.numArgs = sigInfoFull.numArgs;
        sigInfo.callConv = sigInfoFull.callConv;
        sigInfo.retType = sigInfoFull.retType;
    }

    // Point A in our cycle count.


    // TODO: enable when NamedIntrinsic is available to interpreter

    /*
    // Is the method an intrinsic?  If so, and if it's one we've written special-case code for
    // handle intrinsically.
    NamedIntrinsic intrinsicName;
    {
        GCX_PREEMP();
        intrinsicName = getIntrinsicName(CORINFO_METHOD_HANDLE(methToCall), nullptr);
    }

#if INTERP_TRACING
    if (intrinsicName == NI_Illegal)
        InterlockedIncrement(&s_totalInterpCallsToIntrinsics);
#endif // INTERP_TRACING
    bool didIntrinsic = false;
    if (!m_constrainedFlag)
    {
        switch (intrinsicId)
        {
#if INTERP_ILSTUBS
        case NI_System_StubHelpers_GetStubContext:
            OpStackSet<void*>(m_curStackHt, GetStubContext());
            OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_NATIVEINT));
            m_curStackHt++; didIntrinsic = true;
            break;
#endif // INTERP_ILSTUBS
        default:
#if INTERP_TRACING
            InterlockedIncrement(&s_totalInterpCallsToIntrinsicsUnhandled);
#endif // INTERP_TRACING
            break;
        }

        // Plus some other calls that we're going to treat "like" intrinsics...
        if (methToCall == CoreLibBinder::GetMethod(METHOD__STUBHELPERS__SET_LAST_ERROR))
        {
            // If we're interpreting a method that calls "SetLastError", it's very likely that the call(i) whose
            // error we're trying to capture was performed with MethodDescCallSite machinery that itself trashes
            // the last error.  We solve this by saving the last error in a special interpreter-specific field of
            // "Thread" in that case, and essentially implement SetLastError here, taking that field as the
            // source for the last error.
            Thread* thrd = GetThread();
            thrd->m_dwLastError = thrd->m_dwLastErrorInterp;
            didIntrinsic = true;
        }

        // TODO: The following check for hardware intrinsics is not a production-level
        //       solution and may produce incorrect results.
        static ConfigDWORD s_InterpreterHWIntrinsicsIsSupportedFalse;
        if (s_InterpreterHWIntrinsicsIsSupportedFalse.val(CLRConfig::INTERNAL_InterpreterHWIntrinsicsIsSupportedFalse) != 0)
        {
            GCX_PREEMP();

            // Hardware intrinsics are recognized by name.
            const char* namespaceName = NULL;
            const char* className = NULL;
            const char* methodName = m_interpCeeInfo.getMethodNameFromMetadata((CORINFO_METHOD_HANDLE)methToCall, &className, &namespaceName, NULL);
            if (
#if defined(TARGET_X86) || defined(TARGET_AMD64)
                strcmp(namespaceName, "System.Runtime.Intrinsics.X86") == 0 &&
#elif defined(TARGET_ARM64)
                strcmp(namespaceName, "System.Runtime.Intrinsics.Arm") == 0 &&
#endif // defined(TARGET_X86) || defined(TARGET_AMD64)
                strcmp(methodName, "get_IsSupported") == 0
            )
            {
                GCX_COOP();
                DoGetIsSupported();
                didIntrinsic = true;
            }
        }

#if FEATURE_SIMD
        // Check for the simd class...
        _ASSERTE(exactClass != NULL);
        GCX_PREEMP();
        bool isIntrinsicType = m_interpCeeInfo.isIntrinsicType(exactClass);

        if (isIntrinsicType)
        {
            // SIMD intrinsics are recognized by name.
            const char* namespaceName = NULL;
            const char* className = NULL;
            const char* methodName = m_interpCeeInfo.getMethodNameFromMetadata((CORINFO_METHOD_HANDLE)methToCall, &className, &namespaceName, NULL);
            if ((strcmp(methodName, "get_IsHardwareAccelerated") == 0) && (strcmp(className, "Vector") == 0) && (strcmp(namespaceName, "System.Numerics") == 0))
            {
                GCX_COOP();
                DoSIMDHwAccelerated();
                didIntrinsic = true;
            }
        }

        if (didIntrinsic)
        {
            // Must block caching or we lose easy access to the class
            doNotCache = true;
        }
#endif // FEATURE_SIMD

    }

    if (didIntrinsic)
    {
        if (s_InterpreterUseCaching && !doNotCache)
        {
            // Cache the token resolution result...
            pCscd = new CallSiteCacheData(methToCall, sigInfo);
            CacheCallInfo(iloffset, pCscd);
        }
        // Now we can return.
        return;
    }
    */

    // Handle other simple special cases:

#if FEATURE_INTERPRETER_DEADSIMPLE_OPT
#ifndef DACCESS_COMPILE
    // Dead simple static getters.
    InterpreterMethodInfo* calleeInterpMethInfo;
    if (GetMethodHandleToInterpMethInfoPtrMap()->Lookup(CORINFO_METHOD_HANDLE(methToCall), &calleeInterpMethInfo))
    {
        if (calleeInterpMethInfo->GetFlag<InterpreterMethodInfo::Flag_methIsDeadSimpleGetter>())
        {
            if (methToCall->IsStatic())
            {
                // TODO
            }
            else
            {
                ILOffsetToItemCache* calleeCache;
                {
                    Object* thisArg = OpStackGet<Object*>(m_curStackHt-1);
                    GCX_FORBID();
                    // We pass NULL for the generic context arg, because a dead simple getter takes none, by definition.
                    calleeCache = calleeInterpMethInfo->GetCacheForCall(thisArg, /*genericsContextArg*/NULL);
                }
                // We've interpreted the getter at least once, so the cache for *some* generics context is populated -- but maybe not
                // this one.  We're hoping that it usually is.
                if (calleeCache != NULL)
                {
                    CachedItem cachedItem;
                    unsigned offsetOfLd;
                    if (calleeInterpMethInfo->GetFlag<InterpreterMethodInfo::Flag_methIsDeadSimpleGetterIsDbgForm>())
                        offsetOfLd = ILOffsetOfLdFldInDeadSimpleInstanceGetterOpt;
                    else
                        offsetOfLd = ILOffsetOfLdFldInDeadSimpleInstanceGetterOpt;

                    bool b = calleeCache->GetItem(offsetOfLd, cachedItem);
                    _ASSERTE_MSG(b, "If the cache exists for this generic context, it should an entry for the LdFld.");
                    _ASSERTE_MSG(cachedItem.m_tag == CIK_InstanceField, "If it's there, it should be an instance field cache.");
                    LdFld(cachedItem.m_value.m_instanceField);
#if INTERP_TRACING
                    InterlockedIncrement(&s_totalInterpCallsToDeadSimpleGetters);
                    InterlockedIncrement(&s_totalInterpCallsToDeadSimpleGettersShortCircuited);
#endif // INTERP_TRACING
                    return;
                }
            }
        }
    }
#endif // DACCESS_COMPILE
#endif // FEATURE_INTERPRETER_DEADSIMPLE_OPT

    unsigned totalSigArgs;
    CORINFO_VARARGS_HANDLE vaSigCookie = nullptr;
    if ((sigInfo.callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_VARARG ||
        (sigInfo.callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_NATIVEVARARG)
    {
        GCX_PREEMP();
        CORINFO_SIG_INFO sig;
        m_interpCeeInfo.findCallSiteSig(m_methInfo->m_module, methTokPtr->token, MAKE_METHODCONTEXT(m_methInfo->m_method), &sig);
        sigInfo.retTypeClass = sig.retTypeClass;
        sigInfo.numArgs = sig.numArgs;
        sigInfo.callConv = sig.callConv;
        sigInfo.retType = sig.retType;
        // Adding 'this' pointer because, numArgs doesn't include the this pointer.
        totalSigArgs = sigInfo.numArgs + sigInfo.hasThis();

        if ((sigInfo.callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_VARARG)
        {
            Module* module = GetModule(sig.scope);
            vaSigCookie = CORINFO_VARARGS_HANDLE(module->GetVASigCookie(Signature(sig.pSig, sig.cbSig)));
        }
        doNotCache = true;
    }
    else
    {
        totalSigArgs = sigInfo.totalILArgs();
    }

    // Note that "totalNativeArgs()" includes space for ret buff arg.
    unsigned nSlots = totalSigArgs + 1;
    if (sigInfo.hasTypeArg()) nSlots++;
    if (sigInfo.isVarArg()) nSlots++;

    DelegateCtorArgs ctorData;
    // If any of these are non-null, they will be pushed as extra arguments (see the code below).
    ctorData.pArg3 = NULL;
    ctorData.pArg4 = NULL;
    ctorData.pArg5 = NULL;

    // Since we make "doNotCache" true below, well never have a non-null "pCscd" for a delegate
    // constructor.  But we have to check for a cached method first, since callInfoPtr may be null in the cached case.
    if (pCscd == NULL && callInfoPtr->classFlags & CORINFO_FLG_DELEGATE && callInfoPtr->methodFlags & CORINFO_FLG_CONSTRUCTOR)
    {
        // We won't cache this case.
        doNotCache = true;

        _ASSERTE_MSG(!sigInfo.hasTypeArg(), "I assume that this isn't possible.");
        GCX_PREEMP();

        ctorData.pMethod = methToCall;

        // Second argument to delegate constructor will be code address of the function the delegate wraps.
        _ASSERTE(TOSIsPtr() && OpStackTypeGet(m_curStackHt-1).ToCorInfoType() != CORINFO_TYPE_BYREF);
        CORINFO_METHOD_HANDLE targetMethodHnd = GetFunctionPointerStack()[m_curStackHt-1];
        _ASSERTE(targetMethodHnd != NULL);
        CORINFO_METHOD_HANDLE alternateCtorHnd = m_interpCeeInfo.GetDelegateCtor(reinterpret_cast<CORINFO_METHOD_HANDLE>(methToCall), methTokPtr->hClass, targetMethodHnd, &ctorData);
        MethodDesc* alternateCtor = reinterpret_cast<MethodDesc*>(alternateCtorHnd);
        if (alternateCtor != methToCall)
        {
            methToCall = alternateCtor;

            // Translate the method address argument from a method handle to the actual callable code address.
            void* val =  (void *)((MethodDesc *)targetMethodHnd)->GetMultiCallableAddrOfCode();
            // Change the method argument to the code pointer.
            OpStackSet<void*>(m_curStackHt-1, val);

            // Now if there are extra arguments, add them to the number of slots; we'll push them on the
            // arg list later.
            if (ctorData.pArg3) nSlots++;
            if (ctorData.pArg4) nSlots++;
            if (ctorData.pArg5) nSlots++;
        }
    }

    // Make sure that the operand stack has the required number of arguments.
    // (Note that this is IL args, not native.)
    //

    // The total number of arguments on the IL stack.  Initially we assume that all the IL arguments
    // the callee expects are on the stack, but may be adjusted downwards if the "this" argument
    // is provided by an allocation (the call is to a constructor).
    unsigned totalArgsOnILStack = totalSigArgs;
    if (m_callThisArg != NULL)
    {
        _ASSERTE(totalArgsOnILStack > 0);
        totalArgsOnILStack--;
    }

#if defined(FEATURE_HFA)
    // Does the callee have an HFA return type?
    unsigned HFAReturnArgSlots = 0;
    {
        GCX_PREEMP();

        if (sigInfo.retType == CORINFO_TYPE_VALUECLASS
            && (m_interpCeeInfo.getHFAType(sigInfo.retTypeClass) != CORINFO_HFA_ELEM_NONE)
            && (sigInfo.getCallConv() & CORINFO_CALLCONV_VARARG) == 0)
        {
            HFAReturnArgSlots = getClassSize(sigInfo.retTypeClass);
            // Round up to a multiple of double size.
            HFAReturnArgSlots = (HFAReturnArgSlots + sizeof(ARG_SLOT) - 1) / sizeof(ARG_SLOT);
        }
    }
#elif defined(UNIX_AMD64_ABI)
    unsigned HasTwoSlotBuf = sigInfo.retType == CORINFO_TYPE_VALUECLASS &&
        getClassSize(sigInfo.retTypeClass) == 16;
#endif

    // Point B

    const unsigned LOCAL_ARG_SLOTS = 8;
    ARG_SLOT localArgs[LOCAL_ARG_SLOTS];
    InterpreterType localArgTypes[LOCAL_ARG_SLOTS];

    ARG_SLOT* args;
    InterpreterType* argTypes;
#if defined(HOST_X86)
    unsigned totalArgSlots = nSlots;
#elif defined(HOST_ARM) || defined(HOST_ARM64)
    // ARM64TODO: Verify that the following statement is correct for ARM64.
    unsigned totalArgSlots = nSlots + HFAReturnArgSlots;
#elif defined(HOST_AMD64)
    unsigned totalArgSlots = nSlots;
#elif defined(HOST_LOONGARCH64)
    unsigned totalArgSlots = nSlots;
#elif defined(HOST_RISCV64)
    unsigned totalArgSlots = nSlots;
#else
#error "unsupported platform"
#endif

    if (totalArgSlots <= LOCAL_ARG_SLOTS)
    {
        args = &localArgs[0];
        argTypes = &localArgTypes[0];
    }
    else
    {
        args = (ARG_SLOT*)_alloca(totalArgSlots * sizeof(ARG_SLOT));
#if defined(HOST_ARM)
        // The HFA return buffer, if any, is assumed to be at a negative
        // offset from the IL arg pointer, so adjust that pointer upward.
        args = args + HFAReturnArgSlots;
#endif // defined(HOST_ARM)
        argTypes = (InterpreterType*)_alloca(nSlots * sizeof(InterpreterType));
    }
    // Make sure that we don't scan any of these until we overwrite them with
    // the real types of the arguments.
    InterpreterType undefIt(CORINFO_TYPE_UNDEF);
    for (unsigned i = 0; i < nSlots; i++) argTypes[i] = undefIt;

    // GC-protect the argument array (as byrefs).
    m_args = args; m_argsSize = nSlots; m_argTypes = argTypes;

    // This is the index into the "args" array (where we copy the value to).
    int curArgSlot = 0;

    // The operand stack index of the first IL argument.
    _ASSERTE(m_curStackHt >= totalArgsOnILStack);
    int argsBase = m_curStackHt - totalArgsOnILStack;

    // Current on-stack argument index.
    unsigned arg = 0;

    // We do "this" -- in the case of a constructor, we "shuffle" the "m_callThisArg" argument in as the first
    // argument -- it isn't on the IL operand stack.

    if (m_constrainedFlag)
    {
        _ASSERTE(m_callThisArg == NULL);  // "m_callThisArg" non-null only for .ctor, which are not callvirts.

        CorInfoType argCIT = OpStackTypeGet(argsBase + arg).ToCorInfoType();
        if (argCIT != CORINFO_TYPE_BYREF)
            VerificationError("This arg of constrained call must be managed pointer.");

        // We only cache for the CORINFO_NO_THIS_TRANSFORM case, so we may assume that if we have a cached call site,
        // there's no thisTransform to perform.
        if (pCscd == NULL)
        {
            switch (callInfoPtr->thisTransform)
            {
            case CORINFO_NO_THIS_TRANSFORM:
                // It is a constrained call on a method implemented by a value type; this is already the proper managed pointer.
                break;

            case CORINFO_DEREF_THIS:
#ifdef _DEBUG
                {
                    GCX_PREEMP();
                    DWORD clsAttribs = m_interpCeeInfo.getClassAttribs(m_constrainedResolvedToken.hClass);
                    _ASSERTE((clsAttribs & CORINFO_FLG_VALUECLASS) == 0);
                }
#endif // _DEBUG
                {
                    // As per the spec, dereference the byref to the "this" pointer, and substitute it as the new "this" pointer.
                    GCX_FORBID();
                    Object** objPtrPtr = OpStackGet<Object**>(argsBase + arg);
                    OpStackSet<Object*>(argsBase + arg, *objPtrPtr);
                    OpStackTypeSet(argsBase + arg, InterpreterType(CORINFO_TYPE_CLASS));
                }
                doNotCache = true;
                break;

            case CORINFO_BOX_THIS:
                // This is the case where the call is to a virtual method of Object the given
                // struct class does not override -- the struct must be boxed, so that the
                // method can be invoked as a virtual.
                BoxStructRefAt(argsBase + arg, m_constrainedResolvedToken.hClass);
                doNotCache = true;
                break;
            }

            exactClass = m_constrainedResolvedToken.hClass;
            {
                GCX_PREEMP();
                DWORD exactClassAttribs = m_interpCeeInfo.getClassAttribs(exactClass);
                // If the constraint type is a value class, then it is the exact class (which will be the
                // "owner type" in the MDCS below.)  If it is not, leave it as the (precise) interface method.
                if (exactClassAttribs & CORINFO_FLG_VALUECLASS)
                {
                    MethodTable* exactClassMT = GetMethodTableFromClsHnd(exactClass);
                    // Find the method on exactClass corresponding to methToCall.
                    methToCall = MethodDesc::FindOrCreateAssociatedMethodDesc(
                        reinterpret_cast<MethodDesc*>(callInfoPtr->hMethod),  // pPrimaryMD
                        exactClassMT,                                         // pExactMT
                        FALSE,                                                // forceBoxedEntryPoint
                        methToCall->GetMethodInstantiation(),                 // methodInst
                        FALSE);                                               // allowInstParam
                }
                else
                {
                    exactClass = methTokPtr->hClass;
                }
            }
        }

        // We've consumed the constraint, so reset the flag.
        m_constrainedFlag = false;
    }

    if (pCscd == NULL)
    {
        if (callInfoPtr->methodFlags & CORINFO_FLG_STATIC)
        {
            MethodDesc* pMD = reinterpret_cast<MethodDesc*>(callInfoPtr->hMethod);
            EnsureClassInit(pMD->GetMethodTable());
        }
    }

    // Point C

    // We must do anything that might make a COOP->PREEMP transition before copying arguments out of the
    // operand stack (where they are GC-protected) into the args array (where they are not).
#ifdef _DEBUG
    const char* clsOfMethToCallName;;
    const char* methToCallName = NULL;
    {
        GCX_PREEMP();
        methToCallName = m_interpCeeInfo.getMethodName(CORINFO_METHOD_HANDLE(methToCall), &clsOfMethToCallName);
    }
#if INTERP_TRACING
    if (strncmp(methToCallName, "get_", 4) == 0)
    {
        InterlockedIncrement(&s_totalInterpCallsToGetters);
        size_t offsetOfLd;
        if (IsDeadSimpleGetter(&m_interpCeeInfo, methToCall, &offsetOfLd))
        {
            InterlockedIncrement(&s_totalInterpCallsToDeadSimpleGetters);
        }
    }
    else if (strncmp(methToCallName, "set_", 4) == 0)
    {
        InterlockedIncrement(&s_totalInterpCallsToSetters);
    }
#endif // INTERP_TRACING

    // Only do this check on the first call, since it should be the same each time.
    if (pCscd == NULL)
    {
        // Ensure that any value types used as argument types are loaded.  This property is checked
        // by the MethodDescCall site mechanisms.  Since enums are freely convertible with their underlying
        // integer type, this is at least one case where a caller may push a value convertible to a value type
        // without any code having caused the value type to be loaded.  This is DEBUG-only because if the callee
        // the integer-type value as the enum value type, it will have loaded the value type.
        MetaSig ms(methToCall);
        CorElementType argType;
        while ((argType = ms.NextArg()) != ELEMENT_TYPE_END)
        {
            if (argType == ELEMENT_TYPE_VALUETYPE)
            {
                TypeHandle th = ms.GetLastTypeHandleThrowing(ClassLoader::LoadTypes);
                CONSISTENCY_CHECK(th.CheckFullyLoaded());
                CONSISTENCY_CHECK(th.IsRestored());
            }
        }
    }
#endif

    // CYCLE PROFILE: BEFORE ARG PROCESSING.

    if (sigInfo.hasThis())
    {
        if (m_callThisArg != NULL)
        {
            if (size_t(m_callThisArg) == 0x1)
            {
                args[curArgSlot] = NULL;
            }
            else
            {
                args[curArgSlot] = PtrToArgSlot(m_callThisArg);
            }
            argTypes[curArgSlot] = InterpreterType(CORINFO_TYPE_BYREF);
        }
        else
        {
            args[curArgSlot] = PtrToArgSlot(OpStackGet<void*>(argsBase + arg));
            argTypes[curArgSlot] = OpStackTypeGet(argsBase + arg);
            arg++;
        }
        // AV -> NullRef translation is NYI for the interpreter,
        // so we should manually check and throw the correct exception.
        if (args[curArgSlot] == NULL)
        {
            // If we're calling a constructor, we bypass this check since the runtime
            // should have thrown OOM if it was unable to allocate an instance.
            if (m_callThisArg == NULL)
            {
                _ASSERTE(!methToCall->IsStatic());
                ThrowNullPointerException();
            }
            // ...except in the case of strings, which are both
            // allocated and initialized by their special constructor.
            else
            {
                _ASSERTE(methToCall->IsCtor() && methToCall->GetMethodTable()->IsString());
            }
        }
        curArgSlot++;
    }

    // This is the argument slot that will be used to hold the return value.
    // In UNIX_AMD64_ABI, return type may have need tow ARG_SLOTs.
    ARG_SLOT retVals[2] = {0, 0};
#if !defined(HOST_ARM) && !defined(UNIX_AMD64_ABI)
    _ASSERTE (NUMBER_RETURNVALUE_SLOTS == 1);
#endif

    // If the return type is a structure, then these will be initialized.
    CORINFO_CLASS_HANDLE retTypeClsHnd = NULL;
    InterpreterType retTypeIt;
    size_t retTypeSz = 0;

    // If non-null, space allocated to hold a large struct return value.  Should be deleted later.
    // (I could probably optimize this pop all the arguments first, then allocate space for the return value
    // on the large structure operand stack, and pass a pointer directly to that space, avoiding the extra
    // copy we have below.  But this seemed more expedient, and this should be a pretty rare case.)
    BYTE* pLargeStructRetVal = NULL;

    // If there's a "GetFlag<Flag_hasRetBuffArg>()" struct return value, it will be stored in this variable if it fits,
    // otherwise, we'll dynamically allocate memory for it.
    ARG_SLOT smallStructRetVal = 0;

    // We should have no return buffer temp space registered here...unless this is a constructor, in which
    // case it will return void.  In particular, if the return type VALUE_CLASS, then this should be NULL.
    _ASSERTE_MSG((pCscd != NULL) || sigInfo.retType == CORINFO_TYPE_VOID || m_structRetValITPtr == NULL, "Invariant.");

    // Is it the return value a struct with a ret buff?
    _ASSERTE_MSG(methToCall != NULL, "assumption");
    bool hasRetBuffArg = false;
    if (sigInfo.retType == CORINFO_TYPE_VALUECLASS || sigInfo.retType == CORINFO_TYPE_REFANY)
    {
        hasRetBuffArg = !!methToCall->HasRetBuffArg();
        retTypeClsHnd = sigInfo.retTypeClass;

        MetaSig ms(methToCall);


        // On ARM, if there's an HFA return type, we must also allocate a return buffer, since the
        // MDCS calling convention requires it.
        if (hasRetBuffArg
#if defined(HOST_ARM)
            || HFAReturnArgSlots > 0
#endif // defined(HOST_ARM)
            )
        {
            _ASSERTE(retTypeClsHnd != NULL);
            retTypeIt = InterpreterType(&m_interpCeeInfo, retTypeClsHnd);
            retTypeSz = retTypeIt.Size(&m_interpCeeInfo);

#if defined(HOST_ARM)
            if (HFAReturnArgSlots > 0)
            {
                args[curArgSlot] = PtrToArgSlot(args - HFAReturnArgSlots);
            }
            else
#endif // defined(HOST_ARM)

            if (retTypeIt.IsLargeStruct(&m_interpCeeInfo))
            {
                size_t retBuffSize = retTypeSz;
                // If the target architecture can sometimes return a struct in several registers,
                // MethodDescCallSite will reserve a return value array big enough to hold the maximum.
                // It will then copy *all* of this into the return buffer area we allocate.  So make sure
                // we allocate at least that much.
#ifdef ENREGISTERED_RETURNTYPE_MAXSIZE
                retBuffSize = max(retTypeSz, ENREGISTERED_RETURNTYPE_MAXSIZE);
#endif // ENREGISTERED_RETURNTYPE_MAXSIZE
                pLargeStructRetVal = (BYTE*)_alloca(retBuffSize);
                // Clear this in case a GC happens.
                for (unsigned i = 0; i < retTypeSz; i++) pLargeStructRetVal[i] = 0;
                // Register this as location needing GC.
                m_structRetValTempSpace = pLargeStructRetVal;
                // Set it as the return buffer.
                args[curArgSlot] = PtrToArgSlot(pLargeStructRetVal);
            }
            else
            {
                // Clear this in case a GC happens.
                smallStructRetVal = 0;
                // Register this as location needing GC.
                m_structRetValTempSpace = &smallStructRetVal;
                // Set it as the return buffer.
                args[curArgSlot] = PtrToArgSlot(&smallStructRetVal);
            }
            m_structRetValITPtr = &retTypeIt;
            argTypes[curArgSlot] = InterpreterType(CORINFO_TYPE_NATIVEINT);
            curArgSlot++;
        }
        else
        {
            // The struct type might "normalize" to a primitive type.
            if (retTypeClsHnd == NULL)
            {
                retTypeIt = InterpreterType(CEEInfo::asCorInfoType(ms.GetReturnTypeNormalized()));
            }
            else
            {
                retTypeIt = InterpreterType(&m_interpCeeInfo, retTypeClsHnd);
            }
        }
    }

    if (((sigInfo.callConv & CORINFO_CALLCONV_VARARG) != 0) && sigInfo.isVarArg())
    {
        _ASSERTE(vaSigCookie != nullptr);
        args[curArgSlot] = PtrToArgSlot(vaSigCookie);
        argTypes[curArgSlot] = InterpreterType(CORINFO_TYPE_NATIVEINT);
        curArgSlot++;
    }

    if (pCscd == NULL)
    {
        if (sigInfo.hasTypeArg())
        {
            GCX_PREEMP();
            // We will find the instantiating stub for the method, and call that instead.
            CORINFO_SIG_INFO sigInfoFull;
            Instantiation methodInst = methToCall->GetMethodInstantiation();
            BOOL fNeedUnboxingStub = virtualCall && TypeHandle(exactClass).IsValueType() && methToCall->IsVirtual();
            methToCall = MethodDesc::FindOrCreateAssociatedMethodDesc(methToCall,
                TypeHandle(exactClass).GetMethodTable(), fNeedUnboxingStub, methodInst, FALSE, TRUE);
            m_interpCeeInfo.getMethodSig(CORINFO_METHOD_HANDLE(methToCall), &sigInfoFull, nullptr);
            sigInfo.retTypeClass = sigInfoFull.retTypeClass;
            sigInfo.numArgs = sigInfoFull.numArgs;
            sigInfo.callConv = sigInfoFull.callConv;
            sigInfo.retType = sigInfoFull.retType;
        }

        if (sigInfo.hasTypeArg())
        {
            // If we still have a type argument, we're calling an ArrayOpStub and need to pass the array TypeHandle.
            _ASSERTE(methToCall->IsArray());
            doNotCache = true;
            args[curArgSlot] = PtrToArgSlot(exactClass);
            argTypes[curArgSlot] = InterpreterType(CORINFO_TYPE_NATIVEINT);
            curArgSlot++;
        }
    }

    // Now we do the non-this arguments.
    size_t largeStructSpaceToPop = 0;
    for (; arg < totalArgsOnILStack; arg++)
    {
        InterpreterType argIt = OpStackTypeGet(argsBase + arg);
        size_t sz = OpStackTypeGet(argsBase + arg).Size(&m_interpCeeInfo);
        switch (sz)
        {
        case 1:
            args[curArgSlot] = OpStackGet<INT8>(argsBase + arg);
            break;
        case 2:
            args[curArgSlot] = OpStackGet<INT16>(argsBase + arg);
            break;
        case 4:
            args[curArgSlot] = OpStackGet<INT32>(argsBase + arg);
            break;
        case 8:
        default:
            if (sz > 8)
            {
                void* srcPtr = OpStackGet<void*>(argsBase + arg);
                args[curArgSlot] = PtrToArgSlot(srcPtr);
                if (!IsInLargeStructLocalArea(srcPtr))
                    largeStructSpaceToPop += sz;
            }
            else
            {
                args[curArgSlot] = OpStackGet<INT64>(argsBase + arg);
            }
            break;
        }
        argTypes[curArgSlot] = argIt;
        curArgSlot++;
    }

    if (ctorData.pArg3)
    {
        args[curArgSlot] = PtrToArgSlot(ctorData.pArg3);
        argTypes[curArgSlot] = InterpreterType(CORINFO_TYPE_NATIVEINT);
        curArgSlot++;
    }
    if (ctorData.pArg4)
    {
        args[curArgSlot] = PtrToArgSlot(ctorData.pArg4);
        argTypes[curArgSlot] = InterpreterType(CORINFO_TYPE_NATIVEINT);
        curArgSlot++;
    }
    if (ctorData.pArg5)
    {
        args[curArgSlot] = PtrToArgSlot(ctorData.pArg5);
        argTypes[curArgSlot] = InterpreterType(CORINFO_TYPE_NATIVEINT);
        curArgSlot++;
    }

    // CYCLE PROFILE: AFTER ARG PROCESSING.
    {
        Thread* thr = GetThread();

        Object** thisArgHnd = NULL;
        ARG_SLOT nullThisArg = NULL;
        if (sigInfo.hasThis())
        {
            if (m_callThisArg != NULL)
            {
                if (size_t(m_callThisArg) == 0x1)
                {
                    thisArgHnd = reinterpret_cast<Object**>(&nullThisArg);
                }
                else
                {
                    thisArgHnd = reinterpret_cast<Object**>(&m_callThisArg);
                }
            }
            else
            {
                thisArgHnd = OpStackGetAddr<Object*>(argsBase);
            }
        }

        Frame* topFrameBefore = thr->GetFrame();

#if INTERP_ILCYCLE_PROFILE
        unsigned __int64 startCycles;
#endif // INTERP_ILCYCLE_PROFILE

        // CYCLE PROFILE: BEFORE MDCS CREATION.

        PCODE target = NULL;
        MethodDesc *exactMethToCall = methToCall;

        // Determine the target of virtual calls.
        if (virtualCall && methToCall->IsVtableMethod())
        {
            PCODE pCode;

            _ASSERTE(thisArgHnd != NULL);
            OBJECTREF objRef = ObjectToOBJECTREF(*thisArgHnd);
            GCPROTECT_BEGIN(objRef);
            pCode = methToCall->GetMultiCallableAddrOfVirtualizedCode(&objRef, methToCall->GetMethodTable());
            GCPROTECT_END();

            exactMethToCall = Entry2MethodDesc(pCode, objRef->GetMethodTable());
        }

        // Compile the target in advance of calling.
        if (exactMethToCall->IsPointingToPrestub())
        {
            MethodTable* dispatchingMT = NULL;
            if (exactMethToCall->IsVtableMethod())
            {
                _ASSERTE(thisArgHnd != NULL);
                dispatchingMT = (*thisArgHnd)->GetMethodTable();
            }
            GCX_PREEMP();
            target = exactMethToCall->DoPrestub(dispatchingMT);
        }
        else
        {
            target = exactMethToCall->GetMethodEntryPoint();
        }

        // If we're interpreting the method, simply call it directly.
        if (InterpretationStubToMethodInfo(target) == exactMethToCall)
        {
            _ASSERTE(!exactMethToCall->IsILStub());
            InterpreterMethodInfo* methInfo = MethodHandleToInterpreterMethInfoPtr(CORINFO_METHOD_HANDLE(exactMethToCall));
            _ASSERTE(methInfo != NULL);
#if INTERP_ILCYCLE_PROFILE
            bool b = CycleTimer::GetThreadCyclesS(&startCycles); _ASSERTE(b);
#endif // INTERP_ILCYCLE_PROFILE
            retVals[0] = InterpretMethodBody(methInfo, true, reinterpret_cast<BYTE*>(args), NULL);
            pCscd = NULL;  // Nothing to cache.
        }
        else
        {
            MetaSig msig(exactMethToCall);
            // We've already resolved the virtual call target above, so there is no need to do it again.
            MethodDescCallSite mdcs(exactMethToCall, &msig, target);
#if INTERP_ILCYCLE_PROFILE
            bool b = CycleTimer::GetThreadCyclesS(&startCycles); _ASSERTE(b);
#endif // INTERP_ILCYCLE_PROFILE

#if defined(UNIX_AMD64_ABI)
            mdcs.CallTargetWorker(args, retVals, HasTwoSlotBuf ? 16: 8);
#else
            mdcs.CallTargetWorker(args, retVals, 8);
#endif

            if (pCscd != NULL)
            {
                // We will do a check at the end to determine whether to cache pCscd, to set
                // to NULL here to make sure we don't.
                pCscd = NULL;
            }
            else
            {
                // For now, we won't cache virtual calls to virtual methods.
                // TODO: fix this somehow.
                if (virtualCall && (callInfoPtr->methodFlags & CORINFO_FLG_VIRTUAL)) doNotCache = true;

                if (s_InterpreterUseCaching && !doNotCache)
                {
                    // We will add this to the cache later; the locking provokes a GC,
                    // and "retVal" is vulnerable.
                    pCscd = new CallSiteCacheData(exactMethToCall, sigInfo);
                }
            }
        }
#if INTERP_ILCYCLE_PROFILE
        unsigned __int64 endCycles;
        bool b = CycleTimer::GetThreadCyclesS(&endCycles); _ASSERTE(b);
        m_exemptCycles += (endCycles - startCycles);
#endif // INTERP_ILCYCLE_PROFILE

        // retVal is now vulnerable.
        GCX_FORBID();

        // Some managed methods, believe it or not, can push capital-F Frames on the Frame chain.
        // If this happens, executing the EX_CATCH below will pop it, which is bad.
        // So detect that case, pop the explicitly-pushed frame, and push it again after the EX_CATCH.
        // (Asserting that there is only 1 such frame!)
        if (thr->GetFrame() != topFrameBefore)
        {
            ilPushedFrame = thr->GetFrame();
            if (ilPushedFrame != NULL)
            {
                ilPushedFrame->Pop(thr);
                if (thr->GetFrame() != topFrameBefore)
                {
                    // This wasn't an IL-pushed frame, so restore.
                    ilPushedFrame->Push(thr);
                    ilPushedFrame = NULL;
                }
            }
        }
    }

    // retVal is still vulnerable.
    {
        GCX_FORBID();
        m_argsSize = 0;

        // At this point, the call has happened successfully.  We can delete the arguments from the operand stack.
        m_curStackHt -= totalArgsOnILStack;
        // We've already checked that "largeStructSpaceToPop
        LargeStructOperandStackPop(largeStructSpaceToPop, NULL);

        if (size_t(m_callThisArg) == 0x1)
        {
            _ASSERTE_MSG(sigInfo.retType == CORINFO_TYPE_VOID, "Constructor for var-sized object becomes factory method that returns result.");
            OpStackSet<Object*>(m_curStackHt, reinterpret_cast<Object*>(retVals[0]));
            OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_CLASS));
            m_curStackHt++;
        }
        else if (sigInfo.retType != CORINFO_TYPE_VOID)
        {
            switch (sigInfo.retType)
            {
            case CORINFO_TYPE_BOOL:
            case CORINFO_TYPE_BYTE:
                OpStackSet<INT32>(m_curStackHt, static_cast<INT8>(retVals[0]));
                break;
            case CORINFO_TYPE_UBYTE:
                OpStackSet<UINT32>(m_curStackHt, static_cast<UINT8>(retVals[0]));
                break;
            case CORINFO_TYPE_SHORT:
                OpStackSet<INT32>(m_curStackHt, static_cast<INT16>(retVals[0]));
                break;
            case CORINFO_TYPE_USHORT:
            case CORINFO_TYPE_CHAR:
                OpStackSet<UINT32>(m_curStackHt, static_cast<UINT16>(retVals[0]));
                break;
            case CORINFO_TYPE_INT:
            case CORINFO_TYPE_UINT:
            case CORINFO_TYPE_FLOAT:
                OpStackSet<INT32>(m_curStackHt, static_cast<INT32>(retVals[0]));
                break;
            case CORINFO_TYPE_LONG:
            case CORINFO_TYPE_ULONG:
            case CORINFO_TYPE_DOUBLE:
                OpStackSet<INT64>(m_curStackHt, static_cast<INT64>(retVals[0]));
                break;
            case CORINFO_TYPE_NATIVEINT:
            case CORINFO_TYPE_NATIVEUINT:
            case CORINFO_TYPE_PTR:
                OpStackSet<NativeInt>(m_curStackHt, static_cast<NativeInt>(retVals[0]));
                break;
            case CORINFO_TYPE_CLASS:
                OpStackSet<Object*>(m_curStackHt, reinterpret_cast<Object*>(retVals[0]));
                break;
            case CORINFO_TYPE_BYREF:
                OpStackSet<void*>(m_curStackHt, reinterpret_cast<void*>(retVals[0]));
                break;
            case CORINFO_TYPE_VALUECLASS:
            case CORINFO_TYPE_REFANY:
                {
                    // We must be careful here to write the value, the type, and update the stack height in one
                    // sequence that has no COOP->PREEMP transitions in it, so no GC's happen until the value
                    // is protected by being fully "on" the operandStack.
#if defined(HOST_ARM)
                    // Is the return type an HFA?
                    if (HFAReturnArgSlots > 0)
                    {
                        ARG_SLOT* hfaRetBuff = args - HFAReturnArgSlots;
                        if (retTypeIt.IsLargeStruct(&m_interpCeeInfo))
                        {
                            void* dst = LargeStructOperandStackPush(retTypeSz);
                            memcpy(dst, hfaRetBuff, retTypeSz);
                            OpStackSet<void*>(m_curStackHt, dst);
                        }
                        else
                        {
                            memcpy(OpStackGetAddr<UINT64>(m_curStackHt), hfaRetBuff, retTypeSz);
                        }
                    }
                    else
#endif // defined(HOST_ARM)
                    if (pLargeStructRetVal != NULL)
                    {
                        _ASSERTE(hasRetBuffArg);
                        void* dst = LargeStructOperandStackPush(retTypeSz);
                        CopyValueClassUnchecked(dst, pLargeStructRetVal, GetMethodTableFromClsHnd(retTypeClsHnd));
                        OpStackSet<void*>(m_curStackHt, dst);
                    }
                    else if (hasRetBuffArg)
                    {
                        OpStackSet<INT64>(m_curStackHt, GetSmallStructValue(&smallStructRetVal, retTypeSz));
                    }
#if defined(UNIX_AMD64_ABI)
                    else if (HasTwoSlotBuf)
                    {
                        void* dst = LargeStructOperandStackPush(16);
                        CopyValueClassUnchecked(dst, retVals, GetMethodTableFromClsHnd(retTypeClsHnd));
                        OpStackSet<void*>(m_curStackHt, dst);
                    }
#endif
                    else
                    {
                        OpStackSet<UINT64>(m_curStackHt, retVals[0]);
                    }
                    // We already created this interpreter type, so use it.
                    OpStackTypeSet(m_curStackHt, retTypeIt.StackNormalize());
                    m_curStackHt++;


                    // In the value-class case, the call might have used a ret buff, which we would have registered for GC scanning.
                    // Make sure it's unregistered.
                    m_structRetValITPtr = NULL;
                }
                break;
            default:
                NYI_INTERP("Unhandled return type");
                break;
            }
            _ASSERTE_MSG(m_structRetValITPtr == NULL, "Invariant.");

            // The valueclass case is handled fully in the switch above.
            if (sigInfo.retType != CORINFO_TYPE_VALUECLASS &&
                    sigInfo.retType != CORINFO_TYPE_REFANY)
            {
                OpStackTypeSet(m_curStackHt, InterpreterType(sigInfo.retType).StackNormalize());
                m_curStackHt++;
            }
        }
    }

    // Originally, this assertion was in the ValueClass case above, but it does a COOP->PREEMP
    // transition, and therefore causes a GC, and we're GCX_FORBIDden from doing a GC while retVal
    // is vulnerable.  So, for completeness, do it here.
    _ASSERTE(sigInfo.retType != CORINFO_TYPE_VALUECLASS || retTypeIt == InterpreterType(&m_interpCeeInfo, retTypeClsHnd));

    // If we created a cached call site, cache it now (when it's safe to take a GC).
    if (pCscd != NULL && !doNotCache)
    {
        CacheCallInfo(iloffset, pCscd);
    }

    m_callThisArg = NULL;

    // If the call we just made pushed a Frame, we popped it above, so re-push it.
    if (ilPushedFrame != NULL) ilPushedFrame->Push();
}

#include "metadata.h"

void Interpreter::CallI()
{
#if INTERP_DYNAMIC_CONTRACTS
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;
#else
    // Dynamic contract occupies too much stack.
    STATIC_CONTRACT_THROWS;
    STATIC_CONTRACT_GC_TRIGGERS;
    STATIC_CONTRACT_MODE_COOPERATIVE;
#endif

#if INTERP_TRACING
    InterlockedIncrement(&s_totalInterpCalls);
#endif // INTERP_TRACING

    unsigned tok = getU4LittleEndian(m_ILCodePtr + sizeof(BYTE));

    CORINFO_SIG_INFO sigInfo;

    {
        GCX_PREEMP();
        m_interpCeeInfo.findSig(m_methInfo->m_module, tok, GetPreciseGenericsContext(), &sigInfo);
    }

    // I'm assuming that a calli can't depend on the generics context, so the simple form of type
    // context should suffice?
    MethodDesc* pMD = reinterpret_cast<MethodDesc*>(m_methInfo->m_method);
    SigTypeContext sigTypeCtxt(pMD);
    MetaSig mSig(sigInfo.pSig, sigInfo.cbSig, GetModule(sigInfo.scope), &sigTypeCtxt);

    unsigned totalSigArgs = sigInfo.totalILArgs();

    // Note that "totalNativeArgs()" includes space for ret buff arg.
    unsigned nSlots = totalSigArgs + 1;
    if ((sigInfo.callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_VARARG)
    {
        nSlots++;
    }

    // Make sure that the operand stack has the required number of arguments.
    // (Note that this is IL args, not native.)
    //

    // The total number of arguments on the IL stack.  Initially we assume that all the IL arguments
    // the callee expects are on the stack, but may be adjusted downwards if the "this" argument
    // is provided by an allocation (the call is to a constructor).
    unsigned totalArgsOnILStack = totalSigArgs;

    const unsigned LOCAL_ARG_SLOTS = 8;
    ARG_SLOT localArgs[LOCAL_ARG_SLOTS];
    InterpreterType localArgTypes[LOCAL_ARG_SLOTS];

    ARG_SLOT* args;
    InterpreterType* argTypes;
    if (nSlots <= LOCAL_ARG_SLOTS)
    {
        args = &localArgs[0];
        argTypes = &localArgTypes[0];
    }
    else
    {
        args = (ARG_SLOT*)_alloca(nSlots * sizeof(ARG_SLOT));
        argTypes = (InterpreterType*)_alloca(nSlots * sizeof(InterpreterType));
    }
    // Make sure that we don't scan any of these until we overwrite them with
    // the real types of the arguments.
    InterpreterType undefIt(CORINFO_TYPE_UNDEF);
    for (unsigned i = 0; i < nSlots; i++)
    {
        argTypes[i] = undefIt;
    }

    // GC-protect the argument array (as byrefs).
    m_args = args;
    m_argsSize = nSlots;
    m_argTypes = argTypes;

    // This is the index into the "args" array (where we copy the value to).
    int curArgSlot = 0;

    // The operand stack index of the first IL argument.
    unsigned totalArgPositions = totalArgsOnILStack + 1;  // + 1 for the ftn argument.
    _ASSERTE(m_curStackHt >= totalArgPositions);
    int argsBase = m_curStackHt - totalArgPositions;

    // Current on-stack argument index.
    unsigned arg = 0;

    if (sigInfo.hasThis())
    {
        args[curArgSlot] = PtrToArgSlot(OpStackGet<void*>(argsBase + arg));
        argTypes[curArgSlot] = OpStackTypeGet(argsBase + arg);
        // AV -> NullRef translation is NYI for the interpreter,
        // so we should manually check and throw the correct exception.
        ThrowOnInvalidPointer((void*)args[curArgSlot]);
        arg++;
        curArgSlot++;
    }

    // This is the argument slot that will be used to hold the return value.
    ARG_SLOT retVal = 0;

    // If the return type is a structure, then these will be initialized.
    CORINFO_CLASS_HANDLE retTypeClsHnd = NULL;
    InterpreterType retTypeIt;
    size_t retTypeSz = 0;

    // If non-null, space allocated to hold a large struct return value.  Should be deleted later.
    // (I could probably optimize this pop all the arguments first, then allocate space for the return value
    // on the large structure operand stack, and pass a pointer directly to that space, avoiding the extra
    // copy we have below.  But this seemed more expedient, and this should be a pretty rare case.)
    BYTE* pLargeStructRetVal = NULL;

    // If there's a "GetFlag<Flag_hasRetBuffArg>()" struct return value, it will be stored in this variable if it fits,
    // otherwise, we'll dynamically allocate memory for it.
    ARG_SLOT smallStructRetVal = 0;

    // We should have no return buffer temp space registered here...unless this is a constructor, in which
    // case it will return void.  In particular, if the return type VALUE_CLASS, then this should be NULL.
    _ASSERTE_MSG(sigInfo.retType == CORINFO_TYPE_VOID || m_structRetValITPtr == NULL, "Invariant.");

    // Is it the return value a struct with a ret buff?
    bool hasRetBuffArg = false;
    if (sigInfo.retType == CORINFO_TYPE_VALUECLASS)
    {
        retTypeClsHnd = sigInfo.retTypeClass;
        retTypeIt = InterpreterType(&m_interpCeeInfo, retTypeClsHnd);
        retTypeSz = retTypeIt.Size(&m_interpCeeInfo);

#if defined(UNIX_AMD64_ABI)
        //
#elif defined(HOST_AMD64)
        // TODO: Investigate why HasRetBuffArg can't be used. pMD is a hacked up MD for the
        // calli because it belongs to the current method. Doing what the JIT does.
        hasRetBuffArg = (retTypeSz > sizeof(void*)) || ((retTypeSz & (retTypeSz - 1)) != 0);
#else
        hasRetBuffArg = !!pMD->HasRetBuffArg();
#endif
        if (hasRetBuffArg)
        {
            if (retTypeIt.IsLargeStruct(&m_interpCeeInfo))
            {
                size_t retBuffSize = retTypeSz;
                // If the target architecture can sometimes return a struct in several registers,
                // MethodDescCallSite will reserve a return value array big enough to hold the maximum.
                // It will then copy *all* of this into the return buffer area we allocate.  So make sure
                // we allocate at least that much.
#ifdef ENREGISTERED_RETURNTYPE_MAXSIZE
                retBuffSize = max(retTypeSz, ENREGISTERED_RETURNTYPE_MAXSIZE);
#endif // ENREGISTERED_RETURNTYPE_MAXSIZE
                pLargeStructRetVal = (BYTE*)_alloca(retBuffSize);

                // Clear this in case a GC happens.
                for (unsigned i = 0; i < retTypeSz; i++)
                {
                    pLargeStructRetVal[i] = 0;
                }

                // Register this as location needing GC.
                m_structRetValTempSpace = pLargeStructRetVal;

                // Set it as the return buffer.
                args[curArgSlot] = PtrToArgSlot(pLargeStructRetVal);
            }
            else
            {
                // Clear this in case a GC happens.
                smallStructRetVal = 0;

                // Register this as location needing GC.
                m_structRetValTempSpace = &smallStructRetVal;

                // Set it as the return buffer.
                args[curArgSlot] = PtrToArgSlot(&smallStructRetVal);
            }
            m_structRetValITPtr = &retTypeIt;
            argTypes[curArgSlot] = InterpreterType(CORINFO_TYPE_NATIVEINT);
            curArgSlot++;
        }
    }

    if ((sigInfo.callConv & CORINFO_CALLCONV_MASK) == CORINFO_CALLCONV_VARARG)
    {
        Module* module = GetModule(sigInfo.scope);
        CORINFO_VARARGS_HANDLE handle = CORINFO_VARARGS_HANDLE(module->GetVASigCookie(Signature(sigInfo.pSig, sigInfo.cbSig)));
        args[curArgSlot] = PtrToArgSlot(handle);
        argTypes[curArgSlot] = InterpreterType(CORINFO_TYPE_NATIVEINT);
        curArgSlot++;
    }

    // Now we do the non-this arguments.
    size_t largeStructSpaceToPop = 0;
    for (; arg < totalArgsOnILStack; arg++)
    {
        InterpreterType argIt = OpStackTypeGet(argsBase + arg);
        size_t sz = OpStackTypeGet(argsBase + arg).Size(&m_interpCeeInfo);
        switch (sz)
        {
        case 1:
            args[curArgSlot] = OpStackGet<INT8>(argsBase + arg);
            break;
        case 2:
            args[curArgSlot] = OpStackGet<INT16>(argsBase + arg);
            break;
        case 4:
            args[curArgSlot] = OpStackGet<INT32>(argsBase + arg);
            break;
        case 8:
        default:
            if (sz > 8)
            {
                void* srcPtr = OpStackGet<void*>(argsBase + arg);
                args[curArgSlot] = PtrToArgSlot(srcPtr);
                if (!IsInLargeStructLocalArea(srcPtr))
                {
                    largeStructSpaceToPop += sz;
                }
            }
            else
            {
                args[curArgSlot] = OpStackGet<INT64>(argsBase + arg);
            }
            break;
        }
        argTypes[curArgSlot] = argIt;
        curArgSlot++;
    }

    // Finally, we get the code pointer.
    unsigned ftnInd = m_curStackHt - 1;
#ifdef _DEBUG
    CorInfoType ftnType = OpStackTypeGet(ftnInd).ToCorInfoType();
    _ASSERTE(ftnType == CORINFO_TYPE_NATIVEINT
             || ftnType == CORINFO_TYPE_INT
             || ftnType == CORINFO_TYPE_LONG);
#endif // DEBUG

    PCODE ftnPtr = OpStackGet<PCODE>(ftnInd);

    {
        MethodDesc* methToCall;
        // If we're interpreting the target, simply call it directly.
        if ((methToCall = InterpretationStubToMethodInfo((PCODE)ftnPtr)) != NULL)
        {
            InterpreterMethodInfo* methInfo = MethodHandleToInterpreterMethInfoPtr(CORINFO_METHOD_HANDLE(methToCall));
            _ASSERTE(methInfo != NULL);
#if INTERP_ILCYCLE_PROFILE
            bool b = CycleTimer::GetThreadCyclesS(&startCycles); _ASSERTE(b);
#endif // INTERP_ILCYCLE_PROFILE
            retVal = InterpretMethodBody(methInfo, true, reinterpret_cast<BYTE*>(args), NULL);
        }
        else
        {
            // This is not a great workaround.  For the most part, we really don't care what method desc we're using, since
            // we're providing the signature and function pointer -- other than that it's well-formed and "activated."
            // And also, one more thing: whether it is static or not.  Which is actually determined by the signature.
            // So we query the signature we have to determine whether we need a static or instance MethodDesc, and then
            // use one of the appropriate staticness that happens to be sitting around in global variables.  For static
            // we use "RuntimeHelpers.PrepareConstrainedRegions", for instance we use the default constructor of "Object."
            // TODO: make this cleaner -- maybe invent a couple of empty methods with instructive names, just for this purpose.
            MethodDesc* pMD;
            if (mSig.HasThis())
            {
                pMD = g_pObjectFinalizerMD;
            }
            else
            {
                pMD = CoreLibBinder::GetMethod(METHOD__INTERLOCKED__COMPARE_EXCHANGE_OBJECT);  // A random static method.
            }
            MethodDescCallSite mdcs(pMD, &mSig, ftnPtr);
#if 0
            // If the current method being interpreted is an IL stub, we're calling native code, so
            // change the GC mode.  (We'll only do this at the call if the calling convention turns out
            // to be a managed calling convention.)
            MethodDesc* pStubContextMD = reinterpret_cast<MethodDesc*>(m_stubContext);
            bool transitionToPreemptive = (pStubContextMD != NULL && !pStubContextMD->IsIL());
            mdcs.CallTargetWorker(args, &retVal, sizeof(retVal), transitionToPreemptive);
#else
            // TODO The code above triggers assertion at threads.cpp:6861:
            //     _ASSERTE(thread->PreemptiveGCDisabled());  // Should have been in managed code
            // The workaround will likely break more things than what it is fixing:
            // just do not make transition to preemptive GC for now.
            mdcs.CallTargetWorker(args, &retVal, sizeof(retVal));
#endif
        }
        // retVal is now vulnerable.
        GCX_FORBID();
    }

    // retVal is still vulnerable.
    {
        GCX_FORBID();
        m_argsSize = 0;

        // At this point, the call has happened successfully.  We can delete the arguments from the operand stack.
        m_curStackHt -= totalArgPositions;

        // We've already checked that "largeStructSpaceToPop
        LargeStructOperandStackPop(largeStructSpaceToPop, NULL);

        if (size_t(m_callThisArg) == 0x1)
        {
            _ASSERTE_MSG(sigInfo.retType == CORINFO_TYPE_VOID, "Constructor for var-sized object becomes factory method that returns result.");
            OpStackSet<Object*>(m_curStackHt, reinterpret_cast<Object*>(retVal));
            OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_CLASS));
            m_curStackHt++;
        }
        else if (sigInfo.retType != CORINFO_TYPE_VOID)
        {
            switch (sigInfo.retType)
            {
            case CORINFO_TYPE_BOOL:
            case CORINFO_TYPE_BYTE:
                OpStackSet<INT32>(m_curStackHt, static_cast<INT8>(retVal));
                break;
            case CORINFO_TYPE_UBYTE:
                OpStackSet<UINT32>(m_curStackHt, static_cast<UINT8>(retVal));
                break;
            case CORINFO_TYPE_SHORT:
                OpStackSet<INT32>(m_curStackHt, static_cast<INT16>(retVal));
                break;
            case CORINFO_TYPE_USHORT:
            case CORINFO_TYPE_CHAR:
                OpStackSet<UINT32>(m_curStackHt, static_cast<UINT16>(retVal));
                break;
            case CORINFO_TYPE_INT:
            case CORINFO_TYPE_UINT:
            case CORINFO_TYPE_FLOAT:
                OpStackSet<INT32>(m_curStackHt, static_cast<INT32>(retVal));
                break;
            case CORINFO_TYPE_LONG:
            case CORINFO_TYPE_ULONG:
            case CORINFO_TYPE_DOUBLE:
                OpStackSet<INT64>(m_curStackHt, static_cast<INT64>(retVal));
                break;
            case CORINFO_TYPE_NATIVEINT:
            case CORINFO_TYPE_NATIVEUINT:
            case CORINFO_TYPE_PTR:
                OpStackSet<NativeInt>(m_curStackHt, static_cast<NativeInt>(retVal));
                break;
            case CORINFO_TYPE_CLASS:
                OpStackSet<Object*>(m_curStackHt, reinterpret_cast<Object*>(retVal));
                break;
            case CORINFO_TYPE_VALUECLASS:
                {
                    // We must be careful here to write the value, the type, and update the stack height in one
                    // sequence that has no COOP->PREEMP transitions in it, so no GC's happen until the value
                    // is protected by being fully "on" the operandStack.
                    if (pLargeStructRetVal != NULL)
                    {
                        _ASSERTE(hasRetBuffArg);
                        void* dst = LargeStructOperandStackPush(retTypeSz);
                        CopyValueClassUnchecked(dst, pLargeStructRetVal, GetMethodTableFromClsHnd(retTypeClsHnd));
                        OpStackSet<void*>(m_curStackHt, dst);
                    }
                    else if (hasRetBuffArg)
                    {
                        OpStackSet<INT64>(m_curStackHt, GetSmallStructValue(&smallStructRetVal, retTypeSz));
                    }
                    else
                    {
                        OpStackSet<UINT64>(m_curStackHt, retVal);
                    }
                    // We already created this interpreter type, so use it.
                    OpStackTypeSet(m_curStackHt, retTypeIt.StackNormalize());
                    m_curStackHt++;

                    // In the value-class case, the call might have used a ret buff, which we would have registered for GC scanning.
                    // Make sure it's unregistered.
                    m_structRetValITPtr = NULL;
                }
                break;
            default:
                NYI_INTERP("Unhandled return type");
                break;
            }
            _ASSERTE_MSG(m_structRetValITPtr == NULL, "Invariant.");

            // The valueclass case is handled fully in the switch above.
            if (sigInfo.retType != CORINFO_TYPE_VALUECLASS)
            {
                OpStackTypeSet(m_curStackHt, InterpreterType(sigInfo.retType).StackNormalize());
                m_curStackHt++;
            }
        }
    }

    // Originally, this assertion was in the ValueClass case above, but it does a COOP->PREEMP
    // transition, and therefore causes a GC, and we're GCX_FORBIDden from doing a GC while retVal
    // is vulnerable.  So, for completeness, do it here.
    _ASSERTE(sigInfo.retType != CORINFO_TYPE_VALUECLASS || retTypeIt == InterpreterType(&m_interpCeeInfo, retTypeClsHnd));

    m_ILCodePtr += 5;
}

// static
bool Interpreter::IsDeadSimpleGetter(CEEInfo* info, MethodDesc* pMD, size_t* offsetOfLd)
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_ANY;
    } CONTRACTL_END;

    DWORD flags = pMD->GetAttrs();
    CORINFO_METHOD_INFO methInfo;
    {
        GCX_PREEMP();
        bool b = info->getMethodInfo(CORINFO_METHOD_HANDLE(pMD), &methInfo);
        if (!b) return false;
    }

    // If the method takes a generic type argument, it's not dead simple...
    if (methInfo.args.callConv & CORINFO_CALLCONV_PARAMTYPE) return false;

    BYTE* codePtr = methInfo.ILCode;

    if (flags & CORINFO_FLG_STATIC)
    {
        if (methInfo.ILCodeSize != 6)
            return false;
        if (*codePtr != CEE_LDSFLD)
            return false;
        _ASSERTE(ILOffsetOfLdSFldInDeadSimpleStaticGetter == 0);
        *offsetOfLd = 0;
        codePtr += 5;
        return (*codePtr == CEE_RET);
    }
    else
    {
        // We handle two forms, one for DBG IL, and one for OPT IL.
        bool dbg = false;
        if (methInfo.ILCodeSize == 0xc)
            dbg = true;
        else if (methInfo.ILCodeSize != 7)
            return false;

        if (dbg)
        {
            if (*codePtr != CEE_NOP)
                return false;
            codePtr += 1;
        }
        if (*codePtr != CEE_LDARG_0)
            return false;
        codePtr += 1;
        if (*codePtr != CEE_LDFLD)
            return false;
        *offsetOfLd = codePtr - methInfo.ILCode;
        _ASSERTE((dbg && ILOffsetOfLdFldInDeadSimpleInstanceGetterDbg == *offsetOfLd)
                 || (!dbg && ILOffsetOfLdFldInDeadSimpleInstanceGetterOpt == *offsetOfLd));
        codePtr += 5;
        if (dbg)
        {
            if (*codePtr != CEE_STLOC_0)
                return false;
            codePtr += 1;
            if (*codePtr != CEE_BR)
                return false;
            if (getU4LittleEndian(codePtr + 1) != 0)
                return false;
            codePtr += 5;
            if (*codePtr != CEE_LDLOC_0)
                return false;
        }
        return (*codePtr == CEE_RET);
    }
}

void Interpreter::DoStringLength()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    _ASSERTE(m_curStackHt > 0);
    unsigned ind = m_curStackHt - 1;

#ifdef _DEBUG
    CorInfoType stringCIT = OpStackTypeGet(ind).ToCorInfoType();
    if (stringCIT != CORINFO_TYPE_CLASS)
    {
        VerificationError("StringLength called on non-string.");
    }
#endif // _DEBUG

    Object* obj = OpStackGet<Object*>(ind);

    if (obj == NULL)
    {
        ThrowNullPointerException();
    }

#ifdef _DEBUG
    if (obj->GetMethodTable() != g_pStringClass)
    {
        VerificationError("StringLength called on non-string.");
    }
#endif // _DEBUG

    StringObject* str = reinterpret_cast<StringObject*>(obj);
    INT32 len = str->GetStringLength();
    OpStackSet<INT32>(ind, len);
    OpStackTypeSet(ind, InterpreterType(CORINFO_TYPE_INT));
}

void Interpreter::DoStringGetChar()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    _ASSERTE(m_curStackHt >= 2);
    unsigned strInd = m_curStackHt - 2;
    unsigned indexInd = strInd + 1;

#ifdef _DEBUG
    CorInfoType stringCIT = OpStackTypeGet(strInd).ToCorInfoType();
    if (stringCIT != CORINFO_TYPE_CLASS)
    {
        VerificationError("StringGetChar called on non-string.");
    }
#endif // _DEBUG

    Object* obj = OpStackGet<Object*>(strInd);

    if (obj == NULL)
    {
        ThrowNullPointerException();
    }

#ifdef _DEBUG
    if (obj->GetMethodTable() != g_pStringClass)
    {
        VerificationError("StringGetChar called on non-string.");
    }
#endif // _DEBUG

    StringObject* str = reinterpret_cast<StringObject*>(obj);

#ifdef _DEBUG
    CorInfoType indexCIT = OpStackTypeGet(indexInd).ToCorInfoType();
    if (indexCIT != CORINFO_TYPE_INT)
    {
        VerificationError("StringGetChar needs integer index.");
    }
#endif // _DEBUG

    INT32 ind = OpStackGet<INT32>(indexInd);
    if (ind < 0)
        ThrowArrayBoundsException();
    UINT32 uind = static_cast<UINT32>(ind);
    if (uind >= str->GetStringLength())
        ThrowArrayBoundsException();

    // Otherwise...
    GCX_FORBID(); // str is vulnerable.
    UINT16* dataPtr = reinterpret_cast<UINT16*>(reinterpret_cast<INT8*>(str) + StringObject::GetBufferOffset());
    UINT32 filledChar = dataPtr[ind];
    OpStackSet<UINT32>(strInd, filledChar);
    OpStackTypeSet(strInd, InterpreterType(CORINFO_TYPE_INT));
    m_curStackHt = indexInd;
}

void Interpreter::DoGetTypeFromHandle()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    _ASSERTE(m_curStackHt > 0);
    unsigned ind = m_curStackHt - 1;

#ifdef _DEBUG
    CorInfoType handleCIT = OpStackTypeGet(ind).ToCorInfoType();
    if (handleCIT != CORINFO_TYPE_VALUECLASS && handleCIT != CORINFO_TYPE_CLASS)
    {
        VerificationError("HandleGetTypeFromHandle called on non-RuntimeTypeHandle/non-RuntimeType.");
    }
    Object* obj = OpStackGet<Object*>(ind);
    if (obj->GetMethodTable() != g_pRuntimeTypeClass)
    {
        VerificationError("HandleGetTypeFromHandle called on non-RuntimeTypeHandle/non-RuntimeType.");
    }
#endif // _DEBUG

    OpStackTypeSet(ind, InterpreterType(CORINFO_TYPE_CLASS));
}

void Interpreter::DoSIMDHwAccelerated()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

#if INTERP_TRACING
    if (s_TraceInterpreterILFlag.val(CLRConfig::INTERNAL_TraceInterpreterIL))
    {
        fprintf(GetLogFile(), "    System.Numerics.Vector.IsHardwareAccelerated -- intrinsic\n");
    }
#endif // INTERP_TRACING

    LdIcon(1);
}


void Interpreter::DoGetIsSupported()
{
    CONTRACTL{
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    OpStackSet<BOOL>(m_curStackHt, false);
    OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_INT));
    m_curStackHt++;
}

void Interpreter::RecordConstrainedCall()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

#if INTERP_TRACING
    InterlockedIncrement(&s_tokenResolutionOpportunities[RTK_Constrained]);
#endif // INTERP_TRACING

    {
        GCX_PREEMP();
        ResolveToken(&m_constrainedResolvedToken, getU4LittleEndian(m_ILCodePtr + 2), CORINFO_TOKENKIND_Constrained InterpTracingArg(RTK_Constrained));
    }

    m_constrainedFlag = true;

    m_ILCodePtr += 6;
}

void Interpreter::LargeStructOperandStackEnsureCanPush(size_t sz)
{
    size_t remaining = m_largeStructOperandStackAllocSize - m_largeStructOperandStackHt;
    if (remaining < sz)
    {
        size_t newAllocSize = max(m_largeStructOperandStackAllocSize + sz * 4, m_largeStructOperandStackAllocSize * 2);
        BYTE* newStack = new BYTE[newAllocSize];
        m_largeStructOperandStackAllocSize = newAllocSize;
        if (m_largeStructOperandStack != NULL)
        {
            memcpy(newStack, m_largeStructOperandStack, m_largeStructOperandStackHt);
            delete[] m_largeStructOperandStack;
        }
        m_largeStructOperandStack = newStack;
    }
}

void* Interpreter::LargeStructOperandStackPush(size_t sz)
{
    LargeStructOperandStackEnsureCanPush(sz);
    _ASSERTE(m_largeStructOperandStackAllocSize >= m_largeStructOperandStackHt + sz);
    void* res = &m_largeStructOperandStack[m_largeStructOperandStackHt];
    m_largeStructOperandStackHt += sz;
    return res;
}

void Interpreter::LargeStructOperandStackPop(size_t sz, void* fromAddr)
{
    if (!IsInLargeStructLocalArea(fromAddr))
    {
        _ASSERTE(m_largeStructOperandStackHt >= sz);
        m_largeStructOperandStackHt -= sz;
    }
}

#ifdef _DEBUG
bool Interpreter::LargeStructStackHeightIsValid()
{
    size_t sz2 = 0;
    for (unsigned k = 0; k < m_curStackHt; k++)
    {
        if (OpStackTypeGet(k).IsLargeStruct(&m_interpCeeInfo) && !IsInLargeStructLocalArea(OpStackGet<void*>(k)))
        {
            sz2 += OpStackTypeGet(k).Size(&m_interpCeeInfo);
        }
    }
    _ASSERTE(sz2 == m_largeStructOperandStackHt);
    return sz2 == m_largeStructOperandStackHt;
}
#endif // _DEBUG

void Interpreter::VerificationError(const char* msg)
{
    // TODO: Should raise an exception eventually; for now:
    const char* const msgPrefix = "Verification Error: ";
    size_t len = strlen(msgPrefix) + strlen(msg) + 1;
    char* msgFinal = (char*)_alloca(len);
    strcpy_s(msgFinal, len, msgPrefix);
    strcat_s(msgFinal, len, msg);
    _ASSERTE_MSG(false, msgFinal);
}

void Interpreter::ThrowDivideByZero()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    COMPlusThrow(kDivideByZeroException);
}

void Interpreter::ThrowSysArithException()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    // According to the ECMA spec, this should be an ArithmeticException; however,
    // the JITs throw an OverflowException and consistency is top priority...
    COMPlusThrow(kOverflowException);
}

void Interpreter::ThrowNullPointerException()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    COMPlusThrow(kNullReferenceException);
}

void Interpreter::ThrowOverflowException()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    COMPlusThrow(kOverflowException);
}

void Interpreter::ThrowArrayBoundsException()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    COMPlusThrow(kIndexOutOfRangeException);
}

void Interpreter::ThrowInvalidCastException()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    COMPlusThrow(kInvalidCastException);
}

void Interpreter::ThrowStackOverflow()
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_COOPERATIVE;
    } CONTRACTL_END;

    COMPlusThrow(kStackOverflowException);
}

float Interpreter::RemFunc(float v1, float v2)
{
    return fmodf(v1, v2);
}

double Interpreter::RemFunc(double v1, double v2)
{
    return fmod(v1, v2);
}

// Static members and methods.
Interpreter::AddrToMDMap* Interpreter::s_addrToMDMap = NULL;

unsigned Interpreter::s_interpreterStubNum = 0;

// TODO: contracts and synchronization for the AddrToMDMap methods.
// Requires caller to hold "s_interpStubToMDMapLock".
Interpreter::AddrToMDMap* Interpreter::GetAddrToMdMap()
{
#if 0
    CONTRACTL {
        THROWS;
        GC_NOTRIGGER;
    } CONTRACTL_END;
#endif

    if (s_addrToMDMap == NULL)
    {
        s_addrToMDMap = new AddrToMDMap();
    }
    return s_addrToMDMap;
}

void Interpreter::RecordInterpreterStubForMethodDesc(CORINFO_METHOD_HANDLE md, void* addr)
{
#if 0
    CONTRACTL {
        NOTHROW;
        GC_NOTRIGGER;
    } CONTRACTL_END;
#endif

    CrstHolder ch(&s_interpStubToMDMapLock);

    AddrToMDMap* map = Interpreter::GetAddrToMdMap();
#ifdef _DEBUG
    CORINFO_METHOD_HANDLE dummy;
    _ASSERTE(!map->Lookup(addr, &dummy));
#endif // DEBUG
    map->AddOrReplace(KeyValuePair<void*,CORINFO_METHOD_HANDLE>(addr, md));
}

MethodDesc* Interpreter::InterpretationStubToMethodInfo(PCODE addr)
{
    CONTRACTL {
        NOTHROW;
        GC_NOTRIGGER;
    } CONTRACTL_END;


    // This query function will never allocate the table...
    if (s_addrToMDMap == NULL)
        return NULL;

    // Otherwise...if we observe s_addrToMdMap non-null, the lock below must be initialized.
    // CrstHolder ch(&s_interpStubToMDMapLock);

    AddrToMDMap* map = Interpreter::GetAddrToMdMap();
    CORINFO_METHOD_HANDLE result = NULL;
    (void)map->Lookup((void*)addr, &result);
    return (MethodDesc*)result;
}

Interpreter::MethodHandleToInterpMethInfoPtrMap* Interpreter::s_methodHandleToInterpMethInfoPtrMap = NULL;

// Requires caller to hold "s_interpStubToMDMapLock".
Interpreter::MethodHandleToInterpMethInfoPtrMap* Interpreter::GetMethodHandleToInterpMethInfoPtrMap()
{
#if 0
    CONTRACTL {
        THROWS;
        GC_NOTRIGGER;
    } CONTRACTL_END;
#endif

    if (s_methodHandleToInterpMethInfoPtrMap == NULL)
    {
        s_methodHandleToInterpMethInfoPtrMap = new MethodHandleToInterpMethInfoPtrMap();
    }
    return s_methodHandleToInterpMethInfoPtrMap;
}

InterpreterMethodInfo* Interpreter::RecordInterpreterMethodInfoForMethodHandle(CORINFO_METHOD_HANDLE md, InterpreterMethodInfo* methInfo)
{
#if 0
    CONTRACTL {
        NOTHROW;
        GC_NOTRIGGER;
    } CONTRACTL_END;
#endif

    CrstHolder ch(&s_interpStubToMDMapLock);

    MethodHandleToInterpMethInfoPtrMap* map = Interpreter::GetMethodHandleToInterpMethInfoPtrMap();

    MethInfo mi;
    if (map->Lookup(md, &mi))
    {
        // If there's already an entry, make sure it was created by another thread -- the same thread shouldn't create two
        // of these.
        _ASSERTE_MSG(mi.m_thread != GetThread(), "Two InterpMethInfo's for same meth by same thread.");
        // If we were creating an interpreter stub at the same time as another thread, and we lost the race to
        // insert it, use the already-existing one, and delete this one.
        delete methInfo;
        return mi.m_info;
    }

    mi.m_info = methInfo;
#ifdef _DEBUG
    mi.m_thread = GetThread();
#endif

    _ASSERTE_MSG(map->LookupPtr(md) == NULL, "Multiple InterpMethInfos for method desc.");
    map->Add(md, mi);
    return methInfo;
}

InterpreterMethodInfo* Interpreter::MethodHandleToInterpreterMethInfoPtr(CORINFO_METHOD_HANDLE md)
{
    CONTRACTL {
        NOTHROW;
        GC_TRIGGERS;
    } CONTRACTL_END;

    // This query function will never allocate the table...
    if (s_methodHandleToInterpMethInfoPtrMap == NULL)
        return NULL;

    // Otherwise...if we observe s_addrToMdMap non-null, the lock below must be initialized.
    CrstHolder ch(&s_interpStubToMDMapLock);

    MethodHandleToInterpMethInfoPtrMap* map = Interpreter::GetMethodHandleToInterpMethInfoPtrMap();

    MethInfo mi;
    mi.m_info = NULL;
    (void)map->Lookup(md, &mi);
    return mi.m_info;
}


#ifndef DACCESS_COMPILE

// Requires that the current thread holds "s_methodCacheLock."
ILOffsetToItemCache* InterpreterMethodInfo::GetCacheForCall(Object* thisArg, void* genericsCtxtArg, bool alloc)
{
    // First, does the current method have dynamic generic information, and, if so,
    // what kind?
    CORINFO_CONTEXT_HANDLE context = GetPreciseGenericsContext(thisArg, genericsCtxtArg);
    if (context == MAKE_METHODCONTEXT(m_method))
    {
        // No dynamic generics context information.  The caching field in "m_methInfo" is the
        // ILoffset->Item cache directly.
        // First, ensure that it's allocated.
        if (m_methodCache == NULL && alloc)
        {
            // Lazy init via compare-exchange.
            ILOffsetToItemCache* cache = new ILOffsetToItemCache();
            void* prev = InterlockedCompareExchangeT<void*>(&m_methodCache, cache, NULL);
            if (prev != NULL) delete cache;
        }
        return reinterpret_cast<ILOffsetToItemCache*>(m_methodCache);
    }
    else
    {
        // Otherwise, it does have generic info, so find the right cache.
        // First ensure that the top-level generics-context --> cache cache exists.
        GenericContextToInnerCache* outerCache = reinterpret_cast<GenericContextToInnerCache*>(m_methodCache);
        if (outerCache == NULL)
        {
            if (alloc)
            {
                // Lazy init via compare-exchange.
                outerCache = new GenericContextToInnerCache();
                void* prev = InterlockedCompareExchangeT<void*>(&m_methodCache, outerCache, NULL);
                if (prev != NULL)
                {
                    delete outerCache;
                    outerCache = reinterpret_cast<GenericContextToInnerCache*>(prev);
                }
            }
            else
            {
                return NULL;
            }
        }
        // Does the outerCache already have an entry for this instantiation?
        ILOffsetToItemCache* innerCache = NULL;
        if (!outerCache->GetItem(size_t(context), innerCache) && alloc)
        {
            innerCache = new ILOffsetToItemCache();
            outerCache->AddItem(size_t(context), innerCache);
        }
        return innerCache;
    }
}

void Interpreter::CacheCallInfo(unsigned iloffset, CallSiteCacheData* callInfo)
{
    CrstHolder ch(&s_methodCacheLock);

    ILOffsetToItemCache* cache = GetThisExecCache(true);
    // Insert, but if the item is already there, delete "mdcs" (which would have been owned
    // by the cache).
    // (Duplicate entries can happen because of recursive calls -- F makes a recursive call to F, and when it
    // returns wants to cache it, but the recursive call makes a furher recursive call, and caches that, so the
    // first call finds the iloffset already occupied.)
    if (!cache->AddItem(iloffset, CachedItem(callInfo)))
    {
        delete callInfo;
    }
}

CallSiteCacheData* Interpreter::GetCachedCallInfo(unsigned iloffset)
{
    CrstHolder ch(&s_methodCacheLock);

    ILOffsetToItemCache* cache = GetThisExecCache(false);
    if (cache == NULL) return NULL;
    // Otherwise...
    CachedItem item;
    if (cache->GetItem(iloffset, item))
    {
        _ASSERTE_MSG(item.m_tag == CIK_CallSite, "Wrong cached item tag.");
        return item.m_value.m_callSiteInfo;
    }
    else
    {
        return NULL;
    }
}

void Interpreter::CacheInstanceField(unsigned iloffset, FieldDesc* fld)
{
    CrstHolder ch(&s_methodCacheLock);

    ILOffsetToItemCache* cache = GetThisExecCache(true);
    cache->AddItem(iloffset, CachedItem(fld));
}

FieldDesc* Interpreter::GetCachedInstanceField(unsigned iloffset)
{
    CrstHolder ch(&s_methodCacheLock);

    ILOffsetToItemCache* cache = GetThisExecCache(false);
    if (cache == NULL) return NULL;
    // Otherwise...
    CachedItem item;
    if (cache->GetItem(iloffset, item))
    {
        _ASSERTE_MSG(item.m_tag == CIK_InstanceField, "Wrong cached item tag.");
        return item.m_value.m_instanceField;
    }
    else
    {
        return NULL;
    }
}

void Interpreter::CacheStaticField(unsigned iloffset, StaticFieldCacheEntry* pEntry)
{
    CrstHolder ch(&s_methodCacheLock);

    ILOffsetToItemCache* cache = GetThisExecCache(true);
    // If (say) a concurrent thread has beaten us to this, delete the entry (which otherwise would have
    // been owned by the cache).
    if (!cache->AddItem(iloffset, CachedItem(pEntry)))
    {
        delete pEntry;
    }
}

StaticFieldCacheEntry* Interpreter::GetCachedStaticField(unsigned iloffset)
{
    CrstHolder ch(&s_methodCacheLock);

    ILOffsetToItemCache* cache = GetThisExecCache(false);
    if (cache == NULL)
        return NULL;

    // Otherwise...
    CachedItem item;
    if (cache->GetItem(iloffset, item))
    {
        _ASSERTE_MSG(item.m_tag == CIK_StaticField, "Wrong cached item tag.");
        return item.m_value.m_staticFieldAddr;
    }
    else
    {
        return NULL;
    }
}


void Interpreter::CacheClassHandle(unsigned iloffset, CORINFO_CLASS_HANDLE clsHnd)
{
    CrstHolder ch(&s_methodCacheLock);

    ILOffsetToItemCache* cache = GetThisExecCache(true);
    cache->AddItem(iloffset, CachedItem(clsHnd));
}

CORINFO_CLASS_HANDLE Interpreter::GetCachedClassHandle(unsigned iloffset)
{
    CrstHolder ch(&s_methodCacheLock);

    ILOffsetToItemCache* cache = GetThisExecCache(false);
    if (cache == NULL)
        return NULL;

    // Otherwise...
    CachedItem item;
    if (cache->GetItem(iloffset, item))
    {
        _ASSERTE_MSG(item.m_tag == CIK_ClassHandle, "Wrong cached item tag.");
        return item.m_value.m_clsHnd;
    }
    else
    {
        return NULL;
    }
}
#endif // DACCESS_COMPILE

// Statics

// Theses are not debug-only.
ConfigMethodSet Interpreter::s_InterpretMeths;
ConfigMethodSet Interpreter::s_InterpretMethsExclude;
ConfigDWORD Interpreter::s_InterpretMethHashMin;
ConfigDWORD Interpreter::s_InterpretMethHashMax;
ConfigDWORD Interpreter::s_InterpreterJITThreshold;
ConfigDWORD Interpreter::s_InterpreterDoLoopMethodsFlag;
ConfigDWORD Interpreter::s_InterpreterUseCachingFlag;
ConfigDWORD Interpreter::s_InterpreterLooseRulesFlag;

bool Interpreter::s_InterpreterDoLoopMethods;
bool Interpreter::s_InterpreterUseCaching;
bool Interpreter::s_InterpreterLooseRules;

CrstExplicitInit Interpreter::s_methodCacheLock;
CrstExplicitInit Interpreter::s_interpStubToMDMapLock;

// The static variables below are debug-only.
#if INTERP_TRACING
LONG Interpreter::s_totalInvocations = 0;
LONG Interpreter::s_totalInterpCalls = 0;
LONG Interpreter::s_totalInterpCallsToGetters = 0;
LONG Interpreter::s_totalInterpCallsToDeadSimpleGetters = 0;
LONG Interpreter::s_totalInterpCallsToDeadSimpleGettersShortCircuited = 0;
LONG Interpreter::s_totalInterpCallsToSetters = 0;
LONG Interpreter::s_totalInterpCallsToIntrinsics = 0;
LONG Interpreter::s_totalInterpCallsToIntrinsicsUnhandled = 0;

LONG Interpreter::s_tokenResolutionOpportunities[RTK_Count] = {0, };
LONG Interpreter::s_tokenResolutionCalls[RTK_Count] = {0, };
const char* Interpreter::s_tokenResolutionKindNames[RTK_Count] =
{
    "Undefined",
    "Constrained",
    "NewObj",
    "NewArr",
    "LdToken",
    "LdFtn",
    "LdVirtFtn",
    "SFldAddr",
    "LdElem",
    "Call",
    "LdObj",
    "StObj",
    "CpObj",
    "InitObj",
    "IsInst",
    "CastClass",
    "MkRefAny",
    "RefAnyVal",
    "Sizeof",
    "StElem",
    "Box",
    "Unbox",
    "UnboxAny",
    "LdFld",
    "LdFldA",
    "StFld",
    "FindClass",
    "Exception",
};

FILE*       Interpreter::s_InterpreterLogFile = NULL;
ConfigDWORD Interpreter::s_DumpInterpreterStubsFlag;
ConfigDWORD Interpreter::s_TraceInterpreterEntriesFlag;
ConfigDWORD Interpreter::s_TraceInterpreterILFlag;
ConfigDWORD Interpreter::s_TraceInterpreterOstackFlag;
ConfigDWORD Interpreter::s_TraceInterpreterVerboseFlag;
ConfigDWORD Interpreter::s_TraceInterpreterJITTransitionFlag;
ConfigDWORD Interpreter::s_InterpreterStubMin;
ConfigDWORD Interpreter::s_InterpreterStubMax;
#endif // INTERP_TRACING

#if INTERP_ILINSTR_PROFILE
unsigned short Interpreter::s_ILInstrCategories[512];

int        Interpreter::s_ILInstrExecs[256] = {0, };
int        Interpreter::s_ILInstrExecsByCategory[512] = {0, };
int        Interpreter::s_ILInstr2ByteExecs[Interpreter::CountIlInstr2Byte] = {0, };
#if INTERP_ILCYCLE_PROFILE
unsigned __int64       Interpreter::s_ILInstrCycles[512] = { 0, };
unsigned __int64       Interpreter::s_ILInstrCyclesByCategory[512] = { 0, };
// XXX
unsigned __int64       Interpreter::s_callCycles = 0;
unsigned               Interpreter::s_calls = 0;

void Interpreter::UpdateCycleCount()
{
    unsigned __int64 endCycles;
    bool b = CycleTimer::GetThreadCyclesS(&endCycles); _ASSERTE(b);
    if (m_instr != CEE_COUNT)
    {
        unsigned __int64 delta = (endCycles - m_startCycles);
        if (m_exemptCycles > 0)
        {
            delta = delta - m_exemptCycles;
            m_exemptCycles = 0;
        }
        CycleTimer::InterlockedAddU64(&s_ILInstrCycles[m_instr], delta);
    }
    // In any case, set the instruction to the current one, and record it's start time.
    m_instr = (*m_ILCodePtr);
    if (m_instr == CEE_PREFIX1) {
        m_instr = *(m_ILCodePtr + 1) + 0x100;
    }
    b = CycleTimer::GetThreadCyclesS(&m_startCycles); _ASSERTE(b);
}

#endif // INTERP_ILCYCLE_PROFILE
#endif // INTERP_ILINSTR_PROFILE

#ifdef _DEBUG
InterpreterMethodInfo** Interpreter::s_interpMethInfos = NULL;
unsigned Interpreter::s_interpMethInfosAllocSize = 0;
unsigned Interpreter::s_interpMethInfosCount = 0;

bool Interpreter::TOSIsPtr()
{
    if (m_curStackHt == 0)
        return false;

    return CorInfoTypeIsPointer(OpStackTypeGet(m_curStackHt - 1).ToCorInfoType());
}
#endif // DEBUG

ConfigDWORD Interpreter::s_PrintPostMortemFlag;

// InterpreterCache.
template<typename Key, typename Val>
InterpreterCache<Key,Val>::InterpreterCache() : m_pairs(NULL), m_allocSize(0), m_count(0)
{
#ifdef _DEBUG
    AddAllocBytes(sizeof(*this));
#endif
}

#ifdef _DEBUG
// static
static unsigned InterpreterCacheAllocBytes = 0;
const unsigned KBYTE = 1024;
const unsigned MBYTE = KBYTE*KBYTE;
const unsigned InterpreterCacheAllocBytesIncrement = 16*KBYTE;
static unsigned InterpreterCacheAllocBytesNextTarget = InterpreterCacheAllocBytesIncrement;

template<typename Key, typename Val>
void InterpreterCache<Key,Val>::AddAllocBytes(unsigned bytes)
{
    // Reinstate this code if you want to track bytes attributable to caching.
#if 0
    InterpreterCacheAllocBytes += bytes;
    if (InterpreterCacheAllocBytes > InterpreterCacheAllocBytesNextTarget)
    {
        printf("Total cache alloc = %d bytes.\n", InterpreterCacheAllocBytes);
        fflush(stdout);
        InterpreterCacheAllocBytesNextTarget += InterpreterCacheAllocBytesIncrement;
    }
#endif
}
#endif // _DEBUG

template<typename Key, typename Val>
void InterpreterCache<Key,Val>::EnsureCanInsert()
{
    if (m_count < m_allocSize)
        return;

    // Otherwise, must make room.
    if (m_allocSize == 0)
    {
        _ASSERTE(m_count == 0);
        m_pairs = new KeyValPair[InitSize];
        m_allocSize = InitSize;
#ifdef _DEBUG
        AddAllocBytes(m_allocSize * sizeof(KeyValPair));
#endif
    }
    else
    {
        unsigned short newSize = min(m_allocSize * 2, USHRT_MAX);

        KeyValPair* newPairs = new KeyValPair[newSize];
        memcpy(newPairs, m_pairs, m_count * sizeof(KeyValPair));
        delete[] m_pairs;
        m_pairs = newPairs;
#ifdef _DEBUG
        AddAllocBytes((newSize - m_allocSize) * sizeof(KeyValPair));
#endif
        m_allocSize = newSize;
    }
}

template<typename Key, typename Val>
bool InterpreterCache<Key,Val>::AddItem(Key key, Val val)
{
    EnsureCanInsert();
    // Find the index to insert before.
    unsigned firstGreaterOrEqual = 0;
    for (; firstGreaterOrEqual < m_count; firstGreaterOrEqual++)
    {
        if (m_pairs[firstGreaterOrEqual].m_key >= key)
            break;
    }
    if (firstGreaterOrEqual < m_count && m_pairs[firstGreaterOrEqual].m_key == key)
    {
        _ASSERTE(m_pairs[firstGreaterOrEqual].m_val == val);
        return false;
    }
    // Move everything starting at firstGreater up one index (if necessary)
    if (m_count > 0)
    {
        for (unsigned k = m_count-1; k >= firstGreaterOrEqual; k--)
        {
            m_pairs[k + 1] = m_pairs[k];
            if (k == 0)
                break;
        }
    }
    // Now we can insert the new element.
    m_pairs[firstGreaterOrEqual].m_key = key;
    m_pairs[firstGreaterOrEqual].m_val = val;
    m_count++;
    return true;
}

template<typename Key, typename Val>
bool InterpreterCache<Key,Val>::GetItem(Key key, Val& v)
{
    unsigned lo = 0;
    unsigned hi = m_count;
    // Invariant: we've determined that the pair for "iloffset", if present,
    // is in the index interval [lo, hi).
    while (lo < hi)
    {
        unsigned mid = (hi + lo)/2;
        Key midKey = m_pairs[mid].m_key;
        if (key == midKey)
        {
            v = m_pairs[mid].m_val;
            return true;
        }
        else if (key < midKey)
        {
            hi = mid;
        }
        else
        {
            _ASSERTE(key > midKey);
            lo = mid + 1;
        }
    }
    // If we reach here without returning, it's not here.
    return false;
}

// TODO: add a header comment here describing this function.
void Interpreter::OpStackNormalize()
{
    size_t largeStructStackOffset = 0;
    // Yes, I've written a quadratic algorithm here.  I don't think it will matter in practice.
    for (unsigned i = 0; i < m_curStackHt; i++)
    {
        InterpreterType tp = OpStackTypeGet(i);
        if (tp.IsLargeStruct(&m_interpCeeInfo))
        {
            size_t sz = tp.Size(&m_interpCeeInfo);

            void* addr = OpStackGet<void*>(i);
            if (IsInLargeStructLocalArea(addr))
            {
                // We're going to allocate space at the top for the new value, then copy everything above the current slot
                // up into that new space, then copy the value into the vacated space.
                // How much will we have to copy?
                size_t toCopy = m_largeStructOperandStackHt - largeStructStackOffset;

                // Allocate space for the new value.
                void* dummy = LargeStructOperandStackPush(sz);

                // Remember where we're going to write to.
                BYTE* fromAddr = m_largeStructOperandStack + largeStructStackOffset;
                BYTE* toAddr = fromAddr + sz;
                memcpy(toAddr, fromAddr, toCopy);

                // Now copy the local variable value.
                memcpy(fromAddr, addr, sz);
                OpStackSet<void*>(i, fromAddr);
            }
            largeStructStackOffset += sz;
        }
    }
    // When we've normalized the stack, it contains no pointers to locals.
    m_orOfPushedInterpreterTypes = 0;
}

#if INTERP_TRACING

// Code copied from eeinterface.cpp in "compiler".  Should be common...

static const char* CorInfoTypeNames[] = {
    "undef",
    "void",
    "bool",
    "char",
    "byte",
    "ubyte",
    "short",
    "ushort",
    "int",
    "uint",
    "long",
    "ulong",
    "nativeint",
    "nativeuint",
    "float",
    "double",
    "string",
    "ptr",
    "byref",
    "valueclass",
    "class",
    "refany",
    "var"
};

const char* eeGetMethodFullName(CEEInfo* info, CORINFO_METHOD_HANDLE hnd, const char** clsName)
{
    CONTRACTL {
        THROWS;
        GC_TRIGGERS;
        MODE_ANY;
    } CONTRACTL_END;

    GCX_PREEMP();

    const char* returnType = NULL;

    const char* className;
    const char* methodName = info->getMethodName(hnd, &className);
    if (clsName != NULL)
    {
        *clsName = className;
    }

    size_t length = 0;
    unsigned i;

    /* Generating the full signature is a two-pass process. First we have to walk
       the components in order to assess the total size, then we allocate the buffer
       and copy the elements into it.
     */

    /* Right now there is a race-condition in the EE, className can be NULL */

    /* initialize length with length of className and '.' */

    if (className)
    {
        length = strlen(className) + 1;
    }
    else
    {
        _ASSERTE(strlen("<NULL>.") == 7);
        length = 7;
    }

    /* add length of methodName and opening bracket */
    length += strlen(methodName) + 1;

    CORINFO_SIG_INFO sig;
    info->getMethodSig(hnd, &sig, nullptr);
    CORINFO_ARG_LIST_HANDLE argLst = sig.args;

    CORINFO_CLASS_HANDLE dummyCls;
    for (i = 0; i < sig.numArgs; i++)
    {
        CorInfoType type = strip(info->getArgType(&sig, argLst, &dummyCls));

        length += strlen(CorInfoTypeNames[type]);
        argLst = info->getArgNext(argLst);
    }

    /* add ',' if there is more than one argument */

    if (sig.numArgs > 1)
    {
        length += (sig.numArgs - 1);
    }

    if (sig.retType != CORINFO_TYPE_VOID)
    {
        returnType = CorInfoTypeNames[sig.retType];
        length += strlen(returnType) + 1; // don't forget the delimiter ':'
    }

    /* add closing bracket and null terminator */

    length += 2;

    char* retName = new char[length];

    /* Now generate the full signature string in the allocated buffer */

    if (className)
    {
        strcpy_s(retName, length, className);
        strcat_s(retName, length, ":");
    }
    else
    {
        strcpy_s(retName, length, "<NULL>.");
    }

    strcat_s(retName, length, methodName);

    // append the signature
    strcat_s(retName, length, "(");

    argLst = sig.args;

    for (i = 0; i < sig.numArgs; i++)
    {
        CorInfoType type = strip(info->getArgType(&sig, argLst, &dummyCls));
        strcat_s(retName, length, CorInfoTypeNames[type]);

        argLst = info->getArgNext(argLst);
        if (i + 1 < sig.numArgs)
        {
            strcat_s(retName, length, ",");
        }
    }

    strcat_s(retName, length, ")");

    if (returnType)
    {
        strcat_s(retName, length, ":");
        strcat_s(retName, length, returnType);
    }

    _ASSERTE(strlen(retName) == length - 1);

    return(retName);
}

const char* Interpreter::eeGetMethodFullName(CORINFO_METHOD_HANDLE hnd)
{
    return ::eeGetMethodFullName(&m_interpCeeInfo, hnd);
}

const char* ILOpNames[256*2];
bool ILOpNamesInited = false;

void InitILOpNames()
{
    if (!ILOpNamesInited)
    {
        // Initialize the array.
#define OPDEF(c,s,pop,push,args,type,l,s1,s2,ctrl) if (s1 == 0xfe || s1 == 0xff) { int ind ((unsigned(s1) << 8) + unsigned(s2)); ind -= 0xfe00; ILOpNames[ind] = s; }
#include "opcode.def"
#undef OPDEF
        ILOpNamesInited = true;
    }
};
const char* Interpreter::ILOp(BYTE* m_ILCodePtr)
{
    InitILOpNames();
    BYTE b = *m_ILCodePtr;
    if (b == 0xfe)
    {
        return ILOpNames[*(m_ILCodePtr + 1)];
    }
    else
    {
        return ILOpNames[(0x1 << 8) + b];
    }
}
const char* Interpreter::ILOp1Byte(unsigned short ilInstrVal)
{
    InitILOpNames();
    return ILOpNames[(0x1 << 8) + ilInstrVal];
}
const char* Interpreter::ILOp2Byte(unsigned short ilInstrVal)
{
    InitILOpNames();
    return ILOpNames[ilInstrVal];
}

void Interpreter::PrintOStack()
{
    if (m_curStackHt == 0)
    {
        fprintf(GetLogFile(), "      <empty>\n");
    }
    else
    {
        for (unsigned k = 0; k < m_curStackHt; k++)
        {
            CorInfoType cit = OpStackTypeGet(k).ToCorInfoType();
            _ASSERTE(IsStackNormalType(cit));
            fprintf(GetLogFile(), "      %4d: %10s: ", k, CorInfoTypeNames[cit]);
            PrintOStackValue(k);
            fprintf(GetLogFile(), "\n");
        }
    }
    fflush(GetLogFile());
}

void Interpreter::PrintOStackValue(unsigned index)
{
    _ASSERTE_MSG(index < m_curStackHt, "precondition");
    InterpreterType it = OpStackTypeGet(index);
    if (it.IsLargeStruct(&m_interpCeeInfo))
    {
        PrintValue(it, OpStackGet<BYTE*>(index));
    }
    else
    {
        PrintValue(it, reinterpret_cast<BYTE*>(OpStackGetAddr(index, it.Size(&m_interpCeeInfo))));
    }
}

void Interpreter::PrintLocals()
{
    if (m_methInfo->m_numLocals == 0)
    {
        fprintf(GetLogFile(), "      <no locals>\n");
    }
    else
    {
        for (unsigned i = 0; i < m_methInfo->m_numLocals; i++)
        {
            InterpreterType it = m_methInfo->m_localDescs[i].m_type;
            CorInfoType cit = it.ToCorInfoType();
            void* localPtr = NULL;
            if (it.IsLargeStruct(&m_interpCeeInfo))
            {
                void* structPtr = ArgSlotEndiannessFixup(reinterpret_cast<ARG_SLOT*>(FixedSizeLocalSlot(i)), sizeof(void**));
                localPtr = *reinterpret_cast<void**>(structPtr);
            }
            else
            {
                localPtr = ArgSlotEndiannessFixup(reinterpret_cast<ARG_SLOT*>(FixedSizeLocalSlot(i)), it.Size(&m_interpCeeInfo));
            }
            fprintf(GetLogFile(), "      loc%-4d: %10s: ", i, CorInfoTypeNames[cit]);
            PrintValue(it, reinterpret_cast<BYTE*>(localPtr));
            fprintf(GetLogFile(), "\n");
        }
    }
    fflush(GetLogFile());
}

void Interpreter::PrintArgs()
{
    for (unsigned k = 0; k < m_methInfo->m_numArgs; k++)
    {
        CorInfoType cit = GetArgType(k).ToCorInfoType();
        fprintf(GetLogFile(), "      %4d: %10s: ", k, CorInfoTypeNames[cit]);
        PrintArgValue(k);
        fprintf(GetLogFile(), "\n");
    }
    fprintf(GetLogFile(), "\n");
    fflush(GetLogFile());
}

void Interpreter::PrintArgValue(unsigned argNum)
{
    _ASSERTE_MSG(argNum < m_methInfo->m_numArgs, "precondition");
    InterpreterType it = GetArgType(argNum);
    PrintValue(it, GetArgAddr(argNum));
}

// Note that this is used to print non-stack-normal values, so
// it must handle all cases.
void Interpreter::PrintValue(InterpreterType it, BYTE* valAddr)
{
    switch (it.ToCorInfoType())
    {
    case CORINFO_TYPE_BOOL:
        fprintf(GetLogFile(), "%s", ((*reinterpret_cast<INT8*>(valAddr)) ? "true" : "false"));
        break;
    case CORINFO_TYPE_BYTE:
        fprintf(GetLogFile(), "%d", *reinterpret_cast<INT8*>(valAddr));
        break;
    case CORINFO_TYPE_UBYTE:
        fprintf(GetLogFile(), "%u", *reinterpret_cast<UINT8*>(valAddr));
        break;

    case CORINFO_TYPE_SHORT:
        fprintf(GetLogFile(), "%d", *reinterpret_cast<INT16*>(valAddr));
        break;
    case CORINFO_TYPE_USHORT: case CORINFO_TYPE_CHAR:
        fprintf(GetLogFile(), "%u", *reinterpret_cast<UINT16*>(valAddr));
        break;

    case CORINFO_TYPE_INT:
        fprintf(GetLogFile(), "%d", *reinterpret_cast<INT32*>(valAddr));
        break;
    case CORINFO_TYPE_UINT:
        fprintf(GetLogFile(), "%u", *reinterpret_cast<UINT32*>(valAddr));
        break;

    case CORINFO_TYPE_NATIVEINT:
        {
            INT64 val = static_cast<INT64>(*reinterpret_cast<NativeInt*>(valAddr));
            fprintf(GetLogFile(), "%lld (= 0x%llx)", val, val);
        }
        break;
    case CORINFO_TYPE_NATIVEUINT:
        {
            UINT64 val = static_cast<UINT64>(*reinterpret_cast<NativeUInt*>(valAddr));
            fprintf(GetLogFile(), "%lld (= 0x%llx)", val, val);
        }
        break;

    case CORINFO_TYPE_BYREF:
        fprintf(GetLogFile(), "0x%p", *reinterpret_cast<void**>(valAddr));
        break;

    case CORINFO_TYPE_LONG:
        {
            INT64 val = *reinterpret_cast<INT64*>(valAddr);
            fprintf(GetLogFile(), "%lld (= 0x%llx)", val, val);
        }
        break;
    case CORINFO_TYPE_ULONG:
        fprintf(GetLogFile(), "%lld", *reinterpret_cast<UINT64*>(valAddr));
        break;

    case CORINFO_TYPE_CLASS:
        {
            Object* obj = *reinterpret_cast<Object**>(valAddr);
            if (obj == NULL)
            {
                fprintf(GetLogFile(), "null");
            }
            else
            {
#ifdef _DEBUG
                fprintf(GetLogFile(), "0x%p (%s) [", obj, obj->GetMethodTable()->GetDebugClassName());
#else
                fprintf(GetLogFile(), "0x%p (MT=0x%p) [", obj, obj->GetMethodTable());
#endif
                unsigned sz = obj->GetMethodTable()->GetBaseSize();
                BYTE* objBytes = reinterpret_cast<BYTE*>(obj);
                for (unsigned i = 0; i < sz; i++)
                {
                    if (i > 0)
                    {
                        fprintf(GetLogFile(), " ");
                    }
                    fprintf(GetLogFile(), "0x%x", objBytes[i]);
                }
                fprintf(GetLogFile(), "]");
            }
        }
        break;
    case CORINFO_TYPE_VALUECLASS:
        {
            GCX_PREEMP();
            fprintf(GetLogFile(), "<%s>: [", m_interpCeeInfo.getClassName(it.ToClassHandle()));
            unsigned sz = getClassSize(it.ToClassHandle());
            for (unsigned i = 0; i < sz; i++)
            {
                if (i > 0)
                {
                    fprintf(GetLogFile(), " ");
                }
                fprintf(GetLogFile(), "0x%02x", valAddr[i]);
            }
            fprintf(GetLogFile(), "]");
        }
        break;
    case CORINFO_TYPE_REFANY:
        fprintf(GetLogFile(), "<refany>");
        break;
    case CORINFO_TYPE_FLOAT:
        fprintf(GetLogFile(), "%f", *reinterpret_cast<float*>(valAddr));
        break;
    case CORINFO_TYPE_DOUBLE:
        fprintf(GetLogFile(), "%g", *reinterpret_cast<double*>(valAddr));
        break;
    case CORINFO_TYPE_PTR:
        fprintf(GetLogFile(), "0x%p", *reinterpret_cast<void**>(valAddr));
        break;
    default:
        _ASSERTE_MSG(false, "Unknown type in PrintValue.");
        break;
    }
}
#endif // INTERP_TRACING

#ifdef _DEBUG
void Interpreter::AddInterpMethInfo(InterpreterMethodInfo* methInfo)
{
    typedef InterpreterMethodInfo* InterpreterMethodInfoPtr;
    // TODO: this requires synchronization.
    const unsigned InitSize = 128;
    if (s_interpMethInfos == NULL)
    {
        s_interpMethInfos = new InterpreterMethodInfoPtr[InitSize];
        s_interpMethInfosAllocSize = InitSize;
    }
    if (s_interpMethInfosAllocSize == s_interpMethInfosCount)
    {
        unsigned newSize = s_interpMethInfosAllocSize * 2;
        InterpreterMethodInfoPtr* tmp = new InterpreterMethodInfoPtr[newSize];
        memcpy(tmp, s_interpMethInfos, s_interpMethInfosCount * sizeof(InterpreterMethodInfoPtr));
        delete[] s_interpMethInfos;
        s_interpMethInfos = tmp;
        s_interpMethInfosAllocSize = newSize;
    }
    s_interpMethInfos[s_interpMethInfosCount] = methInfo;
    s_interpMethInfosCount++;
}

int _cdecl Interpreter::CompareMethInfosByInvocations(const void* mi0in, const void* mi1in)
{
    const InterpreterMethodInfo* mi0 = *((const InterpreterMethodInfo**)mi0in);
    const InterpreterMethodInfo* mi1 = *((const InterpreterMethodInfo**)mi1in);
    if (mi0->m_invocations < mi1->m_invocations)
    {
        return -1;
    }
    else if (mi0->m_invocations == mi1->m_invocations)
    {
        return 0;
    }
    else
    {
        _ASSERTE(mi0->m_invocations > mi1->m_invocations);
        return 1;
    }
}

#if INTERP_PROFILE
int _cdecl Interpreter::CompareMethInfosByILInstrs(const void* mi0in, const void* mi1in)
{
    const InterpreterMethodInfo* mi0 = *((const InterpreterMethodInfo**)mi0in);
    const InterpreterMethodInfo* mi1 = *((const InterpreterMethodInfo**)mi1in);
    if (mi0->m_totIlInstructionsExeced < mi1->m_totIlInstructionsExeced) return 1;
    else if (mi0->m_totIlInstructionsExeced == mi1->m_totIlInstructionsExeced) return 0;
    else
    {
        _ASSERTE(mi0->m_totIlInstructionsExeced > mi1->m_totIlInstructionsExeced);
        return -1;
    }
}
#endif // INTERP_PROFILE
#endif // _DEBUG

const int MIL = 1000000;

// Leaving this disabled for now.
#if 0
unsigned __int64 ForceSigWalkCycles = 0;
#endif

void Interpreter::PrintPostMortemData()
{
    if (s_PrintPostMortemFlag.val(CLRConfig::INTERNAL_InterpreterPrintPostMortem) == 0)
        return;

    // Otherwise...

#if INTERP_TRACING
    // Let's print two things: the number of methods that are 0-10, or more, and
    // For each 10% of methods, cumulative % of invocations they represent.  By 1% for last 10%.

    // First one doesn't require any sorting.
    const unsigned HistoMax = 11;
    unsigned histo[HistoMax];
    unsigned numExecs[HistoMax];
    for (unsigned k = 0; k < HistoMax; k++)
    {
        histo[k] = 0; numExecs[k] = 0;
    }
    for (unsigned k = 0; k < s_interpMethInfosCount; k++)
    {
        unsigned invokes = s_interpMethInfos[k]->m_invocations;
        if (invokes > HistoMax - 1)
        {
            invokes = HistoMax - 1;
        }
        histo[invokes]++;
        numExecs[invokes] += s_interpMethInfos[k]->m_invocations;
    }

    fprintf(GetLogFile(), "Histogram of method executions:\n");
    fprintf(GetLogFile(), "   # of execs   |   # meths (%%)    |   cum %% | %% cum execs\n");
    fprintf(GetLogFile(), "   -------------------------------------------------------\n");
    float fTotMeths = float(s_interpMethInfosCount);
    float fTotExecs = float(s_totalInvocations);
    float numPct = 0.0f;
    float numExecPct = 0.0f;
    for (unsigned k = 0; k < HistoMax; k++)
    {
        fprintf(GetLogFile(), "   %10d", k);
        if (k == HistoMax)
        {
            fprintf(GetLogFile(), "+  ");
        }
        else
        {
            fprintf(GetLogFile(), "   ");
        }
        float pct = float(histo[k])*100.0f/fTotMeths;
        numPct += pct;
        float execPct = float(numExecs[k])*100.0f/fTotExecs;
        numExecPct += execPct;
        fprintf(GetLogFile(), "| %7d (%5.2f%%) | %6.2f%% | %6.2f%%\n", histo[k], pct, numPct, numExecPct);
    }

    // This sorts them in ascending order of number of invocations.
    qsort(&s_interpMethInfos[0], s_interpMethInfosCount, sizeof(InterpreterMethodInfo*), &CompareMethInfosByInvocations);

    fprintf(GetLogFile(), "\nFor methods sorted in ascending # of executions order, cumulative %% of executions:\n");
    if (s_totalInvocations > 0)
    {
        fprintf(GetLogFile(), "   %% of methods  | max execs | cum %% of execs\n");
        fprintf(GetLogFile(), "   ------------------------------------------\n");
        unsigned methNum = 0;
        unsigned nNumExecs = 0;
        float totExecsF = float(s_totalInvocations);
        for (unsigned k = 10; k < 100; k += 10)
        {
            unsigned targ = unsigned((float(k)/100.0f)*float(s_interpMethInfosCount));
            unsigned targLess1 = (targ > 0 ? targ - 1 : 0);
            while (methNum < targ)
            {
                nNumExecs += s_interpMethInfos[methNum]->m_invocations;
                methNum++;
            }
            float pctExecs = float(nNumExecs) * 100.0f / totExecsF;

            fprintf(GetLogFile(), "   %8d%%     | %9d |    %8.2f%%\n", k, s_interpMethInfos[targLess1]->m_invocations, pctExecs);

            if (k == 90)
            {
                k++;
                for (; k < 100; k++)
                {
                    unsigned targ = unsigned((float(k)/100.0f)*float(s_interpMethInfosCount));
                    while (methNum < targ)
                    {
                        nNumExecs += s_interpMethInfos[methNum]->m_invocations;
                        methNum++;
                    }
                    pctExecs = float(nNumExecs) * 100.0f / totExecsF;

                    fprintf(GetLogFile(), "     %8d%%   | %9d |    %8.2f%%\n", k, s_interpMethInfos[targLess1]->m_invocations, pctExecs);
                }

                // Now do 100%.
                targ = s_interpMethInfosCount;
                while (methNum < targ)
                {
                    nNumExecs += s_interpMethInfos[methNum]->m_invocations;
                    methNum++;
                }
                pctExecs = float(nNumExecs) * 100.0f / totExecsF;
                fprintf(GetLogFile(), "   %8d%%     | %9d |    %8.2f%%\n", k, s_interpMethInfos[targLess1]->m_invocations, pctExecs);
            }
        }
    }

    fprintf(GetLogFile(), "\nTotal number of calls from interpreted code: %d.\n", s_totalInterpCalls);
    fprintf(GetLogFile(), "    Also, %d are intrinsics; %d of these are not currently handled intrinsically.\n",
        s_totalInterpCallsToIntrinsics, s_totalInterpCallsToIntrinsicsUnhandled);
    fprintf(GetLogFile(), "    Of these, %d to potential property getters (%d of these dead simple), %d to setters.\n",
        s_totalInterpCallsToGetters, s_totalInterpCallsToDeadSimpleGetters, s_totalInterpCallsToSetters);
    fprintf(GetLogFile(), "    Of the dead simple getter calls, %d have been short-circuited.\n",
        s_totalInterpCallsToDeadSimpleGettersShortCircuited);

    fprintf(GetLogFile(), "\nToken resolutions by category:\n");
    fprintf(GetLogFile(), "Category     |  opportunities  |   calls   |      %%\n");
    fprintf(GetLogFile(), "---------------------------------------------------\n");
    for (unsigned i = RTK_Undefined; i < RTK_Count; i++)
    {
        float pct = 0.0;
        if (s_tokenResolutionOpportunities[i] > 0)
            pct = 100.0f * float(s_tokenResolutionCalls[i]) / float(s_tokenResolutionOpportunities[i]);
        fprintf(GetLogFile(), "%12s | %15d | %9d | %6.2f%%\n",
            s_tokenResolutionKindNames[i], s_tokenResolutionOpportunities[i], s_tokenResolutionCalls[i], pct);
    }

#if INTERP_PROFILE
    fprintf(GetLogFile(), "Information on num of execs:\n");

    UINT64 totILInstrs = 0;
    for (unsigned i = 0; i < s_interpMethInfosCount; i++) totILInstrs += s_interpMethInfos[i]->m_totIlInstructionsExeced;

    float totILInstrsF = float(totILInstrs);

    fprintf(GetLogFile(), "\nTotal instructions = %lld.\n", totILInstrs);
    fprintf(GetLogFile(), "\nTop <=10 methods by # of IL instructions executed.\n");
    fprintf(GetLogFile(), "%10s | %9s | %10s | %10s | %8s | %s\n", "tot execs", "# invokes", "code size", "ratio", "% of tot", "Method");
    fprintf(GetLogFile(), "----------------------------------------------------------------------------\n");

    qsort(&s_interpMethInfos[0], s_interpMethInfosCount, sizeof(InterpreterMethodInfo*), &CompareMethInfosByILInstrs);

    for (unsigned i = 0; i < min(10, s_interpMethInfosCount); i++)
    {
        unsigned ilCodeSize = unsigned(s_interpMethInfos[i]->m_ILCodeEnd - s_interpMethInfos[i]->m_ILCode);
        fprintf(GetLogFile(), "%10lld | %9d | %10d | %10.2f | %8.2f%% | %s:%s\n",
            s_interpMethInfos[i]->m_totIlInstructionsExeced,
            s_interpMethInfos[i]->m_invocations,
            ilCodeSize,
            float(s_interpMethInfos[i]->m_totIlInstructionsExeced) / float(ilCodeSize),
            float(s_interpMethInfos[i]->m_totIlInstructionsExeced) * 100.0f / totILInstrsF,
            s_interpMethInfos[i]->m_clsName,
            s_interpMethInfos[i]->m_methName);
    }
#endif // INTERP_PROFILE
#endif // _DEBUG

#if INTERP_ILINSTR_PROFILE
    fprintf(GetLogFile(), "\nIL instruction profiling:\n");
    // First, classify by categories.
    unsigned totInstrs = 0;
#if INTERP_ILCYCLE_PROFILE
    unsigned __int64 totCycles = 0;
    unsigned __int64 perMeasurementOverhead = CycleTimer::QueryOverhead();
#endif // INTERP_ILCYCLE_PROFILE
    for (unsigned i = 0; i < 256; i++)
    {
        s_ILInstrExecsByCategory[s_ILInstrCategories[i]] += s_ILInstrExecs[i];
        totInstrs += s_ILInstrExecs[i];
#if INTERP_ILCYCLE_PROFILE
        unsigned __int64 cycles = s_ILInstrCycles[i];
        if (cycles > s_ILInstrExecs[i] * perMeasurementOverhead) cycles -= s_ILInstrExecs[i] * perMeasurementOverhead;
        else cycles = 0;
        s_ILInstrCycles[i] = cycles;
        s_ILInstrCyclesByCategory[s_ILInstrCategories[i]] += cycles;
        totCycles += cycles;
#endif // INTERP_ILCYCLE_PROFILE
    }
    unsigned totInstrs2Byte = 0;
#if INTERP_ILCYCLE_PROFILE
    unsigned __int64 totCycles2Byte = 0;
#endif // INTERP_ILCYCLE_PROFILE
    for (unsigned i = 0; i < CountIlInstr2Byte; i++)
    {
        unsigned ind = 0x100 + i;
        s_ILInstrExecsByCategory[s_ILInstrCategories[ind]] += s_ILInstr2ByteExecs[i];
        totInstrs += s_ILInstr2ByteExecs[i];
        totInstrs2Byte +=  s_ILInstr2ByteExecs[i];
#if INTERP_ILCYCLE_PROFILE
        unsigned __int64 cycles = s_ILInstrCycles[ind];
        if (cycles > s_ILInstrExecs[ind] * perMeasurementOverhead) cycles -= s_ILInstrExecs[ind] * perMeasurementOverhead;
        else cycles = 0;
        s_ILInstrCycles[i] = cycles;
        s_ILInstrCyclesByCategory[s_ILInstrCategories[ind]] += cycles;
        totCycles += cycles;
        totCycles2Byte +=  cycles;
#endif // INTERP_ILCYCLE_PROFILE
    }

    // Now sort the categories by # of occurrences.

    InstrExecRecord ieps[256 + CountIlInstr2Byte];
    for (unsigned short i = 0; i < 256; i++)
    {
        ieps[i].m_instr = i; ieps[i].m_is2byte = false; ieps[i].m_execs = s_ILInstrExecs[i];
#if INTERP_ILCYCLE_PROFILE
        if (i == CEE_BREAK)
        {
            ieps[i].m_cycles = 0;
            continue; // Don't count these if they occur...
        }
        ieps[i].m_cycles = s_ILInstrCycles[i];
        _ASSERTE((ieps[i].m_execs != 0) || (ieps[i].m_cycles == 0)); // Cycles can be zero for non-zero execs because of measurement correction.
#endif // INTERP_ILCYCLE_PROFILE
    }
    for (unsigned short i = 0; i < CountIlInstr2Byte; i++)
    {
        int ind = 256 + i;
        ieps[ind].m_instr = i; ieps[ind].m_is2byte = true; ieps[ind].m_execs = s_ILInstr2ByteExecs[i];
#if INTERP_ILCYCLE_PROFILE
        ieps[ind].m_cycles = s_ILInstrCycles[ind];
        _ASSERTE((ieps[i].m_execs != 0) || (ieps[i].m_cycles == 0)); // Cycles can be zero for non-zero execs because of measurement correction.
#endif // INTERP_ILCYCLE_PROFILE
    }

    qsort(&ieps[0], 256 + CountIlInstr2Byte, sizeof(InstrExecRecord), &InstrExecRecord::Compare);

    fprintf(GetLogFile(), "\nInstructions (%d total, %d 1-byte):\n", totInstrs, totInstrs - totInstrs2Byte);
#if INTERP_ILCYCLE_PROFILE
    if (s_callCycles > s_calls * perMeasurementOverhead) s_callCycles -= s_calls * perMeasurementOverhead;
    else s_callCycles = 0;
    fprintf(GetLogFile(), "      MCycles (%lld total, %lld 1-byte, %lld calls (%d calls, %10.2f cyc/call):\n",
            totCycles/MIL, (totCycles - totCycles2Byte)/MIL, s_callCycles/MIL, s_calls, float(s_callCycles)/float(s_calls));
#if 0
    extern unsigned __int64 MetaSigCtor1Cycles;
    fprintf(GetLogFile(), "      MetaSig(MethodDesc, TypeHandle) ctor: %lld MCycles.\n",
        MetaSigCtor1Cycles/MIL);
    fprintf(GetLogFile(), "      ForceSigWalk: %lld MCycles.\n",
        ForceSigWalkCycles/MIL);
#endif
#endif // INTERP_ILCYCLE_PROFILE

    PrintILProfile(&ieps[0], totInstrs
#if INTERP_ILCYCLE_PROFILE
                   , totCycles
#endif // INTERP_ILCYCLE_PROFILE
                  );

    fprintf(GetLogFile(), "\nInstructions grouped by category: (%d total, %d 1-byte):\n", totInstrs, totInstrs - totInstrs2Byte);
#if INTERP_ILCYCLE_PROFILE
    fprintf(GetLogFile(), "                           MCycles (%lld total, %lld 1-byte):\n",
            totCycles/MIL, (totCycles - totCycles2Byte)/MIL);
#endif // INTERP_ILCYCLE_PROFILE
    for (unsigned short i = 0; i < 256 + CountIlInstr2Byte; i++)
    {
        if (i < 256)
        {
            ieps[i].m_instr = i; ieps[i].m_is2byte = false;
        }
        else
        {
            ieps[i].m_instr = i - 256; ieps[i].m_is2byte = true;
        }
        ieps[i].m_execs = s_ILInstrExecsByCategory[i];
#if INTERP_ILCYCLE_PROFILE
        ieps[i].m_cycles = s_ILInstrCyclesByCategory[i];
#endif // INTERP_ILCYCLE_PROFILE
    }
    qsort(&ieps[0], 256 + CountIlInstr2Byte, sizeof(InstrExecRecord), &InstrExecRecord::Compare);
    PrintILProfile(&ieps[0], totInstrs
#if INTERP_ILCYCLE_PROFILE
                   , totCycles
#endif // INTERP_ILCYCLE_PROFILE
                   );

#if 0
    // Early debugging code.
    fprintf(GetLogFile(), "\nInstructions grouped category mapping:\n", totInstrs, totInstrs - totInstrs2Byte);
    for (unsigned short i = 0; i < 256; i++)
    {
        unsigned short cat = s_ILInstrCategories[i];
        if (cat < 256) {
            fprintf(GetLogFile(), "Instr: %12s  ==>  %12s.\n", ILOp1Byte(i), ILOp1Byte(cat));
        } else {
            fprintf(GetLogFile(), "Instr: %12s  ==>  %12s.\n", ILOp1Byte(i), ILOp2Byte(cat - 256));
        }
    }
    for (unsigned short i = 0; i < CountIlInstr2Byte; i++)
    {
        unsigned ind = 256 + i;
        unsigned short cat = s_ILInstrCategories[ind];
        if (cat < 256) {
            fprintf(GetLogFile(), "Instr: %12s  ==>  %12s.\n", ILOp2Byte(i), ILOp1Byte(cat));
        } else {
            fprintf(GetLogFile(), "Instr: %12s  ==>  %12s.\n", ILOp2Byte(i), ILOp2Byte(cat - 256));
        }
    }
#endif
#endif // INTERP_ILINSTR_PROFILE
}

#if INTERP_ILINSTR_PROFILE

const int K = 1000;

// static
void Interpreter::PrintILProfile(Interpreter::InstrExecRecord *recs, unsigned int totInstrs
#if INTERP_ILCYCLE_PROFILE
                                 , unsigned __int64 totCycles
#endif // INTERP_ILCYCLE_PROFILE
                                 )
{
    float fTotInstrs = float(totInstrs);
    fprintf(GetLogFile(), "Instruction  |   execs   |       %% |   cum %%");
#if INTERP_ILCYCLE_PROFILE
    float fTotCycles = float(totCycles);
    fprintf(GetLogFile(), "|      KCycles |       %% |   cum %% |   cyc/inst\n");
    fprintf(GetLogFile(), "--------------------------------------------------"
            "-----------------------------------------\n");
#else
    fprintf(GetLogFile(), "\n-------------------------------------------\n");
#endif
    float numPct = 0.0f;
#if INTERP_ILCYCLE_PROFILE
    float numCyclePct = 0.0f;
#endif // INTERP_ILCYCLE_PROFILE
    for (unsigned i = 0; i < 256 + CountIlInstr2Byte; i++)
    {
        float pct = 0.0f;
        if (totInstrs > 0) pct = float(recs[i].m_execs) * 100.0f / fTotInstrs;
        numPct += pct;
        if (recs[i].m_execs > 0)
        {
            fprintf(GetLogFile(), "%12s | %9d | %6.2f%% | %6.2f%%",
            (recs[i].m_is2byte ? ILOp2Byte(recs[i].m_instr) : ILOp1Byte(recs[i].m_instr)), recs[i].m_execs,
            pct, numPct);
#if INTERP_ILCYCLE_PROFILE
            pct = 0.0f;
            if (totCycles > 0) pct = float(recs[i].m_cycles) * 100.0f / fTotCycles;
            numCyclePct += pct;
            float cyclesPerInst = float(recs[i].m_cycles) / float(recs[i].m_execs);
            fprintf(GetLogFile(), "| %12llu | %6.2f%% | %6.2f%% | %11.2f",
                    recs[i].m_cycles/K, pct, numCyclePct, cyclesPerInst);
#endif // INTERP_ILCYCLE_PROFILE
            fprintf(GetLogFile(), "\n");
        }
    }
}
#endif // INTERP_ILINSTR_PROFILE

#endif // FEATURE_INTERPRETER
