Permalink
Fetching contributors…
Cannot retrieve contributors at this time
12160 lines (10845 sloc) 404 KB
// 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 "gcheaputilities.h"
#include <float.h>
#include "jitinterface.h"
#include "safemath.h"
#include "exceptmacros.h"
#include "runtimeexceptionkind.h"
#include "runtimehandles.h"
#include "vars.hpp"
#include "cycletimer.h"
inline CORINFO_CALLINFO_FLAGS combine(CORINFO_CALLINFO_FLAGS flag1, CORINFO_CALLINFO_FLAGS flag2)
{
return (CORINFO_CALLINFO_FLAGS) (flag1 | flag2);
}
static CorInfoType asCorInfoType(CORINFO_CLASS_HANDLE clsHnd)
{
TypeHandle typeHnd(clsHnd);
return CEEInfo::asCorInfoType(typeHnd.GetInternalCorElementType(), typeHnd, NULL);
}
InterpreterMethodInfo::InterpreterMethodInfo(CEEInfo* comp, CORINFO_METHOD_INFO* methInfo)
: m_method(methInfo->ftn),
m_module(methInfo->scope),
m_ILCode(methInfo->ILCode),
m_ILCodeEnd(methInfo->ILCode + methInfo->ILCodeSize),
m_maxStack(methInfo->maxStack),
#if INTERP_PROFILE
m_totIlInstructionsExeced(0),
m_maxIlInstructionsExeced(0),
#endif
m_ehClauseCount(methInfo->EHcount),
m_varArgHandleArgNum(NO_VA_ARGNUM),
m_numArgs(methInfo->args.numArgs),
m_numLocals(methInfo->locals.numArgs),
m_flags(0),
m_argDescs(NULL),
m_returnType(methInfo->args.retType),
m_invocations(0),
m_methodCache(NULL)
{
// Overflow sanity check. (Can ILCodeSize ever be zero?)
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_)
// 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
// 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_)
#if defined(_X86_)
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);
#elif defined(_ARM64_)
// x8 through x15 are scratch registers on ARM64.
IntReg x8 = IntReg(8);
IntReg x9 = IntReg(9);
#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_)
// Pass "ilArgs", i.e. just the point where registers have been homed, as 2nd arg
sl.X86EmitIndexLeaRSP(ARGUMENT_kREG2, static_cast<X86Reg>(kESP_Unsafe), 8);
// Allocate space for homing callee's (InterpretMethod's) arguments.
// Calling convention requires a default allocation space of 4,
// but to double align the stack frame, we'd allocate 5.
int interpMethodArgSize = 5 * sizeof(void*);
sl.X86EmitSubEsp(interpMethodArgSize);
// If we have IL stubs pass the stub context in R10 or else pass NULL.
#if INTERP_ILSTUBS
if (pMD->IsILStub())
{
sl.X86EmitMovRegReg(kR8, kR10);
}
else
#endif
{
// For a non-ILStub method, push NULL as the StubContext argument.
sl.X86EmitZeroOutReg(ARGUMENT_kREG1);
sl.X86EmitMovRegReg(kR8, ARGUMENT_kREG1);
}
sl.X86EmitRegLoad(ARGUMENT_kREG1, reinterpret_cast<UINT_PTR>(interpMethInfo));
sl.X86EmitCall(sl.NewExternalCodeLabel(interpretMethodFunc), 0);
sl.X86EmitAddEsp(interpMethodArgSize);
sl.X86EmitReturn(0);
#elif defined(_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(SystemDomain::GetGlobalLoaderAllocator()->GetStubHeap());
*nativeSizeOfCode = static_cast<ULONG>(stub->GetNumCodeBytes());
// TODO: manage reference count of interpreter stubs. Look for examples...
*nativeEntry = dac_cast<BYTE*>(stub->GetEntryPoint());
}
// Initialize the arg offset information.
interpMethInfo->InitArgInfo(comp, info, argState.argOffsets);
#ifdef _DEBUG
AddInterpMethInfo(interpMethInfo);
#endif // _DEBUG
if (!jmpCall)
{
// Remember the mapping between code address and MethodDesc*.
RecordInterpreterStubForMethodDesc(info->ftn, *nativeEntry);
}
return CORJIT_OK;
#undef TRACE_SKIPPED
}
size_t Interpreter::GetFrameSize(InterpreterMethodInfo* interpMethInfo)
{
size_t sz = interpMethInfo->LocalMemSize();
#if COMBINE_OPSTACK_VAL_TYPE
sz += (interpMethInfo->m_maxStack * sizeof(OpStackValAndType));
#else
sz += (interpMethInfo->m_maxStack * (sizeof(INT64) + sizeof(InterpreterType*)));
#endif
return sz;
}
// static
ARG_SLOT Interpreter::ExecuteMethodWrapper(struct InterpreterMethodInfo* interpMethInfo, bool directCall, BYTE* ilArgs, void* stubContext, __out bool* pDoJmpCall, CORINFO_RESOLVED_TOKEN* pResolvedToken)
{
#define INTERP_DYNAMIC_CONTRACTS 1
#if INTERP_DYNAMIC_CONTRACTS
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
#else
// Dynamic contract occupies too much stack.
STATIC_CONTRACT_THROWS;
STATIC_CONTRACT_GC_TRIGGERS;
STATIC_CONTRACT_MODE_COOPERATIVE;
#endif
size_t sizeWithGS = GetFrameSize(interpMethInfo) + sizeof(GSCookie);
BYTE* frameMemoryGS = static_cast<BYTE*>(_alloca(sizeWithGS));
ARG_SLOT retVal = 0;
unsigned jmpCallToken = 0;
Interpreter interp(interpMethInfo, directCall, ilArgs, stubContext, frameMemoryGS);
// Make sure we can do a GC Scan properly.
FrameWithCookie<InterpreterFrame> interpFrame(&interp);
// Update the interpretation count.
InterlockedIncrement(reinterpret_cast<LONG *>(&interpMethInfo->m_invocations));
// Need to wait until this point to do this JITting, since it may trigger a GC.
JitMethodIfAppropriate(interpMethInfo);
// Pass buffers to get jmpCall flag and the token, if necessary.
interp.ExecuteMethod(&retVal, pDoJmpCall, &jmpCallToken);
if (*pDoJmpCall)
{
GCX_PREEMP();
interp.ResolveToken(pResolvedToken, jmpCallToken, CORINFO_TOKENKIND_Method InterpTracingArg(RTK_Call));
}
interpFrame.Pop();
return retVal;
}
// TODO: Add GSCookie checks
// static
inline ARG_SLOT Interpreter::InterpretMethodBody(struct InterpreterMethodInfo* interpMethInfo, bool directCall, BYTE* ilArgs, void* stubContext)
{
#if INTERP_DYNAMIC_CONTRACTS
CONTRACTL {
THROWS;
GC_TRIGGERS;
MODE_COOPERATIVE;
} CONTRACTL_END;
#else
// Dynamic contract occupies too much stack.
STATIC_CONTRACT_THROWS;
STATIC_CONTRACT_GC_TRIGGERS;
STATIC_CONTRACT_MODE_COOPERATIVE;
#endif
CEEInfo* jitInfo = NULL;
for (bool doJmpCall = true; doJmpCall; )
{
unsigned jmpCallToken = 0;
CORINFO_RESOLVED_TOKEN methTokPtr;
ARG_SLOT retVal = ExecuteMethodWrapper(interpMethInfo, directCall, ilArgs, stubContext, &doJmpCall, &methTokPtr);
// Clear any allocated jitInfo.
delete jitInfo;
// Nothing to do if the recent method asks not to do a jmpCall.
if (!doJmpCall)
{
return retVal;
}
// The recently executed method wants us to perform a jmpCall.
MethodDesc* pMD = GetMethod(methTokPtr.hMethod);
interpMethInfo = MethodHandleToInterpreterMethInfoPtr(CORINFO_METHOD_HANDLE(pMD));
// Allocate a new jitInfo and also a new interpMethInfo.
if (interpMethInfo == NULL)
{
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)
{
#if INTERP_TRACING
if (s_TraceInterpreterJITTransitionFlag.val(CLRConfig::INTERNAL_TraceInterpreterJITTransition))
{
fprintf(GetLogFile(), "JITting method %s:%s.\n", md->m_pszDebugClassName, md->m_pszDebugMethodName);
}
#endif // INTERP_TRACING
CORJIT_FLAGS jitFlags(CORJIT_FLAGS::CORJIT_FLAG_MAKEFINALCODE);
NewHolder<COR_ILMETHOD_DECODER> pDecoder(NULL);
// Dynamic methods (e.g., IL stubs) do not have an IL decoder but may
// require additional flags. Ordinary methods require the opposite.
if (md->IsDynamicMethod())
{
jitFlags.Add(md->AsDynamicMethodDesc()->GetILStubResolver()->GetJitFlags());
}
else
{
COR_ILMETHOD_DECODER::DecoderStatus status;
pDecoder = new COR_ILMETHOD_DECODER(md->GetILHeader(TRUE),
md->GetMDImport(),
&status);
}
// This used to be a synchronous jit and could be made so again if desired,
// but using ASP.Net MusicStore as an example scenario the performance is
// better doing the JIT asynchronously. Given the not-on-by-default nature of the
// interpreter I didn't wring my hands too much trying to determine the ideal
// policy.
#ifdef FEATURE_TIERED_COMPILATION
GetAppDomain()->GetTieredCompilationManager()->AsyncPromoteMethodToTier1(md);
#else
#error FEATURE_INTERPRETER depends on FEATURE_TIERED_COMPILATION now
#endif
}
}
}
// 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()->GetRecursionLevel() == 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);
#if INTERP_TRACING
if (s_TraceInterpreterILFlag.val(CLRConfig::INTERNAL_TraceInterpreterIL))
{
fprintf(GetLogFile(), " Returning to method %s, stub num %d.\n", methName, m_methInfo->m_stubNum);
}
#endif // INTERP_TRACING
continue;
case CEE_CALLVIRT:
DoCall(/*virtualCall*/true);
#if INTERP_TRACING
if (s_TraceInterpreterILFlag.val(CLRConfig::INTERNAL_TraceInterpreterIL))
{
fprintf(GetLogFile(), " Returning to method %s, stub num %d.\n", methName, m_methInfo->m_stubNum);
}
#endif // INTERP_TRACING
continue;
// HARD
case CEE_CALLI:
CallI();
continue;
case CEE_RET:
if (m_methInfo->m_returnType == CORINFO_TYPE_VOID)
{
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--;
#if defined(_DEBUG) || defined(_AMD64_)
CorInfoType cit = OpStackTypeGet(m_curStackHt).ToCorInfoType();
#endif // _DEBUG || _AMD64_
#ifdef _DEBUG
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();
#if INTERP_TRACING
if (s_TraceInterpreterILFlag.val(CLRConfig::INTERNAL_TraceInterpreterIL))
{
fprintf(GetLogFile(), " Returning to method %s, stub num %d.\n", methName, m_methInfo->m_stubNum);
}
#endif // INTERP_TRACING
continue;
case CEE_CASTCLASS:
CastClass();
continue;
case CEE_ISINST:
IsInst();
continue;
case CEE_CONV_R_UN:
ConvRUn();
break;
case CEE_UNBOX:
Unbox();
continue;
case CEE_THROW:
Throw();
break;
case CEE_LDFLD:
LdFld();
continue;
case CEE_LDFLDA:
LdFldA();
continue;
case CEE_STFLD:
StFld();
continue;
case CEE_LDSFLD:
LdSFld();
continue;
case CEE_LDSFLDA:
LdSFldA();
continue;
case CEE_STSFLD:
StSFld();
continue;
case CEE_STOBJ:
StObj();
continue;
case CEE_CONV_OVF_I1_UN:
ConvOvfUn<INT8, SCHAR_MIN, SCHAR_MAX, /*TCanHoldPtr*/false, CORINFO_TYPE_INT>();
break;
case CEE_CONV_OVF_I2_UN:
ConvOvfUn<INT16, SHRT_MIN, SHRT_MAX, /*TCanHoldPtr*/false, CORINFO_TYPE_INT>();
break;
case CEE_CONV_OVF_I4_UN:
ConvOvfUn<INT32, INT_MIN, INT_MAX, /*TCanHoldPtr*/false, CORINFO_TYPE_INT>();
break;
case CEE_CONV_OVF_I8_UN:
ConvOvfUn<INT64, _I64_MIN, _I64_MAX, /*TCanHoldPtr*/true, CORINFO_TYPE_LONG>();
break;
case CEE_CONV_OVF_U1_UN:
ConvOvfUn<UINT8, 0, UCHAR_MAX, /*TCanHoldPtr*/false, CORINFO_TYPE_INT>();
break;
case CEE_CONV_OVF_U2_UN:
ConvOvfUn<UINT16, 0, USHRT_MAX, /*TCanHoldPtr*/false, CORINFO_TYPE_INT>();
break;
case CEE_CONV_OVF_U4_UN:
ConvOvfUn<UINT32, 0, UINT_MAX, /*TCanHoldPtr*/false, CORINFO_TYPE_INT>();
break;
case CEE_CONV_OVF_U8_UN:
ConvOvfUn<UINT64, 0, _UI64_MAX, /*TCanHoldPtr*/true, CORINFO_TYPE_LONG>();
break;
case CEE_CONV_OVF_I_UN:
if (sizeof(NativeInt) == 4)
{
ConvOvfUn<NativeInt, INT_MIN, INT_MAX, /*TCanHoldPtr*/true, CORINFO_TYPE_NATIVEINT>();
}
else
{
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: