summaryrefslogtreecommitdiff
path: root/src/vm/interpreter.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/vm/interpreter.cpp')
-rw-r--r--src/vm/interpreter.cpp12253
1 files changed, 12253 insertions, 0 deletions
diff --git a/src/vm/interpreter.cpp b/src/vm/interpreter.cpp
new file mode 100644
index 0000000000..a540cff0b0
--- /dev/null
+++ b/src/vm/interpreter.cpp
@@ -0,0 +1,12253 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+//
+#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 "gc.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"
+#ifdef FEATURE_REMOTING
+#include "remoting.h"
+#endif
+
+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_jittedCode(0),
+ 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?)
+ assert(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 && CorInfoTypeIsFloatingPoint(comp->getHFAType(methInfo->args.retTypeClass)) && methInfo->args.getCallConv() != CORINFO_CALLCONV_VARARG)
+ {
+ hasRetBuff = false;
+ }
+#endif
+#if defined(_ARM_) || defined(_AMD64_)|| defined(_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_STUBS_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_STUBS_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 = reinterpret_cast<short>(ArgSlotEndianessFixup(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(_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
+ && CorInfoTypeIsFloatingPoint(comp->getHFAType(methInfo->args.retTypeClass))
+ && methInfo->args.getCallConv() != CORINFO_CALLCONV_VARARG;
+#endif // defined(_ARM_)
+
+ if (GetFlag<Flag_hasRetBuffArg>()
+#if defined(_ARM_)
+ // On ARM, for direct calls we always treat HFA return types as having ret buffs.
+ || hasHFARetType
+#endif // defined(_ARM_)
+ )
+ {
+ directRetBuffOffset = reinterpret_cast<short>(ArgSlotEndianessFixup(directOffset, sizeof(void*)));
+ directOffset++;
+ }
+#if defined(_AMD64_)
+ if (GetFlag<Flag_isVarArg>())
+ {
+ directVarArgOffset = reinterpret_cast<short>(ArgSlotEndianessFixup(directOffset, sizeof(void*)));
+ directOffset++;
+ }
+ if (GetFlag<Flag_hasGenericsContextArg>())
+ {
+ directTypeParamOffset = reinterpret_cast<short>(ArgSlotEndianessFixup(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 = reinterpret_cast<short>(ArgSlotEndianessFixup(directOffset, sizeof(void*)));
+ }
+ else
+ {
+ m_argDescs[k].m_directOffset = reinterpret_cast<short>(ArgSlotEndianessFixup(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 CORINFO_CALLCONV_C:
+ NYI_INTERP("InterpreterMethodInfo::InitArgInfo -- CORINFO_CALLCONV_C");
+ break;
+
+ case CORINFO_CALLCONV_STDCALL:
+ NYI_INTERP("InterpreterMethodInfo::InitArgInfo -- CORINFO_CALLCONV_STDCALL");
+ break;
+
+ case CORINFO_CALLCONV_THISCALL:
+ NYI_INTERP("InterpreterMethodInfo::InitArgInfo -- CORINFO_CALLCONV_THISCALL");
+ break;
+
+ case CORINFO_CALLCONV_FASTCALL:
+ NYI_INTERP("InterpreterMethodInfo::InitArgInfo -- CORINFO_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_NATIVEVARARG:
+ NYI_INTERP("InterpreterMethodInfo::InitArgInfo -- CORINFO_CALLCONV_NATIVEVARARG");
+ break;
+
+ default:
+ _ASSERTE_ALL_BUILDS(__FILE__, 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(_AMD64_)
+ assert(!noReg);
+ assert(!twoSlotAlign);
+ AddArgAmd64(canonIndex, numSlots, /*isFloatingType*/false);
+#else // !_AMD64_
+#if defined(_X86_) || defined(_ARM64_)
+ assert(!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(_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(_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(_ARM_) || defined(_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);
+#endif
+ offset *= static_cast<short>(sizeof(void*));
+ assert(!offset.IsOverflow());
+ argOffsets[canonIndex] = offset.Value();
+#if defined(_ARM_) || defined(_ARM64_)
+ callerArgStackSlots += numSlots;
+#endif
+ }
+#endif // !_AMD64_
+}
+
+#if defined(_AMD64_)
+// 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))
+ {
+ assert(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*));
+ assert(!offset.IsOverflow());
+ argOffsets[canonIndex] = offset.Value();
+ callerArgStackSlots += 1;
+ }
+}
+#endif
+
+void Interpreter::ArgState::AddFPArg(unsigned canonIndex, unsigned short numSlots, bool twoSlotAlign)
+{
+#if defined(_AMD64_)
+ assert(!twoSlotAlign);
+ assert(numSlots == 1);
+ AddArgAmd64(canonIndex, numSlots, /*isFloatingType*/ true);
+#elif defined(_X86_)
+ assert(false); // Don't call this on x86; we pass all FP on the stack.
+#elif defined(_ARM_)
+ // We require "numSlots" alignment.
+ assert(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.
+ assert((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++;
+ assert(slot < 32); // Search succeeded.
+ assert(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(_ARM64_)
+
+ 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
+ {
+ assert(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(_X86_) || defined(_AMD64_)
+ // First do "short-circuiting" if the method has JITted code, and we couldn't find/update the call site:
+ // eax = &interpMethInfo
+ // eax = [eax + offsetof(m_jittedCode)]
+ // if (eax == zero) goto doInterpret:
+ // /*else*/ jmp [eax]
+ // doInterpret:
+ // 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 intepreted.
+ // 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 (_ARM_)
+ // TODO.
+#endif
+
+ // The IL stub case is hard. The portion of the interpreter stub that short-circuits
+ // to JITted code requires an extra "scratch" volatile register, not an argument register;
+ // in the IL stub case, it too is using such a register, as an extra argument, to hold the stub context.
+ // On x86 and ARM, there is only one such extra volatile register, and we've got a conundrum.
+ // The cases where this short-circuiting is important is when the address of an interpreter stub
+ // becomes "embedded" in other code. The examples I know of are VSD stubs and delegates.
+ // The first of these is not a problem for IL stubs -- methods invoked via p/Invoke (the ones that
+ // [I think!] use IL stubs) are static, and cannot be invoked via VSD stubs. Delegates, on the other
+ // remain a problem [I believe].
+ // For the short term, we'll ignore this issue, and never do short-circuiting for IL stubs.
+ // So interpreter stubs embedded in delegates will continue to interpret the IL stub, even after
+ // the stub has been JITted.
+ // The long-term intention is that when we JIT a method with an interpreter stub, we keep a mapping
+ // from interpreter stub address to corresponding native code address. If this mapping is non-empty,
+ // at GC time we would visit the locations in which interpreter stub addresses might be located, like
+ // VSD stubs and delegate objects, and update them to point to new addresses. This would be a necessary
+ // part of any scheme to GC interpreter stubs, and InterpreterMethodInfos.
+
+ // If we *really* wanted to make short-circuiting work for the IL stub case, we would have to
+ // (in the x86 case, which should be sufficiently illustrative):
+ // push eax
+ // <get the address of JITted code, if any, into eax>
+ // if there is JITted code in eax, we'd have to
+ // push 2 non-volatile registers, say esi and edi.
+ // copy the JITted code address from eax into esi.
+ // copy the method arguments (without the return address) down the stack, using edi
+ // as a scratch register.
+ // restore the original stub context value into eax from the stack
+ // call (not jmp) to the JITted code address in esi
+ // pop esi and edi from the stack.
+ // now the stack has original args, followed by original return address. Do a "ret"
+ // that returns to the return address, and also pops the original args from the stack.
+ // If we did this, we'd have to give this portion of the stub proper unwind info.
+ // Also, we'd have to adjust the rest of the stub to pop eax from the stack.
+
+ // 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(_X86_) || defined(_AMD64_)
+ // First we do "short-circuiting" if the method has JITted code.
+#if INTERP_ILSTUBS
+ if (!pMD->IsILStub()) // As discussed above, we don't do short-circuiting for IL stubs.
+#endif
+ {
+ // First read the m_jittedCode field.
+ sl.X86EmitRegLoad(kEAX, UINT_PTR(interpMethInfo));
+ sl.X86EmitOffsetModRM(0x8b, kEAX, kEAX, offsetof(InterpreterMethodInfo, m_jittedCode));
+ // If it is still zero, then go on to do the interpretation.
+ sl.X86EmitCmpRegImm32(kEAX, 0);
+ CodeLabel* doInterpret = sl.NewCodeLabel();
+ sl.X86EmitCondJump(doInterpret, X86CondCode::kJE);
+ // Otherwise...
+ sl.X86EmitJumpReg(kEAX); // tail call to JITted code.
+ sl.EmitLabel(doInterpret);
+ }
+#if defined(_X86_)
+ // Start regular interpretation
+ sl.X86EmitPushReg(kEBP);
+ sl.X86EmitMovRegReg(kEBP, static_cast<X86Reg>(kESP_Unsafe));
+#endif
+#elif defined(_ARM_)
+ // On ARM we use R12 as a "scratch" register -- callee-trashed, not used
+ // for arguments.
+ ThumbReg r11 = ThumbReg(11);
+ ThumbReg r12 = ThumbReg(12);
+
+#if INTERP_ILSTUBS
+ if (!pMD->IsILStub()) // As discussed above, we don't do short-circuiting for IL stubs.
+#endif
+ {
+ // But we also have to use r4, because ThumbEmitCondRegJump below requires a low register.
+ sl.ThumbEmitMovConstant(r12, UINT_PTR(interpMethInfo));
+ sl.ThumbEmitLoadRegIndirect(r12, r12, offsetof(InterpreterMethodInfo, m_jittedCode));
+ sl.ThumbEmitCmpImm(r12, 0); // Set condition codes.
+ // If r12 is zero, then go on to do the interpretation.
+ CodeLabel* doInterpret = sl.NewCodeLabel();
+ sl.ThumbEmitCondFlagJump(doInterpret, thumbCondEq.cond);
+ sl.ThumbEmitJumpRegister(r12); // If non-zero, tail call to JITted code.
+ sl.EmitLabel(doInterpret);
+ }
+
+ // Start regular interpretation
+
+#elif defined(_ARM64_)
+ // x8 through x15 are scratch registers on ARM64.
+ IntReg x8 = IntReg(8);
+ IntReg x9 = IntReg(9);
+
+#if INTERP_ILSTUBS
+ if (!pMD->IsILStub()) // As discussed above, we don't do short-circuiting for IL stubs.
+#endif
+ {
+ sl.EmitMovConstant(x8, UINT64(interpMethInfo));
+ sl.EmitLoadStoreRegImm(StubLinkerCPU::eLOAD, x9, x8, offsetof(InterpreterMethodInfo, m_jittedCode));
+ sl.EmitCmpImm(x9, 0);
+ CodeLabel* doInterpret = sl.NewCodeLabel();
+ sl.EmitCondFlagJump(doInterpret, CondEq.cond);
+ sl.EmitJumpRegister(x9);
+ sl.EmitLabel(doInterpret);
+ }
+
+ // Start regular interpretation
+#else
+#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())
+ {
+ assert(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)
+ {
+ assert(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(_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(_ARM64_)
+ // FP, LR
+ const int NumberOfFixedPushes = 2;
+#endif
+
+#if defined(FEATURE_HFA)
+#if defined(_ARM_) || defined(_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(_ARM64_)
+ unsigned cHFAVars = 0;
+#endif
+ if (info->args.retType == CORINFO_TYPE_VALUECLASS
+ && CorInfoTypeIsFloatingPoint(comp->getHFAType(info->args.retTypeClass))
+ && info->args.getCallConv() != CORINFO_CALLCONV_VARARG)
+ {
+ HFARetTypeSize = getClassSize(info->args.retTypeClass);
+#if defined(_ARM_)
+ // Round up to a double boundary;
+ HFARetTypeSize = ((HFARetTypeSize+ sizeof(double) - 1) / sizeof(double)) * sizeof(double);
+#elif defined(_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_TYPE_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(_ARM_) || defined(_AMD64_) || defined(_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 // _ARM_ || _AMD64_ || _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(__FILE__, 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(_X86_)
+ // Longs are always passed on the stack -- with no obvious alignment.
+ argState.AddArg(k, 2, /*noReg*/true);
+#elif defined(_ARM_)
+ // LONGS have 2-reg alignment; inc reg if necessary.
+ argState.AddArg(k, 2, /*noReg*/false, /*twoSlotAlign*/true);
+#elif defined(_AMD64_) || defined(_ARM64_)
+ argState.AddArg(k);
+#else
+#error unknown platform
+#endif
+ break;
+
+ // One float slot args:
+ case CORINFO_TYPE_FLOAT:
+#if defined(_X86_)
+ argState.AddArg(k, 1, /*noReg*/true);
+#elif defined(_ARM_)
+ argState.AddFPArg(k, 1, /*twoSlotAlign*/false);
+#elif defined(_AMD64_) || defined(_ARM64_)
+ argState.AddFPArg(k, 1, false);
+#else
+#error unknown platform
+#endif
+ break;
+
+ // Two float slot args
+ case CORINFO_TYPE_DOUBLE:
+#if defined(_X86_)
+ argState.AddArg(k, 2, /*noReg*/true);
+#elif defined(_ARM_)
+ argState.AddFPArg(k, 2, /*twoSlotAlign*/true);
+#elif defined(_AMD64_) || defined(_ARM64_)
+ 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(_X86_)
+ argState.AddArg(k, static_cast<short>(szSlots), /*noReg*/true);
+#elif defined(_AMD64_)
+ argState.AddArg(k, static_cast<short>(szSlots));
+#elif defined(_ARM_) || defined(_ARM64_)
+ CorInfoType hfaType = comp->getHFAType(vcTypeRet);
+ if (CorInfoTypeIsFloatingPoint(hfaType))
+ {
+ argState.AddFPArg(k, szSlots,
+#if defined(_ARM_)
+ /*twoSlotAlign*/ (hfaType == CORINFO_TYPE_DOUBLE)
+#elif defined(_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(_ARM_)
+ /*twoSlotAlign*/ (align == 8)
+#elif defined(_ARM64_)
+ /*twoSlotAlign*/ false
+#endif
+ );
+ }
+#else
+#error unknown platform
+#endif
+ }
+ break;
+
+
+ default:
+ _ASSERTE_MSG(false, "should not reach here, unknown arg type");
+ }
+ argPtr = comp->getArgNext(argPtr);
+ }
+
+#if defined(_X86_)
+ // Generics context comes last on _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(_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(_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(_AMD64_)
+ unsigned short stackArgBaseOffset = (argState.numRegArgs) * sizeof(void*);
+#else
+#error unsupported platform
+#endif
+
+#if defined(_ARM_)
+ WORD regArgMask = 0;
+#endif // defined(_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];
+
+ assert(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(_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
+ {
+ assert(regArgsFound == 2);
+ if (!jmpCall) { sl.X86EmitPushReg(kEDX); }
+ argState.argOffsets[k] = (argState.numRegArgs - regArgsFound)*sizeof(void*);
+ }
+#elif defined(_ARM_) || defined(_ARM64_)
+ argState.argOffsets[k] += intRegArgBaseOffset;
+#elif defined(_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*);
+#else
+#error unsupported platform
+#endif
+ }
+#if defined(_AMD64_)
+ 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 CORINFO_CALLCONV_C:
+ NYI_INTERP("GenerateInterpreterStub -- CORINFO_CALLCONV_C");
+ break;
+
+ case CORINFO_CALLCONV_STDCALL:
+ NYI_INTERP("GenerateInterpreterStub -- CORINFO_CALLCONV_STDCALL");
+ break;
+
+ case CORINFO_CALLCONV_THISCALL:
+ NYI_INTERP("GenerateInterpreterStub -- CORINFO_CALLCONV_THISCALL");
+ break;
+
+ case CORINFO_CALLCONV_FASTCALL:
+ NYI_INTERP("GenerateInterpreterStub -- CORINFO_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_NATIVEVARARG:
+ NYI_INTERP("GenerateInterpreterStub -- CORINFO_CALLCONV_NATIVEVARARG");
+ break;
+
+ default:
+ _ASSERTE_ALL_BUILDS(__FILE__, 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(_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(_AMD64_)
+ // EDX has "ilArgs" i.e., just the point where registers have been homed.
+ sl.X86EmitIndexLeaRSP(kEDX, 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(kRCX);
+ sl.X86EmitMovRegReg(kR8, kRCX);
+ }
+ sl.X86EmitRegLoad(kRCX, reinterpret_cast<UINT_PTR>(interpMethInfo));
+ sl.X86EmitCall(sl.NewExternalCodeLabel(interpretMethodFunc), 0);
+ sl.X86EmitAddEsp(interpMethodArgSize);
+ sl.X86EmitReturn(0);
+#elif defined(_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(_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();
+
+
+#else
+#error unsupported platform
+#endif
+ stub = sl.Link();
+
+ *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)
+ {
+ assert(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);
+
+ if (force || interpMethInfo->m_invocations > MaxInterpretCount)
+ {
+ GCX_PREEMP();
+ MethodDesc *md = reinterpret_cast<MethodDesc *>(interpMethInfo->m_method);
+ PCODE stub = md->GetNativeCode();
+
+ if (InterpretationStubToMethodInfo(stub) == md)
+ {
+#ifdef _DEBUG
+ if (s_TraceInterpreterJITTransitionFlag.val(CLRConfig::INTERNAL_TraceInterpreterJITTransition))
+ {
+ fprintf(GetLogFile(), "JITting method %s:%s.\n", md->m_pszDebugClassName, md->m_pszDebugMethodName);
+ }
+#endif // _DEBUG
+ DWORD dwFlags = CORJIT_FLG_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())
+ {
+ dwFlags |= md->AsDynamicMethodDesc()->GetILStubResolver()->GetJitFlags();
+ }
+ else
+ {
+ COR_ILMETHOD_DECODER::DecoderStatus status;
+ pDecoder = new COR_ILMETHOD_DECODER(md->GetILHeader(TRUE),
+ md->GetMDImport(),
+ &status);
+ }
+ PCODE res = md->MakeJitWorker(pDecoder, dwFlags, 0);
+ interpMethInfo->m_jittedCode = res;
+ }
+ }
+}
+
+// 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*>(ArgSlotEndianessFixup(&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*>(ArgSlotEndianessFixup(&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);
+
+#ifdef _DEBUG
+ Thread *pThread = GetThread();
+ DWORD lockCount = pThread->m_dwLockCount;
+#endif
+ if (GET_THREAD()->CatchAtSafePointOpportunistic())
+ {
+ GET_THREAD()->PulseGCMode();
+ }
+ objRef->EnterObjMonitor();
+ _ASSERTE ((objRef->GetSyncBlock()->GetMonitor()->m_Recursion == 1 && pThread->m_dwLockCount == lockCount + 1) ||
+ pThread->m_dwLockCount == lockCount);
+ 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;
+
+ TESTHOOKCALL(AppDomainCanBeUnloaded(GET_THREAD()->GetDomain()->GetId().m_dwId,FALSE));
+
+ 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);
+
+ TESTHOOKCALL(AppDomainCanBeUnloaded(GET_THREAD()->GetDomain()->GetId().m_dwId,FALSE));
+ 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();
+ kind = m_interpCeeInfo.getLocationOfThisType(m_methInfo->m_method);
+ }
+ 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:
+ {
+ classHnd = (CORINFO_CLASS_HANDLE) GetPreciseGenericsContext();
+ }
+ break;
+ case CORINFO_LOOKUP_METHODPARAM:
+ {
+ MethodDesc* pMD = (MethodDesc*) GetPreciseGenericsContext();
+ classHnd = (CORINFO_CLASS_HANDLE) pMD->GetMethodTable();
+ }
+ break;
+ default:
+ NYI_INTERP("Unknown lookup for synchronized methods");
+ break;
+ }
+ MethodTable* pMT = GetMethodTableFromClsHnd(classHnd);
+ OBJECTREF ref = pMT->GetManagedClassObject();
+ ASSERT(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:
+ assert(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:
+ assert(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);
+#ifdef _DEBUG
+ if (s_TraceInterpreterILFlag.val(CLRConfig::INTERNAL_TraceInterpreterIL))
+ {
+ fprintf(GetLogFile(), " Returning to method %s, stub num %d.\n", methName, m_methInfo->m_stubNum);
+ }
+#endif // _DEBUG
+ continue;
+
+ case CEE_CALLVIRT:
+ DoCall(/*virtualCall*/true);
+#ifdef _DEBUG
+ if (s_TraceInterpreterILFlag.val(CLRConfig::INTERNAL_TraceInterpreterIL))
+ {
+ fprintf(GetLogFile(), " Returning to method %s, stub num %d.\n", methName, m_methInfo->m_stubNum);
+ }
+#endif // _DEBUG
+ continue;
+
+ // HARD
+ case CEE_CALLI:
+ CallI();
+ continue;
+
+ case CEE_RET:
+ if (m_methInfo->m_returnType == CORINFO_TYPE_VOID)
+ {
+ assert(m_curStackHt == 0);
+ }
+ else
+ {
+ assert(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.
+ assert((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)
+ CorInfoType cit = CORINFO_TYPE_UNDEF;
+ {
+ GCX_PREEMP();
+ if(m_methInfo->m_returnType == CORINFO_TYPE_VALUECLASS)
+ cit = m_interpCeeInfo.getHFAType(retValIt.ToClassHandle());
+ }
+#endif
+ if (m_methInfo->GetFlag<InterpreterMethodInfo::Flag_hasRetBuffArg>())
+ {
+ assert((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
+ && CorInfoTypeIsFloatingPoint(cit)
+ && (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);
+ }
+ }
+#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.)
+ assert(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:
+ assert(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:
+ assert(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:
+ assert(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:
+ assert(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:
+ assert(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:
+ assert(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:
+ assert(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:
+ assert(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:
+ {
+ assert(m_curStackHt > 0);
+ m_curStackHt--;
+#ifdef _DEBUG
+ CorInfoType cit = OpStackTypeGet(m_curStackHt).ToCorInfoType();
+ assert(cit == CORINFO_TYPE_INT || cit == CORINFO_TYPE_UINT || cit == CORINFO_TYPE_NATIVEINT);
+#endif // _DEBUG
+#if defined(_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();
+#ifdef _DEBUG
+ if (s_TraceInterpreterILFlag.val(CLRConfig::INTERNAL_TraceInterpreterIL))
+ {
+ fprintf(GetLogFile(), " Returning to method %s, stub num %d.\n", methName, m_methInfo->m_stubNum);
+ }
+#endif // _DEBUG
+ 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
+ {
+ assert(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
+ {
+ assert(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
+ {
+ assert(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
+ {
+ assert(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();
+ assert(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.
+ assert(!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");
+ assert(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();
+ assert(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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ bool handlesEx = false;
+
+ if (orThrowable != NULL)
+ {
+ PTR_Thread pCurThread = GetThread();
+
+ // 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);
+ assert(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->GetTrueTypeHandle()))
+ {
+ // 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:
+ assert(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;
+ assert(op < CEE_COUNT); // Bounds check for below.
+ // deliberate fall-through here.
+ 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 {
+ SO_TOLERANT;
+ 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);
+ assert(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 = ArgSlotEndianessFixup(reinterpret_cast<ARG_SLOT*>(FixedSizeLocalSlot(i)), sizeof(void**));
+ localPtr = *reinterpret_cast<void**>(structPtr);
+ }
+ else
+ {
+ localPtr = ArgSlotEndianessFixup(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 = ArgSlotEndianessFixup(&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:
+ assert(!pinningRef);
+ GCScanValueClassRootAtLoc(loc, it.ToClassHandle(), pf, sc);
+ break;
+
+ default:
+ assert(!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:
+ assert(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:
+ assert(sizeof(NativeInt) == sizeof(NativeUInt));
+ if (it2.Size(info) == sizeof(NativeInt))
+ return true;
+ break;
+
+ case CORINFO_TYPE_INT:
+ case CORINFO_TYPE_UINT:
+ assert(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 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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ LdFromMemAddr(GetArgAddr(argNum), GetArgType(argNum));
+}
+
+void Interpreter::LdArgA(int argNum)
+{
+ CONTRACTL {
+ SO_TOLERANT;
+ 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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ StToLocalMemAddr(GetArgAddr(argNum), GetArgType(argNum));
+}
+
+
+void Interpreter::LdLocA(int locNum)
+{
+ CONTRACTL {
+ SO_TOLERANT;
+ 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 = ArgSlotEndianessFixup(reinterpret_cast<ARG_SLOT*>(FixedSizeLocalSlot(locNum)), sizeof(void**));
+ addr = *reinterpret_cast<void**>(structPtr);
+ }
+ else
+ {
+ addr = ArgSlotEndianessFixup(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 {
+ SO_TOLERANT;
+ 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 {
+ SO_TOLERANT;
+ 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 {
+ SO_TOLERANT;
+ 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 {
+ SO_TOLERANT;
+ 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 {
+ SO_TOLERANT;
+ 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()
+{
+ assert(TOSIsPtr());
+ assert(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()
+{
+ assert(TOSIsPtr());
+ assert(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()
+{
+ assert(m_curStackHt >= 2);
+ assert(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;
+
+#ifdef _DEBUG
+ if (s_TraceInterpreterILFlag.val(CLRConfig::INTERNAL_TraceInterpreterIL) &&
+ IsInLocalArea(ptr))
+ {
+ PrintLocals();
+ }
+#endif // _DEBUG
+}
+
+void Interpreter::StInd_Ref()
+{
+ assert(m_curStackHt >= 2);
+ assert(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);
+ SetObjectReferenceUnchecked(ptr, val);
+ m_curStackHt -= 2;
+
+#ifdef _DEBUG
+ if (s_TraceInterpreterILFlag.val(CLRConfig::INTERNAL_TraceInterpreterIL) &&
+ IsInLocalArea(ptr))
+ {
+ PrintLocals();
+ }
+#endif // _DEBUG
+}
+
+
+template<int op>
+void Interpreter::BinaryArithOp()
+{
+ CONTRACTL {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ assert(m_curStackHt >= 2);
+ unsigned op1idx = m_curStackHt - 2;
+ unsigned op2idx = m_curStackHt - 1;
+ InterpreterType t1 = OpStackTypeGet(op1idx);
+ assert(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);
+ assert(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(_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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ assert(m_curStackHt >= 2);
+ unsigned op1idx = m_curStackHt - 2;
+ unsigned op2idx = m_curStackHt - 1;
+
+ InterpreterType t1 = OpStackTypeGet(op1idx);
+ CorInfoType cit1 = t1.ToCorInfoType();
+ assert(IsStackNormalType(cit1));
+
+ InterpreterType t2 = OpStackTypeGet(op2idx);
+ CorInfoType cit2 = t2.ToCorInfoType();
+ assert(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 {
+ SO_TOLERANT;
+ 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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ assert(m_curStackHt >= 2);
+ unsigned op1idx = m_curStackHt - 2;
+ unsigned op2idx = m_curStackHt - 1;
+
+ InterpreterType t1 = OpStackTypeGet(op1idx);
+ CorInfoType cit1 = t1.ToCorInfoType();
+ assert(IsStackNormalType(cit1));
+
+ InterpreterType t2 = OpStackTypeGet(op2idx);
+ CorInfoType cit2 = t2.ToCorInfoType();
+ assert(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
+ {
+ assert(op == BIO_DivUn || op == BIO_RemUn);
+ if (val2 == 0)
+ {
+ ThrowDivideByZero();
+ }
+ else if (val2 == -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 {
+ SO_TOLERANT;
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ assert(m_curStackHt >= 2);
+ unsigned op1idx = m_curStackHt - 2;
+ unsigned op2idx = m_curStackHt - 1;
+
+ InterpreterType t1 = OpStackTypeGet(op1idx);
+ CorInfoType cit1 = t1.ToCorInfoType();
+ assert(IsStackNormalType(cit1));
+
+ InterpreterType t2 = OpStackTypeGet(op2idx);
+ CorInfoType cit2 = t2.ToCorInfoType();
+ assert(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
+ {
+ assert(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
+ {
+ assert(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 {
+ SO_TOLERANT;
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ assert(m_curStackHt >= 1);
+ unsigned opidx = m_curStackHt - 1;
+
+ InterpreterType t1 = OpStackTypeGet(opidx);
+ CorInfoType cit1 = t1.ToCorInfoType();
+ assert(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 {
+ SO_TOLERANT;
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ assert(m_curStackHt >= 1);
+ unsigned opidx = m_curStackHt - 1;
+
+ InterpreterType t1 = OpStackTypeGet(opidx);
+ CorInfoType cit1 = t1.ToCorInfoType();
+ assert(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 {
+ SO_TOLERANT;
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ assert(m_curStackHt >= 1);
+ unsigned opidx = m_curStackHt - 1;
+
+ InterpreterType t1 = OpStackTypeGet(opidx);
+ CorInfoType cit1 = t1.ToCorInfoType();
+ assert(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 {
+ SO_TOLERANT;
+ NOTHROW;
+ GC_NOTRIGGER;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ assert(m_curStackHt >= 1);
+ unsigned opidx = m_curStackHt - 1;
+
+ InterpreterType t1 = OpStackTypeGet(opidx);
+ CorInfoType cit1 = t1.ToCorInfoType();
+ assert(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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ assert(m_curStackHt >= 1);
+ unsigned opidx = m_curStackHt - 1;
+
+ InterpreterType t1 = OpStackTypeGet(opidx);
+ CorInfoType cit1 = t1.ToCorInfoType();
+ assert(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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ assert(m_curStackHt >= 1);
+ unsigned opidx = m_curStackHt - 1;
+
+ InterpreterType t1 = OpStackTypeGet(opidx);
+ CorInfoType cit1 = t1.ToCorInfoType();
+ assert(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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ BarrierIfVolatile();
+
+ assert(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 {
+ SO_TOLERANT;
+ 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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ GCX_PREEMP();
+
+ CORINFO_GENERICHANDLE_RESULT embedInfo;
+ 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(_AMD64_)
+ isValid = isValid || (s_InterpreterLooseRules && cit == CORINFO_TYPE_LONG);
+#endif
+ return isValid;
+}
+
+void Interpreter::CpObj()
+{
+ CONTRACTL {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ assert(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);
+ SetObjectReferenceUnchecked(reinterpret_cast<OBJECTREF*>(dest), val);
+ }
+ m_curStackHt -= 2;
+ m_ILCodePtr += 5;
+}
+
+void Interpreter::StObj()
+{
+ CONTRACTL {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ assert(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);
+
+ assert( (OpStackTypeGet(valInd).ToCorInfoType() == CORINFO_TYPE_VALUECLASS &&
+ OpStackTypeGet(valInd).ToClassHandle() == clsHnd)
+ ||
+ (OpStackTypeGet(valInd).ToCorInfoType() ==
+ CorInfoTypeStackNormalize(GetTypeForPrimitiveValueClass(clsHnd)))
+ || (s_InterpreterLooseRules && sz <= sizeof(dest)));
+
+ 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.
+ assert(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));
+ SetObjectReferenceUnchecked(reinterpret_cast<OBJECTREF*>(dest), val);
+ }
+
+ m_curStackHt -= 2;
+ m_ILCodePtr += 5;
+
+ BarrierIfVolatile();
+}
+
+void Interpreter::InitObj()
+{
+ CONTRACTL {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ assert(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.
+ SetObjectReferenceUnchecked(reinterpret_cast<OBJECTREF*>(dest), NULL);
+ }
+ m_curStackHt -= 1;
+ m_ILCodePtr += 6;
+}
+
+void Interpreter::LdStr()
+{
+ CONTRACTL {
+ SO_TOLERANT;
+ 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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+#else
+ // Dynamic contract occupies too much stack.
+ STATIC_CONTRACT_SO_TOLERANT;
+ 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)
+ {
+ assert(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();
+ assert(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();
+ newHelper = m_interpCeeInfo.getNewHelper(&methTok, m_methInfo->m_method);
+ }
+
+ MethodTable * pNewObjMT = GetMethodTableFromClsHnd(methTok.hClass);
+ switch (newHelper)
+ {
+#ifdef FEATURE_REMOTING
+ case CORINFO_HELP_NEW_CROSSCONTEXT:
+ {
+ if (CRemotingServices::RequiresManagedActivation(pNewObjMT) && !pNewObjMT->IsComObjectType())
+ {
+ thisArgObj = CRemotingServices::CreateProxyOrObject(pNewObjMT);
+ }
+ else
+ {
+ thisArgObj = AllocateObject(pNewObjMT);
+ }
+ }
+ break;
+#endif // FEATURE_REMOTING
+ 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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ assert(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 _WIN64
+ // 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 typeHnd(elemClsHnd);
+ ArrayTypeDesc* pArrayClassRef = typeHnd.AsArray();
+
+ pArrayClassRef->GetMethodTable()->CheckRunClassInitThrowing();
+
+ INT32 size32 = (INT32)sz;
+ Object* newarray = OBJECTREFToObject(AllocateArrayEx(typeHnd, &size32, 1));
+
+ GCX_FORBID();
+ OpStackTypeSet(stkInd, InterpreterType(CORINFO_TYPE_CLASS));
+ OpStackSet<Object*>(stkInd, newarray);
+ }
+
+ m_ILCodePtr += 5;
+}
+
+void Interpreter::IsInst()
+{
+ CONTRACTL {
+ SO_TOLERANT;
+ 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));
+
+ assert(m_curStackHt >= 1);
+ unsigned idx = m_curStackHt - 1;
+#ifdef _DEBUG
+ CorInfoType cit = OpStackTypeGet(idx).ToCorInfoType();
+ assert(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 {
+ SO_TOLERANT;
+ 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));
+
+ assert(m_curStackHt >= 1);
+ unsigned idx = m_curStackHt - 1;
+#ifdef _DEBUG
+ CorInfoType cit = OpStackTypeGet(idx).ToCorInfoType();
+ assert(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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ assert(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 {
+ SO_TOLERANT;
+ 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));
+ assert(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(_AMD64_)
+ assert(typedRefIT.IsLargeStruct(&m_interpCeeInfo));
+ tbr = (TypedByRef*) LargeStructOperandStackPush(GetTypedRefSize(&m_interpCeeInfo));
+ OpStackSet<void*>(idx, tbr);
+#elif defined(_X86_) || defined(_ARM_)
+ assert(!typedRefIT.IsLargeStruct(&m_interpCeeInfo));
+ tbr = OpStackGetAddr<TypedByRef>(idx);
+#elif defined(_ARM64_)
+ tbr = NULL;
+ NYI_INTERP("Unimplemented code: MkRefAny");
+#else
+#error "unsupported platform"
+#endif
+ tbr->data = ptr;
+ tbr->type = TypeHandle(cls);
+ OpStackTypeSet(idx, typedRefIT);
+
+ m_ILCodePtr += 5;
+}
+
+void Interpreter::RefanyType()
+{
+ CONTRACTL {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ assert(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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ GCX_PREEMP();
+
+ return m_interpCeeInfo.getTypeForPrimitiveValueClass(clsHnd);
+}
+
+void Interpreter::RefanyVal()
+{
+ CONTRACTL {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ assert(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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ assert(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 {
+ SO_TOLERANT;
+ 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 {
+ SO_TOLERANT;
+ 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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ assert(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_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 {
+ SO_TOLERANT;
+ 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()
+{
+ assert(!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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ assert(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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ assert(m_curStackHt >= op1idx + 2);
+ unsigned op2idx = op1idx + 1;
+ InterpreterType t1 = OpStackTypeGet(op1idx);
+ CorInfoType cit1 = t1.ToCorInfoType();
+ assert(IsStackNormalType(cit1));
+ InterpreterType t2 = OpStackTypeGet(op2idx);
+ CorInfoType cit2 = t2.ToCorInfoType();
+ assert(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
+ {
+ assert(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
+ {
+ assert(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
+ {
+ assert(op == CO_LT_UN);
+ if (static_cast<UINT32>(val1) < static_cast<UINT32>(val2)) res = 1;
+ }
+ }
+ else
+ {
+ VerificationError("Binary comparision 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)
+ {
+ assert(OpStackGet<void*>(op2idx) == 0);
+ val2 = 0;
+ }
+ else
+ {
+ assert(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
+ {
+ assert(op == CO_LT_UN);
+ if (static_cast<NativeUInt>(val1) < static_cast<NativeUInt>(val2)) res = 1;
+ }
+ }
+ else
+ {
+ VerificationError("Binary comparision operation: type mismatch.");
+ }
+ break;
+ case CORINFO_TYPE_LONG:
+ {
+ bool looseLong = false;
+#if defined(_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
+ {
+ assert(op == CO_LT_UN);
+ if (static_cast<UINT64>(val1) < static_cast<UINT64>(val2)) res = 1;
+ }
+ }
+ else
+ {
+ VerificationError("Binary comparision 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 comparision operation: type mismatch.");
+ }
+ }
+ else
+ {
+ VerificationError("Binary comparision 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
+ {
+ assert(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 comparision 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
+ {
+ assert(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 comparision 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
+ {
+ assert(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
+ {
+ assert(op == CO_LT_UN);
+ if (static_cast<NativeUInt>(val1) < static_cast<NativeUInt>(val2)) res = 1;
+ }
+ }
+ else
+ {
+ VerificationError("Binary comparision 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:
+ assert(false); // Should not be here if the type is stack-normal.
+ }
+
+ return res;
+}
+
+template<bool val, int targetLen>
+void Interpreter::BrOnValue()
+{
+ assert(targetLen == 1 || targetLen == 4);
+ assert(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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ assert(targetLen == 1 || targetLen == 4);
+ assert(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 {
+ SO_TOLERANT;
+ 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));
+ assert(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);
+ structValIT = InterpreterType(&m_interpCeeInfo, valClsHnd);
+ }
+
+ UINT sz = fld->GetSize();
+
+ // Live vars: valCit, structValIt
+ assert(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));
+#ifdef FEATURE_REMOTING
+ if (obj->IsTransparentProxy())
+ {
+ NYI_INTERP("Thunking objects not supported");
+ }
+#endif
+ 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, obj->GetAppDomain());
+ }
+ 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
+ {
+ assert(CorInfoTypeIsPointer(addrCit));
+ ptr = OpStackGet<INT8*>(stackInd);
+ ThrowOnInvalidPointer(ptr);
+ }
+
+ assert(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 {
+ SO_TOLERANT;
+ 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);
+ }
+ assert(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 {
+ SO_TOLERANT;
+ 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));
+ assert(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();
+ assert(m_curStackHt >= 2);
+ unsigned addrInd = m_curStackHt - 2;
+ CorInfoType addrCit = OpStackTypeGet(addrInd).ToCorInfoType();
+ unsigned valInd = m_curStackHt - 1;
+ CorInfoType valCit = OpStackTypeGet(valInd).ToCorInfoType();
+ assert(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
+ {
+#ifdef _DEBUG
+ if (obj->IsTransparentProxy()) NYI_INTERP("Stores to thunking objects.");
+#endif
+ 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
+ {
+ assert(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));
+ SetObjectReferenceUnchecked(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 {
+ SO_TOLERANT;
+ 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)
+ {
+ 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 {
+ SO_TOLERANT;
+ 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); assert(b);
+#endif // INTERP_ILCYCLE_PROFILE
+
+ pMT->CheckRunClassInitThrowing();
+
+#if INTERP_ILCYCLE_PROFILE
+ unsigned __int64 endCycles;
+ b = CycleTimer::GetThreadCyclesS(&endCycles); assert(b);
+ m_exemptCycles += (endCycles - startCycles);
+#endif // INTERP_ILCYCLE_PROFILE
+ }
+}
+
+void Interpreter::LdSFldA()
+{
+ CONTRACTL {
+ SO_TOLERANT;
+ 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 {
+ SO_TOLERANT;
+ 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)
+ {
+ SetObjectReferenceUnchecked(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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ assert(m_curStackHt >= 2);
+ unsigned arrInd = m_curStackHt - 2;
+ unsigned indexInd = m_curStackHt - 1;
+
+ assert(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
+ {
+ T res = reinterpret_cast<Array<T>*>(a)->GetDirectConstPointerToNonObjectElements()[index];
+ if (cit == CORINFO_TYPE_INT)
+ {
+ // Widen narrow types.
+ int ires = (int)res;
+ OpStackSet<int>(arrInd, ires);
+ }
+ else
+ {
+ OpStackSet<T>(arrInd, res);
+ }
+ }
+ }
+ else
+ {
+ assert(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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+
+ assert(m_curStackHt >= 3);
+ unsigned arrInd = m_curStackHt - 3;
+ unsigned indexInd = m_curStackHt - 2;
+ unsigned valInd = m_curStackHt - 1;
+
+ assert(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
+ {
+ assert(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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ assert(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);
+ }
+
+ assert(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
+ {
+ assert(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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ assert(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
+
+ assert(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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ assert(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(_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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ assert(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(_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(_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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ assert(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 {
+ SO_TOLERANT;
+ 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"));
+
+ {
+ Object* res = OBJECTREFToObject(pMT->Box(valPtr));
+
+ GCX_FORBID();
+
+ OpStackSet<Object*>(ind, res);
+ OpStackTypeSet(ind, InterpreterType(CORINFO_TYPE_CLASS));
+ }
+}
+
+
+void Interpreter::Unbox()
+{
+ CONTRACTL {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END
+
+ assert(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 primtive type to be interchangable
+ 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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END
+
+ assert(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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END
+
+ OBJECTREF throwable = GetThread()->LastThrownObject();
+ COMPlusThrow(throwable);
+}
+
+void Interpreter::UnboxAny()
+{
+ CONTRACTL {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ assert(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
+ {
+ CorElementType type1 = pMT1->GetInternalCorElementType();
+ CorElementType type2 = pMT2->GetInternalCorElementType();
+
+ // we allow enums and their primtive type to be interchangable
+ if (type1 == type2)
+ {
+ if ((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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ assert(m_curStackHt >= 1);
+ unsigned arrInd = m_curStackHt - 1;
+
+ assert(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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+#else
+ // Dynamic contract occupies too much stack.
+ STATIC_CONTRACT_SO_TOLERANT;
+ 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>())
+ {
+ assert(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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+#else
+ // Dynamic contract occupies too much stack.
+ STATIC_CONTRACT_SO_TOLERANT;
+ 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); assert(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.)
+ assert(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.
+ assert(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); assert(b);
+ unsigned __int64 delta = (callEndCycles - callStartCycles);
+ delta -= (m_exemptCycles - callStartExemptCycles);
+ s_callCycles += delta;
+ s_calls++;
+ }
+#endif
+#endif // INTERP_ILCYCLE_PROFILE
+
+ callInfoPtr = &callInfo;
+
+ assert(!callInfoPtr->exactContextNeedsRuntimeLookup);
+
+ methToCall = reinterpret_cast<MethodDesc*>(methTok.hMethod);
+ exactClass = methTok.hClass;
+ }
+ else
+ {
+ // callInfoPtr and methTokPtr must either both be NULL, or neither.
+ assert(methTokPtr != NULL);
+
+ assert(!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);
+ sigInfo.retTypeClass = sigInfoFull.retTypeClass;
+ sigInfo.numArgs = sigInfoFull.numArgs;
+ sigInfo.callConv = sigInfoFull.callConv;
+ sigInfo.retType = sigInfoFull.retType;
+ }
+
+ // Point A in our cycle count.
+
+
+ // Is the method an intrinsic? If so, and if it's one we've written special-case code for
+ // handle intrinsically.
+ CorInfoIntrinsics intrinsicId;
+ {
+ GCX_PREEMP();
+ intrinsicId = m_interpCeeInfo.getIntrinsicID(CORINFO_METHOD_HANDLE(methToCall));
+ }
+
+#if INTERP_TRACING
+ if (intrinsicId != CORINFO_INTRINSIC_Illegal)
+ InterlockedIncrement(&s_totalInterpCallsToIntrinsics);
+#endif // INTERP_TRACING
+ bool didIntrinsic = false;
+ if (!m_constrainedFlag)
+ {
+ switch (intrinsicId)
+ {
+ case CORINFO_INTRINSIC_StringLength:
+ DoStringLength(); didIntrinsic = true;
+ break;
+ case CORINFO_INTRINSIC_StringGetChar:
+ DoStringGetChar(); didIntrinsic = true;
+ break;
+ case CORINFO_INTRINSIC_GetTypeFromHandle:
+ // This is an identity transformation. (At least until I change LdToken to
+ // return a RuntimeTypeHandle struct...which is a TODO.)
+ DoGetTypeFromHandle();
+ didIntrinsic = true;
+ break;
+#if INTERP_ILSTUBS
+ case CORINFO_INTRINSIC_StubHelpers_GetStubContext:
+ OpStackSet<void*>(m_curStackHt, GetStubContext());
+ OpStackTypeSet(m_curStackHt, InterpreterType(CORINFO_TYPE_NATIVEINT));
+ m_curStackHt++; didIntrinsic = true;
+ break;
+ case CORINFO_INTRINSIC_StubHelpers_GetStubContextAddr:
+ OpStackSet<void*>(m_curStackHt, GetStubContextAddr());
+ 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 == MscorlibBinder::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;
+ }
+ }
+
+ 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.
+ assert(TOSIsPtr() && OpStackTypeGet(m_curStackHt-1).ToCorInfoType() != CORINFO_TYPE_BYREF);
+ CORINFO_METHOD_HANDLE targetMethodHnd = GetFunctionPointerStack()[m_curStackHt-1];
+ assert(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)
+ {
+ assert(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
+ && CorInfoTypeIsFloatingPoint(m_interpCeeInfo.getHFAType(sigInfo.retTypeClass))
+ && (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);
+ }
+ }
+#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(_X86_)
+ unsigned totalArgSlots = nSlots;
+#elif defined(_ARM_) || defined(_ARM64_)
+ // ARM64TODO: Verify that the following statement is correct for ARM64.
+ unsigned totalArgSlots = nSlots + HFAReturnArgSlots;
+#elif defined(_AMD64_)
+ 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(_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(_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.
+ assert(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)
+ {
+ _ASSERT(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);
+ assert((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_NoLogging());
+ }
+ }
+ }
+#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)
+ {
+ assert(!methToCall->IsStatic());
+ ThrowNullPointerException();
+ }
+ // ...except in the case of strings, which are both
+ // allocated and initialized by their special constructor.
+ else
+ {
+ assert(methToCall->IsCtor() && methToCall->GetMethodTable()->IsString());
+ }
+ }
+ curArgSlot++;
+ }
+
+ // This is the argument slot that will be used to hold the return value.
+ ARG_SLOT retVal = 0;
+ _ASSERTE (NUMBER_RETURNVALUE_SLOTS == 1);
+
+ // 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(_ARM_)
+ || HFAReturnArgSlots > 0
+#endif // defined(_ARM_)
+ )
+ {
+ assert(retTypeClsHnd != NULL);
+ retTypeIt = InterpreterType(&m_interpCeeInfo, retTypeClsHnd);
+ retTypeSz = retTypeIt.Size(&m_interpCeeInfo);
+
+#if defined(_ARM_)
+ if (HFAReturnArgSlots > 0)
+ {
+ args[curArgSlot] = PtrToArgSlot(args - HFAReturnArgSlots);
+ }
+ else
+#endif // defined(_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())
+ {
+ assert(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);
+ 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.
+ assert(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;
+
+ assert(thisArgHnd != NULL);
+ OBJECTREF objRef = ObjectToOBJECTREF(*thisArgHnd);
+ GCPROTECT_BEGIN(objRef);
+ pCode = methToCall->GetMultiCallableAddrOfVirtualizedCode(&objRef, methToCall->GetMethodTable());
+ GCPROTECT_END();
+
+ exactMethToCall = Entry2MethodDesc(pCode, objRef->GetTrueMethodTable());
+ }
+
+ // Compile the target in advance of calling.
+ if (exactMethToCall->IsPointingToPrestub())
+ {
+ MethodTable* dispatchingMT = NULL;
+ if (exactMethToCall->IsVtableMethod())
+ {
+ assert(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)
+ {
+ assert(!exactMethToCall->IsILStub());
+ InterpreterMethodInfo* methInfo = MethodHandleToInterpreterMethInfoPtr(CORINFO_METHOD_HANDLE(exactMethToCall));
+ assert(methInfo != NULL);
+#if INTERP_ILCYCLE_PROFILE
+ bool b = CycleTimer::GetThreadCyclesS(&startCycles); assert(b);
+#endif // INTERP_ILCYCLE_PROFILE
+ retVal = 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); assert(b);
+#endif // INTERP_ILCYCLE_PROFILE
+ mdcs.CallTargetWorker(args, &retVal, sizeof(retVal));
+
+ 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); assert(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.
+ // The example I've found involves SecurityContextFrame.Push/Pop.
+ // 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*>(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_BYREF:
+ OpStackSet<void*>(m_curStackHt, reinterpret_cast<void*>(retVal));
+ 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(_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(_ARM_)
+ if (pLargeStructRetVal != NULL)
+ {
+ assert(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 &&
+ 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.
+ assert(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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+#else
+ // Dynamic contract occupies too much stack.
+ STATIC_CONTRACT_SO_TOLERANT;
+ 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.
+ assert(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(_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();
+ assert(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));
+ assert(methInfo != NULL);
+#if INTERP_ILCYCLE_PROFILE
+ bool b = CycleTimer::GetThreadCyclesS(&startCycles); assert(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_pObjectCtorMD;
+ }
+ else
+ {
+ pMD = g_pPrepareConstrainedRegionsMethod; // A random static method.
+ }
+ MethodDescCallSite mdcs(pMD, &mSig, ftnPtr);
+ // 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);
+ }
+ // 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)
+ {
+ assert(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.
+ assert(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 {
+ SO_TOLERANT;
+ 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;
+ assert(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;
+ assert((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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ assert(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);
+
+#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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ assert(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);
+
+#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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ assert(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::RecordConstrainedCall()
+{
+ CONTRACTL {
+ SO_TOLERANT;
+ 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);
+ assert(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))
+ {
+ assert(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);
+ }
+ }
+ assert(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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ COMPlusThrow(kDivideByZeroException);
+}
+
+void Interpreter::ThrowSysArithException()
+{
+ CONTRACTL {
+ SO_TOLERANT;
+ 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 {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ COMPlusThrow(kNullReferenceException);
+}
+
+void Interpreter::ThrowOverflowException()
+{
+ CONTRACTL {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ COMPlusThrow(kOverflowException);
+}
+
+void Interpreter::ThrowArrayBoundsException()
+{
+ CONTRACTL {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ COMPlusThrow(kIndexOutOfRangeException);
+}
+
+void Interpreter::ThrowInvalidCastException()
+{
+ CONTRACTL {
+ SO_TOLERANT;
+ THROWS;
+ GC_TRIGGERS;
+ MODE_COOPERATIVE;
+ } CONTRACTL_END;
+
+ COMPlusThrow(kInvalidCastException);
+}
+
+void Interpreter::ThrowStackOverflow()
+{
+ CONTRACTL {
+ SO_TOLERANT;
+ 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 {
+ SO_TOLERANT;
+ 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 {
+ SO_TOLERANT;
+ NOTHROW;
+ GC_NOTRIGGER;
+ } CONTRACTL_END;
+#endif
+
+ CrstHolder ch(&s_interpStubToMDMapLock);
+
+ AddrToMDMap* map = Interpreter::GetAddrToMdMap();
+#ifdef _DEBUG
+ CORINFO_METHOD_HANDLE dummy;
+ assert(!map->Lookup(addr, &dummy));
+#endif // DEBUG
+ map->AddOrReplace(KeyValuePair<void*,CORINFO_METHOD_HANDLE>(addr, md));
+}
+
+MethodDesc* Interpreter::InterpretationStubToMethodInfo(PCODE addr)
+{
+ CONTRACTL {
+ SO_TOLERANT;
+ 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 {
+ SO_TOLERANT;
+ 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 {
+ SO_TOLERANT;
+ 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 {
+ SO_TOLERANT;
+ 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); assert(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); assert(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)
+ {
+ assert(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)
+ {
+ assert(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
+ {
+ assert(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 {
+ SO_TOLERANT;
+ 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
+ {
+ assert(strlen("<NULL>.") == 7);
+ length = 7;
+ }
+
+ /* add length of methodName and opening bracket */
+ length += strlen(methodName) + 1;
+
+ CORINFO_SIG_INFO sig;
+ info->getMethodSig(hnd, &sig);
+ 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);
+ }
+
+ assert(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();
+ assert(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 = ArgSlotEndianessFixup(reinterpret_cast<ARG_SLOT*>(FixedSizeLocalSlot(i)), sizeof(void**));
+ localPtr = *reinterpret_cast<void**>(structPtr);
+ }
+ else
+ {
+ localPtr = ArgSlotEndianessFixup(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
+ {
+ assert(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
+ {
+ assert(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...
+
+#ifdef _DEBUG
+ // 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];
+ assert((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];
+ assert((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